@zudojs/openapi 0.1.0 → 1.2.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.
Files changed (130) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -21
  3. package/dist/index.d.ts +26 -21
  4. package/dist/index.js +31 -13
  5. package/dist/openApiComponents/index.d.ts +2 -2
  6. package/dist/openApiComponents/index.js +1 -1
  7. package/dist/openApiConstants/index.d.ts +1 -1
  8. package/dist/openApiConstants/index.js +1 -1
  9. package/dist/openApiConstants/openApiConstants.core.d.ts +15 -3
  10. package/dist/openApiConstants/openApiConstants.core.js +22 -3
  11. package/dist/openApiDocument/index.d.ts +1 -1
  12. package/dist/openApiDocument/index.js +1 -1
  13. package/dist/openApiDocument/openApiDocument.builder.d.ts +39 -52
  14. package/dist/openApiDocument/openApiDocument.builder.js +73 -72
  15. package/dist/openApiErrors/index.d.ts +2 -2
  16. package/dist/openApiErrors/index.js +2 -1
  17. package/dist/openApiErrors/openApiError.base.d.ts +9 -0
  18. package/dist/openApiErrors/openApiError.base.js +7 -0
  19. package/dist/openApiErrors/openApiError.types.d.ts +34 -14
  20. package/dist/openApiErrors/openApiError.types.js +83 -35
  21. package/dist/openApiHttp/index.d.ts +2 -2
  22. package/dist/openApiHttp/index.js +2 -2
  23. package/dist/openApiHttp/openApiHttpAdapter.core.d.ts +129 -25
  24. package/dist/openApiHttp/openApiHttpAdapter.core.js +226 -16
  25. package/dist/openApiRegistry/openApiRegistry.core.d.ts +47 -5
  26. package/dist/openApiRegistry/openApiRegistry.core.js +180 -66
  27. package/dist/openApiRegistry/openApiRegistry.type.d.ts +18 -13
  28. package/dist/openApiRegistry/openApiRegistry.type.js +1 -2
  29. package/dist/openApiRouting/index.d.ts +2 -2
  30. package/dist/openApiRouting/index.js +1 -1
  31. package/dist/openApiRouting/routeConverter.core.d.ts +25 -7
  32. package/dist/openApiRouting/routeConverter.core.js +104 -58
  33. package/dist/openApiRouting/routeMetadata.type.d.ts +40 -17
  34. package/dist/openApiRouting/routeMetadata.type.js +9 -0
  35. package/dist/openApiRouting/routeScanner.core.d.ts +17 -47
  36. package/dist/openApiRouting/routeScanner.core.js +40 -55
  37. package/dist/openApiSchema/index.d.ts +4 -4
  38. package/dist/openApiSchema/index.js +2 -2
  39. package/dist/openApiSchema/references.core.d.ts +18 -1
  40. package/dist/openApiSchema/references.core.js +21 -1
  41. package/dist/openApiSchema/schemaConverter.core.d.ts +61 -3
  42. package/dist/openApiSchema/schemaConverter.core.js +523 -117
  43. package/dist/openApiSchema/schemaRegistry.core.d.ts +27 -10
  44. package/dist/openApiSchema/schemaRegistry.core.js +33 -14
  45. package/dist/openApiSerialization/openApiSerializer.core.d.ts +10 -4
  46. package/dist/openApiSerialization/openApiSerializer.core.js +158 -9
  47. package/dist/openApiTypes/index.d.ts +1 -1
  48. package/dist/openApiTypes/openApiDocument.type.d.ts +16 -0
  49. package/dist/openApiTypes/openApiSchema.type.d.ts +24 -1
  50. package/dist/openApiTypes/openApiSchema.type.js +3 -0
  51. package/dist/openApiUi/index.d.ts +8 -0
  52. package/dist/openApiUi/index.js +8 -0
  53. package/dist/openApiUi/openApiUi.brand.d.ts +26 -0
  54. package/dist/openApiUi/openApiUi.brand.js +44 -0
  55. package/dist/openApiUi/openApiUi.core.d.ts +47 -0
  56. package/dist/openApiUi/openApiUi.core.js +157 -0
  57. package/dist/openApiValidation/index.d.ts +2 -2
  58. package/dist/openApiValidation/index.js +1 -1
  59. package/dist/openApiValidation/openApiValidator.core.d.ts +33 -11
  60. package/dist/openApiValidation/openApiValidator.core.js +295 -66
  61. package/package.json +29 -18
  62. package/dist/.tsbuildinfo +0 -1
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/index.js.map +0 -1
  65. package/dist/openApiComponents/index.d.ts.map +0 -1
  66. package/dist/openApiComponents/index.js.map +0 -1
  67. package/dist/openApiConstants/index.d.ts.map +0 -1
  68. package/dist/openApiConstants/index.js.map +0 -1
  69. package/dist/openApiConstants/openApiConstants.core.d.ts.map +0 -1
  70. package/dist/openApiConstants/openApiConstants.core.js.map +0 -1
  71. package/dist/openApiDocument/index.d.ts.map +0 -1
  72. package/dist/openApiDocument/index.js.map +0 -1
  73. package/dist/openApiDocument/openApiDocument.builder.d.ts.map +0 -1
  74. package/dist/openApiDocument/openApiDocument.builder.js.map +0 -1
  75. package/dist/openApiErrors/index.d.ts.map +0 -1
  76. package/dist/openApiErrors/index.js.map +0 -1
  77. package/dist/openApiErrors/openApiError.base.d.ts.map +0 -1
  78. package/dist/openApiErrors/openApiError.base.js.map +0 -1
  79. package/dist/openApiErrors/openApiError.core.d.ts.map +0 -1
  80. package/dist/openApiErrors/openApiError.core.js.map +0 -1
  81. package/dist/openApiErrors/openApiError.types.d.ts.map +0 -1
  82. package/dist/openApiErrors/openApiError.types.js.map +0 -1
  83. package/dist/openApiHttp/index.d.ts.map +0 -1
  84. package/dist/openApiHttp/index.js.map +0 -1
  85. package/dist/openApiHttp/openApiHttpAdapter.core.d.ts.map +0 -1
  86. package/dist/openApiHttp/openApiHttpAdapter.core.js.map +0 -1
  87. package/dist/openApiRegistry/index.d.ts.map +0 -1
  88. package/dist/openApiRegistry/index.js.map +0 -1
  89. package/dist/openApiRegistry/openApiRegistry.core.d.ts.map +0 -1
  90. package/dist/openApiRegistry/openApiRegistry.core.js.map +0 -1
  91. package/dist/openApiRegistry/openApiRegistry.type.d.ts.map +0 -1
  92. package/dist/openApiRegistry/openApiRegistry.type.js.map +0 -1
  93. package/dist/openApiRouting/index.d.ts.map +0 -1
  94. package/dist/openApiRouting/index.js.map +0 -1
  95. package/dist/openApiRouting/routeConverter.core.d.ts.map +0 -1
  96. package/dist/openApiRouting/routeConverter.core.js.map +0 -1
  97. package/dist/openApiRouting/routeMetadata.type.d.ts.map +0 -1
  98. package/dist/openApiRouting/routeMetadata.type.js.map +0 -1
  99. package/dist/openApiRouting/routeScanner.core.d.ts.map +0 -1
  100. package/dist/openApiRouting/routeScanner.core.js.map +0 -1
  101. package/dist/openApiSchema/index.d.ts.map +0 -1
  102. package/dist/openApiSchema/index.js.map +0 -1
  103. package/dist/openApiSchema/references.core.d.ts.map +0 -1
  104. package/dist/openApiSchema/references.core.js.map +0 -1
  105. package/dist/openApiSchema/schemaConverter.core.d.ts.map +0 -1
  106. package/dist/openApiSchema/schemaConverter.core.js.map +0 -1
  107. package/dist/openApiSchema/schemaRegistry.core.d.ts.map +0 -1
  108. package/dist/openApiSchema/schemaRegistry.core.js.map +0 -1
  109. package/dist/openApiSerialization/index.d.ts.map +0 -1
  110. package/dist/openApiSerialization/index.js.map +0 -1
  111. package/dist/openApiSerialization/openApiSerializer.core.d.ts.map +0 -1
  112. package/dist/openApiSerialization/openApiSerializer.core.js.map +0 -1
  113. package/dist/openApiTypes/index.d.ts.map +0 -1
  114. package/dist/openApiTypes/index.js.map +0 -1
  115. package/dist/openApiTypes/openApiComponent.type.d.ts.map +0 -1
  116. package/dist/openApiTypes/openApiComponent.type.js.map +0 -1
  117. package/dist/openApiTypes/openApiDocument.type.d.ts.map +0 -1
  118. package/dist/openApiTypes/openApiDocument.type.js.map +0 -1
  119. package/dist/openApiTypes/openApiPath.type.d.ts.map +0 -1
  120. package/dist/openApiTypes/openApiPath.type.js.map +0 -1
  121. package/dist/openApiTypes/openApiSchema.type.d.ts.map +0 -1
  122. package/dist/openApiTypes/openApiSchema.type.js.map +0 -1
  123. package/dist/openApiTypes/openApiSecurity.type.d.ts.map +0 -1
  124. package/dist/openApiTypes/openApiSecurity.type.js.map +0 -1
  125. package/dist/openApiTypes/openApiTypes.core.d.ts.map +0 -1
  126. package/dist/openApiTypes/openApiTypes.core.js.map +0 -1
  127. package/dist/openApiValidation/index.d.ts.map +0 -1
  128. package/dist/openApiValidation/index.js.map +0 -1
  129. package/dist/openApiValidation/openApiValidator.core.d.ts.map +0 -1
  130. package/dist/openApiValidation/openApiValidator.core.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zudojs Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @zudojs/openapi
2
2
 
3
- OpenAPI 3.1 specification generation, validation, and SDK generation for Zudojs applications.
3
+ OpenAPI 3.0 and 3.1 specification generation, validation, and serialization for Zudojs applications.
4
4
 
5
5
  ## Installation
6
6
 
@@ -10,35 +10,354 @@ npm install @zudojs/openapi
10
10
 
11
11
  ## Quick Start
12
12
 
13
+ `OpenAPIManager` coordinates route collection, schema conversion, document
14
+ assembly, validation and serving.
15
+
13
16
  ```typescript
14
- import { createOpenAPIRouter } from "@zudojs/openapi";
17
+ import { OpenAPIManager } from "@zudojs/openapi";
15
18
 
16
- const router = createOpenAPIRouter({
17
- info: { title: "My API", version: "1.0.0" },
19
+ const manager = new OpenAPIManager({
20
+ version: "3.1.0",
21
+ info: { title: "Orders API", version: "1.2.0" },
22
+ servers: [{ url: "https://api.example.com" }],
18
23
  });
19
24
 
20
- router.get(
21
- "/users/:id",
22
- {
23
- responses: { 200: { schema: UserSchema } },
24
- },
25
- async (ctx) => {
26
- return ctx.params.id;
25
+ manager.addRoute({
26
+ method: "get",
27
+ path: "/users/:id",
28
+ metadata: {
29
+ openapi: {
30
+ operationId: "users.get",
31
+ summary: "Get a user",
32
+ tags: ["users"],
33
+ parameters: [{ name: "id", in: "path", schema: { type: "string" } }],
34
+ responses: {
35
+ "200": { description: "User found" },
36
+ "404": { description: "No such user" },
37
+ },
38
+ },
27
39
  },
40
+ });
41
+
42
+ const document = manager.generate(true); // true = validate while generating
43
+ const json = manager.toJSON();
44
+ const yaml = manager.toYAML();
45
+ ```
46
+
47
+ Every documented response reaches the document — `4xx`, `5xx` and `default`
48
+ included. `:id` becomes `{id}`, and a path parameter is marked required
49
+ because the specification requires it.
50
+
51
+ Generation is idempotent: call `generate()` as often as you like.
52
+
53
+ ## Serving the document
54
+
55
+ ```typescript
56
+ const response = manager.toResponse({ format: "json" });
57
+ // { status: 200, headers: { "content-type", "cache-control" }, body }
58
+ ```
59
+
60
+ `{ status, headers, body }` is framework-agnostic; hand it to whichever HTTP
61
+ adapter you use.
62
+
63
+ ## Serving a documentation page
64
+
65
+ `toUIResponse` returns the same `{ status, headers, body }` shape carrying a
66
+ complete HTML page that reads the specification from `specUrl`. Pair it with
67
+ `toResponse`, which serves the specification itself:
68
+
69
+ ```typescript
70
+ app.get("/openapi.json", () => manager.toResponse());
71
+ app.get("/docs", () => manager.toUIResponse({ specUrl: "/openapi.json" }));
72
+ ```
73
+
74
+ Swagger UI is rendered by default; pass `renderer: "redoc"` for ReDoc.
75
+
76
+ ```typescript
77
+ manager.toUIResponse({ specUrl: "/openapi.json", renderer: "redoc" });
78
+ ```
79
+
80
+ `renderOpenAPIUI(options)` returns the HTML string on its own, without a
81
+ manager. Both accept the same options:
82
+
83
+ | Option | Meaning | Default |
84
+ | ---------------- | -------------------------------------------------- | ------------------ |
85
+ | `specUrl` | Where the page fetches the document from (required) | — |
86
+ | `title` | Page title and header text | `"API reference"` |
87
+ | `renderer` | `"swagger"` or `"redoc"` | `"swagger"` |
88
+ | `logo` | Header logo, or `false` for none | Zudo wordmark |
89
+ | `favicon` | Favicon URL or data URI, or `false` | Zudo favicon |
90
+ | `customCss` | CSS appended after the built-in theme | — |
91
+ | `assetsBaseUrl` | Where the viewer's own JS and CSS load from | public CDN |
92
+ | `swaggerOptions` | Forwarded to `SwaggerUIBundle`; ignored by ReDoc | — |
93
+
94
+ `assetsBaseUrl` points the viewer's assets at a self-hosted copy, which is what
95
+ an air-gapped deployment needs — the default CDN renders a blank page with no
96
+ egress. Swagger UI loads `swagger-ui.css` and `swagger-ui-bundle.js` from that
97
+ base; ReDoc loads `redoc.standalone.js`.
98
+
99
+ ```typescript
100
+ manager.toUIResponse({ specUrl: "/openapi.json", assetsBaseUrl: "/vendor/swagger" });
101
+ ```
102
+
103
+ Caller-supplied text is escaped, and input that would break out of the page is
104
+ refused rather than mangled: a `javascript:` or `vbscript:` URL throws (the
105
+ scheme is read after stripping the control characters browsers ignore, so
106
+ `java\nscript:` is caught too), a `data:` URL that is not an image throws
107
+ (the assets base lands in `<script src>`), an empty `specUrl` throws, and
108
+ `customCss` containing `</style>` throws — that sequence ends the style block
109
+ and lets the rest be parsed as HTML.
110
+
111
+ ## Branding
112
+
113
+ ReDoc, Scalar and several other viewers read a logo from the non-standard
114
+ `info["x-logo"]` field. Generated documents carry the Zudo mark there by
115
+ default, so a spec opened in one of them shows a logo rather than nothing.
116
+
117
+ ```typescript
118
+ new OpenAPIManager({ info }).generate().info["x-logo"];
119
+ // { url: "data:image/svg+xml;…", href: "https://zudo.dev", altText: "Zudo", … }
120
+ ```
121
+
122
+ The `branding` option controls it, and the same value is used by
123
+ `toUIResponse` for the page header:
124
+
125
+ - omitted or `true` — the Zudo mark
126
+ - `false` — no `x-logo`, and no logo on the page
127
+ - an `OpenAPILogo` (`{ url, href?, altText?, backgroundColor? }`) — your own
128
+
129
+ ```typescript
130
+ new OpenAPIManager({ info, branding: false });
131
+ new OpenAPIManager({
132
+ info,
133
+ branding: { url: "https://acme.example/logo.svg", href: "https://acme.example", altText: "Acme" },
134
+ });
135
+ ```
136
+
137
+ A logo already present on `info["x-logo"]` is never overwritten, whatever
138
+ `branding` says.
139
+
140
+ The brand assets are exported as inline SVG strings and as data URIs, so a page
141
+ can show them without a network request: `ZUDO_MARK_SVG`, `ZUDO_MARK_DARK_SVG`,
142
+ `ZUDO_WORDMARK_SVG`, `ZUDO_WORDMARK_DARK_SVG`, `ZUDO_FAVICON_SVG`, a
143
+ `*_DATA_URI` counterpart for each, plus `ZUDO_SITE_URL`, `zudoLogo(overrides?)`
144
+ and `svgToDataUri(svg)`. The types are `OpenAPIUIOptions`,
145
+ `OpenAPIUIRenderer`, `OpenAPIUIResponse` and `OpenAPILogo`.
146
+
147
+ ## Schemas
148
+
149
+ `addSchema` converts a `@zudojs/schema` schema into an OpenAPI component and
150
+ registers it.
151
+
152
+ ```typescript
153
+ import {
154
+ objectSchema,
155
+ stringSchema,
156
+ numberSchema,
157
+ optionalSchema,
158
+ } from "@zudojs/schema";
159
+
160
+ manager.addSchema(
161
+ "User",
162
+ objectSchema({
163
+ id: stringSchema().uuid(),
164
+ age: numberSchema().int().min(0),
165
+ nickname: optionalSchema(stringSchema()),
166
+ }),
28
167
  );
29
168
  ```
30
169
 
31
- ## Features
170
+ produces
171
+
172
+ ```json
173
+ {
174
+ "type": "object",
175
+ "properties": {
176
+ "id": { "type": "string", "format": "uuid" },
177
+ "age": { "type": "integer", "minimum": 0 },
178
+ "nickname": { "type": "string" }
179
+ },
180
+ "required": ["id", "age"]
181
+ }
182
+ ```
183
+
184
+ Objects, arrays, enums, literals, unions, discriminated unions,
185
+ intersections, records, tuples, sets, optionals, nullables, defaults,
186
+ refinements, transforms, lazy schemas, bigints and the coercion wrappers are
187
+ all converted, along with string and number constraints (`min`, `max`,
188
+ `length`, `pattern`, `format`, `int`, `multipleOf`, `gt`, `lt`). Constraints
189
+ on a coercing schema (`coerce.number().int().min(1)`) are carried through.
190
+
191
+ A property is listed in `required` exactly when the object parser rejects
192
+ its absence: fields wrapped in `optional`, fields with a `default`, and
193
+ `any` / `unknown` fields are left out, and `.required()` forces every key
194
+ back on. A default supplied as a factory (`.default(() => new Date())`) is
195
+ invoked once and its value emitted.
196
+
197
+ Anything that cannot be expressed exactly produces a **warning** rather than a
198
+ silent `{}`:
199
+
200
+ ```typescript
201
+ const manager = new OpenAPIManager({
202
+ info,
203
+ onSchemaWarning: (name, warnings) => logger.warn({ name, warnings }),
204
+ });
205
+
206
+ manager.schemaWarnings(); // Map<componentName, warnings>
207
+ ```
208
+
209
+ ### Version awareness
210
+
211
+ 3.0 and 3.1 spell several keywords differently, and the difference is not
212
+ cosmetic: the 3.1 spelling in a 3.0 document is either rejected by a strict
213
+ tool or ignored by a lenient one, so the constraint silently disappears from
214
+ the published contract. The converter emits whichever spelling the target
215
+ version defines.
216
+
217
+ | Constraint | 3.1.x | 3.0.x |
218
+ | ---------------- | ---------------------------- | ------------------------------------ |
219
+ | `gt(5)` | `exclusiveMinimum: 5` | `minimum: 5, exclusiveMinimum: true` |
220
+ | `lt(10)` | `exclusiveMaximum: 10` | `maximum: 10, exclusiveMaximum: true`|
221
+ | `positive()` | `exclusiveMinimum: 0` | `minimum: 0, exclusiveMinimum: true` |
222
+ | nullable | `type: ["string", "null"]` | `nullable: true` |
223
+ | literal | `const: "yes"` | `enum: ["yes"]` |
224
+ | tuple | `prefixItems` | `minItems` / `maxItems` |
225
+
226
+ In 3.1 `exclusiveMinimum` carries the bound itself; in 3.0 it is a boolean
227
+ modifier on `minimum`. Emitting the number into a 3.0 document produced a
228
+ keyword of the wrong type, which is how a `gt(5)` constraint used to vanish
229
+ from a 3.0 spec. Both spellings are now correct, and no 3.1-only keyword
230
+ reaches a 3.0 document.
231
+
232
+ A regular expression's flags have nowhere to go: OpenAPI's `pattern` carries
233
+ the source and nothing else. A `/^abc$/i` pattern would therefore become
234
+ case-*sensitive* in the document — a published contract stricter than the code
235
+ validating against it. Rather than drop the flags silently, the converter
236
+ emits the source and raises a warning naming them.
237
+
238
+ Recursive schemas are detected and reported rather than overflowing the stack —
239
+ register the recursive type as a named component and reference it with `$ref`.
240
+
241
+ Use `convertSchema` directly when you want the conversion without the
242
+ registry:
243
+
244
+ ```typescript
245
+ import { convertSchema } from "@zudojs/openapi";
246
+
247
+ const { schema, warnings } = convertSchema(mySchema, { version: "3.0.3" });
248
+ ```
249
+
250
+ > The converter reads `@zudojs/schema`'s runtime fields structurally rather
251
+ > than importing its classes, which keeps it usable with any compatible
252
+ > object. The field names it depends on are listed at the top of
253
+ > `schemaConverter.core.ts` and covered by tests.
254
+
255
+ ## Validation
256
+
257
+ ```typescript
258
+ const result = manager.validate();
259
+ result.valid; // boolean
260
+ result.errors; // OpenAPIValidationIssue[]
261
+ result.warnings; // OpenAPIValidationIssue[]
262
+ ```
263
+
264
+ The validator checks:
265
+
266
+ - required document fields, and that `openapi` is a supported version
267
+ - that every operation declares at least one response, keyed by a status
268
+ code, a `4XX`-style range, or `default`, each with a description
269
+ - that path templates and `in: "path"` parameters agree in both directions —
270
+ the classic "`{id}` is in the path but nowhere in `parameters`" mistake
271
+ - that path parameters are marked required, and that no parameter is declared
272
+ twice in one list (an operation-level parameter may override a path-level
273
+ one with the same name and location)
274
+ - that no two paths are identical apart from their template parameter names
275
+ (`/users/{id}` next to `/users/{userId}`)
276
+ - `operationId` uniqueness and length
277
+ - that every `security` requirement names a scheme declared in
278
+ `components.securitySchemes` — a typo there yields a document that _looks_
279
+ protected
280
+ - that every local `$ref` resolves within the document
281
+ - that every non-local `$ref` uses a scheme a resolver may reasonably be
282
+ pointed at — only `http` and `https`. A `$ref` is an instruction to whatever
283
+ dereferences the document, so `file:///etc/passwd` or
284
+ `http://169.254.169.254/latest/meta-data/` turns the spec into a file-read or
285
+ SSRF sink in the resolver downstream. Any other scheme is an **error**; an
286
+ http(s) or relative reference is legal OpenAPI and so is a **warning**,
287
+ telling you something outside the document will be fetched
288
+ - that no path still uses `:id` instead of `{id}`
289
+
290
+ `assertValid` throws an `OpenAPIValidationError` that **carries the issues**:
291
+
292
+ ```typescript
293
+ try {
294
+ manager.generate(true);
295
+ } catch (error) {
296
+ if (error instanceof OpenAPIValidationError) {
297
+ console.error(error.format()); // one line per issue
298
+ error.issues; // structured
299
+ }
300
+ }
301
+ ```
302
+
303
+ ## Building a document by hand
304
+
305
+ ```typescript
306
+ import { OpenAPIDocumentBuilder } from "@zudojs/openapi";
307
+
308
+ const document = new OpenAPIDocumentBuilder({
309
+ info: { title: "Orders API", version: "1.2.0" },
310
+ })
311
+ .addServer({ url: "https://api.example.com" })
312
+ .addTag({ name: "orders" })
313
+ .addSecurityScheme("bearerAuth", { type: "http", scheme: "bearer" })
314
+ .addSecurity({ bearerAuth: [] })
315
+ .addSchema("Order", { type: "object" })
316
+ .addPath("/orders", {
317
+ get: { responses: { "200": { description: "OK" } } },
318
+ post: { responses: { "201": { description: "Created" } } },
319
+ })
320
+ .build();
321
+ ```
322
+
323
+ The builder and the manager assemble documents through the same registry, so
324
+ both paths produce the same shape and obey the same rules.
325
+
326
+ ## Serialization
327
+
328
+ `toOpenAPIJSON` and `toOpenAPIYAML` both take a document. The YAML output is
329
+ real YAML — a document is plain maps, arrays and scalars, and strings that YAML
330
+ would reinterpret (`true`, `null`, `1.0`, anything opening with a reserved
331
+ character) are quoted.
332
+
333
+ ## References
334
+
335
+ ```typescript
336
+ import { createComponentReference } from "@zudojs/openapi";
337
+
338
+ createComponentReference("schemas", "User");
339
+ // { $ref: "#/components/schemas/User" }
340
+ ```
341
+
342
+ Component names are escaped per RFC 6901, so a name containing `/` or `~`
343
+ still produces a pointer that resolves.
344
+
345
+ ## Errors
346
+
347
+ All errors extend `OpenAPIError` (a `BaseError` from `@zudojs/errors`) and
348
+ default to status 500, not exposed — these are failures while a service builds
349
+ or validates its own specification, not responses to a client request:
350
+
351
+ `OpenAPIValidationError` · `OpenAPIDocumentError` · `OpenAPIComponentError` ·
352
+ `OpenAPIComponentConflictError` · `OpenAPIReferenceError` ·
353
+ `OpenAPIRouteError` · `OpenAPISchemaError` · `OpenAPISerializationError` ·
354
+ `OpenAPIVersionError` · `OpenAPIOperationError`
32
355
 
33
- - OpenAPI 3.1 spec generation
34
- - Route documentation decorators
35
- - Schema integration with `@zudojs/schema`
36
- - Request/response validation
37
- - SDK generation
356
+ Each accepts `statusCode` and `expose` overrides.
38
357
 
39
358
  ## Use Cases
40
359
 
41
- - REST API documentation
42
- - Client SDK generation
43
- - API contract validation
44
- - Developer portals
360
+ - Generating API documentation from route metadata
361
+ - Serving a spec, and a branded Swagger UI or ReDoc page, from your own app
362
+ - Feeding client and server code generators
363
+ - Contract checks in CI
package/dist/index.d.ts CHANGED
@@ -10,7 +10,11 @@
10
10
  * ```ts
11
11
  * import { OpenAPIManager } from "@zudojs/openapi";
12
12
  *
13
- * const manager = new OpenAPIManager("3.1.0");
13
+ * const manager = new OpenAPIManager({
14
+ * version: "3.1.0",
15
+ * info: { title: "Orders API", version: "1.2.0" },
16
+ * servers: [{ url: "https://api.example.com" }],
17
+ * });
14
18
  *
15
19
  * manager.addRoute({
16
20
  * method: "get",
@@ -19,32 +23,33 @@
19
23
  * openapi: {
20
24
  * operationId: "users.get",
21
25
  * summary: "Get a user",
22
- * responses: { "200": { description: "User found" } },
26
+ * parameters: [{ name: "id", in: "path", required: true }],
27
+ * responses: {
28
+ * "200": { description: "User found" },
29
+ * "404": { description: "No such user" },
30
+ * },
23
31
  * },
24
32
  * },
25
33
  * });
26
34
  *
27
- * const document = manager.generate();
35
+ * const document = manager.generate(true); // validate while generating
28
36
  * const json = manager.toJSON();
37
+ * const yaml = manager.toYAML();
29
38
  * ```
30
39
  */
31
- export { OpenAPIDocumentBuilder } from "./openApiDocument/openApiDocument.builder.js";
32
- export type { OpenAPIDocumentOptions } from "./openApiDocument/openApiDocument.builder.js";
33
- export { OpenAPIRegistryImpl } from "./openApiRegistry/openApiRegistry.core.js";
34
- export type { OpenAPIRegistry, OpenAPIRoute, OpenAPIComponentRegistration, } from "./openApiRegistry/openApiRegistry.type.js";
35
- export { OpenAPIError, OpenAPIValidationError, OpenAPIDocumentError, OpenAPIComponentError, OpenAPIComponentConflictError, OpenAPIReferenceError, OpenAPIRouteError, OpenAPISchemaError, OpenAPISerializationError, OpenAPIVersionError, OpenAPIOperationError, createOpenAPIError, isOpenAPIError, } from "./openApiErrors/openApiError.core.js";
36
- export type { OpenAPIErrorOptions, OpenAPIValidationIssue, } from "./openApiErrors/openApiError.core.js";
37
- export { DEFAULT_OPENAPI_VERSION, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants/openApiConstants.core.js";
38
- export { toOpenAPIPath, convertRouteToOpenAPI, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, } from "./openApiRouting/index.js";
39
- export type { RouteMetadata, RouteOpenAPIMetadata, RouteParameterMetadata, RouteInfo, } from "./openApiRouting/index.js";
40
- export { convertSchema, createSchemaConverter, } from "./openApiSchema/schemaConverter.core.js";
41
- export type { SchemaConverter, SchemaConversionResult, } from "./openApiSchema/schemaConverter.core.js";
42
- export { SchemaRegistryImpl } from "./openApiSchema/schemaRegistry.core.js";
43
- export type { SchemaRegistry } from "./openApiSchema/schemaRegistry.core.js";
44
- export { createComponentReference } from "./openApiSchema/references.core.js";
45
- export { OpenAPIValidatorImpl } from "./openApiValidation/openApiValidator.core.js";
46
- export type { OpenAPIValidator } from "./openApiValidation/openApiValidator.core.js";
40
+ export { renderOpenAPIUI, zudoLogo, svgToDataUri, ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, type OpenAPIUIOptions, type OpenAPIUIRenderer, } from "./openApiUi/index.js";
41
+ export { OpenAPIDocumentBuilder, createOpenAPIDocumentBuilder, type OpenAPIDocumentOptions, } from "./openApiDocument/index.js";
42
+ export { OpenAPIRegistryImpl } from "./openApiRegistry/index.js";
43
+ export type { OpenAPIRegistry, OpenAPIRoute, OpenAPIComponentRegistration, } from "./openApiRegistry/index.js";
44
+ export { OpenAPIError, OpenAPIValidationError, OpenAPIDocumentError, OpenAPIComponentError, OpenAPIComponentConflictError, OpenAPIReferenceError, OpenAPIRouteError, OpenAPISchemaError, OpenAPISerializationError, OpenAPIVersionError, OpenAPIOperationError, createOpenAPIError, isOpenAPIError, formatIssuePath, type OpenAPIErrorOptions, type OpenAPIValidationIssue, } from "./openApiErrors/index.js";
45
+ export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants/index.js";
46
+ export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, } from "./openApiRouting/index.js";
47
+ export type { RouteMetadata, RouteOpenAPIMetadata, RouteParameterMetadata, RouteInfo, OpenAPIHttpMethod, } from "./openApiRouting/index.js";
48
+ export { convertSchema, createSchemaConverter, isVersion31, SchemaRegistryImpl, createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, } from "./openApiSchema/index.js";
49
+ export type { SchemaConverter, SchemaConversionResult, SchemaConversionOptions, SchemaRegistry, SchemaRegistryOptions, ComponentSection, } from "./openApiSchema/index.js";
50
+ export { OpenAPIValidatorImpl, createOpenAPIValidator, } from "./openApiValidation/index.js";
51
+ export type { OpenAPIValidator, OpenAPIValidationResult, } from "./openApiValidation/index.js";
47
52
  export { toOpenAPIJSON, toOpenAPIYAML, } from "./openApiSerialization/openApiSerializer.core.js";
48
- export { OpenAPIManager } from "./openApiHttp/openApiHttpAdapter.core.js";
49
- export type { OpenAPIVersion, OpenAPIDocument, OpenAPIComponents, OpenAPISecurityRequirement, OpenAPIParameterLocation, OpenAPIResponse, OpenAPIParameter, OpenAPIRequestBody, OpenAPIMediaType, OpenAPIEncoding, OpenAPIHeader, OpenAPILink, OpenAPIExample, OpenAPIPaths, OpenAPIPathItem, OpenAPIOperation, OpenAPIResponses, OpenAPIServer, OpenAPIServerVariable, OpenAPIInfo, OpenAPIContact, OpenAPILicense, OpenAPIExternalDocumentation, OpenAPISchema, OpenAPIDiscriminator, OpenAPIXml, OpenAPISecurityScheme, OpenAPIOAuthFlows, OpenAPIOAuthFlow, OpenAPITag, OpenAPIReference, } from "./openApiTypes/openApiTypes.core.js";
53
+ export { OpenAPIManager, createOpenAPIManager, type OpenAPIManagerOptions, type OpenAPIDocumentResponse, type OpenAPIUIResponse, } from "./openApiHttp/index.js";
54
+ export type { OpenAPIVersion, OpenAPIDocument, OpenAPIComponents, OpenAPISecurityRequirement, OpenAPIParameterLocation, OpenAPIResponse, OpenAPIParameter, OpenAPIRequestBody, OpenAPIMediaType, OpenAPIEncoding, OpenAPIHeader, OpenAPILink, OpenAPIExample, OpenAPIPaths, OpenAPIPathItem, OpenAPIOperation, OpenAPIResponses, OpenAPIServer, OpenAPIServerVariable, OpenAPIInfo, OpenAPILogo, OpenAPIContact, OpenAPILicense, OpenAPIExternalDocumentation, OpenAPISchema, OpenAPIDiscriminator, OpenAPIXml, OpenAPISecurityScheme, OpenAPIOAuthFlows, OpenAPIOAuthFlow, OpenAPITag, OpenAPIReference, } from "./openApiTypes/openApiTypes.core.js";
50
55
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -10,7 +10,11 @@
10
10
  * ```ts
11
11
  * import { OpenAPIManager } from "@zudojs/openapi";
12
12
  *
13
- * const manager = new OpenAPIManager("3.1.0");
13
+ * const manager = new OpenAPIManager({
14
+ * version: "3.1.0",
15
+ * info: { title: "Orders API", version: "1.2.0" },
16
+ * servers: [{ url: "https://api.example.com" }],
17
+ * });
14
18
  *
15
19
  * manager.addRoute({
16
20
  * method: "get",
@@ -19,24 +23,38 @@
19
23
  * openapi: {
20
24
  * operationId: "users.get",
21
25
  * summary: "Get a user",
22
- * responses: { "200": { description: "User found" } },
26
+ * parameters: [{ name: "id", in: "path", required: true }],
27
+ * responses: {
28
+ * "200": { description: "User found" },
29
+ * "404": { description: "No such user" },
30
+ * },
23
31
  * },
24
32
  * },
25
33
  * });
26
34
  *
27
- * const document = manager.generate();
35
+ * const document = manager.generate(true); // validate while generating
28
36
  * const json = manager.toJSON();
37
+ * const yaml = manager.toYAML();
29
38
  * ```
30
39
  */
31
- export { OpenAPIDocumentBuilder } from "./openApiDocument/openApiDocument.builder.js";
32
- export { OpenAPIRegistryImpl } from "./openApiRegistry/openApiRegistry.core.js";
33
- export { OpenAPIError, OpenAPIValidationError, OpenAPIDocumentError, OpenAPIComponentError, OpenAPIComponentConflictError, OpenAPIReferenceError, OpenAPIRouteError, OpenAPISchemaError, OpenAPISerializationError, OpenAPIVersionError, OpenAPIOperationError, createOpenAPIError, isOpenAPIError, } from "./openApiErrors/openApiError.core.js";
34
- export { DEFAULT_OPENAPI_VERSION, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants/openApiConstants.core.js";
35
- export { toOpenAPIPath, convertRouteToOpenAPI, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, } from "./openApiRouting/index.js";
36
- export { convertSchema, createSchemaConverter, } from "./openApiSchema/schemaConverter.core.js";
37
- export { SchemaRegistryImpl } from "./openApiSchema/schemaRegistry.core.js";
38
- export { createComponentReference } from "./openApiSchema/references.core.js";
39
- export { OpenAPIValidatorImpl } from "./openApiValidation/openApiValidator.core.js";
40
+ /* ─── Documentation UI & branding ──────────────────────────────────────── */
41
+ export { renderOpenAPIUI, zudoLogo, svgToDataUri, ZUDO_MARK_SVG, ZUDO_MARK_DARK_SVG, ZUDO_WORDMARK_SVG, ZUDO_WORDMARK_DARK_SVG, ZUDO_FAVICON_SVG, ZUDO_MARK_DATA_URI, ZUDO_MARK_DARK_DATA_URI, ZUDO_WORDMARK_DATA_URI, ZUDO_WORDMARK_DARK_DATA_URI, ZUDO_FAVICON_DATA_URI, ZUDO_SITE_URL, } from "./openApiUi/index.js";
42
+ /* ─── Document builder ──────────────────────────────────────────────────── */
43
+ export { OpenAPIDocumentBuilder, createOpenAPIDocumentBuilder, } from "./openApiDocument/index.js";
44
+ /* ─── Registry ──────────────────────────────────────────────────────────── */
45
+ export { OpenAPIRegistryImpl } from "./openApiRegistry/index.js";
46
+ /* ─── Errors ────────────────────────────────────────────────────────────── */
47
+ export { OpenAPIError, OpenAPIValidationError, OpenAPIDocumentError, OpenAPIComponentError, OpenAPIComponentConflictError, OpenAPIReferenceError, OpenAPIRouteError, OpenAPISchemaError, OpenAPISerializationError, OpenAPIVersionError, OpenAPIOperationError, createOpenAPIError, isOpenAPIError, formatIssuePath, } from "./openApiErrors/index.js";
48
+ /* ─── Constants ─────────────────────────────────────────────────────────── */
49
+ export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants/index.js";
50
+ /* ─── Routing ───────────────────────────────────────────────────────────── */
51
+ export { toOpenAPIPath, extractPathParameters, convertRouteToOpenAPI, buildResponses, isOpenAPIMethod, ZUDOLIB_TO_OPENAPI_METHODS, OpenAPIRouteScannerImpl, } from "./openApiRouting/index.js";
52
+ /* ─── Schema conversion ─────────────────────────────────────────────────── */
53
+ export { convertSchema, createSchemaConverter, isVersion31, SchemaRegistryImpl, createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, } from "./openApiSchema/index.js";
54
+ /* ─── Validation ────────────────────────────────────────────────────────── */
55
+ export { OpenAPIValidatorImpl, createOpenAPIValidator, } from "./openApiValidation/index.js";
56
+ /* ─── Serialization ─────────────────────────────────────────────────────── */
40
57
  export { toOpenAPIJSON, toOpenAPIYAML, } from "./openApiSerialization/openApiSerializer.core.js";
41
- export { OpenAPIManager } from "./openApiHttp/openApiHttpAdapter.core.js";
58
+ /* ─── Manager ───────────────────────────────────────────────────────────── */
59
+ export { OpenAPIManager, createOpenAPIManager, } from "./openApiHttp/index.js";
42
60
  //# sourceMappingURL=index.js.map
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Reusable OpenAPI component helpers.
5
5
  */
6
- export { createComponentReference } from "../openApiSchema/references.core.js";
7
- export type { SchemaRegistry } from "../openApiSchema/schemaRegistry.core.js";
6
+ export { createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, type ComponentSection, } from "../openApiSchema/references.core.js";
7
+ export type { SchemaRegistry, SchemaRegistryOptions, } from "../openApiSchema/schemaRegistry.core.js";
8
8
  export { SchemaRegistryImpl } from "../openApiSchema/schemaRegistry.core.js";
9
9
  //# sourceMappingURL=index.d.ts.map
@@ -3,6 +3,6 @@
3
3
  *
4
4
  * Reusable OpenAPI component helpers.
5
5
  */
6
- export { createComponentReference } from "../openApiSchema/references.core.js";
6
+ export { createComponentReference, escapeJsonPointerSegment, unescapeJsonPointerSegment, } from "../openApiSchema/references.core.js";
7
7
  export { SchemaRegistryImpl } from "../openApiSchema/schemaRegistry.core.js";
8
8
  //# sourceMappingURL=index.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * @zudojs/openapi/openApiConstants
3
3
  */
4
- export { DEFAULT_OPENAPI_VERSION, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants.core.js";
4
+ export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants.core.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1,5 +1,5 @@
1
1
  /**
2
2
  * @zudojs/openapi/openApiConstants
3
3
  */
4
- export { DEFAULT_OPENAPI_VERSION, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants.core.js";
4
+ export { DEFAULT_OPENAPI_VERSION, SUPPORTED_OPENAPI_VERSIONS, MAX_OPERATION_ID_LENGTH, COMPONENT_REF_PREFIX, DEFAULT_MEDIA_TYPE, STATUS_CODE_CATEGORIES, RESPONSE_KEY_PATTERN, PATH_TEMPLATE_PARAMETER, DEFAULT_SERVER_URL, DOCUMENT_CACHE_TTL_MS, } from "./openApiConstants.core.js";
5
5
  //# sourceMappingURL=index.js.map
@@ -8,7 +8,13 @@
8
8
  */
9
9
  export declare const DEFAULT_OPENAPI_VERSION: "3.1.0";
10
10
  /**
11
- * Maximum operation ID length allowed by OpenAPI.
11
+ * Every specification version this package can emit and validate.
12
+ */
13
+ export declare const SUPPORTED_OPENAPI_VERSIONS: readonly ["3.0.0", "3.0.1", "3.0.2", "3.0.3", "3.1.0", "3.1.1"];
14
+ /**
15
+ * Maximum operation ID length. Not a specification limit — a practical one:
16
+ * operation IDs become function names in generated clients, and tooling
17
+ * routinely truncates beyond this.
12
18
  */
13
19
  export declare const MAX_OPERATION_ID_LENGTH = 128;
14
20
  /**
@@ -20,7 +26,7 @@ export declare const COMPONENT_REF_PREFIX = "#/components";
20
26
  */
21
27
  export declare const DEFAULT_MEDIA_TYPE = "application/json";
22
28
  /**
23
- * Status code categories.
29
+ * Status code categories, usable as OpenAPI response keys.
24
30
  */
25
31
  export declare const STATUS_CODE_CATEGORIES: {
26
32
  readonly INFORMATIONAL: "1XX";
@@ -29,12 +35,18 @@ export declare const STATUS_CODE_CATEGORIES: {
29
35
  readonly CLIENT_ERROR: "4XX";
30
36
  readonly SERVER_ERROR: "5XX";
31
37
  };
38
+ /** A response key is valid when it is `default`, `NXX`, or a status code. */
39
+ export declare const RESPONSE_KEY_PATTERN: RegExp;
40
+ /** OpenAPI path template parameter, e.g. `{orderId}`. */
41
+ export declare const PATH_TEMPLATE_PARAMETER: RegExp;
32
42
  /**
33
43
  * Default server URL.
34
44
  */
35
45
  export declare const DEFAULT_SERVER_URL = "http://localhost";
36
46
  /**
37
- * Cache TTL for generated documents (5 minutes).
47
+ * Default time a generated document stays cached before it is rebuilt.
48
+ *
49
+ * Applied by `OpenAPIManager`; pass `cacheTtlMs` to change or disable it.
38
50
  */
39
51
  export declare const DOCUMENT_CACHE_TTL_MS: number;
40
52
  //# sourceMappingURL=openApiConstants.core.d.ts.map