UNPKG

@atproto/common-web

Version:

Shared web-platform-friendly code for atproto libraries

181 lines (159 loc) 4.19 kB
export const noUndefinedVals = <T>( obj: Record<string, T | undefined>, ): Record<string, T> => { Object.keys(obj).forEach((k) => { if (obj[k] === undefined) { delete obj[k] } }) return obj as Record<string, T> } 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<string, unknown>, K extends keyof NonNullable<T>, >( object: T, rejectedKeys: readonly K[], ): T extends undefined ? undefined : T extends null ? null : Omit<T, K> export function omit( src: undefined | null | Record<string, unknown>, rejectedKeys: readonly string[], ): undefined | null | Record<string, unknown> { // 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<void> } export const bailableWait = (ms: number): BailableWait => { let bail const waitPromise = new Promise<void>((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<Uint8Array>, ): Promise<Uint8Array> => { 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 <T>( arr: T[], fn: (t: T) => Promise<boolean>, ) => { 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 = <T>(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: string[]): string[] => { return [...new Set(strs)] } export const parseIntWithFallback = <T>( value: string | undefined, fallback: T, ): number | T => { const parsed = parseInt(value || '', 10) return isNaN(parsed) ? fallback : parsed }