UNPKG

@spaik/mcp-server-roi

Version:

MCP server for AI ROI prediction and tracking with Monte Carlo simulations

181 lines 6.86 kB
import { logger } from './logger.js'; export var ErrorCode; (function (ErrorCode) { // Database errors ErrorCode["TRANSACTION_FAILED"] = "TRANSACTION_FAILED"; ErrorCode["CONSTRAINT_VIOLATION"] = "CONSTRAINT_VIOLATION"; ErrorCode["FOREIGN_KEY_VIOLATION"] = "FOREIGN_KEY_VIOLATION"; ErrorCode["UNIQUE_VIOLATION"] = "UNIQUE_VIOLATION"; // Validation errors ErrorCode["VALIDATION_FAILED"] = "VALIDATION_FAILED"; ErrorCode["INVALID_INPUT"] = "INVALID_INPUT"; ErrorCode["MISSING_REQUIRED_FIELD"] = "MISSING_REQUIRED_FIELD"; // Business logic errors ErrorCode["PROJECT_NOT_FOUND"] = "PROJECT_NOT_FOUND"; ErrorCode["INSUFFICIENT_DATA"] = "INSUFFICIENT_DATA"; ErrorCode["CALCULATION_ERROR"] = "CALCULATION_ERROR"; // System errors ErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR"; ErrorCode["TIMEOUT_ERROR"] = "TIMEOUT_ERROR"; ErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR"; })(ErrorCode || (ErrorCode = {})); export class ROIError extends Error { code; details; originalError; constructor(code, message, details, originalError) { super(message); this.code = code; this.details = details; this.originalError = originalError; this.name = 'ROIError'; } toJSON() { return { code: this.code, message: this.message, details: this.details, timestamp: new Date().toISOString() }; } } export class TransactionError extends ROIError { rollbackRequired; constructor(message, rollbackRequired = true, details, originalError) { super(ErrorCode.TRANSACTION_FAILED, message, details, originalError); this.rollbackRequired = rollbackRequired; this.name = 'TransactionError'; } } export class ValidationError extends ROIError { fields; constructor(message, fields, details) { super(ErrorCode.VALIDATION_FAILED, message, { fields, ...details }); this.fields = fields; this.name = 'ValidationError'; } } /** * Converts Supabase/PostgreSQL errors to ROIError */ export function handleDatabaseError(error) { if ('code' in error) { const pgError = error; switch (pgError.code) { case '23505': // unique_violation return new ROIError(ErrorCode.UNIQUE_VIOLATION, 'A record with this value already exists', { originalCode: pgError.code, hint: pgError.hint }); case '23503': // foreign_key_violation return new ROIError(ErrorCode.FOREIGN_KEY_VIOLATION, 'Referenced record does not exist', { originalCode: pgError.code, hint: pgError.hint }); case '23502': // not_null_violation return new ROIError(ErrorCode.MISSING_REQUIRED_FIELD, 'Required field is missing', { originalCode: pgError.code, hint: pgError.hint }); case '23514': // check_violation return new ROIError(ErrorCode.CONSTRAINT_VIOLATION, 'Data validation constraint failed', { originalCode: pgError.code, hint: pgError.hint }); default: return new ROIError(ErrorCode.UNKNOWN_ERROR, pgError.message || 'Database operation failed', { originalCode: pgError.code, details: pgError.details }); } } // Handle generic errors return new ROIError(ErrorCode.UNKNOWN_ERROR, error.message || 'An unexpected error occurred', undefined, error); } export class ErrorRecovery { strategies = new Map(); register(code, strategy) { this.strategies.set(code, strategy); } async tryRecover(error, context) { const strategy = this.strategies.get(error.code); if (strategy && strategy.canRecover(error)) { try { return await strategy.recover(error, context); } catch (recoveryError) { logger.error('Recovery failed', recoveryError, { originalError: error }); throw error; // Re-throw original error if recovery fails } } throw error; // No recovery strategy available } } // Default error recovery instance export const errorRecovery = new ErrorRecovery(); // Register some default recovery strategies errorRecovery.register(ErrorCode.TIMEOUT_ERROR, { canRecover: () => true, recover: async (error, context) => { // Retry with exponential backoff const maxRetries = 3; const baseDelay = 1000; for (let i = 0; i < maxRetries; i++) { try { await new Promise(resolve => setTimeout(resolve, baseDelay * Math.pow(2, i))); // Retry the operation (context should contain the retry function) if (context?.retry) { return await context.retry(); } } catch (retryError) { if (i === maxRetries - 1) throw retryError; } } } }); /** * Wraps an async function with error handling and optional recovery */ export function withErrorHandling(fn, options) { return (async (...args) => { try { return await fn(...args); } catch (error) { const roiError = error instanceof ROIError ? error : options?.errorTransform ? options.errorTransform(error) : handleDatabaseError(error); if (options?.enableRecovery) { return await errorRecovery.tryRecover(roiError, options.recoveryContext); } throw roiError; } }); } export class TransactionRollback { actions = []; add(action) { this.actions.push(action); } async execute() { logger.info(`Executing rollback actions`, { count: this.actions.length }); // Execute rollback actions in reverse order for (const action of this.actions.reverse()) { try { logger.info(`Executing rollback`, { action: action.description }); await action.execute(); } catch (error) { logger.error(`Rollback failed`, error, { action: action.description }); // Continue with other rollbacks even if one fails } } } } /** * Logs errors with context for debugging */ export function logError(error, context) { const timestamp = new Date().toISOString(); const errorInfo = { timestamp, name: error.name, message: error.message, code: error instanceof ROIError ? error.code : 'UNKNOWN', stack: error.stack, context }; logger.error('ROI Server Error', errorInfo); // In production, you might send this to a logging service // await sendToLoggingService(errorInfo); } //# sourceMappingURL=error-handler.js.map