@setu-ts/exceptions 0.0.0-stage → 0.10.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/AGENT_CARD.md +50 -0
- package/LICENSE +21 -0
- package/README.md +188 -2
- package/dist/errors/exceptions.d.ts +146 -0
- package/dist/errors/exceptions.d.ts.map +1 -0
- package/dist/errors/exceptions.js +189 -0
- package/dist/errors/exceptions.js.map +1 -0
- package/dist/errors/http-error.d.ts +111 -0
- package/dist/errors/http-error.d.ts.map +1 -0
- package/dist/errors/http-error.js +81 -0
- package/dist/errors/http-error.js.map +1 -0
- package/dist/formatters/error-formatter.d.ts +81 -0
- package/dist/formatters/error-formatter.d.ts.map +1 -0
- package/dist/formatters/error-formatter.js +58 -0
- package/dist/formatters/error-formatter.js.map +1 -0
- package/dist/formatters/problem-details.d.ts +118 -0
- package/dist/formatters/problem-details.d.ts.map +1 -0
- package/dist/formatters/problem-details.js +108 -0
- package/dist/formatters/problem-details.js.map +1 -0
- package/dist/formatters/rfc7807-formatter.d.ts +20 -0
- package/dist/formatters/rfc7807-formatter.d.ts.map +1 -0
- package/dist/formatters/rfc7807-formatter.js +27 -0
- package/dist/formatters/rfc7807-formatter.js.map +1 -0
- package/dist/formatters/rfc9457-formatter.d.ts +29 -0
- package/dist/formatters/rfc9457-formatter.d.ts.map +1 -0
- package/dist/formatters/rfc9457-formatter.js +43 -0
- package/dist/formatters/rfc9457-formatter.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/error-handler.d.ts +111 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +221 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/error-responder-impl.d.ts +57 -0
- package/dist/middleware/error-responder-impl.d.ts.map +1 -0
- package/dist/middleware/error-responder-impl.js +158 -0
- package/dist/middleware/error-responder-impl.js.map +1 -0
- package/package.json +53 -3
- package/src/errors/exceptions.ts +209 -0
- package/src/errors/http-error.ts +132 -0
- package/src/formatters/error-formatter.ts +127 -0
- package/src/formatters/problem-details.ts +177 -0
- package/src/formatters/rfc7807-formatter.ts +48 -0
- package/src/formatters/rfc9457-formatter.ts +69 -0
- package/src/index.ts +57 -0
- package/src/middleware/error-handler.ts +357 -0
- package/src/middleware/error-responder-impl.ts +192 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ErrorHandlerFormatter } from './error-formatter.ts';
|
|
2
|
+
export { ERROR_TYPE_BASE } from './problem-details.ts';
|
|
3
|
+
export type { ProblemDetails } from './problem-details.ts';
|
|
4
|
+
/**
|
|
5
|
+
* Format an error as RFC 9457 Problem Details.
|
|
6
|
+
*
|
|
7
|
+
* `type` is `about:blank` for problems whose only semantics are their status
|
|
8
|
+
* code, and the framework validation problem type when the error carries an
|
|
9
|
+
* `errors` extension. Every other member is assembled identically to the
|
|
10
|
+
* deprecated RFC 7807 formatter.
|
|
11
|
+
*
|
|
12
|
+
* @param error - The thrown error to format
|
|
13
|
+
* @param ctx - Optional request context (used for `instance`)
|
|
14
|
+
* @returns An RFC 9457 Problem Details body
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* rfc9457Formatter(notFound('User 42 does not exist'), ctx);
|
|
18
|
+
* // {
|
|
19
|
+
* // type: 'about:blank',
|
|
20
|
+
* // title: 'Not Found',
|
|
21
|
+
* // status: 404,
|
|
22
|
+
* // detail: 'User 42 does not exist',
|
|
23
|
+
* // instance: '/users/42',
|
|
24
|
+
* // }
|
|
25
|
+
* ```
|
|
26
|
+
* @since 0.1.0
|
|
27
|
+
*/
|
|
28
|
+
export declare const rfc9457Formatter: ErrorHandlerFormatter;
|
|
29
|
+
//# sourceMappingURL=rfc9457-formatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rfc9457-formatter.d.ts","sourceRoot":"","sources":["../../src/formatters/rfc9457-formatter.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAQlE,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAmB3D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,gBAAgB,EAAE,qBAGyC,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ABOUT_BLANK, buildProblemDetails, VALIDATION_TYPE, } from "./problem-details.js";
|
|
2
|
+
export { ERROR_TYPE_BASE } from "./problem-details.js";
|
|
3
|
+
/**
|
|
4
|
+
* Resolves the RFC 9457 `type` member.
|
|
5
|
+
*
|
|
6
|
+
* An {@linkcode HttpError} carries no problem-type identity beyond its status
|
|
7
|
+
* code, so a URI minted from that status would identify nothing the `status`
|
|
8
|
+
* member does not already convey — which is exactly the case RFC 9457 §4.2
|
|
9
|
+
* registers `about:blank` for. The one exception is the body produced by
|
|
10
|
+
* `validationError()`, which defines an `errors` extension member and is
|
|
11
|
+
* therefore a distinct problem type deserving its own URI.
|
|
12
|
+
*
|
|
13
|
+
* @param _statusCode - The HTTP status code (not used; the status member carries it)
|
|
14
|
+
* @param hasErrors - Whether the error carries a validation `errors` extension
|
|
15
|
+
* @returns `about:blank`, or the validation problem type URI
|
|
16
|
+
*/
|
|
17
|
+
const resolveRfc9457Type = (_statusCode, hasErrors) => hasErrors ? VALIDATION_TYPE : ABOUT_BLANK;
|
|
18
|
+
/**
|
|
19
|
+
* Format an error as RFC 9457 Problem Details.
|
|
20
|
+
*
|
|
21
|
+
* `type` is `about:blank` for problems whose only semantics are their status
|
|
22
|
+
* code, and the framework validation problem type when the error carries an
|
|
23
|
+
* `errors` extension. Every other member is assembled identically to the
|
|
24
|
+
* deprecated RFC 7807 formatter.
|
|
25
|
+
*
|
|
26
|
+
* @param error - The thrown error to format
|
|
27
|
+
* @param ctx - Optional request context (used for `instance`)
|
|
28
|
+
* @returns An RFC 9457 Problem Details body
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* rfc9457Formatter(notFound('User 42 does not exist'), ctx);
|
|
32
|
+
* // {
|
|
33
|
+
* // type: 'about:blank',
|
|
34
|
+
* // title: 'Not Found',
|
|
35
|
+
* // status: 404,
|
|
36
|
+
* // detail: 'User 42 does not exist',
|
|
37
|
+
* // instance: '/users/42',
|
|
38
|
+
* // }
|
|
39
|
+
* ```
|
|
40
|
+
* @since 0.1.0
|
|
41
|
+
*/
|
|
42
|
+
export const rfc9457Formatter = (error, ctx) => buildProblemDetails(error, ctx, resolveRfc9457Type);
|
|
43
|
+
//# sourceMappingURL=rfc9457-formatter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rfc9457-formatter.js","sourceRoot":"","sources":["../../src/formatters/rfc9457-formatter.ts"],"names":[],"mappings":"AAcA,OAAO,EACL,WAAW,EACX,mBAAmB,EAEnB,eAAe,GAChB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAGvD;;;;;;;;;;;;;GAaG;AACH,MAAM,kBAAkB,GAAG,CAAC,WAAmB,EAAE,SAAkB,EAAU,EAAE,CAC7E,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,WAAW,CAAC;AAE5C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA0B,CACrD,KAAY,EACZ,GAAqB,EACL,EAAE,CAAC,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
*
|
|
4
|
+
* Exception factory functions, `HttpError`, error formatters, and the global
|
|
5
|
+
* error handler middleware.
|
|
6
|
+
*
|
|
7
|
+
* This is a **plain package** (not a plugin) — it depends on
|
|
8
|
+
* `@setu-ts/common` only and exposes types, factories, and a
|
|
9
|
+
* middleware factory. Register the middleware via the application's pipeline:
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { errorHandler } from '@setu-ts/exceptions';
|
|
13
|
+
*
|
|
14
|
+
* app.middleware.add(errorHandler({ format: 'rfc9457' }), {
|
|
15
|
+
* priority: 0,
|
|
16
|
+
* name: 'error-handler',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Every export here is public API and documented in PUBLIC_API.md
|
|
21
|
+
* (AI_GUIDELINES §10).
|
|
22
|
+
*/
|
|
23
|
+
export { HttpError } from './errors/http-error.ts';
|
|
24
|
+
export type { HttpErrorInit, ValidationError } from './errors/http-error.ts';
|
|
25
|
+
export { badRequest, conflict, forbidden, internalServerError, notFound, notImplemented, serviceUnavailable, STATUS_TITLES, statusTitle, tooManyRequests, unauthorized, validationError, } from './errors/exceptions.ts';
|
|
26
|
+
export { defaultFormatter, selectFormatter } from './formatters/error-formatter.ts';
|
|
27
|
+
export type { DefaultErrorBody, ErrorFormat, ErrorHandlerFormatter, } from './formatters/error-formatter.ts';
|
|
28
|
+
export { ERROR_TYPE_BASE, rfc9457Formatter } from './formatters/rfc9457-formatter.ts';
|
|
29
|
+
export type { ProblemDetails } from './formatters/rfc9457-formatter.ts';
|
|
30
|
+
export { rfc7807Formatter } from './formatters/rfc7807-formatter.ts';
|
|
31
|
+
export { errorHandler } from './middleware/error-handler.ts';
|
|
32
|
+
export type { ErrorHandlerOptions } from './middleware/error-handler.ts';
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAG7E,OAAO,EACL,UAAU,EACV,QAAQ,EACR,SAAS,EACT,mBAAmB,EACnB,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,eAAe,EACf,YAAY,EACZ,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AACpF,YAAY,EACV,gBAAgB,EAChB,WAAW,EACX,qBAAqB,GACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AACtF,YAAY,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAGrE,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAC7D,YAAY,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
*
|
|
4
|
+
* Exception factory functions, `HttpError`, error formatters, and the global
|
|
5
|
+
* error handler middleware.
|
|
6
|
+
*
|
|
7
|
+
* This is a **plain package** (not a plugin) — it depends on
|
|
8
|
+
* `@setu-ts/common` only and exposes types, factories, and a
|
|
9
|
+
* middleware factory. Register the middleware via the application's pipeline:
|
|
10
|
+
*
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { errorHandler } from '@setu-ts/exceptions';
|
|
13
|
+
*
|
|
14
|
+
* app.middleware.add(errorHandler({ format: 'rfc9457' }), {
|
|
15
|
+
* priority: 0,
|
|
16
|
+
* name: 'error-handler',
|
|
17
|
+
* });
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Every export here is public API and documented in PUBLIC_API.md
|
|
21
|
+
* (AI_GUIDELINES §10).
|
|
22
|
+
*/
|
|
23
|
+
// Error type
|
|
24
|
+
export { HttpError } from "./errors/http-error.js";
|
|
25
|
+
// Exception factories
|
|
26
|
+
export { badRequest, conflict, forbidden, internalServerError, notFound, notImplemented, serviceUnavailable, STATUS_TITLES, statusTitle, tooManyRequests, unauthorized, validationError, } from "./errors/exceptions.js";
|
|
27
|
+
// Formatters
|
|
28
|
+
export { defaultFormatter, selectFormatter } from "./formatters/error-formatter.js";
|
|
29
|
+
export { ERROR_TYPE_BASE, rfc9457Formatter } from "./formatters/rfc9457-formatter.js";
|
|
30
|
+
export { rfc7807Formatter } from "./formatters/rfc7807-formatter.js";
|
|
31
|
+
// Middleware
|
|
32
|
+
export { errorHandler } from "./middleware/error-handler.js";
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,aAAa;AACb,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAGnD,sBAAsB;AACtB,OAAO,EACL,UAAU,EACV,QAAQ,EACR,SAAS,EACT,mBAAmB,EACnB,QAAQ,EACR,cAAc,EACd,kBAAkB,EAClB,aAAa,EACb,WAAW,EACX,eAAe,EACf,YAAY,EACZ,eAAe,GAChB,MAAM,wBAAwB,CAAC;AAEhC,aAAa;AACb,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAMpF,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAEtF,OAAO,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAErE,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global error-handler middleware factory.
|
|
3
|
+
*
|
|
4
|
+
* Returns a {@linkcode MiddlewareFunction} that wraps `next()` in a try/catch,
|
|
5
|
+
* so any error thrown by downstream middleware or the route handler is caught,
|
|
6
|
+
* logged (when a logger is registered), formatted, and sent as a JSON error
|
|
7
|
+
* response. Register it as the **outermost** middleware (lowest priority number)
|
|
8
|
+
* so it wraps the entire pipeline.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { errorHandler } from '@setu-ts/exceptions';
|
|
13
|
+
*
|
|
14
|
+
* app.middleware.add(errorHandler({
|
|
15
|
+
* format: 'rfc9457',
|
|
16
|
+
* includeStackTrace: config.get('NODE_ENV') === 'development',
|
|
17
|
+
* logErrors: true,
|
|
18
|
+
* }), { priority: 0, name: 'error-handler' });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import type { HandlerResult, IRequestContext, MiddlewareFunction } from '@setu-ts/common';
|
|
24
|
+
import { HttpError } from '../errors/http-error.ts';
|
|
25
|
+
import { type ErrorFormat, type ErrorHandlerFormatter } from '../formatters/error-formatter.ts';
|
|
26
|
+
/**
|
|
27
|
+
* Options for the {@linkcode errorHandler} middleware factory.
|
|
28
|
+
*
|
|
29
|
+
* @since 0.1.0
|
|
30
|
+
*/
|
|
31
|
+
export interface ErrorHandlerOptions {
|
|
32
|
+
/**
|
|
33
|
+
* The error body format: `'default'`, `'rfc9457'`, the deprecated
|
|
34
|
+
* `'rfc7807'`, or a custom formatter function. Defaults to `'default'`.
|
|
35
|
+
*/
|
|
36
|
+
readonly format?: ErrorFormat | ErrorHandlerFormatter;
|
|
37
|
+
/**
|
|
38
|
+
* When `true`, the error `stack` trace is included in the response body.
|
|
39
|
+
* **Never** enable this in production — pass a config-derived boolean (e.g.
|
|
40
|
+
* `config.get('NODE_ENV') === 'development'`), never read `process.env`
|
|
41
|
+
* directly (AI_GUIDELINES §4.1). Defaults to `false`.
|
|
42
|
+
*
|
|
43
|
+
* Note the stack is the _secondary_ disclosure: the primary one is the
|
|
44
|
+
* error **message**, which for a failed query carries the SQL and its bound
|
|
45
|
+
* parameter values. Masking that is `maskInternalErrors`' job.
|
|
46
|
+
*
|
|
47
|
+
* The two compose safely, and masking wins: an error masked by
|
|
48
|
+
* {@linkcode ErrorHandlerOptions.maskInternalErrors} carries **no** stack in
|
|
49
|
+
* the body even when this option is `true`, because a stack begins with the
|
|
50
|
+
* very message that was masked. Set `maskInternalErrors: false` to see the
|
|
51
|
+
* stack of an internal error.
|
|
52
|
+
*/
|
|
53
|
+
readonly includeStackTrace?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* When `true` (the default), a caught value that was **not** an
|
|
56
|
+
* {@linkcode HttpError} and resolves to a status `>= 500` is masked in the
|
|
57
|
+
* response: its `detail`/`message` becomes the status title
|
|
58
|
+
* (`'Internal Server Error'`) and the raw message — which for a failed
|
|
59
|
+
* query carries the SQL and its bound parameter values — is dropped from the
|
|
60
|
+
* body. The log is unaffected: `logErrors` still records the unmasked error
|
|
61
|
+
* and its cause chain, so an operator loses nothing unless `logErrors` is
|
|
62
|
+
* _also_ `false`, the configuration that already logs nothing.
|
|
63
|
+
*
|
|
64
|
+
* A deliberately thrown `HttpError` is never masked — `instanceof HttpError`
|
|
65
|
+
* is the line between "the developer wrote this for a caller" and "this
|
|
66
|
+
* escaped from a driver". `false` restores the previous behaviour verbatim.
|
|
67
|
+
*/
|
|
68
|
+
readonly maskInternalErrors?: boolean;
|
|
69
|
+
/**
|
|
70
|
+
* When `true` (the default), logs once at `warn` below 500 and `error`
|
|
71
|
+
* otherwise. Without a logger, only 5xx reaches runtime.reportError.
|
|
72
|
+
*/
|
|
73
|
+
readonly logErrors?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Lets the application write its own response for a caught error. The hook
|
|
76
|
+
* receives the normalized error after status hints, internal-error masking,
|
|
77
|
+
* and status resolution, so `error.statusCode` is safe to serve. Return a
|
|
78
|
+
* {@linkcode HandlerResult} produced by `ctx.response` to use that response;
|
|
79
|
+
* it may resolve that result asynchronously. Return or resolve to `undefined`
|
|
80
|
+
* to fall through to the configured formatter unchanged.
|
|
81
|
+
*
|
|
82
|
+
* The hook owns its returned response's status, headers, and body. It is
|
|
83
|
+
* called only for errors caught by this middleware, not responder-based
|
|
84
|
+
* terminals such as an unmatched-path `404`.
|
|
85
|
+
*/
|
|
86
|
+
readonly respond?: (error: HttpError, ctx: IRequestContext) => HandlerResult | undefined | Promise<HandlerResult | undefined>;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Creates a global error-handler middleware.
|
|
90
|
+
*
|
|
91
|
+
* Behavior:
|
|
92
|
+
* 1. Calls `next()` inside a try/catch.
|
|
93
|
+
* 2. If `next()` throws an `HttpError`, its `statusCode` is used as-is.
|
|
94
|
+
* 3. If `next()` throws any other `Error`, it is wrapped in a `500`
|
|
95
|
+
* `internalServerError` carrying the original as `cause`.
|
|
96
|
+
* 4. When configured, lets the application write its own response. A returned
|
|
97
|
+
* or resolved `HandlerResult` short-circuits formatting; `undefined` falls
|
|
98
|
+
* through.
|
|
99
|
+
* 5. When `logErrors` is on and a logger is registered, the unmasked error is
|
|
100
|
+
* logged with the status the selected response serves.
|
|
101
|
+
* 6. Otherwise, formats the error via {@linkcode selectFormatter}, optionally
|
|
102
|
+
* enriches it with a `stack` trace, and sends it with the right status and
|
|
103
|
+
* content type. The middleware **returns a `HandlerResult`** (short-circuit)
|
|
104
|
+
* and never re-invokes `next()`.
|
|
105
|
+
*
|
|
106
|
+
* @param options - Error handler configuration
|
|
107
|
+
* @returns A middleware function
|
|
108
|
+
* @since 0.1.0
|
|
109
|
+
*/
|
|
110
|
+
export declare function errorHandler(options?: ErrorHandlerOptions): MiddlewareFunction;
|
|
111
|
+
//# sourceMappingURL=error-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.d.ts","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EACV,aAAa,EAEb,eAAe,EAEf,kBAAkB,EACnB,MAAM,iBAAiB,CAAC;AAUzB,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAOpD,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,qBAAqB,EAE3B,MAAM,kCAAkC,CAAC;AAI1C;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,qBAAqB,CAAC;IACtD;;;;;;;;;;;;;;;OAeG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;IACrC;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;IACtC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CACjB,KAAK,EAAE,SAAS,EAChB,GAAG,EAAE,eAAe,KACjB,aAAa,GAAG,SAAS,GAAG,OAAO,CAAC,aAAa,GAAG,SAAS,CAAC,CAAC;CACrE;AAuBD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,kBAAkB,CAkK9E"}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { brandErrorResponder, CAPABILITIES, ERROR_RESPONDER_STATE_KEY, httpStatusHintOf, resolveResponseStatus, serializeError, } from '@setu-ts/common';
|
|
2
|
+
import { HttpError } from "../errors/http-error.js";
|
|
3
|
+
import { internalServerError, statusTitle } from "../errors/exceptions.js";
|
|
4
|
+
import { buildErrorFromInit, createErrorResponder, thrownErrorStatus, } from "./error-responder-impl.js";
|
|
5
|
+
import { selectFormatter, } from "../formatters/error-formatter.js";
|
|
6
|
+
import { rfc9457Formatter } from "../formatters/rfc9457-formatter.js";
|
|
7
|
+
import { rfc7807Formatter } from "../formatters/rfc7807-formatter.js";
|
|
8
|
+
/** The `application/problem+json` content type for Problem Details responses. */
|
|
9
|
+
const PROBLEM_JSON = 'application/problem+json';
|
|
10
|
+
/** The default JSON content type. */
|
|
11
|
+
const JSON_CONTENT_TYPE = 'application/json; charset=utf-8';
|
|
12
|
+
/**
|
|
13
|
+
* The formatters whose bodies are Problem Details and therefore require the
|
|
14
|
+
* `application/problem+json` media type (RFC 9457 §3).
|
|
15
|
+
*
|
|
16
|
+
* Membership is keyed on the RESOLVED formatter rather than on the format
|
|
17
|
+
* string, because both formatters are exported: `format: rfc9457Formatter`
|
|
18
|
+
* passes a reference and must carry the same media type as `format: 'rfc9457'`.
|
|
19
|
+
* A body served as `application/json` is ignored by generic problem-details
|
|
20
|
+
* clients, so a formatter missing from this set is a silent interoperability
|
|
21
|
+
* defect rather than a visible failure.
|
|
22
|
+
*/
|
|
23
|
+
const PROBLEM_DETAILS_FORMATTERS = new Set([
|
|
24
|
+
rfc9457Formatter,
|
|
25
|
+
rfc7807Formatter,
|
|
26
|
+
]);
|
|
27
|
+
/**
|
|
28
|
+
* Creates a global error-handler middleware.
|
|
29
|
+
*
|
|
30
|
+
* Behavior:
|
|
31
|
+
* 1. Calls `next()` inside a try/catch.
|
|
32
|
+
* 2. If `next()` throws an `HttpError`, its `statusCode` is used as-is.
|
|
33
|
+
* 3. If `next()` throws any other `Error`, it is wrapped in a `500`
|
|
34
|
+
* `internalServerError` carrying the original as `cause`.
|
|
35
|
+
* 4. When configured, lets the application write its own response. A returned
|
|
36
|
+
* or resolved `HandlerResult` short-circuits formatting; `undefined` falls
|
|
37
|
+
* through.
|
|
38
|
+
* 5. When `logErrors` is on and a logger is registered, the unmasked error is
|
|
39
|
+
* logged with the status the selected response serves.
|
|
40
|
+
* 6. Otherwise, formats the error via {@linkcode selectFormatter}, optionally
|
|
41
|
+
* enriches it with a `stack` trace, and sends it with the right status and
|
|
42
|
+
* content type. The middleware **returns a `HandlerResult`** (short-circuit)
|
|
43
|
+
* and never re-invokes `next()`.
|
|
44
|
+
*
|
|
45
|
+
* @param options - Error handler configuration
|
|
46
|
+
* @returns A middleware function
|
|
47
|
+
* @since 0.1.0
|
|
48
|
+
*/
|
|
49
|
+
export function errorHandler(options) {
|
|
50
|
+
const format = options?.format ?? 'default';
|
|
51
|
+
const includeStackTrace = options?.includeStackTrace ?? false;
|
|
52
|
+
const maskInternalErrors = options?.maskInternalErrors ?? true;
|
|
53
|
+
const logErrors = options?.logErrors ?? true;
|
|
54
|
+
const respond = options?.respond;
|
|
55
|
+
const formatter = selectFormatter(format);
|
|
56
|
+
const contentType = PROBLEM_DETAILS_FORMATTERS.has(formatter) ? PROBLEM_JSON : JSON_CONTENT_TYPE;
|
|
57
|
+
// The responder is built ONCE at factory time from the formatter and content
|
|
58
|
+
// type already resolved above, and published per request below: one
|
|
59
|
+
// `Map.set` of a pre-built object, not a re-resolution (AI_GUIDELINES §14).
|
|
60
|
+
const responder = createErrorResponder(formatter, contentType);
|
|
61
|
+
const handleError = async (ctx, next) => {
|
|
62
|
+
// Publish the responder BEFORE `next()` so every site inside the pipeline
|
|
63
|
+
// — the kernel's own terminals and every short-circuiting middleware —
|
|
64
|
+
// answers in this application's configured format (M70f).
|
|
65
|
+
ctx.state.set(ERROR_RESPONDER_STATE_KEY, responder);
|
|
66
|
+
try {
|
|
67
|
+
await next();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
catch (rawError) {
|
|
71
|
+
// Normalize: HttpError passes through, anything else becomes a 500.
|
|
72
|
+
const isHttpError = rawError instanceof HttpError;
|
|
73
|
+
const error = isHttpError
|
|
74
|
+
? rawError
|
|
75
|
+
: internalServerError(rawError instanceof Error ? rawError.message : 'Internal Server Error', rawError instanceof Error ? rawError : undefined);
|
|
76
|
+
// A thrower that cannot import this package states the status its own
|
|
77
|
+
// error means by branding it with an `HttpStatusHint` (M89b) — the seam
|
|
78
|
+
// `@setu-ts/database-plugin` uses for its query-shape refusals, which
|
|
79
|
+
// are caller errors and were answered as masked 500s. The hint carries
|
|
80
|
+
// its own caller-safe `detail`, so the response is built from THAT and
|
|
81
|
+
// never from the error's message, and `buildErrorFromInit` is the same
|
|
82
|
+
// constructor `respondWithError` reaches: one mapping, one body shape.
|
|
83
|
+
//
|
|
84
|
+
// Read only for a non-`HttpError`. A deliberately thrown `HttpError`
|
|
85
|
+
// already states its own status, and letting a brand override it would
|
|
86
|
+
// give one error two answers.
|
|
87
|
+
const hint = isHttpError ? undefined : httpStatusHintOf(rawError);
|
|
88
|
+
// Mask a driver-shaped 500 for the response: the raw message becomes the
|
|
89
|
+
// status title so the body carries neither the statement nor the values.
|
|
90
|
+
// A deliberately thrown HttpError is never masked, and a 4xx is left
|
|
91
|
+
// alone — only a non-HttpError with status >= 500 is rewritten.
|
|
92
|
+
//
|
|
93
|
+
// A hinted error is exempt, and the exemption is narrow by construction
|
|
94
|
+
// rather than by trust: what it serves is the hint's own `detail`, a
|
|
95
|
+
// fixed sentence the brand site wrote, never the `Error`'s message. So
|
|
96
|
+
// there is no driver diagnostic in the body for masking to remove. It is
|
|
97
|
+
// checked FIRST because a hinted 501 satisfies all three masking clauses
|
|
98
|
+
// and would otherwise be rewritten into the exact symptom this closes.
|
|
99
|
+
let responseError = error;
|
|
100
|
+
if (hint !== undefined) {
|
|
101
|
+
responseError = buildErrorFromInit(hint);
|
|
102
|
+
}
|
|
103
|
+
else if (maskInternalErrors && !isHttpError && error.statusCode >= 500) {
|
|
104
|
+
// Preserve the log's cause chain without leaking the message. `cause`
|
|
105
|
+
// is `unknown` on `Error`; only an `Error` value is a valid
|
|
106
|
+
// `HttpError` cause, so narrow rather than cast.
|
|
107
|
+
const cause = error.cause instanceof Error ? error.cause : undefined;
|
|
108
|
+
responseError = new HttpError(error.statusCode, statusTitle(error.statusCode), undefined, cause);
|
|
109
|
+
}
|
|
110
|
+
// An `HttpError` carries whatever status its thrower passed: the
|
|
111
|
+
// constructor validates nothing, and its own JSDoc says the factory
|
|
112
|
+
// functions are what "guarantee a correct status code". So
|
|
113
|
+
// `throw new HttpError(4004, ...)` — a typo for `404` — reached
|
|
114
|
+
// `response.status()` and threw `RangeError` out of the one `catch` that
|
|
115
|
+
// exists to contain throws. The status is clamped BEFORE the log and the
|
|
116
|
+
// body are built, so the logged status, the body's own status member and
|
|
117
|
+
// the written status are one number. Rebuilding rather than clamping at
|
|
118
|
+
// the write also makes `responseError === error` false, which suppresses
|
|
119
|
+
// the stack — correct by that check's own stated invariant, since the
|
|
120
|
+
// error being SERVED is no longer the error that was LOGGED.
|
|
121
|
+
const servedStatus = resolveResponseStatus(thrownErrorStatus(rawError).requested, ctx);
|
|
122
|
+
if (servedStatus !== responseError.statusCode) {
|
|
123
|
+
responseError = new HttpError(servedStatus, responseError.message, responseError.details, responseError.cause instanceof Error ? responseError.cause : undefined);
|
|
124
|
+
}
|
|
125
|
+
// The hook sees the same safe error and served status that the formatter
|
|
126
|
+
// would receive. It can perform asynchronous view rendering; only
|
|
127
|
+
// `undefined` means "use the framework formatter". A hook failure stays
|
|
128
|
+
// an application failure, but records the original caught diagnostic
|
|
129
|
+
// before it propagates to the surrounding pipeline.
|
|
130
|
+
let result;
|
|
131
|
+
try {
|
|
132
|
+
result = await respond?.(responseError, ctx);
|
|
133
|
+
}
|
|
134
|
+
catch (hookError) {
|
|
135
|
+
if (logErrors) {
|
|
136
|
+
logError(ctx, error, responseError.statusCode);
|
|
137
|
+
}
|
|
138
|
+
throw hookError;
|
|
139
|
+
}
|
|
140
|
+
if (result !== undefined) {
|
|
141
|
+
// The application-owned result can choose a status different from the
|
|
142
|
+
// normalized error (for example a 418 HTML page for a caught 500), so
|
|
143
|
+
// inspect the completed writer rather than reporting the fallback.
|
|
144
|
+
if (logErrors) {
|
|
145
|
+
logError(ctx, error, ctx.response.snapshot().status);
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
// Log the unmasked diagnostic and cause chain, with the status the
|
|
150
|
+
// formatter below will actually serve. A hint may change a normalized
|
|
151
|
+
// `500` to `501`, so logging the pre-hint status would mislead operators.
|
|
152
|
+
if (logErrors) {
|
|
153
|
+
logError(ctx, error, responseError.statusCode);
|
|
154
|
+
}
|
|
155
|
+
const body = formatter(responseError, ctx);
|
|
156
|
+
// Masking wins over `includeStackTrace`. A stack's first line is
|
|
157
|
+
// `<name>: <message>`, so attaching the unmasked error's stack would put
|
|
158
|
+
// the SQL and its bound parameter values straight back into the body that
|
|
159
|
+
// was just masked — defeating the mask through the one option documented
|
|
160
|
+
// as unsafe in production. A masked error therefore carries no stack at
|
|
161
|
+
// all; the unmasked one is in the log, where `logErrors` already sent it.
|
|
162
|
+
//
|
|
163
|
+
// The condition is `responseError === error` rather than `!masked`
|
|
164
|
+
// because a hinted response is the second case with the same property:
|
|
165
|
+
// its body deliberately carries the brand site's sentence and not the
|
|
166
|
+
// error's own text, and a stack's first line is `<name>: <message>`. So
|
|
167
|
+
// the stack is attached exactly when the error being SERVED is the error
|
|
168
|
+
// that was LOGGED, which is the invariant both exclusions want.
|
|
169
|
+
if (includeStackTrace && responseError === error && error.stack !== undefined) {
|
|
170
|
+
body.stack = error.stack;
|
|
171
|
+
}
|
|
172
|
+
// Serialize and send via `.send(bytes)` rather than `.json(body)` so
|
|
173
|
+
// the content-type header we set is not overwritten by json()'s own
|
|
174
|
+
// `application/json` default — RFC 9457 requires `application/problem+json`.
|
|
175
|
+
const bytes = new TextEncoder().encode(JSON.stringify(body));
|
|
176
|
+
return ctx.response
|
|
177
|
+
.status(responseError.statusCode)
|
|
178
|
+
.header('content-type', contentType)
|
|
179
|
+
.send(bytes);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
// Brand the middleware with the SAME responder it publishes into `ctx.state`
|
|
183
|
+
// below, so the kernel can seed it into the state of the pre-pipeline sites
|
|
184
|
+
// — the drain `503`, the malformed-request `400`, and the request-lifecycle
|
|
185
|
+
// hooks — which run before this middleware and would otherwise always take
|
|
186
|
+
// the fallback shape (M70f re-review, findings 1 & 2).
|
|
187
|
+
brandErrorResponder(handleError, responder);
|
|
188
|
+
return handleError;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Logs once through the registered logger, or the runtime sink for 5xx when
|
|
192
|
+
* no logger is registered. Handled 4xx without a logger stays silent.
|
|
193
|
+
*
|
|
194
|
+
* @param ctx - The request context
|
|
195
|
+
* @param error - The error to log, carrying the unmasked message and cause
|
|
196
|
+
* @param statusCode - The status actually served, which a hint may have changed
|
|
197
|
+
*/
|
|
198
|
+
function logError(ctx, error, statusCode) {
|
|
199
|
+
if (!ctx.services.has(CAPABILITIES.LOGGER)) {
|
|
200
|
+
if (statusCode >= 500 && ctx.services.has(CAPABILITIES.RUNTIME)) {
|
|
201
|
+
ctx.services.get(CAPABILITIES.RUNTIME).reportError?.({
|
|
202
|
+
level: 'error',
|
|
203
|
+
...serializeError(error),
|
|
204
|
+
statusCode,
|
|
205
|
+
requestId: ctx.id,
|
|
206
|
+
method: ctx.request.method,
|
|
207
|
+
path: ctx.request.path,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
const logger = ctx.services.get(CAPABILITIES.LOGGER);
|
|
213
|
+
// Serialize the cause (X2-5): a raw `Error` in log metadata renders as `{}`
|
|
214
|
+
// under `JSON.stringify` because `message`/`stack` are non-enumerable.
|
|
215
|
+
logger[statusCode < 500 ? 'warn' : 'error'](error.message, {
|
|
216
|
+
statusCode,
|
|
217
|
+
requestId: ctx.id,
|
|
218
|
+
...(error.cause !== undefined && { cause: serializeError(error.cause) }),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=error-handler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-handler.js","sourceRoot":"","sources":["../../src/middleware/error-handler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `exceptions` implementation of the request-scoped error responder seam
|
|
3
|
+
* (M70f).
|
|
4
|
+
*
|
|
5
|
+
* This module is **internal**: it is not exported from `src/index.ts`. The
|
|
6
|
+
* interface it satisfies (`IErrorResponder`), the state key, the init type, and
|
|
7
|
+
* the `respondWithError` free function all live in `@setu-ts/common`; exporting
|
|
8
|
+
* the implementation as well would give one concept two public names. A
|
|
9
|
+
* `barrel-exports.test.ts` assertion pins that it stays out of the barrel.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
12
|
+
*/
|
|
13
|
+
import type { ErrorResponseInit, IErrorResponder } from '@setu-ts/common';
|
|
14
|
+
import { HttpError } from '../errors/http-error.ts';
|
|
15
|
+
import type { ErrorHandlerFormatter } from '../formatters/error-formatter.ts';
|
|
16
|
+
/** The pure thrown-value rule, shared by serving and request-log prediction. */
|
|
17
|
+
export declare function thrownErrorStatus(error: unknown): {
|
|
18
|
+
requested: number;
|
|
19
|
+
status: number;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Builds the {@linkcode HttpError} that carries one error response's status,
|
|
23
|
+
* title and disclosure.
|
|
24
|
+
*
|
|
25
|
+
* This is the single owner of that mapping, and it has **two** callers: the
|
|
26
|
+
* responder below, and `errorHandler`'s status-hint path (M89b), which answers
|
|
27
|
+
* a thrown error branded with an `HttpStatusHint` — a type that IS an
|
|
28
|
+
* `ErrorResponseInit`. Sharing it is what makes the two agree: a hinted throw
|
|
29
|
+
* and a `respondWithError` call carrying the same values produce byte-identical
|
|
30
|
+
* bodies under `default`, `rfc9457`, `rfc7807` and any custom formatter. A
|
|
31
|
+
* second hand-rolled construction would silently diverge — the `default`
|
|
32
|
+
* formatter reads `details.detail` while the Problem Details formatters read
|
|
33
|
+
* the {@linkcode RESPONDER_DETAIL} symbol, so getting either half wrong loses
|
|
34
|
+
* the disclosure in exactly one format.
|
|
35
|
+
*
|
|
36
|
+
* @param init - The status, title, disclosure and optional structured details
|
|
37
|
+
* @returns An `HttpError` every formatter renders with the disclosure intact
|
|
38
|
+
*/
|
|
39
|
+
export declare function buildErrorFromInit(init: ErrorResponseInit): HttpError;
|
|
40
|
+
/**
|
|
41
|
+
* Builds an {@linkcode IErrorResponder} from the formatter and content type
|
|
42
|
+
* that `errorHandler` has **already** resolved at factory time.
|
|
43
|
+
*
|
|
44
|
+
* The responder keeps the `HttpError` construction inside the only package that
|
|
45
|
+
* owns it: it builds an `HttpError` from the init (so `buildProblemDetails` sees
|
|
46
|
+
* a real `statusCode` and the validation `errors` extension, and
|
|
47
|
+
* `maskInternalErrors` would never mask a deliberately 4xx), runs the resolved
|
|
48
|
+
* formatter over it, and writes status, `content-type` and the serialized body
|
|
49
|
+
* — the same three-step tail `errorHandler`'s catch path performs.
|
|
50
|
+
*
|
|
51
|
+
* @param formatter - The resolved error formatter
|
|
52
|
+
* @param contentType - The resolved response content type
|
|
53
|
+
* @returns A responder publishing the application's error format
|
|
54
|
+
* @since 0.1.0
|
|
55
|
+
*/
|
|
56
|
+
export declare function createErrorResponder(formatter: ErrorHandlerFormatter, contentType: string): IErrorResponder;
|
|
57
|
+
//# sourceMappingURL=error-responder-impl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-responder-impl.d.ts","sourceRoot":"","sources":["../../src/middleware/error-responder-impl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,KAAK,EAEV,iBAAiB,EACjB,eAAe,EAEhB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AAW9E,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAcvF;AA4BD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,CAiCrE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,qBAAqB,EAChC,WAAW,EAAE,MAAM,GAClB,eAAe,CA2CjB"}
|