193 lines
5.3 KiB
TypeScript
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!;
|
|
} |