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
JavaScript
"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