/** * Platform Service Error System * * This module provides a comprehensive error handling system for the Platform Service, * with structured error types, error codes, and consistent patterns for logging and recovery. */ // Export error codes and types export * from './error.codes.js'; // Export base error classes export * from './base.errors.js'; // Export domain-specific error classes export * from './email.errors.js'; export * from './mta.errors.js'; export * from './reputation.errors.js'; // Export utility function to create specific error types based on the error category import { getErrorClassForCategory } from './base.errors.js'; export { getErrorClassForCategory }; /** * Create a typed error from a standard Error * Useful for converting errors from external libraries or APIs * * @param error Standard error to convert * @param code Error code to assign * @param contextData Additional context data * @returns Typed PlatformError */ export function fromError( error: Error, code: string, contextData: Record = {} ) { // Import and use PlatformError const { PlatformError } = require('./base.errors.js'); const { ErrorSeverity, ErrorCategory, ErrorRecoverability } = require('./error.codes.js'); return new PlatformError( error.message, code, ErrorSeverity.MEDIUM, ErrorCategory.OPERATION, ErrorRecoverability.NON_RECOVERABLE, { data: { ...contextData, originalError: { name: error.name, message: error.message, stack: error.stack } } } ); } /** * Determine if an error is retryable * * @param error Error to check * @returns Boolean indicating if the error should be retried */ export function isRetryable(error: any): boolean { // If it's our platform error, use its recoverability property if (error && typeof error === 'object' && 'recoverability' in error) { const { ErrorRecoverability } = require('./error.codes.js'); return error.recoverability === ErrorRecoverability.RECOVERABLE || error.recoverability === ErrorRecoverability.MAYBE_RECOVERABLE || error.recoverability === ErrorRecoverability.TRANSIENT; } // Check if it's a network error (these are often transient) if (error && typeof error === 'object' && error.code) { const networkErrors = [ 'ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EHOSTUNREACH', 'ENETUNREACH', 'ENOTFOUND', 'EPROTO', 'ECONNABORTED' ]; return networkErrors.includes(error.code); } // By default, we can't determine if the error is retryable return false; } /** * Create a wrapped version of a function that catches errors * and converts them to typed PlatformErrors * * @param fn Function to wrap * @param errorCode Default error code to use * @param contextData Additional context data * @returns Wrapped function */ export function withErrorHandling Promise>( fn: T, errorCode: string, contextData: Record = {} ): T { return (async function(...args: Parameters): Promise> { try { return await fn(...args); } catch (error) { if (error && typeof error === 'object' && 'code' in error) { // Already a typed error, rethrow throw error; } throw fromError( error instanceof Error ? error : new Error(String(error)), errorCode, { ...contextData, fnName: fn.name, args: args.map(arg => typeof arg === 'object' ? '[Object]' : String(arg).substring(0, 100) ) } ); } }) as T; } /** * Retry a function with exponential backoff * * @param fn Function to retry * @param options Retry options * @returns Function result or throws after max retries */ export async function retry( fn: () => Promise, options: { maxRetries?: number; initialDelay?: number; maxDelay?: number; backoffFactor?: number; retryableErrors?: Array; } = {} ): Promise { const { maxRetries = 3, initialDelay = 1000, maxDelay = 30000, backoffFactor = 2, retryableErrors = [] } = options; let lastError: Error; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); // Check if we should retry const shouldRetry = attempt < maxRetries && ( isRetryable(error) || retryableErrors.some(pattern => { if (typeof pattern === 'string') { return lastError.message.includes(pattern); } return pattern.test(lastError.message); }) ); if (!shouldRetry) { throw lastError; } // Calculate delay with exponential backoff const delay = Math.min(initialDelay * Math.pow(backoffFactor, attempt), maxDelay); // Add jitter to prevent thundering herd problem (±20%) const jitter = 0.8 + Math.random() * 0.4; const actualDelay = Math.floor(delay * jitter); // Wait before next retry await new Promise(resolve => setTimeout(resolve, actualDelay)); } } // This should never happen, but TypeScript needs it throw lastError!; }