voyageai-cli
Version:
CLI for Voyage AI embeddings, reranking, and MongoDB Atlas Vector Search
108 lines (96 loc) • 3.15 kB
JavaScript
;
/**
* Custom application error with HTTP status code.
* Throw these from route handlers for consistent error responses.
*
* Usage: throw new AppError('Task not found', 404)
*/
class AppError extends Error {
constructor(message, statusCode = 500) {
super(message);
this.name = 'AppError';
this.statusCode = statusCode;
this.isOperational = true; // Distinguishes expected errors from bugs
}
}
/**
* Validation error with field-level details.
* Used by the validate middleware to report exactly which fields failed.
*
* Usage: throw new ValidationError([{ field: 'email', message: 'Invalid format' }])
*/
class ValidationError extends AppError {
constructor(errors) {
super('Validation failed', 400);
this.name = 'ValidationError';
this.errors = errors; // Array of { field, message }
}
}
/**
* 404 handler — catches requests that don't match any route.
* Must be registered after all route handlers.
*/
function notFoundHandler(req, res, _next) {
res.status(404).json({
error: 'Not Found',
message: `No route matches ${req.method} ${req.originalUrl}`,
suggestion: 'Check the API documentation for available endpoints.',
});
}
/**
* Global error handler — catches all errors thrown in route handlers.
*
* Behavior varies by error type:
* - AppError: Returns the error's status code and message (expected errors)
* - ValidationError: Returns 400 with field-level error details
* - MongoDB duplicate key: Returns 409 Conflict
* - Unknown errors: Returns 500 with generic message (hides internals)
*
* In development mode (NODE_ENV !== 'production'), includes the stack trace.
*/
function errorHandler(err, req, res, _next) {
// Log all errors (structured for log aggregation)
console.error(JSON.stringify({
level: 'error',
message: err.message,
stack: err.stack,
method: req.method,
path: req.originalUrl,
userId: req.user?.userId || 'anonymous',
timestamp: new Date().toISOString(),
}));
// Validation errors — return field details
if (err instanceof ValidationError) {
return res.status(400).json({
error: 'Validation Error',
message: err.message,
details: err.errors,
});
}
// Known operational errors
if (err instanceof AppError) {
return res.status(err.statusCode).json({
error: err.name,
message: err.message,
});
}
// MongoDB duplicate key error (code 11000)
if (err.code === 11000) {
const field = Object.keys(err.keyPattern || {})[0] || 'field';
return res.status(409).json({
error: 'Conflict',
message: `A record with this ${field} already exists.`,
});
}
// Unknown / programmer errors — don't leak internals
const response = {
error: 'Internal Server Error',
message: 'An unexpected error occurred. Please try again later.',
};
// Include stack trace in development
if (process.env.NODE_ENV !== 'production') {
response.debug = { message: err.message, stack: err.stack };
}
res.status(500).json(response);
}
module.exports = { AppError, ValidationError, notFoundHandler, errorHandler };