- Install:
npm install metautil - Require:
const metautil = require('metautil')
toBool = [() => true, () => false]- Example:
const created = await mkdir(path).then(...toBool);
- Example:
timeout(msec: number, signal?: AbortSignal): Promise<void>delay(msec: number, signal?: AbortSignal): Promise<void>timeoutify(promise: Promise<unknown>, msec: number): Promise<unknown>throttle(fn: (...args: Array<unknown>) => unknown, msec: number): LimitControl- At most once per
msec: leading call, trailing with latest args
- At most once per
debounce(fn: Function, msec: number): LimitControl- After
msecquiet: trailing with latest args
- After
LimitControl:{ fn, cancel, flush }fn(...args)โ rate-limited wrappercancel()โ clear timer, drop pending argsflush()โ invoke pending args now (no-op if idle)
const { fn: onScroll, cancel } = throttle(updatePosition, 100);
window.addEventListener('scroll', onScroll);
window.addEventListener('popstate', cancel);
const { fn: onType, flush } = debounce(search, 300);
input.addEventListener('input', onType);
form.addEventListener('submit', flush);collect(keys: Array<string>, options?: CollectorOptions): Collectoroptions.exact?: booleanoptions.timeout?: numberoptions.reassign?: booleanoptions.defaults?: objectoptions.validate?: (data: Record<string, unknown>) => unknown
Async collection is an utility to collect needed keys and signalize on done.
constructor(keys: Array<string>, options?: CollectorOptions)options.exact?: booleanoptions.timeout?: numberoptions.reassign?: booleanoptions.defaults?: objectoptions.validate?: (data: Record<string, unknown>) => unknown
set(key: string, value: unknown): voidwait(key: string, fn: AsyncFunction | Promise<unknown>, ...args: Array<unknown>): voidtake(key: string, fn: Function, ...args: Array<unknown>): voidcollect(sources: Record<string, Collector>): voidfail(error: Error): voidabort(): voidthen(onFulfilled: Function, onRejected?: Function): Promise<unknown>done: booleandata: Dictionarykeys: Array<string>count: numberexact: booleantimeout: numberdefaults: objectreassign: booleanvalidate?: (data: Record<string, unknown>) => unknownsignal: 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);
}cryptoRandom(min?: number, max?: number): numberrandom(min?: number, max?: number): numbergenerateUUID(): stringgenerateKey(possible: string, length: number): stringcrcToken(secret: string, key: string): stringgenerateToken(secret: string, characters: string, length: number): stringvalidateToken(secret: string, token: string): booleanserializeHash(hash: Buffer, salt: Buffer): stringdeserializeHash(phcString: string): HashInfohashPassword(password: string): Promise<string>validatePassword(password: string, serHash: string): Promise<boolean>md5(fileName: string): Promise<string>getX509names(cert: X509Certificate): Strings
const x509 = new crypto.X509Certificate(cert);
const domains = getX509names(x509);duration(s: string | number): numbernowDate(date?: Date): stringnowDateTimeUTC(date?: Date, timeSep?: string): stringparseMonth(s: string): numberparseDay(s: string): numberparseEvery(s?: string): EverynextEvent(every: Every, date?: Date): number
- Class
Errorconstructor(message: string, options?: number | string | ErrorOptions)options.code?: number | stringoptions.cause?: Erroroptions.name?: string
message: stringstack: stringcode?: number | stringcause?: Errorname: string
- Class
DomainErrorconstructor(code?: string, options?: number | string | ErrorOptions)options.code?: number | stringoptions.cause?: Erroroptions.name?: string
message: stringstack: stringcode?: number | stringcause?: Errorname: stringtoError(errors: Errors): Error
isError(instance: object): boolean
A container holding either a value or an error, useful to avoid try/catch
boilerplate and to pass either outcome around as a single value.
constructor(value?: unknown, error?: unknown)static ok(value?: unknown): Resultstatic fail(error: unknown): Resultstatic from(fn: () => unknown): Resultstatic fromAsync(fn: () => Promise<unknown>): Promise<Result>value: unknownerror: unknownok: booleanmatch(handlers: { ok, fail })[Symbol.toPrimitive](hint: 'boolean'): booleanunwrap(defaultValue?: unknown): unknownmap(fn: (value: unknown) => unknown): Result
const parsed = Result.from(() => JSON.parse(input));
if (parsed.ok) console.log(parsed.value);
else console.error(parsed.error);
parsed.match({
ok: (value) => console.log(value),
fail: (error) => console.error(error),
});
const loaded = await Result.fromAsync(() => readFile(path));
const size = loaded.map((buffer) => buffer.length).unwrap(0);exists(path: string): Promise<boolean>directoryExists(path: string): Promise<boolean>fileExists(path: string): Promise<boolean>ensureDirectory(path: string): Promise<boolean>parsePath(relPath: string): Strings
parseHost(host?: string): stringparseParams(params: string): CookiesparseCookies(cookie: string): HeadersparseRange(range: string): StreamRange
- Deprecated in 4.x:
fetch(url: string, options?: FetchOptions): Promise<Response> receiveBody(stream: IncomingMessage, limit?: number): Promise<Buffer>โ reads the request body; defaultlimitis 10MBipToInt(ip?: string): numberintToIp(int: number): stringhttpApiCall(url: string, options: ApiOptions): Promise<object>options.method?: HttpMethodoptions.headers?: objectoptions.body?: Body
makePrivate(instance: object): objectprotect(allowMixins: Strings, ...namespaces: Namespaces): voidjsonParse(data?: Buffer | string | null): unknownโ safe parse; returnsnullon error or nullish inputisHashObject(o: string | number | boolean | object): booleanflatObject(source: Dictionary, fields?: Strings): DictionaryunflatObject(source: Dictionary, fields: Strings): DictionarygetSignature(method: Function): StringsnamespaceByPath(namespace: Dictionary, path: string): Dictionary | nullserializeArguments(fields: Strings, args: Dictionary): stringfirstKey(obj: Dictionary): string | undefinedisInstanceOf(obj: unknown, constrName: string): boolean
Typed records with schema inferred from literal defaults.
Struct.immutable(className: string, defaults: object): StructClassStruct.mutable(className: string, defaults: object): StructClass
Default literals define field types:
undefinedโ schemaunknown, accepts any value, defaults toundefinednullโ schemaref, accepts null, objects, and functions, defaults tonull[]โ schemaarray, accepts arrays, fresh copy per instance{}โ schemaobject, accepts plain objects, fresh copy per instance- primitive โ schema
typeof, accepts exact primitive type, literal default value
Generated class:
constructor(data?: object)static create(data?: object): StructRecordstatic fields: Array<string>static schema: objectstatic mutable: booleanupdate(updates: object): this(mutable only)fork(updates?: object): StructRecordbranch(updates?: object): StructRecordtoObject(): object
const City = metautil.Struct.immutable('City', { name: 'Unknown' });
const rome = new City({ name: 'Rome' });
const User = metautil.Struct.mutable('User', {
id: 0,
name: 'Anonymous',
roles: [],
});
const marcus = User.create({ id: 1, name: 'Marcus' });Exclusive handle returned by Pool.capture(). Holds a pool resource until
release() is called (via the lease or pool.release(lease)).
constructor(resource: unknown, release: () => void)resource: unknownโ captured pool item (readonly)release(): voidโ return the resource to the pool; throws if already released
Round-robin pool of reusable resources. next() peeks at the next free
item without capturing it. capture() takes an exclusive Lease (waits
when all items are busy, optionally timing out).
constructor(options?: PoolOptions)options.timeout?: numberโ max wait forcapture()when no free items;0(default) waits indefinitely
add(resource: unknown): voidnext(): unknown | nullโ next free resource without capturingcapture(): Lease | Promise<Lease> | nullrelease(lease: Lease): voidisFree(resource: unknown): boolean
const pool = new Pool();
const obj1 = { a: 1 };
const obj2 = { a: 2 };
const obj3 = { a: 3 };
pool.add(obj1);
pool.add(obj2);
pool.add(obj3);
console.log(pool.isFree(obj1)); // true
const lease = await pool.capture();
console.log(lease.resource === obj1); // true
console.log(pool.isFree(obj1)); // false
console.log(pool.next()); // { a: 2 }
pool.release(lease);
// or: lease.release();
console.log(pool.isFree(obj1)); // trueMost data structure classes share a common interoperability contract:
static fromArray, toArray, and [Symbol.iterator], making structures
convertible through Array as the universal interchange format.
ConsList also exposes static fromIterable.
CircularBuffer is a growable ring with Array-like end operations
(unshift / push / shift / pop) and random access through at.
Deque exposes the same double-ended operations over CircularBuffer.
Queue (enqueue / dequeue / peek) and Stack
(push / pop / peek) are thin ADT facades over CircularBuffer.
List is a mutable sequence backed by a doubly-linked ListNode chain.
ListNode is the low-level link cell (create / append / prepend /
unlink / seek / fromArray / copy / link) for custom structures
that own their own head, tail, and size.
ConsList is an immutable cons-list ADT with structural sharing.
Trie is a prefix tree for string keys with optional associated values.
UnrolledList is a specialized high-throughput FIFO backed by pooled
unrolled nodes. UnrolledQueue is the Queue-compatible facade over it.
| Class | ADT | Backed by | Ends | Index |
|---|---|---|---|---|
CircularBuffer |
ring buffer | array | O(1) | O(1) |
Deque |
double-ended | CircularBuffer |
O(1) | โ |
Queue |
FIFO | CircularBuffer |
O(1) | โ |
Stack |
LIFO | CircularBuffer |
O(1) | โ |
UnrolledList |
FIFO | pooled unrolled | O(1) | โ |
UnrolledQueue |
FIFO | UnrolledList |
O(1) | โ |
List |
sequence | ListNode |
O(1) | O(n) |
ConsList |
immutable cons | shared nodes | O(1) | O(n) |
Trie |
prefix map | character nodes | โ | โ |
// Any interoperable structure can feed any other through Array
const list = List.fromArray([1, 2, 3, 4, 5]);
const queue = Queue.fromArray(list.filter((n) => n % 2 === 0).toArray());
const deque = Deque.fromArray(queue.toArray());
const cons = ConsList.fromArray(deque.toArray());An immutable singly-linked cons-list with structural sharing.
Every prepend returns a new ConsList that shares its tail with the
original โ enabling multiple independent branches from a common suffix
at zero copy cost (inspired by LISP cons cells).
static empty: ConsList<never>โ canonical empty singletonstatic of<T>(...values: Array<T>): ConsList<T>โ build from arguments in order (same asfromArray(values))static fromArray<T>(values: Array<T>): ConsList<T>static fromIterable<T>(iterable: Iterable<T>): ConsList<T>static merge<T>(...lists: Array<ConsList<T>>): ConsList<T>โ join in argument order (merge(a, b)โathenb); O(n) over all but the last list; shares the last list as suffix; no args โemptyprepend(value: T): ConsList<T>โ O(1), new list withvalueat the front, sharing this list as tailuncons(): Uncons<T>โ split head and rest (emptyโ{ value: undefined, tail: empty }); inverse ofconsequals(other: ConsList<T>): booleanโ structural equality (===on elements; non-ConsListโfalse; same reference short-circuits)includes(value: T): booleanโ O(n), whethervalueappears (===)member(value: T): ConsList<T>โ O(n), first suffix whose head===value(shared node), oremptyif missing; e.g.of(1, 2, 3).member(2)โ[2, 3]toReversed(): ConsList<T>โ O(n), new list in reverse ordermap<U>(fn: (value: T, index: number) => U): ConsList<U>โ O(n), new list of mapped valuesfilter(fn: (value: T, index: number) => boolean): ConsList<T>โ O(n), keeps elements wherefnreturns strictlytruefind(fn: (value: T, index: number) => boolean): T | undefinedโ O(n), first element wherefnreturns strictlytruesome(fn: (value: T, index: number) => boolean): booleanโ O(n), whetherfnreturns strictlytruefor any elementevery(fn: (value: T, index: number) => boolean): booleanโ O(n), whetherfnnever returns strictlyfalsereduce(fn: (acc: T, value: T, index: number) => T): Tโ O(n); throwsTypeErroron empty list without a seedreduce<U>(fn: (acc: U, value: T, index: number) => U, acc: U): Uโ O(n)readonly value: T | undefinedโ head (front) elementreadonly tail: ConsList<T>โ rest after the head (O(1), shared;emptywhen none)toArray(): Array<T>[Symbol.iterator](): IterableIterator<T>readonly size: numberisEmpty(): boolean
Interface Uncons<T> โ result of uncons():
value: T | undefinedtail: ConsList<T>
const shared = ConsList.of(3, 4, 5);
const branch1 = shared.prepend(2).prepend(1); // [1, 2, 3, 4, 5]
const branch2 = shared.prepend(99); // [99, 3, 4, 5]
// Both branches share the [3, 4, 5] suffix โ no copying
console.log(branch1.tail.tail === shared); // true
console.log(branch2.tail === shared); // trueconst { ConsList, cons } = metautil;
const list = ConsList.of(1, 2, 3);
const { value, tail } = list.uncons();
console.log(value); // 1
console.log(tail.toArray()); // [2, 3]
// Round-trip with cons (inverse of uncons)
console.log(cons(value, tail).toArray()); // [1, 2, 3]
const empty = ConsList.empty.uncons();
console.log(empty.value); // undefined
console.log(empty.tail === ConsList.empty); // trueUse case: undo history with branching (time-travel state)
let history = ConsList.of('draft v1');
history = history.prepend('draft v2');
history = history.prepend('draft v3');
console.log(history.value); // 'draft v3'
// Jump back in time โ earlier states remain valid and untouched
const undone = history.tail;
console.log(undone.value); // 'draft v2'
// Branch a new edit off the older state without affecting `history`
const branched = undone.prepend('draft v2b');
console.log(branched.toArray()); // ['draft v2b', 'draft v2', 'draft v1']
console.log(history.toArray()); // ['draft v3', 'draft v2', 'draft v1']Lisp-style constructor for ConsList: prepends value onto tail.
cons(value: T, tail?: ConsList<T>): ConsList<T>โ same astail.prepend(value);taildefaults toConsList.empty
const { cons } = metautil;
const list = cons(1, cons(2, cons(3)));
console.log(list.toArray()); // [1, 2, 3]
console.log(list.value); // 1
console.log(list.tail.value); // 2Inverse of cons: splits a ConsList into head and tail.
uncons(list: ConsList<T>): Uncons<T>โ same aslist.uncons()
const { cons, uncons } = metautil;
const list = cons(1, cons(2, cons(3)));
const { value, tail } = uncons(list);
console.log(value); // 1
console.log(tail.toArray()); // [2, 3]
console.log(cons(value, tail).toArray()); // [1, 2, 3]Low-level doubly-linked node. Callers own head/tail/size invariants;
mutating prev / next directly can corrupt any structure that uses
the node.
constructor(value?: T)โ creates an unlinked node (prev/nextarenull)value: Tprev: ListNode<T> | nullnext: ListNode<T> | nullappend(value?: T): ListNode<T>โ splice a new node after thisprepend(value?: T): ListNode<T>โ splice a new node before thisunlink(): { prev, next }โ remove this from neighbors; clears linksseek(n?: number): ListNode<T> | nullโ movensteps (+vianext,-viaprev);0returns this; non-integer or past the end โnullstatic create<T>(value?: T, prev?: ListNode<T> | null, next?: ListNode<T> | null): ListNode<T>โ allocate and wire neighbors (prev.next/next.prev)static fromArray<T>(values: ArrayLike<T>): { head, tail, size }โ build a detached chain; empty โ{ head: null, tail: null, size: 0 }static copy<T>(node: ListNode<T>, count: number): { head, tail, next, size }โ clonecountnodes starting fromnode;nextis the first source node after the copied range (ornodewhencountis invalid / non-positive)static link(left: ListNode<T> | null, right: ListNode<T> | null): voidโ linkleft.next/right.prev; null side is skipped
const { ListNode } = metautil;
const a = new ListNode(1);
const b = ListNode.create(2, a);
console.log(a.next.value); // 2
console.log(b.prev.value); // 1A doubly-linked-list-backed sequence with a comprehensive API.
append / prepend are O(1); index-based operations are O(n).
Internally uses ListNode; the public API is value/index based and
does not expose nodes. Indexes and counts must be integers
(non-integers are ignored / no-op). Negative indexes count from the end
(at(-1) is the last element).
Construction
constructor()static of<T>(...values: Array<T>): List<T>static fromArray<T>(values: Array<T>): List<T>static merge<T>(...lists: Array<List<T>>): List<T>
CRUD / index
append(...values: Array<T>): voidprepend(...values: Array<T>): voidinsert(index: number, ...values: Array<T>): voiddelete(index: number, count?: number): voidat(index: number): T | undefinedset(index: number, value: T): void
Slicing
drop(n: number): voidโ drops first n (or last |n| if negative)take(n: number): List<T> | nullโ first n (or last |n| if negative); non-integer,0, or empty source โnullslice(start?: number, end?: number): List<T> | nullโ non-integer bounds or empty range โnull
Rearranging
rotate(n?: number): voidโ positive rotates left, negative right (default 1); non-integer โ no-opswap(i: number, j: number): voidmove(from: number, to: number): voidsplitAt(index: number): { before: List<T>; after: List<T> }โ non-integer index treated as0groupBy<K>(getKey: (value: T) => K): Map<K, List<T>>
Search / compare
includes(value: T): booleanโ strict===(NaNโfalse)indexOf(value: T): numberโ strict===(NaNโ-1)lastIndexOf(value: T): numberโ strict===
Bulk mutations
remove(...values: Array<T>): numberโ strict===match; returns how many nodes were removedreplace(oldValue: T, newValue?: T): voidโ strict===match
Ordering
reverse(): voidtoReversed(): List<T>sort(compare?: (a: T, b: T) => number): voidtoSorted(compare?: (a: T, b: T) => number): List<T>
Functional
map<U>(fn: (value: T, index: number) => U): List<U>flatMap<U>(fn: (value: T) => Iterable<U>): List<U>filter(fn: (value: T, index: number) => boolean): List<T>reduce<U>(fn: (acc: U, value: T, index: number) => U, initial: U): Usome(fn: (value: T, index: number) => boolean): booleanevery(fn: (value: T, index: number) => boolean): booleanfind(fn: (value: T, index: number) => boolean): T | undefinedfindIndex(fn: (value: T, index: number) => boolean): number
Stats
sum(fn: (value: T) => number): numberavg(fn: (value: T) => number): numbermin(compare: (a: T, b: T) => number): T | undefinedmax(compare: (a: T, b: T) => number): T | undefined
Utility
isEmpty(): booleanclear(): voidtoArray(): Array<T>clone(): List<T>[Symbol.iterator](): IterableIterator<T>size: number
const list = List.fromArray([1, 2, 3, 4, 5]);
list.append(6);
list.prepend(0);
console.log(list.toArray()); // [0, 1, 2, 3, 4, 5, 6]
console.log(list.filter((v) => v % 2 === 0).toArray()); // [0, 2, 4, 6]
console.log(list.reduce((acc, v) => acc + v, 0)); // 21
const grouped = list.groupBy((v) => v % 3);
console.log(grouped.get(0).toArray()); // [0, 3, 6]Use case: playlist manager
const playlist = List.fromArray(['intro', 'verse', 'chorus', 'outro']);
playlist.move(3, 0); // move 'outro' to the front
console.log(playlist.toArray()); // ['outro', 'intro', 'verse', 'chorus']
console.log(playlist.find((track) => track.startsWith('ch'))); // 'chorus'Growable circular (ring) buffer with power-of-two capacity. Shared engine
for Deque, Queue, and Stack. O(1) ops at both ends via masked index
wrap โ Array-like names: unshift / push / shift / pop.
constructor()static fromArray<T>(values: Array<T>): CircularBuffer<T>unshift(value: T): voidโ insert at the frontpush(value: T): voidโ insert at the backshift(): T | undefinedโ remove and return the front elementpop(): T | undefinedโ remove and return the back elementat(index: number): T | undefinedโ Array-like index access (-1is last)isEmpty(): booleanincludes(value: T): booleanevery(fn: (value: T, index: number) => boolean): booleanโ whetherfnnever returns strictlyfalsereduce(fn: (acc: T, value: T, index: number) => T): Tโ Array-like; throwsTypeErrorwhen empty without a seedreduce<U>(fn: (acc: U, value: T, index: number) => U, acc: U): Uโ explicit seedclear(): voidtoArray(): Array<T>[Symbol.iterator](): IterableIterator<T>size: number
const buf = CircularBuffer.fromArray([1, 2, 3]);
buf.unshift(0);
buf.push(4);
console.log(buf.shift()); // 0
console.log(buf.pop()); // 4
console.log(buf.toArray()); // [1, 2, 3]Double-ended queue facade over CircularBuffer. Supports O(1) ops at
both ends with Array-like names (unshift / push / shift / pop).
constructor()static fromArray<T>(values: Array<T>): Deque<T>unshift(value: T): voidโ insert at the frontpush(value: T): voidโ insert at the backshift(): T | undefinedโ remove and return the front elementpop(): T | undefinedโ remove and return the back elementisEmpty(): booleanincludes(value: T): booleanevery(fn: (value: T, index: number) => boolean): booleanโ delegates toCircularBufferreduce(fn: (acc: T, value: T, index: number) => T): Tโ Array-like; (delegates toCircularBuffer)reduce<U>(fn: (acc: U, value: T, index: number) => U, acc: U): Uclear(): voidtoArray(): Array<T>[Symbol.iterator](): IterableIterator<T>โ delegates toCircularBuffersize: number
const deque = Deque.fromArray([1, 2, 3, 4, 5]);
deque.unshift(0);
deque.push(6);
console.log(deque.shift()); // 0
console.log(deque.pop()); // 6
console.log(deque.toArray()); // [1, 2, 3, 4, 5]FIFO (first in, first out) facade over CircularBuffer: enqueue
appends at the back, dequeue / peek operate at the front. Same O(1)
end costs as CircularBuffer.
constructor()static fromArray<T>(values: Array<T>): Queue<T>enqueue(value: T): voidโ appends at the backdequeue(): T | undefinedโ removes and returns the frontpeek(): T | undefinedโ front element, does not removeisEmpty(): booleanincludes(value: T): booleanclear(): voidtoArray(): Array<T>[Symbol.iterator](): IterableIterator<T>โ delegates toCircularBuffersize: number
const queue = new Queue();
queue.enqueue('a');
queue.enqueue('b');
queue.enqueue('c');
console.log(queue.peek()); // 'a'
console.log(queue.dequeue()); // 'a'
console.log(queue.size); // 2Use case: breadth-first traversal
const tree = {
value: 1,
children: [
{ value: 2, children: [{ value: 4, children: [] }] },
{ value: 3, children: [] },
],
};
const queue = new Queue();
queue.enqueue(tree);
const order = [];
while (!queue.isEmpty()) {
const node = queue.dequeue();
order.push(node.value);
for (const child of node.children) queue.enqueue(child);
}
console.log(order); // [1, 2, 3, 4]High-throughput FIFO queue backed by a singly-linked chain of fixed-size
array nodes, with an internal pool that reuses drained nodes. Engine for
UnrolledQueue. Prefer this over CircularBuffer when enqueue/dequeue
volume is high and you do not need index access or ops at both ends.
constructor(options?: UnrolledListOptions)options.nodeSize?: numberโ items per node (default1024)options.poolSize?: numberโ max pooled drained nodes (default2)
static fromArray<T>(values: Array<T>, options?: UnrolledListOptions): UnrolledList<T>enqueue(item: T): voidโ append at the write enddequeue(): T | undefinedโ remove and return from the read endpeek(): T | undefinedโ next item at the read end without removingisEmpty(): booleanincludes(value: T): booleanclear(): voidโ drop all items and return extra nodes to the pooltoArray(): Array<T>[Symbol.iterator](): IterableIterator<T>size: number
const list = new UnrolledList({ nodeSize: 64, poolSize: 4 });
list.enqueue('a');
list.enqueue('b');
console.log(list.peek()); // 'a'
console.log(list.dequeue()); // 'a'
console.log(list.size); // 1
list.clear();
console.log(list.isEmpty()); // trueUse case: event / task drain loop
const pending = new UnrolledList({ nodeSize: 256 });
const schedule = (task) => {
pending.enqueue(task);
};
const drain = () => {
let task = pending.dequeue();
while (task !== undefined) {
task();
task = pending.dequeue();
}
};FIFO facade over UnrolledList with the same contract as Queue
(enqueue / dequeue / peek, Array interop). Prefer this over
Queue when enqueue/dequeue volume is high and you do not need index
access. Pass UnrolledListOptions to tune chunk and pool size.
constructor(options?: UnrolledListOptions)static fromArray<T>(values: Array<T>, options?: UnrolledListOptions): UnrolledQueue<T>enqueue(value: T): voidโ appends at the backdequeue(): T | undefinedโ removes and returns the frontpeek(): T | undefinedโ front element, does not removeisEmpty(): booleanincludes(value: T): booleanclear(): voidtoArray(): Array<T>[Symbol.iterator](): IterableIterator<T>โ delegates toUnrolledListsize: number
const queue = new UnrolledQueue({ nodeSize: 64 });
queue.enqueue('a');
queue.enqueue('b');
console.log(queue.peek()); // 'a'
console.log(queue.dequeue()); // 'a'
console.log(queue.toArray()); // ['b']LIFO (last in, first out) facade over CircularBuffer: push / pop /
peek operate at the back. Same O(1) end costs as CircularBuffer.
constructor()static fromArray<T>(values: Array<T>): Stack<T>push(value: T): voidโ appends at the backpop(): T | undefinedโ removes and returns the backpeek(): T | undefinedโ back element, does not removeisEmpty(): booleanincludes(value: T): booleanevery(fn: (value: T, index: number) => boolean): booleanโ delegates toCircularBufferreduce(fn: (acc: T, value: T, index: number) => T): Tโ Array-like; (delegates toCircularBuffer)reduce<U>(fn: (acc: U, value: T, index: number) => U, acc: U): Uclear(): voidtoArray(): Array<T>[Symbol.iterator](): IterableIterator<T>โ delegates toCircularBuffersize: number
const stack = new Stack();
stack.push(1);
stack.push(2);
stack.push(3);
console.log(stack.peek()); // 3
console.log(stack.pop()); // 3
console.log(stack.size); // 2Use case: balanced brackets validator
function isBalanced(input) {
const pairs = { ')': '(', ']': '[', '}': '{' };
const stack = new Stack();
for (const char of input) {
if ('([{'.includes(char)) stack.push(char);
else if (char in pairs && stack.pop() !== pairs[char]) return false;
}
return stack.isEmpty();
}
console.log(isBalanced('{[()]}')); // true
console.log(isBalanced('{[(])}')); // falsePrefix tree (trie) for string keys with optional associated values.
Supports exact lookup, deletion with branch pruning, and prefix
autocomplete via complete.
readonly size: numberโ number of stored keysinsert(word: string, value?: T): thisโ store key (default valuetruewhenTdefaults toboolean); throwsTypeErrorifwordis not a stringdelete(word: string): booleanโ remove key and prune empty branchesclear(): voidisEmpty(): booleanhas(word: string): booleanโ exact key presentget(word: string): T | undefinedโ associated value, orundefinedif missingcomplete(prefix: string): Array<string>โ all keys with the given prefix
const trie = new Trie();
trie.insert('cat');
trie.insert('car', 42);
trie.insert('card');
trie.has('car'); // true
trie.get('car'); // 42
trie.complete('ca'); // ['cat', 'car', 'card'] (order may vary)
trie.delete('car'); // true
trie.size; // 2const cards = ['๐ก', '๐', '๐ฎ', '๐ท', '๐'];
const card = sample(cards);const players = [{ id: 10 }, { id: 12 }, { id: 15 }];
const places = shuffle(players);const player = { name: 'Marcus', score: 1500, socket };
const playerState = projection(player, ['name', 'score']);constructor(options: SemaphoreOptions)options.concurrency: numberoptions.size?: numberoptions.timeout?: number
concurrency: numbercounter: numbertimeout: numbersize: numberempty: booleanqueue: 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();replace(str: string, substr: string, newstr: string): stringbetween(s: string, prefix: string, suffix: string): stringsplit(s: string, separator: string): [string, string]isFirstUpper(s: string): booleanisFirstLower(s: string): booleanisFirstLetter(s: string): booleantoLowerCamel(s: string): stringtoUpperCamel(s: string): stringtoLower(s: string): stringtoCamel(separator: string): (s: string) => stringspinalToCamel(s: string): stringsnakeToCamel(s: string): stringisConstant(s: string): booleanfileExt(fileName: string): stringtrimLines(s: string): string
bytesToSize(bytes: number): stringsizeToBytes(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 |
- Events:
constructor(options?: { maxListeners?: number })emit(eventName: EventName, data: unknown): Promise<void>on(eventName: EventName, listener: Listener): voidonce(eventName: EventName, listener: Listener): voidoff(eventName: EventName, listener?: Listener): void
- Adapters:
toPromise(eventName: EventName): Promise<unknown>toAsyncIterable(eventName: EventName): AsyncIterable<unknown>
- Utilities:
clear(eventName?: EventName): voidlisteners(eventName: EventName): Listener[]listenerCount(eventName: EventName): numbereventNames(): 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 });
}Copyright (c) 2017-2026 Metarhia contributors.
Metautil is MIT licensed.
Metautil is a part of Metarhia technology stack.