export const noUndefinedVals = ( obj: Record, ): Record => { for (const k of Object.keys(obj)) { if (obj[k] === undefined) { delete obj[k] } } return obj as Record } export function aggregateErrors( errors: unknown[], message?: string, ): Error | AggregateError { if (errors.length === 1) { return errors[0] instanceof Error ? errors[0] : new Error(message ?? stringifyError(errors[0]), { cause: errors[0] }) } else { return new AggregateError( errors, message ?? `Multiple errors: ${errors.map(stringifyError).join('\n')}`, ) } } function stringifyError(reason: unknown): string { if (reason instanceof Error) { return reason.message } return String(reason) } /** * Returns a shallow copy of the object without the specified keys. If the input * is nullish, it returns the input. */ export function omit< T extends undefined | null | Record, K extends keyof NonNullable, >( object: T, rejectedKeys: readonly K[], ): T extends undefined ? undefined : T extends null ? null : Omit export function omit( src: undefined | null | Record, rejectedKeys: readonly string[], ): undefined | null | Record { // Hot path if (!src) return src const dst = {} const srcKeys = Object.keys(src) for (let i = 0; i < srcKeys.length; i++) { const key = srcKeys[i] if (!rejectedKeys.includes(key)) { dst[key] = src[key] } } return dst } export const jitter = (maxMs: number) => { return Math.round((Math.random() - 0.5) * maxMs * 2) } export const wait = (ms: number) => { return new Promise((res) => setTimeout(res, ms)) } export type BailableWait = { bail: () => void wait: () => Promise } export const bailableWait = (ms: number): BailableWait => { let bail const waitPromise = new Promise((res) => { const timeout = setTimeout(res, ms) bail = () => { clearTimeout(timeout) res() } }) return { bail, wait: () => waitPromise } } export const flattenUint8Arrays = (arrs: Uint8Array[]): Uint8Array => { const length = arrs.reduce((acc, cur) => { return acc + cur.length }, 0) const flattened = new Uint8Array(length) let offset = 0 arrs.forEach((arr) => { flattened.set(arr, offset) offset += arr.length }) return flattened } export const streamToBuffer = async ( stream: AsyncIterable, ): Promise => { const arrays: Uint8Array[] = [] for await (const chunk of stream) { arrays.push(chunk) } return flattenUint8Arrays(arrays) } const S32_CHAR = '234567abcdefghijklmnopqrstuvwxyz' export const s32encode = (i: number): string => { let s = '' while (i) { const c = i % 32 i = Math.floor(i / 32) s = S32_CHAR.charAt(c) + s } return s } export const s32decode = (s: string): number => { let i = 0 for (const c of s) { i = i * 32 + S32_CHAR.indexOf(c) } return i } export const asyncFilter = async ( arr: T[], fn: (t: T) => Promise, ) => { const results = await Promise.all(arr.map((t) => fn(t))) return arr.filter((_, i) => results[i]) } export const isErrnoException = ( err: unknown, ): err is NodeJS.ErrnoException => { return !!err && err['code'] } export const errHasMsg = (err: unknown, msg: string): boolean => { return !!err && typeof err === 'object' && err['message'] === msg } export const chunkArray = (arr: T[], chunkSize: number): T[][] => { return arr.reduce((acc, cur, i) => { const chunkI = Math.floor(i / chunkSize) if (!acc[chunkI]) { acc[chunkI] = [] } acc[chunkI].push(cur) return acc }, [] as T[][]) } export const range = (num: number): number[] => { const nums: number[] = [] for (let i = 0; i < num; i++) { nums.push(i) } return nums } export const dedupeStrs = (strs: Iterable): T[] => { return [...new Set(strs)] } export const parseIntWithFallback = ( value: string | undefined, fallback: T, ): number | T => { const parsed = parseInt(value || '', 10) return isNaN(parsed) ? fallback : parsed }