@setu-ts/exceptions 0.0.0-stage → 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.
- package/LICENSE +21 -0
- package/README.md +180 -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 +112 -0
- package/dist/middleware/error-handler.d.ts.map +1 -0
- package/dist/middleware/error-handler.js +210 -0
- package/dist/middleware/error-handler.js.map +1 -0
- package/dist/middleware/error-responder-impl.d.ts +52 -0
- package/dist/middleware/error-responder-impl.d.ts.map +1 -0
- package/dist/middleware/error-responder-impl.js +148 -0
- package/dist/middleware/error-responder-impl.js.map +1 -0
- package/package.json +52 -3
- package/src/errors/exceptions.ts +212 -0
- package/src/errors/http-error.ts +132 -0
- package/src/formatters/error-formatter.ts +127 -0
- package/src/formatters/problem-details.ts +176 -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 +337 -0
- package/src/middleware/error-responder-impl.ts +178 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Setu-TS Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,181 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @setu-ts/exceptions
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Exception hierarchy, factory functions, error formatters, and the global error-handler middleware.
|
|
4
|
+
|
|
5
|
+
This is a **plain package, not a plugin** — it depends on `@setu-ts/common` only and registers no
|
|
6
|
+
capability. You wire the middleware into the application pipeline yourself.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import { errorHandler, notFound } from '@setu-ts/exceptions';
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
import { badRequest, errorHandler, HttpError, notFound } from '@setu-ts/exceptions';
|
|
18
|
+
|
|
19
|
+
app.middleware.add(errorHandler({ format: 'rfc9457' }), {
|
|
20
|
+
priority: 0,
|
|
21
|
+
name: 'error-handler',
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
app.router.get('/users/:id', async (ctx) => {
|
|
25
|
+
const user = await users.find(ctx.params.id);
|
|
26
|
+
if (!user) throw notFound('User not found');
|
|
27
|
+
return ctx.response.json(user);
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Options
|
|
32
|
+
|
|
33
|
+
`errorHandler(options?)` takes:
|
|
34
|
+
|
|
35
|
+
| Option | Type | Default | Description |
|
|
36
|
+
| -------------------- | --------------------------------------------------------------------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
37
|
+
| `format` | `ErrorFormat \| ErrorHandlerFormatter` | `'default'` | Body shape, or a formatter function. |
|
|
38
|
+
| `includeStackTrace` | `boolean` | `false` | Add the stack to the body. Development only. |
|
|
39
|
+
| `maskInternalErrors` | `boolean` | `true` | Replace unhandled non-`HttpError` 5xx diagnostics with their status title before serving the response. |
|
|
40
|
+
| `logErrors` | `boolean` | `true` | Report handled errors to the resolved `ILogger`. |
|
|
41
|
+
| `respond` | `(error: HttpError, ctx: IRequestContext) => HandlerResult \| undefined \| Promise<HandlerResult \| undefined>` | — | Write an application-owned caught-error response; return or resolve `undefined` to use the configured formatter instead. |
|
|
42
|
+
|
|
43
|
+
`format` accepts `'default'`, `'rfc9457'`, the deprecated `'rfc7807'`, or a function. Passing a
|
|
44
|
+
formatter directly is equivalent to naming it — both paths select the same
|
|
45
|
+
`application/problem+json` content type for Problem Details bodies.
|
|
46
|
+
|
|
47
|
+
`respond` is called after error normalization, masking, and status resolution. It can return or
|
|
48
|
+
resolve an HTML (or another application-owned) result through `ctx.response`; the callback owns that
|
|
49
|
+
result's status, headers, and body. The log retains the unmasked diagnostic and records the status
|
|
50
|
+
that result serves. It applies only to errors caught by `errorHandler`, not responder-based
|
|
51
|
+
terminals such as an unmatched-path `404`.
|
|
52
|
+
|
|
53
|
+
The natural spelling of the hook omits the one statement that matters and serves `200` for every
|
|
54
|
+
branded error: `renderView(ctx, Component, props)` takes no status, and nothing else sets it. The
|
|
55
|
+
callback owns the status, so the example carries that line on its own:
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { errorHandler, statusTitle } from '@setu-ts/exceptions';
|
|
59
|
+
import type { HandlerResult, IRequestContext } from '@setu-ts/common';
|
|
60
|
+
import { renderView } from '@setu-ts/view-plugin';
|
|
61
|
+
|
|
62
|
+
interface ErrorPageProps {
|
|
63
|
+
readonly status: number;
|
|
64
|
+
readonly title: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const ErrorPage = (props: ErrorPageProps) => (
|
|
68
|
+
<main>
|
|
69
|
+
<h1>{props.status}</h1>
|
|
70
|
+
<p>{props.title}</p>
|
|
71
|
+
</main>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
function wantsHtml(ctx: IRequestContext): boolean {
|
|
75
|
+
// Deliberately simple — the point here is that the callback owns the status,
|
|
76
|
+
// not content negotiation. This does NOT honour `q` values, so a client
|
|
77
|
+
// sending `text/html;q=0` (a refusal) still gets HTML; parse the media
|
|
78
|
+
// ranges if your callers send them.
|
|
79
|
+
return (ctx.request.headers.get('accept') ?? '').includes('text/html');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
app.middleware.add(
|
|
83
|
+
errorHandler({
|
|
84
|
+
format: 'rfc9457',
|
|
85
|
+
async respond(error, ctx): Promise<HandlerResult | undefined> {
|
|
86
|
+
if (!wantsHtml(ctx)) return undefined; // API clients keep Problem Details
|
|
87
|
+
// The callback owns the result's status, and renderView takes none —
|
|
88
|
+
// without this line every branded error answers 200.
|
|
89
|
+
ctx.response.status(error.statusCode);
|
|
90
|
+
return await renderView(ctx, ErrorPage, {
|
|
91
|
+
status: error.statusCode,
|
|
92
|
+
title: statusTitle(error.statusCode),
|
|
93
|
+
});
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
{ priority: 0, name: 'error-handler' },
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
What the hook does NOT cover is stated by the rule in
|
|
101
|
+
[docs/mvc.md](https://github.com/setu-ts/setu-ts/blob/main/docs/mvc.md#error-pages): every responder
|
|
102
|
+
terminal — an unmatched-path `404`, a guard's `401`, the rate limiter's `429` — is emitted outside
|
|
103
|
+
`errorHandler`, so each keeps its formatter's JSON no matter what this callback renders.
|
|
104
|
+
|
|
105
|
+
## What it exports
|
|
106
|
+
|
|
107
|
+
- **`HttpError`** — the error type, carrying a status, a title, and optional validation details.
|
|
108
|
+
- **Factories** — `badRequest`, `unauthorized`, `forbidden`, `notFound`, `conflict`,
|
|
109
|
+
`tooManyRequests`, `internalServerError`, `notImplemented`, `serviceUnavailable`,
|
|
110
|
+
`validationError`, plus `statusTitle` / `STATUS_TITLES`.
|
|
111
|
+
- **Formatters** — `defaultFormatter` and `rfc9457Formatter` ([RFC 9457][rfc9457] Problem Details),
|
|
112
|
+
plus a deprecated `rfc7807Formatter`, selected by `selectFormatter`. A Problem Details body
|
|
113
|
+
carries `detail`, never `message`.
|
|
114
|
+
- **Middleware** — `errorHandler(options)`.
|
|
115
|
+
|
|
116
|
+
## Problem Details and `about:blank`
|
|
117
|
+
|
|
118
|
+
The `'rfc9457'` format emits `type: "about:blank"` for any error whose only semantics are its status
|
|
119
|
+
code — which is every factory in this package except `validationError()`. [RFC 9457][rfc9457] §4.2
|
|
120
|
+
registers `about:blank` for exactly that case, because a URI minted from the status code identifies
|
|
121
|
+
nothing the `status` member does not already carry. Read `status` to distinguish errors.
|
|
122
|
+
|
|
123
|
+
`validationError()` is the exception: it defines an `errors` extension member, so it is a distinct
|
|
124
|
+
problem type identified by `https://setu-ts.dev/errors/validation` — the same URI
|
|
125
|
+
`@setu-ts/validation-plugin` uses.
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"type": "about:blank",
|
|
130
|
+
"title": "Not Found",
|
|
131
|
+
"status": 404,
|
|
132
|
+
"detail": "User 42 does not exist",
|
|
133
|
+
"instance": "/users/42"
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
> **`'rfc7807'` is deprecated.** RFC 7807 was obsoleted by RFC 9457 in July 2023. The `'rfc7807'`
|
|
138
|
+
> alias and `rfc7807Formatter` still emit the status-derived `type`
|
|
139
|
+
> (`https://setu-ts.dev/errors/404`) they always did — a deprecated symbol must not silently change
|
|
140
|
+
> behavior — so upgrading is a deliberate one-word edit. Removal is scheduled for v1.0.0.
|
|
141
|
+
|
|
142
|
+
## Exports
|
|
143
|
+
|
|
144
|
+
| Export | Kind |
|
|
145
|
+
| ----------------------- | --------- |
|
|
146
|
+
| `badRequest` | function |
|
|
147
|
+
| `conflict` | function |
|
|
148
|
+
| `defaultFormatter` | function |
|
|
149
|
+
| `errorHandler` | function |
|
|
150
|
+
| `forbidden` | function |
|
|
151
|
+
| `internalServerError` | function |
|
|
152
|
+
| `notFound` | function |
|
|
153
|
+
| `notImplemented` | function |
|
|
154
|
+
| `rfc7807Formatter` | function |
|
|
155
|
+
| `rfc9457Formatter` | function |
|
|
156
|
+
| `selectFormatter` | function |
|
|
157
|
+
| `serviceUnavailable` | function |
|
|
158
|
+
| `statusTitle` | function |
|
|
159
|
+
| `tooManyRequests` | function |
|
|
160
|
+
| `unauthorized` | function |
|
|
161
|
+
| `validationError` | function |
|
|
162
|
+
| `HttpError` | class |
|
|
163
|
+
| `ERROR_TYPE_BASE` | const |
|
|
164
|
+
| `STATUS_TITLES` | const |
|
|
165
|
+
| `DefaultErrorBody` | interface |
|
|
166
|
+
| `ErrorHandlerOptions` | interface |
|
|
167
|
+
| `HttpErrorInit` | interface |
|
|
168
|
+
| `ProblemDetails` | interface |
|
|
169
|
+
| `ValidationError` | interface |
|
|
170
|
+
| `ErrorFormat` | type |
|
|
171
|
+
| `ErrorHandlerFormatter` | type |
|
|
172
|
+
|
|
173
|
+
Generated from the package barrel by `deno task docs:exports`; `deno task check:docs` fails when it
|
|
174
|
+
drifts.
|
|
175
|
+
|
|
176
|
+
## Full API
|
|
177
|
+
|
|
178
|
+
Every export, its signature, and the exact response shapes are documented in
|
|
179
|
+
[PUBLIC_API.md](https://github.com/setu-ts/setu-ts/blob/main/PUBLIC_API.md#api-reference-setu-tsexceptions).
|
|
180
|
+
|
|
181
|
+
[rfc9457]: https://www.rfc-editor.org/rfc/rfc9457.html
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception factory functions.
|
|
3
|
+
*
|
|
4
|
+
* Each factory returns a {@linkcode HttpError} with a fixed `statusCode`,
|
|
5
|
+
* embodying **composition over inheritance** (AI_GUIDELINES §1.4,
|
|
6
|
+
* ARCHITECTURE.md §13). Throw one in a route handler or service and the
|
|
7
|
+
* error-handler middleware will format and send it with the right status.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { notFound, badRequest } from '@setu-ts/exceptions';
|
|
12
|
+
*
|
|
13
|
+
* function getUser(id: string) {
|
|
14
|
+
* if (!id) throw badRequest('id is required');
|
|
15
|
+
* const user = db.find(id);
|
|
16
|
+
* if (!user) throw notFound(`User ${id} not found`);
|
|
17
|
+
* return user;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { HttpError } from './http-error.ts';
|
|
24
|
+
import type { ValidationError } from './http-error.ts';
|
|
25
|
+
/**
|
|
26
|
+
* A human-readable title for a given HTTP status code. This is the single
|
|
27
|
+
* source of truth used by both the factory functions and the Problem Details
|
|
28
|
+
* formatters so the `title` field never drifts from the produced `statusCode`.
|
|
29
|
+
*
|
|
30
|
+
* Keys are every status the framework itself produces — the ones the factories
|
|
31
|
+
* below return AND the ones first-party code brands onto an error with
|
|
32
|
+
* `withHttpStatusHint` from `@setu-ts/common`, which reaches this table through
|
|
33
|
+
* the responder seam without passing a factory (`413` from the request-size and
|
|
34
|
+
* body-cap refusals, `415` from the form accessor, `501`/`503`/`504` from the
|
|
35
|
+
* database, secrets and resilience refusals). A branded status with no row here
|
|
36
|
+
* is served the generic `'Error'` title, so `test/status-title-coverage.test.ts`
|
|
37
|
+
* enumerates the branded statuses from source and fails when one is missing —
|
|
38
|
+
* `415` reached a client as `"title": "Error"` under `'rfc9457'` while the same
|
|
39
|
+
* error read `"message": "Unsupported Media Type"` under `'default'`, because
|
|
40
|
+
* every test asserted the brand on the error object and none read the served
|
|
41
|
+
* body.
|
|
42
|
+
*
|
|
43
|
+
* @since 0.1.0
|
|
44
|
+
*/
|
|
45
|
+
export declare const STATUS_TITLES: Readonly<Record<number, string>>;
|
|
46
|
+
/**
|
|
47
|
+
* Resolves the human-readable title for a status code, falling back to a
|
|
48
|
+
* generic title for codes outside the well-known set.
|
|
49
|
+
*
|
|
50
|
+
* @param statusCode - The HTTP status code
|
|
51
|
+
* @returns A human-readable title
|
|
52
|
+
* @since 0.1.0
|
|
53
|
+
*/
|
|
54
|
+
export declare function statusTitle(statusCode: number): string;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a `400 Bad Request` error.
|
|
57
|
+
*
|
|
58
|
+
* @param message - Human-readable error message
|
|
59
|
+
* @param details - Optional structured details appended to the error body
|
|
60
|
+
* @returns A `400` {@linkcode HttpError}
|
|
61
|
+
* @since 0.1.0
|
|
62
|
+
*/
|
|
63
|
+
export declare function badRequest(message: string, details?: Readonly<Record<string, unknown>>): HttpError;
|
|
64
|
+
/**
|
|
65
|
+
* Creates a `401 Unauthorized` error.
|
|
66
|
+
*
|
|
67
|
+
* @param message - Human-readable error message
|
|
68
|
+
* @returns A `401` {@linkcode HttpError}
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
*/
|
|
71
|
+
export declare function unauthorized(message: string): HttpError;
|
|
72
|
+
/**
|
|
73
|
+
* Creates a `403 Forbidden` error.
|
|
74
|
+
*
|
|
75
|
+
* @param message - Human-readable error message
|
|
76
|
+
* @returns A `403` {@linkcode HttpError}
|
|
77
|
+
* @since 0.1.0
|
|
78
|
+
*/
|
|
79
|
+
export declare function forbidden(message: string): HttpError;
|
|
80
|
+
/**
|
|
81
|
+
* Creates a `404 Not Found` error.
|
|
82
|
+
*
|
|
83
|
+
* @param message - Human-readable error message
|
|
84
|
+
* @returns A `404` {@linkcode HttpError}
|
|
85
|
+
* @since 0.1.0
|
|
86
|
+
*/
|
|
87
|
+
export declare function notFound(message: string): HttpError;
|
|
88
|
+
/**
|
|
89
|
+
* Creates a `409 Conflict` error.
|
|
90
|
+
*
|
|
91
|
+
* @param message - Human-readable error message
|
|
92
|
+
* @returns A `409` {@linkcode HttpError}
|
|
93
|
+
* @since 0.1.0
|
|
94
|
+
*/
|
|
95
|
+
export declare function conflict(message: string): HttpError;
|
|
96
|
+
/**
|
|
97
|
+
* Creates a `422 Unprocessable Entity` error wrapping a list of validation
|
|
98
|
+
* failures.
|
|
99
|
+
*
|
|
100
|
+
* The `errors` array is stored in the error's `details.errors` so it survives
|
|
101
|
+
* serialization into both the default and RFC 7807 error bodies.
|
|
102
|
+
*
|
|
103
|
+
* @param errors - The validation failures that caused this error
|
|
104
|
+
* @param message - Optional custom message (defaults to a count-based summary)
|
|
105
|
+
* @returns A `422` {@linkcode HttpError}
|
|
106
|
+
* @since 0.1.0
|
|
107
|
+
*/
|
|
108
|
+
export declare function validationError(errors: readonly ValidationError[], message?: string): HttpError;
|
|
109
|
+
/**
|
|
110
|
+
* Creates a `429 Too Many Requests` error.
|
|
111
|
+
*
|
|
112
|
+
* @param message - Human-readable error message
|
|
113
|
+
* @param details - Optional structured details (e.g. `retryAfter`)
|
|
114
|
+
* @returns A `429` {@linkcode HttpError}
|
|
115
|
+
* @since 0.1.0
|
|
116
|
+
*/
|
|
117
|
+
export declare function tooManyRequests(message: string, details?: Readonly<Record<string, unknown>>): HttpError;
|
|
118
|
+
/**
|
|
119
|
+
* Creates a `500 Internal Server Error` error.
|
|
120
|
+
*
|
|
121
|
+
* Pass the original error as `cause` so the root cause is preserved in the
|
|
122
|
+
* ES2022 error chain for logging and debugging.
|
|
123
|
+
*
|
|
124
|
+
* @param message - Human-readable error message
|
|
125
|
+
* @param cause - Optional underlying error forwarded to the cause chain
|
|
126
|
+
* @returns A `500` {@linkcode HttpError}
|
|
127
|
+
* @since 0.1.0
|
|
128
|
+
*/
|
|
129
|
+
export declare function internalServerError(message: string, cause?: Error): HttpError;
|
|
130
|
+
/**
|
|
131
|
+
* Creates a `501 Not Implemented` error.
|
|
132
|
+
*
|
|
133
|
+
* @param message - Human-readable error message
|
|
134
|
+
* @returns A `501` {@linkcode HttpError}
|
|
135
|
+
* @since 0.1.0
|
|
136
|
+
*/
|
|
137
|
+
export declare function notImplemented(message: string): HttpError;
|
|
138
|
+
/**
|
|
139
|
+
* Creates a `503 Service Unavailable` error.
|
|
140
|
+
*
|
|
141
|
+
* @param message - Human-readable error message
|
|
142
|
+
* @returns A `503` {@linkcode HttpError}
|
|
143
|
+
* @since 0.1.0
|
|
144
|
+
*/
|
|
145
|
+
export declare function serviceUnavailable(message: string): HttpError;
|
|
146
|
+
//# sourceMappingURL=exceptions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exceptions.d.ts","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAMvD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAe1D,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEtD;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAEX;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEpD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEnD;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,SAAS,eAAe,EAAE,EAClC,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,CAGX;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAC1C,SAAS,CAEX;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAEzD;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAE7D"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exception factory functions.
|
|
3
|
+
*
|
|
4
|
+
* Each factory returns a {@linkcode HttpError} with a fixed `statusCode`,
|
|
5
|
+
* embodying **composition over inheritance** (AI_GUIDELINES §1.4,
|
|
6
|
+
* ARCHITECTURE.md §13). Throw one in a route handler or service and the
|
|
7
|
+
* error-handler middleware will format and send it with the right status.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { notFound, badRequest } from '@setu-ts/exceptions';
|
|
12
|
+
*
|
|
13
|
+
* function getUser(id: string) {
|
|
14
|
+
* if (!id) throw badRequest('id is required');
|
|
15
|
+
* const user = db.find(id);
|
|
16
|
+
* if (!user) throw notFound(`User ${id} not found`);
|
|
17
|
+
* return user;
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module
|
|
22
|
+
*/
|
|
23
|
+
import { HttpError } from "./http-error.js";
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Status-code → human title (single source of truth, shared with formatters)
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
/**
|
|
28
|
+
* A human-readable title for a given HTTP status code. This is the single
|
|
29
|
+
* source of truth used by both the factory functions and the Problem Details
|
|
30
|
+
* formatters so the `title` field never drifts from the produced `statusCode`.
|
|
31
|
+
*
|
|
32
|
+
* Keys are every status the framework itself produces — the ones the factories
|
|
33
|
+
* below return AND the ones first-party code brands onto an error with
|
|
34
|
+
* `withHttpStatusHint` from `@setu-ts/common`, which reaches this table through
|
|
35
|
+
* the responder seam without passing a factory (`413` from the request-size and
|
|
36
|
+
* body-cap refusals, `415` from the form accessor, `501`/`503`/`504` from the
|
|
37
|
+
* database, secrets and resilience refusals). A branded status with no row here
|
|
38
|
+
* is served the generic `'Error'` title, so `test/status-title-coverage.test.ts`
|
|
39
|
+
* enumerates the branded statuses from source and fails when one is missing —
|
|
40
|
+
* `415` reached a client as `"title": "Error"` under `'rfc9457'` while the same
|
|
41
|
+
* error read `"message": "Unsupported Media Type"` under `'default'`, because
|
|
42
|
+
* every test asserted the brand on the error object and none read the served
|
|
43
|
+
* body.
|
|
44
|
+
*
|
|
45
|
+
* @since 0.1.0
|
|
46
|
+
*/
|
|
47
|
+
export const STATUS_TITLES = {
|
|
48
|
+
400: 'Bad Request',
|
|
49
|
+
401: 'Unauthorized',
|
|
50
|
+
403: 'Forbidden',
|
|
51
|
+
404: 'Not Found',
|
|
52
|
+
409: 'Conflict',
|
|
53
|
+
413: 'Payload Too Large',
|
|
54
|
+
415: 'Unsupported Media Type',
|
|
55
|
+
422: 'Unprocessable Entity',
|
|
56
|
+
429: 'Too Many Requests',
|
|
57
|
+
500: 'Internal Server Error',
|
|
58
|
+
501: 'Not Implemented',
|
|
59
|
+
502: 'Bad Gateway',
|
|
60
|
+
503: 'Service Unavailable',
|
|
61
|
+
504: 'Gateway Timeout',
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Resolves the human-readable title for a status code, falling back to a
|
|
65
|
+
* generic title for codes outside the well-known set.
|
|
66
|
+
*
|
|
67
|
+
* @param statusCode - The HTTP status code
|
|
68
|
+
* @returns A human-readable title
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
*/
|
|
71
|
+
export function statusTitle(statusCode) {
|
|
72
|
+
return STATUS_TITLES[statusCode] ?? 'Error';
|
|
73
|
+
}
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
// Factories
|
|
76
|
+
// ---------------------------------------------------------------------------
|
|
77
|
+
/**
|
|
78
|
+
* Creates a `400 Bad Request` error.
|
|
79
|
+
*
|
|
80
|
+
* @param message - Human-readable error message
|
|
81
|
+
* @param details - Optional structured details appended to the error body
|
|
82
|
+
* @returns A `400` {@linkcode HttpError}
|
|
83
|
+
* @since 0.1.0
|
|
84
|
+
*/
|
|
85
|
+
export function badRequest(message, details) {
|
|
86
|
+
return new HttpError(400, message, details);
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Creates a `401 Unauthorized` error.
|
|
90
|
+
*
|
|
91
|
+
* @param message - Human-readable error message
|
|
92
|
+
* @returns A `401` {@linkcode HttpError}
|
|
93
|
+
* @since 0.1.0
|
|
94
|
+
*/
|
|
95
|
+
export function unauthorized(message) {
|
|
96
|
+
return new HttpError(401, message);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Creates a `403 Forbidden` error.
|
|
100
|
+
*
|
|
101
|
+
* @param message - Human-readable error message
|
|
102
|
+
* @returns A `403` {@linkcode HttpError}
|
|
103
|
+
* @since 0.1.0
|
|
104
|
+
*/
|
|
105
|
+
export function forbidden(message) {
|
|
106
|
+
return new HttpError(403, message);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Creates a `404 Not Found` error.
|
|
110
|
+
*
|
|
111
|
+
* @param message - Human-readable error message
|
|
112
|
+
* @returns A `404` {@linkcode HttpError}
|
|
113
|
+
* @since 0.1.0
|
|
114
|
+
*/
|
|
115
|
+
export function notFound(message) {
|
|
116
|
+
return new HttpError(404, message);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Creates a `409 Conflict` error.
|
|
120
|
+
*
|
|
121
|
+
* @param message - Human-readable error message
|
|
122
|
+
* @returns A `409` {@linkcode HttpError}
|
|
123
|
+
* @since 0.1.0
|
|
124
|
+
*/
|
|
125
|
+
export function conflict(message) {
|
|
126
|
+
return new HttpError(409, message);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Creates a `422 Unprocessable Entity` error wrapping a list of validation
|
|
130
|
+
* failures.
|
|
131
|
+
*
|
|
132
|
+
* The `errors` array is stored in the error's `details.errors` so it survives
|
|
133
|
+
* serialization into both the default and RFC 7807 error bodies.
|
|
134
|
+
*
|
|
135
|
+
* @param errors - The validation failures that caused this error
|
|
136
|
+
* @param message - Optional custom message (defaults to a count-based summary)
|
|
137
|
+
* @returns A `422` {@linkcode HttpError}
|
|
138
|
+
* @since 0.1.0
|
|
139
|
+
*/
|
|
140
|
+
export function validationError(errors, message) {
|
|
141
|
+
const summary = message ?? `Validation failed with ${errors.length} error(s).`;
|
|
142
|
+
return new HttpError(422, summary, { errors });
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Creates a `429 Too Many Requests` error.
|
|
146
|
+
*
|
|
147
|
+
* @param message - Human-readable error message
|
|
148
|
+
* @param details - Optional structured details (e.g. `retryAfter`)
|
|
149
|
+
* @returns A `429` {@linkcode HttpError}
|
|
150
|
+
* @since 0.1.0
|
|
151
|
+
*/
|
|
152
|
+
export function tooManyRequests(message, details) {
|
|
153
|
+
return new HttpError(429, message, details);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Creates a `500 Internal Server Error` error.
|
|
157
|
+
*
|
|
158
|
+
* Pass the original error as `cause` so the root cause is preserved in the
|
|
159
|
+
* ES2022 error chain for logging and debugging.
|
|
160
|
+
*
|
|
161
|
+
* @param message - Human-readable error message
|
|
162
|
+
* @param cause - Optional underlying error forwarded to the cause chain
|
|
163
|
+
* @returns A `500` {@linkcode HttpError}
|
|
164
|
+
* @since 0.1.0
|
|
165
|
+
*/
|
|
166
|
+
export function internalServerError(message, cause) {
|
|
167
|
+
return new HttpError(500, message, undefined, cause);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Creates a `501 Not Implemented` error.
|
|
171
|
+
*
|
|
172
|
+
* @param message - Human-readable error message
|
|
173
|
+
* @returns A `501` {@linkcode HttpError}
|
|
174
|
+
* @since 0.1.0
|
|
175
|
+
*/
|
|
176
|
+
export function notImplemented(message) {
|
|
177
|
+
return new HttpError(501, message);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Creates a `503 Service Unavailable` error.
|
|
181
|
+
*
|
|
182
|
+
* @param message - Human-readable error message
|
|
183
|
+
* @returns A `503` {@linkcode HttpError}
|
|
184
|
+
* @since 0.1.0
|
|
185
|
+
*/
|
|
186
|
+
export function serviceUnavailable(message) {
|
|
187
|
+
return new HttpError(503, message);
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=exceptions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exceptions.js","sourceRoot":"","sources":["../../src/errors/exceptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,8EAA8E;AAC9E,6EAA6E;AAC7E,8EAA8E;AAE9E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,MAAM,aAAa,GAAqC;IAC7D,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,UAAU;IACf,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,wBAAwB;IAC7B,GAAG,EAAE,sBAAsB;IAC3B,GAAG,EAAE,mBAAmB;IACxB,GAAG,EAAE,uBAAuB;IAC5B,GAAG,EAAE,iBAAiB;IACtB,GAAG,EAAE,aAAa;IAClB,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,iBAAiB;CACvB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,UAAkB;IAC5C,OAAO,aAAa,CAAC,UAAU,CAAC,IAAI,OAAO,CAAC;AAC9C,CAAC;AAED,8EAA8E;AAC9E,YAAY;AACZ,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CACxB,OAAe,EACf,OAA2C;IAE3C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAkC,EAClC,OAAgB;IAEhB,MAAM,OAAO,GAAG,OAAO,IAAI,0BAA0B,MAAM,CAAC,MAAM,YAAY,CAAC;IAC/E,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;AACjD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,OAA2C;IAE3C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,OAAe,EAAE,KAAa;IAChE,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAe;IAChD,OAAO,IAAI,SAAS,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
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
|
+
* A single validation failure carried by a `422` error.
|
|
18
|
+
*
|
|
19
|
+
* Mirrors the shape used by `@setu-ts/validation-plugin` so error
|
|
20
|
+
* payloads are consistent across the validation and exceptions packages.
|
|
21
|
+
*
|
|
22
|
+
* @since 0.1.0
|
|
23
|
+
*/
|
|
24
|
+
export interface ValidationError {
|
|
25
|
+
/** Dot-path of the offending field (e.g. `"address.zip"`). */
|
|
26
|
+
readonly field: string;
|
|
27
|
+
/** Human-readable description of the failure. */
|
|
28
|
+
readonly message: string;
|
|
29
|
+
/** Optional machine-readable failure code. */
|
|
30
|
+
readonly code?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Options accepted by the {@linkcode HttpError} constructor.
|
|
34
|
+
*
|
|
35
|
+
* Extracted as a type so factory functions can accept a consistent object.
|
|
36
|
+
*
|
|
37
|
+
* @since 0.1.0
|
|
38
|
+
*/
|
|
39
|
+
export interface HttpErrorInit {
|
|
40
|
+
/** HTTP status code (e.g. `404`). */
|
|
41
|
+
readonly statusCode: number;
|
|
42
|
+
/** Human-readable error message. */
|
|
43
|
+
readonly message: string;
|
|
44
|
+
/** Optional structured details attached to the error body. */
|
|
45
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
46
|
+
/** Optional underlying cause (forwarded to the ES2022 `Error` cause chain). */
|
|
47
|
+
readonly cause?: Error;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* The framework's HTTP error type.
|
|
51
|
+
*
|
|
52
|
+
* Throw an instance directly, or — preferably — construct one via a factory
|
|
53
|
+
* function from {@linkcode ./exceptions.ts} so the status code is correct by
|
|
54
|
+
* construction:
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* import { notFound } from '@setu-ts/exceptions';
|
|
59
|
+
*
|
|
60
|
+
* throw notFound(`User ${id} not found`);
|
|
61
|
+
* ```
|
|
62
|
+
*
|
|
63
|
+
* The error handler middleware inspects the `statusCode` to set the response
|
|
64
|
+
* status, and serializes `message` and `details` into the error body.
|
|
65
|
+
*
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
*/
|
|
68
|
+
export declare class HttpError extends Error {
|
|
69
|
+
/** The HTTP status code this error maps to. */
|
|
70
|
+
readonly statusCode: number;
|
|
71
|
+
/**
|
|
72
|
+
* Structured details appended to the error body. Omitted entirely when not
|
|
73
|
+
* supplied (never `undefined`) so serialization stays clean.
|
|
74
|
+
*
|
|
75
|
+
* `declare` suppresses the ES2022 class-field initialization that would
|
|
76
|
+
* otherwise set this to `undefined` on every instance — it only exists on
|
|
77
|
+
* the object when actually assigned by the constructor.
|
|
78
|
+
*/
|
|
79
|
+
readonly details?: Readonly<Record<string, unknown>>;
|
|
80
|
+
/**
|
|
81
|
+
* Creates a new `HttpError`.
|
|
82
|
+
*
|
|
83
|
+
* Prefer the factory functions in `exceptions.ts` over calling this
|
|
84
|
+
* constructor directly — they guarantee a correct status code.
|
|
85
|
+
*
|
|
86
|
+
* @param statusCode - HTTP status code (e.g. `404`)
|
|
87
|
+
* @param message - Human-readable error message
|
|
88
|
+
* @param details - Optional structured details; omitted when absent
|
|
89
|
+
* @param cause - Optional underlying error; forwarded to the ES2022 cause chain
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```typescript
|
|
93
|
+
* const err = new HttpError(404, 'User not found');
|
|
94
|
+
* console.log(err.statusCode); // 404
|
|
95
|
+
* ```
|
|
96
|
+
* @since 0.1.0
|
|
97
|
+
*/
|
|
98
|
+
constructor(statusCode: number, message: string, details?: Readonly<Record<string, unknown>>, cause?: Error);
|
|
99
|
+
/**
|
|
100
|
+
* Creates an `HttpError` from an {@linkcode HttpErrorInit} object.
|
|
101
|
+
*
|
|
102
|
+
* Convenience overload used by factory functions so they can branch on
|
|
103
|
+
* optional `details`/`cause` once.
|
|
104
|
+
*
|
|
105
|
+
* @param init - The error options
|
|
106
|
+
* @returns A new `HttpError`
|
|
107
|
+
* @since 0.1.0
|
|
108
|
+
*/
|
|
109
|
+
static from(init: HttpErrorInit): HttpError;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=http-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-error.d.ts","sourceRoot":"","sources":["../../src/errors/http-error.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,qCAAqC;IACrC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACrD,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,+CAA+C;IAC/C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B;;;;;;;OAOG;IACH,SAAiB,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAE7D;;;;;;;;;;;;;;;;;OAiBG;gBAED,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EAC3C,KAAK,CAAC,EAAE,KAAK;IAaf;;;;;;;;;OASG;IACH,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,aAAa,GAAG,SAAS;CAG5C"}
|