@nextrush/errors 3.0.7 → 4.0.0-beta.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.
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Context, Middleware, Next } from '@nextrush/types';
1
+ import { Context, Middleware } from '@nextrush/types';
2
2
 
3
3
  /**
4
4
  * @nextrush/errors - Base Error Classes
@@ -7,6 +7,15 @@ import { Context, Middleware, Next } from '@nextrush/types';
7
7
  *
8
8
  * @packageDocumentation
9
9
  */
10
+ /** Options accepted by {@link NextRushError.hydrate} when reconstructing errors. */
11
+ interface HydrateOptions {
12
+ status?: number;
13
+ code?: string;
14
+ details?: Record<string, unknown>;
15
+ requestId?: string;
16
+ traceId?: string;
17
+ timestamp?: string;
18
+ }
10
19
  /**
11
20
  * Base error class for all NextRush errors
12
21
  */
@@ -21,17 +30,45 @@ declare class NextRushError extends Error {
21
30
  readonly details?: Record<string, unknown>;
22
31
  /** Original error that caused this error */
23
32
  readonly cause?: unknown;
33
+ /** Correlation/request identifier for tracing across boundaries (audit E-5). */
34
+ readonly requestId?: string;
35
+ /** Distributed-trace identifier (audit E-5). */
36
+ readonly traceId?: string;
37
+ /** ISO-8601 timestamp of when the error was created, when supplied. */
38
+ readonly timestamp?: string;
24
39
  constructor(message: string, options?: {
25
40
  status?: number;
26
41
  code?: string;
27
42
  expose?: boolean;
28
43
  details?: Record<string, unknown>;
29
44
  cause?: unknown;
45
+ requestId?: string;
46
+ traceId?: string;
47
+ timestamp?: string;
30
48
  });
31
49
  /**
32
50
  * Convert error to JSON representation
33
51
  */
34
52
  toJSON(): Record<string, unknown>;
53
+ /**
54
+ * Reconstruct a {@link NextRushError} from a serialized {@link toJSON} payload
55
+ * (audit E-7).
56
+ *
57
+ * @remarks
58
+ * Enables cross-service transport: a downstream service can rebuild a typed
59
+ * error (with a working `instanceof NextRushError`) from the JSON it received,
60
+ * rather than being left with an opaque plain object. `message` falls back to
61
+ * a generic string when the source error was not exposed.
62
+ *
63
+ * @param json - A payload previously produced by {@link toJSON}.
64
+ * @returns A reconstructed {@link NextRushError}.
65
+ */
66
+ static fromJSON(json: Record<string, unknown>): NextRushError;
67
+ /**
68
+ * Shared hydration logic for {@link fromJSON} across the hierarchy.
69
+ * @internal
70
+ */
71
+ protected static hydrate<T extends NextRushError>(json: Record<string, unknown>, build: (message: string, options: HydrateOptions) => T): T;
35
72
  /**
36
73
  * Create a response-safe version of the error
37
74
  */
@@ -53,9 +90,52 @@ declare class HttpError extends NextRushError {
53
90
  expose?: boolean;
54
91
  details?: Record<string, unknown>;
55
92
  cause?: unknown;
93
+ requestId?: string;
94
+ traceId?: string;
95
+ timestamp?: string;
56
96
  });
97
+ /**
98
+ * Reconstruct an {@link HttpError} from a serialized {@link NextRushError.toJSON}
99
+ * payload (audit E-7).
100
+ *
101
+ * @param json - A payload previously produced by `toJSON()`.
102
+ * @returns A reconstructed {@link HttpError}.
103
+ */
104
+ static fromJSON(json: Record<string, unknown>): HttpError;
57
105
  }
58
106
 
107
+ /**
108
+ * @nextrush/errors - Central Error Code Registry
109
+ *
110
+ * Single source of truth mapping HTTP status codes to their canonical,
111
+ * machine-readable error codes (audit E-4). Both the typed error classes and
112
+ * the {@link createError} factory resolve codes through this registry, so a
113
+ * given status maps to exactly one code regardless of construction path.
114
+ *
115
+ * @packageDocumentation
116
+ */
117
+ /**
118
+ * Canonical error code for each supported HTTP status.
119
+ *
120
+ * @remarks
121
+ * Keep this in sync with the typed classes in `http-errors.ts`. The
122
+ * `audit-fixes` test suite asserts `createError(status).code === ERROR_CODES[status]`
123
+ * for every entry, so drift between a class and this registry fails CI.
124
+ */
125
+ declare const ERROR_CODES: Readonly<Record<number, string>>;
126
+ /**
127
+ * Resolve the canonical error code for an HTTP status.
128
+ *
129
+ * @param status - HTTP status code.
130
+ * @returns The registered canonical code, or `HTTP_<status>` for statuses with
131
+ * no dedicated class.
132
+ */
133
+ declare function codeForStatus(status: number): string;
134
+ /** Generic internal-error code used when no status-specific code applies. */
135
+ declare const GENERIC_ERROR_CODE = "INTERNAL_ERROR";
136
+ /** Canonical code for validation failures. */
137
+ declare const VALIDATION_ERROR_CODE = "VALIDATION_ERROR";
138
+
59
139
  /**
60
140
  * @nextrush/errors - HTTP Error Classes
61
141
  *
@@ -72,6 +152,12 @@ interface HttpErrorOptions {
72
152
  expose?: boolean;
73
153
  details?: Record<string, unknown>;
74
154
  cause?: unknown;
155
+ /** Correlation/request identifier (audit E-5). */
156
+ requestId?: string;
157
+ /** Distributed-trace identifier (audit E-5). */
158
+ traceId?: string;
159
+ /** ISO-8601 creation timestamp (audit E-5). */
160
+ timestamp?: string;
75
161
  }
76
162
  /**
77
163
  * 400 Bad Request - Invalid syntax or malformed request
@@ -505,25 +591,6 @@ declare function getSafeErrorMessage(error: unknown): string;
505
591
  * @packageDocumentation
506
592
  */
507
593
 
508
- /**
509
- * Minimal context interface for error handling.
510
- *
511
- * @deprecated Use `Context` from `@nextrush/types` instead.
512
- * Kept for backward compatibility — will be removed in v4.
513
- */
514
- interface ErrorContext {
515
- method: string;
516
- path: string;
517
- status: number;
518
- json: (data: unknown) => void;
519
- }
520
- /**
521
- * Error handler middleware function type.
522
- *
523
- * @deprecated Use `Middleware` from `@nextrush/types` instead.
524
- * Kept for backward compatibility — will be removed in v4.
525
- */
526
- type ErrorMiddleware = Middleware;
527
594
  /**
528
595
  * Error handler options
529
596
  */
@@ -562,22 +629,5 @@ declare function errorHandler(options?: ErrorHandlerOptions): Middleware;
562
629
  * ```
563
630
  */
564
631
  declare function notFoundHandler(message?: string): Middleware;
565
- /**
566
- * Catch async errors wrapper for route handlers
567
- *
568
- * @example
569
- * ```typescript
570
- * router.get('/users/:id', catchAsync(async (ctx) => {
571
- * const user = await db.users.findById(ctx.params.id);
572
- * if (!user) throw new NotFoundError('User not found');
573
- * ctx.json(user);
574
- * }));
575
- * ```
576
- *
577
- * @deprecated This wrapper is redundant — async errors propagate naturally
578
- * through the middleware chain and are caught by `errorHandler()`. Use the
579
- * handler directly instead.
580
- */
581
- declare function catchAsync(handler: (ctx: Context, next: Next) => Promise<void>): Middleware;
582
632
 
583
- export { BadGatewayError, BadRequestError, ConflictError, type ErrorContext, type ErrorHandlerOptions, type ErrorMiddleware, ExpectationFailedError, FailedDependencyError, ForbiddenError, GatewayTimeoutError, GoneError, HttpError, type HttpErrorOptions, HttpVersionNotSupportedError, ImATeapotError, InsufficientStorageError, InternalServerError, InvalidEmailError, InvalidUrlError, LengthError, LengthRequiredError, LockedError, LoopDetectedError, MethodNotAllowedError, NetworkAuthRequiredError, NextRushError, NotAcceptableError, NotExtendedError, NotFoundError, NotImplementedError, PatternError, PayloadTooLargeError, PaymentRequiredError, PreconditionFailedError, PreconditionRequiredError, ProxyAuthRequiredError, RangeNotSatisfiableError, RangeValidationError, RequestHeaderFieldsTooLargeError, RequestTimeoutError, RequiredFieldError, ServiceUnavailableError, TooEarlyError, TooManyRequestsError, TypeMismatchError, UnauthorizedError, UnavailableForLegalReasonsError, UnprocessableEntityError, UnsupportedMediaTypeError, UpgradeRequiredError, UriTooLongError, ValidationError, type ValidationIssue, VariantAlsoNegotiatesError, badGateway, badRequest, catchAsync, conflict, createError, errorHandler, forbidden, gatewayTimeout, getErrorStatus, getHttpStatusMessage, getSafeErrorMessage, internalError, isHttpError, methodNotAllowed, notFound, notFoundHandler, serviceUnavailable, tooManyRequests, unauthorized, unprocessableEntity };
633
+ export { BadGatewayError, BadRequestError, ConflictError, ERROR_CODES, type ErrorHandlerOptions, ExpectationFailedError, FailedDependencyError, ForbiddenError, GENERIC_ERROR_CODE, GatewayTimeoutError, GoneError, HttpError, type HttpErrorOptions, HttpVersionNotSupportedError, ImATeapotError, InsufficientStorageError, InternalServerError, InvalidEmailError, InvalidUrlError, LengthError, LengthRequiredError, LockedError, LoopDetectedError, MethodNotAllowedError, NetworkAuthRequiredError, NextRushError, NotAcceptableError, NotExtendedError, NotFoundError, NotImplementedError, PatternError, PayloadTooLargeError, PaymentRequiredError, PreconditionFailedError, PreconditionRequiredError, ProxyAuthRequiredError, RangeNotSatisfiableError, RangeValidationError, RequestHeaderFieldsTooLargeError, RequestTimeoutError, RequiredFieldError, ServiceUnavailableError, TooEarlyError, TooManyRequestsError, TypeMismatchError, UnauthorizedError, UnavailableForLegalReasonsError, UnprocessableEntityError, UnsupportedMediaTypeError, UpgradeRequiredError, UriTooLongError, VALIDATION_ERROR_CODE, ValidationError, type ValidationIssue, VariantAlsoNegotiatesError, badGateway, badRequest, codeForStatus, conflict, createError, errorHandler, forbidden, gatewayTimeout, getErrorStatus, getHttpStatusMessage, getSafeErrorMessage, internalError, isHttpError, methodNotAllowed, notFound, notFoundHandler, serviceUnavailable, tooManyRequests, unauthorized, unprocessableEntity };
package/dist/index.js CHANGED
@@ -1,9 +1,76 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
3
 
4
+ // src/codes.ts
5
+ var ERROR_CODES = Object.freeze({
6
+ 400: "BAD_REQUEST",
7
+ 401: "UNAUTHORIZED",
8
+ 402: "PAYMENT_REQUIRED",
9
+ 403: "FORBIDDEN",
10
+ 404: "NOT_FOUND",
11
+ 405: "METHOD_NOT_ALLOWED",
12
+ 406: "NOT_ACCEPTABLE",
13
+ 407: "PROXY_AUTH_REQUIRED",
14
+ 408: "REQUEST_TIMEOUT",
15
+ 409: "CONFLICT",
16
+ 410: "GONE",
17
+ 411: "LENGTH_REQUIRED",
18
+ 412: "PRECONDITION_FAILED",
19
+ 413: "PAYLOAD_TOO_LARGE",
20
+ 414: "URI_TOO_LONG",
21
+ 415: "UNSUPPORTED_MEDIA_TYPE",
22
+ 416: "RANGE_NOT_SATISFIABLE",
23
+ 417: "EXPECTATION_FAILED",
24
+ 418: "IM_A_TEAPOT",
25
+ 422: "UNPROCESSABLE_ENTITY",
26
+ 423: "LOCKED",
27
+ 424: "FAILED_DEPENDENCY",
28
+ 425: "TOO_EARLY",
29
+ 426: "UPGRADE_REQUIRED",
30
+ 428: "PRECONDITION_REQUIRED",
31
+ 429: "TOO_MANY_REQUESTS",
32
+ 431: "REQUEST_HEADER_FIELDS_TOO_LARGE",
33
+ 451: "UNAVAILABLE_FOR_LEGAL_REASONS",
34
+ 500: "INTERNAL_SERVER_ERROR",
35
+ 501: "NOT_IMPLEMENTED",
36
+ 502: "BAD_GATEWAY",
37
+ 503: "SERVICE_UNAVAILABLE",
38
+ 504: "GATEWAY_TIMEOUT",
39
+ 505: "HTTP_VERSION_NOT_SUPPORTED",
40
+ 506: "VARIANT_ALSO_NEGOTIATES",
41
+ 507: "INSUFFICIENT_STORAGE",
42
+ 508: "LOOP_DETECTED",
43
+ 510: "NOT_EXTENDED",
44
+ 511: "NETWORK_AUTH_REQUIRED"
45
+ });
46
+ function codeForStatus(status) {
47
+ return ERROR_CODES[status] ?? `HTTP_${status}`;
48
+ }
49
+ __name(codeForStatus, "codeForStatus");
50
+ var GENERIC_ERROR_CODE = "INTERNAL_ERROR";
51
+ var VALIDATION_ERROR_CODE = "VALIDATION_ERROR";
52
+
4
53
  // src/base.ts
5
54
  var V8Error = Error;
6
- var NextRushError = class extends Error {
55
+ var MAX_CAUSE_DEPTH = 5;
56
+ function serializeCause(cause, seen, depth) {
57
+ if (cause === void 0 || cause === null || depth >= MAX_CAUSE_DEPTH) return void 0;
58
+ if (typeof cause !== "object") return {
59
+ message: String(cause)
60
+ };
61
+ if (seen.has(cause)) return void 0;
62
+ seen.add(cause);
63
+ const err = cause;
64
+ const out = {};
65
+ if (typeof err.name === "string") out.name = err.name;
66
+ if (typeof err.message === "string") out.message = err.message;
67
+ if (typeof err.code === "string") out.code = err.code;
68
+ const nested = serializeCause(err.cause, seen, depth + 1);
69
+ if (nested !== void 0) out.cause = nested;
70
+ return out;
71
+ }
72
+ __name(serializeCause, "serializeCause");
73
+ var NextRushError = class _NextRushError extends Error {
7
74
  static {
8
75
  __name(this, "NextRushError");
9
76
  }
@@ -17,14 +84,27 @@ var NextRushError = class extends Error {
17
84
  details;
18
85
  /** Original error that caused this error */
19
86
  cause;
87
+ /** Correlation/request identifier for tracing across boundaries (audit E-5). */
88
+ requestId;
89
+ /** Distributed-trace identifier (audit E-5). */
90
+ traceId;
91
+ /** ISO-8601 timestamp of when the error was created, when supplied. */
92
+ timestamp;
20
93
  constructor(message, options = {}) {
21
- super(message);
94
+ super(message, options.cause !== void 0 ? {
95
+ cause: options.cause
96
+ } : void 0);
22
97
  this.name = this.constructor.name;
23
98
  this.status = options.status ?? 500;
24
99
  this.code = options.code ?? "INTERNAL_ERROR";
25
100
  this.expose = options.expose ?? this.status < 500;
26
- this.details = options.details;
101
+ this.details = options.details ? Object.freeze({
102
+ ...options.details
103
+ }) : void 0;
27
104
  this.cause = options.cause;
105
+ this.requestId = options.requestId;
106
+ this.traceId = options.traceId;
107
+ this.timestamp = options.timestamp;
28
108
  if (V8Error.captureStackTrace && !(this.expose && this.status < 500)) {
29
109
  V8Error.captureStackTrace(this, this.constructor);
30
110
  }
@@ -42,9 +122,50 @@ var NextRushError = class extends Error {
42
122
  if (this.expose && this.details) {
43
123
  json.details = this.details;
44
124
  }
125
+ if (this.expose && this.cause !== void 0) {
126
+ const serialized = serializeCause(this.cause, /* @__PURE__ */ new Set(), 0);
127
+ if (serialized !== void 0) {
128
+ json.cause = serialized;
129
+ }
130
+ }
131
+ if (this.requestId !== void 0) json.requestId = this.requestId;
132
+ if (this.traceId !== void 0) json.traceId = this.traceId;
133
+ if (this.timestamp !== void 0) json.timestamp = this.timestamp;
45
134
  return json;
46
135
  }
47
136
  /**
137
+ * Reconstruct a {@link NextRushError} from a serialized {@link toJSON} payload
138
+ * (audit E-7).
139
+ *
140
+ * @remarks
141
+ * Enables cross-service transport: a downstream service can rebuild a typed
142
+ * error (with a working `instanceof NextRushError`) from the JSON it received,
143
+ * rather than being left with an opaque plain object. `message` falls back to
144
+ * a generic string when the source error was not exposed.
145
+ *
146
+ * @param json - A payload previously produced by {@link toJSON}.
147
+ * @returns A reconstructed {@link NextRushError}.
148
+ */
149
+ static fromJSON(json) {
150
+ return _NextRushError.hydrate(json, (message, options) => new _NextRushError(message, options));
151
+ }
152
+ /**
153
+ * Shared hydration logic for {@link fromJSON} across the hierarchy.
154
+ * @internal
155
+ */
156
+ static hydrate(json, build) {
157
+ const status = typeof json.status === "number" ? json.status : 500;
158
+ const message = typeof json.message === "string" ? json.message : getHttpStatusMessage(status);
159
+ return build(message, {
160
+ status,
161
+ code: typeof json.code === "string" ? json.code : void 0,
162
+ details: json.details !== null && typeof json.details === "object" ? json.details : void 0,
163
+ requestId: typeof json.requestId === "string" ? json.requestId : void 0,
164
+ traceId: typeof json.traceId === "string" ? json.traceId : void 0,
165
+ timestamp: typeof json.timestamp === "string" ? json.timestamp : void 0
166
+ });
167
+ }
168
+ /**
48
169
  * Create a response-safe version of the error
49
170
  */
50
171
  toResponse() {
@@ -99,19 +220,38 @@ function getHttpStatusMessage(status) {
99
220
  return HTTP_STATUS_MESSAGES[status] ?? `HTTP Error ${status}`;
100
221
  }
101
222
  __name(getHttpStatusMessage, "getHttpStatusMessage");
102
- var HttpError = class extends NextRushError {
223
+ var HttpError = class _HttpError extends NextRushError {
103
224
  static {
104
225
  __name(this, "HttpError");
105
226
  }
106
227
  constructor(status, message, options = {}) {
107
228
  super(message ?? getHttpStatusMessage(status), {
108
229
  status,
109
- code: options.code ?? `HTTP_${status}`,
230
+ code: options.code ?? codeForStatus(status),
110
231
  expose: options.expose ?? status < 500,
111
232
  details: options.details,
112
- cause: options.cause
233
+ cause: options.cause,
234
+ requestId: options.requestId,
235
+ traceId: options.traceId,
236
+ timestamp: options.timestamp
113
237
  });
114
238
  }
239
+ /**
240
+ * Reconstruct an {@link HttpError} from a serialized {@link NextRushError.toJSON}
241
+ * payload (audit E-7).
242
+ *
243
+ * @param json - A payload previously produced by `toJSON()`.
244
+ * @returns A reconstructed {@link HttpError}.
245
+ */
246
+ static fromJSON(json) {
247
+ return NextRushError.hydrate(json, (message, options) => new _HttpError(options.status ?? 500, message, {
248
+ code: options.code,
249
+ details: options.details,
250
+ requestId: options.requestId,
251
+ traceId: options.traceId,
252
+ timestamp: options.timestamp
253
+ }));
254
+ }
115
255
  };
116
256
 
117
257
  // src/http-errors.ts
@@ -581,7 +721,9 @@ var ValidationError = class _ValidationError extends NextRushError {
581
721
  code: "VALIDATION_ERROR",
582
722
  expose: true
583
723
  });
584
- this.issues = issues;
724
+ this.issues = Object.freeze([
725
+ ...issues
726
+ ]);
585
727
  }
586
728
  /**
587
729
  * Create from a single field error
@@ -777,16 +919,42 @@ var InvalidUrlError = class extends ValidationError {
777
919
  var ERROR_MAP = {
778
920
  400: BadRequestError,
779
921
  401: UnauthorizedError,
922
+ 402: PaymentRequiredError,
780
923
  403: ForbiddenError,
781
924
  404: NotFoundError,
925
+ 406: NotAcceptableError,
926
+ 407: ProxyAuthRequiredError,
927
+ 408: RequestTimeoutError,
782
928
  409: ConflictError,
929
+ 410: GoneError,
930
+ 411: LengthRequiredError,
931
+ 412: PreconditionFailedError,
932
+ 413: PayloadTooLargeError,
933
+ 414: UriTooLongError,
934
+ 415: UnsupportedMediaTypeError,
935
+ 416: RangeNotSatisfiableError,
936
+ 417: ExpectationFailedError,
937
+ 418: ImATeapotError,
783
938
  422: UnprocessableEntityError,
939
+ 423: LockedError,
940
+ 424: FailedDependencyError,
941
+ 425: TooEarlyError,
942
+ 426: UpgradeRequiredError,
943
+ 428: PreconditionRequiredError,
784
944
  429: TooManyRequestsError,
945
+ 431: RequestHeaderFieldsTooLargeError,
946
+ 451: UnavailableForLegalReasonsError,
785
947
  500: InternalServerError,
786
948
  501: NotImplementedError,
787
949
  502: BadGatewayError,
788
950
  503: ServiceUnavailableError,
789
- 504: GatewayTimeoutError
951
+ 504: GatewayTimeoutError,
952
+ 505: HttpVersionNotSupportedError,
953
+ 506: VariantAlsoNegotiatesError,
954
+ 507: InsufficientStorageError,
955
+ 508: LoopDetectedError,
956
+ 510: NotExtendedError,
957
+ 511: NetworkAuthRequiredError
790
958
  };
791
959
  function createError(status, message, options) {
792
960
  if (status === 405) {
@@ -910,6 +1078,8 @@ function errorHandler(options = {}) {
910
1078
  let body;
911
1079
  if (transform) {
912
1080
  body = transform(err, ctx);
1081
+ } else if (err instanceof NextRushError) {
1082
+ body = err.toJSON();
913
1083
  } else {
914
1084
  body = {
915
1085
  error: expose ? err.name : getHttpStatusMessage(status),
@@ -920,9 +1090,9 @@ function errorHandler(options = {}) {
920
1090
  if (expose && details) {
921
1091
  body.details = details;
922
1092
  }
923
- if (includeStack && err.stack) {
924
- body.stack = err.stack.split("\n").map((line) => line.trim());
925
- }
1093
+ }
1094
+ if (includeStack && err.stack) {
1095
+ body.stack = err.stack.split("\n").map((line) => line.trim());
926
1096
  }
927
1097
  ctx.json(body);
928
1098
  }
@@ -943,17 +1113,15 @@ function notFoundHandler(message = "Not Found") {
943
1113
  };
944
1114
  }
945
1115
  __name(notFoundHandler, "notFoundHandler");
946
- function catchAsync(handler) {
947
- return handler;
948
- }
949
- __name(catchAsync, "catchAsync");
950
1116
  export {
951
1117
  BadGatewayError,
952
1118
  BadRequestError,
953
1119
  ConflictError,
1120
+ ERROR_CODES,
954
1121
  ExpectationFailedError,
955
1122
  FailedDependencyError,
956
1123
  ForbiddenError,
1124
+ GENERIC_ERROR_CODE,
957
1125
  GatewayTimeoutError,
958
1126
  GoneError,
959
1127
  HttpError,
@@ -995,11 +1163,12 @@ export {
995
1163
  UnsupportedMediaTypeError,
996
1164
  UpgradeRequiredError,
997
1165
  UriTooLongError,
1166
+ VALIDATION_ERROR_CODE,
998
1167
  ValidationError,
999
1168
  VariantAlsoNegotiatesError,
1000
1169
  badGateway,
1001
1170
  badRequest,
1002
- catchAsync,
1171
+ codeForStatus,
1003
1172
  conflict,
1004
1173
  createError,
1005
1174
  errorHandler,