shared-utils.ts
1 /** 2 * Shared utility functions used across the codebase. 3 * 4 * These replace ad-hoc patterns that were duplicated in 100+ files: 5 * - errorMessage: 231 occurrences across 112 files 6 * - safeJsonParse: 137 occurrences across 82 files 7 * - truncate: 62+ occurrences across 20+ files 8 * - hmrSingleton: 77 occurrences across 38 files 9 * - dedup/dedupBy: 40 occurrences across 28 files 10 * - sleep: 25 occurrences across 17 files 11 */ 12 13 /** Extract a human-readable error message from an unknown catch value. */ 14 export function errorMessage(err: unknown): string { 15 return err instanceof Error ? err.message : String(err) 16 } 17 18 /** Parse JSON with a fallback value instead of throwing. */ 19 export function safeJsonParse<T>(json: string, fallback: T): T { 20 try { 21 return JSON.parse(json) as T 22 } catch { 23 return fallback 24 } 25 } 26 27 /** Truncate a string to `limit` characters, optionally appending a suffix. */ 28 export function truncate(s: string, limit: number, suffix = ''): string { 29 if (s.length <= limit) return s 30 const cutoff = Math.max(0, limit - suffix.length) 31 return s.slice(0, cutoff) + suffix 32 } 33 34 /** 35 * HMR-safe singleton on globalThis. Survives Next.js hot module reloads. 36 * Replaces the ad-hoc `__swarmclaw_*` pattern scattered across 38 files. 37 */ 38 export function hmrSingleton<T>(key: string, init: () => T): T { 39 const g = globalThis as Record<string, unknown> 40 return (g[key] ??= init()) as T 41 } 42 43 /** Deduplicate an array preserving insertion order. */ 44 export function dedup<T>(arr: T[]): T[] { 45 return [...new Set(arr)] 46 } 47 48 /** Deduplicate an array by a key function, keeping the first occurrence. */ 49 export function dedupBy<T>(arr: T[], key: (item: T) => string): T[] { 50 const seen = new Set<string>() 51 return arr.filter((item) => { 52 const k = key(item) 53 if (seen.has(k)) return false 54 seen.add(k) 55 return true 56 }) 57 } 58 59 /** Promise-based sleep. Replaces `await new Promise(r => setTimeout(r, ms))`. */ 60 export function sleep(ms: number): Promise<void> { 61 return new Promise((resolve) => setTimeout(resolve, ms)) 62 } 63 64 /** 65 * Exponential backoff with 0-10% random jitter. 66 * Prevents thundering-herd when many callers retry simultaneously. 67 */ 68 export function jitteredBackoff(baseMs: number, attempt: number, maxMs: number): number { 69 const exponential = Math.min(baseMs * Math.pow(2, attempt), maxMs) 70 const jitter = exponential * (Math.random() * 0.1) 71 return Math.round(exponential + jitter) 72 }