Skip to main content
Glama
retry.ts4.04 kB
/** * Retry logic for API operations * Handles retryable errors with configurable backoff strategies */ import { ApiError } from '../../types/api-operations.js'; import { createScopedLogger } from '../../utils/logger.js'; /** * Configuration options for API call retry */ export interface RetryConfig { /** Maximum number of retry attempts */ maxRetries: number; /** Initial delay in milliseconds before the first retry */ initialDelay: number; /** Maximum delay in milliseconds between retries */ maxDelay: number; /** Whether to use exponential backoff for retry delays */ useExponentialBackoff: boolean; /** HTTP status codes that should trigger a retry */ retryableStatusCodes: number[]; } /** * Default retry configuration */ export const DEFAULT_RETRY_CONFIG: RetryConfig = { maxRetries: 3, initialDelay: 1000, // 1 second maxDelay: 10000, // 10 seconds useExponentialBackoff: true, retryableStatusCodes: [408, 429, 500, 502, 503, 504], }; /** * Calculate delay time for retry with optional exponential backoff * * @param attempt - Current attempt number (0-based) * @param config - Retry configuration * @returns Delay time in milliseconds */ export function calculateRetryDelay( attempt: number, config: RetryConfig ): number { if (!config.useExponentialBackoff) { return config.initialDelay; } // Exponential backoff with jitter const exponentialDelay = config.initialDelay * Math.pow(2, attempt); const jitter = Math.random() * 0.5 + 0.75; // Random value between 0.75 and 1.25 const delay = exponentialDelay * jitter; // Cap at maximum delay return Math.min(delay, config.maxDelay); } /** * Sleep for a specified duration * * @param ms - Duration in milliseconds * @returns Promise that resolves after the specified duration */ function sleep(ms: number): Promise<void> { return new Promise((resolve) => setTimeout(resolve, ms)); } /** * Determines if an error should trigger a retry * * @param error - Error to check * @param config - Retry configuration * @returns Whether the error should trigger a retry */ export function isRetryableError( error: ApiError, config: RetryConfig ): boolean { // Network errors should be retried if (!error.response) { return true; } // Check if status code is in the retryable list const statusCode = error.response.status; // Never retry 4xx client errors (400, 401, 403, 404, etc.) as they won't succeed on retry if (statusCode >= 400 && statusCode < 500) { return false; } return config.retryableStatusCodes.includes(statusCode); } /** * Execute an API call with retry logic * * @param fn - Function that returns a promise for the API call * @param config - Retry configuration * @returns Promise that resolves with the API response */ export async function callWithRetry<T>( fn: () => Promise<T>, config: Partial<RetryConfig> = {} ): Promise<T> { // Merge with default config const retryConfig: RetryConfig = { ...DEFAULT_RETRY_CONFIG, ...config, }; let attempt = 0; let lastError: unknown; while (attempt <= retryConfig.maxRetries) { try { return await fn(); } catch (error: unknown) { lastError = error; // Check if we should retry if ( attempt >= retryConfig.maxRetries || !isRetryableError(error as ApiError, retryConfig) ) { throw error; } // Calculate delay and wait before retrying const delay = calculateRetryDelay(attempt, retryConfig); await sleep(delay); attempt++; // Log retry attempt if in development if (process.env.NODE_ENV === 'development') { const log = createScopedLogger('api.retry', 'callWithRetry'); log.warn('Retrying API call', { attempt, maxRetries: retryConfig.maxRetries, delay, }); } } } // This should never be reached due to the throw in the catch block, // but TypeScript needs it for type safety throw lastError; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kesslerio/attio-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server