@setu-ts/exceptions 0.0.0-bootstrap.0 → 0.9.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.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +179 -2
  3. package/dist/errors/exceptions.d.ts +146 -0
  4. package/dist/errors/exceptions.d.ts.map +1 -0
  5. package/dist/errors/exceptions.js +189 -0
  6. package/dist/errors/exceptions.js.map +1 -0
  7. package/dist/errors/http-error.d.ts +111 -0
  8. package/dist/errors/http-error.d.ts.map +1 -0
  9. package/dist/errors/http-error.js +81 -0
  10. package/dist/errors/http-error.js.map +1 -0
  11. package/dist/formatters/error-formatter.d.ts +81 -0
  12. package/dist/formatters/error-formatter.d.ts.map +1 -0
  13. package/dist/formatters/error-formatter.js +58 -0
  14. package/dist/formatters/error-formatter.js.map +1 -0
  15. package/dist/formatters/problem-details.d.ts +118 -0
  16. package/dist/formatters/problem-details.d.ts.map +1 -0
  17. package/dist/formatters/problem-details.js +108 -0
  18. package/dist/formatters/problem-details.js.map +1 -0
  19. package/dist/formatters/rfc7807-formatter.d.ts +20 -0
  20. package/dist/formatters/rfc7807-formatter.d.ts.map +1 -0
  21. package/dist/formatters/rfc7807-formatter.js +27 -0
  22. package/dist/formatters/rfc7807-formatter.js.map +1 -0
  23. package/dist/formatters/rfc9457-formatter.d.ts +29 -0
  24. package/dist/formatters/rfc9457-formatter.d.ts.map +1 -0
  25. package/dist/formatters/rfc9457-formatter.js +43 -0
  26. package/dist/formatters/rfc9457-formatter.js.map +1 -0
  27. package/dist/index.d.ts +33 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +33 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/middleware/error-handler.d.ts +112 -0
  32. package/dist/middleware/error-handler.d.ts.map +1 -0
  33. package/dist/middleware/error-handler.js +210 -0
  34. package/dist/middleware/error-handler.js.map +1 -0
  35. package/dist/middleware/error-responder-impl.d.ts +52 -0
  36. package/dist/middleware/error-responder-impl.d.ts.map +1 -0
  37. package/dist/middleware/error-responder-impl.js +148 -0
  38. package/dist/middleware/error-responder-impl.js.map +1 -0
  39. package/package.json +44 -3
  40. package/src/errors/exceptions.ts +212 -0
  41. package/src/errors/http-error.ts +132 -0
  42. package/src/formatters/error-formatter.ts +127 -0
  43. package/src/formatters/problem-details.ts +176 -0
  44. package/src/formatters/rfc7807-formatter.ts +48 -0
  45. package/src/formatters/rfc9457-formatter.ts +69 -0
  46. package/src/index.ts +57 -0
  47. package/src/middleware/error-handler.ts +337 -0
  48. package/src/middleware/error-responder-impl.ts +178 -0
@@ -0,0 +1,81 @@
1
+ /**
2
+ * `HttpError` — the framework's single concrete HTTP error type.
3
+ *
4
+ * Rather than a deep class hierarchy (`BadRequestError extends HttpError`,
5
+ * `NotFoundError extends HttpError`, …), the framework uses **composition via
6
+ * factory functions** (AI_GUIDELINES §1.4, ARCHITECTURE.md §13). Every HTTP
7
+ * error is an instance of this one class with a `statusCode` set at
8
+ * construction time by a factory. This keeps the hierarchy flat, makes errors
9
+ * trivially serializable, and allows runtime composition of properties.
10
+ *
11
+ * `cause` is forwarded to the ES2022 `Error` cause chain — pass the original
12
+ * error so callers can inspect the root cause without losing the stack.
13
+ *
14
+ * @module
15
+ */
16
+ /**
17
+ * The framework's HTTP error type.
18
+ *
19
+ * Throw an instance directly, or — preferably — construct one via a factory
20
+ * function from {@linkcode ./exceptions.ts} so the status code is correct by
21
+ * construction:
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { notFound } from '@setu-ts/exceptions';
26
+ *
27
+ * throw notFound(`User ${id} not found`);
28
+ * ```
29
+ *
30
+ * The error handler middleware inspects the `statusCode` to set the response
31
+ * status, and serializes `message` and `details` into the error body.
32
+ *
33
+ * @since 0.1.0
34
+ */
35
+ export class HttpError extends Error {
36
+ /** The HTTP status code this error maps to. */
37
+ statusCode;
38
+ /**
39
+ * Creates a new `HttpError`.
40
+ *
41
+ * Prefer the factory functions in `exceptions.ts` over calling this
42
+ * constructor directly — they guarantee a correct status code.
43
+ *
44
+ * @param statusCode - HTTP status code (e.g. `404`)
45
+ * @param message - Human-readable error message
46
+ * @param details - Optional structured details; omitted when absent
47
+ * @param cause - Optional underlying error; forwarded to the ES2022 cause chain
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * const err = new HttpError(404, 'User not found');
52
+ * console.log(err.statusCode); // 404
53
+ * ```
54
+ * @since 0.1.0
55
+ */
56
+ constructor(statusCode, message, details, cause) {
57
+ // Forward `cause` to the ES2022 Error cause chain when present. Under
58
+ // `exactOptionalPropertyTypes`, passing `{ cause: undefined }` would
59
+ // violate the option, so the options object is built conditionally.
60
+ super(message, cause !== undefined ? { cause } : undefined);
61
+ this.name = 'HttpError';
62
+ this.statusCode = statusCode;
63
+ if (details !== undefined) {
64
+ this.details = details;
65
+ }
66
+ }
67
+ /**
68
+ * Creates an `HttpError` from an {@linkcode HttpErrorInit} object.
69
+ *
70
+ * Convenience overload used by factory functions so they can branch on
71
+ * optional `details`/`cause` once.
72
+ *
73
+ * @param init - The error options
74
+ * @returns A new `HttpError`
75
+ * @since 0.1.0
76
+ */
77
+ static from(init) {
78
+ return new HttpError(init.statusCode, init.message, init.details, init.cause);
79
+ }
80
+ }
81
+ //# sourceMappingURL=http-error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-error.js","sourceRoot":"","sources":["../../src/errors/http-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAqCH;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClC,+CAA+C;IACtC,UAAU,CAAS;IAW5B;;;;;;;;;;;;;;;;;OAiBG;IACH,YACE,UAAkB,EAClB,OAAe,EACf,OAA2C,EAC3C,KAAa;QAEb,sEAAsE;QACtE,qEAAqE;QACrE,oEAAoE;QACpE,KAAK,CAAC,OAAO,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACzB,CAAC;IACH,CAAC;IAED;;;;;;;;;OASG;IACH,MAAM,CAAC,IAAI,CAAC,IAAmB;QAC7B,OAAO,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAChF,CAAC;CACF"}
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Error format selector — resolves the configured error format to a concrete
3
+ * formatter function.
4
+ *
5
+ * Supported formats:
6
+ * - `'default'` — Framework-standard error shape (`statusCode`, `message`,
7
+ * optional `details`).
8
+ * - `'rfc9457'` — RFC 9457 Problem Details (see `rfc9457-formatter.ts`).
9
+ * - `'rfc7807'` — deprecated; RFC 7807 Problem Details (see
10
+ * `rfc7807-formatter.ts`). RFC 7807 was obsoleted by RFC 9457.
11
+ * - A custom function — used as-is.
12
+ *
13
+ * @module
14
+ */
15
+ import type { IRequestContext } from '@setu-ts/common';
16
+ /**
17
+ * A function that formats a thrown error into a serializable error body.
18
+ *
19
+ * @param error - The thrown error (an `HttpError` or a generic `Error`)
20
+ * @param ctx - Optional request context (used for `instance`, logging, etc.)
21
+ * @returns The formatted error body
22
+ * @since 0.1.0
23
+ */
24
+ export type ErrorHandlerFormatter = (error: Error, ctx?: IRequestContext) => Record<string, unknown>;
25
+ /**
26
+ * The built-in error format identifiers for `@setu-ts/exceptions`.
27
+ *
28
+ * `'rfc7807'` is retained for backward compatibility and resolves to the
29
+ * deprecated {@linkcode rfc7807Formatter}; prefer `'rfc9457'`.
30
+ *
31
+ * @since 0.1.0
32
+ */
33
+ export type ErrorFormat = 'default' | 'rfc9457' | 'rfc7807';
34
+ /**
35
+ * The framework-standard error body shape.
36
+ *
37
+ * @since 0.1.0
38
+ */
39
+ export interface DefaultErrorBody {
40
+ /** The HTTP status code. */
41
+ readonly statusCode: number;
42
+ /** Human-readable error message. */
43
+ readonly message: string;
44
+ /** Optional structured details (present when the error carries any). */
45
+ readonly details?: Readonly<Record<string, unknown>>;
46
+ /** Optional stack trace (present only when `includeStackTrace` is on). */
47
+ readonly stack?: string;
48
+ /** Allow callers to attach further members. */
49
+ readonly [key: string]: unknown;
50
+ }
51
+ /**
52
+ * Framework-standard error formatter.
53
+ *
54
+ * Returns a body shaped as:
55
+ *
56
+ * ```json
57
+ * { "statusCode": 404, "message": "User not found", "details": { ... } }
58
+ * ```
59
+ *
60
+ * Generic (non-`HttpError`) errors map to `500`.
61
+ *
62
+ * @param error - The thrown error to format
63
+ * @returns The default formatted error body
64
+ * @since 0.1.0
65
+ */
66
+ export declare const defaultFormatter: ErrorHandlerFormatter;
67
+ /**
68
+ * Resolve the error format configuration to a concrete formatter function.
69
+ *
70
+ * - When `format` is `'default'`, `'rfc9457'`, or the deprecated `'rfc7807'`,
71
+ * the corresponding built-in formatter is returned.
72
+ * - When `format` is already a function, it is returned as-is.
73
+ * - Otherwise a `TypeError` is thrown.
74
+ *
75
+ * @param format - The error format identifier or custom formatter function
76
+ * @returns The resolved formatter function
77
+ * @throws {TypeError} When an unknown format string is provided
78
+ * @since 0.1.0
79
+ */
80
+ export declare function selectFormatter(format?: ErrorFormat | ErrorHandlerFormatter): ErrorHandlerFormatter;
81
+ //# sourceMappingURL=error-formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-formatter.d.ts","sourceRoot":"","sources":["../../src/formatters/error-formatter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAUvD;;;;;;;GAOG;AACH,MAAM,MAAM,qBAAqB,GAAG,CAClC,KAAK,EAAE,KAAK,EACZ,GAAG,CAAC,EAAE,eAAe,KAClB,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7B;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;AAM5D;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,4BAA4B;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,+CAA+C;IAC/C,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,gBAAgB,EAAE,qBAO9B,CAAC;AAMF;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAC7B,MAAM,GAAE,WAAW,GAAG,qBAAiC,GACtD,qBAAqB,CAcvB"}
@@ -0,0 +1,58 @@
1
+ import { rfc9457Formatter } from "./rfc9457-formatter.js";
2
+ import { rfc7807Formatter } from "./rfc7807-formatter.js";
3
+ import { HttpError } from "../errors/http-error.js";
4
+ /**
5
+ * Framework-standard error formatter.
6
+ *
7
+ * Returns a body shaped as:
8
+ *
9
+ * ```json
10
+ * { "statusCode": 404, "message": "User not found", "details": { ... } }
11
+ * ```
12
+ *
13
+ * Generic (non-`HttpError`) errors map to `500`.
14
+ *
15
+ * @param error - The thrown error to format
16
+ * @returns The default formatted error body
17
+ * @since 0.1.0
18
+ */
19
+ export const defaultFormatter = (error) => {
20
+ const isHttp = error instanceof HttpError;
21
+ return {
22
+ statusCode: isHttp ? error.statusCode : 500,
23
+ message: error.message,
24
+ ...(isHttp && error.details !== undefined && { details: error.details }),
25
+ };
26
+ };
27
+ // ---------------------------------------------------------------------------
28
+ // Selector
29
+ // ---------------------------------------------------------------------------
30
+ /**
31
+ * Resolve the error format configuration to a concrete formatter function.
32
+ *
33
+ * - When `format` is `'default'`, `'rfc9457'`, or the deprecated `'rfc7807'`,
34
+ * the corresponding built-in formatter is returned.
35
+ * - When `format` is already a function, it is returned as-is.
36
+ * - Otherwise a `TypeError` is thrown.
37
+ *
38
+ * @param format - The error format identifier or custom formatter function
39
+ * @returns The resolved formatter function
40
+ * @throws {TypeError} When an unknown format string is provided
41
+ * @since 0.1.0
42
+ */
43
+ export function selectFormatter(format = 'default') {
44
+ if (typeof format === 'function') {
45
+ return format;
46
+ }
47
+ switch (format) {
48
+ case 'default':
49
+ return defaultFormatter;
50
+ case 'rfc9457':
51
+ return rfc9457Formatter;
52
+ case 'rfc7807':
53
+ return rfc7807Formatter;
54
+ default:
55
+ throw new TypeError(`Unknown error format: "${format}"`);
56
+ }
57
+ }
58
+ //# sourceMappingURL=error-formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-formatter.js","sourceRoot":"","sources":["../../src/formatters/error-formatter.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAmDpD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA0B,CAAC,KAAY,EAAoB,EAAE;IACxF,MAAM,MAAM,GAAG,KAAK,YAAY,SAAS,CAAC;IAC1C,OAAO;QACL,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG;QAC3C,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;KACzE,CAAC;AACJ,CAAC,CAAC;AAEF,8EAA8E;AAC9E,WAAW;AACX,8EAA8E;AAE9E;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,eAAe,CAC7B,SAA8C,SAAS;IAEvD,IAAI,OAAO,MAAM,KAAK,UAAU,EAAE,CAAC;QACjC,OAAO,MAAM,CAAC;IAChB,CAAC;IACD,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,SAAS;YACZ,OAAO,gBAAgB,CAAC;QAC1B,KAAK,SAAS;YACZ,OAAO,gBAAgB,CAAC;QAC1B,KAAK,SAAS;YACZ,OAAO,gBAAgB,CAAC;QAC1B;YACE,MAAM,IAAI,SAAS,CAAC,0BAA0B,MAAM,GAAG,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Shared Problem Details assembly.
3
+ *
4
+ * Both the RFC 9457 formatter and the deprecated RFC 7807 one produce the same
5
+ * body apart from the `type` member, so the assembly lives here once and each
6
+ * formatter supplies only its own {@linkcode ProblemTypeResolver}
7
+ * (AI_GUIDELINES §11.1).
8
+ *
9
+ * This module is internal: `buildProblemDetails` and `ProblemTypeResolver` are
10
+ * not exported from `src/index.ts`. `ERROR_TYPE_BASE` and `ProblemDetails` are
11
+ * re-exported publicly by `rfc9457-formatter.ts`.
12
+ *
13
+ * @module
14
+ */
15
+ import type { IRequestContext } from '@setu-ts/common';
16
+ /**
17
+ * The canonical base URI for framework-produced problem type identifiers.
18
+ *
19
+ * @since 0.1.0
20
+ */
21
+ export declare const ERROR_TYPE_BASE = "https://setu-ts.dev/errors";
22
+ /**
23
+ * The problem type URI identifying a validation failure.
24
+ *
25
+ * Spelled to match the literal `@setu-ts/validation-plugin` emits, so both
26
+ * packages identify the same problem type with the same URI. The two cannot
27
+ * share a constant — a package may not import another package's internals
28
+ * (AI_GUIDELINES §2.2) — so the agreement is pinned by a test in each.
29
+ *
30
+ * @since 0.1.0
31
+ */
32
+ export declare const VALIDATION_TYPE: string;
33
+ /**
34
+ * The RFC 9457 default problem type, meaning "no semantics beyond the HTTP
35
+ * status code" (RFC 9457 §4.2).
36
+ *
37
+ * @since 0.1.0
38
+ */
39
+ export declare const ABOUT_BLANK = "about:blank";
40
+ /**
41
+ * A Problem Details object as defined by
42
+ * [RFC 9457](https://www.rfc-editor.org/rfc/rfc9457.html).
43
+ *
44
+ * Extension members beyond the five core fields are allowed (RFC 9457 §3.2),
45
+ * so `errors` and `stack` may be present.
46
+ *
47
+ * @since 0.1.0
48
+ */
49
+ export interface ProblemDetails {
50
+ /** A URI reference identifying the problem type. */
51
+ readonly type: string;
52
+ /** A short, human-readable summary of the problem type. */
53
+ readonly title: string;
54
+ /** The HTTP status code generated for this occurrence. */
55
+ readonly status: number;
56
+ /** A human-readable explanation specific to this occurrence. */
57
+ readonly detail: string;
58
+ /** A URI reference identifying the specific occurrence (request path). */
59
+ readonly instance?: string;
60
+ /** Optional validation failures extension (present for `422` errors). */
61
+ readonly errors?: ReadonlyArray<{
62
+ field: string;
63
+ message: string;
64
+ code?: string;
65
+ }>;
66
+ /** Optional stack trace (present only when `includeStackTrace` is on). */
67
+ readonly stack?: string;
68
+ /** Allow callers to attach further extension members. */
69
+ readonly [key: string]: unknown;
70
+ }
71
+ /**
72
+ * Marks an {@linkcode HttpError} as one the M70f error responder built, and
73
+ * carries the calling site's disclosure.
74
+ *
75
+ * The responder must hand the disclosure to the Problem Details formatters as
76
+ * the `detail` member while leaving the site's title as the error `message`
77
+ * (which the `default` formatter emits verbatim). It cannot use
78
+ * `HttpError.details` alone as that channel: `details` is a free-form bag an
79
+ * application writes into too, so promoting any `details.detail` would change
80
+ * the body of a thrown error that merely uses the same key name.
81
+ *
82
+ * This marker is a module-private `Symbol` — not `Symbol.for` — because both
83
+ * writer ({@linkcode ../middleware/error-responder-impl.ts}) and reader
84
+ * ({@linkcode buildProblemDetails}) live in this package and import it
85
+ * directly; a global registry key would expose an internal channel that a
86
+ * third party could forge.
87
+ *
88
+ * @internal
89
+ */
90
+ export declare const RESPONDER_DETAIL: unique symbol;
91
+ /**
92
+ * Resolves the `type` member of a Problem Details body.
93
+ *
94
+ * @param statusCode - The HTTP status code of the occurrence
95
+ * @param hasErrors - Whether the error carries a validation `errors` extension
96
+ * @returns The problem type URI
97
+ * @since 0.1.0
98
+ */
99
+ export type ProblemTypeResolver = (statusCode: number, hasErrors: boolean) => string;
100
+ /**
101
+ * Assembles a Problem Details body from a thrown error.
102
+ *
103
+ * - `status` and `detail` come from the {@linkcode HttpError} (or default to
104
+ * `500` / the error message for generic `Error`s).
105
+ * - `title` is derived from the status code via the shared status-title map.
106
+ * - `instance` is the request path when a context is supplied.
107
+ * - `errors` is included when the error carries validation details.
108
+ * - `type` is delegated to `resolveType`, the only member the two supported
109
+ * specification versions disagree on.
110
+ *
111
+ * @param error - The thrown error to format
112
+ * @param ctx - Optional request context (used for `instance`)
113
+ * @param resolveType - Strategy supplying the `type` member
114
+ * @returns A Problem Details body
115
+ * @since 0.1.0
116
+ */
117
+ export declare function buildProblemDetails(error: Error, ctx: IRequestContext | undefined, resolveType: ProblemTypeResolver): ProblemDetails;
118
+ //# sourceMappingURL=problem-details.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"problem-details.d.ts","sourceRoot":"","sources":["../../src/formatters/problem-details.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAKvD;;;;GAIG;AACH,eAAO,MAAM,eAAe,+BAA+B,CAAC;AAE5D;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,EAAE,MAAwC,CAAC;AAEvE;;;;;GAKG;AACH,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC;;;;;;;;GAQG;AACH,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;QAC9B,KAAK,EAAE,MAAM,CAAC;QACd,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAAmD,CAAC;AAE1F;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,UAAU,EAAE,MAAM,EAAE,SAAS,EAAE,OAAO,KAAK,MAAM,CAAC;AAgBrF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,eAAe,GAAG,SAAS,EAChC,WAAW,EAAE,mBAAmB,GAC/B,cAAc,CA+BhB"}
@@ -0,0 +1,108 @@
1
+ import { statusTitle } from "../errors/exceptions.js";
2
+ import { HttpError } from "../errors/http-error.js";
3
+ /**
4
+ * The canonical base URI for framework-produced problem type identifiers.
5
+ *
6
+ * @since 0.1.0
7
+ */
8
+ export const ERROR_TYPE_BASE = 'https://setu-ts.dev/errors';
9
+ /**
10
+ * The problem type URI identifying a validation failure.
11
+ *
12
+ * Spelled to match the literal `@setu-ts/validation-plugin` emits, so both
13
+ * packages identify the same problem type with the same URI. The two cannot
14
+ * share a constant — a package may not import another package's internals
15
+ * (AI_GUIDELINES §2.2) — so the agreement is pinned by a test in each.
16
+ *
17
+ * @since 0.1.0
18
+ */
19
+ export const VALIDATION_TYPE = `${ERROR_TYPE_BASE}/validation`;
20
+ /**
21
+ * The RFC 9457 default problem type, meaning "no semantics beyond the HTTP
22
+ * status code" (RFC 9457 §4.2).
23
+ *
24
+ * @since 0.1.0
25
+ */
26
+ export const ABOUT_BLANK = 'about:blank';
27
+ /**
28
+ * Marks an {@linkcode HttpError} as one the M70f error responder built, and
29
+ * carries the calling site's disclosure.
30
+ *
31
+ * The responder must hand the disclosure to the Problem Details formatters as
32
+ * the `detail` member while leaving the site's title as the error `message`
33
+ * (which the `default` formatter emits verbatim). It cannot use
34
+ * `HttpError.details` alone as that channel: `details` is a free-form bag an
35
+ * application writes into too, so promoting any `details.detail` would change
36
+ * the body of a thrown error that merely uses the same key name.
37
+ *
38
+ * This marker is a module-private `Symbol` — not `Symbol.for` — because both
39
+ * writer ({@linkcode ../middleware/error-responder-impl.ts}) and reader
40
+ * ({@linkcode buildProblemDetails}) live in this package and import it
41
+ * directly; a global registry key would expose an internal channel that a
42
+ * third party could forge.
43
+ *
44
+ * @internal
45
+ */
46
+ export const RESPONDER_DETAIL = Symbol('setu.exceptions.responder-detail');
47
+ /**
48
+ * Extracts the validation `errors` extension from an error's `details`, if any.
49
+ *
50
+ * @param error - The HTTP error to read
51
+ * @returns The validation failures, or `undefined` when the error carries none
52
+ */
53
+ function extractErrors(error) {
54
+ const details = error.details;
55
+ if (details === undefined || !('errors' in details)) {
56
+ return undefined;
57
+ }
58
+ return details.errors;
59
+ }
60
+ /**
61
+ * Assembles a Problem Details body from a thrown error.
62
+ *
63
+ * - `status` and `detail` come from the {@linkcode HttpError} (or default to
64
+ * `500` / the error message for generic `Error`s).
65
+ * - `title` is derived from the status code via the shared status-title map.
66
+ * - `instance` is the request path when a context is supplied.
67
+ * - `errors` is included when the error carries validation details.
68
+ * - `type` is delegated to `resolveType`, the only member the two supported
69
+ * specification versions disagree on.
70
+ *
71
+ * @param error - The thrown error to format
72
+ * @param ctx - Optional request context (used for `instance`)
73
+ * @param resolveType - Strategy supplying the `type` member
74
+ * @returns A Problem Details body
75
+ * @since 0.1.0
76
+ */
77
+ export function buildProblemDetails(error, ctx, resolveType) {
78
+ const isHttp = error instanceof HttpError;
79
+ const statusCode = isHttp ? error.statusCode : 500;
80
+ const errors = isHttp ? extractErrors(error) : undefined;
81
+ // A responder-built error carries the site's disclosure in its structured
82
+ // details under a `detail` key (M70f); the Problem Details `detail` member
83
+ // is that disclosure, falling back to the error message (the title) when a
84
+ // site supplied none.
85
+ //
86
+ // The promotion is gated on {@linkcode RESPONDER_DETAIL}, NOT on the mere
87
+ // presence of a `detail` key, because `HttpError.details` is a free-form bag
88
+ // an application also writes into: reading any `details.detail` would
89
+ // silently change the `detail` member of a thrown
90
+ // `new HttpError(400, 'Invalid payload', { detail: 'code-7' })` from its
91
+ // message to `'code-7'` — a released-behaviour change to every application
92
+ // that happens to use that key name (M70f code review, finding 4). Only an
93
+ // error the responder built carries the marker, so a thrown error keeps the
94
+ // pre-M70f shape: `detail` is the message.
95
+ const carriedDetail = isHttp && RESPONDER_DETAIL in error
96
+ ? error[RESPONDER_DETAIL]
97
+ : undefined;
98
+ const detail = typeof carriedDetail === 'string' ? carriedDetail : error.message;
99
+ return {
100
+ type: resolveType(statusCode, errors !== undefined),
101
+ title: statusTitle(statusCode),
102
+ status: statusCode,
103
+ detail,
104
+ ...(ctx !== undefined && { instance: ctx.request.path }),
105
+ ...(errors !== undefined && { errors }),
106
+ };
107
+ }
108
+ //# sourceMappingURL=problem-details.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"problem-details.js","sourceRoot":"","sources":["../../src/formatters/problem-details.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,4BAA4B,CAAC;AAE5D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,eAAe,GAAW,GAAG,eAAe,aAAa,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC;AAkCzC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAkB,MAAM,CAAC,kCAAkC,CAAC,CAAC;AAY1F;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAgB;IACrC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC9B,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,CAAC,QAAQ,IAAI,OAAO,CAAC,EAAE,CAAC;QACpD,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,OAAO,OAAO,CAAC,MAAkC,CAAC;AACpD,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAAY,EACZ,GAAgC,EAChC,WAAgC;IAEhC,MAAM,MAAM,GAAG,KAAK,YAAY,SAAS,CAAC;IAC1C,MAAM,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC;IACnD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACzD,0EAA0E;IAC1E,2EAA2E;IAC3E,2EAA2E;IAC3E,sBAAsB;IACtB,EAAE;IACF,0EAA0E;IAC1E,6EAA6E;IAC7E,sEAAsE;IACtE,kDAAkD;IAClD,yEAAyE;IACzE,2EAA2E;IAC3E,2EAA2E;IAC3E,4EAA4E;IAC5E,2CAA2C;IAC3C,MAAM,aAAa,GAAG,MAAM,IAAI,gBAAgB,IAAI,KAAK;QACvD,CAAC,CAAE,KAAsD,CAAC,gBAAgB,CAAC;QAC3E,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,MAAM,GAAG,OAAO,aAAa,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC;IAEjF,OAAO;QACL,IAAI,EAAE,WAAW,CAAC,UAAU,EAAE,MAAM,KAAK,SAAS,CAAC;QACnD,KAAK,EAAE,WAAW,CAAC,UAAU,CAAC;QAC9B,MAAM,EAAE,UAAU;QAClB,MAAM;QACN,GAAG,CAAC,GAAG,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACxD,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,20 @@
1
+ import type { ErrorHandlerFormatter } from './error-formatter.ts';
2
+ /**
3
+ * Format an error as RFC 7807 Problem Details.
4
+ *
5
+ * @param error - The thrown error to format
6
+ * @param ctx - Optional request context (used for `instance`)
7
+ * @returns An RFC 7807 Problem Details body
8
+ * @deprecated RFC 7807 was obsoleted by RFC 9457. Use `rfc9457Formatter`
9
+ * instead, or the `'rfc9457'` format alias. Will be removed in v1.0.0.
10
+ * @example
11
+ * ```typescript
12
+ * // Before
13
+ * app.middleware.add(errorHandler({ format: 'rfc7807' }));
14
+ * // After — `type` becomes 'about:blank' for status-only problems
15
+ * app.middleware.add(errorHandler({ format: 'rfc9457' }));
16
+ * ```
17
+ * @since 0.1.0
18
+ */
19
+ export declare const rfc7807Formatter: ErrorHandlerFormatter;
20
+ //# sourceMappingURL=rfc7807-formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfc7807-formatter.d.ts","sourceRoot":"","sources":["../../src/formatters/rfc7807-formatter.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAWlE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,gBAAgB,EAAE,qBAGyC,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { buildProblemDetails, ERROR_TYPE_BASE } from "./problem-details.js";
2
+ /**
3
+ * Resolves the RFC 7807 `type` member: a URI minted from the status code.
4
+ *
5
+ * @param statusCode - The HTTP status code of the occurrence
6
+ * @returns The status-derived problem type URI
7
+ */
8
+ const resolveRfc7807Type = (statusCode) => `${ERROR_TYPE_BASE}/${statusCode}`;
9
+ /**
10
+ * Format an error as RFC 7807 Problem Details.
11
+ *
12
+ * @param error - The thrown error to format
13
+ * @param ctx - Optional request context (used for `instance`)
14
+ * @returns An RFC 7807 Problem Details body
15
+ * @deprecated RFC 7807 was obsoleted by RFC 9457. Use `rfc9457Formatter`
16
+ * instead, or the `'rfc9457'` format alias. Will be removed in v1.0.0.
17
+ * @example
18
+ * ```typescript
19
+ * // Before
20
+ * app.middleware.add(errorHandler({ format: 'rfc7807' }));
21
+ * // After — `type` becomes 'about:blank' for status-only problems
22
+ * app.middleware.add(errorHandler({ format: 'rfc9457' }));
23
+ * ```
24
+ * @since 0.1.0
25
+ */
26
+ export const rfc7807Formatter = (error, ctx) => buildProblemDetails(error, ctx, resolveRfc7807Type);
27
+ //# sourceMappingURL=rfc7807-formatter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rfc7807-formatter.js","sourceRoot":"","sources":["../../src/formatters/rfc7807-formatter.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,mBAAmB,EAAE,eAAe,EAAuB,MAAM,sBAAsB,CAAC;AAEjG;;;;;GAKG;AACH,MAAM,kBAAkB,GAAG,CAAC,UAAkB,EAAU,EAAE,CAAC,GAAG,eAAe,IAAI,UAAU,EAAE,CAAC;AAE9F;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA0B,CACrD,KAAY,EACZ,GAAqB,EACL,EAAE,CAAC,mBAAmB,CAAC,KAAK,EAAE,GAAG,EAAE,kBAAkB,CAAC,CAAC"}
@@ -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"}
@@ -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"}