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
JavaScript
;
/**
* 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