UNPKG

datapilot-cli

Version:

Enterprise-grade streaming multi-format data analysis with comprehensive statistical insights and intelligent relationship detection - supports CSV, JSON, Excel, TSV, Parquet - memory-efficient, cross-platform

688 lines 27.8 kB
"use strict"; /** * Comprehensive error handling and recovery system for DataPilot */ Object.defineProperty(exports, "__esModule", { value: true }); exports.ErrorUtils = exports.globalErrorHandler = exports.ErrorHandler = exports.ErrorCategory = exports.ErrorSeverity = exports.DataPilotError = void 0; const types_1 = require("../core/types"); const logger_1 = require("./logger"); // Re-export core error types for external use var types_2 = require("../core/types"); Object.defineProperty(exports, "DataPilotError", { enumerable: true, get: function () { return types_2.DataPilotError; } }); Object.defineProperty(exports, "ErrorSeverity", { enumerable: true, get: function () { return types_2.ErrorSeverity; } }); Object.defineProperty(exports, "ErrorCategory", { enumerable: true, get: function () { return types_2.ErrorCategory; } }); class ErrorHandler { config; stats; errorHistory = []; constructor(config = {}) { this.config = { maxRetries: 3, retryDelayMs: 1000, enableRecovery: true, logErrors: true, abortOnCritical: true, memoryThresholdBytes: 512 * 1024 * 1024, // 512MB verboseMode: false, enhancedStackTraces: true, silentFailureDetection: true, performanceTracking: true, contextPreservation: true, ...config, }; this.stats = { totalErrors: 0, errorsByCategory: {}, errorsBySeverity: {}, recoveredErrors: 0, criticalErrors: 0, }; // Initialize counters Object.values(types_1.ErrorCategory).forEach((category) => { this.stats.errorsByCategory[category] = 0; }); Object.values(types_1.ErrorSeverity).forEach((severity) => { this.stats.errorsBySeverity[severity] = 0; }); } /** * Handle an error with recovery strategies */ async handleError(error, operation, recoveryStrategy) { this.recordError(error); if (this.config.logErrors) { this.logError(error); } // Check if error is critical and should abort if (error.severity === types_1.ErrorSeverity.CRITICAL && this.config.abortOnCritical) { throw error; } // Check if recovery is enabled and error is recoverable if (!this.config.enableRecovery || !error.recoverable) { throw error; } // Apply recovery strategy if (recoveryStrategy) { return await this.applyRecoveryStrategy(error, operation, recoveryStrategy); } // Default recovery strategies based on error category const defaultStrategy = this.getDefaultRecoveryStrategy(error); if (defaultStrategy) { return await this.applyRecoveryStrategy(error, operation, defaultStrategy); } throw error; } /** * Handle synchronous errors */ handleErrorSync(error, operation, fallbackValue) { this.recordError(error); if (this.config.logErrors) { this.logError(error); } if (error.severity === types_1.ErrorSeverity.CRITICAL && this.config.abortOnCritical) { throw error; } if (!error.recoverable) { if (fallbackValue !== undefined) { this.stats.recoveredErrors++; return fallbackValue; } throw error; } // Try to execute with fallback try { return operation(); } catch (retryError) { if (fallbackValue !== undefined) { this.stats.recoveredErrors++; logger_1.logger.warn(`Operation failed, using fallback value: ${retryError instanceof Error ? retryError.message : 'Unknown error'}`); return fallbackValue; } throw error; } } /** * Validate configuration parameters */ validateConfig(config, schema) { const errors = []; for (const [key, expectedType] of Object.entries(schema)) { const value = config[key]; if (value === undefined || value === null) { continue; // Optional parameters } const actualType = typeof value; if (actualType !== expectedType) { errors.push(types_1.DataPilotError.validation(`Invalid configuration parameter type for '${key}': expected ${expectedType}, got ${actualType}`, 'CONFIG_TYPE_MISMATCH', { operationName: 'validateConfig' }, [ { action: 'Fix configuration', description: `Change '${key}' to type ${expectedType}`, severity: types_1.ErrorSeverity.MEDIUM, }, ])); } // Additional validations based on type if (expectedType === 'number') { const numValue = value; if (isNaN(numValue) || !isFinite(numValue)) { errors.push(types_1.DataPilotError.validation(`Invalid number value for '${key}': ${value}`, 'CONFIG_INVALID_NUMBER', { operationName: 'validateConfig' }, [ { action: 'Fix configuration', description: `Provide a valid number for '${key}'`, severity: types_1.ErrorSeverity.MEDIUM, }, ])); } } } return errors; } /** * Check memory usage and throw error if threshold exceeded */ checkMemoryUsage(context) { const memUsage = process.memoryUsage(); if (memUsage.heapUsed > this.config.memoryThresholdBytes) { const error = types_1.DataPilotError.memory(`Memory usage (${Math.round(memUsage.heapUsed / 1024 / 1024)}MB) exceeds threshold (${Math.round(this.config.memoryThresholdBytes / 1024 / 1024)}MB)`, 'MEMORY_THRESHOLD_EXCEEDED', { ...context, memoryUsage: memUsage.heapUsed }, [ { action: 'Reduce memory usage', description: 'Process data in smaller chunks or increase memory limit', severity: types_1.ErrorSeverity.HIGH, command: '--maxRows 10000 or --memoryLimit 1024', }, { action: 'Free memory', description: 'Close other applications to free system memory', severity: types_1.ErrorSeverity.MEDIUM, }, ]); throw error; } } /** * Create error with file corruption recovery suggestions */ createFileCorruptionError(filePath, details) { return types_1.DataPilotError.parsing(`File appears to be corrupted or invalid: ${details}`, 'FILE_CORRUPTED', { filePath, operationName: 'parseFile' }, [ { action: 'Check file encoding', description: 'Try specifying encoding explicitly (utf8, utf16, latin1)', severity: types_1.ErrorSeverity.HIGH, command: '--encoding utf8', }, { action: 'Validate file format', description: 'Open file in text editor to check for obvious corruption', severity: types_1.ErrorSeverity.HIGH, }, { action: 'Try manual delimiter detection', description: 'Specify delimiter explicitly if auto-detection fails', severity: types_1.ErrorSeverity.MEDIUM, command: '--delimiter "," or --delimiter "\\t"', }, { action: 'Skip problematic rows', description: 'Enable lenient parsing mode', severity: types_1.ErrorSeverity.LOW, command: '--lenient', }, ]); } /** * Create error for insufficient data */ createInsufficientDataError(context, minRequired, actual) { return types_1.DataPilotError.analysis(`Insufficient data for analysis: found ${actual} rows, need at least ${minRequired}`, 'INSUFFICIENT_DATA', context, [ { action: 'Check data source', description: 'Verify that the file contains the expected amount of data', severity: types_1.ErrorSeverity.HIGH, }, { action: 'Adjust analysis parameters', description: 'Reduce minimum requirements or enable simplified analysis', severity: types_1.ErrorSeverity.MEDIUM, command: '--simplified or --minRows 10', }, { action: 'Combine with other data', description: 'Add more data to reach minimum requirements', severity: types_1.ErrorSeverity.LOW, }, ]); } /** * Wrap operation with automatic error handling and enhanced context */ async wrapOperation(operation, operationName, context, recoveryStrategy) { const startTime = performance.now(); const memoryBefore = this.config.performanceTracking ? process.memoryUsage() : undefined; try { const result = await operation(); // Silent failure detection if (this.config.silentFailureDetection) { this.detectSilentFailure(result, operationName, context); } return result; } catch (error) { const endTime = performance.now(); const memoryAfter = this.config.performanceTracking ? process.memoryUsage() : undefined; let dataPilotError; if (error instanceof types_1.DataPilotError) { dataPilotError = error; // Enhance context with performance data if (this.config.performanceTracking && dataPilotError.context) { dataPilotError.context.performanceContext = { startTime, endTime, memoryBefore, memoryAfter, }; } } else { // Convert generic error to DataPilotError with enhanced context const enhancedContext = { ...context, operationName, originalStack: error instanceof Error ? error.stack : undefined, performanceContext: this.config.performanceTracking ? { startTime, endTime, memoryBefore, memoryAfter, } : undefined, }; dataPilotError = new types_1.DataPilotError(error instanceof Error ? error.message : 'Unknown error', 'UNKNOWN_ERROR', types_1.ErrorSeverity.MEDIUM, types_1.ErrorCategory.ANALYSIS, enhancedContext); } return await this.handleError(dataPilotError, operation, recoveryStrategy); } } /** * Create enhanced error context with debugging information */ createEnhancedContext(baseContext, operationName, additionalContext) { const context = { ...baseContext, operationName, additionalContext, }; if (this.config.enhancedStackTraces) { // Capture call stack const stack = new Error().stack; if (stack) { context.callStack = stack.split('\n').slice(2, 8); // Skip Error() and this function context.originalStack = stack; } } if (this.config.performanceTracking) { context.performanceContext = { startTime: performance.now(), memoryBefore: process.memoryUsage(), }; } return context; } /** * Detect silent failures in operation results */ detectSilentFailure(result, operationName, context) { if (!result) return; // Check for common silent failure patterns const silentFailureDetected = this.checkForSilentFailurePatterns(result, operationName); if (silentFailureDetected && context) { context.silentFailure = { detected: true, expectedResult: this.getExpectedResultDescription(operationName), actualResult: result, failureType: silentFailureDetected.type, detectionTimestamp: Date.now(), }; // Log silent failure detection in verbose mode if (this.config.verboseMode) { logger_1.logger.warn(`Silent failure detected in ${operationName}: ${silentFailureDetected.description}`); } } } /** * Check for silent failure patterns in results */ checkForSilentFailurePatterns(result, operationName) { // Check for missing or null results if (result === null || result === undefined) { return { type: 'missing_result', description: 'Operation returned null or undefined' }; } // Check for empty objects that should have content if (typeof result === 'object' && Object.keys(result).length === 0) { if (operationName.includes('analyze') || operationName.includes('process')) { return { type: 'invalid_result', description: 'Analysis operation returned empty object' }; } } // Check for partial results (arrays with very few items when more expected) if (Array.isArray(result) && result.length < 2) { if (operationName.includes('analyze') || operationName.includes('process')) { return { type: 'partial_result', description: 'Analysis returned unexpectedly few results' }; } } // Check for error indicators in result objects if (typeof result === 'object' && result !== null) { const resultObj = result; if (resultObj.error || resultObj.failed || resultObj.timeout) { return { type: 'invalid_result', description: 'Result contains error indicators' }; } } return null; } /** * Get expected result description for operation */ getExpectedResultDescription(operationName) { const operationMap = { 'analyze': 'Analysis results object with statistics', 'parse': 'Parsed data array or object', 'format': 'Formatted output string', 'process': 'Processed data structure', 'validate': 'Validation results object', 'transform': 'Transformed data structure', }; for (const [key, description] of Object.entries(operationMap)) { if (operationName.toLowerCase().includes(key)) { return description; } } return 'Operation-specific result object'; } /** * Enable or disable verbose mode */ setVerboseMode(enabled) { this.config.verboseMode = enabled; } /** * Get configuration for debugging */ getConfig() { return { ...this.config }; } /** * Get error statistics */ getStats() { return { ...this.stats }; } /** * Get error statistics (alias for compatibility) */ getErrorStatistics() { return { totalErrors: this.stats.totalErrors, byCategory: this.stats.errorsByCategory, bySeverity: this.stats.errorsBySeverity, }; } /** * Get recent errors */ getRecentErrors(limit = 10) { return this.errorHistory.slice(-limit); } /** * Clear error history and reset stats */ reset() { this.errorHistory = []; this.stats = { totalErrors: 0, errorsByCategory: {}, errorsBySeverity: {}, recoveredErrors: 0, criticalErrors: 0, }; Object.values(types_1.ErrorCategory).forEach((category) => { this.stats.errorsByCategory[category] = 0; }); Object.values(types_1.ErrorSeverity).forEach((severity) => { this.stats.errorsBySeverity[severity] = 0; }); } recordError(error) { this.stats.totalErrors++; this.stats.errorsByCategory[error.category]++; this.stats.errorsBySeverity[error.severity]++; if (error.severity === types_1.ErrorSeverity.CRITICAL) { this.stats.criticalErrors++; } this.errorHistory.push(error); // Keep history limited to prevent memory issues if (this.errorHistory.length > 100) { this.errorHistory = this.errorHistory.slice(-50); } } logError(error) { // Set verbose info if in verbose mode error.setVerboseInfo(this.config.verboseMode); const message = error.getFormattedMessage(this.config.verboseMode); const suggestions = error.getEnhancedSuggestions(this.config.verboseMode); switch (error.severity) { case types_1.ErrorSeverity.CRITICAL: logger_1.logger.error(message); break; case types_1.ErrorSeverity.HIGH: logger_1.logger.error(message); break; case types_1.ErrorSeverity.MEDIUM: logger_1.logger.warn(message); break; case types_1.ErrorSeverity.LOW: logger_1.logger.info(message); break; } if (suggestions.length > 0) { const suggestionHeader = this.config.verboseMode ? 'Suggestions (with debugging context):' : 'Suggestions:'; logger_1.logger.info(suggestionHeader); suggestions.forEach((suggestion) => logger_1.logger.info(suggestion)); } // Log verbose information if enabled if (this.config.verboseMode && error.verboseInfo) { logger_1.logger.debug('Verbose Error Information:', { context: error.verboseInfo.fullContext, performance: error.verboseInfo.performanceMetrics, memory: error.verboseInfo.memorySnapshot, }); } } async applyRecoveryStrategy(error, operation, strategy) { switch (strategy.strategy) { case 'retry': return await this.retryOperation(operation, strategy.maxRetries || this.config.maxRetries, strategy.retryDelay || this.config.retryDelayMs); case 'fallback': this.stats.recoveredErrors++; return strategy.fallbackValue; case 'skip': this.stats.recoveredErrors++; return null; case 'continue': this.stats.recoveredErrors++; logger_1.logger.warn(`Continuing despite error: ${error.message}`); return null; case 'abort': throw error; default: throw error; } } async retryOperation(operation, maxRetries, delayMs) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const result = await operation(); if (attempt > 1) { this.stats.recoveredErrors++; logger_1.logger.info(`Operation succeeded on attempt ${attempt}`); } return result; } catch (error) { lastError = error instanceof Error ? error : new Error('Unknown error'); if (attempt < maxRetries) { logger_1.logger.warn(`Operation failed on attempt ${attempt}, retrying in ${delayMs}ms...`); await this.delay(delayMs); delayMs *= 1.5; // Exponential backoff } } } throw lastError; } getDefaultRecoveryStrategy(error) { switch (error.category) { case types_1.ErrorCategory.MEMORY: return { strategy: 'abort' }; // Memory errors should generally abort case types_1.ErrorCategory.PARSING: if (error.code === 'ENCODING_DETECTION_FAILED') { return { strategy: 'fallback', fallbackValue: 'utf8' }; } return { strategy: 'skip' }; // Skip malformed rows case types_1.ErrorCategory.ANALYSIS: return { strategy: 'continue' }; // Continue with reduced functionality case types_1.ErrorCategory.NETWORK: return { strategy: 'retry', maxRetries: 3, retryDelay: 1000 }; case types_1.ErrorCategory.IO: return { strategy: 'retry', maxRetries: 2, retryDelay: 500 }; default: return null; } } delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } } exports.ErrorHandler = ErrorHandler; // Global error handler instance exports.globalErrorHandler = new ErrorHandler(); /** * Enhanced utility functions for common error scenarios */ class ErrorUtils { /** * Handle CSV parsing errors with enhanced recovery and context */ static async handleParsingError(error, filePath, retryWithFallback, operationContext) { let dataPilotError; if (error instanceof types_1.DataPilotError) { dataPilotError = error; } else { const errorMessage = error instanceof Error ? error.message : 'Unknown parsing error'; const enhancedContext = exports.globalErrorHandler.createEnhancedContext({ filePath, ...operationContext }, 'parseFile', { originalError: error }); dataPilotError = exports.globalErrorHandler.createFileCorruptionError(filePath, errorMessage); dataPilotError.context = { ...dataPilotError.context, ...enhancedContext }; } return await exports.globalErrorHandler.handleError(dataPilotError, retryWithFallback, { strategy: 'retry', maxRetries: 2, }); } /** * Handle insufficient data scenarios */ static handleInsufficientData(actualCount, minimumRequired, context, fallbackValue) { if (actualCount >= minimumRequired) { return fallbackValue; // Not actually an error } const error = exports.globalErrorHandler.createInsufficientDataError(context, minimumRequired, actualCount); return exports.globalErrorHandler.handleErrorSync(error, () => { throw error; // Force fallback }, fallbackValue); } /** * Validate and clean data array */ static validateDataArray(data, context) { if (!Array.isArray(data)) { throw types_1.DataPilotError.validation('Expected array data format', 'INVALID_DATA_FORMAT', context, [ { action: 'Check data source', description: 'Ensure data is properly formatted as an array', severity: types_1.ErrorSeverity.HIGH, }, ]); } if (data.length === 0) { throw exports.globalErrorHandler.createInsufficientDataError(context, 1, 0); } return data.filter((row) => row !== null && row !== undefined); } /** * Safe numeric conversion with error handling */ static safeToNumber(value, defaultValue = 0) { if (typeof value === 'number' && isFinite(value)) { return value; } if (typeof value === 'string') { const parsed = parseFloat(value); if (isFinite(parsed)) { return parsed; } } return defaultValue; } /** * Safe property access with error handling */ static safeGet(obj, path, defaultValue) { try { const keys = path.split('.'); let current = obj; for (const key of keys) { if (current === null || current === undefined) { return defaultValue; } current = current[key]; } return current; } catch { return defaultValue; } } /** * Wrap async operation with enhanced error context */ static async withEnhancedContext(operation, context) { const enhancedContext = exports.globalErrorHandler.createEnhancedContext({ section: context.section, analyzer: context.analyzer, filePath: context.filePath, }, context.operationName, context.additionalContext); return await exports.globalErrorHandler.wrapOperation(operation, context.operationName, enhancedContext); } /** * Create a contextual error with enhanced debugging information */ static createContextualError(message, code, category, severity, context) { const enhancedContext = exports.globalErrorHandler.createEnhancedContext({ section: context.section, analyzer: context.analyzer, filePath: context.filePath, dependencyContext: context.dependencyContext, }, context.operationName, context.additionalContext); return new types_1.DataPilotError(message, code, severity, category, enhancedContext, undefined, true); } /** * Check operation result and detect silent failures */ static validateOperationResult(result, expectedType, operationName, context) { // Basic type validation if (result === null || result === undefined) { throw ErrorUtils.createContextualError(`Operation ${operationName} returned null or undefined`, 'NULL_RESULT', types_1.ErrorCategory.ANALYSIS, types_1.ErrorSeverity.HIGH, { operationName, ...context, additionalContext: { expectedType, actualResult: result } }); } // Type-specific validation switch (expectedType) { case 'object': if (typeof result !== 'object' || Array.isArray(result)) { throw ErrorUtils.createContextualError(`Operation ${operationName} expected object, got ${typeof result}`, 'TYPE_MISMATCH', types_1.ErrorCategory.VALIDATION, types_1.ErrorSeverity.HIGH, { operationName, ...context, additionalContext: { expectedType, actualType: typeof result } }); } break; case 'array': if (!Array.isArray(result)) { throw ErrorUtils.createContextualError(`Operation ${operationName} expected array, got ${typeof result}`, 'TYPE_MISMATCH', types_1.ErrorCategory.VALIDATION, types_1.ErrorSeverity.HIGH, { operationName, ...context, additionalContext: { expectedType, actualType: typeof result } }); } break; } return result; } } exports.ErrorUtils = ErrorUtils; //# sourceMappingURL=error-handler.js.map