@nextrush/errors 3.0.7 → 4.0.0-beta.1

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,73 @@ 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;
105
+ }
106
+
107
+ /**
108
+ * @nextrush/errors - Header Validation Error
109
+ *
110
+ * @packageDocumentation
111
+ */
112
+
113
+ /**
114
+ * Thrown when a header field name or value fails RFC 9110 grammar
115
+ * validation (field-name token grammar, field-value grammar — no control
116
+ * characters, no leading/trailing whitespace, no obs-fold).
117
+ *
118
+ * @remarks
119
+ * A rejected write here means the application (or a framework internal) is
120
+ * constructing an invalid header, not that a client sent bad input — so this
121
+ * is a 500-class programming error, not a validation-issue-list shape like
122
+ * {@link ValidationError}.
123
+ */
124
+ declare class HeaderValidationError extends NextRushError {
125
+ constructor(message: string);
57
126
  }
58
127
 
128
+ /**
129
+ * @nextrush/errors - Central Error Code Registry
130
+ *
131
+ * Single source of truth mapping HTTP status codes to their canonical,
132
+ * machine-readable error codes (audit E-4). Both the typed error classes and
133
+ * the {@link createError} factory resolve codes through this registry, so a
134
+ * given status maps to exactly one code regardless of construction path.
135
+ *
136
+ * @packageDocumentation
137
+ */
138
+ /**
139
+ * Canonical error code for each supported HTTP status.
140
+ *
141
+ * @remarks
142
+ * Keep this in sync with the typed classes in `http-errors.ts`. The
143
+ * `audit-fixes` test suite asserts `createError(status).code === ERROR_CODES[status]`
144
+ * for every entry, so drift between a class and this registry fails CI.
145
+ */
146
+ declare const ERROR_CODES: Readonly<Record<number, string>>;
147
+ /**
148
+ * Resolve the canonical error code for an HTTP status.
149
+ *
150
+ * @param status - HTTP status code.
151
+ * @returns The registered canonical code, or `HTTP_<status>` for statuses with
152
+ * no dedicated class.
153
+ */
154
+ declare function codeForStatus(status: number): string;
155
+ /** Generic internal-error code used when no status-specific code applies. */
156
+ declare const GENERIC_ERROR_CODE = "INTERNAL_ERROR";
157
+ /** Canonical code for validation failures. */
158
+ declare const VALIDATION_ERROR_CODE = "VALIDATION_ERROR";
159
+
59
160
  /**
60
161
  * @nextrush/errors - HTTP Error Classes
61
162
  *
@@ -72,6 +173,12 @@ interface HttpErrorOptions {
72
173
  expose?: boolean;
73
174
  details?: Record<string, unknown>;
74
175
  cause?: unknown;
176
+ /** Correlation/request identifier (audit E-5). */
177
+ requestId?: string;
178
+ /** Distributed-trace identifier (audit E-5). */
179
+ traceId?: string;
180
+ /** ISO-8601 creation timestamp (audit E-5). */
181
+ timestamp?: string;
75
182
  }
76
183
  /**
77
184
  * 400 Bad Request - Invalid syntax or malformed request
@@ -505,31 +612,26 @@ declare function getSafeErrorMessage(error: unknown): string;
505
612
  * @packageDocumentation
506
613
  */
507
614
 
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
615
  /**
528
616
  * Error handler options
529
617
  */
530
618
  interface ErrorHandlerOptions {
531
619
  /** Include stack trace in development */
532
620
  includeStack?: boolean;
621
+ /**
622
+ * Whether the application is running in production (SEC-14).
623
+ *
624
+ * @remarks
625
+ * `@nextrush/errors` has no access to `app.isProduction` on its own — the
626
+ * caller threads it through explicitly, mirroring `@nextrush/core`'s
627
+ * `writeDefaultErrorResponse(opts.isProduction)`. When `true`, a truthy
628
+ * `includeStack` is ignored (fail closed) and a single warning is logged
629
+ * once per process, rather than once per request. Defaults to `false` so
630
+ * existing callers that don't pass this option keep today's behavior.
631
+ *
632
+ * @default false
633
+ */
634
+ isProduction?: boolean;
533
635
  /** Custom error logger */
534
636
  logger?: (error: Error, ctx: Context) => void;
535
637
  /** Custom error transformer */
@@ -562,22 +664,5 @@ declare function errorHandler(options?: ErrorHandlerOptions): Middleware;
562
664
  * ```
563
665
  */
564
666
  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
667
 
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 };
668
+ export { BadGatewayError, BadRequestError, ConflictError, ERROR_CODES, type ErrorHandlerOptions, ExpectationFailedError, FailedDependencyError, ForbiddenError, GENERIC_ERROR_CODE, GatewayTimeoutError, GoneError, HeaderValidationError, 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,17 +220,50 @@ 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
237
+ });
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
+ }
255
+ };
256
+
257
+ // src/header-validation.ts
258
+ var HeaderValidationError = class extends NextRushError {
259
+ static {
260
+ __name(this, "HeaderValidationError");
261
+ }
262
+ constructor(message) {
263
+ super(message, {
264
+ status: 500,
265
+ code: "HEADER_VALIDATION_ERROR",
266
+ expose: false
113
267
  });
114
268
  }
115
269
  };
@@ -581,7 +735,9 @@ var ValidationError = class _ValidationError extends NextRushError {
581
735
  code: "VALIDATION_ERROR",
582
736
  expose: true
583
737
  });
584
- this.issues = issues;
738
+ this.issues = Object.freeze([
739
+ ...issues
740
+ ]);
585
741
  }
586
742
  /**
587
743
  * Create from a single field error
@@ -777,16 +933,42 @@ var InvalidUrlError = class extends ValidationError {
777
933
  var ERROR_MAP = {
778
934
  400: BadRequestError,
779
935
  401: UnauthorizedError,
936
+ 402: PaymentRequiredError,
780
937
  403: ForbiddenError,
781
938
  404: NotFoundError,
939
+ 406: NotAcceptableError,
940
+ 407: ProxyAuthRequiredError,
941
+ 408: RequestTimeoutError,
782
942
  409: ConflictError,
943
+ 410: GoneError,
944
+ 411: LengthRequiredError,
945
+ 412: PreconditionFailedError,
946
+ 413: PayloadTooLargeError,
947
+ 414: UriTooLongError,
948
+ 415: UnsupportedMediaTypeError,
949
+ 416: RangeNotSatisfiableError,
950
+ 417: ExpectationFailedError,
951
+ 418: ImATeapotError,
783
952
  422: UnprocessableEntityError,
953
+ 423: LockedError,
954
+ 424: FailedDependencyError,
955
+ 425: TooEarlyError,
956
+ 426: UpgradeRequiredError,
957
+ 428: PreconditionRequiredError,
784
958
  429: TooManyRequestsError,
959
+ 431: RequestHeaderFieldsTooLargeError,
960
+ 451: UnavailableForLegalReasonsError,
785
961
  500: InternalServerError,
786
962
  501: NotImplementedError,
787
963
  502: BadGatewayError,
788
964
  503: ServiceUnavailableError,
789
- 504: GatewayTimeoutError
965
+ 504: GatewayTimeoutError,
966
+ 505: HttpVersionNotSupportedError,
967
+ 506: VariantAlsoNegotiatesError,
968
+ 507: InsufficientStorageError,
969
+ 508: LoopDetectedError,
970
+ 510: NotExtendedError,
971
+ 511: NetworkAuthRequiredError
790
972
  };
791
973
  function createError(status, message, options) {
792
974
  if (status === 405) {
@@ -871,6 +1053,7 @@ function getSafeErrorMessage(error) {
871
1053
  __name(getSafeErrorMessage, "getSafeErrorMessage");
872
1054
 
873
1055
  // src/middleware.ts
1056
+ import { SECURITY_AUDIT } from "@nextrush/types";
874
1057
  function defaultLogger(error, ctx) {
875
1058
  const status = error instanceof HttpError ? error.status : 500;
876
1059
  if (status >= 500) {
@@ -881,17 +1064,18 @@ function defaultLogger(error, ctx) {
881
1064
  }
882
1065
  __name(defaultLogger, "defaultLogger");
883
1066
  function errorHandler(options = {}) {
884
- const { includeStack = false, logger = defaultLogger, transform, handlers } = options;
885
- return async (ctx, next) => {
1067
+ const { includeStack = false, isProduction = false, logger = defaultLogger, transform, handlers } = options;
1068
+ let warnedIncludeStackInProduction = false;
1069
+ const handler = /* @__PURE__ */ __name(async (ctx, next) => {
886
1070
  try {
887
1071
  await next();
888
1072
  } catch (error) {
889
1073
  const err = error instanceof Error ? error : new Error(String(error));
890
1074
  logger(err, ctx);
891
1075
  if (handlers) {
892
- for (const [ErrorType, handler] of handlers) {
1076
+ for (const [ErrorType, handler2] of handlers) {
893
1077
  if (err instanceof ErrorType) {
894
- handler(err, ctx);
1078
+ handler2(err, ctx);
895
1079
  return;
896
1080
  }
897
1081
  }
@@ -910,6 +1094,8 @@ function errorHandler(options = {}) {
910
1094
  let body;
911
1095
  if (transform) {
912
1096
  body = transform(err, ctx);
1097
+ } else if (err instanceof NextRushError) {
1098
+ body = err.toJSON();
913
1099
  } else {
914
1100
  body = {
915
1101
  error: expose ? err.name : getHttpStatusMessage(status),
@@ -920,13 +1106,37 @@ function errorHandler(options = {}) {
920
1106
  if (expose && details) {
921
1107
  body.details = details;
922
1108
  }
923
- if (includeStack && err.stack) {
1109
+ }
1110
+ if (includeStack && err.stack) {
1111
+ if (isProduction) {
1112
+ if (!warnedIncludeStackInProduction) {
1113
+ warnedIncludeStackInProduction = true;
1114
+ console.warn("[@nextrush/errors] includeStack: true was ignored because isProduction is true. Stack traces are never emitted in production regardless of configuration.");
1115
+ }
1116
+ } else {
924
1117
  body.stack = err.stack.split("\n").map((line) => line.trim());
925
1118
  }
926
1119
  }
927
1120
  ctx.json(body);
928
1121
  }
929
- };
1122
+ }, "handler");
1123
+ function auditVerdict() {
1124
+ if (includeStack && !isProduction) {
1125
+ return {
1126
+ level: "warn",
1127
+ message: "errorHandler({ includeStack: true }) was constructed without isProduction \u2014 the per-request guard that ignores includeStack in production never fires unless isProduction is explicitly threaded through (e.g. isProduction: app.isProduction). Wire it, or this instance will leak stack traces in production."
1128
+ };
1129
+ }
1130
+ return {
1131
+ level: "ok"
1132
+ };
1133
+ }
1134
+ __name(auditVerdict, "auditVerdict");
1135
+ Object.defineProperty(handler, SECURITY_AUDIT, {
1136
+ value: auditVerdict,
1137
+ enumerable: false
1138
+ });
1139
+ return handler;
930
1140
  }
931
1141
  __name(errorHandler, "errorHandler");
932
1142
  function notFoundHandler(message = "Not Found") {
@@ -943,19 +1153,18 @@ function notFoundHandler(message = "Not Found") {
943
1153
  };
944
1154
  }
945
1155
  __name(notFoundHandler, "notFoundHandler");
946
- function catchAsync(handler) {
947
- return handler;
948
- }
949
- __name(catchAsync, "catchAsync");
950
1156
  export {
951
1157
  BadGatewayError,
952
1158
  BadRequestError,
953
1159
  ConflictError,
1160
+ ERROR_CODES,
954
1161
  ExpectationFailedError,
955
1162
  FailedDependencyError,
956
1163
  ForbiddenError,
1164
+ GENERIC_ERROR_CODE,
957
1165
  GatewayTimeoutError,
958
1166
  GoneError,
1167
+ HeaderValidationError,
959
1168
  HttpError,
960
1169
  HttpVersionNotSupportedError,
961
1170
  ImATeapotError,
@@ -995,11 +1204,12 @@ export {
995
1204
  UnsupportedMediaTypeError,
996
1205
  UpgradeRequiredError,
997
1206
  UriTooLongError,
1207
+ VALIDATION_ERROR_CODE,
998
1208
  ValidationError,
999
1209
  VariantAlsoNegotiatesError,
1000
1210
  badGateway,
1001
1211
  badRequest,
1002
- catchAsync,
1212
+ codeForStatus,
1003
1213
  conflict,
1004
1214
  createError,
1005
1215
  errorHandler,