Important: This documentation covers Yarn 1 (Classic).
For Yarn 2+ docs and migration guide, see yarnpkg.com.

Package detail

metautil

metarhia1.3kMIT5.2.5TypeScript support: included

Metarhia utilities

node.js, metarhia, util

readme

Metarhia utilities

ci status snyk npm version npm downloads/month npm downloads license

Usage

  • Install: npm install metautil
  • Require: const metautil = require('metautil')

Async utilities

  • toBool = [() => true, () => false]
    • Example: const created = await mkdir(path).then(...toBool);
  • timeout(msec: number, signal?: AbortSignal): Promise<void>
  • delay(msec: number, signal?: AbortSignal): Promise<void>
  • timeoutify(promise: Promise<unknown>, msec: number): Promise<unknown>
  • collect(keys: Array<string>, options?: CollectorOptions): Collector
    • options.exact?: boolean
    • options.timeout?: number
    • options.reassign?: boolean

Class Collector

Async collection is an utility to collect needed keys and signalize on done.

  • constructor(keys: Array<string>, options?: CollectorOptions)
    • options.exact?: boolean
    • options.timeout?: number
    • options.reassign?: boolean
    • options.defaults?: object
    • options.validate?: (data: Record<string, unknown>) => unknown
  • set(key: string, value: unknown): void
  • wait(key: string, fn: AsyncFunction | Promise<unknown>, ...args?: Array<unknown>): void
  • take(key: string, fn: Function, ...args?: Array<unknown>): void
  • collect(sources: Record<string, Collector>): void
  • fail(error: Error): void
  • abort(): void
  • then(onFulfilled: Function, onRejected?: Function): Promise<unknown>
  • done: boolean
  • data: Dictionary
  • keys: Array<string>
  • count: number
  • exact: boolean
  • timeout: number
  • defaults: object
  • reassign: boolean
  • validate?: (data: Record<string, unknown>) => unknown
  • signal: AbortSignal

Collect keys with .set method:

const ac = collect(['userName', 'fileName']);

setTimeout(() => ac.set('fileName', 'marcus.txt'), 100);
setTimeout(() => ac.set('userName', 'Marcus'), 200);

const result = await ac;
console.log(result);

Collect keys with .wait method from async or promise-returning function:

const ac = collect(['user', 'file']);

ac.wait('file', getFilePromisified, 'marcus.txt');
ac.wait('user', getUserPromisified, 'Marcus');

try {
  const result = await ac;
  console.log(result);
} catch (error) {
  console.error(error);
}

Collect keys with .take method from callback-last-error-first function:

const ac = collect(['user', 'file'], { timeout: 2000, exact: false });

ac.take('file', getFileCallback, 'marcus.txt');
ac.take('user', getUserCallback, 'Marcus');

const result = await ac;

Set default values ​​for unset keys using the options.defaults argument:

const defaults = { key1: 'sub1', key2: 'sub1' };

const dc = collect(['key1', 'key2'], { defaults, timeout: 2000 });
dc.set('key2', 'sub2');

const result = await dc;

Compose collectors (collect subkeys from multiple sources):

const dc = collect(['key1', 'key2', 'key3']);
const key1 = collect(['sub1']);
const key3 = collect(['sub3']);
dc.collect({ key1, key3 });
const result = await dc;

Complex example: compare Promise.allSettled + Promise.race vs Collector in next two examples:

// Collect 4 keys from different contracts with Promise.allSettled + Promise.race

const promise1 = new Promise((resolve, reject) => {
  fs.readFile('README.md', (err, data) => {
    if (err) return void reject(err);
    resolve(data);
  });
});
const promise2 = fs.promises.readFile('README.md');
const url = 'http://worldtimeapi.org/api/timezone/Europe';
const promise3 = fetch(url).then((data) => data.json());
const promise4 = new Promise((resolve) => {
  setTimeout(() => resolve('value4'), 50);
});
const timeout = new Promise((resolve, reject) => {
  setTimeout(() => reject(new Error('Timed out')), 1000);
});
const data = Promise.allSettled([promise1, promise2, promise3, promise4]);
try {
  const keys = await Promise.race([data, timeout]);
  const [key1, key2, key3, key4] = keys.map(({ value }) => value);
  const result = { key1, key2, key3, key4 };
  console.log(result);
} catch (err) {
  console.log(err);
}

Compare with:

// Collect 4 keys from different contracts with Collector

const dc = collect(['key1', 'key2', 'key3', 'key4'], { timeout: 1000 });

dc.take('key1', fs.readFile, 'README.md');
dc.wait('key2', fs.promises.readFile, 'README.md');
const url = 'http://worldtimeapi.org/api/timezone/Europe';
dc.wait(
  'key3',
  fetch(url).then((data) => data.json()),
);
setTimeout(() => dc.set('key4', 'value4'), 50);

try {
  const result = await dc;
  console.log(result);
} catch (err) {
  console.log(err);
}

Crypto utilities

  • cryptoRandom(min?: number, max?: number): number
  • random(min?: number, max?: number): number
  • generateUUID(): string
  • generateKey(possible: string, length: number): string
  • crcToken(secret: string, key: string): string
  • generateToken(secret: string, characters: string, length: number): string
  • validateToken(secret: string, token: string): boolean
  • serializeHash(hash: Buffer, salt: Buffer): string
  • deserializeHash(phcString: string): HashInfo
  • hashPassword(password: string): Promise<string>
  • validatePassword(password: string, serHash: string): Promise<boolean>
  • md5(fileName: string): Promise<string>
  • getX509(cert: X509Certificate): Strings
const x509 = new crypto.X509Certificate(cert);
const domains = metautil.getX509names(x509);

Datetime utilities

  • duration(s: string | number): number
  • nowDate(date?: Date): string
  • nowDateTimeUTC(date?: Date, timeSep?: string): string
  • parseMonth(s: string): number
  • parseDay(s: string): number
  • parseEvery(s: string): Every
  • nextEvent(every: Every, date?: Date): number

Error utilities

  • Class Error
    • constructor(message: string, options?: number | string | ErrorOptions)
      • options.code?: number | string
      • options.cause?: Error
    • message: string
    • stack: string
    • code?: number | string
    • cause?: Error
  • Class DomainError
    • constructor(code?: string, options?: number | string | ErrorOptions)
      • options.code?: number | string
      • options.cause?: Error
    • message: string
    • stack: string
    • code?: number | string
    • cause?: Error
    • toError(errors: Errors): Error
  • isError(instance: object): boolean

File system utilities

  • directoryExists(path: string): Promise<boolean>
  • ensureDirectory(path: string): Promise<boolean>
  • parsePath(relPath: string): Strings

HTTP utilities

  • parseHost(host?: string): string
  • parseParams(params: string): Cookies
  • parseCookies(cookie: string): Headers
  • parseRange(range: string): StreamRange

Network utilities

  • Deprecated in 4.x: fetch(url: string, options?: FetchOptions): Promise<Response>
  • receiveBody(stream: IncomingMessage): Promise<Buffer | null>
  • ipToInt(ip?: string): number
  • intToIp(int: number): string
  • httpApiCall(url: string, options: ApiOptions): Promise<object>
    • options.method?: HttpMethod
    • options.headers?: object
    • options.body?: Body

Objects utilities

  • makePrivate(instance: object): object
  • protect(allowMixins: Strings, ...namespaces: Namespaces): void
  • jsonParse(buffer: Buffer): Dictionary | null
  • isHashObject(o: string | number | boolean | object): boolean
  • flatObject(source: Dictionary, fields: Strings): Dictionary
  • unflatObject(source: Dictionary, fields: Strings): Dictionary
  • getSignature(method: Function): Strings
  • namespaceByPath(namespace: Dictionary, path: string): Dictionary | null
  • serializeArguments(fields: Strings, args: Dictionary): string

Class Pool

  • constructor(options: PoolOptions)
    • options.timeout?: number
  • items: Array<unknown>
  • free: Array<boolean>
  • queue: Array<unknown>
  • current: number
  • size: number
  • available: number
  • timeout: number
  • next(): Promise<unknown>
  • add(item: unknown): void
  • capture(): Promise<unknown>
  • release(item: unknown): void
  • isFree(item: unknown): boolean
const pool = new metautil.Pool();
pool.add({ a: 1 });
pool.add({ a: 2 });
pool.add({ a: 3 });

if (pool.isFree(obj1)) console.log('1 is free');
const item = await pool.capture();
if (pool.isFree(obj1)) console.log('1 is captured');
const obj = await pool.next();
// obj is { a: 2 }
pool.release(item);

Array utilities

sample(array: Array<unknown>): unknown

const cards = ['🂡', '🃒', '🂮', '🂷', '🃚'];
const card = sample(cards);

shuffle(array: Array<unknown>): Array<unknown>

const players = [{ id: 10 }, { id: 12 }, { id: 15 }];
const places = shuffle(players);

projection(source: object, fields: Array<string>): Array<unknown>

const player = { name: 'Marcus', score: 1500, socket };
const playerState = projection(player, ['name', 'score']);

Class Semaphore

  • constructor(options: SemaphoreOptions)
    • options.concurrency: number
    • options.size?: number
    • options.timeout?: number
  • concurrency: number
  • counter: number
  • timeout: number
  • size: number
  • empty: boolean
  • queue: Array<QueueElement>
  • enter(): Promise<void>
  • leave(): void
const options = { concurrency: 3, size: 4, timeout: 1500 };
const semaphore = new Semaphore(options);
await semaphore.enter();
// Do something
semaphore.leave();

Strings utilities

  • replace(str: string, substr: string, newstr: string): string
  • between(s: string, prefix: string, suffix: string): string
  • split(s: string, separator: string): [string, string]
  • isFirstUpper(s: string): boolean
  • isFirstLower(s: string): boolean
  • isFirstLetter(s: string): boolean
  • toLowerCamel(s: string): string
  • toUpperCamel(s: string): string
  • toLower(s: string): string
  • toCamel(separator: string): (s: string) => string
  • spinalToCamel(s: string): string
  • snakeToCamel(s: string): string
  • isConstant(s: string): boolean
  • fileExt(fileName: string): string
  • parsePath(relPath: string): Strings
  • trimLines(s: string): string

Units utilities

  • bytesToSize(bytes: number): string
  • sizeToBytes(size: string): number
const size = bytesToSize(100000000);
const bytes = sizeToBytes(size);
console.log({ size, bytes });
// { size: '100 MB', bytes: 100000000 }
Symbol zeros Unit
yb 24 yottabyte
zb 21 zettabyte
eb 18 exabyte
pb 15 petabyte
tb 12 terabyte
gb 9 gigabyte
mb 6 megabyte
kb 3 kilobyte

Class Emitter

  • Events:
    • constructor(options?: { maxListeners?: number })
    • emit(eventName: EventName, data: unknown): Promise<void>
    • on(eventName: EventName, listener: Listener): void
    • once(eventName: EventName, listener: Listener): void
    • off(eventName: EventName, listener?: Listener): void
  • Adapters:
    • toPromise(eventName: EventName): Promise<unknown>
    • toAsyncIterable(eventName: EventName): AsyncIterable<unknown>
  • Utilities:
    • clear(eventName?: EventName): void
    • listeners(eventName?: EventName): Listener[]
    • listenerCount(eventName?: EventName): number
    • eventNames(): EventName[]

Examples:

const ee = new Emitter();
ee.on('eventA', (data) => {
  console.log({ data });
  // Prints: { data: 'value' }
});
ee.emit('eventA', 'value');
const ee = new Emitter();
setTimeout(() => {
  ee.emit('eventA', 'value');
}, 100);
const result = await ee.toPromise('eventA');
const ee = new Emitter();
passReferenceSomewhere(ee);
const iterable = ee.toAsyncIterable('eventB');
for await (const eventData of iterable) {
  console.log({ eventData });
}

License & Contributors

Copyright (c) 2017-2025 Metarhia contributors. Metautil is MIT licensed.\ Metautil is a part of Metarhia technology stack.

changelog

Changelog

Unreleased

[5.2.5][] - 2025-05-21

  • Add node.js 24 to CI
  • Update dependencies

5.2.4 - 2024-09-12

  • Update eslint/prettier/metarhia configs

5.2.3 - 2024-08-30

  • Update eslint to 9.x and prettier with configs
  • Add node.js 22 to CI
  • Add await for res.json()

5.2.2 - 2024-04-26

  • Fix Content-length on httpApiCall
  • Fix memory-leak on fetch
  • Validate collected data
  • Update dependencies

5.2.1 - 2024-02-10

  • Fixed abort handling in Collector
  • Fixed Pool: release captured items to queue
  • Update dependencies

5.2.0 - 2023-12-20

  • Added defaults to Collector options
  • Collector: fix signel getter
  • Collector: use Signal.timeout instead of setTimeout
  • Collector: add .signal: AbortSignal and abort()

5.1.0 - 2023-12-17

  • Support waiting for promise in Collector.wait
  • Add reassign: boolean to Collector options

5.0.0 - 2023-12-10

  • Changed Semaphore signature, moved all parameters to options
  • Changed generateKey parameter order
  • Replaced node:http/https with node native fetch for httpApiCall
  • Move EventEmitter polyfill from metacom (with improvements)
  • Added customHeaders option for httpApiCall
  • Implemented async collection: Collector
  • Fixed sync capture in Pool

4.0.1 - 2023-11-18

  • Fix package: add dist.js
  • Fix browser support: change global to globalThis
  • Deprecate fetch polyfill

4.0.0 - 2023-11-17

  • Add browser support
  • Change IPv4 representation in integer
  • Implement intToIp
  • Update dependencies and add node.js 21

3.15.0 - 2023-10-21

  • Add error.code ETIMEOUT for timeouts

3.14.0 - 2023-10-09

  • Implement titeoutify

3.13.0 - 2023-10-06

  • Use buf.subarray instead of buf.slice
  • Drop node.js 16 and 19, update dependencies
  • Convert lockfileVersion 2 to 3

3.12.0 - 2023-08-26

  • Add projection to copy subset of fields
  • Improve random and cryptoRandom contracts to (min?, max?)
  • Copy shuffle implementation from metarhia/common
  • Pass random function as parameter to shuffle and sample

3.11.0 - 2023-07-22

  • New utilities: serializeArguments, httpApiCall, trimLines
  • New fs utilities: directoryExists, ensureDirectory
  • Added toBool for converting promises to boolean

3.10.0 - 2023-06-30

  • Implement DomainError
  • Improve Error class, add { cause, code }
  • Fix error handling in fetch

3.9.1 - 2023-06-18

  • Actualize all .d.ts typings and update short docs in README.md
  • Optimize nextEvent, sizeToBytes (support 10kb and 10 kB)
  • More checks/validation and tests for crypto submodule
  • Refactor generateKey using Uint8Array for speed
  • Use constant-time algorithm to compare CRC for security

3.9.0 - 2023-06-16

  • Implement parseRange for HTTP headers
  • Restructure submodules

3.8.0 - 2023-06-08

  • Moved from impress: getSignature and Error
  • Regroup submodules, new error.js and oop.js
  • Implement getX509names(cert): Array<string>

3.7.3 - 2023-06-05

  • Fix fileExt to support dotfiles
  • Update dependencies

3.7.2 - 2023-04-29

  • Drop node.js 14 support, add node.js 20
  • Convert package_lock.json to lockfileVersion 2
  • Update dependencies

3.7.1 - 2023-03-12

  • Use native UUID from node:crypto
  • Remove defaultHash (empty string) in crypto module
  • Use crypto.randomInt for generateKey
  • Fix fetch polyfill

3.7.0 - 2023-03-04

  • Add generateUUID implementation
  • Improve AbortController polyfill and use native if available

3.6.0 - 2022-12-19

  • Use native fetch from node.js if available
  • Fix fetch polyfill to be compatible with node.js fetch implementation
  • Use receiveBody in fetch polyfill
  • Optimize fileExt

3.5.25 - 2022-08-30

  • Optimize parseCookies
  • Improve code style, apply optimizations, update dependencies

3.5.24 - 2022-08-30

  • Add isError(instance): boolean
  • Support POST requests with body and custom headers in fetch()

3.5.23 - 2022-08-12

  • Add flatObject(sourceObject: object, fieldNames: Array<string>): object
  • Add unflatObject(sourceObject: object, fieldNames: Array<string>): object;

3.5.22 - 2022-07-29

  • New functions: parseDay and parseMonth
  • Fixed and parseEvery and nextEvent, more tests cases
  • Add year support to Every format
  • Add function flatObject(sourceObject: object, fieldNames: Array<string>): object
  • Add function isHashObject(o: string | number | boolean | object): boolean

3.5.21 - 2022-06-27

  • Update dependencies, eslint, and optimize package

3.5.20 - 2022-05-09

  • Fix isFirstUpper bug with special characters
  • Add isFirstLower and isFirstLetter functions

3.5.19 - 2022-03-15

  • Add nodejs 17 to CI
  • Update dependencies

3.5.18 - 2022-01-29

  • Fix semaphore bug with counter and queue

3.5.17 - 2022-01-26

  • Add function nowDateTimeUTC(date?: Date, timeSep?: string): string
  • Add case functions: toLower, toCamel, spinalToCamel, and snakeToCamel
  • Fixed floating bug in tests for nowDateTimeUTC

3.5.16 - 2021-10-10

  • Add Pool method isFree(item: object): boolean
  • Add function jsonParse(buffer: Buffer): object | null
  • Add function receiveBody(req: IncomingMessage): Promise<string | null>

3.5.15 - 2021-09-23

  • Fixed Pool infinite loop case
  • Add simple fetch API implementation

3.5.14 - 2021-09-21

  • Fix bugs in Pool and add tests for important cases

3.5.13 - 2021-09-20

  • Wait for available (released) item in Pool with waiting timeout
  • Pool: prevent to add duplicates and to release not captured items
  • Regrouped utilities into modules and tests

3.5.12 - 2021-09-18

  • Pool implementation with round-robian and exclusive item capture/release
  • Move parsePath from impress

3.5.11 - 2021-09-09

  • Add namespaceByPath(namespace: object, path: string): object | null
  • Add md5(fileName: string): Promise<string>

3.5.10 - 2021-08-25

  • Add and optimize bytesToSize and sizeToBytes from metarhia/common
  • Update dependencies

3.5.9 - 2021-07-27

  • Optimize Semaphore
  • Add Semaphore properties: concurrency: number add empty: boolean

3.5.8 - 2021-07-22

  • Return -1 if past events detected by nextEvent

3.5.7 - 2021-07-21

  • Initial implementation of parseEvery
  • Initial implementation of nextEvent

3.5.6 - 2021-07-18

  • Move types to package root
  • Publish signatures in README.md

3.5.5 - 2021-07-09

  • Add split and parseParams
  • Update dependencies

3.5.4 - 2021-05-24

  • Semaphore default parameters
  • Package maintenance

3.5.3 - 2021-05-05

  • Implement 'toLowerCamel' and 'toUpperCamel'
  • Package maintenance and update dependencies

3.5.2 - 2021-04-20

  • Semaphore bug: remove promise from queue on timeout
  • Improve typings: use object as dictionary, import without require

3.5.1 - 2021-03-04

  • Add typings

3.5.0 - 2021-02-22

  • Implemented await delay(msec, signal)
  • Fix timeout behaviour to reject promise (throw)
  • Generate errors on timeout and on abort timeout

3.4.0 - 2021-02-21

  • Implement simple polyfill for AbortController
  • Support AbortController in await timeout

3.3.0 - 2021-02-19

  • Change library file structure
  • Move Semaphore from metacom with fixes
  • Move timeout function from metacom

3.2.0 - 2021-02-03

  • Add parseCookies (moved from impress/auth)
  • Add hashPassword, validatePassword (moved from impress/security)

3.1.0 - 2021-01-22

  • Added isFirstUpper to check is first char an upper case letter
  • Added isConstant to check is string UPPER_SNAKE case
  • Added makePrivate to emulate protected fields for objects
  • Added protect to freeze interfaces except listed

3.0.1 - 2021-01-18

  • Optimize buffering crypto random generator

3.0.0 - 2021-01-06

2.2.0 - 2020-07-10

See this link for 2.2.0 and all previous versions: https://github.com/metarhia/common/blob/master/CHANGELOG.md