Skip to main content
Glama
retry-utils.ts4.12 kB
import { RetryableError, APIQuotaExceededError } from '../types/errors.js'; /** * Retry configuration */ export interface RetryConfig { maxRetries: number; baseDelay: number; // Base delay in milliseconds maxDelay: number; // Maximum delay in milliseconds backoffMultiplier: number; // Exponential backoff multiplier } /** * Default retry configuration */ const DEFAULT_RETRY_CONFIG: RetryConfig = { maxRetries: 3, baseDelay: 1000, maxDelay: 10000, backoffMultiplier: 2, }; /** * Retry utility for handling API calls with exponential backoff */ export class RetryUtil { /** * Execute a function with retry logic */ static async executeWithRetry<T>( fn: () => Promise<T>, config: Partial<RetryConfig> = {} ): Promise<T> { const retryConfig = { ...DEFAULT_RETRY_CONFIG, ...config }; let lastError: Error; for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; // Don't retry on the last attempt if (attempt === retryConfig.maxRetries) { break; } // Check if error is retryable if (!this.isRetryableError(error)) { throw error; } // Calculate delay with exponential backoff const delay = this.calculateDelay(attempt, retryConfig); console.warn(`Attempt ${attempt + 1} failed, retrying in ${delay}ms:`, error); await this.sleep(delay); } } throw lastError!; } /** * Check if an error is retryable */ private static isRetryableError(error: any): boolean { // Network errors if (error.code === 'ECONNRESET' || error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT') { return true; } // HTTP status codes that are retryable if (error.status || error.statusCode) { const status = error.status || error.statusCode; return status >= 500 || status === 429; // Server errors or rate limiting } // Google API specific errors if (error.message?.includes('quota') || error.message?.includes('rate limit')) { return true; } // RetryableError instances if (error instanceof RetryableError) { return true; } return false; } /** * Calculate delay with exponential backoff */ private static calculateDelay(attempt: number, config: RetryConfig): number { const delay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt); return Math.min(delay, config.maxDelay); } /** * Sleep for specified milliseconds */ private static sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } /** * Handle Google API quota errors specifically */ static async handleQuotaError(error: any): Promise<never> { if (error.message?.includes('quota') || error.message?.includes('rate limit')) { throw new APIQuotaExceededError(); } throw error; } } /** * Rate limiter for API calls */ export class RateLimiter { private requests: number[] = []; private readonly maxRequests: number; private readonly windowMs: number; constructor(maxRequests: number = 60, windowMs: number = 60000) { this.maxRequests = maxRequests; this.windowMs = windowMs; } /** * Check if request is allowed and record it */ async checkRateLimit(): Promise<void> { const now = Date.now(); // Remove old requests outside the window this.requests = this.requests.filter(time => now - time < this.windowMs); // Check if we're at the limit if (this.requests.length >= this.maxRequests) { const oldestRequest = Math.min(...this.requests); const waitTime = this.windowMs - (now - oldestRequest); if (waitTime > 0) { await this.sleep(waitTime); return this.checkRateLimit(); } } // Record this request this.requests.push(now); } private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } }

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/ainetwork-ai/google-sheet-mcp'

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