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

@byelabel/utils

Package Overview
Dependencies
Maintainers
1
Versions
43
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@byelabel/utils

ByeLabel Utilities

Source
npmnpm
Version
2.3.3
Version published
Weekly downloads
191
23.23%
Maintainers
1
Weekly downloads
 
Created
Source

@byelabel/utils

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

pnpm add @byelabel/utils
# or
npm install @byelabel/utils

Imports

Subpath imports are tree-shakable and recommended:

import { isUUID } from '@byelabel/utils/validator';
import { AppError, throwAppError } from '@byelabel/utils/error';
import { logError } from '@byelabel/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 '@byelabel/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 '@byelabel/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 '@byelabel/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 '@byelabel/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 '@byelabel/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 '@byelabel/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 '@byelabel/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. The formatter functions take a single human-readable options object (no raw Intl.NumberFormatOptions pass-through).

import { number, toNumber, formatBytes, getRandom } from '@byelabel/utils';
// or: import * as number from '@byelabel/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, { locale: 'tr-TR' });                // '1.234,5'
number.format(1.5, { decimals: 3 });                       // '1.500'
number.format(1234567.89, { grouping: false });            // '1234567.89'

number.currency(1234.5);                                   // '$1,234.50'
number.currency(10, 'EUR', { locale: 'en-US' });           // '€10.00'
number.currency(10.5, 'USD', { decimals: false });         // '$11'
number.currency(10.5, 'USD', { symbol: false });           // '10.50'
number.currencySymbol('GBP', { locale: 'en-US' });         // '£'

number.percent(25, { locale: 'en-US' });                   // '25%'   (input is already a percentage)
number.percent(25.5, { decimals: 2 });                     // '25.50%'

number.short(1500);                                        // '1.5K'
number.short(2_500_000);                                   // '2.5M'
number.short(1500, { long: true });                        // '1.5 thousand'
number.short(1234, { decimals: 2 });                       // '1.23K'

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

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

Options accepted by each formatter:

FunctionOptions
format(n, options?)locale, decimals (exact fraction digits), grouping (default true)
currency(n, code?, options?)locale, decimals (default true — currency-natural digits; false forces 0), symbol (default true)
currencySymbol(code?, options?)locale
percent(n, options?)locale, decimals (exact fraction digits)
short(n, options?)locale, decimals (max fraction digits, default 1), long (long compact display, default false)

money

Currency-aware money formatting with built-in zero/three-decimal currency tables (e.g. JPY/KRW have 0, KWD/BHD have 3) and minor-unit (cents) conversion. Exposed flat (display, toMinor, toMajor, ...) and namespaced (money.display, ...) — the formatter is display instead of format to avoid colliding with number.format at the root.

import { display, toMajor, toMinor } from '@byelabel/utils/money';
// or: import { money } from '@byelabel/utils';

display(1234.5);                                  // '$1,234.50'
display(1500, { currency: 'JPY' });                   // '¥1,500'   (zero-decimal)
display(1.234, { currency: 'KWD', locale: 'en-US' }); // 'KWD 1.234' (three-decimal)
display(1234.5, { currency: 'EUR', locale: 'de-DE' });// '1.234,50 €'

display(1050, { minor: true });                   // '$10.50'   (1050 cents → $10.50)
display(10.5, { decimals: false });               // '$11'      (suppress fraction digits)
display(10.5, { symbol: false });                 // '10.50'    (no currency symbol)

toMinor(10.5);                                    // 1050   (USD → cents)
toMinor(1.234, 'KWD');                            // 1234   (3 decimals)
toMinor(1500, 'JPY');                             // 1500   (0 decimals, no scale)

toMajor(1050);                                    // 10.5
toMajor(1234, 'KWD');                             // 1.234

money.getCurrencyDecimals('USD');                 // 2
money.getCurrencyDecimals('JPY');                 // 0
money.getCurrencyDecimals('KWD');                 // 3

money.isZeroDecimalCurrency('JPY');               // true
money.isZeroDecimalCurrency('USD');               // false

display accepts MoneyDisplayOptions:

OptionDefaultNotes
currency'USD'ISO 4217 currency code
locale'en-US'BCP 47 locale tag
minorfalseTreat input as minor units (cents) and convert before formatting
decimalstrueWhen false, fraction digits are forced to 0
symboltrueWhen false, formats as plain decimal without the currency symbol

dto

Joi schema validation + a generic data-mapping wrapper.

import { validateSchema, phoneNumberValidation } from '@byelabel/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 '@byelabel/utils';
// or: import * as db from '@byelabel/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 (bounded retry-with-backoff), AMQP heartbeats, message streaming for payloads larger than messageMaxSize (default 5 MB), request/response over reply queues, and open-ended Readable replies via sendMessageForReplyStream for piping large payloads to clients without buffering.

import { rabbit } from '@byelabel/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 });

// request → streaming reply (Readable arrives as soon as the first chunk lands)
const pdf = await rabbit.sendMessageForReplyStream('file.download', { id });
pdf.pipe(res);

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

// listen + reply with a Readable (chunks are sent as they flow)
await rabbit.receiveMessage('file', async (params) => {
  return { payload: fs.createReadStream(`/storage/${params.id}.pdf`) };
});

// via the eventEmitter — payload can be a regular value or a Readable
eventEmitter.on('file.download', (params, reply) => {
  reply(null, fs.createReadStream(`/storage/${params.id}.pdf`));
});

// 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
nameRABBIT_NAMEprocess.env.NAME'microservice'
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)
heartbeatRABBIT_HEARTBEAT60 (seconds; 0 disables)
keepAliveRABBIT_KEEP_ALIVEtrue (set RABBIT_KEEP_ALIVE=false to disable TCP keepalive)
keepAliveDelayRABBIT_KEEP_ALIVE_DELAY10000 (ms; idle time before kernel sends keepalive probes)
maxRetriesRABBIT_MAX_RETRIES5 (additional attempts after the first)
retryDelayRABBIT_RETRY_DELAY500 (ms; initial backoff, doubles per attempt)
retryMaxDelayRABBIT_RETRY_MAX_DELAY5000 (ms; backoff cap)

The connection name advertised to RabbitMQ is ${name}-${pid}name is taken from the name option, falling back to RABBIT_NAME, then process.env.NAME, then 'microservice'. Each service shows up identifiable in the broker's connections view.

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

redis

import { redis } from '@byelabel/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 '@byelabel/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 '@byelabel/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 '@byelabel/utils/output';

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

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

config

Works in both Node and the browser. In Node it autoloads .env from process.cwd() and sets process.env.WORKING_PATH, ROOT_PATH, normalized ROUTE_PREFIX. In the browser the autoload is skipped (no filesystem) — loadEnv returns empty paths and reads ROUTE_PREFIX from whatever the bundler exposes.

import { loadEnv } from '@byelabel/utils/config';

// Node — read .env from cwd
const { rootPath, workingPath, routePrefix } = loadEnv();

// Node — named env file
loadEnv('.env.staging');

// Browser (Vite) — pass bundler-inlined envs
loadEnv('.env', { vars: import.meta.env });

// Anywhere — explicit override (file values still win in Node when both are present)
loadEnv('.env', { vars: { ROUTE_PREFIX: '/api/v1' } });

ROUTE_PREFIX is normalized: leading/trailing slashes are trimmed and collapsed, then a single leading / is added (api/v1//api/v1, ///''). In the browser, rootPath and workingPath are always '' — they have no meaning outside Node.

Repository

github.com/byelabel/helpers — see the workspace README for the companion @byelabel/react package.

License

MIT

Keywords

utils

FAQs

Package last updated on 28 May 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