UNPKG

@equidam/mcp-server

Version:

Equidam MCP Server - Bridge between AI assistants and Equidam's company valuation API

146 lines (124 loc) 3.99 kB
/** * Retry logic utilities for Equidam MCP Server */ const { isRetryableError } = require('./errors'); /** * Default retry configuration */ const DEFAULT_RETRY_CONFIG = { retries: 3, retryDelayBase: 1000, // Base delay in milliseconds retryDelayMax: 30000, // Maximum delay in milliseconds retryDelayMultiplier: 2, // Exponential backoff multiplier jitter: true // Add random jitter to prevent thundering herd }; /** * Calculate retry delay with exponential backoff and jitter * @param {number} retryCount - Current retry attempt (0-based) * @param {object} config - Retry configuration * @returns {number} - Delay in milliseconds */ function calculateRetryDelay(retryCount, config = DEFAULT_RETRY_CONFIG) { const { retryDelayBase, retryDelayMax, retryDelayMultiplier, jitter } = config; // Calculate exponential backoff delay let delay = retryDelayBase * Math.pow(retryDelayMultiplier, retryCount); // Cap at maximum delay delay = Math.min(delay, retryDelayMax); // Add jitter to prevent thundering herd problem if (jitter) { // Add random variation of ±25% const jitterRange = delay * 0.25; const jitterOffset = (Math.random() - 0.5) * 2 * jitterRange; delay += jitterOffset; } return Math.max(0, Math.floor(delay)); } /** * Sleep for specified milliseconds * @param {number} ms - Milliseconds to sleep * @returns {Promise} - Promise that resolves after delay */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Retry an async function with exponential backoff * @param {Function} fn - Async function to retry * @param {object} config - Retry configuration * @param {Function} debugLog - Debug logging function * @returns {Promise} - Promise that resolves with function result or rejects with final error */ async function withRetry(fn, config = DEFAULT_RETRY_CONFIG, debugLog = () => {}) { const { retries } = { ...DEFAULT_RETRY_CONFIG, ...config }; let lastError; let attempt = 0; while (attempt <= retries) { try { const result = await fn(); if (attempt > 0) { debugLog(`Operation succeeded on attempt ${attempt + 1}`); } return result; } catch (error) { lastError = error; // Check if we should retry this error if (!isRetryableError(error)) { debugLog(`Non-retryable error: ${error.message}`); throw error; } // Check if we have retries left if (attempt >= retries) { debugLog(`All ${retries + 1} attempts failed. Final error: ${error.message}`); throw error; } // Calculate delay for next retry const delay = calculateRetryDelay(attempt, config); debugLog(`Attempt ${attempt + 1} failed: ${error.message}. Retrying in ${delay}ms...`); // Wait before retrying await sleep(delay); attempt++; } } // This should never be reached, but just in case throw lastError; } /** * Create a retry wrapper function with predefined configuration * @param {object} config - Retry configuration * @param {Function} debugLog - Debug logging function * @returns {Function} - Retry wrapper function */ function createRetryWrapper(config = DEFAULT_RETRY_CONFIG, debugLog = () => {}) { return (fn) => withRetry(fn, config, debugLog); } /** * Retry configuration specifically for HTTP requests */ const HTTP_RETRY_CONFIG = { ...DEFAULT_RETRY_CONFIG, retries: 3, retryDelayBase: 1000, retryDelayMax: 10000, retryDelayMultiplier: 2, jitter: true }; /** * Retry configuration for rate limit errors (longer delays) */ const RATE_LIMIT_RETRY_CONFIG = { ...DEFAULT_RETRY_CONFIG, retries: 2, retryDelayBase: 5000, retryDelayMax: 60000, retryDelayMultiplier: 2, jitter: true }; module.exports = { DEFAULT_RETRY_CONFIG, HTTP_RETRY_CONFIG, RATE_LIMIT_RETRY_CONFIG, calculateRetryDelay, sleep, withRetry, createRetryWrapper };