2025-05-08 12:46:10 +00:00

193 lines
5.3 KiB
TypeScript

/**
* 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<string, any> = {}
) {
// 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<T extends (...args: any[]) => Promise<any>>(
fn: T,
errorCode: string,
contextData: Record<string, any> = {}
): T {
return (async function(...args: Parameters<T>): Promise<ReturnType<T>> {
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<T>(
fn: () => Promise<T>,
options: {
maxRetries?: number;
initialDelay?: number;
maxDelay?: number;
backoffFactor?: number;
retryableErrors?: Array<string | RegExp>;
} = {}
): Promise<T> {
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!;
}