@orpc/openapi 1.4.5 → 1.5.1

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/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OpenAPI, AnyContractProcedure } from '@orpc/contract';
2
2
  export { OpenAPI } from '@orpc/contract';
3
- export { d as CompositeSchemaConverter, C as ConditionalSchemaConverter, b as OpenAPIGenerator, a as OpenAPIGeneratorGenerateOptions, O as OpenAPIGeneratorOptions, S as SchemaConvertOptions, c as SchemaConverter } from './shared/openapi.qZLdpE0a.mjs';
3
+ export { e as CompositeSchemaConverter, C as ConditionalSchemaConverter, b as OpenAPIGenerator, a as OpenAPIGeneratorGenerateOptions, O as OpenAPIGeneratorOptions, c as SchemaConvertOptions, d as SchemaConverter, S as SchemaConverterComponent } from './shared/openapi.CbIlrReM.mjs';
4
4
  import { HTTPPath, HTTPMethod } from '@orpc/client';
5
5
  import { JSONSchema } from 'json-schema-typed/draft-2020-12';
6
6
  export { JSONSchema, ContentEncoding as JSONSchemaContentEncoding, Format as JSONSchemaFormat, TypeName as JSONSchemaTypeName } from 'json-schema-typed/draft-2020-12';
@@ -65,6 +65,7 @@ declare function checkParamsSchema(schema: ObjectSchema, params: string[]): bool
65
65
  * @internal
66
66
  */
67
67
  declare function toOpenAPISchema(schema: JSONSchema): OpenAPI.SchemaObject & object;
68
+ declare function resolveOpenAPIJsonSchemaRef(doc: OpenAPI.Document, schema: JSONSchema): JSONSchema;
68
69
 
69
70
  declare function createJsonifiedRouterClient<T extends AnyRouter, TClientContext extends ClientContext>(router: Lazyable<T | undefined>, ...rest: MaybeOptionalOptions<CreateProcedureClientOptions<InferRouterInitialContext<T>, Schema<unknown, unknown>, ErrorMap, Meta, TClientContext>>): JsonifiedClient<RouterClient<T, TClientContext>>;
70
71
 
@@ -105,5 +106,5 @@ declare const oo: {
105
106
  spec: typeof customOpenAPIOperation;
106
107
  };
107
108
 
108
- export { LOGIC_KEYWORDS, applyCustomOpenAPIOperation, applySchemaOptionality, checkParamsSchema, createJsonifiedRouterClient, customOpenAPIOperation, expandArrayableSchema, expandUnionSchema, filterSchemaBranches, getCustomOpenAPIOperation, isAnySchema, isFileSchema, isObjectSchema, isPrimitiveSchema, oo, separateObjectSchema, toOpenAPIContent, toOpenAPIEventIteratorContent, toOpenAPIMethod, toOpenAPIParameters, toOpenAPIPath, toOpenAPISchema };
109
+ export { LOGIC_KEYWORDS, applyCustomOpenAPIOperation, applySchemaOptionality, checkParamsSchema, createJsonifiedRouterClient, customOpenAPIOperation, expandArrayableSchema, expandUnionSchema, filterSchemaBranches, getCustomOpenAPIOperation, isAnySchema, isFileSchema, isObjectSchema, isPrimitiveSchema, oo, resolveOpenAPIJsonSchemaRef, separateObjectSchema, toOpenAPIContent, toOpenAPIEventIteratorContent, toOpenAPIMethod, toOpenAPIParameters, toOpenAPIPath, toOpenAPISchema };
109
110
  export type { FileSchema, ObjectSchema, OverrideOperationValue };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { OpenAPI, AnyContractProcedure } from '@orpc/contract';
2
2
  export { OpenAPI } from '@orpc/contract';
3
- export { d as CompositeSchemaConverter, C as ConditionalSchemaConverter, b as OpenAPIGenerator, a as OpenAPIGeneratorGenerateOptions, O as OpenAPIGeneratorOptions, S as SchemaConvertOptions, c as SchemaConverter } from './shared/openapi.qZLdpE0a.js';
3
+ export { e as CompositeSchemaConverter, C as ConditionalSchemaConverter, b as OpenAPIGenerator, a as OpenAPIGeneratorGenerateOptions, O as OpenAPIGeneratorOptions, c as SchemaConvertOptions, d as SchemaConverter, S as SchemaConverterComponent } from './shared/openapi.CbIlrReM.js';
4
4
  import { HTTPPath, HTTPMethod } from '@orpc/client';
5
5
  import { JSONSchema } from 'json-schema-typed/draft-2020-12';
6
6
  export { JSONSchema, ContentEncoding as JSONSchemaContentEncoding, Format as JSONSchemaFormat, TypeName as JSONSchemaTypeName } from 'json-schema-typed/draft-2020-12';
@@ -65,6 +65,7 @@ declare function checkParamsSchema(schema: ObjectSchema, params: string[]): bool
65
65
  * @internal
66
66
  */
67
67
  declare function toOpenAPISchema(schema: JSONSchema): OpenAPI.SchemaObject & object;
68
+ declare function resolveOpenAPIJsonSchemaRef(doc: OpenAPI.Document, schema: JSONSchema): JSONSchema;
68
69
 
69
70
  declare function createJsonifiedRouterClient<T extends AnyRouter, TClientContext extends ClientContext>(router: Lazyable<T | undefined>, ...rest: MaybeOptionalOptions<CreateProcedureClientOptions<InferRouterInitialContext<T>, Schema<unknown, unknown>, ErrorMap, Meta, TClientContext>>): JsonifiedClient<RouterClient<T, TClientContext>>;
70
71
 
@@ -105,5 +106,5 @@ declare const oo: {
105
106
  spec: typeof customOpenAPIOperation;
106
107
  };
107
108
 
108
- export { LOGIC_KEYWORDS, applyCustomOpenAPIOperation, applySchemaOptionality, checkParamsSchema, createJsonifiedRouterClient, customOpenAPIOperation, expandArrayableSchema, expandUnionSchema, filterSchemaBranches, getCustomOpenAPIOperation, isAnySchema, isFileSchema, isObjectSchema, isPrimitiveSchema, oo, separateObjectSchema, toOpenAPIContent, toOpenAPIEventIteratorContent, toOpenAPIMethod, toOpenAPIParameters, toOpenAPIPath, toOpenAPISchema };
109
+ export { LOGIC_KEYWORDS, applyCustomOpenAPIOperation, applySchemaOptionality, checkParamsSchema, createJsonifiedRouterClient, customOpenAPIOperation, expandArrayableSchema, expandUnionSchema, filterSchemaBranches, getCustomOpenAPIOperation, isAnySchema, isFileSchema, isObjectSchema, isPrimitiveSchema, oo, resolveOpenAPIJsonSchemaRef, separateObjectSchema, toOpenAPIContent, toOpenAPIEventIteratorContent, toOpenAPIMethod, toOpenAPIParameters, toOpenAPIPath, toOpenAPISchema };
109
110
  export type { FileSchema, ObjectSchema, OverrideOperationValue };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { c as customOpenAPIOperation } from './shared/openapi.DaYgbD_w.mjs';
2
- export { C as CompositeSchemaConverter, L as LOGIC_KEYWORDS, O as OpenAPIGenerator, a as applyCustomOpenAPIOperation, n as applySchemaOptionality, h as checkParamsSchema, p as expandArrayableSchema, o as expandUnionSchema, m as filterSchemaBranches, g as getCustomOpenAPIOperation, l as isAnySchema, j as isFileSchema, k as isObjectSchema, q as isPrimitiveSchema, s as separateObjectSchema, d as toOpenAPIContent, e as toOpenAPIEventIteratorContent, b as toOpenAPIMethod, f as toOpenAPIParameters, t as toOpenAPIPath, i as toOpenAPISchema } from './shared/openapi.DaYgbD_w.mjs';
1
+ import { c as customOpenAPIOperation } from './shared/openapi.C_3bk7bB.mjs';
2
+ export { C as CompositeSchemaConverter, L as LOGIC_KEYWORDS, O as OpenAPIGenerator, a as applyCustomOpenAPIOperation, n as applySchemaOptionality, h as checkParamsSchema, p as expandArrayableSchema, o as expandUnionSchema, m as filterSchemaBranches, g as getCustomOpenAPIOperation, l as isAnySchema, j as isFileSchema, k as isObjectSchema, q as isPrimitiveSchema, r as resolveOpenAPIJsonSchemaRef, s as separateObjectSchema, d as toOpenAPIContent, e as toOpenAPIEventIteratorContent, b as toOpenAPIMethod, f as toOpenAPIParameters, t as toOpenAPIPath, i as toOpenAPISchema } from './shared/openapi.C_3bk7bB.mjs';
3
3
  import { createORPCErrorFromJson } from '@orpc/client';
4
4
  import { StandardOpenAPISerializer, StandardOpenAPIJsonSerializer, StandardBracketNotationSerializer } from '@orpc/openapi-client/standard';
5
5
  import { ORPCError, createRouterClient } from '@orpc/server';
@@ -2,7 +2,7 @@ import { OpenAPI } from '@orpc/contract';
2
2
  import { Context, HTTPPath, Router } from '@orpc/server';
3
3
  import { StandardHandlerInterceptorOptions, StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
4
4
  import { Value, Promisable } from '@orpc/shared';
5
- import { O as OpenAPIGeneratorOptions, a as OpenAPIGeneratorGenerateOptions } from '../shared/openapi.qZLdpE0a.mjs';
5
+ import { O as OpenAPIGeneratorOptions, a as OpenAPIGeneratorGenerateOptions } from '../shared/openapi.CbIlrReM.mjs';
6
6
  import '@orpc/openapi-client/standard';
7
7
  import 'json-schema-typed/draft-2020-12';
8
8
 
@@ -2,7 +2,7 @@ import { OpenAPI } from '@orpc/contract';
2
2
  import { Context, HTTPPath, Router } from '@orpc/server';
3
3
  import { StandardHandlerInterceptorOptions, StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
4
4
  import { Value, Promisable } from '@orpc/shared';
5
- import { O as OpenAPIGeneratorOptions, a as OpenAPIGeneratorGenerateOptions } from '../shared/openapi.qZLdpE0a.js';
5
+ import { O as OpenAPIGeneratorOptions, a as OpenAPIGeneratorGenerateOptions } from '../shared/openapi.CbIlrReM.js';
6
6
  import '@orpc/openapi-client/standard';
7
7
  import 'json-schema-typed/draft-2020-12';
8
8
 
@@ -1,5 +1,5 @@
1
1
  import { stringifyJSON, once, value } from '@orpc/shared';
2
- import { O as OpenAPIGenerator } from '../shared/openapi.DaYgbD_w.mjs';
2
+ import { O as OpenAPIGenerator } from '../shared/openapi.C_3bk7bB.mjs';
3
3
  import '@orpc/client';
4
4
  import '@orpc/client/standard';
5
5
  import '@orpc/contract';
@@ -345,6 +345,15 @@ function checkParamsSchema(schema, params) {
345
345
  function toOpenAPISchema(schema) {
346
346
  return schema === true ? {} : schema === false ? { not: {} } : schema;
347
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
+ }
348
357
 
349
358
  class CompositeSchemaConverter {
350
359
  converters;
@@ -381,8 +390,10 @@ class OpenAPIGenerator {
381
390
  ...clone(options),
382
391
  info: options.info ?? { title: "API Reference", version: "0.0.0" },
383
392
  openapi: "3.1.1",
384
- exclude: void 0
393
+ exclude: void 0,
394
+ commonSchemas: void 0
385
395
  };
396
+ const baseSchemaConvertOptions = await this.#resolveCommonSchemas(doc, options.commonSchemas);
386
397
  const contracts = [];
387
398
  await resolveContractProcedures({ path: [], router }, ({ contract, path }) => {
388
399
  if (!exclude(contract, path)) {
@@ -407,9 +418,9 @@ class OpenAPIGenerator {
407
418
  deprecated: def.route.deprecated,
408
419
  tags: def.route.tags?.map((tag) => tag)
409
420
  };
410
- await this.#request(operationObjectRef, def);
411
- await this.#successResponse(operationObjectRef, def);
412
- await this.#errorResponse(operationObjectRef, def);
421
+ await this.#request(doc, operationObjectRef, def, baseSchemaConvertOptions);
422
+ await this.#successResponse(doc, operationObjectRef, def, baseSchemaConvertOptions);
423
+ await this.#errorResponse(operationObjectRef, def, baseSchemaConvertOptions);
413
424
  }
414
425
  doc.paths ??= {};
415
426
  doc.paths[httpPath] ??= {};
@@ -433,22 +444,73 @@ ${errors.join("\n\n")}`
433
444
  }
434
445
  return this.serializer.serialize(doc)[0];
435
446
  }
436
- async #request(ref, def) {
447
+ async #resolveCommonSchemas(doc, commonSchemas) {
448
+ const baseOptions = {};
449
+ if (commonSchemas) {
450
+ baseOptions.components = [];
451
+ for (const key in commonSchemas) {
452
+ const { schema, strategy = "input" } = commonSchemas[key];
453
+ const [required, json] = await this.converter.convert(schema, { strategy });
454
+ const allowedStrategies = [strategy];
455
+ if (strategy === "input") {
456
+ const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
457
+ if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
458
+ allowedStrategies.push("output");
459
+ }
460
+ } else if (strategy === "output") {
461
+ const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
462
+ if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
463
+ allowedStrategies.push("input");
464
+ }
465
+ }
466
+ baseOptions.components.push({
467
+ schema,
468
+ required,
469
+ ref: `#/components/schemas/${key}`,
470
+ allowedStrategies
471
+ });
472
+ }
473
+ doc.components ??= {};
474
+ doc.components.schemas ??= {};
475
+ for (const key in commonSchemas) {
476
+ const { schema, strategy = "input" } = commonSchemas[key];
477
+ const [, json] = await this.converter.convert(
478
+ schema,
479
+ {
480
+ ...baseOptions,
481
+ strategy,
482
+ minStructureDepthForRef: 1
483
+ // not allow use $ref for root schemas
484
+ }
485
+ );
486
+ doc.components.schemas[key] = toOpenAPISchema(json);
487
+ }
488
+ }
489
+ return baseOptions;
490
+ }
491
+ async #request(doc, ref, def, baseSchemaConvertOptions) {
437
492
  const method = fallbackContractConfig("defaultMethod", def.route.method);
438
493
  const details = getEventIteratorSchemaDetails(def.inputSchema);
439
494
  if (details) {
440
495
  ref.requestBody = {
441
496
  required: true,
442
497
  content: toOpenAPIEventIteratorContent(
443
- await this.converter.convert(details.yields, { strategy: "input" }),
444
- await this.converter.convert(details.returns, { strategy: "input" })
498
+ await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
499
+ await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
445
500
  )
446
501
  };
447
502
  return;
448
503
  }
449
504
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
450
505
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
451
- let [required, schema] = await this.converter.convert(def.inputSchema, { strategy: "input" });
506
+ let [required, schema] = await this.converter.convert(
507
+ def.inputSchema,
508
+ {
509
+ ...baseSchemaConvertOptions,
510
+ strategy: "input",
511
+ minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
512
+ }
513
+ );
452
514
  if (isAnySchema(schema) && !dynamicParams?.length) {
453
515
  return;
454
516
  }
@@ -491,7 +553,8 @@ ${errors.join("\n\n")}`
491
553
  if (!isObjectSchema(schema)) {
492
554
  throw error;
493
555
  }
494
- if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
556
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
557
+ if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
495
558
  throw new OpenAPIGeneratorError(
496
559
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
497
560
  );
@@ -499,12 +562,13 @@ ${errors.join("\n\n")}`
499
562
  for (const from of ["params", "query", "headers"]) {
500
563
  const fromSchema = schema.properties?.[from];
501
564
  if (fromSchema !== void 0) {
502
- if (!isObjectSchema(fromSchema)) {
565
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
566
+ if (!isObjectSchema(resolvedSchema)) {
503
567
  throw error;
504
568
  }
505
569
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
506
570
  ref.parameters ??= [];
507
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
571
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
508
572
  }
509
573
  }
510
574
  if (schema.properties?.body !== void 0) {
@@ -514,7 +578,7 @@ ${errors.join("\n\n")}`
514
578
  };
515
579
  }
516
580
  }
517
- async #successResponse(ref, def) {
581
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
518
582
  const outputSchema = def.outputSchema;
519
583
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
520
584
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -525,13 +589,20 @@ ${errors.join("\n\n")}`
525
589
  ref.responses[status] = {
526
590
  description,
527
591
  content: toOpenAPIEventIteratorContent(
528
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
529
- await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
592
+ await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
593
+ await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
530
594
  )
531
595
  };
532
596
  return;
533
597
  }
534
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
598
+ const [required, json] = await this.converter.convert(
599
+ outputSchema,
600
+ {
601
+ ...baseSchemaConvertOptions,
602
+ strategy: "output",
603
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
604
+ }
605
+ );
535
606
  if (outputStructure === "compact") {
536
607
  ref.responses ??= {};
537
608
  ref.responses[status] = {
@@ -558,11 +629,12 @@ ${errors.join("\n\n")}`
558
629
  let schemaStatus;
559
630
  let schemaDescription;
560
631
  if (item.properties?.status !== void 0) {
561
- 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)) {
632
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
633
+ if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
562
634
  throw error;
563
635
  }
564
- schemaStatus = item.properties.status.const;
565
- schemaDescription = item.properties.status.description;
636
+ schemaStatus = statusSchema.const;
637
+ schemaDescription = statusSchema.description;
566
638
  }
567
639
  const itemStatus = schemaStatus ?? status;
568
640
  const itemDescription = schemaDescription ?? description;
@@ -578,16 +650,17 @@ ${errors.join("\n\n")}`
578
650
  description: itemDescription
579
651
  };
580
652
  if (item.properties?.headers !== void 0) {
581
- if (!isObjectSchema(item.properties.headers)) {
653
+ const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
654
+ if (!isObjectSchema(headersSchema)) {
582
655
  throw error;
583
656
  }
584
- for (const key in item.properties.headers.properties) {
585
- const headerSchema = item.properties.headers.properties[key];
657
+ for (const key in headersSchema.properties) {
658
+ const headerSchema = headersSchema.properties[key];
586
659
  if (headerSchema !== void 0) {
587
660
  ref.responses[itemStatus].headers ??= {};
588
661
  ref.responses[itemStatus].headers[key] = {
589
662
  schema: toOpenAPISchema(headerSchema),
590
- required: item.properties.headers.required?.includes(key)
663
+ required: item.required?.includes("headers") && headersSchema.required?.includes(key)
591
664
  };
592
665
  }
593
666
  }
@@ -599,7 +672,7 @@ ${errors.join("\n\n")}`
599
672
  }
600
673
  }
601
674
  }
602
- async #errorResponse(ref, def) {
675
+ async #errorResponse(ref, def, baseSchemaConvertOptions) {
603
676
  const errorMap = def.errorMap;
604
677
  const errors = {};
605
678
  for (const code in errorMap) {
@@ -609,7 +682,7 @@ ${errors.join("\n\n")}`
609
682
  }
610
683
  const status = fallbackORPCErrorStatus(code, config.status);
611
684
  const message = fallbackORPCErrorMessage(code, config.message);
612
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
685
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
613
686
  errors[status] ??= [];
614
687
  errors[status].push({
615
688
  type: "object",
@@ -649,4 +722,4 @@ ${errors.join("\n\n")}`
649
722
  }
650
723
  }
651
724
 
652
- 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 };
725
+ 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 };
@@ -4,8 +4,26 @@ import { AnyProcedure, AnyRouter } from '@orpc/server';
4
4
  import { Promisable } from '@orpc/shared';
5
5
  import { JSONSchema } from 'json-schema-typed/draft-2020-12';
6
6
 
7
+ interface SchemaConverterComponent {
8
+ allowedStrategies: SchemaConvertOptions['strategy'][];
9
+ schema: AnySchema;
10
+ required: boolean;
11
+ ref: string;
12
+ }
7
13
  interface SchemaConvertOptions {
8
14
  strategy: 'input' | 'output';
15
+ /**
16
+ * Common components should use `$ref` to represent themselves if matched.
17
+ */
18
+ components?: 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;
9
27
  }
10
28
  interface SchemaConverter {
11
29
  convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
@@ -29,6 +47,34 @@ interface OpenAPIGeneratorGenerateOptions extends Partial<Omit<OpenAPI.Document,
29
47
  * @default () => false
30
48
  */
31
49
  exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
50
+ /**
51
+ * Common schemas to be used for $ref resolution.
52
+ */
53
+ commonSchemas?: Record<string, {
54
+ /**
55
+ * Determines which schema definition to use when input and output schemas differ.
56
+ * This is needed because some schemas transform data differently between input and output,
57
+ * making it impossible to use a single $ref for both cases.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // This schema transforms a string input into a number output
62
+ * const Schema = z.string()
63
+ * .transform(v => Number(v))
64
+ * .pipe(z.number())
65
+ *
66
+ * // Input schema: { type: 'string' }
67
+ * // Output schema: { type: 'number' }
68
+ * ```
69
+ *
70
+ * When schemas differ between input and output, you must explicitly choose
71
+ * which version to use for the OpenAPI specification.
72
+ *
73
+ * @default 'input' - Uses the input schema definition by default
74
+ */
75
+ strategy?: SchemaConvertOptions['strategy'];
76
+ schema: AnySchema;
77
+ }>;
32
78
  }
33
79
  /**
34
80
  * The generator that converts oRPC routers/contracts to OpenAPI specifications.
@@ -48,5 +94,5 @@ declare class OpenAPIGenerator {
48
94
  generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
49
95
  }
50
96
 
51
- export { OpenAPIGenerator as b, CompositeSchemaConverter as d };
52
- export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConvertOptions as S, OpenAPIGeneratorGenerateOptions as a, SchemaConverter as c };
97
+ export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
98
+ export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };
@@ -4,8 +4,26 @@ import { AnyProcedure, AnyRouter } from '@orpc/server';
4
4
  import { Promisable } from '@orpc/shared';
5
5
  import { JSONSchema } from 'json-schema-typed/draft-2020-12';
6
6
 
7
+ interface SchemaConverterComponent {
8
+ allowedStrategies: SchemaConvertOptions['strategy'][];
9
+ schema: AnySchema;
10
+ required: boolean;
11
+ ref: string;
12
+ }
7
13
  interface SchemaConvertOptions {
8
14
  strategy: 'input' | 'output';
15
+ /**
16
+ * Common components should use `$ref` to represent themselves if matched.
17
+ */
18
+ components?: 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;
9
27
  }
10
28
  interface SchemaConverter {
11
29
  convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
@@ -29,6 +47,34 @@ interface OpenAPIGeneratorGenerateOptions extends Partial<Omit<OpenAPI.Document,
29
47
  * @default () => false
30
48
  */
31
49
  exclude?: (procedure: AnyProcedure | AnyContractProcedure, path: readonly string[]) => boolean;
50
+ /**
51
+ * Common schemas to be used for $ref resolution.
52
+ */
53
+ commonSchemas?: Record<string, {
54
+ /**
55
+ * Determines which schema definition to use when input and output schemas differ.
56
+ * This is needed because some schemas transform data differently between input and output,
57
+ * making it impossible to use a single $ref for both cases.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * // This schema transforms a string input into a number output
62
+ * const Schema = z.string()
63
+ * .transform(v => Number(v))
64
+ * .pipe(z.number())
65
+ *
66
+ * // Input schema: { type: 'string' }
67
+ * // Output schema: { type: 'number' }
68
+ * ```
69
+ *
70
+ * When schemas differ between input and output, you must explicitly choose
71
+ * which version to use for the OpenAPI specification.
72
+ *
73
+ * @default 'input' - Uses the input schema definition by default
74
+ */
75
+ strategy?: SchemaConvertOptions['strategy'];
76
+ schema: AnySchema;
77
+ }>;
32
78
  }
33
79
  /**
34
80
  * The generator that converts oRPC routers/contracts to OpenAPI specifications.
@@ -48,5 +94,5 @@ declare class OpenAPIGenerator {
48
94
  generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
49
95
  }
50
96
 
51
- export { OpenAPIGenerator as b, CompositeSchemaConverter as d };
52
- export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConvertOptions as S, OpenAPIGeneratorGenerateOptions as a, SchemaConverter as c };
97
+ export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
98
+ export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/openapi",
3
3
  "type": "module",
4
- "version": "1.4.5",
4
+ "version": "1.5.1",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -51,15 +51,15 @@
51
51
  "dependencies": {
52
52
  "json-schema-typed": "^8.0.1",
53
53
  "rou3": "^0.6.0",
54
- "@orpc/client": "1.4.5",
55
- "@orpc/contract": "1.4.5",
56
- "@orpc/shared": "1.4.5",
57
- "@orpc/openapi-client": "1.4.5",
58
- "@orpc/standard-server": "1.4.5",
59
- "@orpc/server": "1.4.5"
54
+ "@orpc/contract": "1.5.1",
55
+ "@orpc/client": "1.5.1",
56
+ "@orpc/openapi-client": "1.5.1",
57
+ "@orpc/shared": "1.5.1",
58
+ "@orpc/standard-server": "1.5.1",
59
+ "@orpc/server": "1.5.1"
60
60
  },
61
61
  "devDependencies": {
62
- "zod": "^3.25.49"
62
+ "zod": "^3.25.63"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "unbuild",