@tetsujs/openapi 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/index.js CHANGED
@@ -102,6 +102,164 @@ function failureName(status, error) {
102
102
  return name || `Failure${status}`;
103
103
  }
104
104
 
105
+ // packages/openapi/src/envelopes.ts
106
+ function envelopes(components, format) {
107
+ const declared = new Map;
108
+ const defined = new Map;
109
+ const refs = new Map;
110
+ const codes = new Map;
111
+ const names = new Map;
112
+ const reported = new Set;
113
+ return {
114
+ format,
115
+ declare(status, code, schema) {
116
+ const key = keyOf(status, code);
117
+ if (!declared.has(key)) {
118
+ declared.set(key, schema);
119
+ }
120
+ },
121
+ ref(status, code, schema, warn) {
122
+ const key = keyOf(status, code);
123
+ const definition = declared.get(key) ?? defined.get(key) ?? schema;
124
+ defined.set(key, definition);
125
+ const difference = differenceOf(definition, schema);
126
+ if (difference && !reported.has(`${key} ${difference}`)) {
127
+ reported.add(`${key} ${difference}`);
128
+ warn(`the ${status} "${code}" is described by one definition in the document, and a definition that differs from it in ${difference} is not described`);
129
+ }
130
+ const existing = refs.get(key);
131
+ if (existing) {
132
+ return existing;
133
+ }
134
+ const created = components.ref(nameOf(status, code), definition);
135
+ refs.set(key, created);
136
+ codes.set(created.$ref, code);
137
+ return created;
138
+ },
139
+ codeOf(ref) {
140
+ return codes.get(ref);
141
+ }
142
+ };
143
+ function nameOf(status, code) {
144
+ const name = failureName(status, code);
145
+ const holder = names.get(name);
146
+ if (holder === undefined || holder === keyOf(status, code)) {
147
+ names.set(name, keyOf(status, code));
148
+ return name;
149
+ }
150
+ return `${name}${status}`;
151
+ }
152
+ }
153
+ function branchesOf(schema, code) {
154
+ const keys = Object.keys(schema);
155
+ if (keys.length !== 1) {
156
+ return [schema];
157
+ }
158
+ const within = (branch) => branchesOf(branch, code);
159
+ if (Array.isArray(schema.anyOf)) {
160
+ return schema.anyOf.flatMap(within);
161
+ }
162
+ if (Array.isArray(schema.oneOf)) {
163
+ const branches = schema.oneOf.flatMap(within);
164
+ const codes = branches.map(code);
165
+ if (codes.every((code) => code !== undefined) && new Set(codes).size === codes.length) {
166
+ return branches;
167
+ }
168
+ }
169
+ return [schema];
170
+ }
171
+ function keyOf(status, code) {
172
+ return `${status} ${code}`;
173
+ }
174
+ function differenceOf(kept, other) {
175
+ const keptShape = shapeOf(kept);
176
+ const otherShape = shapeOf(other);
177
+ const changed = [
178
+ ...otherShape.fields.filter((path) => !keptShape.fields.includes(path)),
179
+ ...keptShape.fields.filter((path) => !otherShape.fields.includes(path))
180
+ ];
181
+ if (changed.length > 0) {
182
+ return `fields: ${changed.join(", ")}`;
183
+ }
184
+ return JSON.stringify(keptShape.required) === JSON.stringify(otherShape.required) ? undefined : "which fields are required";
185
+ }
186
+ var shapeDepth = 8;
187
+ function shapeOf(schema) {
188
+ const fields = [];
189
+ const required = [];
190
+ const walk = (node, prefix, depth) => {
191
+ const properties = node.properties;
192
+ if (depth > shapeDepth || properties === null || typeof properties !== "object") {
193
+ return;
194
+ }
195
+ const requiredHere = Array.isArray(node.required) ? node.required : [];
196
+ for (const [name, child] of Object.entries(properties)) {
197
+ const path = `${prefix}${name}`;
198
+ fields.push(path);
199
+ if (requiredHere.includes(name)) {
200
+ required.push(path);
201
+ }
202
+ if (child !== null && typeof child === "object") {
203
+ walk(child, `${path}.`, depth + 1);
204
+ }
205
+ }
206
+ };
207
+ walk(schema, "", 0);
208
+ return { fields: fields.sort(), required: required.sort() };
209
+ }
210
+
211
+ // packages/openapi/src/errors.ts
212
+ function resolveFormat(format) {
213
+ if (format === undefined) {
214
+ return {
215
+ describe: envelope,
216
+ code: codeIn("error"),
217
+ discriminator: "error"
218
+ };
219
+ }
220
+ const discriminator = format.discriminator;
221
+ const read = format.code;
222
+ return {
223
+ describe: (failure) => format.schema(failure),
224
+ code: read ? (schema) => read(schema) : discriminator === undefined ? () => {
225
+ return;
226
+ } : codeIn(discriminator),
227
+ discriminator
228
+ };
229
+ }
230
+ function envelope(failure) {
231
+ const own = {
232
+ status: { type: "integer", const: failure.status },
233
+ message: failure.message ? { type: "string", examples: [failure.message] } : { type: "string" },
234
+ error: failure.error ? { type: "string", const: failure.error } : { type: "string" }
235
+ };
236
+ const added = Object.keys(failure.fields).filter((name) => !Object.hasOwn(own, name));
237
+ return {
238
+ type: "object",
239
+ required: ["status", "message", "error", ...added],
240
+ properties: { ...failure.fields, ...own }
241
+ };
242
+ }
243
+ function codeIn(field) {
244
+ return (schema) => {
245
+ const required = Array.isArray(schema.required) ? schema.required : [];
246
+ if (!required.includes(field)) {
247
+ return;
248
+ }
249
+ const value = schema.properties?.[field];
250
+ if (typeof value?.const === "string") {
251
+ return value.const;
252
+ }
253
+ if (Array.isArray(value?.enum) && value.enum.length === 1 && typeof value.enum[0] === "string") {
254
+ return value.enum[0];
255
+ }
256
+ return;
257
+ };
258
+ }
259
+
260
+ // packages/openapi/src/operation.ts
261
+ import { errorBody } from "@tetsujs/core";
262
+
105
263
  // packages/openapi/src/emit.ts
106
264
  import { toJsonSchema } from "@tetsujs/core";
107
265
  var target = { target: "draft-2020-12" };
@@ -192,7 +350,7 @@ function objectProperties(schema, where, warn) {
192
350
  }
193
351
 
194
352
  // packages/openapi/src/operation.ts
195
- function operationOf(entry, options, components, ids, warn) {
353
+ function operationOf(entry, options, components, envelopes, ids, warn) {
196
354
  const schema = entry.def.schema;
197
355
  const parameters = [
198
356
  ...pathParameters(entry.path, schema?.params, warn),
@@ -211,9 +369,37 @@ function operationOf(entry, options, components, ids, warn) {
211
369
  ...parameters.length > 0 ? { parameters } : {},
212
370
  ...body ? { requestBody: body } : {},
213
371
  ...contributed.security.length > 0 ? { security: securityOf(contributed.conditions) } : {},
214
- responses: responses(entry, options, contributed, components, warn)
372
+ responses: responses(entry, options, contributed, components, envelopes, warn)
215
373
  };
216
374
  }
375
+ function declareEnvelopes(entry, envelopes) {
376
+ for (const [status, schema] of declaredResponses(entry)) {
377
+ const described = schema === null ? undefined : emitted(schema, "", () => {});
378
+ const code = envelopes.format.code;
379
+ for (const branch of described ? branchesOf(described, code) : []) {
380
+ const found = code(branch);
381
+ if (found !== undefined) {
382
+ envelopes.declare(Number(status), found, branch);
383
+ }
384
+ }
385
+ }
386
+ }
387
+ function declaredResponses(entry) {
388
+ const response = entry.def.schema?.response;
389
+ if (!response) {
390
+ return [];
391
+ }
392
+ if ("~standard" in response) {
393
+ return [["200", response]];
394
+ }
395
+ return Object.entries(response);
396
+ }
397
+ function branchesFor(schema, status, envelopes, warn) {
398
+ return branchesOf(schema, envelopes.format.code).map((branch) => {
399
+ const code = envelopes.format.code(branch);
400
+ return code === undefined ? branch : envelopes.ref(status, code, branch, warn);
401
+ });
402
+ }
217
403
  function securityOf(conditions) {
218
404
  let combinations = [{}];
219
405
  for (const alternatives of conditions) {
@@ -317,94 +503,161 @@ function carriesBinary(root) {
317
503
  }
318
504
  return false;
319
505
  }
320
- function responses(entry, options, contributed, components, warn) {
321
- const declared = {};
322
- const response = entry.def.schema?.response;
323
- if (response && "~standard" in response) {
324
- declared["200"] = jsonResponse(response, "Successful response", warn);
325
- } else if (response) {
326
- for (const [status, schema] of Object.entries(response)) {
327
- declared[status] = schema === null ? { description: describeStatus(status) } : jsonResponse(schema, describeStatus(status), warn);
328
- }
506
+ function responses(entry, options, contributed, components, envelopes, warn) {
507
+ const answers = new Map;
508
+ const add = (status, answer) => {
509
+ answers.set(status, [...answers.get(status) ?? [], answer]);
510
+ };
511
+ for (const [status, schema] of declaredResponses(entry)) {
512
+ const described = schema === null ? undefined : emitted(schema, "a response", warn);
513
+ add(status, {
514
+ description: describeStatus(status),
515
+ placeholder: true,
516
+ schemas: described ? branchesFor(described, Number(status), envelopes, warn) : []
517
+ });
329
518
  }
330
- if (!declared["200"] && !hasSuccess(declared)) {
331
- declared["200"] = { description: "Successful response" };
519
+ if (![...answers.keys()].some((status) => status.startsWith("2"))) {
520
+ add("200", {
521
+ description: "Successful response",
522
+ placeholder: true,
523
+ schemas: []
524
+ });
332
525
  }
333
- return {
334
- ...declared,
335
- ...frameworkFailures(entry, options, contributed, declared, components, warn)
336
- };
526
+ for (const [status, answer] of failures(entry, options, contributed, components, envelopes, warn)) {
527
+ add(String(status), answer);
528
+ }
529
+ const described = {};
530
+ for (const [status, list] of answers) {
531
+ described[status] = merge(list, envelopes);
532
+ }
533
+ return described;
337
534
  }
338
- function frameworkFailures(entry, options, contributed, declared, components, warn) {
535
+ function failures(entry, options, contributed, components, envelopes, warn) {
339
536
  const schema = entry.def.schema;
340
- const failures = new Map;
341
- const add = (status, failure) => {
342
- const key = String(status);
343
- failures.set(key, [...failures.get(key) ?? [], failure]);
537
+ const found = [];
538
+ const envelopeRef = (status, error, message, fields) => {
539
+ const described = envelopes.format.describe({
540
+ status,
541
+ ...error === undefined ? {} : { error },
542
+ ...message === undefined ? {} : { message },
543
+ fields: fields ?? {}
544
+ });
545
+ const ref = error === undefined ? components.ref(failureName(status), described) : envelopes.ref(status, error, described, warn);
546
+ return ref;
344
547
  };
345
- const envelopeRef = (status, error, message) => components.ref(failureName(status, error), envelope(status, error, message));
346
548
  if (schema?.params || schema?.query || schema?.headers || schema?.body) {
347
- add(options.validationStatus, {
348
- description: "Request failed schema validation",
349
- schema: components.ref(failureName(options.validationStatus, "VALIDATION_FAILED"), validationFailed(options.validationStatus))
350
- });
549
+ found.push([
550
+ options.validationStatus,
551
+ {
552
+ description: "Request failed schema validation",
553
+ schemas: [
554
+ envelopes.ref(options.validationStatus, "VALIDATION_FAILED", envelopes.format.describe({
555
+ status: options.validationStatus,
556
+ error: "VALIDATION_FAILED",
557
+ message: "Validation failed",
558
+ fields: { issues }
559
+ }), warn)
560
+ ]
561
+ }
562
+ ]);
351
563
  }
352
564
  for (const requirement of contributed.security) {
353
565
  const status = requirement.status ?? 401;
354
- add(status, {
355
- description: requirement.description ?? `Request did not satisfy ${requirement.name}`,
356
- schema: envelopeRef(status, requirement.error, requirement.message)
357
- });
566
+ found.push([
567
+ status,
568
+ {
569
+ description: requirement.description ?? `Request did not satisfy ${requirement.name}`,
570
+ schemas: [envelopeRef(status, requirement.error, requirement.message)]
571
+ }
572
+ ]);
358
573
  }
359
574
  for (const response of contributed.responses) {
360
575
  const described = response.schema ? emitted(response.schema, "a hook's response", warn) : undefined;
361
- add(response.status, {
362
- description: response.description,
363
- schema: described ?? envelopeRef(response.status, response.error, response.message)
364
- });
576
+ found.push([
577
+ response.status,
578
+ {
579
+ description: response.description,
580
+ schemas: described ? branchesFor(described, response.status, envelopes, warn) : [
581
+ envelopeRef(response.status, response.error, response.message, response.fields)
582
+ ],
583
+ ...response.headers ? { headers: response.headers } : {}
584
+ }
585
+ ]);
365
586
  }
366
587
  if (schema?.body || entry.def.bodyType) {
367
588
  const unparsable = parseFailure(entry.def.bodyType);
368
589
  if (unparsable) {
369
- add(400, {
370
- description: "Body could not be parsed in the declared shape",
371
- schema: envelopeRef(400, unparsable.error, unparsable.message)
372
- });
590
+ found.push([
591
+ 400,
592
+ {
593
+ description: "Body could not be parsed in the declared shape",
594
+ schemas: [envelopeRef(400, unparsable.error, unparsable.message)]
595
+ }
596
+ ]);
597
+ }
598
+ found.push([
599
+ 413,
600
+ {
601
+ description: "Body exceeded the configured size limit",
602
+ schemas: [
603
+ envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
604
+ ]
605
+ }
606
+ ]);
607
+ }
608
+ found.push([
609
+ 500,
610
+ {
611
+ description: "The request failed and nothing mapped the failure",
612
+ schemas: [
613
+ envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
614
+ ]
615
+ }
616
+ ]);
617
+ return found;
618
+ }
619
+ function merge(list, envelopes) {
620
+ const schemas = distinct(list.flatMap((answer) => answer.schemas));
621
+ const worded = list.filter((answer) => !answer.placeholder);
622
+ const headers = {};
623
+ for (const answer of list) {
624
+ for (const [name, header] of Object.entries(answer.headers ?? {})) {
625
+ headers[name] ??= header;
373
626
  }
374
- add(413, {
375
- description: "Body exceeded the configured size limit",
376
- schema: envelopeRef(413, "BODY_TOO_LARGE", "Body exceeds the configured limit")
377
- });
378
- }
379
- add(500, {
380
- description: "The request failed and nothing mapped the failure",
381
- schema: envelopeRef(500, "INTERNAL_SERVER_ERROR", "Internal Server Error")
382
- });
383
- const responses = {};
384
- for (const [status, list] of failures) {
385
- responses[status] = merge(declared[status], list);
386
627
  }
387
- return responses;
628
+ return {
629
+ description: (worded.length > 0 ? worded : list).map((answer) => answer.description).join("; "),
630
+ ...Object.keys(headers).length > 0 ? { headers } : {},
631
+ ...schemas.length > 0 ? {
632
+ content: {
633
+ "application/json": { schema: unionOf(schemas, envelopes) }
634
+ }
635
+ } : {}
636
+ };
388
637
  }
389
- function merge(declared, list) {
390
- const descriptions = [
391
- ...declared ? [declared.description] : [],
392
- ...list.map((failure) => failure.description)
393
- ];
394
- const schemas = distinct([
395
- ...declaredSchemas(declared),
396
- ...list.map((failure) => failure.schema)
397
- ]);
638
+ function unionOf(schemas, envelopes) {
398
639
  const [only] = schemas;
640
+ if (schemas.length === 1 && only) {
641
+ return only;
642
+ }
643
+ const propertyName = envelopes.format.discriminator;
644
+ if (propertyName === undefined) {
645
+ return { anyOf: schemas };
646
+ }
647
+ const mapping = {};
648
+ for (const schema of schemas) {
649
+ const ref = schema.$ref;
650
+ const code = typeof ref === "string" ? envelopes.codeOf(ref) : undefined;
651
+ if (typeof ref !== "string" || code === undefined) {
652
+ return { anyOf: schemas };
653
+ }
654
+ mapping[code] = ref;
655
+ }
399
656
  return {
400
- description: descriptions.join("; "),
401
- ...schemas.length === 1 && only ? { content: { "application/json": { schema: only } } } : { content: { "application/json": { schema: { anyOf: schemas } } } }
657
+ anyOf: schemas,
658
+ discriminator: { propertyName, mapping }
402
659
  };
403
660
  }
404
- function declaredSchemas(declared) {
405
- const schema = declared?.content?.["application/json"]?.schema;
406
- return schema ? [schema] : [];
407
- }
408
661
  function distinct(schemas) {
409
662
  const seen = new Set;
410
663
  return schemas.filter((schema) => {
@@ -416,57 +669,23 @@ function distinct(schemas) {
416
669
  return true;
417
670
  });
418
671
  }
419
- function jsonResponse(schema, description, warn) {
420
- const described = emitted(schema, "a response", warn);
421
- if (!described) {
422
- return { description };
423
- }
424
- return {
425
- description,
426
- content: { "application/json": { schema: described } }
427
- };
428
- }
429
- function hasSuccess(declared) {
430
- return Object.keys(declared).some((status) => status.startsWith("2"));
431
- }
432
672
  function describeStatus(status) {
433
673
  if (status === "204") {
434
674
  return "No content";
435
675
  }
436
- return status.startsWith("2") ? "Successful response" : `Response ${status}`;
676
+ return status.startsWith("2") ? "Successful response" : errorBody(Number(status)).message;
437
677
  }
438
- function envelope(status, error, message) {
439
- return {
678
+ var issues = {
679
+ type: "array",
680
+ items: {
440
681
  type: "object",
441
- required: ["status", "message", "error"],
682
+ required: ["message", "path"],
442
683
  properties: {
443
- status: { type: "integer", const: status },
444
- message: message ? { type: "string", examples: [message] } : { type: "string" },
445
- error: error ? { type: "string", const: error } : { type: "string" }
446
- }
447
- };
448
- }
449
- function validationFailed(status) {
450
- const base = envelope(status, "VALIDATION_FAILED", "Validation failed");
451
- return {
452
- ...base,
453
- required: ["status", "message", "error", "issues"],
454
- properties: {
455
- ...base.properties,
456
- issues: {
457
- type: "array",
458
- items: {
459
- type: "object",
460
- required: ["message", "path"],
461
- properties: {
462
- message: { type: "string" },
463
- path: { type: "array", items: { type: ["string", "number"] } }
464
- }
465
- }
466
- }
684
+ message: { type: "string" },
685
+ path: { type: "array", items: { type: ["string", "number"] } }
467
686
  }
468
- };
469
- }
687
+ }
688
+ };
470
689
  function parseFailure(bodyType) {
471
690
  if (bodyType === "text" || bodyType === "stream") {
472
691
  return;
@@ -485,14 +704,24 @@ import { onMount, route } from "@tetsujs/core";
485
704
  // packages/openapi/src/page.ts
486
705
  var defaults = {
487
706
  scalar: {
488
- script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference"
707
+ script: "https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.72.1/dist/browser/standalone.js",
708
+ integrity: {
709
+ script: "sha384-U11tb2XnKvmwt8RlTvnwUnYgrN+ur4Xyh9htLhjajWNR/Oyl5AX5DEz00qRmlrmK"
710
+ }
489
711
  },
490
712
  "swagger-ui": {
491
- script: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui-bundle.js",
492
- style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5/swagger-ui.css"
713
+ script: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui-bundle.js",
714
+ style: "https://cdn.jsdelivr.net/npm/swagger-ui-dist@5.33.0/swagger-ui.css",
715
+ integrity: {
716
+ script: "sha384-YDALVcy8kj8yltLBVi1vBiBAUqdxvus673gM8XKwiy6aDUJFXivF/KCufekjYbVf",
717
+ style: "sha384-Ov4/wv3j2bmct8cDc5X4ngJZohVPzEmc6uDPH8WeljUxO5vtoykvMEfbu9Vh6RaW"
718
+ }
493
719
  },
494
720
  redoc: {
495
- script: "https://cdn.jsdelivr.net/npm/redoc@2/bundles/redoc.standalone.js"
721
+ script: "https://cdn.jsdelivr.net/npm/redoc@2.5.4/bundles/redoc.standalone.js",
722
+ integrity: {
723
+ script: "sha384-w447zOpYfw/1Tv/5AK9NfHTlQIqE3RVR6KY62jCyy9zNDgO64cMwGGP1Fj0zJVf5"
724
+ }
496
725
  }
497
726
  };
498
727
  function docsPage(options) {
@@ -506,11 +735,11 @@ function docsPage(options) {
506
735
  '<meta charset="utf-8" />',
507
736
  '<meta name="viewport" content="width=device-width, initial-scale=1" />',
508
737
  `<title>${title}</title>`,
509
- assets.style ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}" />` : "",
738
+ assets.style ? `<link rel="stylesheet" href="${escapeHtml(assets.style)}"${verified(assets.integrity?.style)} />` : "",
510
739
  "</head>",
511
740
  "<body>",
512
741
  body(options.ui, url),
513
- `<script src="${escapeHtml(assets.script)}"></script>`,
742
+ `<script src="${escapeHtml(assets.script)}"${verified(assets.integrity?.script)}></script>`,
514
743
  bootstrap(options.ui, url),
515
744
  "</body>",
516
745
  "</html>"
@@ -537,6 +766,9 @@ function bootstrap(ui, url) {
537
766
  ].join(`
538
767
  `);
539
768
  }
769
+ function verified(integrity) {
770
+ return integrity ? ` integrity="${escapeHtml(integrity)}" crossorigin="anonymous"` : "";
771
+ }
540
772
  function escapeHtml(value) {
541
773
  return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#39;");
542
774
  }
@@ -547,7 +779,8 @@ class DocsController {
547
779
  json;
548
780
  ui;
549
781
  document;
550
- constructor(options, documentPath, uiPath, page) {
782
+ page;
783
+ constructor(options, documentPath, uiPath) {
551
784
  this.options = options;
552
785
  const hidden = !options.documentSelf;
553
786
  const json = route({
@@ -560,19 +793,31 @@ class DocsController {
560
793
  method: "GET",
561
794
  path: "/docs",
562
795
  docs: { summary: "Rendered documentation", tags: ["docs"], hidden },
563
- handler: () => new Response(page, {
796
+ handler: () => new Response(this.page, {
564
797
  headers: { "content-type": "text/html; charset=utf-8" }
565
798
  })
566
799
  });
567
800
  this.json = { ...json, path: documentPath };
568
- this.ui = { ...ui, path: uiPath };
801
+ this.ui = options.ui === false ? undefined : { ...ui, path: uiPath };
569
802
  }
570
803
  [onMount](app) {
571
804
  const generated = openapi(app, {
572
805
  info: this.options.info,
573
- ...this.options.servers ? { servers: this.options.servers } : {}
806
+ ...this.options.servers ? { servers: this.options.servers } : {},
807
+ ...this.options.errors ? { errors: this.options.errors } : {}
574
808
  });
575
809
  this.document = generated.document;
810
+ const ui = this.options.ui ?? "scalar";
811
+ const json = this.json;
812
+ const served = app.entries.find((entry) => entry.def === json);
813
+ if (ui !== false && served) {
814
+ this.page = docsPage({
815
+ ui,
816
+ documentUrl: served.path,
817
+ title: this.options.title ?? this.options.info.title,
818
+ ...this.options.assets ? { assets: this.options.assets } : {}
819
+ });
820
+ }
576
821
  const report = this.options.onWarning ?? warn;
577
822
  for (const warning of generated.warnings) {
578
823
  report(warning);
@@ -580,15 +825,7 @@ class DocsController {
580
825
  }
581
826
  }
582
827
  function docs(options) {
583
- const documentPath = options.path ?? "/openapi.json";
584
- const uiPath = options.uiPath ?? "/docs";
585
- const page = docsPage({
586
- ui: options.ui ?? "scalar",
587
- documentUrl: documentPath,
588
- title: options.title ?? options.info.title,
589
- ...options.assets ? { assets: options.assets } : {}
590
- });
591
- return new DocsController(options, documentPath, uiPath, page);
828
+ return new DocsController(options, options.path ?? "/openapi.json", options.uiPath ?? "/docs");
592
829
  }
593
830
  function warn(warning) {
594
831
  console.warn(`[openapi] ${warning.route}: ${warning.message}`);
@@ -600,15 +837,17 @@ function openapi(app, options) {
600
837
  const warnings = [];
601
838
  const schemes = {};
602
839
  const components = schemaComponents();
840
+ const envelopes2 = envelopes(components, resolveFormat(options.errors));
603
841
  const ids = operationIds();
842
+ const described = app.entries.filter((entry) => !entry.ws && !entry.def.docs?.hidden);
843
+ for (const entry of described) {
844
+ declareEnvelopes(entry, envelopes2);
845
+ }
604
846
  const occupied = new Map;
605
- for (const entry of app.entries) {
606
- if (entry.ws || entry.def.docs?.hidden) {
607
- continue;
608
- }
847
+ for (const entry of described) {
609
848
  const template = toTemplate(entry.path);
610
849
  const route = `${entry.method} ${entry.path}`;
611
- const operation = operationOf(entry, app.options, components, ids, (message) => {
850
+ const operation = operationOf(entry, app.options, components, envelopes2, ids, (message) => {
612
851
  warnings.push({ route, message });
613
852
  });
614
853
  for (const requirement of contributionsOf(entry.hooks).security) {
@@ -665,5 +904,5 @@ export {
665
904
  secured
666
905
  };
667
906
 
668
- //# debugId=AFF7EFC95370A0C664756E2164756E21
907
+ //# debugId=B1D856E7F5B6819164756E2164756E21
669
908
  //# sourceMappingURL=index.js.map