@voilajsx/appkit
Version:
Minimal and framework agnostic Node.js toolkit designed for AI agentic backend development
200 lines • 8.69 kB
JavaScript
/**
* Core error class with semantic HTTP status codes and middleware
* @module @voilajsx/appkit/error
* @file src/error/error.ts
*
* @llm-rule WHEN: Building apps that need semantic HTTP error handling
* @llm-rule AVOID: Using directly - always get instance via errorClass.get()
* @llm-rule NOTE: Provides semantic error creation (badRequest, unauthorized) and Express middleware
*/
/**
* Error class with semantic HTTP status codes and middleware functionality
*/
export class ErrorClass {
config;
constructor(config) {
this.config = config;
}
/**
* Creates a 400 Bad Request error
* @llm-rule WHEN: Client sends invalid input data (missing fields, wrong format)
* @llm-rule AVOID: Using for server-side validation errors - use conflict() instead
* @llm-rule NOTE: EXAMPLES: missing email, invalid JSON, malformed request body
* @llm-rule NOTE: PATTERN: if (!req.body.email) throw error.badRequest('Email required');
*/
badRequest(message) {
const error = new Error(message || this.config.messages.badRequest);
error.statusCode = 400;
error.type = 'BAD_REQUEST';
return error;
}
/**
* Creates a 401 Unauthorized error
* @llm-rule WHEN: Authentication is required but missing or invalid
* @llm-rule AVOID: Using for permission issues - use forbidden() for access control
* @llm-rule NOTE: EXAMPLES: missing token, invalid token, expired session
* @llm-rule NOTE: PATTERN: if (!token) throw error.unauthorized('Token required');
*/
unauthorized(message) {
const error = new Error(message || this.config.messages.unauthorized);
error.statusCode = 401;
error.type = 'UNAUTHORIZED';
return error;
}
/**
* Creates a 403 Forbidden error
* @llm-rule WHEN: User is authenticated but lacks permission for the action
* @llm-rule AVOID: Using for authentication issues - use unauthorized() instead
* @llm-rule NOTE: EXAMPLES: insufficient role, blocked user, admin-only endpoint
* @llm-rule NOTE: PATTERN: if (!user.isAdmin) throw error.forbidden('Admin access required');
*/
forbidden(message) {
const error = new Error(message || this.config.messages.forbidden);
error.statusCode = 403;
error.type = 'FORBIDDEN';
return error;
}
/**
* Creates a 404 Not Found error
* @llm-rule WHEN: Requested resource does not exist
* @llm-rule AVOID: Using for business logic failures - use conflict() instead
* @llm-rule NOTE: EXAMPLES: user not found, post not found, missing file
* @llm-rule NOTE: PATTERN: if (!user) throw error.notFound('User not found');
*/
notFound(message) {
const error = new Error(message || this.config.messages.notFound);
error.statusCode = 404;
error.type = 'NOT_FOUND';
return error;
}
/**
* Creates a 409 Conflict error
* @llm-rule WHEN: Business logic conflicts or resource already exists
* @llm-rule AVOID: Using for validation errors - use badRequest() for input validation
* @llm-rule NOTE: EXAMPLES: email already exists, duplicate username, state conflicts
* @llm-rule NOTE: PATTERN: if (existingUser) throw error.conflict('Email already registered');
*/
conflict(message) {
const error = new Error(message || this.config.messages.conflict);
error.statusCode = 409;
error.type = 'CONFLICT';
return error;
}
/**
* Creates a 500 Server Error
* @llm-rule WHEN: Internal server failures like database errors, external API failures
* @llm-rule AVOID: Using for business logic issues - use appropriate 4xx errors instead
* @llm-rule NOTE: EXAMPLES: database connection failure, external API timeout, file system errors
* @llm-rule NOTE: PATTERN: catch (dbError) { throw error.serverError('Database unavailable'); }
*/
serverError(message) {
const error = new Error(message || this.config.messages.serverError);
error.statusCode = 500;
error.type = 'SERVER_ERROR';
return error;
}
/**
* Creates a custom error with any status code
* @llm-rule WHEN: Need custom HTTP status codes not covered by semantic methods
* @llm-rule AVOID: Using for standard HTTP codes - use semantic methods instead
* @llm-rule NOTE: EXAMPLES: 429 rate limit, 503 service unavailable, 418 teapot
* @llm-rule NOTE: PATTERN: error.createError(429, 'Rate limit exceeded', 'RATE_LIMIT');
*/
createError(statusCode, message, type) {
const error = new Error(message);
error.statusCode = statusCode;
error.type = type || `HTTP_${statusCode}`;
return error;
}
/**
* Creates production-ready Express error handling middleware
* @llm-rule WHEN: Setting up Express app error handling - use as last middleware
* @llm-rule AVOID: Using multiple error handlers - this should be the final middleware
* @llm-rule NOTE: MIDDLEWARE SETUP: app.use(error.handleErrors()); // Must be LAST
* @llm-rule NOTE: AUTO-FEATURES: dev vs prod responses, stack trace hiding, error logging
*/
handleErrors(options = {}) {
// Use instance config as defaults, allow options to override
const showStack = options.showStack !== undefined
? options.showStack
: this.config.middleware.showStack;
const logErrors = options.logErrors !== undefined
? options.logErrors
: this.config.middleware.logErrors;
return (error, req, res, next) => {
// Log errors if enabled
if (logErrors) {
console.error('Error:', error.message);
if (showStack && error.stack) {
console.error('Stack:', error.stack);
}
}
// Determine status code
const statusCode = error.statusCode || 500;
// Determine error type
const errorType = error.type || (statusCode >= 500 ? 'SERVER_ERROR' : 'CLIENT_ERROR');
// Build response object
const response = {
error: errorType,
message: error.message || 'An error occurred',
};
// Include stack trace in development
if (showStack && error.stack) {
response.stack = error.stack;
}
// Send error response
res.status(statusCode).json(response);
};
}
/**
* Wraps async route handlers to catch errors automatically
* @llm-rule WHEN: Creating async Express route handlers that might throw errors
* @llm-rule AVOID: Manual try/catch in every route - this handles it automatically
* @llm-rule NOTE: ASYNC PATTERN: app.post('/route', error.asyncRoute(async (req, res) => {...}));
* @llm-rule NOTE: ERROR FLOW: thrown errors → automatically caught → sent to handleErrors middleware
*/
asyncRoute(fn) {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
}
/**
* Checks if error is a 4xx client error
* @llm-rule WHEN: Need to categorize errors for logging or metrics
* @llm-rule AVOID: Manual status code checking - this handles the logic
* @llm-rule NOTE: CLIENT ERRORS: 400-499 status codes (user's fault)
*/
isClientError(error) {
return error.statusCode >= 400 && error.statusCode < 500;
}
/**
* Checks if error is a 5xx server error
* @llm-rule WHEN: Need to categorize errors for monitoring or alerting
* @llm-rule AVOID: Manual status code checking - this handles the logic
* @llm-rule NOTE: SERVER ERRORS: 500-599 status codes (server's fault)
*/
isServerError(error) {
return error.statusCode >= 500;
}
/**
* Gets current environment detection info
* @llm-rule WHEN: Need to check current environment configuration
* @llm-rule AVOID: Direct process.env access - this provides parsed config
*/
getEnvironmentInfo() {
return {
isDevelopment: this.config.environment.isDevelopment,
isProduction: this.config.environment.isProduction,
nodeEnv: this.config.environment.nodeEnv,
};
}
/**
* Gets current error configuration
* @llm-rule WHEN: Debugging error setup or inspecting current settings
* @llm-rule AVOID: Accessing config directly - this provides clean interface
*/
getConfig() {
return { ...this.config };
}
}
//# sourceMappingURL=error.js.map