import { err, type Result } from "../types/result.ts"; 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 }; }