npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rshval/back-kit

v1.3.1

Published

Backend toolkit of service factories (create*/build*), validators and Node.js API utilities with DI-friendly patterns

Downloads

377

Readme

@rshval/back-kit

Public npm package @rshval/back-kit with server-side utilities for Node.js/TypeScript projects.

This repository is used for the author's internal projects. Any third-party usage is at your own risk.

Installation

npm i @rshval/back-kit

The package is ESM-only ("type": "module"). For CommonJS, use dynamic import().

Quick navigation


API and usage examples

Below are the public exports from the package (via src/index.ts) with short examples. For key APIs, there are separate production examples that show practical integration patterns for real services.

1) Email

createMailOptions({ from, to, subject, text })

Creates a nodemailer mail options object. Supports to as a string (regular send) or a form object (MailOptionsBody) for requests/feedback scenarios.

import { createMailOptions } from '@rshval/back-kit';

const options = createMailOptions({
  from: '[email protected]',
  to: '[email protected]',
  subject: 'Hello',
  text: 'Welcome!',
});

sendEmailWithConfig({ nodemailerConfig, to, subject, text })

Creates a nodemailer transport, sends an email, and returns a string with the send result.

import { sendEmailWithConfig } from '@rshval/back-kit';

await sendEmailWithConfig({
  nodemailerConfig: {
    host: 'smtp.example.com',
    port: 465,
    secure: true,
    auth: { user: '[email protected]', pass: '***' },
  },
  to: '[email protected]',
  subject: 'Reset password',
  text: 'Code: 123456',
});

Production example:

import { buildSendEmail } from '@rshval/back-kit';

const sendEmailWithConfig = buildSendEmail(runtimeConfig);
await sendEmailWithConfig(to, subject, text);

2) Config and JWT

getBaseUrlByConfig(config, baseUrl?)

Builds a base URL from app config. In development, adds :port.

import { getBaseUrlByConfig } from '@rshval/back-kit';

const base = getBaseUrlByConfig(
  {
    NODE_ENV: 'development',
    server: { domain: 'localhost', port: 3000 },
    jwt: {},
  },
  '/api',
);
// //localhost:3000/api

createTokenByConfig({ config, user, expiresIn })

Creates a JWT token based on config.jwt.JWT_KEY (or JWT_KEY_NO_ENV).

import { createTokenByConfig } from '@rshval/back-kit';

const token = createTokenByConfig({
  config: {
    server: { domain: 'example.com' },
    jwt: { JWT_KEY: 'super-secret' },
  },
  user: { _id: '64a...' },
  expiresIn: '7d',
});

3) Helper utilities

createPinCode(min?, max?)

Generates a random numeric PIN in range (10000..99990 by default).

import { createPinCode } from '@rshval/back-kit';

const pin = createPinCode();

getIp(req)

Attempts to detect client IP from req.ip, sockets, and x-forwarded-for.

import { getIp } from '@rshval/back-kit';

const ip = await getIp(req);

translitUrl(str)

Transliterates a string into a URL-friendly slug (-, lower-case).

import { translitUrl } from '@rshval/back-kit';

const slug = translitUrl('Page example');
// primer-stranicy

4) Validation

patternEmail()

Returns a RegExp for email validation.

patternPassword()

Returns a RegExp for password validation (at least 8 chars, letters + digits).

isValidEmail(val)

Checks whether an email is valid.

isValidPhoneNumber(val)

Validates a phone number via libphonenumber-js.

isValidCode(code, length)

Checks code length.

isEmpty(val)

Checks for an empty string (trim) or an empty object.

import {
  patternEmail,
  patternPassword,
  isValidEmail,
  isValidPhoneNumber,
  isValidCode,
  isEmpty,
} from '@rshval/back-kit';

const emailRegex = patternEmail();
const passwordRegex = patternPassword();

isValidEmail('[email protected]'); // true
isValidPhoneNumber('+79991234567'); // true/false
isValidCode('123456', 6); // true
isEmpty('   '); // true
isEmpty({}); // true

5) Builders (config-bound)

buildGetBaseUrl(config)

Returns a function (baseUrl?) => string.

buildCreateToken(config)

Returns a JWT creator function with pre-bound config.

buildSendEmail(nodemailerConfig)

Returns an email sender function with pre-bound SMTP config.

import {
  buildGetBaseUrl,
  buildCreateToken,
  buildSendEmail,
} from '@rshval/back-kit';

const getBaseUrl = buildGetBaseUrl({
  NODE_ENV: 'production',
  server: { domain: 'example.com' },
  jwt: { JWT_KEY: 'secret' },
});

const createToken = buildCreateToken({
  server: { domain: 'example.com' },
  jwt: { JWT_KEY: 'secret' },
});

const sendEmail = buildSendEmail({
  host: 'smtp.example.com',
  port: 465,
  secure: true,
  auth: { user: '[email protected]', pass: '***' },
});

6) Database

startMongoDatabase(options)

Starts MongoDB connection via mongoose, logs statuses, and supports connection retries.

import { startMongoDatabase } from '@rshval/back-kit';

await startMongoDatabase({
  config: {
    name: 'main',
    connect: process.env.MONGO_URI,
    params: {},
  },
  logger: console,
});

Production example:

await startMongoDatabase({
  config: config.database,
  logger,
  setCacheStatus: (status) =>
    cache.set(cacheIdDatabase, { status }, undefined, undefined, cacheTtl30m),
});

7) Cache

createCacheService(options?)

Creates an LRU cache service with methods:

  • get(key)
  • set(key, data, compareKey?, compareValue?, ttlMs?)
  • delete(key)
  • getId(val)
  • keys()
  • clear()
  • entries() (if exposeEntries: true)
  • values() (if exposeValues: true)
import { createCacheService } from '@rshval/back-kit';

const cache = createCacheService({ ttl: 60_000 });
const key = cache.getId({ service: 'users', page: 1 });
await cache.set(key, [{ s: 'state', data: [1, 2, 3] }]);
const value = await cache.get(key);

createCacheMiddleware({ cache, ... })

Creates a middleware wrapper over the cache service with methods:

  • get(id)
  • set(id, data, expDataTime)
  • del(id)
  • delByPrefix(prefix) (if includeDelByPrefix: true)
import { createCacheService, createCacheMiddleware } from '@rshval/back-kit';

const cache = createCacheService({ supportTtlInSet: true });
const cm = createCacheMiddleware({
  cache,
  passTtlToCacheSet: true,
  includeDelByPrefix: true,
});

await cm.set('users:list', [{ id: 1 }], 60_000);
const cached = await cm.get('users:list');
await cm.delByPrefix?.('users:');

Production example:

const cache = createCacheService({ supportTtlInSet: true });
const cacheMiddleware = createCacheMiddleware({
  cache,
  passTtlToCacheSet: true,
  includeDelByPrefix: true,
});

export const { get, set, del } = cacheMiddleware;
export const delByPrefix = cacheMiddleware.delByPrefix!;

8) Logging

createLoggerService({ rootdir? })

Returns a logger factory. For each namespace (std) it writes:

  • logs/<std>/stdout.log
  • logs/<std>/stderr.log
import { createLoggerService } from '@rshval/back-kit';

const makeLogger = createLoggerService({ rootdir: process.cwd() });
const logger = makeLogger('api');
logger.log('started');

Production example:

const loggerService = createLoggerService();
const logger = loggerService('api');

9) WebSocket client

createSocketClientService(options)

Creates a socket.io client service with methods:

  • doSocketClient() — start connection
  • getSocketClient() — return current instance
import { createSocketClientService } from '@rshval/back-kit';

const ws = createSocketClientService({
  wsName: 'worker',
  socketBase: 'https://example.com',
  logger: console,
  runWorkers: () => {
    // jobs
  },
});

ws.doSocketClient();

Production example:

const socketClientService = createSocketClientService({
  wsName: config.ws.name,
  socketBase: SOCKET_BASE,
  logger,
  runWorkers: socketClientWorkers,
  socketClientInterval: config.settings.socketClientInterval,
});

socketClientService.doSocketClient();

10) Paymaster

createPaymasterService({ paymaster, clientServer, serverBaseUrl })

Creates a service with methods:

  • createPaymentLink({...}) — build payment URL
  • validateCallback(payload, rawBody?) — verify callback signature
  • parseCallback(payload) — normalize callback into a convenient object
  • syncPaymentStatus({ paymentNo, merchantId? }) — safe PSP status sync (returns null on any API error), normalized status: paid | cancelled | pending | refunded
  • createRefund({ paymentNo, amount, currency, reason, merchantId? }) — request refund via provider API
import { createPaymasterService } from '@rshval/back-kit';

const paymaster = createPaymasterService({
  paymaster: {
    merchantId: 'merchant-id',
    secretKey: 'secret',
    checkoutUrl: 'https://paymaster.ru/Payment/Init',
    checkSignature: true,
    statusApiUrl: process.env.PAYMASTER_STATUS_API_URL,
    statusApiToken: process.env.PAYMASTER_STATUS_API_TOKEN,
    statusApiTimeoutMs: Number(
      process.env.PAYMASTER_STATUS_API_TIMEOUT_MS || 8000,
    ),
    refundApiUrl: process.env.PAYMASTER_REFUND_API_URL,
    refundApiToken: process.env.PAYMASTER_REFUND_API_TOKEN,
    refundApiTimeoutMs: Number(
      process.env.PAYMASTER_REFUND_API_TIMEOUT_MS || 8000,
    ),
  },
  clientServer: 'https://site.example.com',
  serverBaseUrl: 'https://api.example.com',
});

const link = paymaster.createPaymentLink({
  requestId: 'order_123',
  amount: 1499,
  description: 'Order #123',
});

const synced = await paymaster.syncPaymentStatus({
  paymentNo: link.paymentNo,
});

if (!synced) {
  // Safe fallback: keep local status unchanged or schedule retry
} else {
  // status: 'paid' | 'cancelled' | 'pending' | 'refunded'
  console.log(synced.status, synced.statusRaw);
}

const refund = await paymaster.createRefund({
  paymentNo: link.paymentNo,
  amount: 1499,
  reason: 'Customer requested cancellation',
  // optional
  currency: 'RUB',
});

if (!refund.success) {
  console.error(refund.error, refund.httpStatus, refund.statusRaw);
}

Status API environment variables:

  • PAYMASTER_STATUS_API_URL
  • PAYMASTER_STATUS_API_TOKEN
  • PAYMASTER_STATUS_API_TIMEOUT_MS

Refund API environment variables:

  • PAYMASTER_REFUND_API_URL
  • PAYMASTER_REFUND_API_TOKEN
  • PAYMASTER_REFUND_API_TIMEOUT_MS

Production example:

export const paymasterService = createPaymasterService({
  paymaster: config.platforms.paymaster,
  clientServer: config.clientServer,
  serverBaseUrl: config.server.baseUrl,
});

11) Mail templates

createMailTemplateService({ findTemplate, sendEmail })

Creates an email template service with methods:

  • extractVariables(template) — list of {{variables}}
  • renderTemplate(template, context?) — render subject/body
  • sendByKey({ key, to, context? }) — find template, render, and send
import { createMailTemplateService } from '@rshval/back-kit';

const mailTemplates = createMailTemplateService({
  findTemplate: async ({ key }) =>
    key === 'welcome'
      ? { subject: 'Hi, {{user.name}}', bodyText: 'Hello {{user.name}}!' }
      : null,
  sendEmail: async (to, subject, text) => {
    console.log('send', to, subject, text);
  },
});

await mailTemplates.sendByKey({
  key: 'welcome',
  to: '[email protected]',
  context: { user: { name: 'Alex' } },
});

Production example:

const sendEmailWithConfig = buildSendEmail(runtimeConfig);

export const mailTemplateService = createMailTemplateService({
  findTemplate: ({ key }) => MailTemplate.findOne({ key, isActive: true }),
  sendEmail: sendEmailWithConfig,
});

12) Audit changes

buildAuditChanges({ beforeData, afterData, fieldsToCheck, protectedFields? })

Compares before/after data and returns an array of changes for selected fields.

createAuditLog({ ..., save })

Creates an audit record via provided save function. Returns null when no changes exist.

import { buildAuditChanges, createAuditLog } from '@rshval/back-kit';

const changes = buildAuditChanges({
  beforeData: { name: 'Old', role: 'user' },
  afterData: { name: 'New', role: 'user' },
  fieldsToCheck: ['name', 'role'],
  protectedFields: ['role'],
});

await createAuditLog({
  entityType: 'user',
  entityId: '507f1f77bcf86cd799439011',
  action: 'update',
  changedBy: '507f191e810c19729de860ea',
  changes,
  save: async (payload) => payload,
});

Production example:

await createAuditLog({
  entityType,
  entityId,
  action,
  changedBy,
  changes,
  meta,
  save: async (payload) => AuditLog.create(payload),
});

13) Seed functions

createSeedFunctions({ cache, retryDelayMs? })

Returns methods:

  • checkDatabaseIsConnected() — checks DB state via cache
  • setSeedData(SeedModel, arr) — inserts seed data after connection check
import { createSeedFunctions, createCacheService } from '@rshval/back-kit';

const cache = createCacheService();
const { setSeedData } = createSeedFunctions({ cache });

// await setSeedData(UserModel, [{ name: 'Admin' }])

Production example:

export const { checkDatabaseIsConnected, setSeedData } = createSeedFunctions({
  cache,
});

14) API client

createApiService({ userAgent, logger? })

Returns an HTTP client with methods:

  • get(path, token?)
  • getXml(path, token?)
  • del(path, token?)
  • post(path, data, token?, xml?, contentType?)
  • put(path, data, token)
import { createApiService } from '@rshval/back-kit';

const api = createApiService({
  userAgent: 'my-service/1.0.0',
  logger: console,
});

const users = await api.get('https://api.example.com/users');
const created = await api.post('https://api.example.com/users', {
  name: 'Alex',
});

Production example:

const apiService = createApiService({
  userAgent: config.api.userAgent,
  logger,
});

export const { get, getXml, del, post, put } = apiService;

Token type is also exported:

import type { Token } from '@rshval/back-kit';

15) Other exported utilities

add(a, b)

Returns the sum of two numbers.

test2()

Logs 99 and returns 8.

import { add, test2 } from '@rshval/back-kit';

add(2, 3); // 5
test2(); // 8

Preparing for npm publication

See detailed guide: docs/publishing.md.

Short wording for npm description:

  • A set of service factories (create*, build*) and ready-to-use utilities/validators.
  • DI-friendly API: dependencies are injected externally (logger, cache, save, findTemplate).
  • Important behavior options (supportTtlInSet, passTtlToCacheSet, includeDelByPrefix) should be documented near examples.

Development (standalone)

npm install
npm run build

Exports

  • ESM: dist/index.js
  • Types: dist/index.d.ts

15) HTTP contracts

@rshval/back-kit/http-contracts exports transport-agnostic request contracts, typed result wrappers, and adapters for Fetch and CapacitorHttp.

Detailed structure + migration guide: docs/http-contracts-migration-guide.md. Pre-migration compatibility notes: docs/http-contracts-compatibility.md.

import {
  AuthHeaderBuilder,
  createFetchAdapter,
  createZodValidator,
  fail,
  isFail,
  isOk,
  mapResult,
  normalizeHttpError,
  normalizeTransportError,
  ok,
  requestApi,
  unwrapOr,
  type ApiResult,
} from '@rshval/back-kit/http-contracts';
import { z } from 'zod';

const auth = new AuthHeaderBuilder(); // default: Authorization: Token <jwt>
const fetchAdapter = createFetchAdapter();

const result: ApiResult<{ id: string }> = await requestApi(fetchAdapter, {
  url: 'https://api.example.com/me',
  method: 'GET',
  headers: auth.apply({}, '<jwt>'),
  timeoutMs: 10_000,
  parseAs: 'json',
});

if (isFail(result)) {
  throw normalizeHttpError(result.error);
}

const id = unwrapOr(
  mapResult(result, (data) => data?.id ?? 'unknown'),
  'unknown',
);

const validateUserDto = createZodValidator(
  z.object({
    id: z.string(),
  }),
);

const dtoResult = validateUserDto.validate({ id: 42 });

const transportError = normalizeTransportError(
  new TypeError('Failed to fetch'),
  {
    source: 'fetch',
  },
);

Features:

  • shared ApiResult<TData, TError> contract with helpers: ok, fail, isOk, isFail, mapResult, unwrapOr;
  • unified error taxonomy: transport | validation | domain | unknown;
  • zod validator adapters via createZodValidator and createZodValidatorFactory;
  • transport-normalization helper normalizeTransportError with stable timeout/abort/network codes;
  • AuthHeaderBuilder with default Token scheme for backward compatibility;
  • Fetch + CapacitorHttp adapters with unified abort + timeout strategy;
  • automatic parsing for JSON/XML/text responses (parseAs: 'auto' | 'json' | 'xml' | 'text').

Migration helpers for downstream repositories are available in @rshval/back-kit/http-contracts/migration:

  • runHttpContractsGuardCodemod(source) — rewrites result.ok / !result.ok checks to isOk(result) / isFail(result) and syncs imports.
  • httpContractsMigrationEslintPlugin.rules["no-result-ok"] — blocks new result.ok usages and offers autofix.

Node example (scripts/migrate-http-contracts.mjs):

import { readFileSync, writeFileSync } from 'node:fs';
import { runHttpContractsGuardCodemod } from '@rshval/back-kit/http-contracts/migration';

const file = 'src/user-api.ts';
const source = readFileSync(file, 'utf8');
const migrated = runHttpContractsGuardCodemod(source);

if (migrated.changed) {
  writeFileSync(file, migrated.code);
  console.log(`updated ${file}: ${migrated.replacements} replacements`);
}

Svelte + ESLint example (eslint.config.js):

import { httpContractsMigrationEslintPlugin } from '@rshval/back-kit/http-contracts/migration';

export default [
  {
    files: ['src/**/*.ts', 'src/**/*.svelte'],
    plugins: {
      'http-contracts-migration': httpContractsMigrationEslintPlugin,
    },
    rules: {
      'http-contracts-migration/no-result-ok': 'error',
    },
  },
];

16) Shared compatibility helper (deprecated aliases)

Use resolveDeprecatedAliasField to support DTO migrations where canonical fields replace legacy aliases without duplicating fallback logic.

import { resolveDeprecatedAliasField } from '@rshval/back-kit';

const status = resolveDeprecatedAliasField({
  source: payload,
  canonicalKey: 'paymentStatus',
  deprecatedAliases: ['payment_status', 'status'],
  removalDate: '2026-12-31',
  warningMessage: '[payments] Deprecated status alias used.',
});

const normalizedStatus = String(status.value ?? '').trim();

Deprecation/removal policy:

  • canonical key always has priority if both canonical and alias are present;
  • deprecated aliases remain readable until removalDate;
  • in development (NODE_ENV === 'development') helper logs a warn-once message for each canonical + alias + removalDate combination;
  • in production helper does not emit warnings.

Migration example (payment_status -> paymentStatus):

const resolved = resolveDeprecatedAliasField({
  source: body,
  canonicalKey: 'paymentStatus',
  deprecatedAliases: ['payment_status'],
  removalDate: '2026-12-31',
  warningMessage: '[paymaster] Deprecated status alias used.',
});

const paymentStatus = String(resolved.value ?? '').toLowerCase();