@orpc/openapi 0.0.0-next.f4ed9ab → 0.0.0-next.f50512c

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.
@@ -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, findDeepMatches, toArray, clone, stringifyJSON } 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) {
@@ -196,6 +196,45 @@ function expandUnionSchema(schema) {
196
196
  }
197
197
  return [schema];
198
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
+ }
199
238
 
200
239
  function toOpenAPIPath(path) {
201
240
  return standardizeHTTPPath(path).replace(/\/\{\+([^}]+)\}/g, "/{$1}");
@@ -268,13 +307,26 @@ function toOpenAPIParameters(schema, parameterIn) {
268
307
  const parameters = [];
269
308
  for (const key in schema.properties) {
270
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
+ }
271
321
  parameters.push({
272
322
  name: key,
273
323
  in: parameterIn,
274
324
  required: schema.required?.includes(key),
275
- style: parameterIn === "query" ? "deepObject" : void 0,
276
- explode: parameterIn === "query" ? true : void 0,
277
- 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
278
330
  });
279
331
  }
280
332
  return parameters;
@@ -293,6 +345,15 @@ function checkParamsSchema(schema, params) {
293
345
  function toOpenAPISchema(schema) {
294
346
  return schema === true ? {} : schema === false ? { not: {} } : schema;
295
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
+ }
296
357
 
297
358
  class CompositeSchemaConverter {
298
359
  converters;
@@ -324,18 +385,24 @@ class OpenAPIGenerator {
324
385
  * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
325
386
  */
326
387
  async generate(router, options = {}) {
327
- const exclude = options.exclude ?? (() => false);
388
+ const filter = options.filter ?? (({ contract, path }) => {
389
+ return !(options.exclude?.(contract, path) ?? false);
390
+ });
328
391
  const doc = {
329
392
  ...clone(options),
330
393
  info: options.info ?? { title: "API Reference", version: "0.0.0" },
331
394
  openapi: "3.1.1",
332
- exclude: void 0
395
+ exclude: void 0,
396
+ filter: void 0,
397
+ commonSchemas: void 0
333
398
  };
399
+ const { baseSchemaConvertOptions, undefinedErrorJsonSchema } = await this.#resolveCommonSchemas(doc, options.commonSchemas);
334
400
  const contracts = [];
335
- await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
336
- if (!exclude(contract, path)) {
337
- contracts.push({ contract, path });
401
+ await resolveContractProcedures({ path: [], router }, (traverseOptions) => {
402
+ if (!value(filter, traverseOptions)) {
403
+ return;
338
404
  }
405
+ contracts.push(traverseOptions);
339
406
  });
340
407
  const errors = [];
341
408
  for (const { contract, path } of contracts) {
@@ -344,16 +411,21 @@ class OpenAPIGenerator {
344
411
  const def = contract["~orpc"];
345
412
  const method = toOpenAPIMethod(fallbackContractConfig("defaultMethod", def.route.method));
346
413
  const httpPath = toOpenAPIPath(def.route.path ?? toHttpPath(path));
347
- const operationObjectRef = {
348
- operationId,
349
- summary: def.route.summary,
350
- description: def.route.description,
351
- deprecated: def.route.deprecated,
352
- tags: def.route.tags?.map((tag) => tag)
353
- };
354
- await this.#request(operationObjectRef, def);
355
- await this.#successResponse(operationObjectRef, def);
356
- 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
+ }
357
429
  doc.paths ??= {};
358
430
  doc.paths[httpPath] ??= {};
359
431
  doc.paths[httpPath][method] = applyCustomOpenAPIOperation(operationObjectRef, contract);
@@ -376,22 +448,96 @@ ${errors.join("\n\n")}`
376
448
  }
377
449
  return this.serializer.serialize(doc)[0];
378
450
  }
379
- 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) {
380
519
  const method = fallbackContractConfig("defaultMethod", def.route.method);
381
520
  const details = getEventIteratorSchemaDetails(def.inputSchema);
382
521
  if (details) {
383
522
  ref.requestBody = {
384
523
  required: true,
385
524
  content: toOpenAPIEventIteratorContent(
386
- await this.converter.convert(details.yields, { strategy: "input" }),
387
- 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" })
388
527
  )
389
528
  };
390
529
  return;
391
530
  }
392
531
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
393
532
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
394
- 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
+ );
395
541
  if (isAnySchema(schema) && !dynamicParams?.length) {
396
542
  return;
397
543
  }
@@ -434,7 +580,8 @@ ${errors.join("\n\n")}`
434
580
  if (!isObjectSchema(schema)) {
435
581
  throw error;
436
582
  }
437
- 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))) {
438
585
  throw new OpenAPIGeneratorError(
439
586
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
440
587
  );
@@ -442,12 +589,13 @@ ${errors.join("\n\n")}`
442
589
  for (const from of ["params", "query", "headers"]) {
443
590
  const fromSchema = schema.properties?.[from];
444
591
  if (fromSchema !== void 0) {
445
- if (!isObjectSchema(fromSchema)) {
592
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
593
+ if (!isObjectSchema(resolvedSchema)) {
446
594
  throw error;
447
595
  }
448
596
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
449
597
  ref.parameters ??= [];
450
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
598
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
451
599
  }
452
600
  }
453
601
  if (schema.properties?.body !== void 0) {
@@ -457,7 +605,7 @@ ${errors.join("\n\n")}`
457
605
  };
458
606
  }
459
607
  }
460
- async #successResponse(ref, def) {
608
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
461
609
  const outputSchema = def.outputSchema;
462
610
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
463
611
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -468,13 +616,20 @@ ${errors.join("\n\n")}`
468
616
  ref.responses[status] = {
469
617
  description,
470
618
  content: toOpenAPIEventIteratorContent(
471
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
472
- 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" })
473
621
  )
474
622
  };
475
623
  return;
476
624
  }
477
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
625
+ const [required, json] = await this.converter.convert(
626
+ outputSchema,
627
+ {
628
+ ...baseSchemaConvertOptions,
629
+ strategy: "output",
630
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
631
+ }
632
+ );
478
633
  if (outputStructure === "compact") {
479
634
  ref.responses ??= {};
480
635
  ref.responses[status] = {
@@ -501,11 +656,12 @@ ${errors.join("\n\n")}`
501
656
  let schemaStatus;
502
657
  let schemaDescription;
503
658
  if (item.properties?.status !== void 0) {
504
- 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)) {
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)) {
505
661
  throw error;
506
662
  }
507
- schemaStatus = item.properties.status.const;
508
- schemaDescription = item.properties.status.description;
663
+ schemaStatus = statusSchema.const;
664
+ schemaDescription = statusSchema.description;
509
665
  }
510
666
  const itemStatus = schemaStatus ?? status;
511
667
  const itemDescription = schemaDescription ?? description;
@@ -521,16 +677,17 @@ ${errors.join("\n\n")}`
521
677
  description: itemDescription
522
678
  };
523
679
  if (item.properties?.headers !== void 0) {
524
- if (!isObjectSchema(item.properties.headers)) {
680
+ const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
681
+ if (!isObjectSchema(headersSchema)) {
525
682
  throw error;
526
683
  }
527
- for (const key in item.properties.headers.properties) {
528
- const headerSchema = item.properties.headers.properties[key];
684
+ for (const key in headersSchema.properties) {
685
+ const headerSchema = headersSchema.properties[key];
529
686
  if (headerSchema !== void 0) {
530
687
  ref.responses[itemStatus].headers ??= {};
531
688
  ref.responses[itemStatus].headers[key] = {
532
689
  schema: toOpenAPISchema(headerSchema),
533
- required: item.properties.headers.required?.includes(key)
690
+ required: item.required?.includes("headers") && headersSchema.required?.includes(key)
534
691
  };
535
692
  }
536
693
  }
@@ -542,7 +699,7 @@ ${errors.join("\n\n")}`
542
699
  }
543
700
  }
544
701
  }
545
- async #errorResponse(ref, def) {
702
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
546
703
  const errorMap = def.errorMap;
547
704
  const errors = {};
548
705
  for (const code in errorMap) {
@@ -552,7 +709,7 @@ ${errors.join("\n\n")}`
552
709
  }
553
710
  const status = fallbackORPCErrorStatus(code, config.status);
554
711
  const message = fallbackORPCErrorMessage(code, config.message);
555
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
712
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
556
713
  errors[status] ??= [];
557
714
  errors[status].push({
558
715
  type: "object",
@@ -574,17 +731,7 @@ ${errors.join("\n\n")}`
574
731
  content: toOpenAPIContent({
575
732
  oneOf: [
576
733
  ...schemas,
577
- {
578
- type: "object",
579
- properties: {
580
- defined: { const: false },
581
- code: { type: "string" },
582
- status: { type: "number" },
583
- message: { type: "string" },
584
- data: {}
585
- },
586
- required: ["defined", "code", "status", "message"]
587
- }
734
+ undefinedErrorSchema
588
735
  ]
589
736
  })
590
737
  };
@@ -592,4 +739,4 @@ ${errors.join("\n\n")}`
592
739
  }
593
740
  }
594
741
 
595
- 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, 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 };
@@ -0,0 +1,108 @@
1
+ import { AnySchema, OpenAPI, AnyContractProcedure, AnyContractRouter } from '@orpc/contract';
2
+ import { StandardOpenAPIJsonSerializerOptions } from '@orpc/openapi-client/standard';
3
+ import { AnyProcedure, TraverseContractProcedureCallbackOptions, AnyRouter } from '@orpc/server';
4
+ import { Promisable, Value } from '@orpc/shared';
5
+ import { JSONSchema } from 'json-schema-typed/draft-2020-12';
6
+
7
+ interface SchemaConverterComponent {
8
+ allowedStrategies: readonly SchemaConvertOptions['strategy'][];
9
+ schema: AnySchema;
10
+ required: boolean;
11
+ ref: string;
12
+ }
13
+ interface SchemaConvertOptions {
14
+ strategy: 'input' | 'output';
15
+ /**
16
+ * Common components should use `$ref` to represent themselves if matched.
17
+ */
18
+ components?: readonly SchemaConverterComponent[];
19
+ /**
20
+ * Minimum schema structure depth required before using `$ref` for components.
21
+ *
22
+ * For example, if set to 2, `$ref` will only be used for schemas nested at depth 2 or greater.
23
+ *
24
+ * @default 0 - No depth limit;
25
+ */
26
+ minStructureDepthForRef?: number;
27
+ }
28
+ interface SchemaConverter {
29
+ convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
30
+ }
31
+ interface ConditionalSchemaConverter extends SchemaConverter {
32
+ condition(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<boolean>;
33
+ }
34
+ declare class CompositeSchemaConverter implements SchemaConverter {
35
+ private readonly converters;
36
+ constructor(converters: readonly ConditionalSchemaConverter[]);
37
+ convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promise<[required: boolean, jsonSchema: JSONSchema]>;
38
+ }
39
+
40
+ interface OpenAPIGeneratorOptions extends StandardOpenAPIJsonSerializerOptions {
41
+ schemaConverters?: ConditionalSchemaConverter[];
42
+ }
43
+ interface OpenAPIGeneratorGenerateOptions extends Partial<Omit<OpenAPI.Document, 'openapi'>> {
44
+ /**
45
+ * Exclude procedures from the OpenAPI specification.
46
+ *
47
+ * @deprecated Use `filter` option instead.
48
+ * @default () => false
49
+ */
50
+ exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
51
+ /**
52
+ * Filter procedures. Return `false` to exclude a procedure from the OpenAPI specification.
53
+ *
54
+ * @default true
55
+ */
56
+ filter?: Value<boolean, [options: TraverseContractProcedureCallbackOptions]>;
57
+ /**
58
+ * Common schemas to be used for $ref resolution.
59
+ */
60
+ commonSchemas?: Record<string, {
61
+ /**
62
+ * Determines which schema definition to use when input and output schemas differ.
63
+ * This is needed because some schemas transform data differently between input and output,
64
+ * making it impossible to use a single $ref for both cases.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // This schema transforms a string input into a number output
69
+ * const Schema = z.string()
70
+ * .transform(v => Number(v))
71
+ * .pipe(z.number())
72
+ *
73
+ * // Input schema: { type: 'string' }
74
+ * // Output schema: { type: 'number' }
75
+ * ```
76
+ *
77
+ * When schemas differ between input and output, you must explicitly choose
78
+ * which version to use for the OpenAPI specification.
79
+ *
80
+ * @default 'input' - Uses the input schema definition by default
81
+ */
82
+ strategy?: SchemaConvertOptions['strategy'];
83
+ schema: AnySchema;
84
+ } | {
85
+ error: 'UndefinedError';
86
+ schema?: never;
87
+ }>;
88
+ }
89
+ /**
90
+ * The generator that converts oRPC routers/contracts to OpenAPI specifications.
91
+ *
92
+ * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
93
+ */
94
+ declare class OpenAPIGenerator {
95
+ #private;
96
+ private readonly serializer;
97
+ private readonly converter;
98
+ constructor(options?: OpenAPIGeneratorOptions);
99
+ /**
100
+ * Generates OpenAPI specifications from oRPC routers/contracts.
101
+ *
102
+ * @see {@link https://orpc.unnoq.com/docs/openapi/openapi-specification OpenAPI Specification Docs}
103
+ */
104
+ generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
105
+ }
106
+
107
+ export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
108
+ export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };