import { Logger } from './logger.js';
// Base error interfaces
export interface ErrorContext {
[key: string]: any;
}
export interface SerializedError {
name: string;
message: string;
code?: string;
status?: number;
context?: ErrorContext;
stack?: string;
timestamp: string;
}
// Custom error classes
export class DataAIError extends Error {
public readonly code: string;
public readonly status?: number;
public readonly context?: ErrorContext;
public readonly timestamp: string;
constructor(
message: string,
code: string = 'DATAI_ERROR',
status?: number,
context?: ErrorContext
) {
super(message);
this.name = 'DataAIError';
this.code = code;
this.status = status;
this.context = context;
this.timestamp = new Date().toISOString();
// Maintain proper stack trace (Node.js specific)
if ((Error as any).captureStackTrace) {
(Error as any).captureStackTrace(this, DataAIError);
}
}
toJSON(): SerializedError {
return {
name: this.name,
message: this.message,
code: this.code,
status: this.status,
context: this.context,
stack: this.stack,
timestamp: this.timestamp
};
}
}
export class ValidationError extends DataAIError {
constructor(message: string, context?: ErrorContext) {
super(message, 'VALIDATION_ERROR', 400, context);
this.name = 'ValidationError';
}
}
export class APIError extends DataAIError {
constructor(message: string, status: number, context?: ErrorContext) {
super(message, 'API_ERROR', status, context);
this.name = 'APIError';
}
}
export class NetworkError extends DataAIError {
constructor(message: string, context?: ErrorContext) {
super(message, 'NETWORK_ERROR', 503, context);
this.name = 'NetworkError';
}
}
export class TimeoutError extends DataAIError {
constructor(message: string, context?: ErrorContext) {
super(message, 'TIMEOUT_ERROR', 408, context);
this.name = 'TimeoutError';
}
}
export class AuthenticationError extends DataAIError {
constructor(message: string, context?: ErrorContext) {
super(message, 'AUTH_ERROR', 401, context);
this.name = 'AuthenticationError';
}
}
export class RateLimitError extends DataAIError {
constructor(message: string, context?: ErrorContext) {
super(message, 'RATE_LIMIT_ERROR', 429, context);
this.name = 'RateLimitError';
}
}
export class ToolExecutionError extends DataAIError {
constructor(message: string, toolName: string, context?: ErrorContext) {
super(message, 'TOOL_EXECUTION_ERROR', 500, { toolName, ...context });
this.name = 'ToolExecutionError';
}
}
// Error type guards
export function isDataAIError(error: unknown): error is DataAIError {
return error instanceof DataAIError;
}
export function isValidationError(error: unknown): error is ValidationError {
return error instanceof ValidationError;
}
export function isAPIError(error: unknown): error is APIError {
return error instanceof APIError;
}
export function isNetworkError(error: unknown): error is NetworkError {
return error instanceof NetworkError;
}
export function isTimeoutError(error: unknown): error is TimeoutError {
return error instanceof TimeoutError;
}
export function isAuthenticationError(error: unknown): error is AuthenticationError {
return error instanceof AuthenticationError;
}
export function isRateLimitError(error: unknown): error is RateLimitError {
return error instanceof RateLimitError;
}
export function isToolExecutionError(error: unknown): error is ToolExecutionError {
return error instanceof ToolExecutionError;
}
// Error handler class
export class ErrorHandler {
private readonly logger: Logger;
constructor(component: string) {
this.logger = new Logger(`ErrorHandler:${component}`);
}
// Transform unknown errors to standardized errors
transformError(error: unknown, context?: ErrorContext): DataAIError {
if (isDataAIError(error)) {
return error;
}
if (error instanceof Error) {
// Check for specific error patterns
if (error.message.includes('timeout')) {
return new TimeoutError(error.message, context);
}
if (error.message.includes('network') || error.message.includes('ENOTFOUND')) {
return new NetworkError(error.message, context);
}
if (error.message.includes('401') || error.message.includes('unauthorized')) {
return new AuthenticationError(error.message, context);
}
if (error.message.includes('429') || error.message.includes('rate limit')) {
return new RateLimitError(error.message, context);
}
// Generic error transformation
return new DataAIError(error.message, 'UNKNOWN_ERROR', 500, context);
}
// Handle non-Error objects
const message = typeof error === 'string' ? error : 'Unknown error occurred';
return new DataAIError(message, 'UNKNOWN_ERROR', 500, context);
}
// Handle and log errors
handleError(error: unknown, context?: ErrorContext): DataAIError {
const standardizedError = this.transformError(error, context);
// Use startup logging since this is outside of tool execution context
const errorMsg = `${standardizedError.message} [${standardizedError.code}:${standardizedError.status}]`;
this.logger.startup.error(errorMsg);
return standardizedError;
}
// Handle async operations with error catching
async handleAsync<T>(
operation: () => Promise<T>,
context?: ErrorContext
): Promise<T> {
try {
return await operation();
} catch (error) {
throw this.handleError(error, context);
}
}
// Handle sync operations with error catching
handleSync<T>(operation: () => T, context?: ErrorContext): T {
try {
return operation();
} catch (error) {
throw this.handleError(error, context);
}
}
}
// HTTP status code mapping
export const HTTP_STATUS_CODES = {
OK: 200,
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
TIMEOUT: 408,
RATE_LIMITED: 429,
INTERNAL_SERVER_ERROR: 500,
BAD_GATEWAY: 502,
SERVICE_UNAVAILABLE: 503,
GATEWAY_TIMEOUT: 504
} as const;
// Error response helpers for FastMCP tools
export function createErrorResponse(error: DataAIError): {
isError: true;
error: SerializedError;
} {
return {
isError: true,
error: error.toJSON()
};
}
export function createSuccessResponse<T>(data: T): {
isError: false;
data: T;
} {
return {
isError: false,
data
};
}
// Global error handler for uncaught errors
export function setupGlobalErrorHandlers(): void {
const globalErrorHandler = new ErrorHandler('Global');
process.on('uncaughtException', (error: Error) => {
globalErrorHandler.handleError(error, { type: 'uncaughtException' });
process.exit(1);
});
process.on('unhandledRejection', (reason: unknown) => {
globalErrorHandler.handleError(reason, { type: 'unhandledRejection' });
});
}
// Export error factory functions for convenience
export const createError = {
validation: (message: string, context?: ErrorContext) => new ValidationError(message, context),
api: (message: string, status: number, context?: ErrorContext) => new APIError(message, status, context),
network: (message: string, context?: ErrorContext) => new NetworkError(message, context),
timeout: (message: string, context?: ErrorContext) => new TimeoutError(message, context),
auth: (message: string, context?: ErrorContext) => new AuthenticationError(message, context),
rateLimit: (message: string, context?: ErrorContext) => new RateLimitError(message, context),
toolExecution: (message: string, toolName: string, context?: ErrorContext) =>
new ToolExecutionError(message, toolName, context),
generic: (message: string, code?: string, status?: number, context?: ErrorContext) =>
new DataAIError(message, code, status, context)
};