/** * General-purpose retry utility with exponential backoff. * * This module provides a shared, dependency-free retry primitive for use * anywhere in the CLEO core. Unlike the agent-specific retry in * `agents/retry.ts`, this utility has no database coupling and is safe to * import from any layer. * * Default schedule (3 attempts, task T040 spec): * - Attempt 1: immediate (0 ms delay before retry) * - Attempt 2: 2 000 ms delay before retry * - Attempt 3: 4 000 ms delay before retry * - After attempt 3: throw last error * * @module lib/retry */ /** * A predicate or pattern used to decide whether an error is retryable. * * - `RegExp` — matched against `error.message` (or `String(error)`) * - `(error: unknown) => boolean` — arbitrary predicate function */ export type RetryablePredicate = RegExp | ((error: unknown) => boolean); /** * Options that control retry behavior for {@link withRetry}. */ export interface RetryOptions { /** * Maximum total number of attempts (initial + retries). * * @default 3 */ maxAttempts?: number; /** * Delay before the second attempt in milliseconds. * Each subsequent delay is `baseDelayMs * 2^(attempt - 1)`. * * @default 2000 */ baseDelayMs?: number; /** * Upper bound on computed delay in milliseconds. * Prevents unbounded growth with many retries. * * @default 30000 */ maxDelayMs?: number; /** * Explicit list of patterns or predicates that identify retryable errors. * * When provided, ONLY errors matching at least one entry are retried. * Errors that match none of the entries cause immediate failure. * * When omitted, all errors are treated as retryable (up to `maxAttempts`). */ retryableErrors?: ReadonlyArray; } /** * Metadata attached to errors thrown after all retry attempts are exhausted. * * The last error from the final attempt is augmented with these fields so * callers can distinguish a retry-exhausted failure from a first-attempt one. */ export interface RetryContext { /** Total number of attempts made (always equal to `maxAttempts`). */ attempts: number; /** Cumulative delay applied across all retry waits in milliseconds. */ totalDelayMs: number; } /** * Execute an async function with automatic retry and exponential backoff. * * @remarks * The function is called up to `maxAttempts` times. After the first failure, * the utility waits `baseDelayMs` milliseconds, then retries. Each subsequent * wait doubles: `baseDelayMs * 2^(attempt - 1)`, capped at `maxDelayMs`. * * If `retryableErrors` is supplied, only errors matching at least one entry * are retried; other errors cause immediate re-throw. * * On final failure the original error is re-thrown. Use {@link RetryContext} * fields (attached to the error) to inspect retry metadata. * * @example * ```ts * // Basic usage — 3 attempts with 0 ms / 2 000 ms / 4 000 ms delays * const data = await withRetry(() => fetchFromApi()); * * // Custom retry window — only on network errors * const result = await withRetry( * () => db.query(sql), * { * maxAttempts: 5, * baseDelayMs: 500, * retryableErrors: [/SQLITE_BUSY/, /database is locked/i], * }, * ); * ``` * * @typeParam T - The resolved type of the async function * @param fn - Async factory that is called on each attempt. * @param options - Optional retry configuration. * @returns Resolved value of `fn` on success. * @throws The last error thrown by `fn`, augmented with {@link RetryContext} * fields (`attempts`, `totalDelayMs`). */ export declare function withRetry(fn: () => Promise, options?: RetryOptions): Promise; /** * Compute the wait time before the next attempt. * * @remarks * Formula: `min(baseDelayMs * 2^(attempt - 1), maxDelayMs)`. * On the first retry (`attempt === 1`) the delay is `baseDelayMs * 1 = baseDelayMs`. * * @example * ```ts * computeDelay(1, 2000, 30000); // 2000 * computeDelay(2, 2000, 30000); // 4000 * computeDelay(3, 2000, 30000); // 8000 * ``` * * @param attempt - The 1-based attempt number that just failed. * @param baseDelayMs - Base delay in milliseconds. * @param maxDelayMs - Maximum allowed delay in milliseconds. * @returns Delay in milliseconds before the next attempt. */ export declare function computeDelay(attempt: number, baseDelayMs: number, maxDelayMs: number): number; //# sourceMappingURL=retry.d.ts.map