/** * Unified backoff policy implementation */ import { BackoffPolicy } from './types'; import { DEFAULT_CONFIG } from './constants'; /** * Calculate backoff delay with Retry-After + jittered exponential caps */ export function calculateBackoffDelay( attempt: number, policy: BackoffPolicy, retryAfterMs?: number ): number { let delay: number; // Respect Retry-After header if present and enabled if (policy.respectRetryAfter && retryAfterMs !== undefined) { delay = Math.min(retryAfterMs, policy.retryAfterMaxMs || policy.maxDelayMs); } else { // Calculate base delay based on strategy switch (policy.strategy) { case 'exponential': delay = policy.baseDelayMs * Math.pow(2, attempt - 1); break; case 'linear': delay = policy.baseDelayMs * attempt; break; case 'fixed': delay = policy.baseDelayMs; break; default: delay = policy.baseDelayMs; } } // Apply jitter if enabled if (policy.jitterEnabled) { const jitterRange = delay * policy.jitterFactor; const jitter = (Math.random() - 0.5) * 2 * jitterRange; delay = Math.max(0, delay + jitter); } // Cap at maximum delay return Math.min(delay, policy.maxDelayMs); } /** * Create default backoff policy */ export function createDefaultBackoffPolicy(): BackoffPolicy { return { maxAttempts: 3, baseDelayMs: DEFAULT_CONFIG.baseDelayMs, maxDelayMs: DEFAULT_CONFIG.maxDelayMs, strategy: 'exponential', jitterEnabled: true, jitterFactor: DEFAULT_CONFIG.jitterFactor, respectRetryAfter: DEFAULT_CONFIG.respectRetryAfter, retryAfterMaxMs: DEFAULT_CONFIG.retryAfterMaxMs }; } /** * Create backoff policy for rate limiting */ export function createRateLimitBackoffPolicy(retryAfterMs: number): BackoffPolicy { return { maxAttempts: 5, baseDelayMs: retryAfterMs, maxDelayMs: Math.max(retryAfterMs * 2, DEFAULT_CONFIG.maxDelayMs), strategy: 'fixed', jitterEnabled: true, jitterFactor: 0.1, // ±10% jitter for rate limits respectRetryAfter: true, retryAfterMaxMs: retryAfterMs * 2 }; } /** * Create backoff policy for network errors */ export function createNetworkErrorBackoffPolicy(): BackoffPolicy { return { maxAttempts: 3, baseDelayMs: DEFAULT_CONFIG.baseDelayMs, maxDelayMs: DEFAULT_CONFIG.maxDelayMs, strategy: 'exponential', jitterEnabled: true, jitterFactor: DEFAULT_CONFIG.jitterFactor, respectRetryAfter: false }; } /** * Create backoff policy for server errors (5xx) */ export function createServerErrorBackoffPolicy(): BackoffPolicy { return { maxAttempts: 3, baseDelayMs: 2000, // Start with 2s for server errors maxDelayMs: DEFAULT_CONFIG.maxDelayMs, strategy: 'exponential', jitterEnabled: true, jitterFactor: 0.5, // ±50% jitter for server errors respectRetryAfter: false }; }