@equidam/mcp-server
Version:
Equidam MCP Server - Bridge between AI assistants and Equidam's company valuation API
146 lines (124 loc) • 3.99 kB
JavaScript
/**
* 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
};