@spaik/mcp-server-roi
Version:
MCP server for AI ROI prediction and tracking with Monte Carlo simulations
181 lines • 6.86 kB
JavaScript
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