import logger from './logger.js'; export async function withRetry(fn, options = {}) { const { maxRetries = 3, baseDelay = 1000, maxDelay = 30000, jitter = true, retryableErrors = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'], onRetry = null, } = options; let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; const isRetryable = _isRetryableError(error, retryableErrors); if (!isRetryable || attempt === maxRetries) { throw error; } const delay = _calculateDelay(attempt, baseDelay, maxDelay, jitter); logger.warn( `Retry ${attempt + 1}/${maxRetries} after ${delay}ms: ${error.message}` ); if (onRetry) { await onRetry(error, attempt, delay); } await _sleep(delay); } } throw lastError; } export async function withTimeout(promise, ms, error = null) { let timeoutId; const timeout = new Promise((_, reject) => { timeoutId = setTimeout( () => reject(error || new Error(`Operation timed out after ${ms}ms`)), ms ); }); try { return await Promise.race([promise, timeout]); } finally { clearTimeout(timeoutId); } } export async function withRateLimit(fn, options = {}) { const { maxRetries = 5, baseDelay = 2000, maxDelay = 120000, } = options; return withRetry(fn, { maxRetries, baseDelay, maxDelay, retryableErrors: ['RATE_LIMITED', '403', '429'], onRetry: (error, attempt, delay) => { if (error.status === 403 || error.status === 429) { const resetTime = error.response?.headers?.['x-ratelimit-reset']; if (resetTime) { const waitTime = (parseInt(resetTime) * 1000) - Date.now(); if (waitTime > 0 && waitTime < maxDelay) { logger.info(`Rate limit reset in ${Math.round(waitTime / 1000)}s`); } } } }, }); } function _isRetryableError(error, retryableErrors) { if (!error) return false; if (error.code && retryableErrors.includes(error.code)) return true; if (error.status && (error.status >= 500 || error.status === 429)) return true; if (error.message && retryableErrors.some(r => error.message.includes(r))) return true; return false; } function _calculateDelay(attempt, baseDelay, maxDelay, jitter) { const exponential = baseDelay * Math.pow(2, attempt); const delay = Math.min(exponential, maxDelay); if (jitter) { const jitterAmount = delay * 0.1; return delay + (Math.random() - 0.5) * 2 * jitterAmount; } return delay; } function _sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } export default { withRetry, withTimeout, withRateLimit };