@orpc/openapi 1.5.0 → 1.5.2

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.B3hexduL.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.B3hexduL.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.DrrBsJ0w.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.DrrBsJ0w.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.B3hexduL.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.B3hexduL.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.DrrBsJ0w.mjs';
3
3
  import '@orpc/client';
4
4
  import '@orpc/client/standard';
5
5
  import '@orpc/contract';
@@ -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: readonly 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?: 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;
9
27
  }
10
28
  interface SchemaConverter {
11
29
  convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
@@ -29,6 +47,37 @@ 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
+ } | {
78
+ error: 'UndefinedError';
79
+ schema?: never;
80
+ }>;
32
81
  }
33
82
  /**
34
83
  * The generator that converts oRPC routers/contracts to OpenAPI specifications.
@@ -48,5 +97,5 @@ declare class OpenAPIGenerator {
48
97
  generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
49
98
  }
50
99
 
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 };
100
+ export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
101
+ 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: readonly 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?: 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;
9
27
  }
10
28
  interface SchemaConverter {
11
29
  convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: JSONSchema]>;
@@ -29,6 +47,37 @@ 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
+ } | {
78
+ error: 'UndefinedError';
79
+ schema?: never;
80
+ }>;
32
81
  }
33
82
  /**
34
83
  * The generator that converts oRPC routers/contracts to OpenAPI specifications.
@@ -48,5 +97,5 @@ declare class OpenAPIGenerator {
48
97
  generate(router: AnyContractRouter | AnyRouter, options?: OpenAPIGeneratorGenerateOptions): Promise<OpenAPI.Document>;
49
98
  }
50
99
 
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 };
100
+ export { OpenAPIGenerator as b, CompositeSchemaConverter as e };
101
+ export type { ConditionalSchemaConverter as C, OpenAPIGeneratorOptions as O, SchemaConverterComponent as S, OpenAPIGeneratorGenerateOptions as a, SchemaConvertOptions as c, SchemaConverter as d };
@@ -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, undefinedErrorJsonSchema } = 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, undefinedErrorJsonSchema);
413
424
  }
414
425
  doc.paths ??= {};
415
426
  doc.paths[httpPath] ??= {};
@@ -433,22 +444,96 @@ ${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
+ let undefinedErrorJsonSchema = {
449
+ type: "object",
450
+ properties: {
451
+ defined: { const: false },
452
+ code: { type: "string" },
453
+ status: { type: "number" },
454
+ message: { type: "string" },
455
+ data: {}
456
+ },
457
+ required: ["defined", "code", "status", "message"]
458
+ };
459
+ const baseSchemaConvertOptions = {};
460
+ if (commonSchemas) {
461
+ baseSchemaConvertOptions.components = [];
462
+ for (const key in commonSchemas) {
463
+ const options = commonSchemas[key];
464
+ if (options.schema === void 0) {
465
+ continue;
466
+ }
467
+ const { schema, strategy = "input" } = options;
468
+ const [required, json] = await this.converter.convert(schema, { strategy });
469
+ const allowedStrategies = [strategy];
470
+ if (strategy === "input") {
471
+ const [outputRequired, outputJson] = await this.converter.convert(schema, { strategy: "output" });
472
+ if (outputRequired === required && stringifyJSON(outputJson) === stringifyJSON(json)) {
473
+ allowedStrategies.push("output");
474
+ }
475
+ } else if (strategy === "output") {
476
+ const [inputRequired, inputJson] = await this.converter.convert(schema, { strategy: "input" });
477
+ if (inputRequired === required && stringifyJSON(inputJson) === stringifyJSON(json)) {
478
+ allowedStrategies.push("input");
479
+ }
480
+ }
481
+ baseSchemaConvertOptions.components.push({
482
+ schema,
483
+ required,
484
+ ref: `#/components/schemas/${key}`,
485
+ allowedStrategies
486
+ });
487
+ }
488
+ doc.components ??= {};
489
+ doc.components.schemas ??= {};
490
+ for (const key in commonSchemas) {
491
+ const options = commonSchemas[key];
492
+ if (options.schema === void 0) {
493
+ if (options.error === "UndefinedError") {
494
+ doc.components.schemas[key] = toOpenAPISchema(undefinedErrorJsonSchema);
495
+ undefinedErrorJsonSchema = { $ref: `#/components/schemas/${key}` };
496
+ }
497
+ continue;
498
+ }
499
+ const { schema, strategy = "input" } = options;
500
+ const [, json] = await this.converter.convert(
501
+ schema,
502
+ {
503
+ ...baseSchemaConvertOptions,
504
+ strategy,
505
+ minStructureDepthForRef: 1
506
+ // not allow use $ref for root schemas
507
+ }
508
+ );
509
+ doc.components.schemas[key] = toOpenAPISchema(json);
510
+ }
511
+ }
512
+ return { baseSchemaConvertOptions, undefinedErrorJsonSchema };
513
+ }
514
+ async #request(doc, ref, def, baseSchemaConvertOptions) {
437
515
  const method = fallbackContractConfig("defaultMethod", def.route.method);
438
516
  const details = getEventIteratorSchemaDetails(def.inputSchema);
439
517
  if (details) {
440
518
  ref.requestBody = {
441
519
  required: true,
442
520
  content: toOpenAPIEventIteratorContent(
443
- await this.converter.convert(details.yields, { strategy: "input" }),
444
- await this.converter.convert(details.returns, { strategy: "input" })
521
+ await this.converter.convert(details.yields, { ...baseSchemaConvertOptions, strategy: "input" }),
522
+ await this.converter.convert(details.returns, { ...baseSchemaConvertOptions, strategy: "input" })
445
523
  )
446
524
  };
447
525
  return;
448
526
  }
449
527
  const dynamicParams = getDynamicParams(def.route.path)?.map((v) => v.name);
450
528
  const inputStructure = fallbackContractConfig("defaultInputStructure", def.route.inputStructure);
451
- let [required, schema] = await this.converter.convert(def.inputSchema, { strategy: "input" });
529
+ let [required, schema] = await this.converter.convert(
530
+ def.inputSchema,
531
+ {
532
+ ...baseSchemaConvertOptions,
533
+ strategy: "input",
534
+ minStructureDepthForRef: dynamicParams?.length || inputStructure === "detailed" ? 1 : 0
535
+ }
536
+ );
452
537
  if (isAnySchema(schema) && !dynamicParams?.length) {
453
538
  return;
454
539
  }
@@ -491,7 +576,8 @@ ${errors.join("\n\n")}`
491
576
  if (!isObjectSchema(schema)) {
492
577
  throw error;
493
578
  }
494
- if (dynamicParams?.length && (schema.properties?.params === void 0 || !isObjectSchema(schema.properties.params) || !checkParamsSchema(schema.properties.params, dynamicParams))) {
579
+ const resolvedParamSchema = schema.properties?.params !== void 0 ? resolveOpenAPIJsonSchemaRef(doc, schema.properties.params) : void 0;
580
+ if (dynamicParams?.length && (resolvedParamSchema === void 0 || !isObjectSchema(resolvedParamSchema) || !checkParamsSchema(resolvedParamSchema, dynamicParams))) {
495
581
  throw new OpenAPIGeneratorError(
496
582
  'When input structure is "detailed" and path has dynamic params, the "params" schema must be an object with all dynamic params as required.'
497
583
  );
@@ -499,12 +585,13 @@ ${errors.join("\n\n")}`
499
585
  for (const from of ["params", "query", "headers"]) {
500
586
  const fromSchema = schema.properties?.[from];
501
587
  if (fromSchema !== void 0) {
502
- if (!isObjectSchema(fromSchema)) {
588
+ const resolvedSchema = resolveOpenAPIJsonSchemaRef(doc, fromSchema);
589
+ if (!isObjectSchema(resolvedSchema)) {
503
590
  throw error;
504
591
  }
505
592
  const parameterIn = from === "params" ? "path" : from === "headers" ? "header" : "query";
506
593
  ref.parameters ??= [];
507
- ref.parameters.push(...toOpenAPIParameters(fromSchema, parameterIn));
594
+ ref.parameters.push(...toOpenAPIParameters(resolvedSchema, parameterIn));
508
595
  }
509
596
  }
510
597
  if (schema.properties?.body !== void 0) {
@@ -514,7 +601,7 @@ ${errors.join("\n\n")}`
514
601
  };
515
602
  }
516
603
  }
517
- async #successResponse(ref, def) {
604
+ async #successResponse(doc, ref, def, baseSchemaConvertOptions) {
518
605
  const outputSchema = def.outputSchema;
519
606
  const status = fallbackContractConfig("defaultSuccessStatus", def.route.successStatus);
520
607
  const description = fallbackContractConfig("defaultSuccessDescription", def.route?.successDescription);
@@ -525,13 +612,20 @@ ${errors.join("\n\n")}`
525
612
  ref.responses[status] = {
526
613
  description,
527
614
  content: toOpenAPIEventIteratorContent(
528
- await this.converter.convert(eventIteratorSchemaDetails.yields, { strategy: "output" }),
529
- await this.converter.convert(eventIteratorSchemaDetails.returns, { strategy: "output" })
615
+ await this.converter.convert(eventIteratorSchemaDetails.yields, { ...baseSchemaConvertOptions, strategy: "output" }),
616
+ await this.converter.convert(eventIteratorSchemaDetails.returns, { ...baseSchemaConvertOptions, strategy: "output" })
530
617
  )
531
618
  };
532
619
  return;
533
620
  }
534
- const [required, json] = await this.converter.convert(outputSchema, { strategy: "output" });
621
+ const [required, json] = await this.converter.convert(
622
+ outputSchema,
623
+ {
624
+ ...baseSchemaConvertOptions,
625
+ strategy: "output",
626
+ minStructureDepthForRef: outputStructure === "detailed" ? 1 : 0
627
+ }
628
+ );
535
629
  if (outputStructure === "compact") {
536
630
  ref.responses ??= {};
537
631
  ref.responses[status] = {
@@ -558,11 +652,12 @@ ${errors.join("\n\n")}`
558
652
  let schemaStatus;
559
653
  let schemaDescription;
560
654
  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)) {
655
+ const statusSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.status);
656
+ if (typeof statusSchema !== "object" || statusSchema.const === void 0 || typeof statusSchema.const !== "number" || !Number.isInteger(statusSchema.const) || isORPCErrorStatus(statusSchema.const)) {
562
657
  throw error;
563
658
  }
564
- schemaStatus = item.properties.status.const;
565
- schemaDescription = item.properties.status.description;
659
+ schemaStatus = statusSchema.const;
660
+ schemaDescription = statusSchema.description;
566
661
  }
567
662
  const itemStatus = schemaStatus ?? status;
568
663
  const itemDescription = schemaDescription ?? description;
@@ -578,16 +673,17 @@ ${errors.join("\n\n")}`
578
673
  description: itemDescription
579
674
  };
580
675
  if (item.properties?.headers !== void 0) {
581
- if (!isObjectSchema(item.properties.headers)) {
676
+ const headersSchema = resolveOpenAPIJsonSchemaRef(doc, item.properties.headers);
677
+ if (!isObjectSchema(headersSchema)) {
582
678
  throw error;
583
679
  }
584
- for (const key in item.properties.headers.properties) {
585
- const headerSchema = item.properties.headers.properties[key];
680
+ for (const key in headersSchema.properties) {
681
+ const headerSchema = headersSchema.properties[key];
586
682
  if (headerSchema !== void 0) {
587
683
  ref.responses[itemStatus].headers ??= {};
588
684
  ref.responses[itemStatus].headers[key] = {
589
685
  schema: toOpenAPISchema(headerSchema),
590
- required: item.properties.headers.required?.includes(key)
686
+ required: item.required?.includes("headers") && headersSchema.required?.includes(key)
591
687
  };
592
688
  }
593
689
  }
@@ -599,7 +695,7 @@ ${errors.join("\n\n")}`
599
695
  }
600
696
  }
601
697
  }
602
- async #errorResponse(ref, def) {
698
+ async #errorResponse(ref, def, baseSchemaConvertOptions, undefinedErrorSchema) {
603
699
  const errorMap = def.errorMap;
604
700
  const errors = {};
605
701
  for (const code in errorMap) {
@@ -609,7 +705,7 @@ ${errors.join("\n\n")}`
609
705
  }
610
706
  const status = fallbackORPCErrorStatus(code, config.status);
611
707
  const message = fallbackORPCErrorMessage(code, config.message);
612
- const [dataRequired, dataSchema] = await this.converter.convert(config.data, { strategy: "output" });
708
+ const [dataRequired, dataSchema] = await this.converter.convert(config.data, { ...baseSchemaConvertOptions, strategy: "output" });
613
709
  errors[status] ??= [];
614
710
  errors[status].push({
615
711
  type: "object",
@@ -631,17 +727,7 @@ ${errors.join("\n\n")}`
631
727
  content: toOpenAPIContent({
632
728
  oneOf: [
633
729
  ...schemas,
634
- {
635
- type: "object",
636
- properties: {
637
- defined: { const: false },
638
- code: { type: "string" },
639
- status: { type: "number" },
640
- message: { type: "string" },
641
- data: {}
642
- },
643
- required: ["defined", "code", "status", "message"]
644
- }
730
+ undefinedErrorSchema
645
731
  ]
646
732
  })
647
733
  };
@@ -649,4 +735,4 @@ ${errors.join("\n\n")}`
649
735
  }
650
736
  }
651
737
 
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 };
738
+ 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 };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/openapi",
3
3
  "type": "module",
4
- "version": "1.5.0",
4
+ "version": "1.5.2",
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.5.0",
55
- "@orpc/contract": "1.5.0",
56
- "@orpc/openapi-client": "1.5.0",
57
- "@orpc/server": "1.5.0",
58
- "@orpc/shared": "1.5.0",
59
- "@orpc/standard-server": "1.5.0"
54
+ "@orpc/openapi-client": "1.5.2",
55
+ "@orpc/server": "1.5.2",
56
+ "@orpc/shared": "1.5.2",
57
+ "@orpc/standard-server": "1.5.2",
58
+ "@orpc/client": "1.5.2",
59
+ "@orpc/contract": "1.5.2"
60
60
  },
61
61
  "devDependencies": {
62
- "zod": "^3.25.57"
62
+ "zod": "^3.25.64"
63
63
  },
64
64
  "scripts": {
65
65
  "build": "unbuild",