/**
* Error Classes
*
* Structured error types for better error handling and consistent API responses.
*/
/**
* Base application error
* All custom errors extend this class
*/
export class AppError extends Error {
constructor(message, code, status = 500) {
super(message)
this.name = 'AppError'
this.code = code
this.status = status
this.timestamp = Date.now()
// Capture stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor)
}
}
/**
* Convert to JSON for API responses
*/
toJSON() {
return {
error: this.message,
code: this.code,
status: this.status
}
}
}
/**
* Resource not found (404)
*/
export class NotFoundError extends AppError {
constructor(message = 'Not found', resource = null) {
super(message, 'NOT_FOUND', 404)
this.name = 'NotFoundError'
this.resource = resource
}
toJSON() {
return {
...super.toJSON(),
resource: this.resource
}
}
}
/**
* Access forbidden (403)
*/
export class ForbiddenError extends AppError {
constructor(message = 'Forbidden', requiredPermission = null) {
super(message, 'FORBIDDEN', 403)
this.name = 'ForbiddenError'
this.requiredPermission = requiredPermission
}
toJSON() {
return {
...super.toJSON(),
requiredPermission: this.requiredPermission
}
}
}
/**
* Authentication required (401)
*/
export class UnauthorizedError extends AppError {
constructor(message = 'Authentication required', authMethod = null) {
super(message, 'UNAUTHORIZED', 401)
this.name = 'UnauthorizedError'
this.authMethod = authMethod
}
toJSON() {
return {
...super.toJSON(),
authMethod: this.authMethod
}
}
}
/**
* Validation error (400)
*/
export class ValidationError extends AppError {
constructor(message, field = null, details = null) {
super(message, 'VALIDATION_ERROR', 400)
this.name = 'ValidationError'
this.field = field
this.details = details
}
toJSON() {
return {
...super.toJSON(),
field: this.field,
details: this.details
}
}
}
/**
* Resource conflict (409)
* Used for duplicate entries, concurrent modifications, etc.
*/
export class ConflictError extends AppError {
constructor(message = 'Resource conflict', conflictType = null) {
super(message, 'CONFLICT', 409)
this.name = 'ConflictError'
this.conflictType = conflictType
}
toJSON() {
return {
...super.toJSON(),
conflictType: this.conflictType
}
}
}
/**
* Rate limit exceeded (429)
*/
export class RateLimitError extends AppError {
constructor(message = 'Rate limit exceeded', retryAfter = null) {
super(message, 'RATE_LIMIT', 429)
this.name = 'RateLimitError'
this.retryAfter = retryAfter
}
toJSON() {
return {
...super.toJSON(),
retryAfter: this.retryAfter
}
}
}
/**
* Bad gateway / upstream error (502)
*/
export class GatewayError extends AppError {
constructor(message = 'Gateway error', upstream = null) {
super(message, 'GATEWAY_ERROR', 502)
this.name = 'GatewayError'
this.upstream = upstream
}
}
/**
* Service unavailable (503)
*/
export class ServiceUnavailableError extends AppError {
constructor(message = 'Service temporarily unavailable', retryAfter = null) {
super(message, 'SERVICE_UNAVAILABLE', 503)
this.name = 'ServiceUnavailableError'
this.retryAfter = retryAfter
}
}
/**
* Check if an error is an AppError instance
*/
export function isAppError(err) {
return err instanceof AppError
}
/**
* Wrap unknown errors in AppError
*/
export function wrapError(err, defaultMessage = 'An error occurred') {
if (err instanceof AppError) {
return err
}
const wrapped = new AppError(
err.message || defaultMessage,
'INTERNAL_ERROR',
500
)
wrapped.originalError = err
wrapped.stack = err.stack
return wrapped
}
export default {
AppError,
NotFoundError,
ForbiddenError,
UnauthorizedError,
ValidationError,
ConflictError,
RateLimitError,
GatewayError,
ServiceUnavailableError,
isAppError,
wrapError
}