@tetsujs/openapi 0.2.0 → 0.4.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 +22 -2
- package/dist/src/controller.d.ts +1 -1
- package/dist/src/index.js +20 -19
- package/dist/src/index.js.map +4 -4
- package/dist/src/operation.d.ts +19 -12
- package/dist/src/operation.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/controller.ts +1 -1
- package/src/operation.ts +48 -44
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ import { docs } from "@tetsujs/openapi";
|
|
|
16
16
|
|
|
17
17
|
createApp({
|
|
18
18
|
routes: [
|
|
19
|
-
group("/api", { children: [
|
|
19
|
+
group("/api", { children: [usersController({ users })] }),
|
|
20
20
|
docs({ info: { title: "Users API", version: "1.0.0" } }),
|
|
21
21
|
],
|
|
22
22
|
});
|
|
@@ -53,11 +53,31 @@ routes are left out of the document.
|
|
|
53
53
|
| `schema.body` and `bodyType` | the request body and its media types |
|
|
54
54
|
| `schema.response` | responses — one, or one per status of a map; `null` is a status without a body |
|
|
55
55
|
| `docs` | `summary`, `description`, `tags`, `deprecated` |
|
|
56
|
-
| the controller and field | `operationId`: `setAvatar`
|
|
56
|
+
| the controller's name and the field | `operationId`: `setAvatar` in `controller("Users", …)` → `usersSetAvatar` |
|
|
57
57
|
|
|
58
58
|
`docs: { hidden: true }` leaves a route out of the document; it is served
|
|
59
59
|
as before.
|
|
60
60
|
|
|
61
|
+
## Operation ids
|
|
62
|
+
|
|
63
|
+
A generated client names its methods after the `operationId`s, so they are
|
|
64
|
+
a contract, and every one comes from a name you wrote:
|
|
65
|
+
|
|
66
|
+
| Route | `operationId` |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| with `docs: { operationId }` | as written |
|
|
69
|
+
| in `controller("Users", …)` as `setAvatar` | `usersSetAvatar` |
|
|
70
|
+
| in a class `UsersController` as `setAvatar` | `usersSetAvatar` |
|
|
71
|
+
| in an object literal as `setAvatar` | `setAvatar` |
|
|
72
|
+
| mounted on its own, `POST /auth/code` | `postAuthCode` |
|
|
73
|
+
|
|
74
|
+
Two routes arriving at the same id stop the application at startup, naming
|
|
75
|
+
both — nothing is renamed behind your back, which would change a method in
|
|
76
|
+
someone's SDK the day a route is added. Give one of them
|
|
77
|
+
`docs.operationId`, or its controller a name of its own. On a public API,
|
|
78
|
+
state the id on every route: it is then visible, and a change to it is a
|
|
79
|
+
change a reviewer sees.
|
|
80
|
+
|
|
61
81
|
```ts
|
|
62
82
|
route({
|
|
63
83
|
method: "POST",
|
package/dist/src/controller.d.ts
CHANGED
|
@@ -74,7 +74,7 @@ export declare class DocsController<Path extends string, UiPath extends string>
|
|
|
74
74
|
* ```ts
|
|
75
75
|
* createApp({
|
|
76
76
|
* routes: [
|
|
77
|
-
* group("/api", { children: [
|
|
77
|
+
* group("/api", { children: [usersController({ users })] }),
|
|
78
78
|
* docs({ info: { title: "Users API", version: "1.0.0" } }),
|
|
79
79
|
* ],
|
|
80
80
|
* });
|
package/dist/src/index.js
CHANGED
|
@@ -190,35 +190,36 @@ function operationOf(entry, options, components, ids, warn) {
|
|
|
190
190
|
};
|
|
191
191
|
}
|
|
192
192
|
function operationIds() {
|
|
193
|
-
const taken = new
|
|
193
|
+
const taken = new Map;
|
|
194
194
|
return {
|
|
195
195
|
take(entry) {
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
196
|
+
const id = idOf(entry);
|
|
197
|
+
const holder = taken.get(id);
|
|
198
|
+
if (holder !== undefined) {
|
|
199
|
+
throw new Error(`operationId "${id}" is taken by both ${describe(holder)} and ${describe(entry)} \u2014 a generated client would get one method for two operations. Give one of them docs.operationId, or its controller a name of its own.`);
|
|
200
200
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
taken.add(derived);
|
|
204
|
-
return derived;
|
|
205
|
-
}
|
|
206
|
-
let name = derived;
|
|
207
|
-
for (let attempt = 2;taken.has(name); attempt += 1) {
|
|
208
|
-
name = `${derived}${attempt}`;
|
|
209
|
-
}
|
|
210
|
-
taken.add(name);
|
|
211
|
-
return name;
|
|
201
|
+
taken.set(id, entry);
|
|
202
|
+
return id;
|
|
212
203
|
}
|
|
213
204
|
};
|
|
214
205
|
}
|
|
215
|
-
function
|
|
206
|
+
function idOf(entry) {
|
|
207
|
+
const stated = entry.def.docs?.operationId;
|
|
208
|
+
if (stated !== undefined) {
|
|
209
|
+
return stated;
|
|
210
|
+
}
|
|
216
211
|
if (entry.name === undefined) {
|
|
217
|
-
return;
|
|
212
|
+
return derivedId(entry);
|
|
213
|
+
}
|
|
214
|
+
if (entry.controller === undefined) {
|
|
215
|
+
return entry.name;
|
|
218
216
|
}
|
|
219
217
|
const controller = entry.controller.replace(/Controller$/, "");
|
|
220
218
|
return `${lowerFirst(controller)}${capitalize(entry.name)}`;
|
|
221
219
|
}
|
|
220
|
+
function describe(entry) {
|
|
221
|
+
return `${entry.method} ${entry.path}`;
|
|
222
|
+
}
|
|
222
223
|
function derivedId(entry) {
|
|
223
224
|
return `${entry.method.toLowerCase()}${entry.path.split(/[/:*]/).filter(Boolean).map(capitalize).join("")}`;
|
|
224
225
|
}
|
|
@@ -612,5 +613,5 @@ export {
|
|
|
612
613
|
secured
|
|
613
614
|
};
|
|
614
615
|
|
|
615
|
-
//# debugId=
|
|
616
|
+
//# debugId=7F7340DB513FEEB464756E2164756E21
|
|
616
617
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
"/**\n * The document's `components/schemas` section.\n *\n * The failures the framework fills in repeat across every operation — a\n * `500` on all of them, a `413` on every route with a body — and inlining\n * the same envelope each time makes a document that is mostly boilerplate.\n * They are defined once here and referenced with `$ref`, which is also how\n * a client generator ends up with one `MalformedJson` type instead of one\n * per endpoint.\n *\n * Only the framework's own shapes go here. A schema that came from a\n * validator is still inlined: it is the author's, it has no name the\n * generator could give it, and `$ref` would invent one.\n *\n * @module\n */\n\n/** A reference to a definition in `components/schemas`. */\nexport interface SchemaRef {\n readonly $ref: string;\n}\n\n/** The definitions collected while generating a document. */\nexport interface SchemaComponents {\n /**\n * Registers a schema and returns the reference to it.\n *\n * Identical schemas share one definition, whatever name each caller\n * asked for; different schemas that want the same name get a numbered\n * one, so a document is never silently wrong about what a `$ref` points\n * at.\n */\n ref(preferred: string, schema: Record<string, unknown>): SchemaRef;\n\n /** What was registered, in registration order. */\n readonly schemas: Record<string, unknown>;\n}\n\nexport function schemaComponents(): SchemaComponents {\n const schemas: Record<string, unknown> = {};\n const registered = new Map<string, string>();\n\n return {\n schemas,\n\n ref(preferred: string, schema: Record<string, unknown>): SchemaRef {\n const fingerprint = JSON.stringify(schema);\n const already = registered.get(fingerprint);\n\n if (already) {\n return { $ref: `#/components/schemas/${already}` };\n }\n\n let name = preferred;\n\n for (let attempt = 2; name in schemas; attempt += 1) {\n name = `${preferred}${attempt}`;\n }\n\n schemas[name] = schema;\n registered.set(fingerprint, name);\n\n return { $ref: `#/components/schemas/${name}` };\n },\n };\n}\n\n/**\n * The name a failure is defined under: its `error` code in PascalCase —\n * `MALFORMED_JSON` becomes `MalformedJson` — so the definition and the code\n * a client branches on read as the same thing.\n *\n * A failure whose code only the hook knows, and which the hook did not\n * declare, is named after its status instead.\n */\nexport function failureName(status: number, error?: string): string {\n const name = (error ?? \"\")\n .toLowerCase()\n .split(/[^a-z0-9]+/)\n .filter(Boolean)\n .map((part) => part.charAt(0).toUpperCase() + part.slice(1))\n .join(\"\");\n\n // A code with nothing ASCII in it sanitizes to nothing — a code in\n // Cyrillic or Japanese, or one that is all punctuation. That is the same\n // situation as no code at all, and gets the same name: the alternative\n // was a component keyed `\"\"`, which the specification does not allow, and\n // a second such code keyed `\"2\"` by the collision numbering below it.\n return name || `Failure${status}`;\n}\n",
|
|
7
7
|
"/**\n * Asking a schema to describe itself, without letting it stop the document.\n *\n * Internal to the package.\n *\n * @module\n */\n\nimport { toJsonSchema } from \"@tetsujs/core\";\n\n/**\n * The JSON Schema dialect a schema is asked for.\n *\n * OpenAPI 3.1 takes JSON Schema 2020-12 as it is, and `draft-2020-12` is a\n * target the Standard JSON Schema specification names — so every library\n * that implements it answers. This used to be `openapi-3.1`, which only\n * Zod accepts: ArkType and Valibot refused it, and their bodies went into\n * the document without a shape and with a warning each.\n */\nconst target = { target: \"draft-2020-12\" } as const;\n\n/**\n * A JSON Schema as the generator reads it: any keyword, with the two it\n * looks into named, so reading them does not depend on the reader's\n * `noPropertyAccessFromIndexSignature`.\n */\nexport interface JsonSchemaObject {\n properties?: unknown;\n required?: unknown;\n [keyword: string]: unknown;\n}\n\n/** Which shape of a schema to describe: what arrives, or what leaves. */\ntype Direction = \"input\" | \"output\";\n\n/**\n * The JSON Schema of a declaration, or `undefined` and a warning saying why.\n *\n * A schema can fail to describe itself in two ways, and the generator owes\n * the same answer to both: an adapter without JSON Schema support returns\n * nothing, and an adapter that has it can still throw — a converter meeting\n * a shape it cannot express, a structure too deep to walk, a `$ref` that\n * leads back to where it started.\n *\n * Only the first was handled, and the difference between them is not the\n * document: it is whether the application starts at all. `docs()` builds\n * the document from `onMount`, which `createApp` calls while it is still\n * assembling itself, so an escaping error is not a gap in a description —\n * it is a server that never listens, over a route that would have worked.\n *\n * `subject` names the declaration for the message, as the reader of a\n * warning would name it: `\"the body\"`, `\"a response\"`.\n */\nexport function emitted(\n schema: unknown,\n subject: string,\n warn: (message: string) => void,\n direction: Direction = \"output\",\n): JsonSchemaObject | undefined {\n let described: JsonSchemaObject | undefined;\n\n try {\n described = toJsonSchema(schema, target, direction);\n } catch (error) {\n warn(`${subject} schema failed to emit JSON Schema: ${reason(error)}`);\n\n return undefined;\n }\n\n if (!described) {\n warn(`${subject} schema does not emit JSON Schema`);\n\n return undefined;\n }\n\n return withoutDialect(described);\n}\n\n/**\n * A schema as the document embeds it: without its own `$schema`.\n *\n * Asked for draft 2020-12, Zod and ArkType name the dialect at the root of\n * every schema they emit. The document declares it once for all of them,\n * and the same line on every body and response is noise.\n */\nfunction withoutDialect(schema: JsonSchemaObject): JsonSchemaObject {\n if (!(\"$schema\" in schema)) {\n return schema;\n }\n\n const { $schema: _, ...rest } = schema;\n\n return rest;\n}\n\nfunction reason(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n",
|
|
8
8
|
"/**\n * Path templates and request parameters.\n *\n * The framework's path syntax is Bun's — `:param` and a trailing `*` —\n * while OpenAPI templates variables as `{param}`. The conversion happens\n * here, together with the parameter objects the template then requires:\n * OpenAPI demands that every variable in a path be declared.\n *\n * @module\n */\n\nimport type { AnySchema } from \"@tetsujs/core\";\nimport type { ParameterObject } from \"./document.ts\";\nimport { emitted } from \"./emit.ts\";\n\n/** The name a wildcard segment is documented under. */\nexport const wildcardName = \"wildcard\";\n\n/**\n * Rewrites a route path as an OpenAPI path template.\n *\n * A wildcard becomes an ordinary variable: OpenAPI has no syntax for\n * \"everything below here\", so the closest honest template is a named\n * segment, documented as such.\n */\nexport function toTemplate(path: string): string {\n return path\n .split(\"/\")\n .map((segment) => {\n if (segment.startsWith(\":\")) {\n return `{${segment.slice(1)}}`;\n }\n\n return segment === \"*\" ? `{${wildcardName}}` : segment;\n })\n .join(\"/\");\n}\n\n/** The variables a path template declares, in order. */\nexport function templateNames(path: string): string[] {\n const names: string[] = [];\n\n for (const segment of path.split(\"/\")) {\n if (segment.startsWith(\":\")) {\n names.push(segment.slice(1));\n } else if (segment === \"*\") {\n names.push(wildcardName);\n }\n }\n\n return names;\n}\n\n/**\n * Describes the path parameters of a route.\n *\n * A `params` schema describes the whole object, so each variable takes the\n * property of the same name; without one — or without a property for a\n * given variable — the value is what the router captured: a string.\n */\nexport function pathParameters(\n path: string,\n schema: AnySchema | undefined,\n warn: (message: string) => void,\n): ParameterObject[] {\n const properties = objectProperties(schema, \"params\", warn);\n\n return templateNames(path).map((name) => ({\n name,\n in: \"path\" as const,\n required: true,\n ...(name === wildcardName\n ? {\n description:\n \"Matches the rest of the path, including further segments.\",\n }\n : {}),\n schema: properties[name] ?? { type: \"string\" },\n }));\n}\n\n/**\n * Describes the query, header or cookie parameters of a route.\n *\n * All three are objects at the schema level and lists of parameters in a\n * document, so each declared property becomes one parameter, required when\n * the schema says so.\n *\n * A session cookie is the exception the specification makes itself: a\n * credential belongs in `securitySchemes` as `apiKey in: cookie`, not\n * among the parameters. Nothing here can tell one cookie from the other —\n * that is what an annotation on the hook reading it is for, the same way\n * `secured()` marks an authenticating hook.\n */\nexport function partParameters(\n schema: AnySchema | undefined,\n where: \"query\" | \"header\" | \"cookie\",\n warn: (message: string) => void,\n): ParameterObject[] {\n if (!schema) {\n return [];\n }\n\n const described = emitted(schema, `the ${where}`, warn, \"input\");\n\n if (!described) {\n return [];\n }\n\n const properties = (described.properties ?? {}) as Record<\n string,\n Record<string, unknown>\n >;\n const required = new Set(\n Array.isArray(described.required) ? (described.required as string[]) : [],\n );\n\n return Object.entries(properties).map(([name, property]) => ({\n name,\n in: where,\n required: required.has(name),\n schema: property,\n }));\n}\n\nfunction objectProperties(\n schema: AnySchema | undefined,\n where: string,\n warn: (message: string) => void,\n): Record<string, Record<string, unknown>> {\n if (!schema) {\n return {};\n }\n\n const described = emitted(schema, `the ${where}`, warn, \"input\");\n\n if (!described) {\n return {};\n }\n\n return (described.properties ?? {}) as Record<\n string,\n Record<string, unknown>\n >;\n}\n",
|
|
9
|
-
"/**\n * One route table entry, described as an OpenAPI operation.\n *\n * Bodies and responses live here; parameters are in `parameters.ts`. The\n * error responses a route can produce without the author writing anything\n * — a rejected schema, an unparsable body, a body over the limit — are\n * derived from what the route declares and from the options the\n * application actually runs with.\n *\n * @module\n */\n\nimport type {\n AnySchema,\n AppOptions,\n BodyType,\n RouteTableEntry,\n} from \"@tetsujs/core\";\nimport type { HookContributions } from \"./annotations.ts\";\nimport { contributionsOf } from \"./annotations.ts\";\nimport type { SchemaComponents } from \"./components.ts\";\nimport { failureName } from \"./components.ts\";\nimport type {\n ContentMap,\n OperationObject,\n ResponseObject,\n} from \"./document.ts\";\nimport type { JsonSchemaObject } from \"./emit.ts\";\nimport { emitted } from \"./emit.ts\";\nimport { partParameters, pathParameters } from \"./parameters.ts\";\n\n/** Describes one route. */\nexport function operationOf(\n entry: RouteTableEntry,\n options: AppOptions,\n components: SchemaComponents,\n ids: OperationIds,\n warn: (message: string) => void,\n): OperationObject {\n const schema = entry.def.schema;\n\n const parameters = [\n ...pathParameters(entry.path, schema?.params, warn),\n ...partParameters(schema?.query, \"query\", warn),\n ...partParameters(schema?.headers, \"header\", warn),\n ...partParameters(schema?.cookies, \"cookie\", warn),\n ];\n\n const body = requestBody(entry, warn);\n const contributed = contributionsOf(entry.hooks, warn);\n\n return {\n operationId: ids.take(entry),\n ...(entry.def.docs?.summary === undefined\n ? {}\n : { summary: entry.def.docs.summary }),\n ...(entry.def.docs?.description === undefined\n ? {}\n : { description: entry.def.docs.description }),\n ...(entry.def.docs?.tags === undefined\n ? {}\n : { tags: entry.def.docs.tags }),\n ...(entry.def.docs?.deprecated === undefined\n ? {}\n : { deprecated: entry.def.docs.deprecated }),\n ...(parameters.length > 0 ? { parameters } : {}),\n ...(body ? { requestBody: body } : {}),\n ...(contributed.security.length > 0\n ? {\n security: contributed.security.map((requirement) => ({\n [requirement.name]: requirement.scopes ?? [],\n })),\n }\n : {}),\n responses: responses(entry, options, contributed, components, warn),\n };\n}\n\n/**\n * Hands out the `operationId` of every route in one document.\n *\n * Uniqueness is a requirement of the specification and cannot be decided\n * one route at a time: the pair a name is built from — the controller and\n * the field — is not unique on its own. One instance mounted under two\n * prefixes is a documented pattern (`/v1` and `/v2` from the same\n * controller), and two controller classes in different modules may simply\n * share a class name. Either way the pair repeats while the routes differ.\n *\n * A repeat falls back to what the route is named by when it has no field\n * at all — its method and path — because that is derived from the route\n * rather than from the order routes were visited in. A numbered name is\n * the last resort only, since it would move between documents as routes\n * are added, and an `operationId` is what a generated client calls its\n * method.\n */\nexport interface OperationIds {\n /** The id for this route, unique within the document being built. */\n take(entry: RouteTableEntry): string;\n}\n\nexport function operationIds(): OperationIds {\n const taken = new Set<string>();\n\n return {\n take(entry: RouteTableEntry): string {\n const preferred = declaredId(entry) ?? derivedId(entry);\n\n if (!taken.has(preferred)) {\n taken.add(preferred);\n\n return preferred;\n }\n\n const derived = derivedId(entry);\n\n if (!taken.has(derived)) {\n taken.add(derived);\n\n return derived;\n }\n\n let name = derived;\n\n for (let attempt = 2; taken.has(name); attempt += 1) {\n name = `${derived}${attempt}`;\n }\n\n taken.add(name);\n\n return name;\n },\n };\n}\n\n/**\n * The name the controller gave it: the field, qualified by the controller\n * — `usersSetAvatar` for `setAvatar` on `UsersController`.\n */\nfunction declaredId(entry: RouteTableEntry): string | undefined {\n if (entry.name === undefined) {\n return undefined;\n }\n\n const controller = entry.controller.replace(/Controller$/, \"\");\n\n return `${lowerFirst(controller)}${capitalize(entry.name)}`;\n}\n\n/**\n * What identifies a route when its field does not: its method and path.\n *\n * Also the name of a route mounted standalone, which has no field to be\n * called after.\n */\nfunction derivedId(entry: RouteTableEntry): string {\n return `${entry.method.toLowerCase()}${entry.path\n .split(/[/:*]/)\n .filter(Boolean)\n .map(capitalize)\n .join(\"\")}`;\n}\n\n/**\n * Describes the request body, when the route reads one.\n *\n * The media type follows the declaration, not a guess: `\"form\"` accepts\n * both multipart and urlencoded at runtime, so both are listed — unless\n * the schema declares a binary field, which only multipart can carry.\n */\nfunction requestBody(\n entry: RouteTableEntry,\n warn: (message: string) => void,\n): { required: boolean; content: ContentMap } | undefined {\n const declared = entry.def.schema?.body;\n const bodyType = entry.def.bodyType ?? (declared ? \"json\" : undefined);\n\n if (!bodyType) {\n return undefined;\n }\n\n const described = declared\n ? emitted(declared, \"the body\", warn, \"input\")\n : undefined;\n\n const schema = described ? { schema: described } : {};\n\n // Whether a body must be sent is a question about the wire, not about\n // the schema: `json` and `form` parse it before anything is validated,\n // and an empty one fails that parse with a `400` — so the client has to\n // send something whether or not a shape was declared. `text` has no such\n // step, because an absent body reads as `\"\"` just like an empty one; if\n // that string is unacceptable, the schema is where it says so. Neither\n // has `stream`: an absent body is handed over as an empty stream.\n const required = bodyType !== \"text\" && bodyType !== \"stream\";\n\n if (bodyType === \"stream\") {\n /**\n * Bytes with no shape to describe: the route takes the body unread, so\n * the document says what it is rather than what is in it. A `stream`\n * route cannot carry a body schema at all — the compiler refuses the\n * pair — so there is never a `schema` to attach here.\n */\n return { required, content: { \"application/octet-stream\": {} } };\n }\n\n if (bodyType === \"text\") {\n return { required, content: { \"text/plain\": schema } };\n }\n\n if (bodyType === \"json\") {\n return { required, content: { \"application/json\": schema } };\n }\n\n const content: ContentMap = { \"multipart/form-data\": schema };\n\n if (!described || !carriesBinary(described)) {\n content[\"application/x-www-form-urlencoded\"] = schema;\n }\n\n return { required, content };\n}\n\n/**\n * Whether any part of an emitted schema describes binary content.\n *\n * Walked with a stack rather than by recursion, and remembering what it has\n * already seen: the shape comes from an adapter, so its depth is the\n * author's to choose and nothing says it is a tree. Recursion would answer\n * a deep one by overflowing the stack, and a self-referential one by never\n * returning — both while `createApp` is still assembling the application.\n */\nfunction carriesBinary(root: unknown): boolean {\n const pending: unknown[] = [root];\n const seen = new Set<unknown>();\n\n while (pending.length > 0) {\n const node = pending.pop();\n\n if (node === null || typeof node !== \"object\" || seen.has(node)) {\n continue;\n }\n\n seen.add(node);\n\n if ((node as { contentEncoding?: unknown }).contentEncoding === \"binary\") {\n return true;\n }\n\n for (const value of Object.values(node)) {\n pending.push(value);\n }\n }\n\n return false;\n}\n\n/**\n * Describes what the route answers with.\n *\n * The declared responses come first; the framework's own failures fill in\n * around them, and never over them — an author who documented `422`\n * himself has said something more specific than this could.\n */\nfunction responses(\n entry: RouteTableEntry,\n options: AppOptions,\n contributed: HookContributions,\n components: SchemaComponents,\n warn: (message: string) => void,\n): Record<string, ResponseObject> {\n const declared: Record<string, ResponseObject> = {};\n const response = entry.def.schema?.response;\n\n if (response && \"~standard\" in response) {\n declared[\"200\"] = jsonResponse(\n response as AnySchema,\n \"Successful response\",\n warn,\n );\n } else if (response) {\n for (const [status, schema] of Object.entries(response)) {\n declared[status] =\n schema === null\n ? { description: describeStatus(status) }\n : jsonResponse(schema, describeStatus(status), warn);\n }\n }\n\n if (!declared[\"200\"] && !hasSuccess(declared)) {\n declared[\"200\"] = { description: \"Successful response\" };\n }\n\n return {\n ...declared,\n ...frameworkFailures(\n entry,\n options,\n contributed,\n declared,\n components,\n warn,\n ),\n };\n}\n\nfunction frameworkFailures(\n entry: RouteTableEntry,\n options: AppOptions,\n contributed: HookContributions,\n declared: Record<string, ResponseObject>,\n components: SchemaComponents,\n warn: (message: string) => void,\n): Record<string, ResponseObject> {\n const schema = entry.def.schema;\n const failures = new Map<string, Failure[]>();\n\n const add = (status: number, failure: Failure): void => {\n const key = String(status);\n\n failures.set(key, [...(failures.get(key) ?? []), failure]);\n };\n\n /** Defines the envelope of one failure once, and refers to it. */\n const envelopeRef = (\n status: number,\n error?: string,\n message?: string,\n ): Record<string, unknown> =>\n components.ref(\n failureName(status, error),\n envelope(status, error, message),\n ) as unknown as Record<string, unknown>;\n\n if (schema?.params || schema?.query || schema?.headers || schema?.body) {\n add(options.validationStatus, {\n description: \"Request failed schema validation\",\n schema: components.ref(\n failureName(options.validationStatus, \"VALIDATION_FAILED\"),\n validationFailed(options.validationStatus),\n ) as unknown as Record<string, unknown>,\n });\n }\n\n for (const requirement of contributed.security) {\n const status = requirement.status ?? 401;\n\n add(status, {\n description:\n requirement.description ??\n `Request did not satisfy ${requirement.name}`,\n schema: envelopeRef(status, requirement.error, requirement.message),\n });\n }\n\n for (const response of contributed.responses) {\n const described = response.schema\n ? emitted(response.schema, \"a hook's response\", warn)\n : undefined;\n\n add(response.status, {\n description: response.description,\n schema:\n described ??\n envelopeRef(response.status, response.error, response.message),\n });\n }\n\n if (schema?.body || entry.def.bodyType) {\n const unparsable = parseFailure(entry.def.bodyType);\n\n if (unparsable) {\n add(400, {\n description: \"Body could not be parsed in the declared shape\",\n schema: envelopeRef(400, unparsable.error, unparsable.message),\n });\n }\n\n add(413, {\n description: \"Body exceeded the configured size limit\",\n schema: envelopeRef(\n 413,\n \"BODY_TOO_LARGE\",\n \"Body exceeds the configured limit\",\n ),\n });\n }\n\n add(500, {\n description: \"The request failed and nothing mapped the failure\",\n schema: envelopeRef(500, \"INTERNAL_SERVER_ERROR\", \"Internal Server Error\"),\n });\n\n const responses: Record<string, ResponseObject> = {};\n\n for (const [status, list] of failures) {\n responses[status] = merge(declared[status], list);\n }\n\n return responses;\n}\n\n/** One way the framework itself can answer. */\ninterface Failure {\n readonly description: string;\n readonly schema: Record<string, unknown>;\n}\n\n/**\n * Folds everything that answers with one status into one response.\n *\n * Three things can meet on a status: what the route declared, and any\n * number of failures the framework fills in — a rejected schema and an\n * unparsable body collide on `400` as soon as an application configures\n * `validation: { status: 400 }`, and a route that answers `404` itself\n * shares that status with nothing while sharing `400` with the parser.\n *\n * All of them are true at once, so the document says so with an `anyOf`\n * rather than picking a winner. The route's own body comes first: it is\n * the answer the endpoint is about, and the failures are what can happen\n * to it. Identical bodies are folded together — a `$ref` repeated is one\n * alternative, not two.\n */\nfunction merge(\n declared: ResponseObject | undefined,\n list: readonly Failure[],\n): ResponseObject {\n const descriptions = [\n ...(declared ? [declared.description] : []),\n ...list.map((failure) => failure.description),\n ];\n\n const schemas = distinct([\n ...declaredSchemas(declared),\n ...list.map((failure) => failure.schema),\n ]);\n\n const [only] = schemas;\n\n return {\n description: descriptions.join(\"; \"),\n ...(schemas.length === 1 && only\n ? { content: { \"application/json\": { schema: only } } }\n : { content: { \"application/json\": { schema: { anyOf: schemas } } } }),\n };\n}\n\n/**\n * The body a declared response describes, if it describes one: a response\n * whose schema could not be emitted contributes its description alone.\n */\nfunction declaredSchemas(\n declared: ResponseObject | undefined,\n): Record<string, unknown>[] {\n const schema = declared?.content?.[\"application/json\"]?.schema;\n\n return schema ? [schema] : [];\n}\n\nfunction distinct(\n schemas: readonly Record<string, unknown>[],\n): Record<string, unknown>[] {\n const seen = new Set<string>();\n\n return schemas.filter((schema) => {\n const fingerprint = JSON.stringify(schema);\n\n if (seen.has(fingerprint)) {\n return false;\n }\n\n seen.add(fingerprint);\n\n return true;\n });\n}\n\nfunction jsonResponse(\n schema: unknown,\n description: string,\n warn: (message: string) => void,\n): ResponseObject {\n const described = emitted(schema, \"a response\", warn);\n\n if (!described) {\n return { description };\n }\n\n return {\n description,\n content: { \"application/json\": { schema: described } },\n };\n}\n\nfunction hasSuccess(declared: Record<string, ResponseObject>): boolean {\n return Object.keys(declared).some((status) => status.startsWith(\"2\"));\n}\n\nfunction describeStatus(status: string): string {\n if (status === \"204\") {\n return \"No content\";\n }\n\n return status.startsWith(\"2\") ? \"Successful response\" : `Response ${status}`;\n}\n\n/**\n * The body of one framework-produced failure, as JSON Schema.\n *\n * The status is a `const`, and so is the code whenever the framework knows\n * which one it will be: a generated client can then discriminate on\n * `error` instead of receiving \"an object with three strings\", and a\n * reader of the document sees `\"BODY_TOO_LARGE\"` rather than `string`.\n *\n * The message is documented by example, never as a `const` — it is wording\n * meant for a human, the one field a client must not match on, and the one\n * this project reserves the right to reword.\n */\nfunction envelope(\n status: number,\n error?: string,\n message?: string,\n): JsonSchemaObject {\n return {\n type: \"object\",\n required: [\"status\", \"message\", \"error\"],\n properties: {\n status: { type: \"integer\", const: status },\n message: message\n ? { type: \"string\", examples: [message] }\n : { type: \"string\" },\n error: error ? { type: \"string\", const: error } : { type: \"string\" },\n },\n };\n}\n\n/** The validation envelope: the standard three fields, plus the issues. */\nfunction validationFailed(status: number): Record<string, unknown> {\n const base = envelope(status, \"VALIDATION_FAILED\", \"Validation failed\");\n\n return {\n ...base,\n required: [\"status\", \"message\", \"error\", \"issues\"],\n properties: {\n ...(base.properties as Record<string, unknown>),\n issues: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"message\", \"path\"],\n properties: {\n message: { type: \"string\" },\n path: { type: \"array\", items: { type: [\"string\", \"number\"] } },\n },\n },\n },\n },\n };\n}\n\n/**\n * How a body of this type fails to parse — or nothing, for a text or a\n * stream body: one is whatever bytes arrived, the other is handed over\n * unread, and neither can be malformed.\n */\nfunction parseFailure(\n bodyType: BodyType | undefined,\n): { error: string; message: string } | undefined {\n if (bodyType === \"text\" || bodyType === \"stream\") {\n return undefined;\n }\n\n return bodyType === \"form\"\n ? { error: \"MALFORMED_FORM\", message: \"Body is not a valid form\" }\n : { error: \"MALFORMED_JSON\", message: \"Body is not valid JSON\" };\n}\n\nfunction capitalize(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction lowerFirst(value: string): string {\n return value.charAt(0).toLowerCase() + value.slice(1);\n}\n",
|
|
10
|
-
"/**\n * The documentation endpoints as a mountable controller.\n *\n * `openapi()` returns data and mounts nothing, which is the right default\n * for a generator but a chore in the ordinary case: every application ends\n * up writing the same controller, holding the same application, to serve\n * the same two routes. {@link docs} is that controller, written once.\n *\n * It is a controller like any other — mounted in `routes`, groupable,\n * hookable — that receives the application through `onMount` and generates\n * the document from it at startup.\n *\n * @module\n */\n\nimport type { App, RouteDef, SchemaConfig, ValidatePath } from \"@tetsujs/core\";\nimport { onMount, route } from \"@tetsujs/core\";\nimport type { OpenApiDocument } from \"./document.ts\";\nimport type { GeneratorWarning } from \"./index.ts\";\nimport { openapi } from \"./index.ts\";\nimport type { DocsAssets, DocsUi } from \"./page.ts\";\nimport { docsPage } from \"./page.ts\";\n\n/** What the documentation controller serves, and from where. */\nexport interface DocsOptions<Path extends string, UiPath extends string> {\n /** Title, version and the rest of the document's `info` block. */\n readonly info: OpenApiDocument[\"info\"];\n\n /** Servers the API is reachable at. */\n readonly servers?: OpenApiDocument[\"servers\"];\n\n /** Where the document is served. Defaults to `/openapi.json`. */\n readonly path?: Path & ValidatePath<Path>;\n\n /** Where the page is served. Defaults to `/docs`. */\n readonly uiPath?: UiPath & ValidatePath<UiPath>;\n\n /** Which renderer the page bootstraps. Defaults to `\"scalar\"`. */\n readonly ui?: DocsUi;\n\n /** Browser title of the page. Defaults to the document's own title. */\n readonly title?: string;\n\n /** Overrides the renderer's CDN URLs, for a pinned or self-hosted copy. */\n readonly assets?: DocsAssets;\n\n /**\n * Documents the two endpoints this controller serves.\n *\n * Off by default: a reader who is looking at the document has already\n * found the document, and an operation for \"the page you are reading\"\n * is one a generated client would happily produce a method for.\n */\n readonly documentSelf?: boolean;\n\n /**\n * What to do with what the generator could not describe.\n *\n * Defaults to one `console.warn` per warning, at startup — a document\n * that silently omits a body is worse than a noisy one.\n */\n readonly onWarning?: (warning: GeneratorWarning) => void;\n}\n\n/**\n * The controller {@link docs} returns: two routes and the `onMount` that\n * fills them.\n *\n * Instantiated through the factory, never directly — the routes carry the\n * configured paths in their types, which only the factory can infer.\n */\nexport class DocsController<Path extends string, UiPath extends string> {\n /** The document itself, as `application/json`. */\n readonly json: RouteDef<Path, SchemaConfig, never, undefined, \"GET\">;\n\n /** The page that renders it. */\n readonly ui: RouteDef<UiPath, SchemaConfig, never, undefined, \"GET\">;\n\n private document: OpenApiDocument | undefined;\n\n constructor(\n private readonly options: DocsOptions<Path, UiPath>,\n documentPath: string,\n uiPath: string,\n page: string,\n ) {\n const hidden = !options.documentSelf;\n\n const json = route({\n method: \"GET\",\n path: \"/openapi.json\",\n docs: { summary: \"This document\", tags: [\"docs\"], hidden },\n handler: () => this.document,\n });\n\n const ui = route({\n method: \"GET\",\n path: \"/docs\",\n docs: { summary: \"Rendered documentation\", tags: [\"docs\"], hidden },\n handler: () =>\n new Response(page, {\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n }),\n });\n\n /**\n * The configured paths, put back on the routes.\n *\n * `route()` validates its path at the type level, and that check\n * cannot be satisfied by a type variable — `ValidatePath<Path>` stays\n * unresolved while `Path` is generic. The routes are therefore built\n * with the default literals and re-typed here, which is also where the\n * configured path lands at runtime.\n */\n this.json = { ...json, path: documentPath } as unknown as this[\"json\"];\n this.ui = { ...ui, path: uiPath } as unknown as this[\"ui\"];\n }\n\n /** Generates the document of the application this was mounted in. */\n [onMount](app: App): void {\n const generated = openapi(app, {\n info: this.options.info,\n ...(this.options.servers ? { servers: this.options.servers } : {}),\n });\n\n this.document = generated.document;\n\n const report = this.options.onWarning ?? warn;\n\n for (const warning of generated.warnings) {\n report(warning);\n }\n }\n}\n\n/**\n * The documentation of an application, as a controller to mount.\n *\n * @example\n * ```ts\n * createApp({\n * routes: [\n * group(\"/api\", { children: [
|
|
9
|
+
"/**\n * One route table entry, described as an OpenAPI operation.\n *\n * Bodies and responses live here; parameters are in `parameters.ts`. The\n * error responses a route can produce without the author writing anything\n * — a rejected schema, an unparsable body, a body over the limit — are\n * derived from what the route declares and from the options the\n * application actually runs with.\n *\n * @module\n */\n\nimport type {\n AnySchema,\n AppOptions,\n BodyType,\n RouteTableEntry,\n} from \"@tetsujs/core\";\nimport type { HookContributions } from \"./annotations.ts\";\nimport { contributionsOf } from \"./annotations.ts\";\nimport type { SchemaComponents } from \"./components.ts\";\nimport { failureName } from \"./components.ts\";\nimport type {\n ContentMap,\n OperationObject,\n ResponseObject,\n} from \"./document.ts\";\nimport type { JsonSchemaObject } from \"./emit.ts\";\nimport { emitted } from \"./emit.ts\";\nimport { partParameters, pathParameters } from \"./parameters.ts\";\n\n/** Describes one route. */\nexport function operationOf(\n entry: RouteTableEntry,\n options: AppOptions,\n components: SchemaComponents,\n ids: OperationIds,\n warn: (message: string) => void,\n): OperationObject {\n const schema = entry.def.schema;\n\n const parameters = [\n ...pathParameters(entry.path, schema?.params, warn),\n ...partParameters(schema?.query, \"query\", warn),\n ...partParameters(schema?.headers, \"header\", warn),\n ...partParameters(schema?.cookies, \"cookie\", warn),\n ];\n\n const body = requestBody(entry, warn);\n const contributed = contributionsOf(entry.hooks, warn);\n\n return {\n operationId: ids.take(entry),\n ...(entry.def.docs?.summary === undefined\n ? {}\n : { summary: entry.def.docs.summary }),\n ...(entry.def.docs?.description === undefined\n ? {}\n : { description: entry.def.docs.description }),\n ...(entry.def.docs?.tags === undefined\n ? {}\n : { tags: entry.def.docs.tags }),\n ...(entry.def.docs?.deprecated === undefined\n ? {}\n : { deprecated: entry.def.docs.deprecated }),\n ...(parameters.length > 0 ? { parameters } : {}),\n ...(body ? { requestBody: body } : {}),\n ...(contributed.security.length > 0\n ? {\n security: contributed.security.map((requirement) => ({\n [requirement.name]: requirement.scopes ?? [],\n })),\n }\n : {}),\n responses: responses(entry, options, contributed, components, warn),\n };\n}\n\n/**\n * Hands out the `operationId` of every route in one document.\n *\n * An `operationId` is a contract: a generated client names its methods\n * after them, so an id that moves breaks code nobody reviewing the change\n * sees. The id is therefore always stated or derived from names the\n * author wrote — never from where a route happened to land:\n *\n * | | |\n * | --- | --- |\n * | `docs.operationId` on the route | taken as written |\n * | a route of a named controller | the name and the field — `authRequestCode` |\n * | a route of an unnamed object | the field — `requestCode` |\n * | a route mounted standalone | its method and path — `postAuthCode` |\n *\n * Two routes arriving at one id is an error, not something to route\n * around. Falling back to the method and path, or numbering the second\n * one, would hand one of them an id that changes when a path is edited or\n * a route is added — silently, in someone else's SDK. The error names\n * both routes and the two ways out: a controller name of its own, or an\n * id stated on the route. The core already refuses two controllers of\n * one name; what reaches this is two stated ids, or a stated id meeting a\n * derived one.\n */\nexport interface OperationIds {\n /** The id for this route, unique within the document being built. */\n take(entry: RouteTableEntry): string;\n}\n\nexport function operationIds(): OperationIds {\n const taken = new Map<string, RouteTableEntry>();\n\n return {\n take(entry: RouteTableEntry): string {\n const id = idOf(entry);\n const holder = taken.get(id);\n\n if (holder !== undefined) {\n throw new Error(\n `operationId \"${id}\" is taken by both ${describe(holder)} and ${describe(entry)} — a generated client would get one method for two operations. Give one of them docs.operationId, or its controller a name of its own.`,\n );\n }\n\n taken.set(id, entry);\n\n return id;\n },\n };\n}\n\n/** The id a route asks for, before anything checks it is free. */\nfunction idOf(entry: RouteTableEntry): string {\n const stated = entry.def.docs?.operationId;\n\n if (stated !== undefined) {\n return stated;\n }\n\n if (entry.name === undefined) {\n return derivedId(entry);\n }\n\n if (entry.controller === undefined) {\n return entry.name;\n }\n\n const controller = entry.controller.replace(/Controller$/, \"\");\n\n return `${lowerFirst(controller)}${capitalize(entry.name)}`;\n}\n\n/** How the error above names a route: the one line that finds it. */\nfunction describe(entry: RouteTableEntry): string {\n return `${entry.method} ${entry.path}`;\n}\n\n/**\n * What a route mounted standalone is named by: it has no field and no\n * controller, and its method and path are what it has.\n */\nfunction derivedId(entry: RouteTableEntry): string {\n return `${entry.method.toLowerCase()}${entry.path\n .split(/[/:*]/)\n .filter(Boolean)\n .map(capitalize)\n .join(\"\")}`;\n}\n\n/**\n * Describes the request body, when the route reads one.\n *\n * The media type follows the declaration, not a guess: `\"form\"` accepts\n * both multipart and urlencoded at runtime, so both are listed — unless\n * the schema declares a binary field, which only multipart can carry.\n */\nfunction requestBody(\n entry: RouteTableEntry,\n warn: (message: string) => void,\n): { required: boolean; content: ContentMap } | undefined {\n const declared = entry.def.schema?.body;\n const bodyType = entry.def.bodyType ?? (declared ? \"json\" : undefined);\n\n if (!bodyType) {\n return undefined;\n }\n\n const described = declared\n ? emitted(declared, \"the body\", warn, \"input\")\n : undefined;\n\n const schema = described ? { schema: described } : {};\n\n // Whether a body must be sent is a question about the wire, not about\n // the schema: `json` and `form` parse it before anything is validated,\n // and an empty one fails that parse with a `400` — so the client has to\n // send something whether or not a shape was declared. `text` has no such\n // step, because an absent body reads as `\"\"` just like an empty one; if\n // that string is unacceptable, the schema is where it says so. Neither\n // has `stream`: an absent body is handed over as an empty stream.\n const required = bodyType !== \"text\" && bodyType !== \"stream\";\n\n if (bodyType === \"stream\") {\n /**\n * Bytes with no shape to describe: the route takes the body unread, so\n * the document says what it is rather than what is in it. A `stream`\n * route cannot carry a body schema at all — the compiler refuses the\n * pair — so there is never a `schema` to attach here.\n */\n return { required, content: { \"application/octet-stream\": {} } };\n }\n\n if (bodyType === \"text\") {\n return { required, content: { \"text/plain\": schema } };\n }\n\n if (bodyType === \"json\") {\n return { required, content: { \"application/json\": schema } };\n }\n\n const content: ContentMap = { \"multipart/form-data\": schema };\n\n if (!described || !carriesBinary(described)) {\n content[\"application/x-www-form-urlencoded\"] = schema;\n }\n\n return { required, content };\n}\n\n/**\n * Whether any part of an emitted schema describes binary content.\n *\n * Walked with a stack rather than by recursion, and remembering what it has\n * already seen: the shape comes from an adapter, so its depth is the\n * author's to choose and nothing says it is a tree. Recursion would answer\n * a deep one by overflowing the stack, and a self-referential one by never\n * returning — both while `createApp` is still assembling the application.\n */\nfunction carriesBinary(root: unknown): boolean {\n const pending: unknown[] = [root];\n const seen = new Set<unknown>();\n\n while (pending.length > 0) {\n const node = pending.pop();\n\n if (node === null || typeof node !== \"object\" || seen.has(node)) {\n continue;\n }\n\n seen.add(node);\n\n if ((node as { contentEncoding?: unknown }).contentEncoding === \"binary\") {\n return true;\n }\n\n for (const value of Object.values(node)) {\n pending.push(value);\n }\n }\n\n return false;\n}\n\n/**\n * Describes what the route answers with.\n *\n * The declared responses come first; the framework's own failures fill in\n * around them, and never over them — an author who documented `422`\n * himself has said something more specific than this could.\n */\nfunction responses(\n entry: RouteTableEntry,\n options: AppOptions,\n contributed: HookContributions,\n components: SchemaComponents,\n warn: (message: string) => void,\n): Record<string, ResponseObject> {\n const declared: Record<string, ResponseObject> = {};\n const response = entry.def.schema?.response;\n\n if (response && \"~standard\" in response) {\n declared[\"200\"] = jsonResponse(\n response as AnySchema,\n \"Successful response\",\n warn,\n );\n } else if (response) {\n for (const [status, schema] of Object.entries(response)) {\n declared[status] =\n schema === null\n ? { description: describeStatus(status) }\n : jsonResponse(schema, describeStatus(status), warn);\n }\n }\n\n if (!declared[\"200\"] && !hasSuccess(declared)) {\n declared[\"200\"] = { description: \"Successful response\" };\n }\n\n return {\n ...declared,\n ...frameworkFailures(\n entry,\n options,\n contributed,\n declared,\n components,\n warn,\n ),\n };\n}\n\nfunction frameworkFailures(\n entry: RouteTableEntry,\n options: AppOptions,\n contributed: HookContributions,\n declared: Record<string, ResponseObject>,\n components: SchemaComponents,\n warn: (message: string) => void,\n): Record<string, ResponseObject> {\n const schema = entry.def.schema;\n const failures = new Map<string, Failure[]>();\n\n const add = (status: number, failure: Failure): void => {\n const key = String(status);\n\n failures.set(key, [...(failures.get(key) ?? []), failure]);\n };\n\n /** Defines the envelope of one failure once, and refers to it. */\n const envelopeRef = (\n status: number,\n error?: string,\n message?: string,\n ): Record<string, unknown> =>\n components.ref(\n failureName(status, error),\n envelope(status, error, message),\n ) as unknown as Record<string, unknown>;\n\n if (schema?.params || schema?.query || schema?.headers || schema?.body) {\n add(options.validationStatus, {\n description: \"Request failed schema validation\",\n schema: components.ref(\n failureName(options.validationStatus, \"VALIDATION_FAILED\"),\n validationFailed(options.validationStatus),\n ) as unknown as Record<string, unknown>,\n });\n }\n\n for (const requirement of contributed.security) {\n const status = requirement.status ?? 401;\n\n add(status, {\n description:\n requirement.description ??\n `Request did not satisfy ${requirement.name}`,\n schema: envelopeRef(status, requirement.error, requirement.message),\n });\n }\n\n for (const response of contributed.responses) {\n const described = response.schema\n ? emitted(response.schema, \"a hook's response\", warn)\n : undefined;\n\n add(response.status, {\n description: response.description,\n schema:\n described ??\n envelopeRef(response.status, response.error, response.message),\n });\n }\n\n if (schema?.body || entry.def.bodyType) {\n const unparsable = parseFailure(entry.def.bodyType);\n\n if (unparsable) {\n add(400, {\n description: \"Body could not be parsed in the declared shape\",\n schema: envelopeRef(400, unparsable.error, unparsable.message),\n });\n }\n\n add(413, {\n description: \"Body exceeded the configured size limit\",\n schema: envelopeRef(\n 413,\n \"BODY_TOO_LARGE\",\n \"Body exceeds the configured limit\",\n ),\n });\n }\n\n add(500, {\n description: \"The request failed and nothing mapped the failure\",\n schema: envelopeRef(500, \"INTERNAL_SERVER_ERROR\", \"Internal Server Error\"),\n });\n\n const responses: Record<string, ResponseObject> = {};\n\n for (const [status, list] of failures) {\n responses[status] = merge(declared[status], list);\n }\n\n return responses;\n}\n\n/** One way the framework itself can answer. */\ninterface Failure {\n readonly description: string;\n readonly schema: Record<string, unknown>;\n}\n\n/**\n * Folds everything that answers with one status into one response.\n *\n * Three things can meet on a status: what the route declared, and any\n * number of failures the framework fills in — a rejected schema and an\n * unparsable body collide on `400` as soon as an application configures\n * `validation: { status: 400 }`, and a route that answers `404` itself\n * shares that status with nothing while sharing `400` with the parser.\n *\n * All of them are true at once, so the document says so with an `anyOf`\n * rather than picking a winner. The route's own body comes first: it is\n * the answer the endpoint is about, and the failures are what can happen\n * to it. Identical bodies are folded together — a `$ref` repeated is one\n * alternative, not two.\n */\nfunction merge(\n declared: ResponseObject | undefined,\n list: readonly Failure[],\n): ResponseObject {\n const descriptions = [\n ...(declared ? [declared.description] : []),\n ...list.map((failure) => failure.description),\n ];\n\n const schemas = distinct([\n ...declaredSchemas(declared),\n ...list.map((failure) => failure.schema),\n ]);\n\n const [only] = schemas;\n\n return {\n description: descriptions.join(\"; \"),\n ...(schemas.length === 1 && only\n ? { content: { \"application/json\": { schema: only } } }\n : { content: { \"application/json\": { schema: { anyOf: schemas } } } }),\n };\n}\n\n/**\n * The body a declared response describes, if it describes one: a response\n * whose schema could not be emitted contributes its description alone.\n */\nfunction declaredSchemas(\n declared: ResponseObject | undefined,\n): Record<string, unknown>[] {\n const schema = declared?.content?.[\"application/json\"]?.schema;\n\n return schema ? [schema] : [];\n}\n\nfunction distinct(\n schemas: readonly Record<string, unknown>[],\n): Record<string, unknown>[] {\n const seen = new Set<string>();\n\n return schemas.filter((schema) => {\n const fingerprint = JSON.stringify(schema);\n\n if (seen.has(fingerprint)) {\n return false;\n }\n\n seen.add(fingerprint);\n\n return true;\n });\n}\n\nfunction jsonResponse(\n schema: unknown,\n description: string,\n warn: (message: string) => void,\n): ResponseObject {\n const described = emitted(schema, \"a response\", warn);\n\n if (!described) {\n return { description };\n }\n\n return {\n description,\n content: { \"application/json\": { schema: described } },\n };\n}\n\nfunction hasSuccess(declared: Record<string, ResponseObject>): boolean {\n return Object.keys(declared).some((status) => status.startsWith(\"2\"));\n}\n\nfunction describeStatus(status: string): string {\n if (status === \"204\") {\n return \"No content\";\n }\n\n return status.startsWith(\"2\") ? \"Successful response\" : `Response ${status}`;\n}\n\n/**\n * The body of one framework-produced failure, as JSON Schema.\n *\n * The status is a `const`, and so is the code whenever the framework knows\n * which one it will be: a generated client can then discriminate on\n * `error` instead of receiving \"an object with three strings\", and a\n * reader of the document sees `\"BODY_TOO_LARGE\"` rather than `string`.\n *\n * The message is documented by example, never as a `const` — it is wording\n * meant for a human, the one field a client must not match on, and the one\n * this project reserves the right to reword.\n */\nfunction envelope(\n status: number,\n error?: string,\n message?: string,\n): JsonSchemaObject {\n return {\n type: \"object\",\n required: [\"status\", \"message\", \"error\"],\n properties: {\n status: { type: \"integer\", const: status },\n message: message\n ? { type: \"string\", examples: [message] }\n : { type: \"string\" },\n error: error ? { type: \"string\", const: error } : { type: \"string\" },\n },\n };\n}\n\n/** The validation envelope: the standard three fields, plus the issues. */\nfunction validationFailed(status: number): Record<string, unknown> {\n const base = envelope(status, \"VALIDATION_FAILED\", \"Validation failed\");\n\n return {\n ...base,\n required: [\"status\", \"message\", \"error\", \"issues\"],\n properties: {\n ...(base.properties as Record<string, unknown>),\n issues: {\n type: \"array\",\n items: {\n type: \"object\",\n required: [\"message\", \"path\"],\n properties: {\n message: { type: \"string\" },\n path: { type: \"array\", items: { type: [\"string\", \"number\"] } },\n },\n },\n },\n },\n };\n}\n\n/**\n * How a body of this type fails to parse — or nothing, for a text or a\n * stream body: one is whatever bytes arrived, the other is handed over\n * unread, and neither can be malformed.\n */\nfunction parseFailure(\n bodyType: BodyType | undefined,\n): { error: string; message: string } | undefined {\n if (bodyType === \"text\" || bodyType === \"stream\") {\n return undefined;\n }\n\n return bodyType === \"form\"\n ? { error: \"MALFORMED_FORM\", message: \"Body is not a valid form\" }\n : { error: \"MALFORMED_JSON\", message: \"Body is not valid JSON\" };\n}\n\nfunction capitalize(value: string): string {\n return value.charAt(0).toUpperCase() + value.slice(1);\n}\n\nfunction lowerFirst(value: string): string {\n return value.charAt(0).toLowerCase() + value.slice(1);\n}\n",
|
|
10
|
+
"/**\n * The documentation endpoints as a mountable controller.\n *\n * `openapi()` returns data and mounts nothing, which is the right default\n * for a generator but a chore in the ordinary case: every application ends\n * up writing the same controller, holding the same application, to serve\n * the same two routes. {@link docs} is that controller, written once.\n *\n * It is a controller like any other — mounted in `routes`, groupable,\n * hookable — that receives the application through `onMount` and generates\n * the document from it at startup.\n *\n * @module\n */\n\nimport type { App, RouteDef, SchemaConfig, ValidatePath } from \"@tetsujs/core\";\nimport { onMount, route } from \"@tetsujs/core\";\nimport type { OpenApiDocument } from \"./document.ts\";\nimport type { GeneratorWarning } from \"./index.ts\";\nimport { openapi } from \"./index.ts\";\nimport type { DocsAssets, DocsUi } from \"./page.ts\";\nimport { docsPage } from \"./page.ts\";\n\n/** What the documentation controller serves, and from where. */\nexport interface DocsOptions<Path extends string, UiPath extends string> {\n /** Title, version and the rest of the document's `info` block. */\n readonly info: OpenApiDocument[\"info\"];\n\n /** Servers the API is reachable at. */\n readonly servers?: OpenApiDocument[\"servers\"];\n\n /** Where the document is served. Defaults to `/openapi.json`. */\n readonly path?: Path & ValidatePath<Path>;\n\n /** Where the page is served. Defaults to `/docs`. */\n readonly uiPath?: UiPath & ValidatePath<UiPath>;\n\n /** Which renderer the page bootstraps. Defaults to `\"scalar\"`. */\n readonly ui?: DocsUi;\n\n /** Browser title of the page. Defaults to the document's own title. */\n readonly title?: string;\n\n /** Overrides the renderer's CDN URLs, for a pinned or self-hosted copy. */\n readonly assets?: DocsAssets;\n\n /**\n * Documents the two endpoints this controller serves.\n *\n * Off by default: a reader who is looking at the document has already\n * found the document, and an operation for \"the page you are reading\"\n * is one a generated client would happily produce a method for.\n */\n readonly documentSelf?: boolean;\n\n /**\n * What to do with what the generator could not describe.\n *\n * Defaults to one `console.warn` per warning, at startup — a document\n * that silently omits a body is worse than a noisy one.\n */\n readonly onWarning?: (warning: GeneratorWarning) => void;\n}\n\n/**\n * The controller {@link docs} returns: two routes and the `onMount` that\n * fills them.\n *\n * Instantiated through the factory, never directly — the routes carry the\n * configured paths in their types, which only the factory can infer.\n */\nexport class DocsController<Path extends string, UiPath extends string> {\n /** The document itself, as `application/json`. */\n readonly json: RouteDef<Path, SchemaConfig, never, undefined, \"GET\">;\n\n /** The page that renders it. */\n readonly ui: RouteDef<UiPath, SchemaConfig, never, undefined, \"GET\">;\n\n private document: OpenApiDocument | undefined;\n\n constructor(\n private readonly options: DocsOptions<Path, UiPath>,\n documentPath: string,\n uiPath: string,\n page: string,\n ) {\n const hidden = !options.documentSelf;\n\n const json = route({\n method: \"GET\",\n path: \"/openapi.json\",\n docs: { summary: \"This document\", tags: [\"docs\"], hidden },\n handler: () => this.document,\n });\n\n const ui = route({\n method: \"GET\",\n path: \"/docs\",\n docs: { summary: \"Rendered documentation\", tags: [\"docs\"], hidden },\n handler: () =>\n new Response(page, {\n headers: { \"content-type\": \"text/html; charset=utf-8\" },\n }),\n });\n\n /**\n * The configured paths, put back on the routes.\n *\n * `route()` validates its path at the type level, and that check\n * cannot be satisfied by a type variable — `ValidatePath<Path>` stays\n * unresolved while `Path` is generic. The routes are therefore built\n * with the default literals and re-typed here, which is also where the\n * configured path lands at runtime.\n */\n this.json = { ...json, path: documentPath } as unknown as this[\"json\"];\n this.ui = { ...ui, path: uiPath } as unknown as this[\"ui\"];\n }\n\n /** Generates the document of the application this was mounted in. */\n [onMount](app: App): void {\n const generated = openapi(app, {\n info: this.options.info,\n ...(this.options.servers ? { servers: this.options.servers } : {}),\n });\n\n this.document = generated.document;\n\n const report = this.options.onWarning ?? warn;\n\n for (const warning of generated.warnings) {\n report(warning);\n }\n }\n}\n\n/**\n * The documentation of an application, as a controller to mount.\n *\n * @example\n * ```ts\n * createApp({\n * routes: [\n * group(\"/api\", { children: [usersController({ users })] }),\n * docs({ info: { title: \"Users API\", version: \"1.0.0\" } }),\n * ],\n * });\n * ```\n *\n * The document is generated once, at startup, from the application the\n * controller was mounted in. Its own two routes stay out of it — pass\n * `documentSelf` to put them in.\n *\n * The page loads its renderer from a CDN; see {@link docsPage} for the\n * trade and for how to point it at a self-hosted copy.\n */\nexport function docs<\n const Path extends string = \"/openapi.json\",\n const UiPath extends string = \"/docs\",\n>(options: DocsOptions<Path, UiPath>): DocsController<Path, UiPath> {\n const documentPath = options.path ?? \"/openapi.json\";\n const uiPath = options.uiPath ?? \"/docs\";\n\n const page = docsPage({\n ui: options.ui ?? \"scalar\",\n documentUrl: documentPath,\n title: options.title ?? options.info.title,\n ...(options.assets ? { assets: options.assets } : {}),\n });\n\n return new DocsController(options, documentPath, uiPath, page);\n}\n\nfunction warn(warning: GeneratorWarning): void {\n console.warn(`[openapi] ${warning.route}: ${warning.message}`);\n}\n",
|
|
11
11
|
"/**\n * The HTML page that renders a document.\n *\n * Returns a string and mounts nothing: which path serves the page, and\n * whether one is served at all, is the application's decision like every\n * other route.\n *\n * The renderers themselves are loaded from a CDN. Vendoring Scalar or\n * Swagger UI would put megabytes of somebody else's bundle into a\n * dependency-free repository, so the trade is stated instead of hidden:\n * the documentation page — and only it — needs the network, and the URL is\n * a parameter, so a self-hosted copy is one option away. Nothing is\n * fetched unless this function is called.\n *\n * @module\n */\n\n/** The renderers this package knows how to bootstrap. */\nexport type DocsUi = \"scalar\" | \"swagger-ui\" | \"redoc\";\n\n/** Where a renderer is loaded from. */\nexport interface DocsAssets {\n readonly script: string;\n readonly style?: string;\n}\n\n/** What the page needs to know. */\nexport interface DocsPageOptions {\n /** Which renderer to bootstrap. */\n readonly ui: DocsUi;\n\n /** Where the document is served — the page fetches it from there. */\n readonly documentUrl: string;\n\n /** Browser title; defaults to `\"API documentation\"`. */\n readonly title?: string;\n\n /** Overrides the CDN URLs, for a pinned version or a self-hosted copy. */\n readonly assets?: DocsAssets;\n}\n\nconst defaults: Record<DocsUi, DocsAssets> = {\n scalar: {\n script: \"https://cdn.jsdelivr.net/npm/@scalar/api-reference\",\n },\n \"swagger-ui\": {\n script:\n \"https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js\",\n style: \"https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css\",\n },\n redoc: {\n script: \"https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js\",\n },\n};\n\n/**\n * Builds the documentation page.\n *\n * Called by `docs()` for the page it serves; call it directly to serve one\n * from somewhere else.\n *\n * @example\n * ```ts\n * const page = docsPage({ ui: \"scalar\", documentUrl: \"/openapi.json\" });\n *\n * const ui = route({\n * method: \"GET\",\n * path: \"/docs\",\n * handler: () =>\n * new Response(page, { headers: { \"content-type\": \"text/html\" } }),\n * });\n * ```\n */\nexport function docsPage(options: DocsPageOptions): string {\n const assets = options.assets ?? defaults[options.ui];\n const title = escapeHtml(options.title ?? \"API documentation\");\n const url = escapeHtml(options.documentUrl);\n\n return [\n \"<!doctype html>\",\n '<html lang=\"en\">',\n \"<head>\",\n '<meta charset=\"utf-8\" />',\n '<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n `<title>${title}</title>`,\n assets.style\n ? `<link rel=\"stylesheet\" href=\"${escapeHtml(assets.style)}\" />`\n : \"\",\n \"</head>\",\n \"<body>\",\n body(options.ui, url),\n `<script src=\"${escapeHtml(assets.script)}\"></script>`,\n bootstrap(options.ui, url),\n \"</body>\",\n \"</html>\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction body(ui: DocsUi, url: string): string {\n if (ui === \"scalar\") {\n return `<script id=\"api-reference\" data-url=\"${url}\"></script>`;\n }\n\n if (ui === \"redoc\") {\n return `<redoc spec-url=\"${url}\"></redoc>`;\n }\n\n return '<div id=\"swagger-ui\"></div>';\n}\n\nfunction bootstrap(ui: DocsUi, url: string): string {\n if (ui !== \"swagger-ui\") {\n return \"\";\n }\n\n return [\n \"<script>\",\n `SwaggerUIBundle({ url: \"${url}\", dom_id: \"#swagger-ui\" });`,\n \"</script>\",\n ].join(\"\\n\");\n}\n\n/**\n * Escapes the values that reach the markup.\n *\n * The document URL and the title come from the application, but they are\n * still values interpolated into HTML — a quote in either would break out\n * of the attribute it sits in.\n */\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&\")\n .replaceAll(\"<\", \"<\")\n .replaceAll(\">\", \">\")\n .replaceAll('\"', \""\")\n .replaceAll(\"'\", \"'\");\n}\n",
|
|
12
12
|
"/**\n * OpenAPI generation for the framework.\n *\n * Reads a compiled application — `app.entries` and the options it actually\n * runs with — and returns an OpenAPI 3.1 document. Nothing is registered,\n * served or mutated: the document is data, and serving it is a route the\n * application declares like any other.\n *\n * Serving it is a route the application declares — or `docs()`, the\n * controller that declares those routes for you.\n *\n * @example\n * ```ts\n * const { document, warnings } = openapi(app, {\n * info: { title: \"Users API\", version: \"1.0.0\" },\n * });\n * ```\n *\n * @module\n */\n\nimport type { App } from \"@tetsujs/core\";\nimport { contributionsOf } from \"./annotations.ts\";\nimport { schemaComponents } from \"./components.ts\";\nimport type { OpenApiDocument, PathItemObject } from \"./document.ts\";\nimport { operationIds, operationOf } from \"./operation.ts\";\nimport { toTemplate } from \"./parameters.ts\";\n\nexport type {\n DocumentedResponse,\n HookContributions,\n HookDocs,\n SecurityRequirement,\n SecurityScheme,\n} from \"./annotations.ts\";\nexport { documented, secured } from \"./annotations.ts\";\nexport type { DocsOptions } from \"./controller.ts\";\nexport { DocsController, docs } from \"./controller.ts\";\nexport type {\n ContentMap,\n DocumentInfo,\n DocumentServer,\n OpenApiDocument,\n OperationObject,\n ParameterObject,\n PathItemObject,\n ResponseObject,\n} from \"./document.ts\";\nexport type { DocsAssets, DocsPageOptions, DocsUi } from \"./page.ts\";\nexport { docsPage } from \"./page.ts\";\n\n/**\n * What the generator cannot read off the application.\n */\nexport interface OpenApiOptions {\n /** Title, version and the rest of the document's `info` block. */\n readonly info: OpenApiDocument[\"info\"];\n\n /** Servers the API is reachable at. */\n readonly servers?: OpenApiDocument[\"servers\"];\n}\n\n/**\n * What the generator could not describe faithfully.\n *\n * Each entry names a route and what was missing — almost always a schema\n * from a validator that emits no JSON Schema. Documentation that silently\n * omits a body is worse than documentation that says it does not know, so\n * these are returned rather than swallowed.\n */\nexport interface GeneratorWarning {\n readonly route: string;\n readonly message: string;\n}\n\n/** A generated document together with what could not be described. */\nexport interface GeneratorResult {\n readonly document: OpenApiDocument;\n readonly warnings: readonly GeneratorWarning[];\n}\n\n/**\n * Generates the OpenAPI document of an application.\n *\n * Socket endpoints are skipped: a handshake is a `GET` in the table, but\n * OpenAPI has no way to describe what happens after it, and documenting\n * the upgrade as an ordinary operation would describe a request that\n * clients must not make. AsyncAPI is that format, and a separate document.\n *\n * So are routes marked `docs: { hidden: true }`, and everything they would\n * have contributed with them: a security scheme only a hidden route\n * requires has nothing left to describe.\n *\n * Every route of the table becomes an operation under its path template,\n * with the parameters, body and responses its schemas describe. What the\n * framework itself can answer — a validation failure, an unparsable body,\n * a body over the limit — is filled in from the options the application\n * runs with, so the document states the status actually configured.\n */\nexport function openapi(app: App, options: OpenApiOptions): GeneratorResult {\n const paths: Record<string, PathItemObject> = {};\n const warnings: GeneratorWarning[] = [];\n const schemes: Record<string, unknown> = {};\n const components = schemaComponents();\n const ids = operationIds();\n\n // Which route already occupies each slot of the document. Two routes the\n // router keeps apart can still meet here, because a template says less\n // than a path does: `/files/*` and `/files/:wildcard` both become\n // `/files/{wildcard}`, and a literal `/users/{id}` — which Bun matches as\n // the string it is — is spelled the same as `/users/:id`. The second one\n // then replaces the first, and the document quietly loses an endpoint\n // that works. It still loses it; it no longer does so without saying.\n const occupied = new Map<string, string>();\n\n for (const entry of app.entries) {\n if (entry.ws || entry.def.docs?.hidden) {\n continue;\n }\n\n const template = toTemplate(entry.path);\n const route = `${entry.method} ${entry.path}`;\n\n const operation = operationOf(\n entry,\n app.options,\n components,\n ids,\n (message) => {\n warnings.push({ route, message });\n },\n );\n\n // First definition wins, as it does among the hooks of one route: a\n // name is the identity of a scheme, and the operations that reference\n // it say only the name. A second, different scheme under that name\n // cannot be described without renaming it out from under everything\n // already pointing at it, so it is refused rather than silently\n // swapped in — which used to make every earlier operation describe an\n // authentication its route does not perform.\n for (const requirement of contributionsOf(entry.hooks).security) {\n const defined = schemes[requirement.name];\n\n if (defined === undefined) {\n schemes[requirement.name] = requirement.scheme;\n\n continue;\n }\n\n if (JSON.stringify(defined) !== JSON.stringify(requirement.scheme)) {\n warnings.push({\n route,\n message: `the security scheme \"${requirement.name}\" is already defined differently; this definition is ignored`,\n });\n }\n }\n\n const slot = `${entry.method.toLowerCase()} ${template}`;\n const taken = occupied.get(slot);\n\n if (taken) {\n warnings.push({\n route,\n message: `${template} is already described by ${taken}, whose operation this replaces`,\n });\n }\n\n occupied.set(slot, route);\n\n paths[template] = {\n ...paths[template],\n [entry.method.toLowerCase()]: operation,\n };\n }\n\n return {\n document: {\n openapi: \"3.1.0\",\n info: options.info,\n ...(options.servers ? { servers: options.servers } : {}),\n paths,\n ...componentsOf(schemes, components.schemas),\n },\n warnings,\n };\n}\n\n/**\n * The `components` block, present only when something goes in it: the\n * security schemes the guards registered, and the failure envelopes the\n * operations refer to.\n */\nfunction componentsOf(\n securitySchemes: Record<string, unknown>,\n schemas: Record<string, unknown>,\n): { components?: Record<string, unknown> } {\n const components = {\n ...(Object.keys(securitySchemes).length > 0 ? { securitySchemes } : {}),\n ...(Object.keys(schemas).length > 0 ? { schemas } : {}),\n };\n\n return Object.keys(components).length > 0 ? { components } : {};\n}\n"
|
|
13
13
|
],
|
|
14
|
-
"mappings": "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",
|
|
15
|
-
"debugId": "
|
|
14
|
+
"mappings": ";;AAgGA,IAAM,UAAU;AAmBT,SAAS,UAA6B,CAAC,MAAS,MAAmB;AAAA,EACxE,MAAM,YAAY,KAAK,KAAK;AAAA,EAE5B,OAAO,eAAe,WAAW,SAAS;AAAA,IACxC,YAAY;AAAA,IACZ,OAAO,KAAK,OAAO,IAAI,MAAM,KAAK;AAAA,EACpC,CAAC;AAAA,EAED,OAAO;AAAA;AAsBF,SAAS,OAA0B,CACxC,MACA,aACG;AAAA,EACH,OAAO,WAAW,MAAM,EAAE,UAAU,YAAY,CAAC;AAAA;AAI5C,SAAS,MAAM,CAAC,MAAqC;AAAA,EAC1D,OAAQ,KAAyD;AAAA;AA0B5D,SAAS,eAAe,CAC7B,QACA,MACmB;AAAA,EACnB,MAAM,WAAW,IAAI;AAAA,EACrB,MAAM,YAAY,IAAI;AAAA,EAEtB,WAAW,QAAQ,OAAO,OAAO,MAAM,GAAG;AAAA,IACxC,WAAW,QAAQ,MAAM;AAAA,MACvB,MAAM,OAAO,OAAO,IAAI;AAAA,MAExB,IAAI,CAAC,MAAM;AAAA,QACT;AAAA,MACF;AAAA,MAEA,IAAI,KAAK,UAAU;AAAA,QACjB,MAAM,UAAU,SAAS,IAAI,KAAK,SAAS,IAAI;AAAA,QAE/C,IAAI,CAAC,SAAS;AAAA,UACZ,SAAS,IAAI,KAAK,SAAS,MAAM,KAAK,QAAQ;AAAA,QAChD,EAAO,SACL,KAAK,UAAU,QAAQ,MAAM,MAC7B,KAAK,UAAU,KAAK,SAAS,MAAM,GACnC;AAAA,UACA,OACE,wCAAwC,KAAK,SAAS,gEACxD;AAAA,QACF;AAAA,MACF;AAAA,MAEA,WAAW,YAAY,KAAK,aAAa,CAAC,GAAG;AAAA,QAC3C,MAAM,MAAM,GAAG,SAAS,UAAU,SAAS;AAAA,QAE3C,IAAI,CAAC,UAAU,IAAI,GAAG,GAAG;AAAA,UACvB,UAAU,IAAI,KAAK,QAAQ;AAAA,QAC7B;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,UAAU,CAAC,GAAG,SAAS,OAAO,CAAC;AAAA,IAC/B,WAAW,CAAC,GAAG,UAAU,OAAO,CAAC;AAAA,EACnC;AAAA;;;ACzLK,SAAS,gBAAgB,GAAqB;AAAA,EACnD,MAAM,UAAmC,CAAC;AAAA,EAC1C,MAAM,aAAa,IAAI;AAAA,EAEvB,OAAO;AAAA,IACL;AAAA,IAEA,GAAG,CAAC,WAAmB,QAA4C;AAAA,MACjE,MAAM,cAAc,KAAK,UAAU,MAAM;AAAA,MACzC,MAAM,UAAU,WAAW,IAAI,WAAW;AAAA,MAE1C,IAAI,SAAS;AAAA,QACX,OAAO,EAAE,MAAM,wBAAwB,UAAU;AAAA,MACnD;AAAA,MAEA,IAAI,OAAO;AAAA,MAEX,SAAS,UAAU,EAAG,QAAQ,SAAS,WAAW,GAAG;AAAA,QACnD,OAAO,GAAG,YAAY;AAAA,MACxB;AAAA,MAEA,QAAQ,QAAQ;AAAA,MAChB,WAAW,IAAI,aAAa,IAAI;AAAA,MAEhC,OAAO,EAAE,MAAM,wBAAwB,OAAO;AAAA;AAAA,EAElD;AAAA;AAWK,SAAS,WAAW,CAAC,QAAgB,OAAwB;AAAA,EAClE,MAAM,QAAQ,SAAS,IACpB,YAAY,EACZ,MAAM,YAAY,EAClB,OAAO,OAAO,EACd,IAAI,CAAC,SAAS,KAAK,OAAO,CAAC,EAAE,YAAY,IAAI,KAAK,MAAM,CAAC,CAAC,EAC1D,KAAK,EAAE;AAAA,EAOV,OAAO,QAAQ,UAAU;AAAA;;;AChF3B;AAWA,IAAM,SAAS,EAAE,QAAQ,gBAAgB;AAkClC,SAAS,OAAO,CACrB,QACA,SACA,MACA,YAAuB,UACO;AAAA,EAC9B,IAAI;AAAA,EAEJ,IAAI;AAAA,IACF,YAAY,aAAa,QAAQ,QAAQ,SAAS;AAAA,IAClD,OAAO,OAAO;AAAA,IACd,KAAK,GAAG,8CAA8C,OAAO,KAAK,GAAG;AAAA,IAErE;AAAA;AAAA,EAGF,IAAI,CAAC,WAAW;AAAA,IACd,KAAK,GAAG,0CAA0C;AAAA,IAElD;AAAA,EACF;AAAA,EAEA,OAAO,eAAe,SAAS;AAAA;AAUjC,SAAS,cAAc,CAAC,QAA4C;AAAA,EAClE,IAAI,EAAE,aAAa,SAAS;AAAA,IAC1B,OAAO;AAAA,EACT;AAAA,EAEA,QAAQ,SAAS,MAAM,SAAS;AAAA,EAEhC,OAAO;AAAA;AAGT,SAAS,MAAM,CAAC,OAAwB;AAAA,EACtC,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA;;;AChFvD,IAAM,eAAe;AASrB,SAAS,UAAU,CAAC,MAAsB;AAAA,EAC/C,OAAO,KACJ,MAAM,GAAG,EACT,IAAI,CAAC,YAAY;AAAA,IAChB,IAAI,QAAQ,WAAW,GAAG,GAAG;AAAA,MAC3B,OAAO,IAAI,QAAQ,MAAM,CAAC;AAAA,IAC5B;AAAA,IAEA,OAAO,YAAY,MAAM,IAAI,kBAAkB;AAAA,GAChD,EACA,KAAK,GAAG;AAAA;AAIN,SAAS,aAAa,CAAC,MAAwB;AAAA,EACpD,MAAM,QAAkB,CAAC;AAAA,EAEzB,WAAW,WAAW,KAAK,MAAM,GAAG,GAAG;AAAA,IACrC,IAAI,QAAQ,WAAW,GAAG,GAAG;AAAA,MAC3B,MAAM,KAAK,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC7B,EAAO,SAAI,YAAY,KAAK;AAAA,MAC1B,MAAM,KAAK,YAAY;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAUF,SAAS,cAAc,CAC5B,MACA,QACA,MACmB;AAAA,EACnB,MAAM,aAAa,iBAAiB,QAAQ,UAAU,IAAI;AAAA,EAE1D,OAAO,cAAc,IAAI,EAAE,IAAI,CAAC,UAAU;AAAA,IACxC;AAAA,IACA,IAAI;AAAA,IACJ,UAAU;AAAA,OACN,SAAS,eACT;AAAA,MACE,aACE;AAAA,IACJ,IACA,CAAC;AAAA,IACL,QAAQ,WAAW,SAAS,EAAE,MAAM,SAAS;AAAA,EAC/C,EAAE;AAAA;AAgBG,SAAS,cAAc,CAC5B,QACA,OACA,MACmB;AAAA,EACnB,IAAI,CAAC,QAAQ;AAAA,IACX,OAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,YAAY,QAAQ,QAAQ,OAAO,SAAS,MAAM,OAAO;AAAA,EAE/D,IAAI,CAAC,WAAW;AAAA,IACd,OAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,aAAc,UAAU,cAAc,CAAC;AAAA,EAI7C,MAAM,WAAW,IAAI,IACnB,MAAM,QAAQ,UAAU,QAAQ,IAAK,UAAU,WAAwB,CAAC,CAC1E;AAAA,EAEA,OAAO,OAAO,QAAQ,UAAU,EAAE,IAAI,EAAE,MAAM,eAAe;AAAA,IAC3D;AAAA,IACA,IAAI;AAAA,IACJ,UAAU,SAAS,IAAI,IAAI;AAAA,IAC3B,QAAQ;AAAA,EACV,EAAE;AAAA;AAGJ,SAAS,gBAAgB,CACvB,QACA,OACA,MACyC;AAAA,EACzC,IAAI,CAAC,QAAQ;AAAA,IACX,OAAO,CAAC;AAAA,EACV;AAAA,EAEA,MAAM,YAAY,QAAQ,QAAQ,OAAO,SAAS,MAAM,OAAO;AAAA,EAE/D,IAAI,CAAC,WAAW;AAAA,IACd,OAAO,CAAC;AAAA,EACV;AAAA,EAEA,OAAQ,UAAU,cAAc,CAAC;AAAA;;;AC5G5B,SAAS,WAAW,CACzB,OACA,SACA,YACA,KACA,MACiB;AAAA,EACjB,MAAM,SAAS,MAAM,IAAI;AAAA,EAEzB,MAAM,aAAa;AAAA,IACjB,GAAG,eAAe,MAAM,MAAM,QAAQ,QAAQ,IAAI;AAAA,IAClD,GAAG,eAAe,QAAQ,OAAO,SAAS,IAAI;AAAA,IAC9C,GAAG,eAAe,QAAQ,SAAS,UAAU,IAAI;AAAA,IACjD,GAAG,eAAe,QAAQ,SAAS,UAAU,IAAI;AAAA,EACnD;AAAA,EAEA,MAAM,OAAO,YAAY,OAAO,IAAI;AAAA,EACpC,MAAM,cAAc,gBAAgB,MAAM,OAAO,IAAI;AAAA,EAErD,OAAO;AAAA,IACL,aAAa,IAAI,KAAK,KAAK;AAAA,OACvB,MAAM,IAAI,MAAM,YAAY,YAC5B,CAAC,IACD,EAAE,SAAS,MAAM,IAAI,KAAK,QAAQ;AAAA,OAClC,MAAM,IAAI,MAAM,gBAAgB,YAChC,CAAC,IACD,EAAE,aAAa,MAAM,IAAI,KAAK,YAAY;AAAA,OAC1C,MAAM,IAAI,MAAM,SAAS,YACzB,CAAC,IACD,EAAE,MAAM,MAAM,IAAI,KAAK,KAAK;AAAA,OAC5B,MAAM,IAAI,MAAM,eAAe,YAC/B,CAAC,IACD,EAAE,YAAY,MAAM,IAAI,KAAK,WAAW;AAAA,OACxC,WAAW,SAAS,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA,OAC1C,OAAO,EAAE,aAAa,KAAK,IAAI,CAAC;AAAA,OAChC,YAAY,SAAS,SAAS,IAC9B;AAAA,MACE,UAAU,YAAY,SAAS,IAAI,CAAC,iBAAiB;AAAA,SAClD,YAAY,OAAO,YAAY,UAAU,CAAC;AAAA,MAC7C,EAAE;AAAA,IACJ,IACA,CAAC;AAAA,IACL,WAAW,UAAU,OAAO,SAAS,aAAa,YAAY,IAAI;AAAA,EACpE;AAAA;AAgCK,SAAS,YAAY,GAAiB;AAAA,EAC3C,MAAM,QAAQ,IAAI;AAAA,EAElB,OAAO;AAAA,IACL,IAAI,CAAC,OAAgC;AAAA,MACnC,MAAM,KAAK,KAAK,KAAK;AAAA,MACrB,MAAM,SAAS,MAAM,IAAI,EAAE;AAAA,MAE3B,IAAI,WAAW,WAAW;AAAA,QACxB,MAAM,IAAI,MACR,gBAAgB,wBAAwB,SAAS,MAAM,SAAS,SAAS,KAAK,8IAChF;AAAA,MACF;AAAA,MAEA,MAAM,IAAI,IAAI,KAAK;AAAA,MAEnB,OAAO;AAAA;AAAA,EAEX;AAAA;AAIF,SAAS,IAAI,CAAC,OAAgC;AAAA,EAC5C,MAAM,SAAS,MAAM,IAAI,MAAM;AAAA,EAE/B,IAAI,WAAW,WAAW;AAAA,IACxB,OAAO;AAAA,EACT;AAAA,EAEA,IAAI,MAAM,SAAS,WAAW;AAAA,IAC5B,OAAO,UAAU,KAAK;AAAA,EACxB;AAAA,EAEA,IAAI,MAAM,eAAe,WAAW;AAAA,IAClC,OAAO,MAAM;AAAA,EACf;AAAA,EAEA,MAAM,aAAa,MAAM,WAAW,QAAQ,eAAe,EAAE;AAAA,EAE7D,OAAO,GAAG,WAAW,UAAU,IAAI,WAAW,MAAM,IAAI;AAAA;AAI1D,SAAS,QAAQ,CAAC,OAAgC;AAAA,EAChD,OAAO,GAAG,MAAM,UAAU,MAAM;AAAA;AAOlC,SAAS,SAAS,CAAC,OAAgC;AAAA,EACjD,OAAO,GAAG,MAAM,OAAO,YAAY,IAAI,MAAM,KAC1C,MAAM,OAAO,EACb,OAAO,OAAO,EACd,IAAI,UAAU,EACd,KAAK,EAAE;AAAA;AAUZ,SAAS,WAAW,CAClB,OACA,MACwD;AAAA,EACxD,MAAM,WAAW,MAAM,IAAI,QAAQ;AAAA,EACnC,MAAM,WAAW,MAAM,IAAI,aAAa,WAAW,SAAS;AAAA,EAE5D,IAAI,CAAC,UAAU;AAAA,IACb;AAAA,EACF;AAAA,EAEA,MAAM,YAAY,WACd,QAAQ,UAAU,YAAY,MAAM,OAAO,IAC3C;AAAA,EAEJ,MAAM,SAAS,YAAY,EAAE,QAAQ,UAAU,IAAI,CAAC;AAAA,EASpD,MAAM,WAAW,aAAa,UAAU,aAAa;AAAA,EAErD,IAAI,aAAa,UAAU;AAAA,IAOzB,OAAO,EAAE,UAAU,SAAS,EAAE,4BAA4B,CAAC,EAAE,EAAE;AAAA,EACjE;AAAA,EAEA,IAAI,aAAa,QAAQ;AAAA,IACvB,OAAO,EAAE,UAAU,SAAS,EAAE,cAAc,OAAO,EAAE;AAAA,EACvD;AAAA,EAEA,IAAI,aAAa,QAAQ;AAAA,IACvB,OAAO,EAAE,UAAU,SAAS,EAAE,oBAAoB,OAAO,EAAE;AAAA,EAC7D;AAAA,EAEA,MAAM,UAAsB,EAAE,uBAAuB,OAAO;AAAA,EAE5D,IAAI,CAAC,aAAa,CAAC,cAAc,SAAS,GAAG;AAAA,IAC3C,QAAQ,uCAAuC;AAAA,EACjD;AAAA,EAEA,OAAO,EAAE,UAAU,QAAQ;AAAA;AAY7B,SAAS,aAAa,CAAC,MAAwB;AAAA,EAC7C,MAAM,UAAqB,CAAC,IAAI;AAAA,EAChC,MAAM,OAAO,IAAI;AAAA,EAEjB,OAAO,QAAQ,SAAS,GAAG;AAAA,IACzB,MAAM,OAAO,QAAQ,IAAI;AAAA,IAEzB,IAAI,SAAS,QAAQ,OAAO,SAAS,YAAY,KAAK,IAAI,IAAI,GAAG;AAAA,MAC/D;AAAA,IACF;AAAA,IAEA,KAAK,IAAI,IAAI;AAAA,IAEb,IAAK,KAAuC,oBAAoB,UAAU;AAAA,MACxE,OAAO;AAAA,IACT;AAAA,IAEA,WAAW,SAAS,OAAO,OAAO,IAAI,GAAG;AAAA,MACvC,QAAQ,KAAK,KAAK;AAAA,IACpB;AAAA,EACF;AAAA,EAEA,OAAO;AAAA;AAUT,SAAS,SAAS,CAChB,OACA,SACA,aACA,YACA,MACgC;AAAA,EAChC,MAAM,WAA2C,CAAC;AAAA,EAClD,MAAM,WAAW,MAAM,IAAI,QAAQ;AAAA,EAEnC,IAAI,YAAY,eAAe,UAAU;AAAA,IACvC,SAAS,SAAS,aAChB,UACA,uBACA,IACF;AAAA,EACF,EAAO,SAAI,UAAU;AAAA,IACnB,YAAY,QAAQ,WAAW,OAAO,QAAQ,QAAQ,GAAG;AAAA,MACvD,SAAS,UACP,WAAW,OACP,EAAE,aAAa,eAAe,MAAM,EAAE,IACtC,aAAa,QAAQ,eAAe,MAAM,GAAG,IAAI;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,IAAI,CAAC,SAAS,UAAU,CAAC,WAAW,QAAQ,GAAG;AAAA,IAC7C,SAAS,SAAS,EAAE,aAAa,sBAAsB;AAAA,EACzD;AAAA,EAEA,OAAO;AAAA,OACF;AAAA,OACA,kBACD,OACA,SACA,aACA,UACA,YACA,IACF;AAAA,EACF;AAAA;AAGF,SAAS,iBAAiB,CACxB,OACA,SACA,aACA,UACA,YACA,MACgC;AAAA,EAChC,MAAM,SAAS,MAAM,IAAI;AAAA,EACzB,MAAM,WAAW,IAAI;AAAA,EAErB,MAAM,MAAM,CAAC,QAAgB,YAA2B;AAAA,IACtD,MAAM,MAAM,OAAO,MAAM;AAAA,IAEzB,SAAS,IAAI,KAAK,CAAC,GAAI,SAAS,IAAI,GAAG,KAAK,CAAC,GAAI,OAAO,CAAC;AAAA;AAAA,EAI3D,MAAM,cAAc,CAClB,QACA,OACA,YAEA,WAAW,IACT,YAAY,QAAQ,KAAK,GACzB,SAAS,QAAQ,OAAO,OAAO,CACjC;AAAA,EAEF,IAAI,QAAQ,UAAU,QAAQ,SAAS,QAAQ,WAAW,QAAQ,MAAM;AAAA,IACtE,IAAI,QAAQ,kBAAkB;AAAA,MAC5B,aAAa;AAAA,MACb,QAAQ,WAAW,IACjB,YAAY,QAAQ,kBAAkB,mBAAmB,GACzD,iBAAiB,QAAQ,gBAAgB,CAC3C;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,eAAe,YAAY,UAAU;AAAA,IAC9C,MAAM,SAAS,YAAY,UAAU;AAAA,IAErC,IAAI,QAAQ;AAAA,MACV,aACE,YAAY,eACZ,2BAA2B,YAAY;AAAA,MACzC,QAAQ,YAAY,QAAQ,YAAY,OAAO,YAAY,OAAO;AAAA,IACpE,CAAC;AAAA,EACH;AAAA,EAEA,WAAW,YAAY,YAAY,WAAW;AAAA,IAC5C,MAAM,YAAY,SAAS,SACvB,QAAQ,SAAS,QAAQ,qBAAqB,IAAI,IAClD;AAAA,IAEJ,IAAI,SAAS,QAAQ;AAAA,MACnB,aAAa,SAAS;AAAA,MACtB,QACE,aACA,YAAY,SAAS,QAAQ,SAAS,OAAO,SAAS,OAAO;AAAA,IACjE,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,QAAQ,QAAQ,MAAM,IAAI,UAAU;AAAA,IACtC,MAAM,aAAa,aAAa,MAAM,IAAI,QAAQ;AAAA,IAElD,IAAI,YAAY;AAAA,MACd,IAAI,KAAK;AAAA,QACP,aAAa;AAAA,QACb,QAAQ,YAAY,KAAK,WAAW,OAAO,WAAW,OAAO;AAAA,MAC/D,CAAC;AAAA,IACH;AAAA,IAEA,IAAI,KAAK;AAAA,MACP,aAAa;AAAA,MACb,QAAQ,YACN,KACA,kBACA,mCACF;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEA,IAAI,KAAK;AAAA,IACP,aAAa;AAAA,IACb,QAAQ,YAAY,KAAK,yBAAyB,uBAAuB;AAAA,EAC3E,CAAC;AAAA,EAED,MAAM,YAA4C,CAAC;AAAA,EAEnD,YAAY,QAAQ,SAAS,UAAU;AAAA,IACrC,UAAU,UAAU,MAAM,SAAS,SAAS,IAAI;AAAA,EAClD;AAAA,EAEA,OAAO;AAAA;AAwBT,SAAS,KAAK,CACZ,UACA,MACgB;AAAA,EAChB,MAAM,eAAe;AAAA,IACnB,GAAI,WAAW,CAAC,SAAS,WAAW,IAAI,CAAC;AAAA,IACzC,GAAG,KAAK,IAAI,CAAC,YAAY,QAAQ,WAAW;AAAA,EAC9C;AAAA,EAEA,MAAM,UAAU,SAAS;AAAA,IACvB,GAAG,gBAAgB,QAAQ;AAAA,IAC3B,GAAG,KAAK,IAAI,CAAC,YAAY,QAAQ,MAAM;AAAA,EACzC,CAAC;AAAA,EAED,OAAO,QAAQ;AAAA,EAEf,OAAO;AAAA,IACL,aAAa,aAAa,KAAK,IAAI;AAAA,OAC/B,QAAQ,WAAW,KAAK,OACxB,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,KAAK,EAAE,EAAE,IACpD,EAAE,SAAS,EAAE,oBAAoB,EAAE,QAAQ,EAAE,OAAO,QAAQ,EAAE,EAAE,EAAE;AAAA,EACxE;AAAA;AAOF,SAAS,eAAe,CACtB,UAC2B;AAAA,EAC3B,MAAM,SAAS,UAAU,UAAU,qBAAqB;AAAA,EAExD,OAAO,SAAS,CAAC,MAAM,IAAI,CAAC;AAAA;AAG9B,SAAS,QAAQ,CACf,SAC2B;AAAA,EAC3B,MAAM,OAAO,IAAI;AAAA,EAEjB,OAAO,QAAQ,OAAO,CAAC,WAAW;AAAA,IAChC,MAAM,cAAc,KAAK,UAAU,MAAM;AAAA,IAEzC,IAAI,KAAK,IAAI,WAAW,GAAG;AAAA,MACzB,OAAO;AAAA,IACT;AAAA,IAEA,KAAK,IAAI,WAAW;AAAA,IAEpB,OAAO;AAAA,GACR;AAAA;AAGH,SAAS,YAAY,CACnB,QACA,aACA,MACgB;AAAA,EAChB,MAAM,YAAY,QAAQ,QAAQ,cAAc,IAAI;AAAA,EAEpD,IAAI,CAAC,WAAW;AAAA,IACd,OAAO,EAAE,YAAY;AAAA,EACvB;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA,SAAS,EAAE,oBAAoB,EAAE,QAAQ,UAAU,EAAE;AAAA,EACvD;AAAA;AAGF,SAAS,UAAU,CAAC,UAAmD;AAAA,EACrE,OAAO,OAAO,KAAK,QAAQ,EAAE,KAAK,CAAC,WAAW,OAAO,WAAW,GAAG,CAAC;AAAA;AAGtE,SAAS,cAAc,CAAC,QAAwB;AAAA,EAC9C,IAAI,WAAW,OAAO;AAAA,IACpB,OAAO;AAAA,EACT;AAAA,EAEA,OAAO,OAAO,WAAW,GAAG,IAAI,wBAAwB,YAAY;AAAA;AAetE,SAAS,QAAQ,CACf,QACA,OACA,SACkB;AAAA,EAClB,OAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU,CAAC,UAAU,WAAW,OAAO;AAAA,IACvC,YAAY;AAAA,MACV,QAAQ,EAAE,MAAM,WAAW,OAAO,OAAO;AAAA,MACzC,SAAS,UACL,EAAE,MAAM,UAAU,UAAU,CAAC,OAAO,EAAE,IACtC,EAAE,MAAM,SAAS;AAAA,MACrB,OAAO,QAAQ,EAAE,MAAM,UAAU,OAAO,MAAM,IAAI,EAAE,MAAM,SAAS;AAAA,IACrE;AAAA,EACF;AAAA;AAIF,SAAS,gBAAgB,CAAC,QAAyC;AAAA,EACjE,MAAM,OAAO,SAAS,QAAQ,qBAAqB,mBAAmB;AAAA,EAEtE,OAAO;AAAA,OACF;AAAA,IACH,UAAU,CAAC,UAAU,WAAW,SAAS,QAAQ;AAAA,IACjD,YAAY;AAAA,SACN,KAAK;AAAA,MACT,QAAQ;AAAA,QACN,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,UAAU,CAAC,WAAW,MAAM;AAAA,UAC5B,YAAY;AAAA,YACV,SAAS,EAAE,MAAM,SAAS;AAAA,YAC1B,MAAM,EAAE,MAAM,SAAS,OAAO,EAAE,MAAM,CAAC,UAAU,QAAQ,EAAE,EAAE;AAAA,UAC/D;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAQF,SAAS,YAAY,CACnB,UACgD;AAAA,EAChD,IAAI,aAAa,UAAU,aAAa,UAAU;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,OAAO,aAAa,SAChB,EAAE,OAAO,kBAAkB,SAAS,2BAA2B,IAC/D,EAAE,OAAO,kBAAkB,SAAS,yBAAyB;AAAA;AAGnE,SAAS,UAAU,CAAC,OAAuB;AAAA,EACzC,OAAO,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AAAA;AAGtD,SAAS,UAAU,CAAC,OAAuB;AAAA,EACzC,OAAO,MAAM,OAAO,CAAC,EAAE,YAAY,IAAI,MAAM,MAAM,CAAC;AAAA;;ACzjBtD;;;ACyBA,IAAM,WAAuC;AAAA,EAC3C,QAAQ;AAAA,IACN,QAAQ;AAAA,EACV;AAAA,EACA,cAAc;AAAA,IACZ,QACE;AAAA,IACF,OAAO;AAAA,EACT;AAAA,EACA,OAAO;AAAA,IACL,QAAQ;AAAA,EACV;AACF;AAoBO,SAAS,QAAQ,CAAC,SAAkC;AAAA,EACzD,MAAM,SAAS,QAAQ,UAAU,SAAS,QAAQ;AAAA,EAClD,MAAM,QAAQ,WAAW,QAAQ,SAAS,mBAAmB;AAAA,EAC7D,MAAM,MAAM,WAAW,QAAQ,WAAW;AAAA,EAE1C,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,OAAO,QACH,gCAAgC,WAAW,OAAO,KAAK,UACvD;AAAA,IACJ;AAAA,IACA;AAAA,IACA,KAAK,QAAQ,IAAI,GAAG;AAAA,IACpB,gBAAgB,WAAW,OAAO,MAAM;AAAA,IACxC,UAAU,QAAQ,IAAI,GAAG;AAAA,IACzB;AAAA,IACA;AAAA,EACF,EACG,OAAO,OAAO,EACd,KAAK;AAAA,CAAI;AAAA;AAGd,SAAS,IAAI,CAAC,IAAY,KAAqB;AAAA,EAC7C,IAAI,OAAO,UAAU;AAAA,IACnB,OAAO,wCAAwC;AAAA,EACjD;AAAA,EAEA,IAAI,OAAO,SAAS;AAAA,IAClB,OAAO,oBAAoB;AAAA,EAC7B;AAAA,EAEA,OAAO;AAAA;AAGT,SAAS,SAAS,CAAC,IAAY,KAAqB;AAAA,EAClD,IAAI,OAAO,cAAc;AAAA,IACvB,OAAO;AAAA,EACT;AAAA,EAEA,OAAO;AAAA,IACL;AAAA,IACA,2BAA2B;AAAA,IAC3B;AAAA,EACF,EAAE,KAAK;AAAA,CAAI;AAAA;AAUb,SAAS,UAAU,CAAC,OAAuB;AAAA,EACzC,OAAO,MACJ,WAAW,KAAK,OAAO,EACvB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,MAAM,EACtB,WAAW,KAAK,QAAQ,EACxB,WAAW,KAAK,OAAO;AAAA;;;ADlErB,MAAM,eAA2D;AAAA,EAUnD;AAAA,EARV;AAAA,EAGA;AAAA,EAED;AAAA,EAER,WAAW,CACQ,SACjB,cACA,QACA,MACA;AAAA,IAJiB;AAAA,IAKjB,MAAM,SAAS,CAAC,QAAQ;AAAA,IAExB,MAAM,OAAO,MAAM;AAAA,MACjB,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM,EAAE,SAAS,iBAAiB,MAAM,CAAC,MAAM,GAAG,OAAO;AAAA,MACzD,SAAS,MAAM,KAAK;AAAA,IACtB,CAAC;AAAA,IAED,MAAM,KAAK,MAAM;AAAA,MACf,QAAQ;AAAA,MACR,MAAM;AAAA,MACN,MAAM,EAAE,SAAS,0BAA0B,MAAM,CAAC,MAAM,GAAG,OAAO;AAAA,MAClE,SAAS,MACP,IAAI,SAAS,MAAM;AAAA,QACjB,SAAS,EAAE,gBAAgB,2BAA2B;AAAA,MACxD,CAAC;AAAA,IACL,CAAC;AAAA,IAWD,KAAK,OAAO,KAAK,MAAM,MAAM,aAAa;AAAA,IAC1C,KAAK,KAAK,KAAK,IAAI,MAAM,OAAO;AAAA;AAAA,GAIjC,QAAQ,CAAC,KAAgB;AAAA,IACxB,MAAM,YAAY,QAAQ,KAAK;AAAA,MAC7B,MAAM,KAAK,QAAQ;AAAA,SACf,KAAK,QAAQ,UAAU,EAAE,SAAS,KAAK,QAAQ,QAAQ,IAAI,CAAC;AAAA,IAClE,CAAC;AAAA,IAED,KAAK,WAAW,UAAU;AAAA,IAE1B,MAAM,SAAS,KAAK,QAAQ,aAAa;AAAA,IAEzC,WAAW,WAAW,UAAU,UAAU;AAAA,MACxC,OAAO,OAAO;AAAA,IAChB;AAAA;AAEJ;AAsBO,SAAS,IAGf,CAAC,SAAkE;AAAA,EAClE,MAAM,eAAe,QAAQ,QAAQ;AAAA,EACrC,MAAM,SAAS,QAAQ,UAAU;AAAA,EAEjC,MAAM,OAAO,SAAS;AAAA,IACpB,IAAI,QAAQ,MAAM;AAAA,IAClB,aAAa;AAAA,IACb,OAAO,QAAQ,SAAS,QAAQ,KAAK;AAAA,OACjC,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,EACrD,CAAC;AAAA,EAED,OAAO,IAAI,eAAe,SAAS,cAAc,QAAQ,IAAI;AAAA;AAG/D,SAAS,IAAI,CAAC,SAAiC;AAAA,EAC7C,QAAQ,KAAK,aAAa,QAAQ,UAAU,QAAQ,SAAS;AAAA;;;AE1ExD,SAAS,OAAO,CAAC,KAAU,SAA0C;AAAA,EAC1E,MAAM,QAAwC,CAAC;AAAA,EAC/C,MAAM,WAA+B,CAAC;AAAA,EACtC,MAAM,UAAmC,CAAC;AAAA,EAC1C,MAAM,aAAa,iBAAiB;AAAA,EACpC,MAAM,MAAM,aAAa;AAAA,EASzB,MAAM,WAAW,IAAI;AAAA,EAErB,WAAW,SAAS,IAAI,SAAS;AAAA,IAC/B,IAAI,MAAM,MAAM,MAAM,IAAI,MAAM,QAAQ;AAAA,MACtC;AAAA,IACF;AAAA,IAEA,MAAM,WAAW,WAAW,MAAM,IAAI;AAAA,IACtC,MAAM,QAAQ,GAAG,MAAM,UAAU,MAAM;AAAA,IAEvC,MAAM,YAAY,YAChB,OACA,IAAI,SACJ,YACA,KACA,CAAC,YAAY;AAAA,MACX,SAAS,KAAK,EAAE,OAAO,QAAQ,CAAC;AAAA,KAEpC;AAAA,IASA,WAAW,eAAe,gBAAgB,MAAM,KAAK,EAAE,UAAU;AAAA,MAC/D,MAAM,UAAU,QAAQ,YAAY;AAAA,MAEpC,IAAI,YAAY,WAAW;AAAA,QACzB,QAAQ,YAAY,QAAQ,YAAY;AAAA,QAExC;AAAA,MACF;AAAA,MAEA,IAAI,KAAK,UAAU,OAAO,MAAM,KAAK,UAAU,YAAY,MAAM,GAAG;AAAA,QAClE,SAAS,KAAK;AAAA,UACZ;AAAA,UACA,SAAS,wBAAwB,YAAY;AAAA,QAC/C,CAAC;AAAA,MACH;AAAA,IACF;AAAA,IAEA,MAAM,OAAO,GAAG,MAAM,OAAO,YAAY,KAAK;AAAA,IAC9C,MAAM,QAAQ,SAAS,IAAI,IAAI;AAAA,IAE/B,IAAI,OAAO;AAAA,MACT,SAAS,KAAK;AAAA,QACZ;AAAA,QACA,SAAS,GAAG,oCAAoC;AAAA,MAClD,CAAC;AAAA,IACH;AAAA,IAEA,SAAS,IAAI,MAAM,KAAK;AAAA,IAExB,MAAM,YAAY;AAAA,SACb,MAAM;AAAA,OACR,MAAM,OAAO,YAAY,IAAI;AAAA,IAChC;AAAA,EACF;AAAA,EAEA,OAAO;AAAA,IACL,UAAU;AAAA,MACR,SAAS;AAAA,MACT,MAAM,QAAQ;AAAA,SACV,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACtD;AAAA,SACG,aAAa,SAAS,WAAW,OAAO;AAAA,IAC7C;AAAA,IACA;AAAA,EACF;AAAA;AAQF,SAAS,YAAY,CACnB,iBACA,SAC0C;AAAA,EAC1C,MAAM,aAAa;AAAA,OACb,OAAO,KAAK,eAAe,EAAE,SAAS,IAAI,EAAE,gBAAgB,IAAI,CAAC;AAAA,OACjE,OAAO,KAAK,OAAO,EAAE,SAAS,IAAI,EAAE,QAAQ,IAAI,CAAC;AAAA,EACvD;AAAA,EAEA,OAAO,OAAO,KAAK,UAAU,EAAE,SAAS,IAAI,EAAE,WAAW,IAAI,CAAC;AAAA;",
|
|
15
|
+
"debugId": "7F7340DB513FEEB464756E2164756E21",
|
|
16
16
|
"names": []
|
|
17
17
|
}
|
package/dist/src/operation.d.ts
CHANGED
|
@@ -17,19 +17,26 @@ export declare function operationOf(entry: RouteTableEntry, options: AppOptions,
|
|
|
17
17
|
/**
|
|
18
18
|
* Hands out the `operationId` of every route in one document.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* controller), and two controller classes in different modules may simply
|
|
25
|
-
* share a class name. Either way the pair repeats while the routes differ.
|
|
20
|
+
* An `operationId` is a contract: a generated client names its methods
|
|
21
|
+
* after them, so an id that moves breaks code nobody reviewing the change
|
|
22
|
+
* sees. The id is therefore always stated or derived from names the
|
|
23
|
+
* author wrote — never from where a route happened to land:
|
|
26
24
|
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* method
|
|
25
|
+
* | | |
|
|
26
|
+
* | --- | --- |
|
|
27
|
+
* | `docs.operationId` on the route | taken as written |
|
|
28
|
+
* | a route of a named controller | the name and the field — `authRequestCode` |
|
|
29
|
+
* | a route of an unnamed object | the field — `requestCode` |
|
|
30
|
+
* | a route mounted standalone | its method and path — `postAuthCode` |
|
|
31
|
+
*
|
|
32
|
+
* Two routes arriving at one id is an error, not something to route
|
|
33
|
+
* around. Falling back to the method and path, or numbering the second
|
|
34
|
+
* one, would hand one of them an id that changes when a path is edited or
|
|
35
|
+
* a route is added — silently, in someone else's SDK. The error names
|
|
36
|
+
* both routes and the two ways out: a controller name of its own, or an
|
|
37
|
+
* id stated on the route. The core already refuses two controllers of
|
|
38
|
+
* one name; what reaches this is two stated ids, or a stated id meeting a
|
|
39
|
+
* derived one.
|
|
33
40
|
*/
|
|
34
41
|
export interface OperationIds {
|
|
35
42
|
/** The id for this route, unique within the document being built. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/operation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAEV,UAAU,EAEV,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,eAAe,CAAC;AAKvB,2BAA2B;AAC3B,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,gBAAgB,EAC5B,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,eAAe,CAsCjB;AAED
|
|
1
|
+
{"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../../src/operation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAEV,UAAU,EAEV,eAAe,EAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAExD,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,eAAe,CAAC;AAKvB,2BAA2B;AAC3B,wBAAgB,WAAW,CACzB,KAAK,EAAE,eAAe,EACtB,OAAO,EAAE,UAAU,EACnB,UAAU,EAAE,gBAAgB,EAC5B,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,GAC9B,eAAe,CAsCjB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,IAAI,CAAC,KAAK,EAAE,eAAe,GAAG,MAAM,CAAC;CACtC;AAED,wBAAgB,YAAY,IAAI,YAAY,CAmB3C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tetsujs/openapi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "An OpenAPI 3.1 document and docs page generated from Tetsu routes",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
}
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@tetsujs/core": "^0.
|
|
37
|
+
"@tetsujs/core": "^0.4.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@tetsujs/core": "0.
|
|
40
|
+
"@tetsujs/core": "0.4.0"
|
|
41
41
|
},
|
|
42
42
|
"publishConfig": {
|
|
43
43
|
"access": "public"
|
package/src/controller.ts
CHANGED
|
@@ -140,7 +140,7 @@ export class DocsController<Path extends string, UiPath extends string> {
|
|
|
140
140
|
* ```ts
|
|
141
141
|
* createApp({
|
|
142
142
|
* routes: [
|
|
143
|
-
* group("/api", { children: [
|
|
143
|
+
* group("/api", { children: [usersController({ users })] }),
|
|
144
144
|
* docs({ info: { title: "Users API", version: "1.0.0" } }),
|
|
145
145
|
* ],
|
|
146
146
|
* });
|
package/src/operation.ts
CHANGED
|
@@ -79,19 +79,26 @@ export function operationOf(
|
|
|
79
79
|
/**
|
|
80
80
|
* Hands out the `operationId` of every route in one document.
|
|
81
81
|
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
* controller), and two controller classes in different modules may simply
|
|
87
|
-
* share a class name. Either way the pair repeats while the routes differ.
|
|
82
|
+
* An `operationId` is a contract: a generated client names its methods
|
|
83
|
+
* after them, so an id that moves breaks code nobody reviewing the change
|
|
84
|
+
* sees. The id is therefore always stated or derived from names the
|
|
85
|
+
* author wrote — never from where a route happened to land:
|
|
88
86
|
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* method
|
|
87
|
+
* | | |
|
|
88
|
+
* | --- | --- |
|
|
89
|
+
* | `docs.operationId` on the route | taken as written |
|
|
90
|
+
* | a route of a named controller | the name and the field — `authRequestCode` |
|
|
91
|
+
* | a route of an unnamed object | the field — `requestCode` |
|
|
92
|
+
* | a route mounted standalone | its method and path — `postAuthCode` |
|
|
93
|
+
*
|
|
94
|
+
* Two routes arriving at one id is an error, not something to route
|
|
95
|
+
* around. Falling back to the method and path, or numbering the second
|
|
96
|
+
* one, would hand one of them an id that changes when a path is edited or
|
|
97
|
+
* a route is added — silently, in someone else's SDK. The error names
|
|
98
|
+
* both routes and the two ways out: a controller name of its own, or an
|
|
99
|
+
* id stated on the route. The core already refuses two controllers of
|
|
100
|
+
* one name; what reaches this is two stated ids, or a stated id meeting a
|
|
101
|
+
* derived one.
|
|
95
102
|
*/
|
|
96
103
|
export interface OperationIds {
|
|
97
104
|
/** The id for this route, unique within the document being built. */
|
|
@@ -99,46 +106,40 @@ export interface OperationIds {
|
|
|
99
106
|
}
|
|
100
107
|
|
|
101
108
|
export function operationIds(): OperationIds {
|
|
102
|
-
const taken = new
|
|
109
|
+
const taken = new Map<string, RouteTableEntry>();
|
|
103
110
|
|
|
104
111
|
return {
|
|
105
112
|
take(entry: RouteTableEntry): string {
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
if (!taken.has(preferred)) {
|
|
109
|
-
taken.add(preferred);
|
|
110
|
-
|
|
111
|
-
return preferred;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const derived = derivedId(entry);
|
|
113
|
+
const id = idOf(entry);
|
|
114
|
+
const holder = taken.get(id);
|
|
115
115
|
|
|
116
|
-
if (
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
116
|
+
if (holder !== undefined) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`operationId "${id}" is taken by both ${describe(holder)} and ${describe(entry)} — a generated client would get one method for two operations. Give one of them docs.operationId, or its controller a name of its own.`,
|
|
119
|
+
);
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
for (let attempt = 2; taken.has(name); attempt += 1) {
|
|
125
|
-
name = `${derived}${attempt}`;
|
|
126
|
-
}
|
|
122
|
+
taken.set(id, entry);
|
|
127
123
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return name;
|
|
124
|
+
return id;
|
|
131
125
|
},
|
|
132
126
|
};
|
|
133
127
|
}
|
|
134
128
|
|
|
135
|
-
/**
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
129
|
+
/** The id a route asks for, before anything checks it is free. */
|
|
130
|
+
function idOf(entry: RouteTableEntry): string {
|
|
131
|
+
const stated = entry.def.docs?.operationId;
|
|
132
|
+
|
|
133
|
+
if (stated !== undefined) {
|
|
134
|
+
return stated;
|
|
135
|
+
}
|
|
136
|
+
|
|
140
137
|
if (entry.name === undefined) {
|
|
141
|
-
return
|
|
138
|
+
return derivedId(entry);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (entry.controller === undefined) {
|
|
142
|
+
return entry.name;
|
|
142
143
|
}
|
|
143
144
|
|
|
144
145
|
const controller = entry.controller.replace(/Controller$/, "");
|
|
@@ -146,11 +147,14 @@ function declaredId(entry: RouteTableEntry): string | undefined {
|
|
|
146
147
|
return `${lowerFirst(controller)}${capitalize(entry.name)}`;
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
/** How the error above names a route: the one line that finds it. */
|
|
151
|
+
function describe(entry: RouteTableEntry): string {
|
|
152
|
+
return `${entry.method} ${entry.path}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
149
155
|
/**
|
|
150
|
-
* What
|
|
151
|
-
*
|
|
152
|
-
* Also the name of a route mounted standalone, which has no field to be
|
|
153
|
-
* called after.
|
|
156
|
+
* What a route mounted standalone is named by: it has no field and no
|
|
157
|
+
* controller, and its method and path are what it has.
|
|
154
158
|
*/
|
|
155
159
|
function derivedId(entry: RouteTableEntry): string {
|
|
156
160
|
return `${entry.method.toLowerCase()}${entry.path
|