@tetsujs/openapi 0.1.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 +146 -0
- package/dist/src/annotations.d.ts +141 -0
- package/dist/src/annotations.d.ts.map +1 -0
- package/dist/src/components.d.ts +45 -0
- package/dist/src/components.d.ts.map +1 -0
- package/dist/src/controller.d.ts +91 -0
- package/dist/src/controller.d.ts.map +1 -0
- package/dist/src/document.d.ts +80 -0
- package/dist/src/document.d.ts.map +1 -0
- package/dist/src/emit.d.ts +40 -0
- package/dist/src/emit.d.ts.map +1 -0
- package/dist/src/index.d.ts +75 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +616 -0
- package/dist/src/index.js.map +17 -0
- package/dist/src/operation.d.ts +39 -0
- package/dist/src/operation.d.ts.map +1 -0
- package/dist/src/page.d.ts +54 -0
- package/dist/src/page.d.ts.map +1 -0
- package/dist/src/parameters.d.ts +47 -0
- package/dist/src/parameters.d.ts.map +1 -0
- package/package.json +52 -0
- package/src/annotations.ts +225 -0
- package/src/components.ts +90 -0
- package/src/controller.ts +175 -0
- package/src/document.ts +90 -0
- package/src/emit.ts +98 -0
- package/src/index.ts +203 -0
- package/src/operation.ts +583 -0
- package/src/page.ts +139 -0
- package/src/parameters.ts +145 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tetsuodev
|
|
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
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# @tetsujs/openapi
|
|
2
|
+
|
|
3
|
+
An OpenAPI 3.1 document and a docs page, generated from the routes you
|
|
4
|
+
already declared.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
bun add @tetsujs/openapi
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
Mount the `docs()` controller next to your own:
|
|
13
|
+
|
|
14
|
+
```ts
|
|
15
|
+
import { docs } from "@tetsujs/openapi";
|
|
16
|
+
|
|
17
|
+
createApp({
|
|
18
|
+
routes: [
|
|
19
|
+
group("/api", { children: [new UsersController(users)] }),
|
|
20
|
+
docs({ info: { title: "Users API", version: "1.0.0" } }),
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`/openapi.json` serves the document and `/docs` renders it. The document
|
|
26
|
+
is built once, at startup, so anything that cannot be described is
|
|
27
|
+
reported before the first request.
|
|
28
|
+
|
|
29
|
+
`docs()` is an ordinary controller: put it in a group to move it under a
|
|
30
|
+
prefix, guard it with hooks, or leave it out in production. Its own two
|
|
31
|
+
routes are left out of the document.
|
|
32
|
+
|
|
33
|
+
## Options
|
|
34
|
+
|
|
35
|
+
| Option | Default | |
|
|
36
|
+
| --- | --- | --- |
|
|
37
|
+
| `info` | — | the document's `info`: title, version, description |
|
|
38
|
+
| `servers` | — | where the API is reachable |
|
|
39
|
+
| `path` | `/openapi.json` | where the document is served |
|
|
40
|
+
| `uiPath` | `/docs` | where the page is served |
|
|
41
|
+
| `ui` | `"scalar"` | `"scalar"`, `"swagger-ui"` or `"redoc"` |
|
|
42
|
+
| `title` | the document's title | the page's title |
|
|
43
|
+
| `assets` | the renderer's CDN | pinned or self-hosted renderer URLs |
|
|
44
|
+
| `documentSelf` | `false` | include `/openapi.json` and `/docs` in the document |
|
|
45
|
+
| `onWarning` | `console.warn` | receives what could not be described |
|
|
46
|
+
|
|
47
|
+
## What goes into the document
|
|
48
|
+
|
|
49
|
+
| From the route | Becomes |
|
|
50
|
+
| --- | --- |
|
|
51
|
+
| `path` | the path: `/users/:id` → `/users/{id}` |
|
|
52
|
+
| `schema.params`, `query`, `headers`, `cookies` | parameters, required as the schema says |
|
|
53
|
+
| `schema.body` and `bodyType` | the request body and its media types |
|
|
54
|
+
| `schema.response` | responses — one, or one per status of a map; `null` is a status without a body |
|
|
55
|
+
| `docs` | `summary`, `description`, `tags`, `deprecated` |
|
|
56
|
+
| the controller and field | `operationId`: `setAvatar` on `UsersController` → `usersSetAvatar` |
|
|
57
|
+
|
|
58
|
+
`docs: { hidden: true }` leaves a route out of the document; it is served
|
|
59
|
+
as before.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
route({
|
|
63
|
+
method: "POST",
|
|
64
|
+
path: "/internal/drain",
|
|
65
|
+
docs: { summary: "Drain the queue", hidden: true },
|
|
66
|
+
handler: () => queue.drain(),
|
|
67
|
+
});
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Schemas are described through Standard Schema's JSON Schema support: Zod,
|
|
71
|
+
Valibot, ArkType and [`@tetsujs/typebox`](../typebox) provide it.
|
|
72
|
+
|
|
73
|
+
## Responses the framework adds
|
|
74
|
+
|
|
75
|
+
Failures the framework answers by itself are documented on every route
|
|
76
|
+
where they can happen, in the same error envelope the server sends:
|
|
77
|
+
|
|
78
|
+
| Status | `error` | When |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `422` (or the configured validation status) | `VALIDATION_FAILED` | a request part failed its schema |
|
|
81
|
+
| `400` | `MALFORMED_JSON` / `MALFORMED_FORM` | the body could not be parsed |
|
|
82
|
+
| `413` | `BODY_TOO_LARGE` | the body exceeded `maxBodySize` |
|
|
83
|
+
| `500` | `INTERNAL_SERVER_ERROR` | any unhandled failure |
|
|
84
|
+
|
|
85
|
+
The `error` code is a constant in each schema, so a generated client can
|
|
86
|
+
tell failures apart by it. A status the route declares itself is kept, and
|
|
87
|
+
the framework's failures for the same status are listed next to it.
|
|
88
|
+
|
|
89
|
+
## Documenting hooks
|
|
90
|
+
|
|
91
|
+
A hook that answers by itself — an auth check, a limiter — can say so, and
|
|
92
|
+
every route it guards is documented accordingly. `secured()` adds a
|
|
93
|
+
security scheme, `documented()` adds responses:
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { documented, secured } from "@tetsujs/openapi";
|
|
97
|
+
|
|
98
|
+
export const auth = secured(
|
|
99
|
+
hook.beforeParse((ctx) => {
|
|
100
|
+
const user = verify(ctx.req.headers.get("authorization"));
|
|
101
|
+
|
|
102
|
+
if (!user) throw new HttpError(401);
|
|
103
|
+
|
|
104
|
+
return { user };
|
|
105
|
+
}),
|
|
106
|
+
{ name: "bearerAuth", scheme: { type: "http", scheme: "bearer", bearerFormat: "JWT" } },
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
export const guard = documented(hook.beforeParse(check), {
|
|
110
|
+
responses: [{ status: 429, description: "Rate limit exceeded", error: "RATE_LIMITED" }],
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
Hooks from `@tetsujs/rate-limit` are already documented this way. A scheme
|
|
115
|
+
is identified by its `name`: the same one mounted twice is one
|
|
116
|
+
requirement, and two different schemes under one name are reported.
|
|
117
|
+
|
|
118
|
+
## Using the document directly
|
|
119
|
+
|
|
120
|
+
When the document itself is the output — written to a file in CI, fed to a
|
|
121
|
+
client generator — call the generator:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { openapi } from "@tetsujs/openapi";
|
|
125
|
+
|
|
126
|
+
const { document, warnings } = openapi(app, {
|
|
127
|
+
info: { title: "Users API", version: "1.0.0" },
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
await Bun.write("openapi.json", JSON.stringify(document, null, 2));
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`docsPage()` renders the page on its own, for serving it elsewhere.
|
|
134
|
+
|
|
135
|
+
## Warnings
|
|
136
|
+
|
|
137
|
+
A route is still documented when part of it cannot be described, and the
|
|
138
|
+
gap is reported instead of failing the startup:
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
[openapi] POST /api/users: the body schema does not emit JSON Schema
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
This happens with a validator that does not emit JSON Schema, a schema
|
|
145
|
+
whose conversion throws, and two routes that map to the same OpenAPI path
|
|
146
|
+
(`/files/*` and `/files/:wildcard` are both `/files/{wildcard}`).
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What hooks contribute to the documentation.
|
|
3
|
+
*
|
|
4
|
+
* What a route is protected by, or answers with when a guard refuses, is
|
|
5
|
+
* not in its config: a hook is a function in a slot, and the route that
|
|
6
|
+
* mounts it says nothing about tokens or rate limits. The contribution is
|
|
7
|
+
* therefore annotated onto the hook — from this package, not from
|
|
8
|
+
* `hook.*` — so the core keeps knowing nothing about OpenAPI and the
|
|
9
|
+
* factories stay free of documentation options.
|
|
10
|
+
*
|
|
11
|
+
* A hook annotated once is documented everywhere it runs: application and
|
|
12
|
+
* group chains are merged into every route's chains at startup, so a guard
|
|
13
|
+
* mounted on a zone documents the zone's operations without repeating
|
|
14
|
+
* itself.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import type { AnyHook, AnySchema } from "@tetsujs/core";
|
|
19
|
+
/**
|
|
20
|
+
* The definition of a scheme, as OpenAPI spells it.
|
|
21
|
+
*
|
|
22
|
+
* Typed as loosely as the spec is wide — `http`, `apiKey`, `oauth2` and
|
|
23
|
+
* `openIdConnect` carry different fields, and narrowing them here would
|
|
24
|
+
* be a second, poorer copy of the specification.
|
|
25
|
+
*/
|
|
26
|
+
export interface SecurityScheme {
|
|
27
|
+
readonly type: "http" | "apiKey" | "oauth2" | "openIdConnect";
|
|
28
|
+
readonly [key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
/** What a hook demands of a request, and what it answers when refused. */
|
|
31
|
+
export interface SecurityRequirement {
|
|
32
|
+
/** The name the scheme is registered under in `components`. */
|
|
33
|
+
readonly name: string;
|
|
34
|
+
/** The scheme itself, registered once per name. */
|
|
35
|
+
readonly scheme: SecurityScheme;
|
|
36
|
+
/** OAuth2 scopes; empty for every other type. */
|
|
37
|
+
readonly scopes?: readonly string[];
|
|
38
|
+
/** The status an unauthenticated request gets. Defaults to `401`. */
|
|
39
|
+
readonly status?: number;
|
|
40
|
+
/** How the refusal is described in the document. */
|
|
41
|
+
readonly description?: string;
|
|
42
|
+
/**
|
|
43
|
+
* The `error` code the refusal carries, when the guard answers with the
|
|
44
|
+
* framework's envelope — `"UNAUTHORIZED"`, `"SESSION_EXPIRED"`.
|
|
45
|
+
*
|
|
46
|
+
* Documented as a `const` for the same reason as on a
|
|
47
|
+
* {@link DocumentedResponse}: only the guard knows its own code.
|
|
48
|
+
*/
|
|
49
|
+
readonly error?: string;
|
|
50
|
+
/** An example of the refusal's `message`, for the same reason. */
|
|
51
|
+
readonly message?: string;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* One response a hook can produce, which the route's own schema knows
|
|
55
|
+
* nothing about — a rate limiter's `429`, a guard's `401`.
|
|
56
|
+
*/
|
|
57
|
+
export interface DocumentedResponse {
|
|
58
|
+
readonly status: number;
|
|
59
|
+
readonly description: string;
|
|
60
|
+
/** The body, when the hook answers with one. */
|
|
61
|
+
readonly schema?: AnySchema;
|
|
62
|
+
/**
|
|
63
|
+
* The `error` code of the failure envelope, when the hook answers with
|
|
64
|
+
* the framework's shape — `"RATE_LIMITED"`, `"SESSION_EXPIRED"`.
|
|
65
|
+
*
|
|
66
|
+
* Documented as a `const`, so a client can discriminate on it. Only the
|
|
67
|
+
* hook knows its own code; without one the document says the field is a
|
|
68
|
+
* string and no more.
|
|
69
|
+
*/
|
|
70
|
+
readonly error?: string;
|
|
71
|
+
/** An example of the envelope's `message`, for the same reason. */
|
|
72
|
+
readonly message?: string;
|
|
73
|
+
}
|
|
74
|
+
/** Everything a hook tells the generator about itself. */
|
|
75
|
+
export interface HookDocs {
|
|
76
|
+
/** What the hook demands of a request. */
|
|
77
|
+
readonly security?: SecurityRequirement;
|
|
78
|
+
/** What the hook can answer with. */
|
|
79
|
+
readonly responses?: readonly DocumentedResponse[];
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Annotates a hook with what it contributes to the documentation.
|
|
83
|
+
*
|
|
84
|
+
* Returns a copy: what the caller exports is what carries the annotation,
|
|
85
|
+
* and a hook used elsewhere is not changed behind its author's back. The
|
|
86
|
+
* type is preserved exactly, so an annotated hook goes into a stack like
|
|
87
|
+
* any other.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```ts
|
|
91
|
+
* export const limiter = documented(hook.beforeParse(check), {
|
|
92
|
+
* responses: [
|
|
93
|
+
* { status: 429, description: "Rate limit exceeded", schema: TooMany },
|
|
94
|
+
* ],
|
|
95
|
+
* });
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function documented<H extends AnyHook>(hook: H, docs: HookDocs): H;
|
|
99
|
+
/**
|
|
100
|
+
* Annotates a hook with the security it enforces — {@link documented} for
|
|
101
|
+
* the case that comes up most.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* export const auth = secured(
|
|
106
|
+
* hook.beforeParse((ctx) => {
|
|
107
|
+
* const user = verify(ctx.req.headers.get("authorization"));
|
|
108
|
+
* if (!user) throw new HttpError(401);
|
|
109
|
+
* return { user };
|
|
110
|
+
* }),
|
|
111
|
+
* {
|
|
112
|
+
* name: "bearerAuth",
|
|
113
|
+
* scheme: { type: "http", scheme: "bearer", bearerFormat: "JWT" },
|
|
114
|
+
* },
|
|
115
|
+
* );
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
export declare function secured<H extends AnyHook>(hook: H, requirement: SecurityRequirement): H;
|
|
119
|
+
/** Reads everything a hook was annotated with. */
|
|
120
|
+
export declare function docsOf(hook: AnyHook): HookDocs | undefined;
|
|
121
|
+
/** Reads the security requirement a hook carries, if it carries one. */
|
|
122
|
+
export declare function securityOf(hook: AnyHook): SecurityRequirement | undefined;
|
|
123
|
+
/** What the hooks of one route contribute, in execution order. */
|
|
124
|
+
export interface HookContributions {
|
|
125
|
+
readonly security: readonly SecurityRequirement[];
|
|
126
|
+
readonly responses: readonly DocumentedResponse[];
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Collects the contributions of a route's chains.
|
|
130
|
+
*
|
|
131
|
+
* A scheme is registered once per name and a response once per status and
|
|
132
|
+
* description: the same guard on the application and on a group is one
|
|
133
|
+
* requirement, not two.
|
|
134
|
+
*
|
|
135
|
+
* Deduplicating by name means two hooks claiming one name with different
|
|
136
|
+
* schemes lose one of them, and that is not a duplicate being collapsed —
|
|
137
|
+
* it is a requirement the route really has going undescribed. Passing
|
|
138
|
+
* `warn` is how the caller hears about it; the generator does.
|
|
139
|
+
*/
|
|
140
|
+
export declare function contributionsOf(chains: Record<string, readonly AnyHook[]>, warn?: (message: string) => void): HookContributions;
|
|
141
|
+
//# sourceMappingURL=annotations.d.ts.map
|
|
@@ -0,0 +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;AAExD;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,eAAe,CAAC;IAC9D,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACjC;AAED,0EAA0E;AAC1E,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,mDAAmD;IACnD,QAAQ,CAAC,MAAM,EAAE,cAAc,CAAC;IAEhC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEpC,qEAAqE;IACrE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,oDAAoD;IACpD,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B;;;;;;OAMG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,kEAAkE;IAClE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAE7B,gDAAgD;IAChD,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAE5B;;;;;;;OAOG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAExB,mEAAmE;IACnE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,0DAA0D;AAC1D,MAAM,WAAW,QAAQ;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,QAAQ,CAAC,EAAE,mBAAmB,CAAC;IAExC,qCAAqC;IACrC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACpD;AAID;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,QAAQ,GAAG,CAAC,CASxE;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,OAAO,CAAC,CAAC,SAAS,OAAO,EACvC,IAAI,EAAE,CAAC,EACP,WAAW,EAAE,mBAAmB,GAC/B,CAAC,CAEH;AAED,kDAAkD;AAClD,wBAAgB,MAAM,CAAC,IAAI,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAE1D;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,GAAG,SAAS,CAEzE;AAED,kEAAkE;AAClE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAC;IAClD,QAAQ,CAAC,SAAS,EAAE,SAAS,kBAAkB,EAAE,CAAC;CACnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC,EAC1C,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC/B,iBAAiB,CAyCnB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The document's `components/schemas` section.
|
|
3
|
+
*
|
|
4
|
+
* The failures the framework fills in repeat across every operation — a
|
|
5
|
+
* `500` on all of them, a `413` on every route with a body — and inlining
|
|
6
|
+
* the same envelope each time makes a document that is mostly boilerplate.
|
|
7
|
+
* They are defined once here and referenced with `$ref`, which is also how
|
|
8
|
+
* a client generator ends up with one `MalformedJson` type instead of one
|
|
9
|
+
* per endpoint.
|
|
10
|
+
*
|
|
11
|
+
* Only the framework's own shapes go here. A schema that came from a
|
|
12
|
+
* validator is still inlined: it is the author's, it has no name the
|
|
13
|
+
* generator could give it, and `$ref` would invent one.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/** A reference to a definition in `components/schemas`. */
|
|
18
|
+
export interface SchemaRef {
|
|
19
|
+
readonly $ref: string;
|
|
20
|
+
}
|
|
21
|
+
/** The definitions collected while generating a document. */
|
|
22
|
+
export interface SchemaComponents {
|
|
23
|
+
/**
|
|
24
|
+
* Registers a schema and returns the reference to it.
|
|
25
|
+
*
|
|
26
|
+
* Identical schemas share one definition, whatever name each caller
|
|
27
|
+
* asked for; different schemas that want the same name get a numbered
|
|
28
|
+
* one, so a document is never silently wrong about what a `$ref` points
|
|
29
|
+
* at.
|
|
30
|
+
*/
|
|
31
|
+
ref(preferred: string, schema: Record<string, unknown>): SchemaRef;
|
|
32
|
+
/** What was registered, in registration order. */
|
|
33
|
+
readonly schemas: Record<string, unknown>;
|
|
34
|
+
}
|
|
35
|
+
export declare function schemaComponents(): SchemaComponents;
|
|
36
|
+
/**
|
|
37
|
+
* The name a failure is defined under: its `error` code in PascalCase —
|
|
38
|
+
* `MALFORMED_JSON` becomes `MalformedJson` — so the definition and the code
|
|
39
|
+
* a client branches on read as the same thing.
|
|
40
|
+
*
|
|
41
|
+
* A failure whose code only the hook knows, and which the hook did not
|
|
42
|
+
* declare, is named after its status instead.
|
|
43
|
+
*/
|
|
44
|
+
export declare function failureName(status: number, error?: string): string;
|
|
45
|
+
//# sourceMappingURL=components.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../../src/components.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,6DAA6D;AAC7D,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IAEnE,kDAAkD;IAClD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC3C;AAED,wBAAgB,gBAAgB,IAAI,gBAAgB,CA2BnD;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAclE"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The documentation endpoints as a mountable controller.
|
|
3
|
+
*
|
|
4
|
+
* `openapi()` returns data and mounts nothing, which is the right default
|
|
5
|
+
* for a generator but a chore in the ordinary case: every application ends
|
|
6
|
+
* up writing the same controller, holding the same application, to serve
|
|
7
|
+
* the same two routes. {@link docs} is that controller, written once.
|
|
8
|
+
*
|
|
9
|
+
* It is a controller like any other — mounted in `routes`, groupable,
|
|
10
|
+
* hookable — that receives the application through `onMount` and generates
|
|
11
|
+
* the document from it at startup.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
import type { App, RouteDef, SchemaConfig, ValidatePath } from "@tetsujs/core";
|
|
16
|
+
import { onMount } from "@tetsujs/core";
|
|
17
|
+
import type { OpenApiDocument } from "./document.ts";
|
|
18
|
+
import type { GeneratorWarning } from "./index.ts";
|
|
19
|
+
import type { DocsAssets, DocsUi } from "./page.ts";
|
|
20
|
+
/** What the documentation controller serves, and from where. */
|
|
21
|
+
export interface DocsOptions<Path extends string, UiPath extends string> {
|
|
22
|
+
/** Title, version and the rest of the document's `info` block. */
|
|
23
|
+
readonly info: OpenApiDocument["info"];
|
|
24
|
+
/** Servers the API is reachable at. */
|
|
25
|
+
readonly servers?: OpenApiDocument["servers"];
|
|
26
|
+
/** Where the document is served. Defaults to `/openapi.json`. */
|
|
27
|
+
readonly path?: Path & ValidatePath<Path>;
|
|
28
|
+
/** Where the page is served. Defaults to `/docs`. */
|
|
29
|
+
readonly uiPath?: UiPath & ValidatePath<UiPath>;
|
|
30
|
+
/** Which renderer the page bootstraps. Defaults to `"scalar"`. */
|
|
31
|
+
readonly ui?: DocsUi;
|
|
32
|
+
/** Browser title of the page. Defaults to the document's own title. */
|
|
33
|
+
readonly title?: string;
|
|
34
|
+
/** Overrides the renderer's CDN URLs, for a pinned or self-hosted copy. */
|
|
35
|
+
readonly assets?: DocsAssets;
|
|
36
|
+
/**
|
|
37
|
+
* Documents the two endpoints this controller serves.
|
|
38
|
+
*
|
|
39
|
+
* Off by default: a reader who is looking at the document has already
|
|
40
|
+
* found the document, and an operation for "the page you are reading"
|
|
41
|
+
* is one a generated client would happily produce a method for.
|
|
42
|
+
*/
|
|
43
|
+
readonly documentSelf?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* What to do with what the generator could not describe.
|
|
46
|
+
*
|
|
47
|
+
* Defaults to one `console.warn` per warning, at startup — a document
|
|
48
|
+
* that silently omits a body is worse than a noisy one.
|
|
49
|
+
*/
|
|
50
|
+
readonly onWarning?: (warning: GeneratorWarning) => void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* The controller {@link docs} returns: two routes and the `onMount` that
|
|
54
|
+
* fills them.
|
|
55
|
+
*
|
|
56
|
+
* Instantiated through the factory, never directly — the routes carry the
|
|
57
|
+
* configured paths in their types, which only the factory can infer.
|
|
58
|
+
*/
|
|
59
|
+
export declare class DocsController<Path extends string, UiPath extends string> {
|
|
60
|
+
private readonly options;
|
|
61
|
+
/** The document itself, as `application/json`. */
|
|
62
|
+
readonly json: RouteDef<Path, SchemaConfig, never, undefined, "GET">;
|
|
63
|
+
/** The page that renders it. */
|
|
64
|
+
readonly ui: RouteDef<UiPath, SchemaConfig, never, undefined, "GET">;
|
|
65
|
+
private document;
|
|
66
|
+
constructor(options: DocsOptions<Path, UiPath>, documentPath: string, uiPath: string, page: string);
|
|
67
|
+
/** Generates the document of the application this was mounted in. */
|
|
68
|
+
[onMount](app: App): void;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* The documentation of an application, as a controller to mount.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```ts
|
|
75
|
+
* createApp({
|
|
76
|
+
* routes: [
|
|
77
|
+
* group("/api", { children: [new UsersController(users)] }),
|
|
78
|
+
* docs({ info: { title: "Users API", version: "1.0.0" } }),
|
|
79
|
+
* ],
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* The document is generated once, at startup, from the application the
|
|
84
|
+
* controller was mounted in. Its own two routes stay out of it — pass
|
|
85
|
+
* `documentSelf` to put them in.
|
|
86
|
+
*
|
|
87
|
+
* The page loads its renderer from a CDN; see {@link docsPage} for the
|
|
88
|
+
* trade and for how to point it at a self-hosted copy.
|
|
89
|
+
*/
|
|
90
|
+
export declare function docs<const Path extends string = "/openapi.json", const UiPath extends string = "/docs">(options: DocsOptions<Path, UiPath>): DocsController<Path, UiPath>;
|
|
91
|
+
//# sourceMappingURL=controller.d.ts.map
|
|
@@ -0,0 +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,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM;IACrE,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,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,kEAAkE;IAClE,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IAErB,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,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,SAAS,MAAM;IAUlE,OAAO,CAAC,QAAQ,CAAC,OAAO;IAT1B,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,gCAAgC;IAChC,QAAQ,CAAC,EAAE,EAAE,QAAQ,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAErE,OAAO,CAAC,QAAQ,CAA8B;IAE9C,YACmB,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,EACnD,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAgCb;IAED,qEAAqE;IACrE,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,GAAG,GAAG,IAAI,CAaxB;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,IAAI,CAClB,KAAK,CAAC,IAAI,SAAS,MAAM,GAAG,eAAe,EAC3C,KAAK,CAAC,MAAM,SAAS,MAAM,GAAG,OAAO,EACrC,OAAO,EAAE,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,cAAc,CAAC,IAAI,EAAE,MAAM,CAAC,CAYlE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shape of the documents this package produces.
|
|
3
|
+
*
|
|
4
|
+
* Typed only as far as the generator fills it in: an OpenAPI document is
|
|
5
|
+
* far larger than what a route table can honestly describe, and declaring
|
|
6
|
+
* the rest would promise support that does not exist. Everything typed
|
|
7
|
+
* here is emitted; everything a caller adds by hand passes through the
|
|
8
|
+
* index signatures.
|
|
9
|
+
*
|
|
10
|
+
* OpenAPI 3.1 only. It is JSON Schema 2020-12 verbatim, which is what
|
|
11
|
+
* validators emit through Standard Schema — 3.0 would mean converting the
|
|
12
|
+
* dialect and being subtly wrong about `nullable`, `examples` and binary
|
|
13
|
+
* payloads.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
/** Metadata every OpenAPI document must carry. */
|
|
18
|
+
export interface DocumentInfo {
|
|
19
|
+
readonly title: string;
|
|
20
|
+
readonly version: string;
|
|
21
|
+
readonly description?: string;
|
|
22
|
+
readonly [key: string]: unknown;
|
|
23
|
+
}
|
|
24
|
+
/** A server the API is reachable at. */
|
|
25
|
+
export interface DocumentServer {
|
|
26
|
+
readonly url: string;
|
|
27
|
+
readonly description?: string;
|
|
28
|
+
}
|
|
29
|
+
/** One request parameter: a path segment, a query key or a header. */
|
|
30
|
+
export interface ParameterObject {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly in: "path" | "query" | "header" | "cookie";
|
|
33
|
+
readonly required: boolean;
|
|
34
|
+
readonly description?: string;
|
|
35
|
+
readonly schema?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/** A body or response payload, keyed by media type. */
|
|
38
|
+
export type ContentMap = Record<string, {
|
|
39
|
+
readonly schema?: Record<string, unknown>;
|
|
40
|
+
}>;
|
|
41
|
+
/** One response of an operation. */
|
|
42
|
+
export interface ResponseObject {
|
|
43
|
+
readonly description: string;
|
|
44
|
+
readonly content?: ContentMap;
|
|
45
|
+
}
|
|
46
|
+
/** One endpoint: a method on a path. */
|
|
47
|
+
export interface OperationObject {
|
|
48
|
+
readonly operationId: string;
|
|
49
|
+
readonly summary?: string;
|
|
50
|
+
readonly description?: string;
|
|
51
|
+
readonly tags?: readonly string[];
|
|
52
|
+
readonly deprecated?: boolean;
|
|
53
|
+
readonly parameters?: readonly ParameterObject[];
|
|
54
|
+
readonly requestBody?: {
|
|
55
|
+
readonly required: boolean;
|
|
56
|
+
readonly content: ContentMap;
|
|
57
|
+
};
|
|
58
|
+
readonly security?: readonly Record<string, readonly string[]>[];
|
|
59
|
+
readonly responses: Record<string, ResponseObject>;
|
|
60
|
+
}
|
|
61
|
+
/** Every operation declared on one path template. */
|
|
62
|
+
export type PathItemObject = Record<string, OperationObject>;
|
|
63
|
+
/** The generated document. */
|
|
64
|
+
export interface OpenApiDocument {
|
|
65
|
+
readonly openapi: "3.1.0";
|
|
66
|
+
readonly info: DocumentInfo;
|
|
67
|
+
readonly servers?: readonly DocumentServer[];
|
|
68
|
+
readonly paths: Record<string, PathItemObject>;
|
|
69
|
+
/**
|
|
70
|
+
* What the operations refer to rather than repeat: the security schemes
|
|
71
|
+
* the guards registered, and the failure envelopes the framework fills
|
|
72
|
+
* in. Absent when neither has anything to say.
|
|
73
|
+
*/
|
|
74
|
+
readonly components?: {
|
|
75
|
+
readonly securitySchemes?: Record<string, unknown>;
|
|
76
|
+
readonly schemas?: Record<string, unknown>;
|
|
77
|
+
};
|
|
78
|
+
readonly [key: string]: unknown;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=document.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Asking a schema to describe itself, without letting it stop the document.
|
|
3
|
+
*
|
|
4
|
+
* Internal to the package.
|
|
5
|
+
*
|
|
6
|
+
* @module
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A JSON Schema as the generator reads it: any keyword, with the two it
|
|
10
|
+
* looks into named, so reading them does not depend on the reader's
|
|
11
|
+
* `noPropertyAccessFromIndexSignature`.
|
|
12
|
+
*/
|
|
13
|
+
export interface JsonSchemaObject {
|
|
14
|
+
properties?: unknown;
|
|
15
|
+
required?: unknown;
|
|
16
|
+
[keyword: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
/** Which shape of a schema to describe: what arrives, or what leaves. */
|
|
19
|
+
type Direction = "input" | "output";
|
|
20
|
+
/**
|
|
21
|
+
* The JSON Schema of a declaration, or `undefined` and a warning saying why.
|
|
22
|
+
*
|
|
23
|
+
* A schema can fail to describe itself in two ways, and the generator owes
|
|
24
|
+
* the same answer to both: an adapter without JSON Schema support returns
|
|
25
|
+
* nothing, and an adapter that has it can still throw — a converter meeting
|
|
26
|
+
* a shape it cannot express, a structure too deep to walk, a `$ref` that
|
|
27
|
+
* leads back to where it started.
|
|
28
|
+
*
|
|
29
|
+
* Only the first was handled, and the difference between them is not the
|
|
30
|
+
* document: it is whether the application starts at all. `docs()` builds
|
|
31
|
+
* the document from `onMount`, which `createApp` calls while it is still
|
|
32
|
+
* assembling itself, so an escaping error is not a gap in a description —
|
|
33
|
+
* it is a server that never listens, over a route that would have worked.
|
|
34
|
+
*
|
|
35
|
+
* `subject` names the declaration for the message, as the reader of a
|
|
36
|
+
* warning would name it: `"the body"`, `"a response"`.
|
|
37
|
+
*/
|
|
38
|
+
export declare function emitted(schema: unknown, subject: string, warn: (message: string) => void, direction?: Direction): JsonSchemaObject | undefined;
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=emit.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI generation for the framework.
|
|
3
|
+
*
|
|
4
|
+
* Reads a compiled application — `app.entries` and the options it actually
|
|
5
|
+
* runs with — and returns an OpenAPI 3.1 document. Nothing is registered,
|
|
6
|
+
* served or mutated: the document is data, and serving it is a route the
|
|
7
|
+
* application declares like any other.
|
|
8
|
+
*
|
|
9
|
+
* Serving it is a route the application declares — or `docs()`, the
|
|
10
|
+
* controller that declares those routes for you.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const { document, warnings } = openapi(app, {
|
|
15
|
+
* info: { title: "Users API", version: "1.0.0" },
|
|
16
|
+
* });
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import type { App } from "@tetsujs/core";
|
|
22
|
+
import type { OpenApiDocument } from "./document.ts";
|
|
23
|
+
export type { DocumentedResponse, HookContributions, HookDocs, SecurityRequirement, SecurityScheme, } from "./annotations.ts";
|
|
24
|
+
export { documented, secured } from "./annotations.ts";
|
|
25
|
+
export type { DocsOptions } from "./controller.ts";
|
|
26
|
+
export { DocsController, docs } from "./controller.ts";
|
|
27
|
+
export type { ContentMap, DocumentInfo, DocumentServer, OpenApiDocument, OperationObject, ParameterObject, PathItemObject, ResponseObject, } from "./document.ts";
|
|
28
|
+
export type { DocsAssets, DocsPageOptions, DocsUi } from "./page.ts";
|
|
29
|
+
export { docsPage } from "./page.ts";
|
|
30
|
+
/**
|
|
31
|
+
* What the generator cannot read off the application.
|
|
32
|
+
*/
|
|
33
|
+
export interface OpenApiOptions {
|
|
34
|
+
/** Title, version and the rest of the document's `info` block. */
|
|
35
|
+
readonly info: OpenApiDocument["info"];
|
|
36
|
+
/** Servers the API is reachable at. */
|
|
37
|
+
readonly servers?: OpenApiDocument["servers"];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* What the generator could not describe faithfully.
|
|
41
|
+
*
|
|
42
|
+
* Each entry names a route and what was missing — almost always a schema
|
|
43
|
+
* from a validator that emits no JSON Schema. Documentation that silently
|
|
44
|
+
* omits a body is worse than documentation that says it does not know, so
|
|
45
|
+
* these are returned rather than swallowed.
|
|
46
|
+
*/
|
|
47
|
+
export interface GeneratorWarning {
|
|
48
|
+
readonly route: string;
|
|
49
|
+
readonly message: string;
|
|
50
|
+
}
|
|
51
|
+
/** A generated document together with what could not be described. */
|
|
52
|
+
export interface GeneratorResult {
|
|
53
|
+
readonly document: OpenApiDocument;
|
|
54
|
+
readonly warnings: readonly GeneratorWarning[];
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Generates the OpenAPI document of an application.
|
|
58
|
+
*
|
|
59
|
+
* Socket endpoints are skipped: a handshake is a `GET` in the table, but
|
|
60
|
+
* OpenAPI has no way to describe what happens after it, and documenting
|
|
61
|
+
* the upgrade as an ordinary operation would describe a request that
|
|
62
|
+
* clients must not make. AsyncAPI is that format, and a separate document.
|
|
63
|
+
*
|
|
64
|
+
* So are routes marked `docs: { hidden: true }`, and everything they would
|
|
65
|
+
* have contributed with them: a security scheme only a hidden route
|
|
66
|
+
* requires has nothing left to describe.
|
|
67
|
+
*
|
|
68
|
+
* Every route of the table becomes an operation under its path template,
|
|
69
|
+
* with the parameters, body and responses its schemas describe. What the
|
|
70
|
+
* framework itself can answer — a validation failure, an unparsable body,
|
|
71
|
+
* a body over the limit — is filled in from the options the application
|
|
72
|
+
* runs with, so the document states the status actually configured.
|
|
73
|
+
*/
|
|
74
|
+
export declare function openapi(app: App, options: OpenApiOptions): GeneratorResult;
|
|
75
|
+
//# sourceMappingURL=index.d.ts.map
|