/** * Timeout utility for wrapping promises with timeout protection */ export class TimeoutError extends Error { constructor(public operation: string, public timeoutMs: number) { super(`Operation '${operation}' timed out after ${timeoutMs}ms`); this.name = "TimeoutError"; } } /** * Wraps a promise with a timeout * @param promise The promise to wrap * @param timeoutMs Timeout in milliseconds * @param operation Name of the operation for error messages * @returns The result of the promise * @throws TimeoutError if the operation times out */ export async function withTimeout( promise: Promise, timeoutMs: number, operation: string ): Promise { const timeoutPromise = new Promise((_, reject) => { const timer = setTimeout(() => { reject(new TimeoutError(operation, timeoutMs)); }, timeoutMs); // Clean up the timer when the promise settles promise.finally(() => clearTimeout(timer)).catch(() => {}); }); return Promise.race([promise, timeoutPromise]); } /** * Creates a timeout controller that can be used with AbortController * @param timeoutMs Timeout in milliseconds * @param operation Name of the operation for error messages * @returns AbortController that aborts after timeout */ export function createTimeoutController(timeoutMs: number, operation: string): AbortController { const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(new TimeoutError(operation, timeoutMs)); }, timeoutMs); // Clean up if aborted manually controller.signal.addEventListener('abort', () => { clearTimeout(timeoutId); }); return controller; }