UNPKG

bowling-analysis-system

Version:

A comprehensive system for analyzing bowling techniques using video processing and metrics calculation

283 lines (250 loc) 8.71 kB
/** * Centralized error registry and management * Standardizes error handling across the application * @class ErrorRegistry */ class ErrorRegistry { /** * Creates an instance of ErrorRegistry * @param {Object} options - Options * @param {Object} [options.logger] - Logger instance */ constructor(options = {}) { this.logger = options.logger || console; this.errorDefinitions = new Map(); this.errorStrategies = new Map(); this.errorHistory = []; this.historyLimit = options.historyLimit || 100; } /** * Register an error definition * @param {String} code - Unique error code * @param {Object} definition - Error definition * @param {String} definition.message - Error message template * @param {String} [definition.category='general'] - Error category * @param {String} [definition.severity='error'] - Error severity (error|warning|info) * @param {Boolean} [definition.isRecoverable=true] - If true, error can be recovered from * @param {String} [definition.strategy] - Recovery strategy name * @param {String} [definition.description] - Error description * @returns {ErrorRegistry} - For method chaining */ registerError(code, definition) { if (this.errorDefinitions.has(code)) { this.logger.warn(`Error code '${code}' already registered, overwriting`); } const errorDef = { code, message: definition.message || `Error: ${code}`, category: definition.category || 'general', severity: definition.severity || 'error', isRecoverable: definition.isRecoverable !== false, strategy: definition.strategy || null, description: definition.description || '', ...definition }; this.errorDefinitions.set(code, errorDef); return this; } /** * Register a recovery strategy * @param {String} name - Strategy name * @param {Function} strategyFn - Strategy function * @param {Object} [options={}] - Strategy options * @returns {ErrorRegistry} - For method chaining */ registerStrategy(name, strategyFn, options = {}) { if (typeof strategyFn !== 'function') { throw new Error(`Error strategy must be a function: ${name}`); } this.errorStrategies.set(name, { name, execute: strategyFn, options }); return this; } /** * Format error parameters in message template * @param {String} template - Message template * @param {Object} params - Parameters * @returns {String} - Formatted message * @private */ _formatMessage(template, params = {}) { return template.replace(/\{(\w+)\}/g, (match, key) => { return params[key] !== undefined ? String(params[key]) : match; }); } /** * Create an error instance * @param {String} code - Error code * @param {Object} [params={}] - Error parameters * @param {Error} [originalError] - Original error that caused this * @returns {Error} - Error instance */ createError(code, params = {}, originalError = null) { const definition = this.errorDefinitions.get(code); if (!definition) { this.logger.warn(`Unknown error code: ${code}, using generic error`); const error = new Error(params.message || `Unknown error: ${code}`); error.code = code; error.params = params; if (originalError) error.cause = originalError; return error; } const message = this._formatMessage(definition.message, params); const error = new Error(message); // Add error metadata error.code = code; error.category = definition.category; error.severity = definition.severity; error.isRecoverable = definition.isRecoverable; error.strategy = definition.strategy; error.params = params; if (originalError) error.cause = originalError; // Add recovery method if strategy exists if (definition.isRecoverable && definition.strategy) { const strategy = this.errorStrategies.get(definition.strategy); if (strategy) { error.recover = async (context = {}) => { return this.executeStrategy(definition.strategy, error, context); }; } } // Record error in history this._recordError(error); return error; } /** * Create and throw an error * @param {String} code - Error code * @param {Object} [params={}] - Error parameters * @param {Error} [originalError] - Original error * @throws {Error} - Throws the created error */ throwError(code, params = {}, originalError = null) { throw this.createError(code, params, originalError); } /** * Record error in history * @param {Error} error - Error to record * @private */ _recordError(error) { this.errorHistory.unshift({ timestamp: new Date().toISOString(), code: error.code, message: error.message, category: error.category, severity: error.severity, isRecoverable: error.isRecoverable, strategy: error.strategy, params: error.params, stack: error.stack }); // Limit history size if (this.errorHistory.length > this.historyLimit) { this.errorHistory.length = this.historyLimit; } } /** * Execute a recovery strategy * @param {String} strategyName - Strategy name * @param {Error} error - Error to recover from * @param {Object} [context={}] - Recovery context * @returns {Promise<Object>} - Recovery result */ async executeStrategy(strategyName, error, context = {}) { const strategy = this.errorStrategies.get(strategyName); if (!strategy) { throw new Error(`Unknown recovery strategy: ${strategyName}`); } try { const strategyContext = { ...context, logger: this.logger, registry: this }; return await strategy.execute(error, strategyContext); } catch (recoveryError) { this.logger.error(`Error executing recovery strategy '${strategyName}':`, recoveryError); throw new Error(`Recovery strategy '${strategyName}' failed: ${recoveryError.message}`); } } /** * Get error definition by code * @param {String} code - Error code * @returns {Object|null} - Error definition or null if not found */ getErrorDefinition(code) { return this.errorDefinitions.get(code) || null; } /** * Get strategy by name * @param {String} name - Strategy name * @returns {Object|null} - Strategy or null if not found */ getStrategy(name) { return this.errorStrategies.get(name) || null; } /** * Get all error definitions * @returns {Map<String, Object>} - Map of error code to definition */ getAllErrorDefinitions() { return new Map(this.errorDefinitions); } /** * Get all recovery strategies * @returns {Map<String, Object>} - Map of strategy name to strategy */ getAllStrategies() { return new Map(this.errorStrategies); } /** * Get error history * @param {Object} [options={}] - Filter options * @param {String} [options.category] - Filter by category * @param {String} [options.severity] - Filter by severity * @param {Number} [options.limit] - Limit number of results * @returns {Array<Object>} - Error history */ getErrorHistory(options = {}) { let history = [...this.errorHistory]; if (options.category) { history = history.filter(e => e.category === options.category); } if (options.severity) { history = history.filter(e => e.severity === options.severity); } if (options.limit) { history = history.slice(0, options.limit); } return history; } /** * Clear error history * @returns {ErrorRegistry} - For method chaining */ clearHistory() { this.errorHistory = []; return this; } /** * Wrap a function with error handling * @param {Function} fn - Function to wrap * @param {String} errorCode - Error code to use if function throws * @param {Object} [params={}] - Additional error parameters * @returns {Function} - Wrapped function */ withErrorHandling(fn, errorCode, params = {}) { return async (...args) => { try { return await fn(...args); } catch (error) { throw this.createError(errorCode, { ...params, originalMessage: error.message }, error); } }; } } module.exports = ErrorRegistry;