@orpc/openapi 0.0.0-next.e385fb7 → 0.0.0-next.e4b0df6

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.
@@ -1,11 +1,11 @@
1
1
  import { stringifyJSON, once, value } from '@orpc/shared';
2
- import { O as OpenAPIGenerator } from '../shared/openapi.ExRBHuvW.mjs';
2
+ import { O as OpenAPIGenerator } from '../shared/openapi.BlSv9FKY.mjs';
3
3
  import '@orpc/client';
4
4
  import '@orpc/client/standard';
5
5
  import '@orpc/contract';
6
6
  import '@orpc/openapi-client/standard';
7
7
  import '@orpc/server';
8
- import 'json-schema-typed/draft-2020-12';
8
+ import '@orpc/interop/json-schema-typed/draft-2020-12';
9
9
 
10
10
  class OpenAPIReferencePlugin {
11
11
  generator;
@@ -14,7 +14,9 @@ class OpenAPIReferencePlugin {
14
14
  docsPath;
15
15
  docsTitle;
16
16
  docsHead;
17
+ docsProvider;
17
18
  docsScriptUrl;
19
+ docsCssUrl;
18
20
  docsConfig;
19
21
  renderDocsHtml;
20
22
  constructor(options = {}) {
@@ -22,16 +24,59 @@ class OpenAPIReferencePlugin {
22
24
  this.docsPath = options.docsPath ?? "/";
23
25
  this.docsTitle = options.docsTitle ?? "API Reference";
24
26
  this.docsConfig = options.docsConfig ?? void 0;
25
- this.docsScriptUrl = options.docsScriptUrl ?? "https://cdn.jsdelivr.net/npm/@scalar/api-reference";
27
+ this.docsProvider = options.docsProvider ?? "scalar";
28
+ this.docsScriptUrl = options.docsScriptUrl ?? (this.docsProvider === "swagger" ? "https://unpkg.com/swagger-ui-dist/swagger-ui-bundle.js" : "https://cdn.jsdelivr.net/npm/@scalar/api-reference");
29
+ this.docsCssUrl = options.docsCssUrl ?? (this.docsProvider === "swagger" ? "https://unpkg.com/swagger-ui-dist/swagger-ui.css" : void 0);
26
30
  this.docsHead = options.docsHead ?? "";
27
31
  this.specPath = options.specPath ?? "/spec.json";
28
32
  this.generator = new OpenAPIGenerator(options);
29
33
  const esc = (s) => s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
30
- this.renderDocsHtml = options.renderDocsHtml ?? ((specUrl, title, head, scriptUrl, config, spec) => {
31
- const finalConfig = {
32
- content: stringifyJSON(spec),
33
- ...config
34
- };
34
+ this.renderDocsHtml = options.renderDocsHtml ?? ((specUrl, title, head, scriptUrl, config, spec, docsProvider, cssUrl) => {
35
+ let body;
36
+ if (docsProvider === "swagger") {
37
+ const swaggerConfig = {
38
+ dom_id: "#app",
39
+ spec,
40
+ deepLinking: true,
41
+ presets: [
42
+ "SwaggerUIBundle.presets.apis",
43
+ "SwaggerUIBundle.presets.standalone"
44
+ ],
45
+ plugins: [
46
+ "SwaggerUIBundle.plugins.DownloadUrl"
47
+ ],
48
+ ...config
49
+ };
50
+ body = `
51
+ <body>
52
+ <div id="app"></div>
53
+
54
+ <script src="${esc(scriptUrl)}"><\/script>
55
+
56
+ <script>
57
+ window.onload = () => {
58
+ window.ui = SwaggerUIBundle(${stringifyJSON(swaggerConfig).replace(/"(SwaggerUIBundle\.[^"]+)"/g, "$1")})
59
+ }
60
+ <\/script>
61
+ </body>
62
+ `;
63
+ } else {
64
+ const scalarConfig = {
65
+ content: stringifyJSON(spec),
66
+ ...config
67
+ };
68
+ body = `
69
+ <body>
70
+ <div id="app" data-config="${esc(stringifyJSON(scalarConfig))}"></div>
71
+
72
+ <script src="${esc(scriptUrl)}"><\/script>
73
+
74
+ <script>
75
+ Scalar.createApiReference('#app', JSON.parse(document.getElementById('app').dataset.config))
76
+ <\/script>
77
+ </body>
78
+ `;
79
+ }
35
80
  return `
36
81
  <!doctype html>
37
82
  <html>
@@ -39,19 +84,12 @@ class OpenAPIReferencePlugin {
39
84
  <meta charset="utf-8" />
40
85
  <meta name="viewport" content="width=device-width, initial-scale=1" />
41
86
  <title>${esc(title)}</title>
87
+ ${cssUrl ? `<link rel="stylesheet" type="text/css" href="${esc(cssUrl)}" />` : ""}
42
88
  ${head}
43
89
  </head>
44
- <body>
45
- <div id="app" data-config="${esc(stringifyJSON(finalConfig))}"></div>
46
-
47
- <script src="${esc(scriptUrl)}"><\/script>
48
-
49
- <script>
50
- Scalar.createApiReference('#app', JSON.parse(document.getElementById('app').dataset.config))
51
- <\/script>
52
- </body>
90
+ ${body}
53
91
  </html>
54
- `;
92
+ `;
55
93
  });
56
94
  }
57
95
  init(options, router) {
@@ -89,7 +127,9 @@ class OpenAPIReferencePlugin {
89
127
  await value(this.docsHead, options2),
90
128
  await value(this.docsScriptUrl, options2),
91
129
  await value(this.docsConfig, options2),
92
- await generateSpec()
130
+ await generateSpec(),
131
+ this.docsProvider,
132
+ await value(this.docsCssUrl, options2)
93
133
  );
94
134
  return {
95
135
  matched: true,
@@ -2,7 +2,7 @@ import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, StandardBracketNota
2
2
  import { StandardHandler } from '@orpc/server/standard';
3
3
  import { isORPCErrorStatus } from '@orpc/client';
4
4
  import { fallbackContractConfig } from '@orpc/contract';
5
- import { isObject, stringifyJSON } from '@orpc/shared';
5
+ import { isObject, stringifyJSON, tryDecodeURIComponent, value } from '@orpc/shared';
6
6
  import { toHttpPath } from '@orpc/client/standard';
7
7
  import { traverseContractProcedures, isProcedure, getLazyMeta, unlazy, getRouter, createContractedProcedure } from '@orpc/server';
8
8
  import { createRouter, addRoute, findRoute } from 'rou3';
@@ -97,14 +97,22 @@ function toRou3Pattern(path) {
97
97
  return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/**:$1").replace(/\/\{([^}]+)\}/g, "/:$1");
98
98
  }
99
99
  function decodeParams(params) {
100
- return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, decodeURIComponent(value)]));
100
+ return Object.fromEntries(Object.entries(params).map(([key, value]) => [key, tryDecodeURIComponent(value)]));
101
101
  }
102
102
 
103
103
  class StandardOpenAPIMatcher {
104
+ filter;
104
105
  tree = createRouter();
105
106
  pendingRouters = [];
107
+ constructor(options = {}) {
108
+ this.filter = options.filter ?? true;
109
+ }
106
110
  init(router, path = []) {
107
- const laziedOptions = traverseContractProcedures({ router, path }, ({ path: path2, contract }) => {
111
+ const laziedOptions = traverseContractProcedures({ router, path }, (traverseOptions) => {
112
+ if (!value(this.filter, traverseOptions)) {
113
+ return;
114
+ }
115
+ const { path: path2, contract } = traverseOptions;
108
116
  const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
109
117
  const httpPath = toRou3Pattern(contract["~orpc"].route.path ?? toHttpPath(path2));
110
118
  if (isProcedure(contract)) {
@@ -168,9 +176,9 @@ class StandardOpenAPIMatcher {
168
176
  class StandardOpenAPIHandler extends StandardHandler {
169
177
  constructor(router, options) {
170
178
  const jsonSerializer = new StandardOpenAPIJsonSerializer(options);
171
- const bracketNotationSerializer = new StandardBracketNotationSerializer();
179
+ const bracketNotationSerializer = new StandardBracketNotationSerializer(options);
172
180
  const serializer = new StandardOpenAPISerializer(jsonSerializer, bracketNotationSerializer);
173
- const matcher = new StandardOpenAPIMatcher();
181
+ const matcher = new StandardOpenAPIMatcher(options);
174
182
  const codec = new StandardOpenAPICodec(serializer);
175
183
  super(router, matcher, codec, options);
176
184
  }
@@ -3,8 +3,8 @@ import { toHttpPath } from '@orpc/client/standard';
3
3
  import { fallbackContractConfig, getEventIteratorSchemaDetails } from '@orpc/contract';
4
4
  import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, getDynamicParams } from '@orpc/openapi-client/standard';
5
5
  import { isProcedure, resolveContractProcedures } from '@orpc/server';
6
- import { isObject, stringifyJSON, findDeepMatches, toArray, clone } from '@orpc/shared';
7
- import { TypeName } from 'json-schema-typed/draft-2020-12';
6
+ import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
7
+ import { TypeName } from '@orpc/interop/json-schema-typed/draft-2020-12';
8
8
 
9
9
  const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
10
10
  function customOpenAPIOperation(o, extend) {
@@ -114,13 +114,18 @@ function isAnySchema(schema) {
114
114
  return false;
115
115
  }
116
116
  function separateObjectSchema(schema, separatedProperties) {
117
- if (Object.keys(schema).some((k) => k !== "type" && k !== "properties" && k !== "required" && LOGIC_KEYWORDS.includes(k))) {
117
+ if (Object.keys(schema).some(
118
+ (k) => !["type", "properties", "required", "additionalProperties"].includes(k) && LOGIC_KEYWORDS.includes(k) && schema[k] !== void 0
119
+ )) {
118
120
  return [{ type: "object" }, schema];
119
121
  }
120
122
  const matched = { ...schema };
121
123
  const rest = { ...schema };
122
- matched.properties = schema.properties && Object.entries(schema.properties).filter(([key]) => separatedProperties.includes(key)).reduce((acc, [key, value]) => {
123
- acc[key] = value;
124
+ matched.properties = separatedProperties.reduce((acc, key) => {
125
+ const keySchema = schema.properties?.[key] ?? schema.additionalProperties;
126
+ if (keySchema !== void 0) {
127
+ acc[key] = keySchema;
128
+ }
124
129
  return acc;
125
130
  }, {});
126
131
  matched.required = schema.required?.filter((key) => separatedProperties.includes(key));
@@ -345,6 +350,15 @@ function checkParamsSchema(schema, params) {
345
350
  function toOpenAPISchema(schema) {
346
351
  return schema === true ? {} : schema === false ? { not: {} } : schema;
347
352
  }
353
+ const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
354
+ function resolveOpenAPIJsonSchemaRef(doc, schema) {
355
+ if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
356
+ return schema;
357
+ }
358
+ const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
359
+ const resolved = doc.components?.schemas?.[name];
360
+ return resolved ?? schema;
361
+ }
348
362
 
349
363
  class CompositeSchemaConverter {
350
364
  converters;
@@ -376,36 +390,50 @@ class OpenAPIGenerator {
376
390
  * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
377
391
  */
378
392
  async generate(router, options = {}) {
379
- const exclude = options.exclude ?? (() => false);
393
+ const filter = options.filter ?? (({ contract, path }) => {
394
+ return !(options.exclude?.(contract, path) ?? false);
395
+ });
380
396
  const doc = {
381
397
  ...clone(options),
382
398
  info: options.info ?? { title: "API Reference", version: "0.0.0" },
383
399
  openapi: "3.1.1",
384
- exclude: void 0
400
+ exclude: void 0,
401
+ filter: void 0,
402
+ commonSchemas: void 0
385
403
  };
404
+ const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
386
405
  const contracts = [];
387
- await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
388
- if (!exclude(contract, path)) {
389
- contracts.push({ contract, path });
406
+ await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
407
+ if (!value(filter, traverseOptions)) {
408
+ return;
390
409
  }
410
+ contracts.push(traverseOptions);
391
411
  });
392
412
  const errors = [];
393
413
  for (const { contract, path } of contracts) {
394
- const operationId = path.join(".");
414
+ const stringPath = path.join(".");
395
415
  try {
396
416
  const def = contract["~orpc"];
397
417
  const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
398
418
  const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
399
- const operationObjectRef = {
400
- operationId,
401
- summary: def.route.summary,
402
- description: def.route.description,
403
- deprecated: def.route.deprecated,
404
- tags: def.route.tags?.map((tag) => tag)
405
- };
406
- await this.#request(operationObjectRef, def);
407
- await this.#successResponse(operationObjectRef, def);
408
- await this.#errorResponse(operationObjectRef, def);
419
+ let operationObjectRef;
420
+ if (def.route.spec !== void 0 && typeof def.route.spec !== "function") {
421
+ operationObjectRef = def.route.spec;
422
+ } else {
423
+ operationObjectRef = {
424
+ operationId: def.route.operationId ?? stringPath,
425
+ summary: def.route.summary,
426
+ description: def.route.description,
427
+ deprecated: def.route.deprecated,
428
+ tags: def.route.tags?.map((tag) => tag)
429
+ };
430
+ await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
431
+ await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
432
+ await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
433
+ }
434
+ if (typeof def.route.spec === "function") {
435
+ operationObjectRef = def.route.spec(operationObjectRef);
436
+ }
409
437
  doc.paths ??= {};
410
438
  doc.paths[httpPath] ??= {};
411
439
  doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
@@ -414,7 +442,7 @@ class OpenAPIGenerator {
414
442
  throw e;
415
443
  }
416
444
  errors.push(
417
- `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${operationId}
445
+ `[OpenAPIGenerator] Error occurred while generating OpenAPI for procedure at path: ${stringPath}
418
446
  ${e.message}`
419
447
  );
420
448
  }
@@ -428,22 +456,96 @@ ${errors.join("\n\n")}`
428
456
  }
429
457
  return this.serializer.serialize(doc)[0];
430
458
  }
431
- async #request(ref, def) {
459
+ async #resolveCommonSchemas(doc, commonSchemas) {
460
+ let undefinedErrorJsonSchema = {
461
+ type: "object",
462
+ properties: {
463
+ defined: { const: false },
464
+ code: { type: "string" },
465
+ status: { type: "number" },
466
+ message: { type: "string" },
467
+ data: {}
468
+ },
469
+ required: ["defined", "code", "status", "message"]
470
+ };
471
+ const baseSchemaConvertOptions = {};
472
+ if (commonSchemas) {
473
+ baseSchemaConvertOptions.components = [];
474
+ for (const key in commonSchemas) {
475
+ const options = commonSchemas[key];
476
+ if (options.schema === void 0) {
477
+ continue;
478
+ }
479
+ const { schema, strategy = "input" } = options;
480
+ const [required, json] = await this.converter.convert(schema, { strategy });
481
+ const allowedStrategies = [strategy];
482
+ if (strategy === "input") {
483
+ const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
484
+ if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
485
+ allowedStrategies.push("output");
486
+ }
487
+ } else if (strategy === "output") {
488
+ const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
489
+ if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
490
+ allowedStrategies.push("input");
491
+ }
492
+ }
493
+ baseSchemaConvertOptions.components.push({
494
+ schema,
495
+ required,
496
+ ref: `#/components/schemas/${key}`,
497
+ allowedStrategies
498
+ });
499
+ }
500
+ doc.components ??= {};
501
+ doc.components.schemas ??= {};
502
+ for (const key in commonSchemas) {
503
+ const options = commonSchemas[key];
504
+ if (options.schema === void 0) {
505
+ if (options.error === "UndefinedError") {
506
+ doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
507
+ undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
508
+ }
509
+ continue;
510
+ }
511
+ const { schema, strategy = "input" } = options;
512
+ const [, json] = await this.converter.convert(
513
+ schema,
514
+ {
515
+ ...baseSchemaConvertOptions,
516
+ strategy,
517
+ minStructureDepthForRef: 1
518
+ // not allow use $ref for root schemas
519
+ }
520
+ );
521
+ doc.components.schemas[key] = toOpenAPISchema(json);
522
+ }
523
+ }
524
+ return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
525
+ }
526
+ async #request(doc, ref, def, baseSchemaConvertOptions) {
432
527
  const method = fallbackContractConfig("defaultMethod", def.route.method);
433
528
  const details = getEventIteratorSchemaDetails(def.inputSchema);
434
529
  if (details) {
435
530
  ref.requestBody = {
436
531
  required: true,
437
532
  content: toOpenAPIEventIteratorContent(
438
- await this.converter.convert(details.yields, { strategy: "input" }),
439
- await this.converter.convert(details.returns, { strategy: "input" })
533
+ await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
534
+ await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
440
535
  )
441
536
  };
442
537
  return;
443
538
  }
444
539
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
445
540
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
446
- let [required, schema] = await this.converter.convert(def.inputSchema, { strategy: "input" });
541
+ let [required, schema] = await this.converter.convert(
542
+ def.inputSchema,
543
+ {
544
+ ...baseSchemaConvertOptions,
545
+ strategy: "input",
546
+ minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
547
+ }
548
+ );
447
549
  if (isAnySchema(schema) && !dynamicParams?.length) {
448
550
  return;
449
551
  }
@@ -465,13 +567,14 @@ ${errors.join("\n\n")}`
465
567
  ref.parameters.push(...toOpenAPIParameters(paramsSchema, "path"));
466
568
  }
467
569
  if (method === "GET") {
468
- if (!isObjectSchema(schema)) {
570
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, schema);
571
+ if (!isObjectSchema(resolvedSchema)) {
469
572
  throw new OpenAPIGeneratorError(
470
573
  'When method is "GET", input schema must satisfy: object | any | unknown'
471
574
  );
472
575
  }
473
576
  ref.parameters ??= [];
474
- ref.parameters.push(...toOpenAPIParameters(schema, "query"));
577
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, "query"));
475
578
  } else {
476
579
  ref.requestBody = {
477
580
  required,
@@ -486,7 +589,8 @@ ${errors.join("\n\n")}`
486
589
  if (!isObjectSchema(schema)) {
487
590
  throw error;
488
591
  }
489
- if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
592
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
593
+ if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
490
594
  throw new OpenAPIGeneratorError(
491
595
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
492
596
  );
@@ -494,12 +598,13 @@ ${errors.join("\n\n")}`
494
598
  for (const from of ["params", "query", "headers"]) {
495
599
  const fromSchema = schema.properties?.[from];
496
600
  if (fromSchema !== void 0) {
497
- if (!isObjectSchema(fromSchema)) {
601
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
602
+ if (!isObjectSchema(resolvedSchema)) {
498
603
  throw error;
499
604
  }
500
605
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
501
606
  ref.parameters ??= [];
502
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
607
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
503
608
  }
504
609
  }
505
610
  if (schema.properties?.body !== void 0) {
@@ -509,7 +614,7 @@ ${errors.join("\n\n")}`
509
614
  };
510
615
  }
511
616
  }
512
- async #successResponse(ref, def) {
617
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
513
618
  const outputSchema = def.outputSchema;
514
619
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
515
620
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -520,13 +625,20 @@ ${errors.join("\n\n")}`
520
625
  ref.responses[status] = {
521
626
  description,
522
627
  content: toOpenAPIEventIteratorContent(
523
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
524
- await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
628
+ await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
629
+ await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
525
630
  )
526
631
  };
527
632
  return;
528
633
  }
529
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
634
+ const [required, json] = await this.converter.convert(
635
+ outputSchema,
636
+ {
637
+ ...baseSchemaConvertOptions,
638
+ strategy: "output",
639
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
640
+ }
641
+ );
530
642
  if (outputStructure === "compact") {
531
643
  ref.responses ??= {};
532
644
  ref.responses[status] = {
@@ -553,11 +665,12 @@ ${errors.join("\n\n")}`
553
665
  let schemaStatus;
554
666
  let schemaDescription;
555
667
  if (item.properties?.status !== void 0) {
556
- if (typeof item.properties.status !== "object" || item.properties.status.const === void 0 || typeof item.properties.status.const !== "number" || !Number.isInteger(item.properties.status.const) || isORPCErrorStatus(item.properties.status.const)) {
668
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
669
+ if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
557
670
  throw error;
558
671
  }
559
- schemaStatus = item.properties.status.const;
560
- schemaDescription = item.properties.status.description;
672
+ schemaStatus = statusSchema.const;
673
+ schemaDescription = statusSchema.description;
561
674
  }
562
675
  const itemStatus = schemaStatus ?? status;
563
676
  const itemDescription = schemaDescription ?? description;
@@ -573,16 +686,17 @@ ${errors.join("\n\n")}`
573
686
  description: itemDescription
574
687
  };
575
688
  if (item.properties?.headers !== void 0) {
576
- if (!isObjectSchema(item.properties.headers)) {
689
+ const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
690
+ if (!isObjectSchema(headersSchema)) {
577
691
  throw error;
578
692
  }
579
- for (const key in item.properties.headers.properties) {
580
- const headerSchema = item.properties.headers.properties[key];
693
+ for (const key in headersSchema.properties) {
694
+ const headerSchema = headersSchema.properties[key];
581
695
  if (headerSchema !== void 0) {
582
696
  ref.responses[itemStatus].headers ??= {};
583
697
  ref.responses[itemStatus].headers[key] = {
584
698
  schema: toOpenAPISchema(headerSchema),
585
- required: item.properties.headers.required?.includes(key)
699
+ required: item.required?.includes("headers") && headersSchema.required?.includes(key)
586
700
  };
587
701
  }
588
702
  }
@@ -594,7 +708,7 @@ ${errors.join("\n\n")}`
594
708
  }
595
709
  }
596
710
  }
597
- async #errorResponse(ref, def) {
711
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
598
712
  const errorMap = def.errorMap;
599
713
  const errors = {};
600
714
  for (const code in errorMap) {
@@ -604,7 +718,7 @@ ${errors.join("\n\n")}`
604
718
  }
605
719
  const status = fallbackORPCErrorStatus(code, config.status);
606
720
  const message = fallbackORPCErrorMessage(code, config.message);
607
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
721
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
608
722
  errors[status] ??= [];
609
723
  errors[status].push({
610
724
  type: "object",
@@ -626,17 +740,7 @@ ${errors.join("\n\n")}`
626
740
  content: toOpenAPIContent({
627
741
  oneOf: [
628
742
  ...schemas,
629
- {
630
- type: "object",
631
- properties: {
632
- defined: { const: false },
633
- code: { type: "string" },
634
- status: { type: "number" },
635
- message: { type: "string" },
636
- data: {}
637
- },
638
- required: ["defined", "code", "status", "message"]
639
- }
743
+ undefinedErrorSchema
640
744
  ]
641
745
  })
642
746
  };
@@ -644,4 +748,4 @@ ${errors.join("\n\n")}`
644
748
  }
645
749
  }
646
750
 
647
- export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, separateObjectSchema as s, toOpenAPIPath as t };
751
+ export { CompositeSchemaConverter as C, LOGIC_KEYWORDS as L, OpenAPIGenerator as O, applyCustomOpenAPIOperation as a, toOpenAPIMethod as b, customOpenAPIOperation as c, toOpenAPIContent as d, toOpenAPIEventIteratorContent as e, toOpenAPIParameters as f, getCustomOpenAPIOperation as g, checkParamsSchema as h, toOpenAPISchema as i, isFileSchema as j, isObjectSchema as k, isAnySchema as l, filterSchemaBranches as m, applySchemaOptionality as n, expandUnionSchema as o, expandArrayableSchema as p, isPrimitiveSchema as q, resolveOpenAPIJsonSchemaRef as r, separateObjectSchema as s, toOpenAPIPath as t };
@@ -0,0 +1,31 @@
1
+ import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
2
+ import { TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
3
+ import { StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
+ import { HTTPPath } from '@orpc/client';
5
+ import { Value } from '@orpc/shared';
6
+
7
+ interface StandardOpenAPIMatcherOptions {
8
+ /**
9
+ * Filter procedures. Return `false` to exclude a procedure from matching.
10
+ *
11
+ * @default true
12
+ */
13
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
14
+ }
15
+ declare class StandardOpenAPIMatcher implements StandardMatcher {
16
+ private readonly filter;
17
+ private readonly tree;
18
+ private pendingRouters;
19
+ constructor(options?: StandardOpenAPIMatcherOptions);
20
+ init(router: AnyRouter, path?: readonly string[]): void;
21
+ match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
22
+ }
23
+
24
+ interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions {
25
+ }
26
+ declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
27
+ constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
28
+ }
29
+
30
+ export { StandardOpenAPIHandler as a, StandardOpenAPIMatcher as c };
31
+ export type { StandardOpenAPIHandlerOptions as S, StandardOpenAPIMatcherOptions as b };
@@ -0,0 +1,31 @@
1
+ import { StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions } from '@orpc/openapi-client/standard';
2
+ import { TraverseContractProcedureCallbackOptions, AnyRouter, Context, Router } from '@orpc/server';
3
+ import { StandardMatcher, StandardMatchResult, StandardHandlerOptions, StandardHandler } from '@orpc/server/standard';
4
+ import { HTTPPath } from '@orpc/client';
5
+ import { Value } from '@orpc/shared';
6
+
7
+ interface StandardOpenAPIMatcherOptions {
8
+ /**
9
+ * Filter procedures. Return `false` to exclude a procedure from matching.
10
+ *
11
+ * @default true
12
+ */
13
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
14
+ }
15
+ declare class StandardOpenAPIMatcher implements StandardMatcher {
16
+ private readonly filter;
17
+ private readonly tree;
18
+ private pendingRouters;
19
+ constructor(options?: StandardOpenAPIMatcherOptions);
20
+ init(router: AnyRouter, path?: readonly string[]): void;
21
+ match(method: string, pathname: HTTPPath): Promise<StandardMatchResult>;
22
+ }
23
+
24
+ interface StandardOpenAPIHandlerOptions<T extends Context> extends StandardHandlerOptions<T>, StandardOpenAPIJsonSerializerOptions, StandardBracketNotationSerializerOptions, StandardOpenAPIMatcherOptions {
25
+ }
26
+ declare class StandardOpenAPIHandler<T extends Context> extends StandardHandler<T> {
27
+ constructor(router: Router<any, T>, options: NoInfer<StandardOpenAPIHandlerOptions<T>>);
28
+ }
29
+
30
+ export { StandardOpenAPIHandler as a, StandardOpenAPIMatcher as c };
31
+ export type { StandardOpenAPIHandlerOptions as S, StandardOpenAPIMatcherOptions as b };