Skip to main content
Glama
errors.ts9.07 kB
/** * Custom error types for better error handling and recovery * Following WCGW: Specific errors for specific failures */ export class BaseError extends Error { public readonly isOperational: boolean; public readonly statusCode: number; public readonly timestamp: Date; public readonly context?: Record<string, any>; constructor( message: string, statusCode: number = 500, isOperational: boolean = true, context?: Record<string, any> ) { super(message); this.name = this.constructor.name; this.statusCode = statusCode; this.isOperational = isOperational; this.timestamp = new Date(); this.context = context; Error.captureStackTrace(this, this.constructor); } toJSON(): Record<string, any> { return { name: this.name, message: this.message, statusCode: this.statusCode, isOperational: this.isOperational, timestamp: this.timestamp, context: this.context, stack: this.stack }; } } export class ValidationError extends BaseError { public readonly field?: string; public readonly value?: any; constructor(message: string, field?: string, value?: any) { super(message, 400, true, { field, value }); this.field = field; this.value = value; } } export class AuthenticationError extends BaseError { constructor(message: string = 'Authentication failed') { super(message, 401, true); } } export class AuthorizationError extends BaseError { constructor(message: string = 'Access denied') { super(message, 403, true); } } export class NotFoundError extends BaseError { constructor(resource: string, identifier?: string | number) { const message = identifier ? `${resource} with identifier '${identifier}' not found` : `${resource} not found`; super(message, 404, true, { resource, identifier }); } } export class ConflictError extends BaseError { constructor(message: string, context?: Record<string, any>) { super(message, 409, true, context); } } export class RateLimitError extends BaseError { public readonly retryAfter: number; constructor(retryAfter: number) { super('Rate limit exceeded', 429, true); this.retryAfter = retryAfter; } } export class ResourceExhaustedError extends BaseError { public readonly resourceType: string; public readonly limit: number; public readonly current: number; constructor(resourceType: string, limit: number, current: number) { super( `Resource limit exceeded for ${resourceType}: ${current}/${limit}`, 503, true, { resourceType, limit, current } ); this.resourceType = resourceType; this.limit = limit; this.current = current; } } export class TimeoutError extends BaseError { public readonly operation: string; public readonly timeoutMs: number; constructor(operation: string, timeoutMs: number) { super( `Operation '${operation}' timed out after ${timeoutMs}ms`, 504, true, { operation, timeoutMs } ); this.operation = operation; this.timeoutMs = timeoutMs; } } export class ExternalServiceError extends BaseError { public readonly service: string; public readonly originalError?: any; constructor(service: string, message: string, originalError?: any) { super( `External service error (${service}): ${message}`, 502, true, { service, originalError } ); this.service = service; this.originalError = originalError; } } export class DatabaseError extends BaseError { public readonly query?: string; public readonly params?: any[]; constructor(message: string, query?: string, params?: any[]) { super(message, 500, false, { query, params }); this.query = query; this.params = params; } } /** * Error handler with recovery strategies */ export class ErrorHandler { private static retryStrategies = new Map<string, number>([ ['TimeoutError', 3], ['ExternalServiceError', 2], ['DatabaseError', 1] ]); /** * Handle error with appropriate recovery */ static async handle( error: Error, context?: Record<string, any> ): Promise<void> { // Log the error const { logger } = await import('./logger.js'); if (error instanceof BaseError) { if (error.isOperational) { logger.warn('Operational error', error.toJSON()); } else { logger.error('Non-operational error', error, context); } } else { logger.error('Unexpected error', error, context); } // Log error for monitoring // Skip emitting events to avoid type conflicts } /** * Wrap async function with error handling */ static wrapAsync<T extends (...args: any[]) => Promise<any>>( fn: T, context?: string ): T { return (async (...args: Parameters<T>) => { try { return await fn(...args); } catch (error) { await ErrorHandler.handle(error as Error, { context, args }); throw error; } }) as T; } /** * Retry operation with exponential backoff */ static async retry<T>( operation: () => Promise<T>, options: { maxAttempts?: number; initialDelay?: number; maxDelay?: number; factor?: number; onRetry?: (error: Error, attempt: number) => void; } = {} ): Promise<T> { const { maxAttempts = 3, initialDelay = 100, maxDelay = 5000, factor = 2, onRetry } = options; let lastError: Error; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error; if (attempt === maxAttempts) { throw lastError; } // Check if error is retryable const shouldRetry = this.shouldRetry(lastError); if (!shouldRetry) { throw lastError; } // Calculate delay with exponential backoff const delay = Math.min( initialDelay * Math.pow(factor, attempt - 1), maxDelay ); if (onRetry) { onRetry(lastError, attempt); } await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError!; } /** * Determine if error should be retried */ private static shouldRetry(error: Error): boolean { // Network errors if (error.message.includes('ECONNREFUSED') || error.message.includes('ETIMEDOUT') || error.message.includes('ENOTFOUND')) { return true; } // Specific error types if (error instanceof TimeoutError || error instanceof ExternalServiceError) { return true; } // Database errors (only some) if (error instanceof DatabaseError) { return error.message.includes('deadlock') || error.message.includes('timeout') || error.message.includes('connection'); } return false; } /** * Circuit breaker pattern */ static createCircuitBreaker<T>( fn: (...args: any[]) => Promise<T>, options: { threshold?: number; timeout?: number; resetTimeout?: number; } = {} ): (...args: any[]) => Promise<T> { const { threshold = 5, timeout = 60000, resetTimeout = 30000 } = options; let failures = 0; let lastFailureTime = 0; let state: 'closed' | 'open' | 'half-open' = 'closed'; return async (...args: any[]): Promise<T> => { // Check if circuit should be reset if (state === 'open' && Date.now() - lastFailureTime > resetTimeout) { state = 'half-open'; failures = 0; } // If circuit is open, fail fast if (state === 'open') { throw new Error('Circuit breaker is open'); } try { const result = await Promise.race([ fn(...args), new Promise<never>((_, reject) => setTimeout(() => reject(new TimeoutError('Circuit breaker timeout', timeout)), timeout) ) ]); // Success - reset failures if (state === 'half-open') { state = 'closed'; } failures = 0; return result; } catch (error) { failures++; lastFailureTime = Date.now(); if (failures >= threshold) { state = 'open'; } throw error; } }; } } /** * Global error handlers */ export function setupGlobalErrorHandlers(): void { process.on('uncaughtException', (error: Error) => { ErrorHandler.handle(error, { type: 'uncaughtException' }); // Graceful shutdown process.exit(1); }); process.on('unhandledRejection', (reason: any, promise: Promise<any>) => { ErrorHandler.handle( new Error(`Unhandled rejection: ${reason}`), { type: 'unhandledRejection', promise } ); }); }

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/Rajawatrajat/mcp-software-engineer'

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