{"id":19874031,"url":"https://github.com/metarhia/metautil","last_synced_at":"2026-03-16T01:58:10.746Z","repository":{"id":40435408,"uuid":"325652434","full_name":"metarhia/metautil","owner":"metarhia","description":"Metarhia utilities 🧰","archived":false,"fork":false,"pushed_at":"2025-04-10T21:25:20.000Z","size":1800,"stargazers_count":104,"open_issues_count":18,"forks_count":34,"subscribers_count":16,"default_branch":"master","last_synced_at":"2025-04-12T06:18:46.715Z","etag":null,"topics":["hacktoberfest","metarhia","nodejs","utilities"],"latest_commit_sha":null,"homepage":"https://metarhia.com","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/metarhia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"patreon":"tshemsedinov"}},"created_at":"2020-12-30T21:34:35.000Z","updated_at":"2025-03-28T23:04:50.000Z","dependencies_parsed_at":"2023-10-02T20:16:58.755Z","dependency_job_id":"c0199711-bbf9-461e-8006-0514af8e0ad2","html_url":"https://github.com/metarhia/metautil","commit_stats":{"total_commits":445,"total_committers":21,"mean_commits":21.19047619047619,"dds":"0.42921348314606744","last_synced_commit":"43b5b22fa574731f6e0d4b6c1dccb4dd0228cc3c"},"previous_names":[],"tags_count":54,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metarhia%2Fmetautil","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metarhia%2Fmetautil/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metarhia%2Fmetautil/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/metarhia%2Fmetautil/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/metarhia","download_url":"https://codeload.github.com/metarhia/metautil/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248525140,"owners_count":21118620,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["hacktoberfest","metarhia","nodejs","utilities"],"created_at":"2024-11-12T16:21:15.300Z","updated_at":"2026-03-16T01:58:10.736Z","avatar_url":"https://github.com/metarhia.png","language":"JavaScript","funding_links":["https://patreon.com/tshemsedinov"],"categories":[],"sub_categories":[],"readme":"# Metarhia utilities\n\n[![ci status](https://github.com/metarhia/metautil/workflows/Testing%20CI/badge.svg)](https://github.com/metarhia/metautil/actions?query=workflow%3A%22Testing+CI%22+branch%3Amaster)\n[![snyk](https://snyk.io/test/github/metarhia/metautil/badge.svg)](https://snyk.io/test/github/metarhia/metautil)\n[![npm version](https://badge.fury.io/js/metautil.svg)](https://badge.fury.io/js/metautil)\n[![npm downloads/month](https://img.shields.io/npm/dm/metautil.svg)](https://www.npmjs.com/package/metautil)\n[![npm downloads](https://img.shields.io/npm/dt/metautil.svg)](https://www.npmjs.com/package/metautil)\n[![license](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/metarhia/metautil/blob/master/LICENSE)\n\n## Usage\n\n- Install: `npm install metautil`\n- Require: `const metautil = require('metautil')`\n\n## Async utilities\n\n- `toBool = [() =\u003e true, () =\u003e false]`\n  - Example: `const created = await mkdir(path).then(...toBool);`\n- `timeout(msec: number, signal?: AbortSignal): Promise\u003cvoid\u003e`\n- `delay(msec: number, signal?: AbortSignal): Promise\u003cvoid\u003e`\n- `timeoutify(promise: Promise\u003cunknown\u003e, msec: number): Promise\u003cunknown\u003e`\n- `collect(keys: Array\u003cstring\u003e, options?: CollectorOptions): Collector`\n  - `options.exact?: boolean`\n  - `options.timeout?: number`\n  - `options.reassign?: boolean`\n\n## Class `Collector`\n\nAsync collection is an utility to collect needed keys and signalize on done.\n\n- `constructor(keys: Array\u003cstring\u003e, options?: CollectorOptions)`\n  - `options.exact?: boolean`\n  - `options.timeout?: number`\n  - `options.reassign?: boolean`\n  - `options.defaults?: object`\n  - `options.validate?: (data: Record\u003cstring, unknown\u003e) =\u003e unknown`\n- `set(key: string, value: unknown): void`\n- `wait(key: string, fn: AsyncFunction | Promise\u003cunknown\u003e, ...args?: Array\u003cunknown\u003e): void`\n- `take(key: string, fn: Function, ...args?: Array\u003cunknown\u003e): void`\n- `collect(sources: Record\u003cstring, Collector\u003e): void`\n- `fail(error: Error): void`\n- `abort(): void`\n- `then(onFulfilled: Function, onRejected?: Function): Promise\u003cunknown\u003e`\n- `done: boolean`\n- `data: Dictionary`\n- `keys: Array\u003cstring\u003e`\n- `count: number`\n- `exact: boolean`\n- `timeout: number`\n- `defaults: object`\n- `reassign: boolean`\n- `validate?: (data: Record\u003cstring, unknown\u003e) =\u003e unknown`\n- `signal: AbortSignal`\n\nCollect keys with `.set` method:\n\n```js\nconst ac = collect(['userName', 'fileName']);\n\nsetTimeout(() =\u003e ac.set('fileName', 'marcus.txt'), 100);\nsetTimeout(() =\u003e ac.set('userName', 'Marcus'), 200);\n\nconst result = await ac;\nconsole.log(result);\n```\n\nCollect keys with `.wait` method from async or promise-returning function:\n\n```js\nconst ac = collect(['user', 'file']);\n\nac.wait('file', getFilePromisified, 'marcus.txt');\nac.wait('user', getUserPromisified, 'Marcus');\n\ntry {\n  const result = await ac;\n  console.log(result);\n} catch (error) {\n  console.error(error);\n}\n```\n\nCollect keys with `.take` method from callback-last-error-first function:\n\n```js\nconst ac = collect(['user', 'file'], { timeout: 2000, exact: false });\n\nac.take('file', getFileCallback, 'marcus.txt');\nac.take('user', getUserCallback, 'Marcus');\n\nconst result = await ac;\n```\n\nSet default values ​​for unset keys using the `options.defaults` argument:\n\n```js\nconst defaults = { key1: 'sub1', key2: 'sub1' };\n\nconst dc = collect(['key1', 'key2'], { defaults, timeout: 2000 });\ndc.set('key2', 'sub2');\n\nconst result = await dc;\n```\n\nCompose collectors (collect subkeys from multiple sources):\n\n```js\nconst dc = collect(['key1', 'key2', 'key3']);\nconst key1 = collect(['sub1']);\nconst key3 = collect(['sub3']);\ndc.collect({ key1, key3 });\nconst result = await dc;\n```\n\nComplex example: compare `Promise.allSettled` + `Promise.race` vs `Collector` in next two examples:\n\n```js\n// Collect 4 keys from different contracts with Promise.allSettled + Promise.race\n\nconst promise1 = new Promise((resolve, reject) =\u003e {\n  fs.readFile('README.md', (err, data) =\u003e {\n    if (err) return void reject(err);\n    resolve(data);\n  });\n});\nconst promise2 = fs.promises.readFile('README.md');\nconst url = 'http://worldtimeapi.org/api/timezone/Europe';\nconst promise3 = fetch(url).then((data) =\u003e data.json());\nconst promise4 = new Promise((resolve) =\u003e {\n  setTimeout(() =\u003e resolve('value4'), 50);\n});\nconst timeout = new Promise((resolve, reject) =\u003e {\n  setTimeout(() =\u003e reject(new Error('Timed out')), 1000);\n});\nconst data = Promise.allSettled([promise1, promise2, promise3, promise4]);\ntry {\n  const keys = await Promise.race([data, timeout]);\n  const [key1, key2, key3, key4] = keys.map(({ value }) =\u003e value);\n  const result = { key1, key2, key3, key4 };\n  console.log(result);\n} catch (err) {\n  console.log(err);\n}\n```\n\nCompare with:\n\n```js\n// Collect 4 keys from different contracts with Collector\n\nconst dc = collect(['key1', 'key2', 'key3', 'key4'], { timeout: 1000 });\n\ndc.take('key1', fs.readFile, 'README.md');\ndc.wait('key2', fs.promises.readFile, 'README.md');\nconst url = 'http://worldtimeapi.org/api/timezone/Europe';\ndc.wait(\n  'key3',\n  fetch(url).then((data) =\u003e data.json()),\n);\nsetTimeout(() =\u003e dc.set('key4', 'value4'), 50);\n\ntry {\n  const result = await dc;\n  console.log(result);\n} catch (err) {\n  console.log(err);\n}\n```\n\n## Crypto utilities\n\n- `cryptoRandom(min?: number, max?: number): number`\n- `random(min?: number, max?: number): number`\n- `generateUUID(): string`\n- `generateKey(possible: string, length: number): string`\n- `crcToken(secret: string, key: string): string`\n- `generateToken(secret: string, characters: string, length: number): string`\n- `validateToken(secret: string, token: string): boolean`\n- `serializeHash(hash: Buffer, salt: Buffer): string`\n- `deserializeHash(phcString: string): HashInfo`\n- `hashPassword(password: string): Promise\u003cstring\u003e`\n- `validatePassword(password: string, serHash: string): Promise\u003cboolean\u003e`\n- `md5(fileName: string): Promise\u003cstring\u003e`\n- `getX509names(cert: X509Certificate): Strings`\n\n```js\nconst x509 = new crypto.X509Certificate(cert);\nconst domains = metautil.getX509names(x509);\n```\n\n## Datetime utilities\n\n- `duration(s: string | number): number`\n- `nowDate(date?: Date): string`\n- `nowDateTimeUTC(date?: Date, timeSep?: string): string`\n- `parseMonth(s: string): number`\n- `parseDay(s: string): number`\n- `parseEvery(s: string): Every`\n- `nextEvent(every: Every, date?: Date): number`\n\n## Error utilities\n\n- Class `Error`\n  - `constructor(message: string, options?: number | string | ErrorOptions)`\n    - `options.code?: number | string`\n    - `options.cause?: Error`\n  - `message: string`\n  - `stack: string`\n  - `code?: number | string`\n  - `cause?: Error`\n- Class `DomainError`\n  - `constructor(code?: string, options?: number | string | ErrorOptions)`\n    - `options.code?: number | string`\n    - `options.cause?: Error`\n  - `message: string`\n  - `stack: string`\n  - `code?: number | string`\n  - `cause?: Error`\n  - `toError(errors: Errors): Error`\n- `isError(instance: object): boolean`\n\n## File system utilities\n\n- `exists(path: string): Promise\u003cboolean\u003e`\n- `directoryExists(path: string): Promise\u003cboolean\u003e`\n- `fileExists(path: string): Promise\u003cboolean\u003e`\n- `ensureDirectory(path: string): Promise\u003cboolean\u003e`\n- `parsePath(relPath: string): Strings`\n\n## HTTP utilities\n\n- `parseHost(host?: string): string`\n- `parseParams(params: string): Cookies`\n- `parseCookies(cookie: string): Headers`\n- `parseRange(range: string): StreamRange`\n\n## Network utilities\n\n- Deprecated in 4.x: `fetch(url: string, options?: FetchOptions): Promise\u003cResponse\u003e`\n- `receiveBody(stream: IncomingMessage): Promise\u003cBuffer | null\u003e`\n- `ipToInt(ip?: string): number`\n- `intToIp(int: number): string`\n- `httpApiCall(url: string, options: ApiOptions): Promise\u003cobject\u003e`\n  - `options.method?: HttpMethod`\n  - `options.headers?: object`\n  - `options.body?: Body`\n\n## Objects utilities\n\n- `makePrivate(instance: object): object`\n- `protect(allowMixins: Strings, ...namespaces: Namespaces): void`\n- `jsonParse(buffer: Buffer): Dictionary | null`\n- `isHashObject(o: string | number | boolean | object): boolean`\n- `flatObject(source: Dictionary, fields: Strings): Dictionary`\n- `unflatObject(source: Dictionary, fields: Strings): Dictionary`\n- `getSignature(method: Function): Strings`\n- `namespaceByPath(namespace: Dictionary, path: string): Dictionary | null`\n- `serializeArguments(fields: Strings, args: Dictionary): string`\n- `firstKey(obj: Dictionary): string | undefined`\n- `isInstanceOf(obj: unknown, constrName: string): boolean`\n\n## Class Pool\n\n- `constructor(options: PoolOptions)`\n  - `options.timeout?: number`\n- `items: Array\u003cunknown\u003e`\n- `free: Array\u003cboolean\u003e`\n- `queue: Array\u003cunknown\u003e`\n- `current: number`\n- `size: number`\n- `available: number`\n- `timeout: number`\n- `next(exclusive?: boolean): Promise\u003cunknown\u003e`\n- `add(item: unknown): void`\n- `capture(): Promise\u003cunknown\u003e`\n- `release(item: unknown): void`\n- `isFree(item: unknown): boolean`\n\n```js\nconst pool = new metautil.Pool();\npool.add({ a: 1 });\npool.add({ a: 2 });\npool.add({ a: 3 });\n\nif (pool.isFree(obj1)) console.log('1 is free');\nconst item = await pool.capture();\nif (pool.isFree(obj1)) console.log('1 is captured');\nconst obj = await pool.next();\n// obj is { a: 2 }\npool.release(item);\n```\n\n## Array utilities\n\n### `sample(array: Array\u003cunknown\u003e): unknown`\n\n```js\nconst cards = ['🂡', '🃒', '🂮', '🂷', '🃚'];\nconst card = sample(cards);\n```\n\n### `shuffle(array: Array\u003cunknown\u003e): Array\u003cunknown\u003e`\n\n```js\nconst players = [{ id: 10 }, { id: 12 }, { id: 15 }];\nconst places = shuffle(players);\n```\n\n### `projection(source: object, fields: Array\u003cstring\u003e): Array\u003cunknown\u003e`\n\n```js\nconst player = { name: 'Marcus', score: 1500, socket };\nconst playerState = projection(player, ['name', 'score']);\n```\n\n## Class Semaphore\n\n- `constructor(options: SemaphoreOptions)`\n  - `options.concurrency: number`\n  - `options.size?: number`\n  - `options.timeout?: number`\n- `concurrency: number`\n- `counter: number`\n- `timeout: number`\n- `size: number`\n- `empty: boolean`\n- `queue: Array\u003cQueueElement\u003e`\n- `enter(): Promise\u003cvoid\u003e`\n- `leave(): void`\n\n```js\nconst options = { concurrency: 3, size: 4, timeout: 1500 };\nconst semaphore = new Semaphore(options);\nawait semaphore.enter();\n// Do something\nsemaphore.leave();\n```\n\n## Strings utilities\n\n- `replace(str: string, substr: string, newstr: string): string`\n- `between(s: string, prefix: string, suffix: string): string`\n- `split(s: string, separator: string): [string, string]`\n- `isFirstUpper(s: string): boolean`\n- `isFirstLower(s: string): boolean`\n- `isFirstLetter(s: string): boolean`\n- `toLowerCamel(s: string): string`\n- `toUpperCamel(s: string): string`\n- `toLower(s: string): string`\n- `toCamel(separator: string): (s: string) =\u003e string`\n- `spinalToCamel(s: string): string`\n- `snakeToCamel(s: string): string`\n- `isConstant(s: string): boolean`\n- `fileExt(fileName: string): string`\n- `parsePath(relPath: string): Strings`\n- `trimLines(s: string): string`\n\n## Units utilities\n\n- `bytesToSize(bytes: number): string`\n- `sizeToBytes(size: string): number`\n\n```js\nconst size = bytesToSize(100000000);\nconst bytes = sizeToBytes(size);\nconsole.log({ size, bytes });\n// { size: '100 MB', bytes: 100000000 }\n```\n\n| Symbol | zeros | Unit      |\n| -----: | ----: | --------- |\n|     yb |    24 | yottabyte |\n|     zb |    21 | zettabyte |\n|     eb |    18 | exabyte   |\n|     pb |    15 | petabyte  |\n|     tb |    12 | terabyte  |\n|     gb |     9 | gigabyte  |\n|     mb |     6 | megabyte  |\n|     kb |     3 | kilobyte  |\n\n## Class `Emitter`\n\n- Events:\n  - `constructor(options?: { maxListeners?: number })`\n  - `emit(eventName: EventName, data: unknown): Promise\u003cvoid\u003e`\n  - `on(eventName: EventName, listener: Listener): void`\n  - `once(eventName: EventName, listener: Listener): void`\n  - `off(eventName: EventName, listener?: Listener): void`\n- Adapters:\n  - `toPromise(eventName: EventName): Promise\u003cunknown\u003e`\n  - `toAsyncIterable(eventName: EventName): AsyncIterable\u003cunknown\u003e`\n- Utilities:\n  - `clear(eventName?: EventName): void`\n  - `listeners(eventName?: EventName): Listener[]`\n  - `listenerCount(eventName?: EventName): number`\n  - `eventNames(): EventName[]`\n\nExamples:\n\n```js\nconst ee = new Emitter();\nee.on('eventA', (data) =\u003e {\n  console.log({ data });\n  // Prints: { data: 'value' }\n});\nee.emit('eventA', 'value');\n```\n\n```js\nconst ee = new Emitter();\nsetTimeout(() =\u003e {\n  ee.emit('eventA', 'value');\n}, 100);\nconst result = await ee.toPromise('eventA');\n```\n\n```js\nconst ee = new Emitter();\npassReferenceSomewhere(ee);\nconst iterable = ee.toAsyncIterable('eventB');\nfor await (const eventData of iterable) {\n  console.log({ eventData });\n}\n```\n\n## License \u0026 Contributors\n\nCopyright (c) 2017-2026 [Metarhia contributors](https://github.com/metarhia/metautil/graphs/contributors).\nMetautil is [MIT licensed](./LICENSE).\\\nMetautil is a part of [Metarhia](https://github.com/metarhia) technology stack.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetarhia%2Fmetautil","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmetarhia%2Fmetautil","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmetarhia%2Fmetautil/lists"}