import { ok, err, type Result } from '../types/result' export function debounce) => void>( fn: T, ms: number ): T & { cancel: () => void } { let timeoutId: ReturnType | null = null const debounced = ((...args: Parameters) => { if (timeoutId) clearTimeout(timeoutId) timeoutId = setTimeout(() => { fn(...args) timeoutId = null }, ms) }) as T & { cancel: () => void } debounced.cancel = () => { if (timeoutId) { clearTimeout(timeoutId) timeoutId = null } } return debounced } export function throttle) => void>( fn: T, ms: number ): T { let lastCall = 0 let timeoutId: ReturnType | null = null return ((...args: Parameters) => { const now = Date.now() const remaining = ms - (now - lastCall) if (remaining <= 0) { if (timeoutId) { clearTimeout(timeoutId) timeoutId = null } lastCall = now fn(...args) } else if (!timeoutId) { timeoutId = setTimeout(() => { lastCall = Date.now() timeoutId = null fn(...args) }, remaining) } }) as T } export function sleep(ms: number): Promise { return new Promise((resolve) => setTimeout(resolve, ms)) } export async function retry( fn: () => Promise, options: { attempts?: number delay?: number backoff?: number shouldRetry?: (error: unknown, attempt: number) => boolean } = {} ): Promise { const { attempts = 3, delay = 1000, backoff = 2, shouldRetry = () => true, } = options let lastError: unknown let currentDelay = delay for (let attempt = 1; attempt <= attempts; attempt++) { try { return await fn() } catch (error) { lastError = error if (attempt === attempts || !shouldRetry(error, attempt)) { throw error } await sleep(currentDelay) currentDelay *= backoff } } throw lastError } export async function retryResult( fn: () => Promise>, options: { attempts?: number delay?: number backoff?: number shouldRetry?: (error: E, attempt: number) => boolean } = {} ): Promise> { const { attempts = 3, delay = 1000, backoff = 2, shouldRetry = () => true, } = options let lastResult: Result | null = null let currentDelay = delay for (let attempt = 1; attempt <= attempts; attempt++) { const result = await fn() lastResult = result if (result.ok) { return result } if (attempt === attempts || !shouldRetry(result.error, attempt)) { return result } await sleep(currentDelay) currentDelay *= backoff } return lastResult! } export function timeout(promise: Promise, ms: number): Promise { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new Error(`Timeout after ${ms}ms`)) }, ms) promise .then((value) => { clearTimeout(timeoutId) resolve(value) }) .catch((error) => { clearTimeout(timeoutId) reject(error) }) }) } export async function timeoutResult( promise: Promise>, ms: number ): Promise> { try { return await timeout(promise, ms) } catch (e) { return err(e instanceof Error ? e : new Error(String(e))) } } export async function parallel( tasks: (() => Promise)[], concurrency: number ): Promise { const results: T[] = [] const executing: Promise[] = [] for (const task of tasks) { const p = task().then((result) => { results.push(result) }) executing.push(p) if (executing.length >= concurrency) { await Promise.race(executing) executing.splice( executing.findIndex((e) => e === p), 1 ) } } await Promise.all(executing) return results } export async function mapParallel( items: T[], fn: (item: T, index: number) => Promise, concurrency: number ): Promise { const results: U[] = new Array(items.length) const executing: Promise[] = [] for (let i = 0; i < items.length; i++) { const index = i const p = fn(items[index], index).then((result) => { results[index] = result }) executing.push(p) if (executing.length >= concurrency) { await Promise.race(executing) const doneIndex = executing.findIndex( (e) => (e as Promise & { _done?: boolean })._done !== false ) if (doneIndex >= 0) { executing.splice(doneIndex, 1) } } } await Promise.all(executing) return results } export function createAbortable( fn: (signal: AbortSignal) => Promise ): { promise: Promise; abort: () => void } { const controller = new AbortController() return { promise: fn(controller.signal), abort: () => controller.abort(), } } export interface Deferred { promise: Promise resolve: (value: T) => void reject: (error: unknown) => void } export function deferred(): Deferred { let resolve!: (value: T) => void let reject!: (error: unknown) => void const promise = new Promise((res, rej) => { resolve = res reject = rej }) return { promise, resolve, reject } }