lsh-framework
Version:
A powerful, extensible shell with advanced job management, database persistence, and modern CLI features
184 lines (183 loc) • 5.42 kB
JavaScript
/**
* API Error Handler
* Consolidates error response formatting across all API endpoints
*/
/**
* HTTP status codes for common error types
*/
export const ErrorStatusCodes = {
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
CONFLICT: 409,
INTERNAL_SERVER_ERROR: 500,
SERVICE_UNAVAILABLE: 503,
};
/**
* Custom API Error class with status code
*/
export class ApiError extends Error {
statusCode;
code;
details;
constructor(message, statusCode = ErrorStatusCodes.INTERNAL_SERVER_ERROR, code, details) {
super(message);
this.statusCode = statusCode;
this.code = code;
this.details = details;
this.name = 'ApiError';
}
}
/**
* Send error response with consistent formatting
*
* @param res - Express response object
* @param error - Error object
* @param statusCode - HTTP status code (default: 500)
*/
export function sendError(res, error, statusCode) {
const status = statusCode || (error instanceof ApiError ? error.statusCode : 500);
const response = {
error: error.message || 'An unexpected error occurred',
timestamp: new Date().toISOString(),
};
if (error instanceof ApiError) {
if (error.code)
response.code = error.code;
if (error.details)
response.details = error.details;
}
// Log error for debugging (in production, use proper logger)
if (status >= 500) {
console.error('API Error:', error);
}
res.status(status).json(response);
}
/**
* Send success response with consistent formatting
*
* @param res - Express response object
* @param data - Response data
* @param statusCode - HTTP status code (default: 200)
* @param includeTimestamp - Include timestamp in response
*/
export function sendSuccess(res, data, statusCode = 200, includeTimestamp = false) {
if (statusCode === 204) {
res.status(204).send();
return;
}
const response = includeTimestamp
? { data, timestamp: new Date().toISOString() }
: data;
res.status(statusCode).json(response);
}
/**
* Wrapper for async route handlers with automatic error handling
*
* Eliminates the need for try-catch blocks in every route handler
*
* @param handler - Async route handler function
* @returns Express middleware function
*
* @example
* ```typescript
* app.get('/api/jobs', asyncHandler(async (req, res) => {
* const jobs = await getJobs();
* sendSuccess(res, jobs);
* }));
* ```
*/
export function asyncHandler(handler) {
return (req, res, next) => {
Promise.resolve(handler(req, res, next)).catch((error) => {
sendError(res, error);
});
};
}
/**
* Execute an operation with automatic success/error handling
*
* This is the most powerful wrapper - it handles:
* - Try-catch
* - Success response formatting
* - Error response formatting
* - Optional webhook triggering
*
* @param res - Express response object
* @param operation - Async operation to execute
* @param config - Configuration options
* @param webhookTrigger - Optional webhook trigger function
*
* @example
* ```typescript
* app.post('/api/jobs', async (req, res) => {
* await handleApiOperation(
* res,
* async () => this.daemon.addJob(req.body),
* {
* successStatus: 201,
* webhookEvent: 'job.created'
* },
* (event, data) => this.triggerWebhook(event, data)
* );
* });
* ```
*/
export async function handleApiOperation(res, operation, config = {}, webhookTrigger) {
const { successStatus = 200, includeTimestamp = false, webhookEvent, webhookData, } = config;
try {
const result = await operation();
// Send success response
sendSuccess(res, result, successStatus, includeTimestamp);
// Trigger webhook if configured
if (webhookEvent && webhookTrigger) {
const webhookPayload = webhookData ? webhookData(result) : result;
webhookTrigger(webhookEvent, webhookPayload);
}
}
catch (error) {
// Determine appropriate status code
let statusCode = 400;
if (error instanceof ApiError) {
statusCode = error.statusCode;
}
else if (error.message.includes('not found') || error.message.includes('Not found')) {
statusCode = 404;
}
else if (error.message.includes('permission') || error.message.includes('unauthorized')) {
statusCode = 403;
}
else if (error.message.includes('exists') || error.message.includes('duplicate')) {
statusCode = 409;
}
sendError(res, error, statusCode);
}
}
/**
* Create a typed API handler with webhook support
*
* Returns a function that can be used to handle API operations with
* automatic error handling and webhook triggering
*
* @param webhookTrigger - Webhook trigger function
* @returns Handler function
*/
export function createApiHandler(webhookTrigger) {
return async function (res, operation, config = {}) {
await handleApiOperation(res, operation, config, webhookTrigger);
};
}
/**
* Express error handling middleware
*
* Should be added after all routes
*
* @example
* ```typescript
* app.use(errorMiddleware);
* ```
*/
export function errorMiddleware(error, req, res, _next) {
sendError(res, error);
}