UNPKG

@travetto/runtime

Version:

Runtime for travetto applications.

121 lines (108 loc) 3.82 kB
import crypto from 'node:crypto'; import timers from 'node:timers/promises'; import { castTo } from './types.ts'; type MapFn<T, U> = (value: T, i: number) => U | Promise<U>; /** * Grab bag of common utilities */ export class Util { static #match<T, K extends unknown[]>( rules: { value: T, positive: boolean }[], compare: (rule: T, ...compareInput: K) => boolean, unmatchedValue: boolean, ...input: K ): boolean { for (const rule of rules) { if (compare(rule.value, ...input)) { return rule.positive; } } return unmatchedValue; } static #allowDenyRuleInput<T>( rule: (string | T | [value: T, positive: boolean] | [value: T]), convert: (inputRule: string) => T ): { value: T, positive: boolean } { return typeof rule === 'string' ? { value: convert(rule.replace(/^!/, '')), positive: !rule.startsWith('!') } : Array.isArray(rule) ? { value: rule[0], positive: rule[1] ?? true } : { value: rule, positive: true }; } /** * Generate a random UUID * @param length The length of the uuid to generate */ static uuid(length: number = 32): string { const bytes = crypto.randomBytes(Math.ceil(length / 2)); if (length === 32) { // Make valid uuid-v4 // eslint-disable-next-line no-bitwise bytes[6] = (bytes[6] & 0x0f) | 0x40; // eslint-disable-next-line no-bitwise bytes[8] = (bytes[8] & 0x3f) | 0x80; } return bytes.toString('hex').substring(0, length); } /** * Map an async iterable with various mapping functions */ static mapAsyncIterable<T, U, V, W>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>, fn3: MapFn<V, W>): AsyncIterable<W>; static mapAsyncIterable<T, U, V>(source: AsyncIterable<T>, fn1: MapFn<T, U>, fn2: MapFn<U, V>): AsyncIterable<V>; static mapAsyncIterable<T, U>(source: AsyncIterable<T>, fn: MapFn<T, U>): AsyncIterable<U>; static async * mapAsyncIterable<T>(input: AsyncIterable<T>, ...fns: MapFn<unknown, unknown>[]): AsyncIterable<unknown> { let idx = -1; for await (const item of input) { if (item !== undefined) { idx += 1; let result = item; for (const fn of fns) { result = castTo(await fn(result, idx)); } yield result; } } } /** * Non-blocking timeout */ static nonBlockingTimeout(time: number): Promise<void> { return timers.setTimeout(time, undefined, { ref: false }).catch(() => { }); } /** * Blocking timeout */ static blockingTimeout(time: number): Promise<void> { return timers.setTimeout(time, undefined, { ref: true }).catch(() => { }); } /** * Queue new macro task */ static queueMacroTask(): Promise<void> { return timers.setImmediate(undefined); } /** * Simple check against allow/deny rules * @param rules */ static allowDeny<T, K extends unknown[]>( rules: string | (string | T | [value: T, positive: boolean])[], convert: (rule: string) => T, compare: (rule: T, ...compareInput: K) => boolean, cacheKey?: (...keyInput: K) => string ): (...input: K) => boolean { const rawRules = (Array.isArray(rules) ? rules : rules.split(/,/g).map(rule => rule.trim())); const convertedRules = rawRules.map(rule => this.#allowDenyRuleInput(rule, convert)); const unmatchedValue = !convertedRules.some(rule => rule.positive); if (convertedRules.length) { if (cacheKey) { const cache: Record<string, boolean> = {}; return (...input: K) => cache[cacheKey(...input)] ??= this.#match(convertedRules, compare, unmatchedValue, ...input); } else { return (...input: K) => this.#match(convertedRules, compare, unmatchedValue, ...input); } } else { return () => true; } } }