
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
@byelabel/utils
Advanced tools
Server-side helpers: validators, error/log/data shapes, encryption, Sequelize/RabbitMQ/Redis bindings.
pnpm add @byelabel/utils
# or
npm install @byelabel/utils
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();
validatorType guards for runtime shape checks. All return boolean.
| Function | True 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
errorimport { 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')));
dataimport { 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')
cryptimport {
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(); // 6-char mixed-case token, no ambiguous chars (0/1/l/O), e.g. 'k7LqRt'
createToken(8, 'usr_'); // custom length + prefix, e.g. 'usr_R3x9Pq2t'
getRandomNumber(1, 100); // inclusive integer in [1, 100]
encryptionAES-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'
logFile-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'
]);
numberNumber 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:
| Function | Options |
|---|---|
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) |
moneyCurrency-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:
| Option | Default | Notes |
|---|---|---|
currency | 'USD' | ISO 4217 currency code |
locale | 'en-US' | BCP 47 locale tag |
minor | false | Treat input as minor units (cents) and convert before formatting |
decimals | true | When false, fraction digits are forced to 0 |
symbol | true | When false, formats as plain decimal without the currency symbol |
dtoJoi 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);
dbSequelize 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:
| Option | Env fallback | Default |
|---|---|---|
engine | DB_ENGINE | 'postgresql' |
host | DB_HOST | — (required) |
port | DB_PORT | 5432 |
name | DB_NAME | — (required) |
user | DB_USER | — (required) |
pass | DB_PASS | '' |
useSsl | DB_USE_SSL === 'true' | false |
prefix | DB_PREFIX | — |
skipSync | DB_SKIP_SYNC === 'true' | false |
forceSync | DB_FORCE_SYNC === 'true' | false |
debug | DB_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.
rabbitRabbitMQ 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:
| Option | Env fallback | Default |
|---|---|---|
name | RABBIT_NAME | process.env.NAME → 'microservice' |
protocol | RABBIT_PROTOCOL | 'amqp' |
host | RABBIT_HOST | — (required) |
port | RABBIT_PORT | 5672 |
user | RABBIT_USER | — |
pass | RABBIT_PASS | — |
vhost | RABBIT_VHOST | — |
namespace | RABBIT_NAMESPACE | — |
messageMaxSize | RABBIT_MESSAGE_MAX_SIZE | 5000000 |
timeout | RABBIT_TIMEOUT | 30/60 (per call) |
queues | RABBIT_QUEUES | — (used by listen) |
exchanges | RABBIT_EXCHANGES | — (used by listen) |
heartbeat | RABBIT_HEARTBEAT | 60 (seconds; 0 disables) |
keepAlive | RABBIT_KEEP_ALIVE | true (set RABBIT_KEEP_ALIVE=false to disable TCP keepalive) |
keepAliveDelay | RABBIT_KEEP_ALIVE_DELAY | 10000 (ms; idle time before kernel sends keepalive probes) |
maxRetries | RABBIT_MAX_RETRIES | 5 (additional attempts after the first) |
retryDelay | RABBIT_RETRY_DELAY | 500 (ms; initial backoff, doubles per attempt) |
retryMaxDelay | RABBIT_RETRY_MAX_DELAY | 5000 (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.
redisimport { 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:
| Option | Env fallback |
|---|---|
host | REDIS_HOST (required) |
port | REDIS_PORT |
user | REDIS_USER |
pass | REDIS_PASS |
db | REDIS_DB |
checkRedisConfig throws AppError('MISSING_REDIS_HOST') when host is missing. Comma-separate host/port/user/pass to enable cluster mode.
eventsSingleton 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
jobsLoads 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.
outputExpress 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: '...' }));
configWorks 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.
github.com/byelabel/helpers — see the workspace README for the companion @byelabel/react package.
MIT
FAQs
ByeLabel Utilities
The npm package @byelabel/utils receives a total of 36 weekly downloads. As such, @byelabel/utils popularity was classified as not popular.
We found that @byelabel/utils demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

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.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.