import { CommonCoreConfig, CoreConfig, StreamConfig } from "../types.js"; export function getControlledPromise() { let resolve: (value: T) => void; let reject: (reason?: unknown) => void; const promise = new Promise((res, rej) => { resolve = res; reject = rej; }); return { promise, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion resolve: resolve!, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion reject: reject!, }; } export function joinChunks(chunks: Uint8Array[], totalBytes?: number) { totalBytes ??= chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0); const buffer = new Uint8Array(totalBytes); let offset = 0; for (const chunk of chunks) { buffer.set(chunk, offset); offset += chunk.byteLength; } return buffer; } export function getPercent(numerator: number, denominator: number): number { return (numerator / denominator) * 100; } export function getRandomItem(items: T[]): T { return items[Math.floor(Math.random() * items.length)]; } export function getWeightedRandomItem( items: T[], weightAccessor: (item: T) => number, ): T { if (items.length === 0) throw new Error("Cannot get item from empty array"); if (items.length === 1) return items[0]; let totalWeight = 0; const weights = items.map((item) => { const weight = weightAccessor(item); totalWeight += weight; return weight; }); let randomWeight = Math.random() * totalWeight; for (let i = 0; i < items.length; i++) { randomWeight -= weights[i]; if (randomWeight <= 0) return items[i]; } return items[items.length - 1]; } export function utf8ToUintArray(utf8String: string): Uint8Array { return new TextEncoder().encode(utf8String); } export function hexToUtf8(hexString: string) { const bytes = new Uint8Array(hexString.length / 2); for (let i = 0; i < hexString.length; i += 2) { bytes[i / 2] = parseInt(hexString.slice(i, i + 2), 16); } const decoder = new TextDecoder(); return decoder.decode(bytes); } export function* arrayBackwards(arr: T[]) { for (let i = arr.length - 1; i >= 0; i--) { yield arr[i]; } } function isObject(item: unknown): item is Record { return !!item && typeof item === "object" && !Array.isArray(item); } function isArray(item: unknown): item is unknown[] { return Array.isArray(item); } export function filterUndefinedProps(obj: T): Partial { function filter(obj: unknown): unknown { if (isObject(obj)) { const result: Record = {}; Object.keys(obj).forEach((key) => { if (obj[key] !== undefined) { const value = filter(obj[key]); if (value !== undefined) { result[key] = value; } } }); return result; } else { return obj; } } return filter(obj) as Partial; } export function deepCopy(item: T): T { if (isArray(item)) { return item.map((element) => deepCopy(element)) as T; } else if (isObject(item)) { const copy = {} as Record; for (const key of Object.keys(item)) { copy[key] = deepCopy(item[key]); } return copy as T; } else { return item; } } export function shuffleArray(array: T[]): T[] { for (let i = array.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [array[i], array[j]] = [array[j], array[i]]; } return array; } type RecursivePartial = { [P in keyof T]?: T[P] extends object ? RecursivePartial : T[P]; }; export function overrideConfig( target: T, updates: RecursivePartial | null, defaults: RecursivePartial = {}, ): T { if ( typeof target !== "object" || target === null || typeof updates !== "object" || updates === null ) { return target; } (Object.keys(updates) as (keyof T)[]).forEach((key) => { const keyStr = typeof key === "symbol" ? key.toString() : String(key); if (key === "__proto__" || key === "constructor" || key === "prototype") { throw new Error(`Attempt to modify restricted property '${keyStr}'`); } const updateValue = updates[key]; const defaultValue = defaults[key]; if (key in target) { if (updateValue === undefined) { target[key] = defaultValue === undefined ? (undefined as (T & object)[keyof T]) : (defaultValue as (T & object)[keyof T]); } else { target[key] = updateValue as (T & object)[keyof T]; } } }); return target; } type MergeConfigsToTypeOptions = { defaultConfig: StreamConfig | CommonCoreConfig | CoreConfig; baseConfig?: Partial; specificStreamConfig?: Partial; }; // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters export function mergeAndFilterConfig(options: MergeConfigsToTypeOptions) { const { defaultConfig, baseConfig = {}, specificStreamConfig = {} } = options; const mergedConfig = deepCopy({ ...defaultConfig, ...baseConfig, ...specificStreamConfig, }); const keysOfT = Object.keys(defaultConfig) as (keyof T)[]; const filteredConfig: Partial = {}; keysOfT.forEach((key) => { if (key in mergedConfig) { filteredConfig[key] = mergedConfig[ key as keyof typeof mergedConfig ] as T[keyof T]; } }); return filteredConfig as T; }