@tetsujs/openapi 0.4.1 → 0.5.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/README.md +140 -3
- package/dist/src/annotations.d.ts +40 -0
- package/dist/src/annotations.d.ts.map +1 -1
- package/dist/src/controller.d.ts +32 -9
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/document.d.ts +7 -0
- package/dist/src/document.d.ts.map +1 -1
- package/dist/src/emit.d.ts +3 -1
- package/dist/src/emit.d.ts.map +1 -1
- package/dist/src/envelopes.d.ts +72 -0
- package/dist/src/envelopes.d.ts.map +1 -0
- package/dist/src/errors.d.ts +74 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/index.d.ts +31 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +374 -135
- package/dist/src/index.js.map +11 -9
- package/dist/src/json-schema.d.ts +91 -0
- package/dist/src/json-schema.d.ts.map +1 -0
- package/dist/src/operation.d.ts +10 -1
- package/dist/src/operation.d.ts.map +1 -1
- package/dist/src/page.d.ts +20 -0
- package/dist/src/page.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/annotations.ts +43 -0
- package/src/controller.ts +69 -22
- package/src/document.ts +9 -0
- package/src/emit.ts +3 -1
- package/src/envelopes.ts +268 -0
- package/src/errors.ts +177 -0
- package/src/index.ts +52 -6
- package/src/json-schema.ts +108 -0
- package/src/operation.ts +328 -187
- package/src/page.ts +65 -6
package/README.md
CHANGED
|
@@ -30,6 +30,38 @@ reported before the first request.
|
|
|
30
30
|
prefix, guard it with hooks, or leave it out in production. Its own two
|
|
31
31
|
routes are left out of the document.
|
|
32
32
|
|
|
33
|
+
### The page runs someone else's code on your origin
|
|
34
|
+
|
|
35
|
+
The page loads its renderer — Scalar, Swagger UI or Redoc — from
|
|
36
|
+
jsDelivr and runs it on the application's origin, with that origin's
|
|
37
|
+
cookies. If a session cookie lives there, a renderer that is not what it
|
|
38
|
+
should be acts as the signed-in user. The defaults are pinned to an exact
|
|
39
|
+
version with a Subresource Integrity hash, so a file the CDN changes is
|
|
40
|
+
refused by the browser; that does not make the renderer yours, and what it
|
|
41
|
+
loads in turn is not covered.
|
|
42
|
+
|
|
43
|
+
Where the origin carries a session, serve the document alone:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
docs({ info, ui: false });
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`/openapi.json` is served and there is no page. For a page anyway, host
|
|
50
|
+
the renderer yourself or pin your own copy, with its hash:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
docs({
|
|
54
|
+
info,
|
|
55
|
+
assets: {
|
|
56
|
+
script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.1/dist/browser/standalone.js",
|
|
57
|
+
integrity: { script: "sha384-…" },
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A` prints
|
|
63
|
+
the hash, to be prefixed with `sha384-`.
|
|
64
|
+
|
|
33
65
|
## Options
|
|
34
66
|
|
|
35
67
|
| Option | Default | |
|
|
@@ -38,11 +70,12 @@ routes are left out of the document.
|
|
|
38
70
|
| `servers` | — | where the API is reachable |
|
|
39
71
|
| `path` | `/openapi.json` | where the document is served |
|
|
40
72
|
| `uiPath` | `/docs` | where the page is served |
|
|
41
|
-
| `ui` | `"scalar"` | `"scalar"`, `"swagger-ui"
|
|
73
|
+
| `ui` | `"scalar"` | `"scalar"`, `"swagger-ui"`, `"redoc"`, or `false` for no page |
|
|
42
74
|
| `title` | the document's title | the page's title |
|
|
43
|
-
| `assets` | the renderer
|
|
75
|
+
| `assets` | the renderer on jsDelivr, pinned | your own renderer URLs, with `integrity` hashes |
|
|
44
76
|
| `documentSelf` | `false` | include `/openapi.json` and `/docs` in the document |
|
|
45
77
|
| `onWarning` | `console.warn` | receives what could not be described |
|
|
78
|
+
| `errors` | the framework's envelope | an error format of your own — see [below](#an-error-format-of-your-own) |
|
|
46
79
|
|
|
47
80
|
## What goes into the document
|
|
48
81
|
|
|
@@ -106,6 +139,95 @@ The `error` code is a constant in each schema, so a generated client can
|
|
|
106
139
|
tell failures apart by it. A status the route declares itself is kept, and
|
|
107
140
|
the framework's failures for the same status are listed next to it.
|
|
108
141
|
|
|
142
|
+
Every envelope — the framework's, a hook's, or one the route declares
|
|
143
|
+
itself — is one definition in `components` per status and code, named
|
|
144
|
+
after the code (`ITEM_NOT_FOUND` is `ItemNotFound`), so a generated client
|
|
145
|
+
gets one type per failure. A schema is recognized as an envelope when its
|
|
146
|
+
`error` is required and a single string. When the route and a hook both
|
|
147
|
+
describe one code, the route's definition is kept; if the other one has
|
|
148
|
+
different fields, the generator warns. A union the route declares joins
|
|
149
|
+
the other failures of its status as one flat `anyOf`.
|
|
150
|
+
|
|
151
|
+
When every alternative of a status is an envelope, the union carries a
|
|
152
|
+
`discriminator` on `error`, with the mapping from each code to its
|
|
153
|
+
definition, and a generated client narrows on the code.
|
|
154
|
+
|
|
155
|
+
## An error format of your own
|
|
156
|
+
|
|
157
|
+
Every failure reaches the application's `onError` hooks — a thrown
|
|
158
|
+
`HttpError`, a validation or body failure, a `404`, a `405`, a rate limit's
|
|
159
|
+
refusal — so one hook answers all of them in your format:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
const inOurFormat = hook.onError(({ error }) => {
|
|
163
|
+
if (!(error instanceof HttpError)) return undefined;
|
|
164
|
+
|
|
165
|
+
const { status, error: code, ...rest } = error.body as ErrorBody;
|
|
166
|
+
|
|
167
|
+
return Response.json({ code, ...rest }, { status });
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The document is generated from the routes, not from that hook, so it is
|
|
172
|
+
told the same format with `errors`:
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
const errors: ErrorFormat = {
|
|
176
|
+
schema: ({ error, message, fields }) => ({
|
|
177
|
+
type: "object",
|
|
178
|
+
required: ["code", "message", ...Object.keys(fields)],
|
|
179
|
+
properties: {
|
|
180
|
+
code: error ? { type: "string", const: error } : { type: "string" },
|
|
181
|
+
message: { type: "string", ...(message ? { examples: [message] } : {}) },
|
|
182
|
+
...fields,
|
|
183
|
+
},
|
|
184
|
+
}),
|
|
185
|
+
discriminator: "code",
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
createApp({
|
|
189
|
+
hooks: { onError: [inOurFormat] },
|
|
190
|
+
routes: [api, docs({ info, errors })],
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`schema` describes one failure from what the generator knows of it: its
|
|
195
|
+
status, its code when known, an example message, and in `fields` what it
|
|
196
|
+
carries besides — `issues` for a validation failure, `retryAfter` for a
|
|
197
|
+
rate limit, a hook's own `fields`. It is used for the framework's failures
|
|
198
|
+
and the hooks' refusals alike.
|
|
199
|
+
|
|
200
|
+
`discriminator` names the top-level field that holds the code. Statuses of
|
|
201
|
+
envelopes are then discriminated on it, and a route's own envelope is
|
|
202
|
+
recognized by it — a `{ code: "ITEM_NOT_FOUND", … }` the route declares is
|
|
203
|
+
the same definition as a hook's. A format that nests its code has no
|
|
204
|
+
top-level field to name; it gives `code` instead, reading the code from a
|
|
205
|
+
route's schema:
|
|
206
|
+
|
|
207
|
+
```ts
|
|
208
|
+
code: (schema) => {
|
|
209
|
+
const error = schema.properties?.error;
|
|
210
|
+
const code = typeof error === "object" ? error.properties?.code : undefined;
|
|
211
|
+
|
|
212
|
+
return typeof code === "object" && typeof code.const === "string" ? code.const : undefined;
|
|
213
|
+
},
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
The hook and `errors` describe one format in two places — the core knows
|
|
217
|
+
nothing about documents, and a function cannot be read for the shape it
|
|
218
|
+
returns — so keep a test that holds them together: provoke a failure and
|
|
219
|
+
check its body against the document, with the JSON Schema validator of
|
|
220
|
+
your choice.
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
test("a validation failure is what the document says", async () => {
|
|
224
|
+
const res = await request("/items", { method: "POST", body: "{}" });
|
|
225
|
+
const schema = document.components?.schemas?.ValidationFailed;
|
|
226
|
+
|
|
227
|
+
expect(validator.validate(schema, await res.json())).toBe(true);
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
109
231
|
## Documenting hooks
|
|
110
232
|
|
|
111
233
|
A hook that answers by itself — an auth check, a limiter — can say so, and
|
|
@@ -127,10 +249,25 @@ export const auth = secured(
|
|
|
127
249
|
);
|
|
128
250
|
|
|
129
251
|
export const guard = documented(hook.beforeParse(check), {
|
|
130
|
-
responses: [
|
|
252
|
+
responses: [
|
|
253
|
+
{
|
|
254
|
+
status: 429,
|
|
255
|
+
description: "Rate limit exceeded",
|
|
256
|
+
error: "RATE_LIMITED",
|
|
257
|
+
fields: { retryAfter: { type: "integer", minimum: 0 } },
|
|
258
|
+
headers: { "retry-after": { schema: { type: "integer", minimum: 0 } } },
|
|
259
|
+
},
|
|
260
|
+
],
|
|
131
261
|
});
|
|
132
262
|
```
|
|
133
263
|
|
|
264
|
+
A response with an `error` code is the framework's envelope, defined once
|
|
265
|
+
in `components` and named after the code. `fields` adds what the hook puts
|
|
266
|
+
next to `status`, `message` and `error`, each always present; `headers`,
|
|
267
|
+
what it sets on the response. Both are JSON Schema written by hand, typed
|
|
268
|
+
keyword by keyword (`JsonSchema`), so a misspelled keyword does not
|
|
269
|
+
compile. A hook whose body is not the envelope passes a `schema` instead.
|
|
270
|
+
|
|
134
271
|
Hooks from `@tetsujs/rate-limit` are already documented this way. A scheme
|
|
135
272
|
is identified by its `name`: the same one mounted twice is one
|
|
136
273
|
requirement, and two different schemes under one name are reported.
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
* @module
|
|
17
17
|
*/
|
|
18
18
|
import type { AnyHook, AnySchema } from "@tetsujs/core";
|
|
19
|
+
import type { JsonSchema } from "./json-schema.ts";
|
|
19
20
|
/**
|
|
20
21
|
* The definition of a scheme, as OpenAPI spells it.
|
|
21
22
|
*
|
|
@@ -70,6 +71,45 @@ export interface DocumentedResponse {
|
|
|
70
71
|
readonly error?: string;
|
|
71
72
|
/** An example of the envelope's `message`, for the same reason. */
|
|
72
73
|
readonly message?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Fields the hook adds to the envelope, as JSON Schema by name — the
|
|
76
|
+
* `retryAfter` of `{ ...errorBody(429, "RATE_LIMITED"), retryAfter }`.
|
|
77
|
+
*
|
|
78
|
+
* Each is documented as always present. The envelope's own `status`,
|
|
79
|
+
* `message` and `error` are not redefined by a field of the same name:
|
|
80
|
+
* `error` is what a client discriminates on, and it stays the code the
|
|
81
|
+
* hook declared. Ignored when the response has a `schema`, which says
|
|
82
|
+
* everything about the body itself.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* documented(limiter, {
|
|
87
|
+
* responses: [
|
|
88
|
+
* {
|
|
89
|
+
* status: 429,
|
|
90
|
+
* description: "Too many requests",
|
|
91
|
+
* error: "RATE_LIMITED",
|
|
92
|
+
* fields: { retryAfter: { type: "integer", minimum: 0 } },
|
|
93
|
+
* },
|
|
94
|
+
* ],
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
readonly fields?: Readonly<Record<string, JsonSchema>>;
|
|
99
|
+
/**
|
|
100
|
+
* Headers the hook sets on this response, by name — the `retry-after`
|
|
101
|
+
* of a refusal.
|
|
102
|
+
*
|
|
103
|
+
* Documented as possible rather than required: a status several sources
|
|
104
|
+
* answer with is one response in the document, and a header one of them
|
|
105
|
+
* sets is not on the others'.
|
|
106
|
+
*/
|
|
107
|
+
readonly headers?: Readonly<Record<string, DocumentedHeader>>;
|
|
108
|
+
}
|
|
109
|
+
/** A header a hook's response carries. */
|
|
110
|
+
export interface DocumentedHeader {
|
|
111
|
+
readonly description?: string;
|
|
112
|
+
readonly schema: JsonSchema;
|
|
73
113
|
}
|
|
74
114
|
/**
|
|
75
115
|
* Requirements of which a hook accepts any one: a session cookie *or* a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/annotations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/annotations.ts"],"names":[],"mappings":"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"}
|
package/dist/src/controller.d.ts
CHANGED
|
@@ -15,20 +15,34 @@
|
|
|
15
15
|
import type { App, RouteDef, SchemaConfig, ValidatePath } from "@tetsujs/core";
|
|
16
16
|
import { onMount } from "@tetsujs/core";
|
|
17
17
|
import type { OpenApiDocument } from "./document.ts";
|
|
18
|
+
import type { ErrorFormat } from "./errors.ts";
|
|
18
19
|
import type { GeneratorWarning } from "./index.ts";
|
|
19
20
|
import type { DocsAssets, DocsUi } from "./page.ts";
|
|
20
21
|
/** What the documentation controller serves, and from where. */
|
|
21
|
-
export interface DocsOptions<Path extends string, UiPath extends string> {
|
|
22
|
+
export interface DocsOptions<Path extends string, UiPath extends string, Ui extends DocsUi | false = DocsUi | false> {
|
|
22
23
|
/** Title, version and the rest of the document's `info` block. */
|
|
23
24
|
readonly info: OpenApiDocument["info"];
|
|
24
25
|
/** Servers the API is reachable at. */
|
|
25
26
|
readonly servers?: OpenApiDocument["servers"];
|
|
27
|
+
/**
|
|
28
|
+
* How the application's errors look, when not like the framework's
|
|
29
|
+
* envelope — see `errors` on {@link openapi}.
|
|
30
|
+
*/
|
|
31
|
+
readonly errors?: ErrorFormat;
|
|
26
32
|
/** Where the document is served. Defaults to `/openapi.json`. */
|
|
27
33
|
readonly path?: Path & ValidatePath<Path>;
|
|
28
34
|
/** Where the page is served. Defaults to `/docs`. */
|
|
29
35
|
readonly uiPath?: UiPath & ValidatePath<UiPath>;
|
|
30
|
-
/**
|
|
31
|
-
|
|
36
|
+
/**
|
|
37
|
+
* Which renderer the page bootstraps. Defaults to `"scalar"`.
|
|
38
|
+
*
|
|
39
|
+
* `false` serves the document without a page — what production wants
|
|
40
|
+
* on an origin that carries a session. The page runs its renderer on the
|
|
41
|
+
* application's origin, so a renderer that is not what it should be acts
|
|
42
|
+
* in the signed-in user's name there; the document alone runs nothing.
|
|
43
|
+
* `uiPath`, `title` and `assets` are then unused.
|
|
44
|
+
*/
|
|
45
|
+
readonly ui?: Ui;
|
|
32
46
|
/** Browser title of the page. Defaults to the document's own title. */
|
|
33
47
|
readonly title?: string;
|
|
34
48
|
/** Overrides the renderer's CDN URLs, for a pinned or self-hosted copy. */
|
|
@@ -56,15 +70,24 @@ export interface DocsOptions<Path extends string, UiPath extends string> {
|
|
|
56
70
|
* Instantiated through the factory, never directly — the routes carry the
|
|
57
71
|
* configured paths in their types, which only the factory can infer.
|
|
58
72
|
*/
|
|
59
|
-
export declare class DocsController<Path extends string, UiPath extends string> {
|
|
73
|
+
export declare class DocsController<Path extends string, UiPath extends string, Page extends boolean = true> {
|
|
60
74
|
private readonly options;
|
|
61
75
|
/** The document itself, as `application/json`. */
|
|
62
76
|
readonly json: RouteDef<Path, SchemaConfig, never, undefined, "GET">;
|
|
63
|
-
/** The page that renders it
|
|
64
|
-
readonly ui: RouteDef<UiPath, SchemaConfig, never, undefined, "GET"
|
|
77
|
+
/** The page that renders it — none with `ui: false`. */
|
|
78
|
+
readonly ui: Page extends true ? RouteDef<UiPath, SchemaConfig, never, undefined, "GET"> : undefined;
|
|
65
79
|
private document;
|
|
66
|
-
|
|
67
|
-
|
|
80
|
+
private page;
|
|
81
|
+
constructor(options: DocsOptions<Path, UiPath>, documentPath: string, uiPath: string);
|
|
82
|
+
/**
|
|
83
|
+
* Generates the document of the application this was mounted in, and
|
|
84
|
+
* the page that renders it.
|
|
85
|
+
*
|
|
86
|
+
* The page is built here rather than with the routes because only the
|
|
87
|
+
* application knows where the document ended up: a group this controller
|
|
88
|
+
* is mounted under prefixes its path, and a page pointing at the path as
|
|
89
|
+
* configured would fetch a `404`.
|
|
90
|
+
*/
|
|
68
91
|
[onMount](app: App): void;
|
|
69
92
|
}
|
|
70
93
|
/**
|
|
@@ -87,5 +110,5 @@ export declare class DocsController<Path extends string, UiPath extends string>
|
|
|
87
110
|
* The page loads its renderer from a CDN; see {@link docsPage} for the
|
|
88
111
|
* trade and for how to point it at a self-hosted copy.
|
|
89
112
|
*/
|
|
90
|
-
export declare function docs<const Path extends string = "/openapi.json", const UiPath extends string = "/docs">(options: DocsOptions<Path, UiPath>): DocsController<Path, UiPath>;
|
|
113
|
+
export declare function docs<const Path extends string = "/openapi.json", const UiPath extends string = "/docs", const Ui extends DocsUi | false = "scalar">(options: DocsOptions<Path, UiPath, Ui>): DocsController<Path, UiPath, Ui extends false ? false : true>;
|
|
91
114
|
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAS,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGpD,gEAAgE;AAChE,MAAM,WAAW,WAAW,
|
|
1
|
+
{"version":3,"file":"controller.d.ts","sourceRoot":"","sources":["../../src/controller.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAS,MAAM,eAAe,CAAC;AAC/C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGpD,gEAAgE;AAChE,MAAM,WAAW,WAAW,CAC1B,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,EACrB,EAAE,SAAS,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK;IAE1C,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAEvC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAE9C;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAE9B,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC;IAE1C,qDAAqD;IACrD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IAEhD;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;IAEjB,uEAAuE;IACvE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAE7B;;;;;;OAMG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,OAAO,CAAC;IAEhC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;CAC1D;AAED;;;;;;GAMG;AACH,qBAAa,cAAc,CACzB,IAAI,SAAS,MAAM,EACnB,MAAM,SAAS,MAAM,EACrB,IAAI,SAAS,OAAO,GAAG,IAAI;IAezB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAb1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,IAAI,SAAS,IAAI,GAC1B,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,GACvD,SAAS,CAAC;IAEd,OAAO,CAAC,QAAQ,CAA8B;IAE9C,OAAO,CAAC,IAAI,CAAqB;IAEjC,YACmB,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EACnD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EAkCf;IAED;;;;;;;;OAQG;IACH,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CA2BxB;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,eAAe,EAC3C,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,OAAO,EACrC,KAAK,CAAC,EAAE,SAAS,MAAM,GAAG,KAAK,GAAG,QAAQ,EAE1C,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,GACrC,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,KAAK,GAAG,KAAK,GAAG,IAAI,CAAC,CAM/D"}
|
package/dist/src/document.d.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*
|
|
15
15
|
* @module
|
|
16
16
|
*/
|
|
17
|
+
import type { JsonSchema } from "./json-schema.ts";
|
|
17
18
|
/** Metadata every OpenAPI document must carry. */
|
|
18
19
|
export interface DocumentInfo {
|
|
19
20
|
readonly title: string;
|
|
@@ -38,9 +39,15 @@ export interface ParameterObject {
|
|
|
38
39
|
export type ContentMap = Record<string, {
|
|
39
40
|
readonly schema?: Record<string, unknown>;
|
|
40
41
|
}>;
|
|
42
|
+
/** A header a response carries. */
|
|
43
|
+
export interface HeaderObject {
|
|
44
|
+
readonly description?: string;
|
|
45
|
+
readonly schema: JsonSchema;
|
|
46
|
+
}
|
|
41
47
|
/** One response of an operation. */
|
|
42
48
|
export interface ResponseObject {
|
|
43
49
|
readonly description: string;
|
|
50
|
+
readonly headers?: Record<string, HeaderObject>;
|
|
44
51
|
readonly content?: ContentMap;
|
|
45
52
|
}
|
|
46
53
|
/** One endpoint: a method on a path. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,kDAAkD;AAClD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,wCAAwC;AACxC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACpD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,uDAAuD;AACvD,MAAM,MAAM,UAAU,GAAG,MAAM,CAC7B,MAAM,EACN;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAC9C,CAAC;AAEF,oCAAoC;AACpC,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;CAC/B;AAED,wCAAwC;AACxC,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,eAAe,EAAE,CAAC;IACjD,QAAQ,CAAC,WAAW,CAAC,EAAE;QACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QAC3B,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;KAC9B,CAAC;IACF,QAAQ,CAAC,QAAQ,CAAC,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,EAAE,CAAC;IACjE,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACpD;AAED,qDAAqD;AACrD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAE7D,8BAA8B;AAC9B,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,cAAc,EAAE,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAE/C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE;QACpB,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACnD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KAC5C,CAAC;IAEF,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC"}
|
|
1
|
+
{"version":3,"file":"document.d.ts","sourceRoot":"","sources":["../../src/document.ts"],"names":[],"mappings":"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"}
|
package/dist/src/emit.d.ts
CHANGED
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
* @module
|
|
7
7
|
*/
|
|
8
8
|
/**
|
|
9
|
-
* A JSON Schema as the generator reads it: any keyword, with the
|
|
9
|
+
* A JSON Schema as the generator reads it: any keyword, with the ones it
|
|
10
10
|
* looks into named, so reading them does not depend on the reader's
|
|
11
11
|
* `noPropertyAccessFromIndexSignature`.
|
|
12
12
|
*/
|
|
13
13
|
export interface JsonSchemaObject {
|
|
14
14
|
properties?: unknown;
|
|
15
15
|
required?: unknown;
|
|
16
|
+
anyOf?: unknown;
|
|
17
|
+
oneOf?: unknown;
|
|
16
18
|
[keyword: string]: unknown;
|
|
17
19
|
}
|
|
18
20
|
/** Which shape of a schema to describe: what arrives, or what leaves. */
|
package/dist/src/emit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,yEAAyE;AACzE,KAAK,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,SAAS,GAAE,SAAoB,GAC9B,gBAAgB,GAAG,SAAS,CAkB9B"}
|
|
1
|
+
{"version":3,"file":"emit.d.ts","sourceRoot":"","sources":["../../src/emit.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAeH;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CAC5B;AAED,yEAAyE;AACzE,KAAK,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEpC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,OAAO,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,EAC/B,SAAS,GAAE,SAAoB,GAC9B,gBAAgB,GAAG,SAAS,CAkB9B"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error envelopes, one definition per status and code.
|
|
3
|
+
*
|
|
4
|
+
* An envelope reaches a document from four directions: a route's response
|
|
5
|
+
* map, a hook's annotation, a guard's security requirement, and the
|
|
6
|
+
* framework's own failures. Each used to be described where it arrived —
|
|
7
|
+
* a route's inline, the rest by reference — so one `UNAUTHORIZED` could be
|
|
8
|
+
* listed twice under one status, once as a `$ref` and once as the same
|
|
9
|
+
* shape without a name, and a generated client got a named type next to an
|
|
10
|
+
* anonymous twin.
|
|
11
|
+
*
|
|
12
|
+
* Here every envelope, whoever declared it, becomes a reference to the
|
|
13
|
+
* one definition of its status and code. That is also what makes a status
|
|
14
|
+
* discriminable: a client can branch on `error` only if every code under
|
|
15
|
+
* the status has exactly one shape.
|
|
16
|
+
*
|
|
17
|
+
* The route's definition wins. It is the author's own contract — usually
|
|
18
|
+
* the stricter one, a validator's `additionalProperties: false` — where a
|
|
19
|
+
* hook's is the framework's generic envelope. The routes are therefore
|
|
20
|
+
* read first, before any operation is built, so the winner does not depend
|
|
21
|
+
* on which route happens to come first in the table.
|
|
22
|
+
*
|
|
23
|
+
* What an envelope looks like — how one is described, where its code is,
|
|
24
|
+
* what a client discriminates on — is the document's error format, which
|
|
25
|
+
* the registry carries for everything that builds or reads one.
|
|
26
|
+
*
|
|
27
|
+
* Internal to the package.
|
|
28
|
+
*
|
|
29
|
+
* @module
|
|
30
|
+
*/
|
|
31
|
+
import type { SchemaComponents, SchemaRef } from "./components.ts";
|
|
32
|
+
import type { JsonSchemaObject } from "./emit.ts";
|
|
33
|
+
import type { ResolvedFormat } from "./errors.ts";
|
|
34
|
+
/** The definitions of every envelope of one document. */
|
|
35
|
+
export interface Envelopes {
|
|
36
|
+
/** The document's error format. */
|
|
37
|
+
readonly format: ResolvedFormat;
|
|
38
|
+
/**
|
|
39
|
+
* Records a route's definition, before anything is referenced. The first
|
|
40
|
+
* route to declare a status and code defines it.
|
|
41
|
+
*/
|
|
42
|
+
declare(status: number, code: string, schema: JsonSchemaObject): void;
|
|
43
|
+
/**
|
|
44
|
+
* The reference to the definition of a status and code.
|
|
45
|
+
*
|
|
46
|
+
* `schema` is how the caller describes it. When a definition already
|
|
47
|
+
* exists, that is what is referenced; if the caller's differs from it
|
|
48
|
+
* in fields, `warn` hears about it — once per document, not once per
|
|
49
|
+
* operation the hook runs on.
|
|
50
|
+
*/
|
|
51
|
+
ref(status: number, code: string, schema: JsonSchemaObject, warn: (message: string) => void): SchemaRef;
|
|
52
|
+
/**
|
|
53
|
+
* The code a reference stands for, when it is one this handed out — what
|
|
54
|
+
* a discriminator maps back from.
|
|
55
|
+
*/
|
|
56
|
+
codeOf(ref: string): string | undefined;
|
|
57
|
+
}
|
|
58
|
+
export declare function envelopes(components: SchemaComponents, format: ResolvedFormat): Envelopes;
|
|
59
|
+
/**
|
|
60
|
+
* The alternatives a schema stands for, as a list the status's own union
|
|
61
|
+
* can take in.
|
|
62
|
+
*
|
|
63
|
+
* `anyOf` nested in `anyOf` says nothing its branches do not say side by
|
|
64
|
+
* side, so a schema that is only an `anyOf` is replaced by its branches.
|
|
65
|
+
* A `oneOf` is different — "exactly one" of two overlapping shapes refuses
|
|
66
|
+
* what both accept — and is taken apart only when its branches cannot
|
|
67
|
+
* overlap: envelopes with a different code each. A schema with anything
|
|
68
|
+
* beside its union, a `title` or a `$defs`, is left whole: those keywords
|
|
69
|
+
* belong to the union, and would be lost with it.
|
|
70
|
+
*/
|
|
71
|
+
export declare function branchesOf(schema: JsonSchemaObject, code: (schema: JsonSchemaObject) => string | undefined): JsonSchemaObject[];
|
|
72
|
+
//# sourceMappingURL=envelopes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../../src/envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAEtE;;;;;;;OAOG;IACH,GAAG,CACD,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,SAAS,CAAC;IAEb;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACzC;AAED,wBAAgB,SAAS,CACvB,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,cAAc,GACrB,SAAS,CAsEX;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,gBAAgB,EACxB,IAAI,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,MAAM,GAAG,SAAS,GACrD,gBAAgB,EAAE,CA0BpB"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape the document gives an error.
|
|
3
|
+
*
|
|
4
|
+
* Every failure an application answers with reaches its `onError` hooks,
|
|
5
|
+
* so one hook sets the format of all of them. The document is generated
|
|
6
|
+
* from the routes, not from that hook, and cannot see what it does: an
|
|
7
|
+
* application with a format of its own says so here too, and the framework's
|
|
8
|
+
* failures, the hooks' refusals and the routes' own envelopes are described
|
|
9
|
+
* in it. Keeping the two in step is the application's to do — the core knows
|
|
10
|
+
* nothing about documents, and a function cannot be read for the shape it
|
|
11
|
+
* returns.
|
|
12
|
+
*
|
|
13
|
+
* Without it the format is the framework's envelope,
|
|
14
|
+
* `{ status, message, error }`.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import type { JsonSchemaObject } from "./emit.ts";
|
|
19
|
+
import type { JsonSchema, JsonSchemaKeywords } from "./json-schema.ts";
|
|
20
|
+
/**
|
|
21
|
+
* One failure the document describes, as the generator knows it.
|
|
22
|
+
*
|
|
23
|
+
* This is everything known about a failure before it is answered: its
|
|
24
|
+
* status, its code when the framework or the hook knows it, an example of
|
|
25
|
+
* its message, and what else it carries — the `issues` of a validation
|
|
26
|
+
* failure, the `retryAfter` of a rate limit. Where each goes in the body
|
|
27
|
+
* is the format's to decide.
|
|
28
|
+
*/
|
|
29
|
+
export interface DocumentedFailure {
|
|
30
|
+
readonly status: number;
|
|
31
|
+
/** The code a client branches on, when it is known. */
|
|
32
|
+
readonly error?: string;
|
|
33
|
+
/** An example of the message; wording, never a contract. */
|
|
34
|
+
readonly message?: string;
|
|
35
|
+
/** What the failure carries besides, as JSON Schema by name. */
|
|
36
|
+
readonly fields: Readonly<Record<string, JsonSchema>>;
|
|
37
|
+
}
|
|
38
|
+
/** How the application's errors look, for the document to describe them. */
|
|
39
|
+
export interface ErrorFormat {
|
|
40
|
+
/** The body of one failure. */
|
|
41
|
+
readonly schema: (failure: DocumentedFailure) => JsonSchemaKeywords;
|
|
42
|
+
/**
|
|
43
|
+
* The top-level field that holds the code, for a client to discriminate
|
|
44
|
+
* on — `"code"` in `{ code, message }`. A status whose alternatives are
|
|
45
|
+
* all envelopes then carries a `discriminator` on it.
|
|
46
|
+
*
|
|
47
|
+
* None unless named: OpenAPI discriminates on a top-level field only, and
|
|
48
|
+
* a format that nests its code — `{ error: { code } }` — has none to
|
|
49
|
+
* name.
|
|
50
|
+
*/
|
|
51
|
+
readonly discriminator?: string;
|
|
52
|
+
/**
|
|
53
|
+
* The code of a schema a route or a hook declared, when it is an
|
|
54
|
+
* envelope in this format — how the document knows a route's own `404`
|
|
55
|
+
* is the same failure as a hook's, and gives both one definition.
|
|
56
|
+
*
|
|
57
|
+
* Read off the JSON Schema the validator emitted. By default, the
|
|
58
|
+
* `const` of the discriminator's field, when there is one; a nested
|
|
59
|
+
* format says where its code is.
|
|
60
|
+
*/
|
|
61
|
+
readonly code?: (schema: JsonSchemaKeywords) => string | undefined;
|
|
62
|
+
}
|
|
63
|
+
/** What the generator works with: every part of a format, filled in. */
|
|
64
|
+
export interface ResolvedFormat {
|
|
65
|
+
describe(failure: DocumentedFailure): JsonSchemaObject;
|
|
66
|
+
code(schema: JsonSchemaObject): string | undefined;
|
|
67
|
+
readonly discriminator: string | undefined;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A format as the generator uses it. Absent, the framework's envelope,
|
|
71
|
+
* discriminated on `error`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function resolveFormat(format: ErrorFormat | undefined): ResolvedFormat;
|
|
74
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAExB,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,4DAA4D;IAC5D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;CACvD;AAED,4EAA4E;AAC5E,MAAM,WAAW,WAAW;IAC1B,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,kBAAkB,CAAC;IAEpE;;;;;;;;OAQG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAEhC;;;;;;;;OAQG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;CACpE;AAED,wEAAwE;AACxE,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,iBAAiB,GAAG,gBAAgB,CAAC;IACvD,IAAI,CAAC,MAAM,EAAE,gBAAgB,GAAG,MAAM,GAAG,SAAS,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,SAAS,GAAG,cAAc,CAqB7E"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -20,11 +20,14 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import type { App } from "@tetsujs/core";
|
|
22
22
|
import type { OpenApiDocument } from "./document.ts";
|
|
23
|
-
|
|
23
|
+
import type { ErrorFormat } from "./errors.ts";
|
|
24
|
+
export type { DocumentedHeader, DocumentedResponse, HookContributions, HookDocs, SecurityAlternatives, SecurityRequirement, SecurityScheme, } from "./annotations.ts";
|
|
24
25
|
export { documented, secured } from "./annotations.ts";
|
|
25
26
|
export type { DocsOptions } from "./controller.ts";
|
|
26
27
|
export { DocsController, docs } from "./controller.ts";
|
|
27
|
-
export type { ContentMap, DocumentInfo, DocumentServer, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
|
|
28
|
+
export type { ContentMap, DocumentInfo, DocumentServer, HeaderObject, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
|
|
29
|
+
export type { DocumentedFailure, ErrorFormat } from "./errors.ts";
|
|
30
|
+
export type { JsonSchema, JsonSchemaKeywords, JsonSchemaType, } from "./json-schema.ts";
|
|
28
31
|
export type { DocsAssets, DocsPageOptions, DocsUi } from "./page.ts";
|
|
29
32
|
export { docsPage } from "./page.ts";
|
|
30
33
|
/**
|
|
@@ -35,6 +38,32 @@ export interface OpenApiOptions {
|
|
|
35
38
|
readonly info: OpenApiDocument["info"];
|
|
36
39
|
/** Servers the API is reachable at. */
|
|
37
40
|
readonly servers?: OpenApiDocument["servers"];
|
|
41
|
+
/**
|
|
42
|
+
* How the application's errors look, when not like the framework's
|
|
43
|
+
* envelope — for an application whose `onError` hook answers in a format
|
|
44
|
+
* of its own. The framework's failures, the hooks' refusals and the
|
|
45
|
+
* routes' own envelopes are then described in it.
|
|
46
|
+
*
|
|
47
|
+
* @example `{ code, message }`, discriminated on `code`
|
|
48
|
+
* ```ts
|
|
49
|
+
* openapi(app, {
|
|
50
|
+
* info,
|
|
51
|
+
* errors: {
|
|
52
|
+
* schema: ({ error, message, fields }) => ({
|
|
53
|
+
* type: "object",
|
|
54
|
+
* required: ["code", "message", ...Object.keys(fields)],
|
|
55
|
+
* properties: {
|
|
56
|
+
* code: error ? { type: "string", const: error } : { type: "string" },
|
|
57
|
+
* message: { type: "string", ...(message ? { examples: [message] } : {}) },
|
|
58
|
+
* ...fields,
|
|
59
|
+
* },
|
|
60
|
+
* }),
|
|
61
|
+
* discriminator: "code",
|
|
62
|
+
* },
|
|
63
|
+
* });
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
readonly errors?: ErrorFormat;
|
|
38
67
|
}
|
|
39
68
|
/**
|
|
40
69
|
* What the generator could not describe faithfully.
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAGzC,OAAO,KAAK,EAAE,eAAe,EAAkB,MAAM,eAAe,CAAC;AAErE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAK/C,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,EACjB,QAAQ,EACR,oBAAoB,EACpB,mBAAmB,EACnB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACvD,YAAY,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AACvD,YAAY,EACV,UAAU,EACV,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,eAAe,EACf,eAAe,EACf,eAAe,EACf,cAAc,EACd,cAAc,GACf,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAClE,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,cAAc,GACf,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACrE,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,CAAC;IAEvC,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAC;IAE9C;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAChD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,GAAG,eAAe,CA8F1E"}
|