mcp-quickbase
Version:
Work with Quickbase via Model Context Protocol
88 lines • 3.42 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.calculateBackoff = calculateBackoff;
exports.withRetry = withRetry;
const logger_1 = require("./logger");
const logger = (0, logger_1.createLogger)("RetryUtil");
/**
* Default retry options
*/
const DEFAULT_RETRY_OPTIONS = {
maxRetries: 3,
baseDelay: 1000,
maxDelay: 10000,
backoffFactor: 2,
isRetryable: (error) => {
// Default logic for determining if an error is retryable
if (!error)
return false;
// Handle fetch response errors
if (typeof error === "object" && error !== null && "status" in error) {
const httpError = error;
// Retry 429 (Too Many Requests), 408 (Request Timeout), and 5xx errors
return (httpError.status === 429 ||
httpError.status === 408 ||
(httpError.status >= 500 && httpError.status < 600));
}
// Handle network errors
if (error instanceof Error) {
return (error.message.includes("network") ||
error.message.includes("timeout") ||
error.message.includes("connection"));
}
return false;
},
};
/**
* Calculates the delay time for a retry attempt with exponential backoff
* @param attempt Retry attempt number (0-based)
* @param options Retry options
* @returns Delay time in milliseconds
*/
function calculateBackoff(attempt, options) {
const { baseDelay, backoffFactor = 2, maxDelay = 10000 } = options;
// Use exponential backoff with jitter
const exponentialDelay = baseDelay * Math.pow(backoffFactor, attempt);
const jitter = Math.random() * 0.2 * exponentialDelay; // 20% jitter
const delay = exponentialDelay + jitter;
// Ensure delay doesn't exceed maximum
return Math.min(delay, maxDelay);
}
/**
* Wrapper function that adds retry logic to any async function
* @param fn Function to add retry logic to
* @param options Retry options
* @returns Function with retry logic
*/
function withRetry(fn, options = {}) {
const fullOptions = { ...DEFAULT_RETRY_OPTIONS, ...options };
return async function retryWrapper(...args) {
let lastError;
for (let attempt = 0; attempt <= fullOptions.maxRetries; attempt++) {
try {
if (attempt > 0) {
logger.info(`Retry attempt ${attempt} of ${fullOptions.maxRetries}`);
}
return await fn(...args);
}
catch (error) {
lastError = error;
const shouldRetry = attempt < fullOptions.maxRetries &&
fullOptions.isRetryable &&
fullOptions.isRetryable(error);
if (!shouldRetry) {
logger.debug("Error not retryable or max retries reached", { error });
throw error;
}
const delay = calculateBackoff(attempt, fullOptions);
logger.debug(`Retrying after ${delay}ms due to error`, { error });
// Wait before retrying
await new Promise((resolve) => setTimeout(resolve, delay));
}
}
// This should never be reached due to the loop structure,
// but TypeScript requires it for type safety
throw lastError;
};
}
//# sourceMappingURL=retry.js.map