TypeScript 97.4%
SQL 1%
JavaScript 0.9%
CSS 0.6%
1import { PolyProviderError } from "./types";2import { sleep } from "@/lib/utils";34export interface RetryOptions {5 maxAttempts?: number;6 baseDelayMs?: number;7 maxDelayMs?: number;8 signal?: AbortSignal;9 onRetry?: (attempt: number, error: PolyProviderError, delayMs: number) => void;10}1112/**13 * Exponential backoff with full jitter. Only retries errors flagged `retryable`14 * (429 / 5xx / network / timeout). Auth, invalid request and unsupported-parameter15 * errors are never retried. Respects Retry-After when present.16 */17export async function withRetry<T>(fn: (attempt: number) => Promise<T>, normalize: (e: unknown) => PolyProviderError, opts: RetryOptions = {}): Promise<T> {18 const max = opts.maxAttempts ?? 3;19 const base = opts.baseDelayMs ?? 500;20 const cap = opts.maxDelayMs ?? 8_000;21 let attempt = 0;22 while (true) {23 attempt++;24 try {25 return await fn(attempt);26 } catch (raw) {27 const err = normalize(raw);28 if (!err.retryable || attempt >= max || opts.signal?.aborted || err.code === "CANCELLED") throw err;29 const backoff = Math.min(cap, base * 2 ** (attempt - 1));30 const delay = err.retryAfterMs ?? Math.round(Math.random() * backoff);31 opts.onRetry?.(attempt, err, delay);32 await sleep(delay, opts.signal);33 }34 }35}36