@nextrush/errors 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,409 @@
1
+ /**
2
+ * @nextrush/errors - HTTP Error Classes
3
+ *
4
+ * Standard HTTP error classes for common status codes.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import { HttpError } from './base';
10
+
11
+ /**
12
+ * Options for creating HTTP errors
13
+ */
14
+ export interface HttpErrorOptions {
15
+ code?: string;
16
+ expose?: boolean;
17
+ details?: Record<string, unknown>;
18
+ cause?: unknown;
19
+ }
20
+
21
+ // =============================================================================
22
+ // 4xx Client Errors
23
+ // =============================================================================
24
+
25
+ /**
26
+ * 400 Bad Request - Invalid syntax or malformed request
27
+ */
28
+ export class BadRequestError extends HttpError {
29
+ constructor(message = 'Bad Request', options: HttpErrorOptions = {}) {
30
+ super(400, message, { code: 'BAD_REQUEST', ...options });
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 401 Unauthorized - Authentication required
36
+ */
37
+ export class UnauthorizedError extends HttpError {
38
+ constructor(message = 'Unauthorized', options: HttpErrorOptions = {}) {
39
+ super(401, message, { code: 'UNAUTHORIZED', ...options });
40
+ }
41
+ }
42
+
43
+ /**
44
+ * 402 Payment Required - Payment needed
45
+ */
46
+ export class PaymentRequiredError extends HttpError {
47
+ constructor(message = 'Payment Required', options: HttpErrorOptions = {}) {
48
+ super(402, message, { code: 'PAYMENT_REQUIRED', ...options });
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 403 Forbidden - Access denied
54
+ */
55
+ export class ForbiddenError extends HttpError {
56
+ constructor(message = 'Forbidden', options: HttpErrorOptions = {}) {
57
+ super(403, message, { code: 'FORBIDDEN', ...options });
58
+ }
59
+ }
60
+
61
+ /**
62
+ * 404 Not Found - Resource not found
63
+ */
64
+ export class NotFoundError extends HttpError {
65
+ constructor(message = 'Not Found', options: HttpErrorOptions = {}) {
66
+ super(404, message, { code: 'NOT_FOUND', ...options });
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 405 Method Not Allowed - HTTP method not supported
72
+ */
73
+ export class MethodNotAllowedError extends HttpError {
74
+ readonly allowedMethods: string[];
75
+
76
+ constructor(
77
+ allowedMethods: string[] = [],
78
+ message = 'Method Not Allowed',
79
+ options: HttpErrorOptions = {}
80
+ ) {
81
+ super(405, message, {
82
+ code: 'METHOD_NOT_ALLOWED',
83
+ details: { allowedMethods },
84
+ ...options,
85
+ });
86
+ this.allowedMethods = allowedMethods;
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 406 Not Acceptable - Cannot produce acceptable response
92
+ */
93
+ export class NotAcceptableError extends HttpError {
94
+ constructor(message = 'Not Acceptable', options: HttpErrorOptions = {}) {
95
+ super(406, message, { code: 'NOT_ACCEPTABLE', ...options });
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 407 Proxy Authentication Required
101
+ */
102
+ export class ProxyAuthRequiredError extends HttpError {
103
+ constructor(message = 'Proxy Authentication Required', options: HttpErrorOptions = {}) {
104
+ super(407, message, { code: 'PROXY_AUTH_REQUIRED', ...options });
105
+ }
106
+ }
107
+
108
+ /**
109
+ * 408 Request Timeout - Client took too long
110
+ */
111
+ export class RequestTimeoutError extends HttpError {
112
+ constructor(message = 'Request Timeout', options: HttpErrorOptions = {}) {
113
+ super(408, message, { code: 'REQUEST_TIMEOUT', ...options });
114
+ }
115
+ }
116
+
117
+ /**
118
+ * 409 Conflict - Request conflicts with current state
119
+ */
120
+ export class ConflictError extends HttpError {
121
+ constructor(message = 'Conflict', options: HttpErrorOptions = {}) {
122
+ super(409, message, { code: 'CONFLICT', ...options });
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 410 Gone - Resource permanently deleted
128
+ */
129
+ export class GoneError extends HttpError {
130
+ constructor(message = 'Gone', options: HttpErrorOptions = {}) {
131
+ super(410, message, { code: 'GONE', ...options });
132
+ }
133
+ }
134
+
135
+ /**
136
+ * 411 Length Required - Content-Length header required
137
+ */
138
+ export class LengthRequiredError extends HttpError {
139
+ constructor(message = 'Length Required', options: HttpErrorOptions = {}) {
140
+ super(411, message, { code: 'LENGTH_REQUIRED', ...options });
141
+ }
142
+ }
143
+
144
+ /**
145
+ * 412 Precondition Failed - Precondition in headers not met
146
+ */
147
+ export class PreconditionFailedError extends HttpError {
148
+ constructor(message = 'Precondition Failed', options: HttpErrorOptions = {}) {
149
+ super(412, message, { code: 'PRECONDITION_FAILED', ...options });
150
+ }
151
+ }
152
+
153
+ /**
154
+ * 413 Payload Too Large - Request body too large
155
+ */
156
+ export class PayloadTooLargeError extends HttpError {
157
+ constructor(message = 'Payload Too Large', options: HttpErrorOptions = {}) {
158
+ super(413, message, { code: 'PAYLOAD_TOO_LARGE', ...options });
159
+ }
160
+ }
161
+
162
+ /**
163
+ * 414 URI Too Long - Request URI too long
164
+ */
165
+ export class UriTooLongError extends HttpError {
166
+ constructor(message = 'URI Too Long', options: HttpErrorOptions = {}) {
167
+ super(414, message, { code: 'URI_TOO_LONG', ...options });
168
+ }
169
+ }
170
+
171
+ /**
172
+ * 415 Unsupported Media Type - Content type not supported
173
+ */
174
+ export class UnsupportedMediaTypeError extends HttpError {
175
+ constructor(message = 'Unsupported Media Type', options: HttpErrorOptions = {}) {
176
+ super(415, message, { code: 'UNSUPPORTED_MEDIA_TYPE', ...options });
177
+ }
178
+ }
179
+
180
+ /**
181
+ * 416 Range Not Satisfiable - Cannot satisfy Range header
182
+ */
183
+ export class RangeNotSatisfiableError extends HttpError {
184
+ constructor(message = 'Range Not Satisfiable', options: HttpErrorOptions = {}) {
185
+ super(416, message, { code: 'RANGE_NOT_SATISFIABLE', ...options });
186
+ }
187
+ }
188
+
189
+ /**
190
+ * 417 Expectation Failed - Cannot meet Expect header
191
+ */
192
+ export class ExpectationFailedError extends HttpError {
193
+ constructor(message = 'Expectation Failed', options: HttpErrorOptions = {}) {
194
+ super(417, message, { code: 'EXPECTATION_FAILED', ...options });
195
+ }
196
+ }
197
+
198
+ /**
199
+ * 418 I'm a Teapot - RFC 2324
200
+ */
201
+ export class ImATeapotError extends HttpError {
202
+ constructor(message = "I'm a teapot", options: HttpErrorOptions = {}) {
203
+ super(418, message, { code: 'IM_A_TEAPOT', ...options });
204
+ }
205
+ }
206
+
207
+ /**
208
+ * 422 Unprocessable Entity - Semantic errors
209
+ */
210
+ export class UnprocessableEntityError extends HttpError {
211
+ constructor(message = 'Unprocessable Entity', options: HttpErrorOptions = {}) {
212
+ super(422, message, { code: 'UNPROCESSABLE_ENTITY', ...options });
213
+ }
214
+ }
215
+
216
+ /**
217
+ * 423 Locked - Resource is locked
218
+ */
219
+ export class LockedError extends HttpError {
220
+ constructor(message = 'Locked', options: HttpErrorOptions = {}) {
221
+ super(423, message, { code: 'LOCKED', ...options });
222
+ }
223
+ }
224
+
225
+ /**
226
+ * 424 Failed Dependency - Dependent request failed
227
+ */
228
+ export class FailedDependencyError extends HttpError {
229
+ constructor(message = 'Failed Dependency', options: HttpErrorOptions = {}) {
230
+ super(424, message, { code: 'FAILED_DEPENDENCY', ...options });
231
+ }
232
+ }
233
+
234
+ /**
235
+ * 425 Too Early - Request replayed too early
236
+ */
237
+ export class TooEarlyError extends HttpError {
238
+ constructor(message = 'Too Early', options: HttpErrorOptions = {}) {
239
+ super(425, message, { code: 'TOO_EARLY', ...options });
240
+ }
241
+ }
242
+
243
+ /**
244
+ * 426 Upgrade Required - Client should upgrade protocol
245
+ */
246
+ export class UpgradeRequiredError extends HttpError {
247
+ constructor(message = 'Upgrade Required', options: HttpErrorOptions = {}) {
248
+ super(426, message, { code: 'UPGRADE_REQUIRED', ...options });
249
+ }
250
+ }
251
+
252
+ /**
253
+ * 428 Precondition Required - Origin server requires conditional request
254
+ */
255
+ export class PreconditionRequiredError extends HttpError {
256
+ constructor(message = 'Precondition Required', options: HttpErrorOptions = {}) {
257
+ super(428, message, { code: 'PRECONDITION_REQUIRED', ...options });
258
+ }
259
+ }
260
+
261
+ /**
262
+ * 429 Too Many Requests - Rate limit exceeded
263
+ */
264
+ export class TooManyRequestsError extends HttpError {
265
+ readonly retryAfter?: number;
266
+
267
+ constructor(
268
+ message = 'Too Many Requests',
269
+ options: HttpErrorOptions & { retryAfter?: number } = {}
270
+ ) {
271
+ super(429, message, {
272
+ code: 'TOO_MANY_REQUESTS',
273
+ details: options.retryAfter ? { retryAfter: options.retryAfter } : undefined,
274
+ ...options,
275
+ });
276
+ this.retryAfter = options.retryAfter;
277
+ }
278
+ }
279
+
280
+ /**
281
+ * 431 Request Header Fields Too Large
282
+ */
283
+ export class RequestHeaderFieldsTooLargeError extends HttpError {
284
+ constructor(message = 'Request Header Fields Too Large', options: HttpErrorOptions = {}) {
285
+ super(431, message, { code: 'REQUEST_HEADER_FIELDS_TOO_LARGE', ...options });
286
+ }
287
+ }
288
+
289
+ /**
290
+ * 451 Unavailable For Legal Reasons
291
+ */
292
+ export class UnavailableForLegalReasonsError extends HttpError {
293
+ constructor(message = 'Unavailable For Legal Reasons', options: HttpErrorOptions = {}) {
294
+ super(451, message, { code: 'UNAVAILABLE_FOR_LEGAL_REASONS', ...options });
295
+ }
296
+ }
297
+
298
+ // =============================================================================
299
+ // 5xx Server Errors
300
+ // =============================================================================
301
+
302
+ /**
303
+ * 500 Internal Server Error - Generic server error
304
+ */
305
+ export class InternalServerError extends HttpError {
306
+ constructor(message = 'Internal Server Error', options: HttpErrorOptions = {}) {
307
+ super(500, message, { code: 'INTERNAL_SERVER_ERROR', expose: false, ...options });
308
+ }
309
+ }
310
+
311
+ /**
312
+ * 501 Not Implemented - Feature not implemented
313
+ */
314
+ export class NotImplementedError extends HttpError {
315
+ constructor(message = 'Not Implemented', options: HttpErrorOptions = {}) {
316
+ super(501, message, { code: 'NOT_IMPLEMENTED', expose: false, ...options });
317
+ }
318
+ }
319
+
320
+ /**
321
+ * 502 Bad Gateway - Invalid upstream response
322
+ */
323
+ export class BadGatewayError extends HttpError {
324
+ constructor(message = 'Bad Gateway', options: HttpErrorOptions = {}) {
325
+ super(502, message, { code: 'BAD_GATEWAY', expose: false, ...options });
326
+ }
327
+ }
328
+
329
+ /**
330
+ * 503 Service Unavailable - Server temporarily unavailable
331
+ */
332
+ export class ServiceUnavailableError extends HttpError {
333
+ readonly retryAfter?: number;
334
+
335
+ constructor(
336
+ message = 'Service Unavailable',
337
+ options: HttpErrorOptions & { retryAfter?: number } = {}
338
+ ) {
339
+ super(503, message, {
340
+ code: 'SERVICE_UNAVAILABLE',
341
+ expose: false,
342
+ ...options,
343
+ });
344
+ this.retryAfter = options.retryAfter;
345
+ }
346
+ }
347
+
348
+ /**
349
+ * 504 Gateway Timeout - Upstream timeout
350
+ */
351
+ export class GatewayTimeoutError extends HttpError {
352
+ constructor(message = 'Gateway Timeout', options: HttpErrorOptions = {}) {
353
+ super(504, message, { code: 'GATEWAY_TIMEOUT', expose: false, ...options });
354
+ }
355
+ }
356
+
357
+ /**
358
+ * 505 HTTP Version Not Supported
359
+ */
360
+ export class HttpVersionNotSupportedError extends HttpError {
361
+ constructor(message = 'HTTP Version Not Supported', options: HttpErrorOptions = {}) {
362
+ super(505, message, { code: 'HTTP_VERSION_NOT_SUPPORTED', expose: false, ...options });
363
+ }
364
+ }
365
+
366
+ /**
367
+ * 506 Variant Also Negotiates
368
+ */
369
+ export class VariantAlsoNegotiatesError extends HttpError {
370
+ constructor(message = 'Variant Also Negotiates', options: HttpErrorOptions = {}) {
371
+ super(506, message, { code: 'VARIANT_ALSO_NEGOTIATES', expose: false, ...options });
372
+ }
373
+ }
374
+
375
+ /**
376
+ * 507 Insufficient Storage
377
+ */
378
+ export class InsufficientStorageError extends HttpError {
379
+ constructor(message = 'Insufficient Storage', options: HttpErrorOptions = {}) {
380
+ super(507, message, { code: 'INSUFFICIENT_STORAGE', expose: false, ...options });
381
+ }
382
+ }
383
+
384
+ /**
385
+ * 508 Loop Detected
386
+ */
387
+ export class LoopDetectedError extends HttpError {
388
+ constructor(message = 'Loop Detected', options: HttpErrorOptions = {}) {
389
+ super(508, message, { code: 'LOOP_DETECTED', expose: false, ...options });
390
+ }
391
+ }
392
+
393
+ /**
394
+ * 510 Not Extended
395
+ */
396
+ export class NotExtendedError extends HttpError {
397
+ constructor(message = 'Not Extended', options: HttpErrorOptions = {}) {
398
+ super(510, message, { code: 'NOT_EXTENDED', expose: false, ...options });
399
+ }
400
+ }
401
+
402
+ /**
403
+ * 511 Network Authentication Required
404
+ */
405
+ export class NetworkAuthRequiredError extends HttpError {
406
+ constructor(message = 'Network Authentication Required', options: HttpErrorOptions = {}) {
407
+ super(511, message, { code: 'NETWORK_AUTH_REQUIRED', expose: false, ...options });
408
+ }
409
+ }
package/src/index.ts ADDED
@@ -0,0 +1,101 @@
1
+ /**
2
+ * @nextrush/errors
3
+ *
4
+ * Standardized error handling for NextRush framework.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ // Base classes
10
+ export { HttpError, NextRushError, getHttpStatusMessage } from './base';
11
+
12
+ // HTTP errors - 4xx
13
+ export {
14
+ BadRequestError,
15
+ ConflictError,
16
+ ExpectationFailedError,
17
+ FailedDependencyError,
18
+ ForbiddenError,
19
+ GoneError,
20
+ ImATeapotError,
21
+ LengthRequiredError,
22
+ LockedError,
23
+ MethodNotAllowedError,
24
+ NotAcceptableError,
25
+ NotFoundError,
26
+ PayloadTooLargeError,
27
+ PaymentRequiredError,
28
+ PreconditionFailedError,
29
+ PreconditionRequiredError,
30
+ ProxyAuthRequiredError,
31
+ RangeNotSatisfiableError,
32
+ RequestHeaderFieldsTooLargeError,
33
+ RequestTimeoutError,
34
+ TooEarlyError,
35
+ TooManyRequestsError,
36
+ UnauthorizedError,
37
+ UnavailableForLegalReasonsError,
38
+ UnprocessableEntityError,
39
+ UnsupportedMediaTypeError,
40
+ UpgradeRequiredError,
41
+ UriTooLongError,
42
+ type HttpErrorOptions,
43
+ } from './http-errors';
44
+
45
+ // HTTP errors - 5xx
46
+ export {
47
+ BadGatewayError,
48
+ GatewayTimeoutError,
49
+ HttpVersionNotSupportedError,
50
+ InsufficientStorageError,
51
+ InternalServerError,
52
+ LoopDetectedError,
53
+ NetworkAuthRequiredError,
54
+ NotExtendedError,
55
+ NotImplementedError,
56
+ ServiceUnavailableError,
57
+ VariantAlsoNegotiatesError,
58
+ } from './http-errors';
59
+
60
+ // Validation errors
61
+ export {
62
+ InvalidEmailError,
63
+ InvalidUrlError,
64
+ LengthError,
65
+ PatternError,
66
+ RangeValidationError,
67
+ RequiredFieldError,
68
+ TypeMismatchError,
69
+ ValidationError,
70
+ type ValidationIssue,
71
+ } from './validation';
72
+
73
+ // Factory functions
74
+ export {
75
+ badGateway,
76
+ badRequest,
77
+ conflict,
78
+ createError,
79
+ forbidden,
80
+ gatewayTimeout,
81
+ getErrorStatus,
82
+ getSafeErrorMessage,
83
+ internalError,
84
+ isHttpError,
85
+ methodNotAllowed,
86
+ notFound,
87
+ serviceUnavailable,
88
+ tooManyRequests,
89
+ unauthorized,
90
+ unprocessableEntity,
91
+ } from './factory';
92
+
93
+ // Middleware
94
+ export {
95
+ catchAsync,
96
+ errorHandler,
97
+ notFoundHandler,
98
+ type ErrorContext,
99
+ type ErrorHandlerOptions,
100
+ type ErrorMiddleware,
101
+ } from './middleware';
@@ -0,0 +1,183 @@
1
+ /**
2
+ * @nextrush/errors - Error Handler Middleware
3
+ *
4
+ * Middleware for handling errors in NextRush applications.
5
+ *
6
+ * @packageDocumentation
7
+ */
8
+
9
+ import type { Context, Middleware, Next } from '@nextrush/types';
10
+ import { HttpError, NextRushError, getHttpStatusMessage } from './base';
11
+
12
+ /**
13
+ * Minimal context interface for error handling.
14
+ *
15
+ * @deprecated Use `Context` from `@nextrush/types` instead.
16
+ * Kept for backward compatibility — will be removed in v4.
17
+ */
18
+ export interface ErrorContext {
19
+ method: string;
20
+ path: string;
21
+ status: number;
22
+ json: (data: unknown) => void;
23
+ }
24
+
25
+ /**
26
+ * Error handler middleware function type.
27
+ *
28
+ * @deprecated Use `Middleware` from `@nextrush/types` instead.
29
+ * Kept for backward compatibility — will be removed in v4.
30
+ */
31
+ export type ErrorMiddleware = Middleware;
32
+
33
+ /**
34
+ * Error handler options
35
+ */
36
+ export interface ErrorHandlerOptions {
37
+ /** Include stack trace in development */
38
+ includeStack?: boolean;
39
+
40
+ /** Custom error logger */
41
+ logger?: (error: Error, ctx: Context) => void;
42
+
43
+ /** Custom error transformer */
44
+ transform?: (error: Error, ctx: Context) => Record<string, unknown>;
45
+
46
+ /** Handle specific error types */
47
+ handlers?: Map<new (...args: unknown[]) => Error, (error: Error, ctx: Context) => void>;
48
+ }
49
+
50
+ /**
51
+ * Default error logger
52
+ */
53
+ function defaultLogger(error: Error, ctx: Context): void {
54
+ const status = error instanceof HttpError ? error.status : 500;
55
+
56
+ if (status >= 500) {
57
+ console.error(`[ERROR] ${ctx.method} ${ctx.path}:`, error);
58
+ } else {
59
+ console.warn(`[WARN] ${ctx.method} ${ctx.path}: ${error.message}`);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Create error handler middleware
65
+ *
66
+ * @example
67
+ * ```typescript
68
+ * const app = createApp();
69
+ *
70
+ * // Add error handler first
71
+ * app.use(errorHandler({
72
+ * includeStack: process.env.NODE_ENV !== 'production',
73
+ * logger: (err, ctx) => myLogger.error(err),
74
+ * }));
75
+ * ```
76
+ */
77
+ export function errorHandler(options: ErrorHandlerOptions = {}): Middleware {
78
+ const { includeStack = false, logger = defaultLogger, transform, handlers } = options;
79
+
80
+ return async (ctx: Context, next: Next): Promise<void> => {
81
+ try {
82
+ await next();
83
+ } catch (error) {
84
+ const err = error instanceof Error ? error : new Error(String(error));
85
+
86
+ // Log the error
87
+ logger(err, ctx);
88
+
89
+ // Check for custom handlers
90
+ if (handlers) {
91
+ for (const [ErrorType, handler] of handlers) {
92
+ if (err instanceof ErrorType) {
93
+ handler(err, ctx);
94
+ return;
95
+ }
96
+ }
97
+ }
98
+
99
+ // Determine status code
100
+ let status = 500;
101
+ let expose = false;
102
+ let code = 'INTERNAL_ERROR';
103
+ let details: Record<string, unknown> | undefined;
104
+
105
+ if (err instanceof HttpError || err instanceof NextRushError) {
106
+ status = err.status;
107
+ expose = err.expose;
108
+ code = err.code;
109
+ details = err.details;
110
+ }
111
+
112
+ ctx.status = status;
113
+
114
+ // Build response body
115
+ let body: Record<string, unknown>;
116
+
117
+ if (transform) {
118
+ body = transform(err, ctx);
119
+ } else {
120
+ body = {
121
+ error: expose ? err.name : getHttpStatusMessage(status),
122
+ message: expose ? err.message : 'Internal Server Error',
123
+ code,
124
+ status,
125
+ };
126
+
127
+ if (expose && details) {
128
+ body.details = details;
129
+ }
130
+
131
+ if (includeStack && err.stack) {
132
+ body.stack = err.stack.split('\n').map((line) => line.trim());
133
+ }
134
+ }
135
+
136
+ ctx.json(body);
137
+ }
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Not found handler middleware - catches unhandled requests
143
+ *
144
+ * @example
145
+ * ```typescript
146
+ * // Add at the end of middleware chain
147
+ * app.use(notFoundHandler());
148
+ * ```
149
+ */
150
+ export function notFoundHandler(message = 'Not Found'): Middleware {
151
+ return async (ctx: Context, next: Next): Promise<void> => {
152
+ await next();
153
+ // Only handle if no response was sent and status indicates unhandled
154
+ if (!ctx.responded && ctx.status === 404) {
155
+ ctx.json({
156
+ error: 'NotFoundError',
157
+ message,
158
+ code: 'NOT_FOUND',
159
+ status: 404,
160
+ });
161
+ }
162
+ };
163
+ }
164
+
165
+ /**
166
+ * Catch async errors wrapper for route handlers
167
+ *
168
+ * @example
169
+ * ```typescript
170
+ * router.get('/users/:id', catchAsync(async (ctx) => {
171
+ * const user = await db.users.findById(ctx.params.id);
172
+ * if (!user) throw new NotFoundError('User not found');
173
+ * ctx.json(user);
174
+ * }));
175
+ * ```
176
+ *
177
+ * @deprecated This wrapper is redundant — async errors propagate naturally
178
+ * through the middleware chain and are caught by `errorHandler()`. Use the
179
+ * handler directly instead.
180
+ */
181
+ export function catchAsync(handler: (ctx: Context, next: Next) => Promise<void>): Middleware {
182
+ return handler;
183
+ }