@tetsujs/openapi 0.6.1 → 0.6.3
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 +8 -1
- package/dist/index-f49v8ege.js +28 -0
- package/dist/index-f49v8ege.js.map +10 -0
- package/dist/src/annotations.d.ts +97 -15
- package/dist/src/annotations.d.ts.map +1 -1
- package/dist/src/envelopes.d.ts +10 -8
- package/dist/src/envelopes.d.ts.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +151 -47
- package/dist/src/index.js.map +6 -6
- package/dist/src/media.d.ts +30 -0
- package/dist/src/media.d.ts.map +1 -0
- package/dist/src/operation.d.ts +5 -2
- package/dist/src/operation.d.ts.map +1 -1
- package/dist/src/testing.d.ts.map +1 -1
- package/dist/src/testing.js +23 -7
- package/dist/src/testing.js.map +3 -3
- package/package.json +3 -3
- package/src/annotations.ts +154 -25
- package/src/envelopes.ts +10 -8
- package/src/index.ts +17 -6
- package/src/media.ts +57 -0
- package/src/operation.ts +249 -71
- package/src/testing.ts +48 -9
package/README.md
CHANGED
|
@@ -17,7 +17,14 @@ createApp({
|
|
|
17
17
|
});
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
## Documentation
|
|
21
|
+
|
|
22
|
+
- [tetsujs.com/docs/packages/openapi](https://tetsujs.com/docs/packages/openapi/): options, details
|
|
23
|
+
and recipes, also [as Markdown](https://tetsujs.com/docs/packages/openapi.md)
|
|
24
|
+
- [llms-full.txt](https://tetsujs.com/llms-full.txt): the whole documentation in one
|
|
25
|
+
file, for tools and AI assistants
|
|
26
|
+
- every export and option is also documented in the package's type
|
|
27
|
+
definitions, which editors and tools read from `node_modules`
|
|
21
28
|
|
|
22
29
|
## License
|
|
23
30
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/openapi/src/media.ts
|
|
3
|
+
var part = /^[!#$%&'+.^_`|~0-9a-z-]+$/i;
|
|
4
|
+
function isMediaType(value) {
|
|
5
|
+
if (typeof value !== "string") {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
const [type, subtype, ...rest] = value.split("/");
|
|
9
|
+
if (type === undefined || subtype === undefined || rest.length > 0) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
if (type === "*") {
|
|
13
|
+
return subtype === "*";
|
|
14
|
+
}
|
|
15
|
+
return part.test(type) && (subtype === "*" || part.test(subtype));
|
|
16
|
+
}
|
|
17
|
+
function isJson(type) {
|
|
18
|
+
return type.toLowerCase() === "application/json";
|
|
19
|
+
}
|
|
20
|
+
function parsesAsJson(type) {
|
|
21
|
+
const lower = type.toLowerCase();
|
|
22
|
+
return lower === "application/json" || lower.endsWith("+json");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export { isMediaType, isJson, parsesAsJson };
|
|
26
|
+
|
|
27
|
+
//# debugId=E3642FF098E4375B64756E2164756E21
|
|
28
|
+
//# sourceMappingURL=index-f49v8ege.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/media.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * Media types, as the document's `content` keys them.\n *\n * The core checks a route's `contentType` when the route is declared; a\n * response annotated with `documented()` reaches the document without the\n * core seeing it, so the same rule is applied here, when it is annotated.\n *\n * @module\n */\n\n/** One part of a media type: HTTP's token characters, a range's star aside. */\nconst part = /^[!#$%&'+.^_`|~0-9a-z-]+$/i;\n\n/**\n * Whether a value is a media type or a range of them, as a document's\n * `content` names it: `text/csv`, `image/*`, the range of every type — and\n * no parameters, which a key would carry into every comparison with what a\n * response says.\n */\nexport function isMediaType(value: unknown): value is string {\n if (typeof value !== \"string\") {\n return false;\n }\n\n const [type, subtype, ...rest] = value.split(\"/\");\n\n if (type === undefined || subtype === undefined || rest.length > 0) {\n return false;\n }\n\n if (type === \"*\") {\n return subtype === \"*\";\n }\n\n return part.test(type) && (subtype === \"*\" || part.test(subtype));\n}\n\n/**\n * Whether a media type is the one a body is described as by default, and\n * the framework sends a returned value with. Compared without regard to\n * case, as media types are.\n */\nexport function isJson(type: string): boolean {\n return type.toLowerCase() === \"application/json\";\n}\n\n/**\n * Whether a body under this key is JSON to parse: `application/json`, or\n * a type with the `+json` suffix. A range is not — its schema describes\n * the bytes of whatever type answers — and neither is `x-ndjson`, which\n * is lines of JSON, not one value.\n */\nexport function parsesAsJson(type: string): boolean {\n const lower = type.toLowerCase();\n\n return lower === \"application/json\" || lower.endsWith(\"+json\");\n}\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";;AAWA,IAAM,OAAO;AAQN,SAAS,WAAW,CAAC,OAAiC;AAAA,EAC3D,IAAI,OAAO,UAAU,UAAU;AAAA,IAC7B,OAAO;AAAA,EACT;AAAA,EAEA,OAAO,MAAM,YAAY,QAAQ,MAAM,MAAM,GAAG;AAAA,EAEhD,IAAI,SAAS,aAAa,YAAY,aAAa,KAAK,SAAS,GAAG;AAAA,IAClE,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,SAAS,KAAK;AAAA,IAChB,OAAO,YAAY;AAAA,EACrB;AAAA,EAEA,OAAO,KAAK,KAAK,IAAI,MAAM,YAAY,OAAO,KAAK,KAAK,OAAO;AAAA;AAQ1D,SAAS,MAAM,CAAC,MAAuB;AAAA,EAC5C,OAAO,KAAK,YAAY,MAAM;AAAA;AASzB,SAAS,YAAY,CAAC,MAAuB;AAAA,EAClD,MAAM,QAAQ,KAAK,YAAY;AAAA,EAE/B,OAAO,UAAU,sBAAsB,MAAM,SAAS,OAAO;AAAA;",
|
|
8
|
+
"debugId": "E3642FF098E4375B64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* What hooks contribute to the
|
|
2
|
+
* What hooks, and the handlers packages hand out, contribute to the
|
|
3
|
+
* documentation.
|
|
3
4
|
*
|
|
4
5
|
* What a route is protected by, or answers with when a guard refuses, is
|
|
5
6
|
* not in its config: a hook is a function in a slot, and the route that
|
|
6
7
|
* mounts it says nothing about tokens or rate limits. The contribution is
|
|
7
8
|
* therefore annotated onto the hook — from this package, not from
|
|
8
9
|
* `hook.*` — so the core keeps knowing nothing about OpenAPI and the
|
|
9
|
-
* factories stay free of documentation options.
|
|
10
|
+
* factories stay free of documentation options. A package's handler is
|
|
11
|
+
* annotated the same way, for the routes that mount it.
|
|
10
12
|
*
|
|
11
13
|
* A hook annotated once is documented everywhere it runs: application and
|
|
12
14
|
* group chains are merged into every route's chains at startup, so a guard
|
|
@@ -53,13 +55,34 @@ export interface SecurityRequirement {
|
|
|
53
55
|
}
|
|
54
56
|
/**
|
|
55
57
|
* One response a hook can produce, which the route's own schema knows
|
|
56
|
-
* nothing about — a rate limiter's `429`, a guard's `401
|
|
58
|
+
* nothing about — a rate limiter's `429`, a guard's `401` — or one a
|
|
59
|
+
* package's handler answers with, for the routes that mount it: a file,
|
|
60
|
+
* a `304`.
|
|
57
61
|
*/
|
|
58
62
|
export interface DocumentedResponse {
|
|
59
63
|
readonly status: number;
|
|
60
64
|
readonly description: string;
|
|
61
|
-
/**
|
|
65
|
+
/**
|
|
66
|
+
* The body, when it is described: JSON, or the content under
|
|
67
|
+
* `contentType` when there is one.
|
|
68
|
+
*
|
|
69
|
+
* Without `schema` or `contentType`, an error status — `400` and above —
|
|
70
|
+
* answers with the framework's envelope, which `error`, `message` and
|
|
71
|
+
* `fields` describe, and any other status carries no body: a `304`, a
|
|
72
|
+
* redirect.
|
|
73
|
+
*/
|
|
62
74
|
readonly schema?: AnySchema;
|
|
75
|
+
/**
|
|
76
|
+
* The media type of the body when it is not JSON — `"text/html"`, or a
|
|
77
|
+
* range such as `"image/*"` or the range of every type for a file whose
|
|
78
|
+
* type is not known in advance. Without a `schema`, the body is
|
|
79
|
+
* described by its type alone.
|
|
80
|
+
*
|
|
81
|
+
* Two responses of one status with different types are both in the
|
|
82
|
+
* document, each under its own: the site's `404.html` next to the
|
|
83
|
+
* envelope an API client gets.
|
|
84
|
+
*/
|
|
85
|
+
readonly contentType?: string;
|
|
63
86
|
/**
|
|
64
87
|
* The `error` code of the failure envelope, when the hook answers with
|
|
65
88
|
* the framework's shape — `"RATE_LIMITED"`, `"SESSION_EXPIRED"`.
|
|
@@ -134,14 +157,58 @@ export interface HookDocs {
|
|
|
134
157
|
readonly responses?: readonly DocumentedResponse[];
|
|
135
158
|
}
|
|
136
159
|
/**
|
|
137
|
-
*
|
|
160
|
+
* Everything a handler tells the generator about the route it answers
|
|
161
|
+
* for.
|
|
162
|
+
*
|
|
163
|
+
* A handler a package hands out — a directory of files, say — knows what
|
|
164
|
+
* it answers with better than the route it is mounted on, whose author
|
|
165
|
+
* wrote one line: annotated once, it describes every route that mounts it.
|
|
166
|
+
*/
|
|
167
|
+
export interface HandlerDocs {
|
|
168
|
+
/**
|
|
169
|
+
* Keeps the route out of the document unless the route says
|
|
170
|
+
* `docs: { hidden: false }`.
|
|
171
|
+
*
|
|
172
|
+
* For a handler whose routes are rarely anyone's API — the files of a
|
|
173
|
+
* site — and which a generated client could not call anyway. The route's
|
|
174
|
+
* own word wins either way: `docs: { hidden: true }` hides it whatever
|
|
175
|
+
* the handler says.
|
|
176
|
+
*/
|
|
177
|
+
readonly hidden?: boolean;
|
|
178
|
+
/**
|
|
179
|
+
* What the handler answers with, as the route's own responses: a status
|
|
180
|
+
* the route's response map declares too is described by both, and a
|
|
181
|
+
* route that says nothing gets no placeholder `200` in their place.
|
|
182
|
+
*/
|
|
183
|
+
readonly responses?: readonly DocumentedResponse[];
|
|
184
|
+
}
|
|
185
|
+
/** Any handler a route can take; the context is the route's to type. */
|
|
186
|
+
type AnyHandler = (ctx: never) => unknown;
|
|
187
|
+
/**
|
|
188
|
+
* Annotates a hook, or a handler, with what it contributes to the
|
|
189
|
+
* documentation.
|
|
138
190
|
*
|
|
139
191
|
* Returns a copy: what the caller exports is what carries the annotation,
|
|
140
|
-
* and a hook used elsewhere is not changed behind its
|
|
141
|
-
*
|
|
142
|
-
*
|
|
192
|
+
* and a hook or a handler used elsewhere is not changed behind its
|
|
193
|
+
* author's back. A handler's copy calls the handler it was made from. The
|
|
194
|
+
* type is preserved exactly, so an annotated hook goes into a stack, and an
|
|
195
|
+
* annotated handler onto a route, like any other.
|
|
143
196
|
*
|
|
144
|
-
*
|
|
197
|
+
* Annotating a handler is for one whose type is already settled — a
|
|
198
|
+
* package's, typed by its own context. An arrow written inside
|
|
199
|
+
* `documented()` on a route does not get the route's context, which
|
|
200
|
+
* `route()` would have given it, and does not compile; its statuses belong
|
|
201
|
+
* in the route's response map.
|
|
202
|
+
*
|
|
203
|
+
* One signature, for a hook and a handler alike, and one shape of what is
|
|
204
|
+
* said about either. An overload for each would keep `hidden` off a hook
|
|
205
|
+
* and `security` off a handler in the types, but TypeScript before 7
|
|
206
|
+
* reports a call no overload matches on the whole call, and a misspelled
|
|
207
|
+
* keyword deep in a response would no longer be pointed at. Those two are
|
|
208
|
+
* refused here instead, when `documented()` is called — as a module loads,
|
|
209
|
+
* before anything is served.
|
|
210
|
+
*
|
|
211
|
+
* @example A hook
|
|
145
212
|
* ```ts
|
|
146
213
|
* export const limiter = documented(hook.beforeParse(check), {
|
|
147
214
|
* responses: [
|
|
@@ -149,8 +216,19 @@ export interface HookDocs {
|
|
|
149
216
|
* ],
|
|
150
217
|
* });
|
|
151
218
|
* ```
|
|
219
|
+
*
|
|
220
|
+
* @example A package's handler, out of the document until a route asks
|
|
221
|
+
* ```ts
|
|
222
|
+
* return documented(serveFile, {
|
|
223
|
+
* hidden: true,
|
|
224
|
+
* responses: [
|
|
225
|
+
* { status: 200, description: "The file", contentType: "image/*" },
|
|
226
|
+
* { status: 304, description: "Not modified" },
|
|
227
|
+
* ],
|
|
228
|
+
* });
|
|
229
|
+
* ```
|
|
152
230
|
*/
|
|
153
|
-
export declare function documented<
|
|
231
|
+
export declare function documented<T extends AnyHook | AnyHandler>(target: T, docs: HookDocs & HandlerDocs): T;
|
|
154
232
|
/**
|
|
155
233
|
* Annotates a hook with the security it enforces — {@link documented} for
|
|
156
234
|
* the case that comes up most.
|
|
@@ -189,6 +267,8 @@ export declare function documented<H extends AnyHook>(hook: H, docs: HookDocs):
|
|
|
189
267
|
export declare function secured<H extends AnyHook>(hook: H, requirement: SecurityRequirement | SecurityAlternatives): H;
|
|
190
268
|
/** Reads everything a hook was annotated with. */
|
|
191
269
|
export declare function docsOf(hook: AnyHook): HookDocs | undefined;
|
|
270
|
+
/** Reads everything a handler was annotated with. */
|
|
271
|
+
export declare function handlerDocsOf(handler: unknown): HandlerDocs | undefined;
|
|
192
272
|
/** Reads the security a hook was annotated with, if it was. */
|
|
193
273
|
export declare function securityOf(hook: AnyHook): SecurityRequirement | SecurityAlternatives | undefined;
|
|
194
274
|
/** What the hooks of one route contribute, in execution order. */
|
|
@@ -209,11 +289,12 @@ export interface HookContributions {
|
|
|
209
289
|
/**
|
|
210
290
|
* Collects the contributions of a route's chains.
|
|
211
291
|
*
|
|
212
|
-
* A scheme is registered once per name
|
|
213
|
-
*
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
292
|
+
* A scheme is registered once per name: two hooks of one scheme asking for
|
|
293
|
+
* different scopes both run, so the requirement asks for the scopes of
|
|
294
|
+
* both — each once, in the order they were first asked for. Responses are
|
|
295
|
+
* kept as the hooks give them. Two guards may answer one status with one
|
|
296
|
+
* description and different codes, and both are what the route sends;
|
|
297
|
+
* answers that say the same are folded when the document is written.
|
|
217
298
|
*
|
|
218
299
|
* Deduplicating by name means two hooks claiming one name with different
|
|
219
300
|
* schemes lose one of them, and that is not a duplicate being collapsed —
|
|
@@ -221,4 +302,5 @@ export interface HookContributions {
|
|
|
221
302
|
* `warn` is how the caller hears about it; the generator does.
|
|
222
303
|
*/
|
|
223
304
|
export declare function contributionsOf(chains: Record<string, readonly AnyHook[]>, warn?: (message: string) => void): HookContributions;
|
|
305
|
+
export {};
|
|
224
306
|
//# sourceMappingURL=annotations.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/annotations.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"annotations.d.ts","sourceRoot":"","sources":["../../src/annotations.ts"],"names":[],"mappings":"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"}
|
package/dist/src/envelopes.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Error envelopes, one definition per status and code.
|
|
3
3
|
*
|
|
4
|
-
* An envelope reaches a document from
|
|
5
|
-
* map, a hook's annotation, a guard's security
|
|
6
|
-
* framework's own failures. Each used to be described
|
|
4
|
+
* An envelope reaches a document from five directions: a route's response
|
|
5
|
+
* map, its handler's annotation, a hook's annotation, a guard's security
|
|
6
|
+
* requirement, and the framework's own failures. Each used to be described
|
|
7
|
+
* where it arrived —
|
|
7
8
|
* a route's inline, the rest by reference — so one `UNAUTHORIZED` could be
|
|
8
9
|
* listed twice under one status, once as a `$ref` and once as the same
|
|
9
10
|
* shape without a name, and a generated client got a named type next to an
|
|
@@ -14,11 +15,12 @@
|
|
|
14
15
|
* discriminable: a client can branch on `error` only if every code under
|
|
15
16
|
* the status has exactly one shape.
|
|
16
17
|
*
|
|
17
|
-
* The route's definition wins
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* The route's definition wins, its handler's annotation counting as the
|
|
19
|
+
* route's own. It is the author's own contract — usually the stricter one,
|
|
20
|
+
* a validator's `additionalProperties: false` — where a hook's is the
|
|
21
|
+
* framework's generic envelope. The routes are therefore read first,
|
|
22
|
+
* before any operation is built, so the winner does not depend on which
|
|
23
|
+
* route happens to come first in the table.
|
|
22
24
|
*
|
|
23
25
|
* What an envelope looks like — how one is described, where its code is,
|
|
24
26
|
* what a client discriminates on — is the document's error format, which
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../../src/envelopes.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"envelopes.d.ts","sourceRoot":"","sources":["../../src/envelopes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;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"}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
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
|
+
export type { DocumentedHeader, DocumentedResponse, HandlerDocs, HookContributions, HookDocs, SecurityAlternatives, SecurityRequirement, SecurityScheme, } from "./annotations.ts";
|
|
25
25
|
export { documented, secured } from "./annotations.ts";
|
|
26
26
|
export type { DocsOptions } from "./controller.ts";
|
|
27
27
|
export { DocsController, docs } from "./controller.ts";
|
|
@@ -118,7 +118,9 @@ export interface GeneratorResult {
|
|
|
118
118
|
*
|
|
119
119
|
* So are routes marked `docs: { hidden: true }`, and everything they would
|
|
120
120
|
* have contributed with them: a security scheme only a hidden route
|
|
121
|
-
* requires has nothing left to describe.
|
|
121
|
+
* requires has nothing left to describe. A route that says nothing either
|
|
122
|
+
* way is hidden when its handler was annotated so — see {@link documented}
|
|
123
|
+
* — and `docs: { hidden: false }` shows it all the same.
|
|
122
124
|
*
|
|
123
125
|
* Every route of the table becomes an operation under its path template,
|
|
124
126
|
* with the parameters, body and responses its schemas describe. What the
|
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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAmB,MAAM,eAAe,CAAC;AAG1D,OAAO,KAAK,EAAE,eAAe,EAA6B,MAAM,eAAe,CAAC;AAEhF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAM/C,YAAY,EACV,gBAAgB,EAChB,kBAAkB,EAClB,WAAW,EACX,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,EACd,SAAS,GACV,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;IAE9B;;;;;;;;;;;;;;;;;;;;OAoBG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,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;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,GAAG,eAAe,CAyG1E"}
|
package/dist/src/index.js
CHANGED
|
@@ -1,7 +1,35 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
import {
|
|
3
|
+
isMediaType,
|
|
4
|
+
isJson
|
|
5
|
+
} from "../index-f49v8ege.js";
|
|
6
|
+
|
|
2
7
|
// packages/openapi/src/annotations.ts
|
|
3
8
|
var docsKey = "~tetsu/hook-docs";
|
|
4
|
-
|
|
9
|
+
var handlerDocsKey = "~tetsu/handler-docs";
|
|
10
|
+
function documented(target, docs) {
|
|
11
|
+
const handled = typeof target === "function";
|
|
12
|
+
if (handled && docs.security !== undefined) {
|
|
13
|
+
throw new Error("documented() was given security for a handler \u2014 security is stated on the hook that enforces it, with secured()");
|
|
14
|
+
}
|
|
15
|
+
if (!handled && docs.hidden !== undefined) {
|
|
16
|
+
throw new Error("documented() was given hidden for a hook \u2014 a route is hidden by its own docs, or by its handler's annotation, not by a hook it mounts");
|
|
17
|
+
}
|
|
18
|
+
for (const response of docs.responses ?? []) {
|
|
19
|
+
if (response.contentType !== undefined && !isMediaType(response.contentType)) {
|
|
20
|
+
throw new Error(`The ${response.status} response given to documented() has the content type ${JSON.stringify(response.contentType)}, which is not a type such as text/html or a range such as image/* \u2014 the type alone, without parameters: the charset belongs on the response`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (handled) {
|
|
24
|
+
const handler = target;
|
|
25
|
+
const copy = (ctx) => handler(ctx);
|
|
26
|
+
Object.defineProperty(copy, handlerDocsKey, {
|
|
27
|
+
enumerable: false,
|
|
28
|
+
value: { ...handlerDocsOf(handler), ...docs }
|
|
29
|
+
});
|
|
30
|
+
return copy;
|
|
31
|
+
}
|
|
32
|
+
const hook = target;
|
|
5
33
|
const annotated = { ...hook };
|
|
6
34
|
Object.defineProperty(annotated, docsKey, {
|
|
7
35
|
enumerable: false,
|
|
@@ -15,10 +43,13 @@ function secured(hook, requirement) {
|
|
|
15
43
|
function docsOf(hook) {
|
|
16
44
|
return hook[docsKey];
|
|
17
45
|
}
|
|
46
|
+
function handlerDocsOf(handler) {
|
|
47
|
+
return typeof handler === "function" ? handler[handlerDocsKey] : undefined;
|
|
48
|
+
}
|
|
18
49
|
function contributionsOf(chains, warn) {
|
|
19
50
|
const security = new Map;
|
|
20
51
|
const conditions = new Map;
|
|
21
|
-
const responses =
|
|
52
|
+
const responses = [];
|
|
22
53
|
for (const slot of Object.values(chains)) {
|
|
23
54
|
for (const hook of slot) {
|
|
24
55
|
const docs = docsOf(hook);
|
|
@@ -42,18 +73,13 @@ function contributionsOf(chains, warn) {
|
|
|
42
73
|
}
|
|
43
74
|
}
|
|
44
75
|
}
|
|
45
|
-
|
|
46
|
-
const key = `${response.status} ${response.description}`;
|
|
47
|
-
if (!responses.has(key)) {
|
|
48
|
-
responses.set(key, response);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
76
|
+
responses.push(...docs.responses ?? []);
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
79
|
return {
|
|
54
80
|
security: [...security.values()],
|
|
55
81
|
conditions: [...conditions.values()],
|
|
56
|
-
responses
|
|
82
|
+
responses
|
|
57
83
|
};
|
|
58
84
|
}
|
|
59
85
|
function alternativesOf(security) {
|
|
@@ -373,13 +399,25 @@ function operationOf(entry, options, components, envelopes, ids, warn) {
|
|
|
373
399
|
};
|
|
374
400
|
}
|
|
375
401
|
function declareEnvelopes(entry, envelopes) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
402
|
+
const own = [];
|
|
403
|
+
for (const [status, { body, contentType }] of declaredResponses(entry)) {
|
|
404
|
+
if (body !== null && (contentType === undefined || isJson(contentType))) {
|
|
405
|
+
own.push([Number(status), body]);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
for (const response of handlerDocsOf(entry.def.handler)?.responses ?? []) {
|
|
409
|
+
const { contentType, schema, status } = response;
|
|
410
|
+
if (schema && (contentType === undefined || isJson(contentType))) {
|
|
411
|
+
own.push([status, schema]);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
const code = envelopes.format.code;
|
|
415
|
+
for (const [status, body] of own) {
|
|
416
|
+
const described = emitted(body, "", () => {});
|
|
379
417
|
for (const branch of described ? branchesOf(described, code) : []) {
|
|
380
418
|
const found = code(branch);
|
|
381
419
|
if (found !== undefined) {
|
|
382
|
-
envelopes.declare(
|
|
420
|
+
envelopes.declare(status, found, branch);
|
|
383
421
|
}
|
|
384
422
|
}
|
|
385
423
|
}
|
|
@@ -404,7 +442,8 @@ function asDeclared(value) {
|
|
|
404
442
|
return {
|
|
405
443
|
body: value.body ?? null,
|
|
406
444
|
...value.headers === undefined ? {} : { headers: value.headers },
|
|
407
|
-
...value.cookies === undefined ? {} : { cookies: value.cookies }
|
|
445
|
+
...value.cookies === undefined ? {} : { cookies: value.cookies },
|
|
446
|
+
...value.contentType === undefined ? {} : { contentType: value.contentType }
|
|
408
447
|
};
|
|
409
448
|
}
|
|
410
449
|
function declaredHeaders(declared, warn) {
|
|
@@ -562,17 +601,24 @@ function responses(entry, options, contributed, components, envelopes, warn) {
|
|
|
562
601
|
answers.set(status, [...answers.get(status) ?? [], answer]);
|
|
563
602
|
};
|
|
564
603
|
const declared = declaredResponses(entry);
|
|
565
|
-
for (const [status, { body, ...rest }] of declared) {
|
|
604
|
+
for (const [status, { body, contentType, ...rest }] of declared) {
|
|
566
605
|
const described = body === null ? undefined : emitted(body, "a response", warn);
|
|
567
606
|
const headers = declaredHeaders({ body, ...rest }, warn);
|
|
607
|
+
const own = contentType !== undefined && !isJson(contentType);
|
|
568
608
|
add(status, {
|
|
569
609
|
placeholder: describeStatus(status),
|
|
570
610
|
said: described ? wordsOf(described, envelopes) : [],
|
|
571
|
-
schemas: described ? branchesFor(unionOnly(described), Number(status), envelopes, warn) : [],
|
|
611
|
+
schemas: described && !own ? branchesFor(unionOnly(described), Number(status), envelopes, warn) : [],
|
|
612
|
+
...own ? { media: [mediaOf(contentType, described)] } : {},
|
|
572
613
|
...headers === undefined ? {} : { headers }
|
|
573
614
|
});
|
|
574
615
|
}
|
|
575
|
-
|
|
616
|
+
const handled = handlerDocsOf(entry.def.handler)?.responses ?? [];
|
|
617
|
+
const envelopeRef = envelopeRefs(components, envelopes, warn);
|
|
618
|
+
for (const response of handled) {
|
|
619
|
+
add(String(response.status), documentedAnswer(response, "a handler's response", envelopeRef, envelopes, warn));
|
|
620
|
+
}
|
|
621
|
+
if (declared.length === 0 && handled.length === 0) {
|
|
576
622
|
add("200", { placeholder: "Successful response", said: [], schemas: [] });
|
|
577
623
|
}
|
|
578
624
|
for (const [status, answer] of failures(entry, options, contributed, components, envelopes, warn)) {
|
|
@@ -587,16 +633,7 @@ function responses(entry, options, contributed, components, envelopes, warn) {
|
|
|
587
633
|
function failures(entry, options, contributed, components, envelopes, warn) {
|
|
588
634
|
const schema = entry.def.schema;
|
|
589
635
|
const found = [];
|
|
590
|
-
const envelopeRef = (
|
|
591
|
-
const described = envelopes.format.describe({
|
|
592
|
-
status,
|
|
593
|
-
...error === undefined ? {} : { error },
|
|
594
|
-
...message === undefined ? {} : { message },
|
|
595
|
-
fields: fields ?? {}
|
|
596
|
-
});
|
|
597
|
-
const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
|
|
598
|
-
return ref;
|
|
599
|
-
};
|
|
636
|
+
const envelopeRef = envelopeRefs(components, envelopes, warn);
|
|
600
637
|
if (schema?.params || schema?.query || schema?.headers || schema?.cookies || schema?.body) {
|
|
601
638
|
found.push([
|
|
602
639
|
options.validationStatus,
|
|
@@ -626,21 +663,14 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
626
663
|
said: [
|
|
627
664
|
said(requirement.error, requirement.description ?? `Request did not satisfy ${requirement.name}`)
|
|
628
665
|
],
|
|
629
|
-
schemas: [envelopeRef(status, requirement.error, requirement.message)]
|
|
666
|
+
schemas: status >= 400 ? [envelopeRef(status, requirement.error, requirement.message)] : []
|
|
630
667
|
}
|
|
631
668
|
]);
|
|
632
669
|
}
|
|
633
670
|
for (const response of contributed.responses) {
|
|
634
|
-
const described = response.schema ? emitted(response.schema, "a hook's response", warn) : undefined;
|
|
635
671
|
found.push([
|
|
636
672
|
response.status,
|
|
637
|
-
|
|
638
|
-
said: [said(response.error, response.description)],
|
|
639
|
-
schemas: described ? branchesFor(described, response.status, envelopes, warn) : [
|
|
640
|
-
envelopeRef(response.status, response.error, response.message, response.fields)
|
|
641
|
-
],
|
|
642
|
-
...response.headers ? { headers: response.headers } : {}
|
|
643
|
-
}
|
|
673
|
+
documentedAnswer(response, "a hook's response", envelopeRef, envelopes, warn)
|
|
644
674
|
]);
|
|
645
675
|
}
|
|
646
676
|
if (schema?.body || entry.def.bodyType || entry.def.rawBody) {
|
|
@@ -681,6 +711,47 @@ function failures(entry, options, contributed, components, envelopes, warn) {
|
|
|
681
711
|
]);
|
|
682
712
|
return found;
|
|
683
713
|
}
|
|
714
|
+
function envelopeRefs(components, envelopes, warn) {
|
|
715
|
+
return (status, error, message, fields) => {
|
|
716
|
+
const described = envelopes.format.describe({
|
|
717
|
+
status,
|
|
718
|
+
...error === undefined ? {} : { error },
|
|
719
|
+
...message === undefined ? {} : { message },
|
|
720
|
+
fields: fields ?? {}
|
|
721
|
+
});
|
|
722
|
+
const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
|
|
723
|
+
return ref;
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
function documentedAnswer(response, subject, envelopeRef, envelopes, warn) {
|
|
727
|
+
const described = response.schema ? emitted(response.schema, subject, warn) : undefined;
|
|
728
|
+
const answer = {
|
|
729
|
+
said: [said(response.error, response.description)],
|
|
730
|
+
...response.headers ? { headers: response.headers } : {}
|
|
731
|
+
};
|
|
732
|
+
if (response.contentType !== undefined && !isJson(response.contentType)) {
|
|
733
|
+
return {
|
|
734
|
+
...answer,
|
|
735
|
+
schemas: [],
|
|
736
|
+
media: [mediaOf(response.contentType, described)]
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
if (described) {
|
|
740
|
+
return {
|
|
741
|
+
...answer,
|
|
742
|
+
schemas: branchesFor(described, response.status, envelopes, warn)
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
return {
|
|
746
|
+
...answer,
|
|
747
|
+
schemas: response.status >= 400 ? [
|
|
748
|
+
envelopeRef(response.status, response.error, response.message, response.fields)
|
|
749
|
+
] : []
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
function mediaOf(type, schema) {
|
|
753
|
+
return schema === undefined ? { type } : { type, schema };
|
|
754
|
+
}
|
|
684
755
|
function said(code, text) {
|
|
685
756
|
return code === undefined ? { text } : { code, text };
|
|
686
757
|
}
|
|
@@ -700,19 +771,33 @@ function unionOnly(schema) {
|
|
|
700
771
|
function merge(list, envelopes) {
|
|
701
772
|
const schemas = distinct(list.flatMap((answer) => answer.schemas));
|
|
702
773
|
const headers = {};
|
|
774
|
+
const media = new Map;
|
|
775
|
+
const unconstrained = new Set;
|
|
703
776
|
for (const answer of list) {
|
|
704
777
|
for (const [name, header] of Object.entries(answer.headers ?? {})) {
|
|
705
778
|
headers[name] ??= header;
|
|
706
779
|
}
|
|
780
|
+
for (const { type, schema } of answer.media ?? []) {
|
|
781
|
+
const key = type.toLowerCase();
|
|
782
|
+
const alternatives = media.get(key) ?? [];
|
|
783
|
+
media.set(key, schema ? [...alternatives, schema] : alternatives);
|
|
784
|
+
if (!schema) {
|
|
785
|
+
unconstrained.add(key);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
const content = {};
|
|
790
|
+
if (schemas.length > 0) {
|
|
791
|
+
content["application/json"] = { schema: unionOf(schemas, envelopes) };
|
|
792
|
+
}
|
|
793
|
+
for (const [type, alternatives] of media) {
|
|
794
|
+
const [only, ...others] = distinct(alternatives);
|
|
795
|
+
content[type] = only === undefined || unconstrained.has(type) ? {} : { schema: others.length === 0 ? only : { anyOf: [only, ...others] } };
|
|
707
796
|
}
|
|
708
797
|
return {
|
|
709
798
|
description: wording(list),
|
|
710
799
|
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
711
|
-
...
|
|
712
|
-
content: {
|
|
713
|
-
"application/json": { schema: unionOf(schemas, envelopes) }
|
|
714
|
-
}
|
|
715
|
-
} : {}
|
|
800
|
+
...Object.keys(content).length > 0 ? { content } : {}
|
|
716
801
|
};
|
|
717
802
|
}
|
|
718
803
|
function wording(list) {
|
|
@@ -728,12 +813,14 @@ ${item.text}`;
|
|
|
728
813
|
}
|
|
729
814
|
}
|
|
730
815
|
}
|
|
731
|
-
const [
|
|
732
|
-
|
|
816
|
+
const coded = new Set(items.flatMap((item) => item.code === undefined ? [] : [item.text]));
|
|
817
|
+
const said = items.filter((item) => item.code !== undefined || !coded.has(item.text));
|
|
818
|
+
const [only] = said;
|
|
819
|
+
if (said.length === 1 && only) {
|
|
733
820
|
return only.text;
|
|
734
821
|
}
|
|
735
|
-
if (
|
|
736
|
-
return
|
|
822
|
+
if (said.length > 1) {
|
|
823
|
+
return said.map((item) => item.code === undefined ? `- ${item.text}` : `- \`${item.code}\`: ${item.text}`).join(`
|
|
737
824
|
`);
|
|
738
825
|
}
|
|
739
826
|
return list.find((answer) => answer.placeholder)?.placeholder ?? "";
|
|
@@ -776,8 +863,22 @@ function describeStatus(status) {
|
|
|
776
863
|
if (status === "204") {
|
|
777
864
|
return "No content";
|
|
778
865
|
}
|
|
866
|
+
if (status === "default") {
|
|
867
|
+
return "Any other response";
|
|
868
|
+
}
|
|
869
|
+
const range = ranges[status.toUpperCase()];
|
|
870
|
+
if (range !== undefined) {
|
|
871
|
+
return range;
|
|
872
|
+
}
|
|
779
873
|
return status.startsWith("2") ? "Successful response" : errorBody(Number(status)).message;
|
|
780
874
|
}
|
|
875
|
+
var ranges = {
|
|
876
|
+
"1XX": "Informational response",
|
|
877
|
+
"2XX": "Successful response",
|
|
878
|
+
"3XX": "Redirection",
|
|
879
|
+
"4XX": "Client error",
|
|
880
|
+
"5XX": "Server error"
|
|
881
|
+
};
|
|
781
882
|
var issues = {
|
|
782
883
|
type: "array",
|
|
783
884
|
items: {
|
|
@@ -1007,7 +1108,7 @@ function openapi(app, options) {
|
|
|
1007
1108
|
const components = schemaComponents();
|
|
1008
1109
|
const envelopes2 = envelopes(components, resolveFormat(options.errors));
|
|
1009
1110
|
const ids = operationIds();
|
|
1010
|
-
const described = app.entries.filter((entry) => !entry.ws && !entry
|
|
1111
|
+
const described = app.entries.filter((entry) => !entry.ws && !hidden(entry));
|
|
1011
1112
|
for (const entry of described) {
|
|
1012
1113
|
declareEnvelopes(entry, envelopes2);
|
|
1013
1114
|
}
|
|
@@ -1063,6 +1164,9 @@ function openapi(app, options) {
|
|
|
1063
1164
|
refsOf(document, occupied, warnings);
|
|
1064
1165
|
return { document, warnings };
|
|
1065
1166
|
}
|
|
1167
|
+
function hidden(entry) {
|
|
1168
|
+
return entry.def.docs?.hidden ?? handlerDocsOf(entry.def.handler)?.hidden ?? false;
|
|
1169
|
+
}
|
|
1066
1170
|
function refsOf(document, routes, warnings) {
|
|
1067
1171
|
const unresolved = (ref) => `refers to "${ref}", which is no schema in the document: references are embedded as they are, and resolve against the document's root`;
|
|
1068
1172
|
for (const [template, item] of Object.entries(document.paths)) {
|
|
@@ -1121,5 +1225,5 @@ export {
|
|
|
1121
1225
|
secured
|
|
1122
1226
|
};
|
|
1123
1227
|
|
|
1124
|
-
//# debugId=
|
|
1228
|
+
//# debugId=94105F31CF41593E64756E2164756E21
|
|
1125
1229
|
//# sourceMappingURL=index.js.map
|