// Retry Logic for API Calls // Exponential backoff with jitter interface HttpError extends Error { response?: { status: number; statusText: string; }; } export interface RetryOptions { maxRetries?: number; initialDelay?: number; maxDelay?: number; backoffMultiplier?: number; shouldRetry?: (error: Error | HttpError) => boolean; } const defaultOptions: Required = { maxRetries: 3, initialDelay: 1000, // 1 second maxDelay: 30000, // 30 seconds backoffMultiplier: 2, shouldRetry: (error) => { // Retry on network errors and 5xx server errors const httpError = error as HttpError; if (!httpError.response) return true; // Network error const status = httpError.response.status; return status >= 500 && status < 600; }, }; function calculateDelay(attempt: number, options: Required): number { const delay = options.initialDelay * Math.pow(options.backoffMultiplier, attempt); const jitter = Math.random() * 0.3 * delay; // Add 0-30% jitter return Math.min(delay + jitter, options.maxDelay); } export async function withRetry( fn: () => Promise, options: RetryOptions = {} ): Promise { const opts = { ...defaultOptions, ...options }; let lastError: Error | HttpError | unknown; for (let attempt = 0; attempt <= opts.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; // Don't retry if it's the last attempt or if we shouldn't retry this error if (attempt === opts.maxRetries || !opts.shouldRetry(error as Error)) { throw error; } // Wait before retrying const delay = calculateDelay(attempt, opts); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError; } // Wrapper for fetch with automatic retries export async function fetchWithRetry( url: string, options?: RequestInit, retryOptions?: RetryOptions ): Promise { return withRetry(async () => { const response = await fetch(url, options); if (!response.ok) { const error = new Error(`HTTP ${response.status}: ${response.statusText}`) as HttpError; error.response = { status: response.status, statusText: response.statusText }; throw error; } return response; }, retryOptions); }