thrilled-be-core
Version:
Core Express backend package with middleware, logging, security, and base application setup
350 lines (349 loc) • 11.9 kB
JavaScript
;
/**
* Custom Error Classes for Enterprise Applications
*
* This file defines a comprehensive set of custom error classes that provide
* better error handling, logging, and debugging capabilities throughout applications.
* These errors follow HTTP status codes and provide structured error information
* for both development and production environments.
*
* @package be-core
* @since 1.0.0
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ErrorUtils = exports.RequestTimeoutError = exports.InternalServerError = exports.SystemError = exports.ConfigurationError = exports.RateLimitError = exports.ThirdPartyAPIError = exports.ServiceUnavailableError = exports.ExternalServiceError = exports.DuplicateResourceError = exports.ConflictError = exports.NotFoundError = exports.ResourceError = exports.BusinessRuleValidationError = exports.SchemaValidationError = exports.ValidationError = exports.SessionExpiredError = exports.InvalidTokenError = exports.TokenExpiredError = exports.AuthorizationError = exports.AuthenticationError = exports.TransactionError = exports.DatabaseConstraintError = exports.QueryTimeoutError = exports.DatabaseConnectionError = exports.DatabaseError = exports.AppError = void 0;
exports.handleErrors = handleErrors;
exports.asyncHandler = asyncHandler;
const thrilled_be_types_1 = require("thrilled-be-types");
/**
* Base application error class
* All custom errors should extend this class
*/
class AppError extends thrilled_be_types_1.HttpException {
timestamp;
context;
code;
isOperational;
status; // Override readonly status from HttpException
constructor(message, status, code, context, isOperational = true) {
super(status, message);
this.name = this.constructor.name;
this.timestamp = new Date();
this.context = context;
this.code = code;
this.isOperational = isOperational;
this.status = status; // Set our mutable status
// Maintains proper stack trace for where our error was thrown
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
/**
* Convert error to JSON representation
*/
toJSON() {
return {
name: this.name,
message: this.message,
status: this.status,
code: this.code,
timestamp: this.timestamp.toISOString(),
context: this.context,
isOperational: this.isOperational,
};
}
}
exports.AppError = AppError;
/**
* Database-related errors
*/
class DatabaseError extends AppError {
constructor(message, context, originalError) {
super(message, 500, 'DATABASE_ERROR', { ...context, originalError: originalError?.message }, true);
}
}
exports.DatabaseError = DatabaseError;
class DatabaseConnectionError extends DatabaseError {
constructor(message = 'Database connection failed', context) {
super(message, context);
this.code = 'DATABASE_CONNECTION_ERROR';
}
}
exports.DatabaseConnectionError = DatabaseConnectionError;
class QueryTimeoutError extends DatabaseError {
constructor(message = 'Database query timed out', context) {
super(message, context);
this.code = 'QUERY_TIMEOUT_ERROR';
this.status = 504; // Gateway timeout
}
}
exports.QueryTimeoutError = QueryTimeoutError;
class DatabaseConstraintError extends DatabaseError {
constructor(message, constraint, context) {
super(message, { ...context, constraint });
this.code = 'DATABASE_CONSTRAINT_ERROR';
this.status = 400; // Bad request
}
}
exports.DatabaseConstraintError = DatabaseConstraintError;
class TransactionError extends DatabaseError {
constructor(message = 'Transaction failed', context) {
super(message, context);
this.code = 'TRANSACTION_ERROR';
}
}
exports.TransactionError = TransactionError;
/**
* Authentication and Authorization errors
*/
class AuthenticationError extends AppError {
constructor(message = 'Authentication failed', context) {
super(message, 401, 'AUTHENTICATION_ERROR', context);
}
}
exports.AuthenticationError = AuthenticationError;
class AuthorizationError extends AppError {
constructor(message = 'Access denied', context) {
super(message, 403, 'AUTHORIZATION_ERROR', context);
}
}
exports.AuthorizationError = AuthorizationError;
class TokenExpiredError extends AuthenticationError {
constructor(message = 'Token has expired', context) {
super(message, context);
this.code = 'TOKEN_EXPIRED_ERROR';
}
}
exports.TokenExpiredError = TokenExpiredError;
class InvalidTokenError extends AuthenticationError {
constructor(message = 'Invalid token provided', context) {
super(message, context);
this.code = 'INVALID_TOKEN_ERROR';
}
}
exports.InvalidTokenError = InvalidTokenError;
class SessionExpiredError extends AuthenticationError {
constructor(message = 'Session has expired', context) {
super(message, context);
this.code = 'SESSION_EXPIRED_ERROR';
}
}
exports.SessionExpiredError = SessionExpiredError;
/**
* Validation errors
*/
class ValidationError extends AppError {
fields;
constructor(message, fields, context) {
super(message, 400, 'VALIDATION_ERROR', context);
this.fields = fields;
}
}
exports.ValidationError = ValidationError;
class SchemaValidationError extends ValidationError {
constructor(message, fields, context) {
super(message, fields, context);
this.code = 'SCHEMA_VALIDATION_ERROR';
}
}
exports.SchemaValidationError = SchemaValidationError;
class BusinessRuleValidationError extends ValidationError {
constructor(message, rule, context) {
super(message, undefined, { ...context, rule });
this.code = 'BUSINESS_RULE_VALIDATION_ERROR';
this.status = 422; // Unprocessable Entity
}
}
exports.BusinessRuleValidationError = BusinessRuleValidationError;
/**
* Resource errors
*/
class ResourceError extends AppError {
constructor(message, status, code, context) {
super(message, status, code, context);
}
}
exports.ResourceError = ResourceError;
class NotFoundError extends ResourceError {
constructor(resource, identifier, context) {
const message = identifier
? `${resource} with ID '${identifier}' not found`
: `${resource} not found`;
super(message, 404, 'NOT_FOUND_ERROR', { ...context, resource, identifier });
}
}
exports.NotFoundError = NotFoundError;
class ConflictError extends ResourceError {
constructor(message, resource, context) {
super(message, 409, 'CONFLICT_ERROR', { ...context, resource });
}
}
exports.ConflictError = ConflictError;
class DuplicateResourceError extends ConflictError {
constructor(resource, field, value, context) {
super(`${resource} with ${field} '${value}' already exists`, resource, { ...context, field, value });
this.code = 'DUPLICATE_RESOURCE_ERROR';
}
}
exports.DuplicateResourceError = DuplicateResourceError;
/**
* External service errors
*/
class ExternalServiceError extends AppError {
constructor(message, service, context) {
super(message, 503, 'EXTERNAL_SERVICE_ERROR', { ...context, service });
}
}
exports.ExternalServiceError = ExternalServiceError;
class ServiceUnavailableError extends ExternalServiceError {
constructor(message = 'Service temporarily unavailable', service, context) {
super(message, service || 'unknown', context);
this.code = 'SERVICE_UNAVAILABLE_ERROR';
}
}
exports.ServiceUnavailableError = ServiceUnavailableError;
class ThirdPartyAPIError extends ExternalServiceError {
constructor(message, apiName, statusCode, context) {
super(message, apiName, { ...context, statusCode });
this.code = 'THIRD_PARTY_API_ERROR';
}
}
exports.ThirdPartyAPIError = ThirdPartyAPIError;
/**
* Rate limiting errors
*/
class RateLimitError extends AppError {
constructor(message = 'Rate limit exceeded', context) {
super(message, 429, 'RATE_LIMIT_ERROR', context);
}
}
exports.RateLimitError = RateLimitError;
/**
* Configuration errors
*/
class ConfigurationError extends AppError {
constructor(message, configKey, context) {
super(message, 500, 'CONFIGURATION_ERROR', { ...context, configKey }, false);
}
}
exports.ConfigurationError = ConfigurationError;
/**
* System errors
*/
class SystemError extends AppError {
constructor(message, context) {
super(message, 500, 'SYSTEM_ERROR', context, false);
}
}
exports.SystemError = SystemError;
class InternalServerError extends SystemError {
constructor(message = 'Internal server error', context) {
super(message, context);
this.code = 'INTERNAL_SERVER_ERROR';
}
}
exports.InternalServerError = InternalServerError;
class RequestTimeoutError extends AppError {
constructor(message = 'Request timeout', context) {
super(message, 408, 'REQUEST_TIMEOUT_ERROR', context);
}
}
exports.RequestTimeoutError = RequestTimeoutError;
/**
* Utility functions for error handling
*/
class ErrorUtils {
/**
* Check if error is operational (expected) or programming error
*/
static isOperationalError(error) {
if (error instanceof AppError) {
return error.isOperational;
}
return false;
}
/**
* Convert any error to AppError instance
*/
static toAppError(error) {
if (error instanceof AppError) {
return error;
}
if (error instanceof Error) {
return new InternalServerError(error.message, { originalError: error.name });
}
return new InternalServerError('Unknown error occurred', { originalError: String(error) });
}
/**
* Check if error should be logged
*/
static shouldLog(error) {
if (error instanceof AppError) {
// Don't log validation errors and expected operational errors
return !(error instanceof ValidationError || error instanceof NotFoundError);
}
return true;
}
/**
* Get error message safe for production
*/
static getPublicMessage(error) {
if (error instanceof AppError) {
return error.message;
}
// Don't expose internal error details in production
if (process.env.NODE_ENV === 'production') {
return 'Internal server error';
}
return error.message;
}
/**
* Extract error context for logging
*/
static getErrorContext(error) {
if (error instanceof AppError) {
return {
name: error.name,
code: error.code,
status: error.status,
context: error.context,
timestamp: error.timestamp,
isOperational: error.isOperational,
};
}
return {
name: error.name,
message: error.message,
stack: error.stack,
};
}
}
exports.ErrorUtils = ErrorUtils;
/**
* Error handler decorator for async functions
*/
function handleErrors(target, propertyKey, descriptor) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args) {
try {
return await originalMethod.apply(this, args);
}
catch (error) {
throw ErrorUtils.toAppError(error);
}
};
return descriptor;
}
/**
* Async wrapper for error handling
*/
function asyncHandler(fn) {
return async (...args) => {
try {
return await fn(...args);
}
catch (error) {
throw ErrorUtils.toAppError(error);
}
};
}