UNPKG

hook-engine

Version:

Production-grade webhook engine with comprehensive adapter support, security, reliability, structured logging, and CLI tools.

211 lines (210 loc) 6.95 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorHandler = void 0; exports.initializeErrorHandler = initializeErrorHandler; exports.getGlobalErrorHandler = getGlobalErrorHandler; const base_1 = require("./base"); /** * Centralized error handler for the hook engine */ class ErrorHandler { constructor(config, reporter) { this.errorCounts = new Map(); this.lastErrorTime = new Map(); this.config = config; this.reporter = reporter; } /** * Handle an error with context and strategy */ async handle(error, context) { // Convert to HookEngineError if needed const hookError = this.normalizeError(error, context); // Log the error this.logError(hookError, context); // Report to external service if configured if (this.reporter) { try { await this.reporter.report(hookError, context); } catch (reportError) { console.error('Failed to report error:', reportError); } } // Track error frequency this.trackError(hookError, context); // Execute strategy await this.executeStrategy(hookError, context); } /** * Handle batch of errors */ async handleBatch(errors) { if (this.reporter) { try { const normalizedErrors = errors.map(({ error, context }) => ({ error: this.normalizeError(error, context), context })); await this.reporter.reportBatch(normalizedErrors); } catch (reportError) { console.error('Failed to report batch errors:', reportError); } } // Handle each error individually for strategy execution for (const { error, context } of errors) { await this.handle(error, context); } } /** * Check if error should be retried based on strategy */ shouldRetry(error, attempt) { if (!(error instanceof base_1.HookEngineError)) { return false; } if (!error.retryable) { return false; } if (this.config.maxRetries && attempt >= this.config.maxRetries) { return false; } // Check if we're in a circuit breaker state if (this.isCircuitBreakerOpen(error.code)) { return false; } return true; } /** * Get retry delay based on strategy */ getRetryDelay(attempt) { return this.config.retryDelay || 1000 * Math.pow(2, attempt - 1); } /** * Reset error tracking for a specific error type */ resetErrorTracking(errorCode) { this.errorCounts.delete(errorCode); this.lastErrorTime.delete(errorCode); } /** * Get error statistics */ getErrorStats() { const stats = {}; for (const [errorCode, count] of this.errorCounts.entries()) { stats[errorCode] = { count, lastOccurrence: this.lastErrorTime.get(errorCode) || 0 }; } return stats; } normalizeError(error, context) { if (error instanceof base_1.HookEngineError) { return error; } // Convert standard errors to HookEngineError return new (class extends base_1.HookEngineError { constructor() { super(error.message, 'UNKNOWN_ERROR', context, false); this.stack = error.stack; } })(); } logError(error, context) { const logData = { timestamp: new Date().toISOString(), error: error.toJSON(), context, strategy: this.config.strategy }; switch (this.config.strategy) { case 'fail-fast': console.error('❌ FAIL-FAST Error:', logData); break; case 'retry': console.warn('🔄 RETRY Error:', logData); break; case 'ignore': console.debug('🔇 IGNORED Error:', logData); break; case 'dead-letter': console.error('💀 DEAD-LETTER Error:', logData); break; } } trackError(error, context) { const errorCode = error.code; const currentCount = this.errorCounts.get(errorCode) || 0; this.errorCounts.set(errorCode, currentCount + 1); this.lastErrorTime.set(errorCode, Date.now()); } async executeStrategy(error, context) { switch (this.config.strategy) { case 'fail-fast': throw error; case 'retry': // Retry logic is handled by the retry system break; case 'ignore': // Do nothing, just log break; case 'dead-letter': await this.sendToDeadLetter(error, context); break; } // Execute fallback action if configured if (this.config.fallbackAction) { try { await this.config.fallbackAction(); } catch (fallbackError) { console.error('Fallback action failed:', fallbackError); } } // Send notification if configured if (this.config.notifyOnError) { await this.sendErrorNotification(error, context); } } isCircuitBreakerOpen(errorCode) { const count = this.errorCounts.get(errorCode) || 0; const lastTime = this.lastErrorTime.get(errorCode) || 0; const timeSinceLastError = Date.now() - lastTime; // Simple circuit breaker: if more than 5 errors in the last 5 minutes const threshold = 5; const timeWindow = 5 * 60 * 1000; // 5 minutes return count >= threshold && timeSinceLastError < timeWindow; } async sendToDeadLetter(error, context) { // TODO: Implement dead letter queue console.error('💀 Sending to dead letter queue:', { error: error.toJSON(), context }); } async sendErrorNotification(error, context) { // TODO: Implement notification system (email, Slack, etc.) console.error('🚨 Error notification:', { error: error.toJSON(), context }); } } exports.ErrorHandler = ErrorHandler; /** * Global error handler instance */ let globalErrorHandler; /** * Initialize global error handler */ function initializeErrorHandler(config, reporter) { globalErrorHandler = new ErrorHandler(config, reporter); } /** * Get global error handler */ function getGlobalErrorHandler() { if (!globalErrorHandler) { throw new Error('Error handler not initialized. Call initializeErrorHandler first.'); } return globalErrorHandler; }