🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@web-im/utils

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@web-im/utils

Web-IM Utilities

latest
Source
npmnpm
Version
1.0.8
Version published
Maintainers
1
Created
Source

@web-im/utils

Server-side helpers: validators, error/log/data shapes, encryption, Sequelize/RabbitMQ/Redis bindings.

pnpm add @web-im/utils
# or
npm install @web-im/utils

Imports

Subpath imports are tree-shakable and recommended:

import { isUUID } from '@web-im/utils/validator';
import { AppError, throwAppError } from '@web-im/utils/error';
import { logError } from '@web-im/utils/log';

The flat barrel re-exports everything. Modules whose names collide on connect / disconnect (db, rabbit, redis) are exposed as namespaces:

import { db, rabbit, redis, AppError, isUUID } from '@web-im/utils';

await db.connect();
await rabbit.connect();
await redis.connect();

Modules

validator

Type guards for runtime shape checks. All return boolean.

FunctionTrue when...
isBuffer(v)v instanceof Buffer
isSymbol(v)typeof symbol
isUndefined(v)undefined
isNull(v)null
isArray(v)array
isNonEmptyArray(v)array with at least one item
isBoolean(v)boolean
isNumber(v)typeof number (does not include numeric strings)
isInteger(v)numeric and integer
isNumeric(v)number or numeric string
isString(v)string
isNonEmptyString(v)string and not ''
isObject(v)plain object
isNonNullObject(v)plain object with at least one key
isEmpty(v) / isNotEmpty(v)empty by any of the above
isFunction(v)function or async function
isAsyncFunction(v)async function
isUUID(v)valid UUID string
isEmail(v)matches x@y
isPhoneNumber(v)matches a flexible international format
isURL(v)parseable http(s)://... URL
isTCNumber(v)valid Turkish national ID (11-digit, checksum)
import { isUUID, isEmail, isNonEmptyArray } from '@web-im/utils/validator';

isUUID('00000000-0000-4000-8000-000000000000'); // true
isEmail('hi@example.com');                       // true
isNonEmptyArray([1, 2, 3]);                      // true

error

import { AppError, ResponseError, throwAppError, getErrorString } from '@web-im/utils/error';

throw new AppError('Resource not found', 'NOT_FOUND', { id: '123' });

// auto-derives a code from the message
throw new AppError('Invalid token');           // code: 'INVALID_TOKEN'

// HTTP-flavored error
throw new ResponseError('Forbidden', 403);

// helper
throwAppError('Bad input', 'VALIDATION_ERROR', { field: 'email' });

// formatted "{message} - [{func} @ {file}:{line}:{col}]"
console.log(getErrorString(new Error('boom')));

data

import { paging, toResult, IList, IInfiniteList } from '@web-im/utils/data';

paging(100);              // { offset: 0, limit: 20, total: 100 }
paging(50, 100, 10);      // { offset: 0, limit: 10, total: 50 } (offset clamped)
paging(5000, 0, 5000);    // { offset: 0, limit: 1000, total: 5000 } (limit capped)

toResult(undefined, { id: 1 });   // { success: true, payload: { id: 1 } }
toResult(new AppError('boom'));   // { success: false, error }
toResult(new Error('boom'));      // wraps as AppError('SYSTEM_ERROR')

crypt

import {
  createHash, comparePassword, createRandomHash, createKey,
  createToken, getRandomNumber, createSimpleHash
} from '@web-im/utils/crypt';

createHash('hello');                     // HMAC-SHA256, secret = process.env.TOKEN_SECRET
createHash('hello', 'md5');              // simple md5
createHash('hello', 'sha256', 'mySec');  // explicit secret

comparePassword(savedHash, plain);       // boolean
createRandomHash(80);                    // hex string of length 80
createKey(6);                            // 6-digit numeric token, e.g. '482913'
createToken(5);                          // 5-char alphanumeric, e.g. 'A2K9F'
getRandomNumber(1, 100);                 // inclusive integer in [1, 100]

encryption

AES-256-GCM with auth tag, IV-prefixed binary output.

import { getKey, encrypt, decrypt } from '@web-im/utils/encryption';

const key = getKey('my-password', 'my-salt');
const ciphertext = encrypt('secret message', key);   // Buffer
const plaintext  = decrypt(ciphertext, key);         // 'secret message'

log

File-rotated logging into ${ROOT_PATH}/${LOGS_PATH || 'logs'}/YYYY-MM-DD-{type}.log (UTC). Optional admin-email digest via sendMessage('message.send', ...) to the rabbit queue when SEND_LOGS=true.

import { logInfo, logWarning, logError, writeToFile, showMessages } from '@web-im/utils/log';

await logInfo('app started');
await logWarning('cache miss', true);          // also console.log
await logError('Login failed', err, { userId });

writeToFile('boot complete', 'info');          // sync write, returns { path, name, fullPath }

showMessages([
  'PORT  : 3000',
  'NODE  : 20.x'
]);

number

Number formatting helpers built on Intl.NumberFormat. Exposed flat (toNumber, formatBytes, getRandom, ...) and namespaced (number.format, number.currency, number.short, number.percent) — the namespace form is recommended for the generic names.

import { number, toNumber, formatBytes, getRandom } from '@web-im/utils';
// or: import * as number from '@web-im/utils/number';

toNumber('1.236', 2);                   // 1.24 (rounded to 2 dp)
toNumber('abc');                        // 0 (non-numeric → 0)

number.format(1234567.89);              // '1,234,567.89'
number.format(1234.5, undefined, 'tr-TR'); // '1.234,5'
number.format(1.5, { minimumFractionDigits: 3 }); // '1.500'

number.currency(1234.5);                // '$1,234.50'
number.currency(10, 'EUR', 'en-US');    // '€10.00'
number.currencySymbol('GBP', 'en-US');  // '£'

number.percent(25, 'en-US');            // '25%'  (input is already a percentage)

number.short(1500);                     // '1.5K'
number.short(2_500_000);                // '2.5M'

getRandom(1, 10);                       // integer in [1, 10] inclusive

formatBytes(2048);                      // '2.0 KB'
formatBytes(5 * 1024 * 1024);           // '5.0 MB'

dto

Joi schema validation + a generic data-mapping wrapper.

import { validateSchema, phoneNumberValidation } from '@web-im/utils/dto';
import joi from 'joi';

const schema = joi.object({ email: joi.string().email().required() });

// sync — throws AppError('VALIDATION_ERROR') with details
const value = validateSchema(schema, { email: 'a@b.co' });

// async
const value2 = await validateSchema(schema, { email: 'a@b.co' }, true);

// custom Joi rule
joi.string().custom(phoneNumberValidation);

db

Sequelize wrapper with reconnection, replica support, and a typed filter/sort/paginate DSL.

import { db } from '@web-im/utils';
// or: import * as db from '@web-im/utils/db';

await db.connect();                                      // env-only
await db.connect({ name: 'analytics', debug: true });    // override per-field
const sequelize = db.sequelize();

// validate config without connecting (throws AppError if host/name/user missing)
db.checkDbConfig();
db.checkDbConfig({ host: 'db.local', name: 'app', user: 'app' });

// safe SQL string
db.escapeString("o'reilly", true);   // "'o''reilly'"

// build a WHERE from a structured filter
const where = await db.filtering([
  ['email', 'contains', 'gmail'],
  ['status', 'isAnyOf', ['active', 'pending']]
]);

// Joi schema for user-supplied filter input
const schema = db.JoiFilter('email', 'string');

// paginated list
const page = await db.getList<User>({
  offset: 0,
  limit: 20,
  sqlBody: async (alias, withColumns) => `SELECT ... FROM users AS "${alias}"`,
  dataModel: rows => rows
});

await db.disconnect();

connect(options?) and checkDbConfig(options?) accept an optional IDbOptions:

OptionEnv fallbackDefault
engineDB_ENGINE'postgresql'
hostDB_HOST— (required)
portDB_PORT5432
nameDB_NAME— (required)
userDB_USER— (required)
passDB_PASS''
useSslDB_USE_SSL === 'true'false
prefixDB_PREFIX
skipSyncDB_SKIP_SYNC === 'true'false
forceSyncDB_FORCE_SYNC === 'true'false
debugDB_DEBUG === 'true'false

checkDbConfig throws AppError (MISSING_DB_HOST, MISSING_DB_NAME, MISSING_DB_USER) when a required field is missing. Comma-separate host/port/user/pass for read replicas.

rabbit

RabbitMQ client with auto-reconnect, message streaming for payloads larger than messageMaxSize (default 5 MB), and request/response over reply queues.

import { rabbit } from '@web-im/utils';

await rabbit.connect();                                            // env-only
await rabbit.connect({ host: 'mq.local', namespace: 'svc' });      // override per-field

// validate config without connecting (throws AppError if host missing)
rabbit.checkRabbitConfig();

// fire-and-forget
await rabbit.sendMessage('log.event', { action: 'login', user_id: id });

// request → reply
const result = await rabbit.sendMessageForReply('account.find', { id });

// listen
await rabbit.receiveMessage('account', async (params) => {
  return { success: true, payload: { /* ... */ } };
});

// pub/sub
await rabbit.publishMessage('user', 'updated', { id });
await rabbit.receivePublishedMessage('user', '*', payload => { /* ... */ });

// hook all queues + exchanges to the eventEmitter
await rabbit.listen(true);

connect(options?) and checkRabbitConfig(options?) accept an optional IRabbitOptions:

OptionEnv fallbackDefault
protocolRABBIT_PROTOCOL'amqp'
hostRABBIT_HOST— (required)
portRABBIT_PORT5672
userRABBIT_USER
passRABBIT_PASS
vhostRABBIT_VHOST
namespaceRABBIT_NAMESPACE
messageMaxSizeRABBIT_MESSAGE_MAX_SIZE5000000
timeoutRABBIT_TIMEOUT30/60 (per call)
queuesRABBIT_QUEUES— (used by listen)
exchangesRABBIT_EXCHANGES— (used by listen)

checkRabbitConfig throws AppError('MISSING_RABBIT_HOST') when host is missing.

redis

import { redis } from '@web-im/utils';

const client = await redis.connect();                                  // env-only
const client2 = await redis.connect({ host: 'cache.local', db: 1 });   // override

// validate config without connecting (throws AppError if host missing)
redis.checkRedisConfig();

await client.set('key', 'value');
await redis.disconnect();

connect(options?) and checkRedisConfig(options?) accept an optional IRedisOptions:

OptionEnv fallback
hostREDIS_HOST (required)
portREDIS_PORT
userREDIS_USER
passREDIS_PASS
dbREDIS_DB

checkRedisConfig throws AppError('MISSING_REDIS_HOST') when host is missing. Comma-separate host/port/user/pass to enable cluster mode.

events

Singleton wildcard EventEmitter2 plus an attachEvents helper that wires events/**/*.js files at a path.

import eventEmitter, { attachEvents, eventResult } from '@web-im/utils/events';

eventEmitter.on('user.created', (payload, cb) => {
  cb(null, { id: payload.id });
});

await attachEvents(__dirname);   // loads ./events/*.js based on EVENTS env

jobs

Loads jobs/**/*.js and runs each export inside a fresh vm context.

import { runJobs } from '@web-im/utils/jobs';

await runJobs(__dirname, { db, rabbit });

Filter set via JOBS=* or JOBS=foo,bar.run.

output

Express middleware helper. Wraps a callback into a structured { success, payload | error, transaction } response, with optional rabbit logging on LOG=request,response.

import { toResponse, responseData, IRequest } from '@web-im/utils/output';

app.post('/login', (req, res) => {
  toResponse(req, res, async (transactionId) => {
    return { token: '...' };
  });
});

// manual shape
res.json(responseData(null, { token: '...' }));

config

Imported for side effects: loads .env from the workspace root, sets process.env.WORKING_PATH, ROOT_PATH, normalizes ROUTE_PREFIX. Imported automatically by other modules — you rarely import it directly.

Repository

github.com/morsaken/web-im-helpers — see the workspace README for the companion @web-im/react package.

License

MIT

Keywords

utils

FAQs

Package last updated on 26 Apr 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts