UNPKG

supa-seed

Version:

A constraint-aware, framework-agnostic database seeding framework with deep PostgreSQL business logic discovery and MakerKit integration support

327 lines โ€ข 11.7 kB
"use strict"; /** * Production Error Handling System * Phase 6, Checkpoint F1 - Comprehensive error handling with logging and recovery */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorHandler = exports.SupaSeedError = void 0; exports.handleErrors = handleErrors; exports.withErrorHandling = withErrorHandling; const logger_1 = require("./logger"); class SupaSeedError extends Error { constructor(message, code, context, recoveryOptions = { retryable: false }, severity = 'medium') { super(message); this.name = 'SupaSeedError'; this.code = code; this.context = context; this.recoveryOptions = recoveryOptions; this.severity = severity; // Capture stack trace if (Error.captureStackTrace) { Error.captureStackTrace(this, SupaSeedError); } this.context.stackTrace = this.stack; } toJSON() { return { name: this.name, message: this.message, code: this.code, context: this.context, recoveryOptions: this.recoveryOptions, severity: this.severity, stack: this.stack }; } } exports.SupaSeedError = SupaSeedError; class ErrorHandler { /** * Handle an error with comprehensive logging and recovery */ static async handle(error, context) { const supaSeedError = error instanceof SupaSeedError ? error : this.wrapError(error, context); // Log the error this.logError(supaSeedError); // Track error frequency this.trackErrorFrequency(supaSeedError); // Add to history this.addToHistory(supaSeedError); // Attempt recovery if specified if (supaSeedError.recoveryOptions.retryable) { await this.attemptRecovery(supaSeedError); } // Notify monitoring systems this.notifyMonitoring(supaSeedError); } /** * Wrap a generic error in SupaSeedError */ static wrapError(error, context) { const errorContext = { operation: context?.operation || 'unknown', component: context?.component || 'unknown', userId: context?.userId, metadata: context?.metadata || {}, timestamp: new Date(), stackTrace: error.stack }; return new SupaSeedError(error.message, 'WRAPPED_ERROR', errorContext, { retryable: false }, 'medium'); } /** * Log error with appropriate severity */ static logError(error) { const logData = { code: error.code, message: error.message, component: error.context.component, operation: error.context.operation, severity: error.severity, metadata: error.context.metadata, userId: error.context.userId }; switch (error.severity) { case 'critical': logger_1.Logger.error('๐Ÿšจ CRITICAL ERROR:', logData); break; case 'high': logger_1.Logger.error('โŒ HIGH SEVERITY ERROR:', logData); break; case 'medium': logger_1.Logger.warn('โš ๏ธ ERROR:', logData); break; case 'low': logger_1.Logger.info('โ„น๏ธ Minor issue:', logData); break; } } /** * Track error frequency for pattern detection */ static trackErrorFrequency(error) { const key = `${error.code}:${error.context.component}:${error.context.operation}`; const current = this.errorCounts.get(key) || 0; this.errorCounts.set(key, current + 1); // Alert on high frequency errors if (current + 1 >= 10) { logger_1.Logger.error('๐Ÿ”„ High frequency error detected:', { errorPattern: key, occurrences: current + 1, recommendation: 'Consider implementing circuit breaker or permanent fix' }); } } /** * Add error to history for analysis */ static addToHistory(error) { this.errorHistory.push({ error, handled: new Date() }); // Maintain history size if (this.errorHistory.length > this.MAX_HISTORY) { this.errorHistory.shift(); } } /** * Attempt error recovery based on options */ static async attemptRecovery(error) { logger_1.Logger.info('๐Ÿ”„ Attempting error recovery:', { code: error.code, strategy: error.recoveryOptions.fallbackStrategy }); // Implementation would depend on specific recovery strategies // For now, just log the attempt } /** * Notify external monitoring systems */ static notifyMonitoring(error) { // This would integrate with monitoring services like Sentry, DataDog, etc. // For now, we'll simulate with logging if (error.severity === 'critical' || error.severity === 'high') { logger_1.Logger.error('๐Ÿ“ก Notifying monitoring systems:', { code: error.code, severity: error.severity, component: error.context.component }); } } /** * Get error statistics for monitoring */ static getErrorStats() { const stats = { totalErrors: this.errorHistory.length, errorsByComponent: {}, errorsBySeverity: {}, topErrors: [], recentErrors: [] }; // Analyze error history for (const entry of this.errorHistory) { const { error } = entry; // Count by component stats.errorsByComponent[error.context.component] = (stats.errorsByComponent[error.context.component] || 0) + 1; // Count by severity stats.errorsBySeverity[error.severity] = (stats.errorsBySeverity[error.severity] || 0) + 1; } // Get top errors stats.topErrors = Array.from(this.errorCounts.entries()) .map(([pattern, count]) => ({ pattern, count })) .sort((a, b) => b.count - a.count) .slice(0, 10); // Get recent errors stats.recentErrors = this.errorHistory .slice(-20) .map(entry => ({ code: entry.error.code, component: entry.error.context.component, timestamp: entry.handled })); return stats; } /** * Clear error history and counters */ static clearHistory() { this.errorHistory.length = 0; this.errorCounts.clear(); logger_1.Logger.info('๐Ÿงน Error history cleared'); } /** * Create common error types with predefined configurations */ static createDatabaseError(message, context) { return new SupaSeedError(message, 'DATABASE_ERROR', { ...context, component: context.component || 'database', operation: context.operation || 'query', timestamp: new Date() }, { retryable: true, maxRetries: 3, fallbackStrategy: 'graceful', userMessage: 'Database operation failed. Please try again.', technicalMessage: `Database error: ${message}` }, 'high'); } static createAIError(message, context) { return new SupaSeedError(message, 'AI_SERVICE_ERROR', { ...context, component: context.component || 'ai', operation: context.operation || 'generation', timestamp: new Date() }, { retryable: true, maxRetries: 2, fallbackStrategy: 'graceful', userMessage: 'AI service temporarily unavailable. Using fallback generation.', technicalMessage: `AI error: ${message}` }, 'medium'); } static createConfigurationError(message, context) { return new SupaSeedError(message, 'CONFIGURATION_ERROR', { ...context, component: context.component || 'config', operation: context.operation || 'validation', timestamp: new Date() }, { retryable: false, fallbackStrategy: 'fail-fast', userMessage: 'Configuration error detected. Please check your settings.', technicalMessage: `Configuration error: ${message}` }, 'high'); } static createValidationError(message, context) { return new SupaSeedError(message, 'VALIDATION_ERROR', { ...context, component: context.component || 'validation', operation: context.operation || 'validate', timestamp: new Date() }, { retryable: false, fallbackStrategy: 'fail-fast', userMessage: 'Invalid input detected. Please correct and try again.', technicalMessage: `Validation error: ${message}` }, 'medium'); } static createNetworkError(message, context) { return new SupaSeedError(message, 'NETWORK_ERROR', { ...context, component: context.component || 'network', operation: context.operation || 'request', timestamp: new Date() }, { retryable: true, maxRetries: 5, fallbackStrategy: 'graceful', userMessage: 'Network connectivity issue. Retrying...', technicalMessage: `Network error: ${message}` }, 'medium'); } } exports.ErrorHandler = ErrorHandler; ErrorHandler.errorCounts = new Map(); ErrorHandler.errorHistory = []; ErrorHandler.MAX_HISTORY = 1000; /** * Decorator for automatic error handling */ function handleErrors(component, operation, recoveryOptions) { return function (target, propertyName, descriptor) { const method = descriptor.value; descriptor.value = async function (...args) { try { return await method.apply(this, args); } catch (error) { await ErrorHandler.handle(error, { component, operation: operation || propertyName, metadata: { args: args.length } }); // Re-throw if not recoverable if (!(error instanceof SupaSeedError) || !error.recoveryOptions.retryable) { throw error; } } }; }; } /** * Async wrapper for error handling */ async function withErrorHandling(operation, context, recoveryOptions) { try { return await operation(); } catch (error) { await ErrorHandler.handle(error, context); if (error instanceof SupaSeedError && error.recoveryOptions.retryable && recoveryOptions?.maxRetries) { // Implement retry logic here for (let i = 0; i < recoveryOptions.maxRetries; i++) { try { logger_1.Logger.info(`๐Ÿ”„ Retry attempt ${i + 1} for ${context.operation}`); return await operation(); } catch (retryError) { if (i === recoveryOptions.maxRetries - 1) { throw retryError; } await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000)); } } } throw error; } } exports.default = ErrorHandler; //# sourceMappingURL=error-handler.js.map