@orpc/openapi 0.0.0-next.a09e9be → 0.0.0-next.a0ab4bf

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,7 +1,8 @@
1
1
  import { standardizeHTTPPath, StandardOpenAPIJsonSerializer, StandardBracketNotationSerializer, StandardOpenAPISerializer } from '@orpc/openapi-client/standard';
2
2
  import { StandardHandler } from '@orpc/server/standard';
3
+ import { isORPCErrorStatus } from '@orpc/client';
3
4
  import { fallbackContractConfig } from '@orpc/contract';
4
- import { isObject } from '@orpc/shared';
5
+ import { isObject, stringifyJSON, value } from '@orpc/shared';
5
6
  import { toHttpPath } from '@orpc/client/standard';
6
7
  import { traverseContractProcedures, isProcedure, getLazyMeta, unlazy, getRouter, createContractedProcedure } from '@orpc/server';
7
8
  import { createRouter, addRoute, findRoute } from 'rou3';
@@ -52,13 +53,21 @@ class StandardOpenAPICodec {
52
53
  body: this.serializer.serialize(output)
53
54
  };
54
55
  }
55
- if (!isObject(output)) {
56
- throw new Error(
57
- 'Invalid output structure for "detailed" output. Expected format: { body: any, headers?: Record<string, string | string[] | undefined> }'
58
- );
56
+ if (!this.#isDetailedOutput(output)) {
57
+ throw new Error(`
58
+ Invalid "detailed" output structure:
59
+ \u2022 Expected an object with optional properties:
60
+ - status (number 200-399)
61
+ - headers (Record<string, string | string[]>)
62
+ - body (any)
63
+ \u2022 No extra keys allowed.
64
+
65
+ Actual value:
66
+ ${stringifyJSON(output)}
67
+ `);
59
68
  }
60
69
  return {
61
- status: successStatus,
70
+ status: output.status ?? successStatus,
62
71
  headers: output.headers ?? {},
63
72
  body: this.serializer.serialize(output.body)
64
73
  };
@@ -70,6 +79,18 @@ class StandardOpenAPICodec {
70
79
  body: this.serializer.serialize(error.toJSON(), { outputFormat: "plain" })
71
80
  };
72
81
  }
82
+ #isDetailedOutput(output) {
83
+ if (!isObject(output)) {
84
+ return false;
85
+ }
86
+ if (output.headers && !isObject(output.headers)) {
87
+ return false;
88
+ }
89
+ if (output.status !== void 0 && (typeof output.status !== "number" || !Number.isInteger(output.status) || isORPCErrorStatus(output.status))) {
90
+ return false;
91
+ }
92
+ return true;
93
+ }
73
94
  }
74
95
 
75
96
  function toRou3Pattern(path) {
@@ -80,10 +101,18 @@ function decodeParams(params) {
80
101
  }
81
102
 
82
103
  class StandardOpenAPIMatcher {
104
+ filter;
83
105
  tree = createRouter();
84
106
  pendingRouters = [];
107
+ constructor(options = {}) {
108
+ this.filter = options.filter ?? true;
109
+ }
85
110
  init(router, path = []) {
86
- 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;
87
116
  const method = fallbackContractConfig("defaultMethod", contract["~orpc"].route.method);
88
117
  const httpPath = toRou3Pattern(contract["~orpc"].route.path ?? toHttpPath(path2));
89
118
  if (isProcedure(contract)) {
@@ -147,9 +176,9 @@ class StandardOpenAPIMatcher {
147
176
  class StandardOpenAPIHandler extends StandardHandler {
148
177
  constructor(router, options) {
149
178
  const jsonSerializer = new StandardOpenAPIJsonSerializer(options);
150
- const bracketNotationSerializer = new StandardBracketNotationSerializer();
179
+ const bracketNotationSerializer = new StandardBracketNotationSerializer(options);
151
180
  const serializer = new StandardOpenAPISerializer(jsonSerializer, bracketNotationSerializer);
152
- const matcher = new StandardOpenAPIMatcher();
181
+ const matcher = new StandardOpenAPIMatcher(options);
153
182
  const codec = new StandardOpenAPICodec(serializer);
154
183
  super(router, matcher, codec, options);
155
184
  }
@@ -1,10 +1,10 @@
1
- import { fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
1
+ import { isORPCErrorStatus, fallbackORPCErrorStatus, fallbackORPCErrorMessage } from '@orpc/client';
2
2
  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, findDeepMatches, toArray, clone } from '@orpc/shared';
7
- import 'json-schema-typed/draft-2020-12';
6
+ import { isObject, stringifyJSON, findDeepMatches, toArray, clone, value } from '@orpc/shared';
7
+ import { TypeName } from 'json-schema-typed/draft-2020-12';
8
8
 
9
9
  const OPERATION_EXTENDER_SYMBOL = Symbol("ORPC_OPERATION_EXTENDER");
10
10
  function customOpenAPIOperation(o, extend) {
@@ -184,6 +184,57 @@ function applySchemaOptionality(required, schema) {
184
184
  ]
185
185
  };
186
186
  }
187
+ function expandUnionSchema(schema) {
188
+ if (typeof schema === "object") {
189
+ for (const keyword of ["anyOf", "oneOf"]) {
190
+ if (schema[keyword] && Object.keys(schema).every(
191
+ (k) => k === keyword || !LOGIC_KEYWORDS.includes(k)
192
+ )) {
193
+ return schema[keyword].flatMap((s) => expandUnionSchema(s));
194
+ }
195
+ }
196
+ }
197
+ return [schema];
198
+ }
199
+ function expandArrayableSchema(schema) {
200
+ const schemas = expandUnionSchema(schema);
201
+ if (schemas.length !== 2) {
202
+ return void 0;
203
+ }
204
+ const arraySchema = schemas.find(
205
+ (s) => typeof s === "object" && s.type === "array" && Object.keys(s).filter((k) => LOGIC_KEYWORDS.includes(k)).every((k) => k === "type" || k === "items")
206
+ );
207
+ if (arraySchema === void 0) {
208
+ return void 0;
209
+ }
210
+ const items1 = arraySchema.items;
211
+ const items2 = schemas.find((s) => s !== arraySchema);
212
+ if (stringifyJSON(items1) !== stringifyJSON(items2)) {
213
+ return void 0;
214
+ }
215
+ return [items2, arraySchema];
216
+ }
217
+ const PRIMITIVE_SCHEMA_TYPES = /* @__PURE__ */ new Set([
218
+ TypeName.String,
219
+ TypeName.Number,
220
+ TypeName.Integer,
221
+ TypeName.Boolean,
222
+ TypeName.Null
223
+ ]);
224
+ function isPrimitiveSchema(schema) {
225
+ return expandUnionSchema(schema).every((s) => {
226
+ if (typeof s === "boolean") {
227
+ return false;
228
+ }
229
+ if (typeof s.type === "string" && PRIMITIVE_SCHEMA_TYPES.has(s.type)) {
230
+ return true;
231
+ }
232
+ if (s.const !== void 0) {
233
+ return true;
234
+ }
235
+ return false;
236
+ });
237
+ }
187
238
 
188
239
  function toOpenAPIPath(path) {
189
240
  return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
@@ -256,13 +307,26 @@ function toOpenAPIParameters(schema, parameterIn) {
256
307
  const parameters = [];
257
308
  for (const key in schema.properties) {
258
309
  const keySchema = schema.properties[key];
310
+ let isDeepObjectStyle = true;
311
+ if (parameterIn !== "query") {
312
+ isDeepObjectStyle = false;
313
+ } else if (isPrimitiveSchema(keySchema)) {
314
+ isDeepObjectStyle = false;
315
+ } else {
316
+ const [item] = expandArrayableSchema(keySchema) ?? [];
317
+ if (item !== void 0 && isPrimitiveSchema(item)) {
318
+ isDeepObjectStyle = false;
319
+ }
320
+ }
259
321
  parameters.push({
260
322
  name: key,
261
323
  in: parameterIn,
262
324
  required: schema.required?.includes(key),
263
- style: parameterIn === "query" ? "deepObject" : void 0,
264
- explode: parameterIn === "query" ? true : void 0,
265
- schema: toOpenAPISchema(keySchema)
325
+ schema: toOpenAPISchema(keySchema),
326
+ style: isDeepObjectStyle ? "deepObject" : void 0,
327
+ explode: isDeepObjectStyle ? true : void 0,
328
+ allowEmptyValue: parameterIn === "query" ? true : void 0,
329
+ allowReserved: parameterIn === "query" ? true : void 0
266
330
  });
267
331
  }
268
332
  return parameters;
@@ -281,6 +345,15 @@ function checkParamsSchema(schema, params) {
281
345
  function toOpenAPISchema(schema) {
282
346
  return schema === true ? {} : schema === false ? { not: {} } : schema;
283
347
  }
348
+ const OPENAPI_JSON_SCHEMA_REF_PREFIX = "#/components/schemas/";
349
+ function resolveOpenAPIJsonSchemaRef(doc, schema) {
350
+ if (typeof schema !== "object" || !schema.$ref?.startsWith(OPENAPI_JSON_SCHEMA_REF_PREFIX)) {
351
+ return schema;
352
+ }
353
+ const name = schema.$ref.slice(OPENAPI_JSON_SCHEMA_REF_PREFIX.length);
354
+ const resolved = doc.components?.schemas?.[name];
355
+ return resolved ?? schema;
356
+ }
284
357
 
285
358
  class CompositeSchemaConverter {
286
359
  converters;
@@ -312,14 +385,24 @@ class OpenAPIGenerator {
312
385
  * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
313
386
  */
314
387
  async generate(router, options = {}) {
388
+ const filter = options.filter ?? (({ contract, path }) => {
389
+ return !(options.exclude?.(contract, path) ?? false);
390
+ });
315
391
  const doc = {
316
392
  ...clone(options),
317
393
  info: options.info ?? { title: "API Reference", version: "0.0.0" },
318
- openapi: "3.1.1"
394
+ openapi: "3.1.1",
395
+ exclude: void 0,
396
+ filter: void 0,
397
+ commonSchemas: void 0
319
398
  };
399
+ const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
320
400
  const contracts = [];
321
- await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
322
- contracts.push({ contract, path });
401
+ await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
402
+ if (!value(filter, traverseOptions)) {
403
+ return;
404
+ }
405
+ contracts.push(traverseOptions);
323
406
  });
324
407
  const errors = [];
325
408
  for (const { contract, path } of contracts) {
@@ -328,16 +411,21 @@ class OpenAPIGenerator {
328
411
  const def = contract["~orpc"];
329
412
  const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
330
413
  const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
331
- const operationObjectRef = {
332
- operationId,
333
- summary: def.route.summary,
334
- description: def.route.description,
335
- deprecated: def.route.deprecated,
336
- tags: def.route.tags?.map((tag) => tag)
337
- };
338
- await this.#request(operationObjectRef, def);
339
- await this.#successResponse(operationObjectRef, def);
340
- await this.#errorResponse(operationObjectRef, def);
414
+ let operationObjectRef;
415
+ if (def.route.spec !== void 0) {
416
+ operationObjectRef = def.route.spec;
417
+ } else {
418
+ operationObjectRef = {
419
+ operationId,
420
+ summary: def.route.summary,
421
+ description: def.route.description,
422
+ deprecated: def.route.deprecated,
423
+ tags: def.route.tags?.map((tag) => tag)
424
+ };
425
+ await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
426
+ await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
427
+ await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions, undefinedErrorJsonSchema);
428
+ }
341
429
  doc.paths ??= {};
342
430
  doc.paths[httpPath] ??= {};
343
431
  doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
@@ -360,22 +448,96 @@ ${errors.join("\n\n")}`
360
448
  }
361
449
  return this.serializer.serialize(doc)[0];
362
450
  }
363
- async #request(ref, def) {
451
+ async #resolveCommonSchemas(doc, commonSchemas) {
452
+ let undefinedErrorJsonSchema = {
453
+ type: "object",
454
+ properties: {
455
+ defined: { const: false },
456
+ code: { type: "string" },
457
+ status: { type: "number" },
458
+ message: { type: "string" },
459
+ data: {}
460
+ },
461
+ required: ["defined", "code", "status", "message"]
462
+ };
463
+ const baseSchemaConvertOptions = {};
464
+ if (commonSchemas) {
465
+ baseSchemaConvertOptions.components = [];
466
+ for (const key in commonSchemas) {
467
+ const options = commonSchemas[key];
468
+ if (options.schema === void 0) {
469
+ continue;
470
+ }
471
+ const { schema, strategy = "input" } = options;
472
+ const [required, json] = await this.converter.convert(schema, { strategy });
473
+ const allowedStrategies = [strategy];
474
+ if (strategy === "input") {
475
+ const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
476
+ if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
477
+ allowedStrategies.push("output");
478
+ }
479
+ } else if (strategy === "output") {
480
+ const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
481
+ if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
482
+ allowedStrategies.push("input");
483
+ }
484
+ }
485
+ baseSchemaConvertOptions.components.push({
486
+ schema,
487
+ required,
488
+ ref: `#/components/schemas/${key}`,
489
+ allowedStrategies
490
+ });
491
+ }
492
+ doc.components ??= {};
493
+ doc.components.schemas ??= {};
494
+ for (const key in commonSchemas) {
495
+ const options = commonSchemas[key];
496
+ if (options.schema === void 0) {
497
+ if (options.error === "UndefinedError") {
498
+ doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
499
+ undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
500
+ }
501
+ continue;
502
+ }
503
+ const { schema, strategy = "input" } = options;
504
+ const [, json] = await this.converter.convert(
505
+ schema,
506
+ {
507
+ ...baseSchemaConvertOptions,
508
+ strategy,
509
+ minStructureDepthForRef: 1
510
+ // not allow use $ref for root schemas
511
+ }
512
+ );
513
+ doc.components.schemas[key] = toOpenAPISchema(json);
514
+ }
515
+ }
516
+ return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
517
+ }
518
+ async #request(doc, ref, def, baseSchemaConvertOptions) {
364
519
  const method = fallbackContractConfig("defaultMethod", def.route.method);
365
520
  const details = getEventIteratorSchemaDetails(def.inputSchema);
366
521
  if (details) {
367
522
  ref.requestBody = {
368
523
  required: true,
369
524
  content: toOpenAPIEventIteratorContent(
370
- await this.converter.convert(details.yields, { strategy: "input" }),
371
- await this.converter.convert(details.returns, { strategy: "input" })
525
+ await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
526
+ await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
372
527
  )
373
528
  };
374
529
  return;
375
530
  }
376
531
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
377
532
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
378
- let [required, schema] = await this.converter.convert(def.inputSchema, { strategy: "input" });
533
+ let [required, schema] = await this.converter.convert(
534
+ def.inputSchema,
535
+ {
536
+ ...baseSchemaConvertOptions,
537
+ strategy: "input",
538
+ minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
539
+ }
540
+ );
379
541
  if (isAnySchema(schema) && !dynamicParams?.length) {
380
542
  return;
381
543
  }
@@ -418,7 +580,8 @@ ${errors.join("\n\n")}`
418
580
  if (!isObjectSchema(schema)) {
419
581
  throw error;
420
582
  }
421
- if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
583
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
584
+ if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
422
585
  throw new OpenAPIGeneratorError(
423
586
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
424
587
  );
@@ -426,12 +589,13 @@ ${errors.join("\n\n")}`
426
589
  for (const from of ["params", "query", "headers"]) {
427
590
  const fromSchema = schema.properties?.[from];
428
591
  if (fromSchema !== void 0) {
429
- if (!isObjectSchema(fromSchema)) {
592
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
593
+ if (!isObjectSchema(resolvedSchema)) {
430
594
  throw error;
431
595
  }
432
596
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
433
597
  ref.parameters ??= [];
434
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
598
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
435
599
  }
436
600
  }
437
601
  if (schema.properties?.body !== void 0) {
@@ -441,7 +605,7 @@ ${errors.join("\n\n")}`
441
605
  };
442
606
  }
443
607
  }
444
- async #successResponse(ref, def) {
608
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
445
609
  const outputSchema = def.outputSchema;
446
610
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
447
611
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -452,46 +616,90 @@ ${errors.join("\n\n")}`
452
616
  ref.responses[status] = {
453
617
  description,
454
618
  content: toOpenAPIEventIteratorContent(
455
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
456
- await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
619
+ await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
620
+ await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
457
621
  )
458
622
  };
459
623
  return;
460
624
  }
461
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
462
- ref.responses ??= {};
463
- ref.responses[status] = {
464
- description
465
- };
625
+ const [required, json] = await this.converter.convert(
626
+ outputSchema,
627
+ {
628
+ ...baseSchemaConvertOptions,
629
+ strategy: "output",
630
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
631
+ }
632
+ );
466
633
  if (outputStructure === "compact") {
634
+ ref.responses ??= {};
635
+ ref.responses[status] = {
636
+ description
637
+ };
467
638
  ref.responses[status].content = toOpenAPIContent(applySchemaOptionality(required, json));
468
639
  return;
469
640
  }
470
- const error = new OpenAPIGeneratorError(
471
- 'When output structure is "detailed", output schema must satisfy: { headers?: Record<string, unknown>, body?: unknown }'
472
- );
473
- if (!isObjectSchema(json)) {
474
- throw error;
475
- }
476
- if (json.properties?.headers !== void 0) {
477
- if (!isObjectSchema(json.properties.headers)) {
641
+ const handledStatuses = /* @__PURE__ */ new Set();
642
+ for (const item of expandUnionSchema(json)) {
643
+ const error = new OpenAPIGeneratorError(`
644
+ When output structure is "detailed", output schema must satisfy:
645
+ {
646
+ status?: number, // must be a literal number and in the range of 200-399
647
+ headers?: Record<string, unknown>,
648
+ body?: unknown
649
+ }
650
+
651
+ But got: ${stringifyJSON(item)}
652
+ `);
653
+ if (!isObjectSchema(item)) {
478
654
  throw error;
479
655
  }
480
- for (const key in json.properties.headers.properties) {
481
- ref.responses[status].headers ??= {};
482
- ref.responses[status].headers[key] = {
483
- schema: toOpenAPISchema(json.properties.headers.properties[key]),
484
- required: json.properties.headers.required?.includes(key)
485
- };
656
+ let schemaStatus;
657
+ let schemaDescription;
658
+ if (item.properties?.status !== void 0) {
659
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
660
+ if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
661
+ throw error;
662
+ }
663
+ schemaStatus = statusSchema.const;
664
+ schemaDescription = statusSchema.description;
665
+ }
666
+ const itemStatus = schemaStatus ?? status;
667
+ const itemDescription = schemaDescription ?? description;
668
+ if (handledStatuses.has(itemStatus)) {
669
+ throw new OpenAPIGeneratorError(`
670
+ When output structure is "detailed", each success status must be unique.
671
+ But got status: ${itemStatus} used more than once.
672
+ `);
673
+ }
674
+ handledStatuses.add(itemStatus);
675
+ ref.responses ??= {};
676
+ ref.responses[itemStatus] = {
677
+ description: itemDescription
678
+ };
679
+ if (item.properties?.headers !== void 0) {
680
+ const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
681
+ if (!isObjectSchema(headersSchema)) {
682
+ throw error;
683
+ }
684
+ for (const key in headersSchema.properties) {
685
+ const headerSchema = headersSchema.properties[key];
686
+ if (headerSchema !== void 0) {
687
+ ref.responses[itemStatus].headers ??= {};
688
+ ref.responses[itemStatus].headers[key] = {
689
+ schema: toOpenAPISchema(headerSchema),
690
+ required: item.required?.includes("headers") && headersSchema.required?.includes(key)
691
+ };
692
+ }
693
+ }
694
+ }
695
+ if (item.properties?.body !== void 0) {
696
+ ref.responses[itemStatus].content = toOpenAPIContent(
697
+ applySchemaOptionality(item.required?.includes("body") ?? false, item.properties.body)
698
+ );
486
699
  }
487
- }
488
- if (json.properties?.body !== void 0) {
489
- ref.responses[status].content = toOpenAPIContent(
490
- applySchemaOptionality(json.required?.includes("body") ?? false, json.properties.body)
491
- );
492
700
  }
493
701
  }
494
- async #errorResponse(ref, def) {
702
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
495
703
  const errorMap = def.errorMap;
496
704
  const errors = {};
497
705
  for (const code in errorMap) {
@@ -501,7 +709,7 @@ ${errors.join("\n\n")}`
501
709
  }
502
710
  const status = fallbackORPCErrorStatus(code, config.status);
503
711
  const message = fallbackORPCErrorMessage(code, config.message);
504
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
712
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
505
713
  errors[status] ??= [];
506
714
  errors[status].push({
507
715
  type: "object",
@@ -523,17 +731,7 @@ ${errors.join("\n\n")}`
523
731
  content: toOpenAPIContent({
524
732
  oneOf: [
525
733
  ...schemas,
526
- {
527
- type: "object",
528
- properties: {
529
- defined: { const: false },
530
- code: { type: "string" },
531
- status: { type: "number" },
532
- message: { type: "string" },
533
- data: {}
534
- },
535
- required: ["defined", "code", "status", "message"]
536
- }
734
+ undefinedErrorSchema
537
735
  ]
538
736
  })
539
737
  };
@@ -541,4 +739,4 @@ ${errors.join("\n\n")}`
541
739
  }
542
740
  }
543
741
 
544
- 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, separateObjectSchema as s, toOpenAPIPath as t };
742
+ 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 };