@orpc/zod 0.0.0-next.cac5dd3 → 0.0.0-next.caeb672

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/README.md CHANGED
@@ -30,7 +30,7 @@
30
30
  - **🔗 End-to-End Type Safety**: Ensure type-safe inputs, outputs, and errors from client to server.
31
31
  - **📘 First-Class OpenAPI**: Built-in support that fully adheres to the OpenAPI standard.
32
32
  - **📝 Contract-First Development**: Optionally define your API contract before implementation.
33
- - **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte), Pinia Colada, and more.
33
+ - **⚙️ Framework Integrations**: Seamlessly integrate with TanStack Query (React, Vue, Solid, Svelte, Angular), Pinia Colada, and more.
34
34
  - **🚀 Server Actions**: Fully compatible with React Server Actions on Next.js, TanStack Start, and other platforms.
35
35
  - **🔠 Standard Schema Support**: Works out of the box with Zod, Valibot, ArkType, and other schema validators.
36
36
  - **🗃️ Native Types**: Supports native types like Date, File, Blob, BigInt, URL, and more.
@@ -49,14 +49,12 @@ You can find the full documentation [here](https://orpc.unnoq.com).
49
49
  - [@orpc/contract](https://www.npmjs.com/package/@orpc/contract): Build your API contract.
50
50
  - [@orpc/server](https://www.npmjs.com/package/@orpc/server): Build your API or implement API contract.
51
51
  - [@orpc/client](https://www.npmjs.com/package/@orpc/client): Consume your API on the client with type-safety.
52
- - [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with NestJS.
52
+ - [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
53
+ - [@orpc/nest](https://www.npmjs.com/package/@orpc/nest): Deeply integrate oRPC with [NestJS](https://nestjs.com/).
53
54
  - [@orpc/react](https://www.npmjs.com/package/@orpc/react): Utilities for integrating oRPC with React and React Server Actions.
54
- - [@orpc/react-query](https://www.npmjs.com/package/@orpc/react-query): Integration with [React Query](https://tanstack.com/query/latest/docs/framework/react/overview).
55
- - [@orpc/vue-query](https://www.npmjs.com/package/@orpc/vue-query): Integration with [Vue Query](https://tanstack.com/query/latest/docs/framework/vue/overview).
56
- - [@orpc/solid-query](https://www.npmjs.com/package/@orpc/solid-query): Integration with [Solid Query](https://tanstack.com/query/latest/docs/framework/solid/overview).
57
- - [@orpc/svelte-query](https://www.npmjs.com/package/@orpc/svelte-query): Integration with [Svelte Query](https://tanstack.com/query/latest/docs/framework/svelte/overview).
55
+ - [@orpc/tanstack-query](https://www.npmjs.com/package/@orpc/tanstack-query): [TanStack Query](https://tanstack.com/query/latest) integration.
58
56
  - [@orpc/vue-colada](https://www.npmjs.com/package/@orpc/vue-colada): Integration with [Pinia Colada](https://pinia-colada.esm.dev/).
59
- - [@orpc/openapi](https://www.npmjs.com/package/@orpc/openapi): Generate OpenAPI specs and handle OpenAPI requests.
57
+ - [@orpc/hey-api](https://www.npmjs.com/package/@orpc/hey-api): [Hey API](https://heyapi.dev/) integration.
60
58
  - [@orpc/zod](https://www.npmjs.com/package/@orpc/zod): More schemas that [Zod](https://zod.dev/) doesn't support yet.
61
59
  - [@orpc/valibot](https://www.npmjs.com/package/@orpc/valibot): OpenAPI spec generation from [Valibot](https://valibot.dev/).
62
60
  - [@orpc/arktype](https://www.npmjs.com/package/@orpc/arktype): OpenAPI spec generation from [ArkType](https://arktype.io/).
@@ -74,7 +72,7 @@ More schemas that [Zod](https://zod.dev/) doesn't support yet, and provides `Zod
74
72
 
75
73
  ```ts
76
74
  import { oz } from '@orpc/zod'
77
- import { z } from 'zod'
75
+ import { z } from 'zod/v3'
78
76
 
79
77
  const Example = z.object({
80
78
  url: oz.url(),
@@ -88,7 +86,7 @@ const Example = z.object({
88
86
 
89
87
  ```ts
90
88
  import { OpenAPIGenerator } from '@orpc/openapi'
91
- import { ZodToJsonSchemaConverter } from '@orpc/zod'
89
+ import { ZodToJsonSchemaConverter } from '@orpc/zod/zod4'
92
90
 
93
91
  const openAPIGenerator = new OpenAPIGenerator({
94
92
  schemaConverters: [new ZodToJsonSchemaConverter()],
@@ -113,7 +111,7 @@ const specFromRouter = await openAPIGenerator.generate(router, {
113
111
 
114
112
  ```ts
115
113
  import { oz } from '@orpc/zod'
116
- import { z } from 'zod'
114
+ import * as z from 'zod'
117
115
 
118
116
  const InputSchema = oz.openapi(
119
117
  z.object({
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { JSONSchema, ConditionalSchemaConverter, SchemaConvertOptions } from '@orpc/openapi';
2
- import { ZodTypeAny, input, output, ZodTypeDef, CustomErrorParams, ZodType, ZodEffects } from 'zod';
2
+ import { ZodTypeAny, input, output, ZodTypeDef, CustomErrorParams, ZodType, ZodEffects } from 'zod/v3';
3
3
  import { Context } from '@orpc/server';
4
4
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
5
5
  import { AnySchema } from '@orpc/contract';
@@ -40,34 +40,43 @@ declare class ZodSmartCoercionPlugin<TContext extends Context> implements Standa
40
40
 
41
41
  interface ZodToJsonSchemaOptions {
42
42
  /**
43
- * Max depth of lazy type, if it exceeds.
43
+ * Max depth of lazy type
44
44
  *
45
- * Used `{}` when reach max depth
45
+ * Used `{}` when exceed max depth
46
46
  *
47
47
  * @default 3
48
48
  */
49
49
  maxLazyDepth?: number;
50
50
  /**
51
- * The schema to be used when the Zod schema is unsupported.
51
+ * Max depth of nested types
52
52
  *
53
- * @default { not: {} }
53
+ * Used anyJsonSchema (`{}`) when exceed max depth
54
+ *
55
+ * @default 10
54
56
  */
55
- unsupportedJsonSchema?: Exclude<JSONSchema, boolean>;
57
+ maxStructureDepth?: number;
56
58
  /**
57
59
  * The schema to be used to represent the any | unknown type.
58
60
  *
59
61
  * @default { }
60
62
  */
61
63
  anyJsonSchema?: Exclude<JSONSchema, boolean>;
64
+ /**
65
+ * The schema to be used when the Zod schema is unsupported.
66
+ *
67
+ * @default { not: {} }
68
+ */
69
+ unsupportedJsonSchema?: Exclude<JSONSchema, boolean>;
62
70
  }
63
71
  declare class ZodToJsonSchemaConverter implements ConditionalSchemaConverter {
64
72
  #private;
65
73
  private readonly maxLazyDepth;
74
+ private readonly maxStructureDepth;
66
75
  private readonly unsupportedJsonSchema;
67
76
  private readonly anyJsonSchema;
68
77
  constructor(options?: ZodToJsonSchemaOptions);
69
78
  condition(schema: AnySchema | undefined): boolean;
70
- convert(schema: AnySchema | undefined, options: SchemaConvertOptions, lazyDepth?: number, isHandledCustomJSONSchema?: boolean, isHandledZodDescription?: boolean): [required: boolean, jsonSchema: Exclude<JSONSchema, boolean>];
79
+ convert(schema: AnySchema | undefined, options: SchemaConvertOptions, lazyDepth?: number, isHandledCustomJSONSchema?: boolean, isHandledZodDescription?: boolean, structureDepth?: number): [required: boolean, jsonSchema: Exclude<JSONSchema, boolean>];
71
80
  }
72
81
 
73
82
  declare const oz: {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { JSONSchema, ConditionalSchemaConverter, SchemaConvertOptions } from '@orpc/openapi';
2
- import { ZodTypeAny, input, output, ZodTypeDef, CustomErrorParams, ZodType, ZodEffects } from 'zod';
2
+ import { ZodTypeAny, input, output, ZodTypeDef, CustomErrorParams, ZodType, ZodEffects } from 'zod/v3';
3
3
  import { Context } from '@orpc/server';
4
4
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
5
5
  import { AnySchema } from '@orpc/contract';
@@ -40,34 +40,43 @@ declare class ZodSmartCoercionPlugin<TContext extends Context> implements Standa
40
40
 
41
41
  interface ZodToJsonSchemaOptions {
42
42
  /**
43
- * Max depth of lazy type, if it exceeds.
43
+ * Max depth of lazy type
44
44
  *
45
- * Used `{}` when reach max depth
45
+ * Used `{}` when exceed max depth
46
46
  *
47
47
  * @default 3
48
48
  */
49
49
  maxLazyDepth?: number;
50
50
  /**
51
- * The schema to be used when the Zod schema is unsupported.
51
+ * Max depth of nested types
52
52
  *
53
- * @default { not: {} }
53
+ * Used anyJsonSchema (`{}`) when exceed max depth
54
+ *
55
+ * @default 10
54
56
  */
55
- unsupportedJsonSchema?: Exclude<JSONSchema, boolean>;
57
+ maxStructureDepth?: number;
56
58
  /**
57
59
  * The schema to be used to represent the any | unknown type.
58
60
  *
59
61
  * @default { }
60
62
  */
61
63
  anyJsonSchema?: Exclude<JSONSchema, boolean>;
64
+ /**
65
+ * The schema to be used when the Zod schema is unsupported.
66
+ *
67
+ * @default { not: {} }
68
+ */
69
+ unsupportedJsonSchema?: Exclude<JSONSchema, boolean>;
62
70
  }
63
71
  declare class ZodToJsonSchemaConverter implements ConditionalSchemaConverter {
64
72
  #private;
65
73
  private readonly maxLazyDepth;
74
+ private readonly maxStructureDepth;
66
75
  private readonly unsupportedJsonSchema;
67
76
  private readonly anyJsonSchema;
68
77
  constructor(options?: ZodToJsonSchemaOptions);
69
78
  condition(schema: AnySchema | undefined): boolean;
70
- convert(schema: AnySchema | undefined, options: SchemaConvertOptions, lazyDepth?: number, isHandledCustomJSONSchema?: boolean, isHandledZodDescription?: boolean): [required: boolean, jsonSchema: Exclude<JSONSchema, boolean>];
79
+ convert(schema: AnySchema | undefined, options: SchemaConvertOptions, lazyDepth?: number, isHandledCustomJSONSchema?: boolean, isHandledZodDescription?: boolean, structureDepth?: number): [required: boolean, jsonSchema: Exclude<JSONSchema, boolean>];
71
80
  }
72
81
 
73
82
  declare const oz: {
package/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import { custom, ZodFirstPartyTypeKind } from 'zod';
1
+ import { custom, ZodFirstPartyTypeKind } from 'zod/v3';
2
2
  import wcmatch from 'wildcard-match';
3
- import { isObject, guard } from '@orpc/shared';
3
+ import { isObject, guard, toArray } from '@orpc/shared';
4
+ import { JsonSchemaXNativeType } from '@orpc/json-schema';
4
5
  import { JSONSchemaFormat } from '@orpc/openapi';
5
6
  import escapeStringRegexp from 'escape-string-regexp';
6
7
 
@@ -128,7 +129,7 @@ class ZodSmartCoercionPlugin {
128
129
  options.clientInterceptors ??= [];
129
130
  options.clientInterceptors.unshift((options2) => {
130
131
  const inputSchema = options2.procedure["~orpc"].inputSchema;
131
- if (!inputSchema || inputSchema["~standard"].vendor !== "zod") {
132
+ if (!inputSchema || inputSchema["~standard"].vendor !== "zod" || "_zod" in inputSchema) {
132
133
  return options2.next();
133
134
  }
134
135
  const coercedInput = zodCoerceInternal(inputSchema, options2.input);
@@ -390,25 +391,39 @@ function safeToDate(value) {
390
391
 
391
392
  class ZodToJsonSchemaConverter {
392
393
  maxLazyDepth;
394
+ maxStructureDepth;
393
395
  unsupportedJsonSchema;
394
396
  anyJsonSchema;
395
397
  constructor(options = {}) {
396
398
  this.maxLazyDepth = options.maxLazyDepth ?? 3;
399
+ this.maxStructureDepth = options.maxStructureDepth ?? 10;
397
400
  this.unsupportedJsonSchema = options.unsupportedJsonSchema ?? { not: {} };
398
401
  this.anyJsonSchema = options.anyJsonSchema ?? {};
399
402
  }
400
403
  condition(schema) {
401
- return schema !== void 0 && schema["~standard"].vendor === "zod";
404
+ return schema !== void 0 && schema["~standard"].vendor === "zod" && !("_zod" in schema);
402
405
  }
403
- convert(schema, options, lazyDepth = 0, isHandledCustomJSONSchema = false, isHandledZodDescription = false) {
406
+ convert(schema, options, lazyDepth = 0, isHandledCustomJSONSchema = false, isHandledZodDescription = false, structureDepth = 0) {
404
407
  const def = schema._def;
408
+ if (structureDepth > this.maxStructureDepth) {
409
+ return [false, this.anyJsonSchema];
410
+ }
411
+ if (!options.minStructureDepthForRef || options.minStructureDepthForRef <= structureDepth) {
412
+ const components = toArray(options.components);
413
+ for (const component of components) {
414
+ if (component.schema === schema && component.allowedStrategies.includes(options.strategy)) {
415
+ return [component.required, { $ref: component.ref }];
416
+ }
417
+ }
418
+ }
405
419
  if (!isHandledZodDescription && "description" in def && typeof def.description === "string") {
406
420
  const [required, json] = this.convert(
407
421
  schema,
408
422
  options,
409
423
  lazyDepth,
410
424
  isHandledCustomJSONSchema,
411
- true
425
+ true,
426
+ structureDepth
412
427
  );
413
428
  return [required, { ...json, description: def.description }];
414
429
  }
@@ -420,7 +435,8 @@ class ZodToJsonSchemaConverter {
420
435
  options,
421
436
  lazyDepth,
422
437
  true,
423
- isHandledZodDescription
438
+ isHandledZodDescription,
439
+ structureDepth
424
440
  );
425
441
  return [required, { ...json, ...customJSONSchema }];
426
442
  }
@@ -548,7 +564,11 @@ class ZodToJsonSchemaConverter {
548
564
  return [true, json];
549
565
  }
550
566
  case ZodFirstPartyTypeKind.ZodBigInt: {
551
- const json = { type: "string", pattern: "^-?[0-9]+$" };
567
+ const json = {
568
+ "type": "string",
569
+ "pattern": "^-?[0-9]+$",
570
+ "x-native-type": JsonSchemaXNativeType.BigInt
571
+ };
552
572
  return [true, json];
553
573
  }
554
574
  case ZodFirstPartyTypeKind.ZodNaN: {
@@ -558,7 +578,11 @@ class ZodToJsonSchemaConverter {
558
578
  return [true, { type: "boolean" }];
559
579
  }
560
580
  case ZodFirstPartyTypeKind.ZodDate: {
561
- const schema2 = { type: "string", format: JSONSchemaFormat.DateTime };
581
+ const schema2 = {
582
+ "type": "string",
583
+ "format": JSONSchemaFormat.DateTime,
584
+ "x-native-type": JsonSchemaXNativeType.Date
585
+ };
562
586
  return [true, schema2];
563
587
  }
564
588
  case ZodFirstPartyTypeKind.ZodNull: {
@@ -591,7 +615,7 @@ class ZodToJsonSchemaConverter {
591
615
  const schema_ = schema;
592
616
  const def2 = schema_._def;
593
617
  const json = { type: "array" };
594
- const [itemRequired, itemJson] = this.convert(def2.type, options, lazyDepth, false, false);
618
+ const [itemRequired, itemJson] = this.convert(def2.type, options, lazyDepth, false, false, structureDepth + 1);
595
619
  json.items = this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy);
596
620
  if (def2.exactLength) {
597
621
  json.maxItems = def2.exactLength.value;
@@ -610,7 +634,7 @@ class ZodToJsonSchemaConverter {
610
634
  const prefixItems = [];
611
635
  const json = { type: "array" };
612
636
  for (const item of schema_._def.items) {
613
- const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false);
637
+ const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth + 1);
614
638
  prefixItems.push(
615
639
  this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy)
616
640
  );
@@ -619,7 +643,7 @@ class ZodToJsonSchemaConverter {
619
643
  json.prefixItems = prefixItems;
620
644
  }
621
645
  if (schema_._def.rest) {
622
- const [itemRequired, itemJson] = this.convert(schema_._def.rest, options, lazyDepth, false, false);
646
+ const [itemRequired, itemJson] = this.convert(schema_._def.rest, options, lazyDepth, false, false, structureDepth + 1);
623
647
  json.items = this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy);
624
648
  }
625
649
  return [true, json];
@@ -630,7 +654,7 @@ class ZodToJsonSchemaConverter {
630
654
  const properties = {};
631
655
  const required = [];
632
656
  for (const [key, value] of Object.entries(schema_.shape)) {
633
- const [itemRequired, itemJson] = this.convert(value, options, lazyDepth, false, false);
657
+ const [itemRequired, itemJson] = this.convert(value, options, lazyDepth, false, false, structureDepth + 1);
634
658
  properties[key] = itemJson;
635
659
  if (itemRequired) {
636
660
  required.push(key);
@@ -648,7 +672,7 @@ class ZodToJsonSchemaConverter {
648
672
  json.additionalProperties = false;
649
673
  }
650
674
  } else {
651
- const [_, addJson] = this.convert(schema_._def.catchall, options, lazyDepth, false, false);
675
+ const [_, addJson] = this.convert(schema_._def.catchall, options, lazyDepth, false, false, structureDepth + 1);
652
676
  json.additionalProperties = addJson;
653
677
  }
654
678
  return [true, json];
@@ -656,28 +680,32 @@ class ZodToJsonSchemaConverter {
656
680
  case ZodFirstPartyTypeKind.ZodRecord: {
657
681
  const schema_ = schema;
658
682
  const json = { type: "object" };
659
- const [__, keyJson] = this.convert(schema_._def.keyType, options, lazyDepth, false, false);
683
+ const [__, keyJson] = this.convert(schema_._def.keyType, options, lazyDepth, false, false, structureDepth + 1);
660
684
  if (Object.entries(keyJson).some(([k, v]) => k !== "type" || v !== "string")) {
661
685
  json.propertyNames = keyJson;
662
686
  }
663
- const [_, itemJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false);
687
+ const [_, itemJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false, structureDepth + 1);
664
688
  json.additionalProperties = itemJson;
665
689
  return [true, json];
666
690
  }
667
691
  case ZodFirstPartyTypeKind.ZodSet: {
668
692
  const schema_ = schema;
669
- const json = { type: "array", uniqueItems: true };
670
- const [itemRequired, itemJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false);
693
+ const json = {
694
+ "type": "array",
695
+ "uniqueItems": true,
696
+ "x-native-type": JsonSchemaXNativeType.Set
697
+ };
698
+ const [itemRequired, itemJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false, structureDepth + 1);
671
699
  json.items = this.#toArrayItemJsonSchema(itemRequired, itemJson, options.strategy);
672
700
  return [true, json];
673
701
  }
674
702
  case ZodFirstPartyTypeKind.ZodMap: {
675
703
  const schema_ = schema;
676
- const [keyRequired, keyJson] = this.convert(schema_._def.keyType, options, lazyDepth, false, false);
677
- const [valueRequired, valueJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false);
678
- return [true, {
679
- type: "array",
680
- items: {
704
+ const [keyRequired, keyJson] = this.convert(schema_._def.keyType, options, lazyDepth, false, false, structureDepth + 1);
705
+ const [valueRequired, valueJson] = this.convert(schema_._def.valueType, options, lazyDepth, false, false, structureDepth + 1);
706
+ const json = {
707
+ "type": "array",
708
+ "items": {
681
709
  type: "array",
682
710
  prefixItems: [
683
711
  this.#toArrayItemJsonSchema(keyRequired, keyJson, options.strategy),
@@ -685,8 +713,10 @@ class ZodToJsonSchemaConverter {
685
713
  ],
686
714
  maxItems: 2,
687
715
  minItems: 2
688
- }
689
- }];
716
+ },
717
+ "x-native-type": JsonSchemaXNativeType.Map
718
+ };
719
+ return [true, json];
690
720
  }
691
721
  case ZodFirstPartyTypeKind.ZodUnion:
692
722
  case ZodFirstPartyTypeKind.ZodDiscriminatedUnion: {
@@ -694,7 +724,7 @@ class ZodToJsonSchemaConverter {
694
724
  const anyOf = [];
695
725
  let required = true;
696
726
  for (const item of schema_._def.options) {
697
- const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false);
727
+ const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth);
698
728
  if (!itemRequired) {
699
729
  required = false;
700
730
  if (itemJson !== this.unsupportedJsonSchema) {
@@ -714,7 +744,7 @@ class ZodToJsonSchemaConverter {
714
744
  const allOf = [];
715
745
  let required = false;
716
746
  for (const item of [schema_._def.left, schema_._def.right]) {
717
- const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false);
747
+ const [itemRequired, itemJson] = this.convert(item, options, lazyDepth, false, false, structureDepth);
718
748
  allOf.push(itemJson);
719
749
  if (itemRequired) {
720
750
  required = true;
@@ -723,25 +753,26 @@ class ZodToJsonSchemaConverter {
723
753
  return [required, { allOf }];
724
754
  }
725
755
  case ZodFirstPartyTypeKind.ZodLazy: {
726
- if (lazyDepth >= this.maxLazyDepth) {
756
+ const currentLazyDepth = lazyDepth + 1;
757
+ if (currentLazyDepth > this.maxLazyDepth) {
727
758
  return [false, this.anyJsonSchema];
728
759
  }
729
760
  const schema_ = schema;
730
- return this.convert(schema_._def.getter(), options, lazyDepth + 1, false, false);
761
+ return this.convert(schema_._def.getter(), options, currentLazyDepth, false, false, structureDepth);
731
762
  }
732
763
  case ZodFirstPartyTypeKind.ZodOptional: {
733
764
  const schema_ = schema;
734
- const [_, inner] = this.convert(schema_._def.innerType, options, lazyDepth, false, false);
765
+ const [_, inner] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
735
766
  return [false, inner];
736
767
  }
737
768
  case ZodFirstPartyTypeKind.ZodReadonly: {
738
769
  const schema_ = schema;
739
- const [required, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false);
770
+ const [required, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
740
771
  return [required, { ...json, readOnly: true }];
741
772
  }
742
773
  case ZodFirstPartyTypeKind.ZodDefault: {
743
774
  const schema_ = schema;
744
- const [_, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false);
775
+ const [_, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
745
776
  return [false, { default: schema_._def.defaultValue(), ...json }];
746
777
  }
747
778
  case ZodFirstPartyTypeKind.ZodEffects: {
@@ -749,15 +780,15 @@ class ZodToJsonSchemaConverter {
749
780
  if (schema_._def.effect.type === "transform" && options.strategy === "output") {
750
781
  return [false, this.anyJsonSchema];
751
782
  }
752
- return this.convert(schema_._def.schema, options, lazyDepth, false, false);
783
+ return this.convert(schema_._def.schema, options, lazyDepth, false, false, structureDepth);
753
784
  }
754
785
  case ZodFirstPartyTypeKind.ZodCatch: {
755
786
  const schema_ = schema;
756
- return this.convert(schema_._def.innerType, options, lazyDepth, false, false);
787
+ return this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
757
788
  }
758
789
  case ZodFirstPartyTypeKind.ZodBranded: {
759
790
  const schema_ = schema;
760
- return this.convert(schema_._def.type, options, lazyDepth, false, false);
791
+ return this.convert(schema_._def.type, options, lazyDepth, false, false, structureDepth);
761
792
  }
762
793
  case ZodFirstPartyTypeKind.ZodPipeline: {
763
794
  const schema_ = schema;
@@ -766,13 +797,14 @@ class ZodToJsonSchemaConverter {
766
797
  options,
767
798
  lazyDepth,
768
799
  false,
769
- false
800
+ false,
801
+ structureDepth
770
802
  );
771
803
  }
772
804
  case ZodFirstPartyTypeKind.ZodNullable: {
773
805
  const schema_ = schema;
774
- const [required, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false);
775
- return [required, { anyOf: [{ type: "null" }, json] }];
806
+ const [required, json] = this.convert(schema_._def.innerType, options, lazyDepth, false, false, structureDepth);
807
+ return [required, { anyOf: [json, { type: "null" }] }];
776
808
  }
777
809
  }
778
810
  return [true, this.unsupportedJsonSchema];
@@ -791,12 +823,17 @@ class ZodToJsonSchemaConverter {
791
823
  }
792
824
  case "regexp": {
793
825
  return {
794
- type: "string",
795
- pattern: "^\\/(.*)\\/([a-z]*)$"
826
+ "type": "string",
827
+ "pattern": "^\\/(.*)\\/([a-z]*)$",
828
+ "x-native-type": JsonSchemaXNativeType.RegExp
796
829
  };
797
830
  }
798
831
  case "url": {
799
- return { type: "string", format: JSONSchemaFormat.URI };
832
+ return {
833
+ "type": "string",
834
+ "format": JSONSchemaFormat.URI,
835
+ "x-native-type": JsonSchemaXNativeType.Url
836
+ };
800
837
  }
801
838
  }
802
839
  }
@@ -2,24 +2,35 @@ import { Context } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
3
3
  import { AnySchema } from '@orpc/contract';
4
4
  import { JSONSchema, SchemaConvertOptions, ConditionalSchemaConverter } from '@orpc/openapi';
5
- import { Interceptor, ThrowableError, Promisable } from '@orpc/shared';
6
- import * as _zod_core from '@zod/core';
7
- import { $ZodType, $input, $output } from '@zod/core';
5
+ import { Interceptor } from '@orpc/shared';
6
+ import * as zod_v4_core from 'zod/v4/core';
7
+ import { $ZodType, $input, $output } from 'zod/v4/core';
8
8
 
9
+ /**
10
+ * @deprecated Use [Smart Coercion Plugin](https://orpc.unnoq.com/docs/openapi/plugins/smart-coercion) instead.
11
+ */
9
12
  declare class experimental_ZodSmartCoercionPlugin<TContext extends Context> implements StandardHandlerPlugin<TContext> {
10
13
  #private;
11
14
  init(options: StandardHandlerOptions<TContext>): void;
12
15
  }
13
16
 
14
- interface experimental_ZodToJsonSchemaOptions {
17
+ interface ZodToJsonSchemaConverterOptions {
15
18
  /**
16
- * Max depth of lazy type, if it exceeds.
19
+ * Max depth of lazy type.
17
20
  *
18
- * Used anyJsonSchema (`{}`) when reach max depth
21
+ * Used anyJsonSchema (`{}`) when exceed max depth
19
22
  *
20
23
  * @default 2
21
24
  */
22
25
  maxLazyDepth?: number;
26
+ /**
27
+ * Max depth of nested types.
28
+ *
29
+ * Used anyJsonSchema (`{}`) when exceed max depth
30
+ *
31
+ * @default 10
32
+ */
33
+ maxStructureDepth?: number;
23
34
  /**
24
35
  * The schema to be used to represent the any | unknown type.
25
36
  *
@@ -46,18 +57,19 @@ interface experimental_ZodToJsonSchemaOptions {
46
57
  }, [
47
58
  required: boolean,
48
59
  jsonSchema: Exclude<JSONSchema, boolean>
49
- ], ThrowableError>[];
60
+ ]>[];
50
61
  }
51
- declare class experimental_ZodToJsonSchemaConverter implements ConditionalSchemaConverter {
62
+ declare class ZodToJsonSchemaConverter implements ConditionalSchemaConverter {
52
63
  #private;
53
64
  private readonly maxLazyDepth;
65
+ private readonly maxStructureDepth;
54
66
  private readonly anyJsonSchema;
55
67
  private readonly unsupportedJsonSchema;
56
68
  private readonly undefinedJsonSchema;
57
69
  private readonly interceptors;
58
- constructor(options?: experimental_ZodToJsonSchemaOptions);
70
+ constructor(options?: ZodToJsonSchemaConverterOptions);
59
71
  condition(schema: AnySchema | undefined): boolean;
60
- convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: Exclude<JSONSchema, boolean>]>;
72
+ convert(schema: AnySchema | undefined, options: SchemaConvertOptions): [required: boolean, jsonSchema: Exclude<JSONSchema, boolean>];
61
73
  }
62
74
 
63
75
  /**
@@ -77,7 +89,7 @@ declare class experimental_ZodToJsonSchemaConverter implements ConditionalSchema
77
89
  * })
78
90
  * ```
79
91
  */
80
- declare const experimental_JSON_SCHEMA_REGISTRY: _zod_core.$ZodRegistry<{
92
+ declare const JSON_SCHEMA_REGISTRY: zod_v4_core.$ZodRegistry<{
81
93
  $anchor?: string;
82
94
  $comment?: string;
83
95
  $defs?: Record<string, JSONSchema>;
@@ -138,7 +150,7 @@ declare const experimental_JSON_SCHEMA_REGISTRY: _zod_core.$ZodRegistry<{
138
150
  unevaluatedProperties?: JSONSchema;
139
151
  uniqueItems?: boolean;
140
152
  writeOnly?: boolean;
141
- }, _zod_core.$ZodType<unknown, unknown>>;
153
+ }, zod_v4_core.$ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>>;
142
154
  /**
143
155
  * Zod registry for customizing generated JSON schema, only useful for .input
144
156
  *
@@ -156,7 +168,7 @@ declare const experimental_JSON_SCHEMA_REGISTRY: _zod_core.$ZodRegistry<{
156
168
  * })
157
169
  * ```
158
170
  */
159
- declare const experimental_JSON_SCHEMA_INPUT_REGISTRY: _zod_core.$ZodRegistry<{
171
+ declare const JSON_SCHEMA_INPUT_REGISTRY: zod_v4_core.$ZodRegistry<{
160
172
  $anchor?: string;
161
173
  $comment?: string;
162
174
  $defs?: Record<string, JSONSchema>;
@@ -217,7 +229,7 @@ declare const experimental_JSON_SCHEMA_INPUT_REGISTRY: _zod_core.$ZodRegistry<{
217
229
  unevaluatedProperties?: JSONSchema;
218
230
  uniqueItems?: boolean;
219
231
  writeOnly?: boolean;
220
- }, _zod_core.$ZodType<unknown, unknown>>;
232
+ }, zod_v4_core.$ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>>;
221
233
  /**
222
234
  * Zod registry for customizing generated JSON schema, only useful for .input
223
235
  *
@@ -235,7 +247,7 @@ declare const experimental_JSON_SCHEMA_INPUT_REGISTRY: _zod_core.$ZodRegistry<{
235
247
  * })
236
248
  * ```
237
249
  */
238
- declare const experimental_JSON_SCHEMA_OUTPUT_REGISTRY: _zod_core.$ZodRegistry<{
250
+ declare const JSON_SCHEMA_OUTPUT_REGISTRY: zod_v4_core.$ZodRegistry<{
239
251
  $anchor?: string;
240
252
  $comment?: string;
241
253
  $defs?: Record<string, JSONSchema>;
@@ -296,7 +308,7 @@ declare const experimental_JSON_SCHEMA_OUTPUT_REGISTRY: _zod_core.$ZodRegistry<{
296
308
  unevaluatedProperties?: JSONSchema;
297
309
  uniqueItems?: boolean;
298
310
  writeOnly?: boolean;
299
- }, _zod_core.$ZodType<unknown, unknown>>;
311
+ }, zod_v4_core.$ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>>;
300
312
 
301
- export { experimental_JSON_SCHEMA_INPUT_REGISTRY, experimental_JSON_SCHEMA_OUTPUT_REGISTRY, experimental_JSON_SCHEMA_REGISTRY, experimental_ZodSmartCoercionPlugin, experimental_ZodToJsonSchemaConverter };
302
- export type { experimental_ZodToJsonSchemaOptions };
313
+ export { JSON_SCHEMA_INPUT_REGISTRY, JSON_SCHEMA_OUTPUT_REGISTRY, JSON_SCHEMA_REGISTRY, ZodToJsonSchemaConverter, experimental_ZodSmartCoercionPlugin };
314
+ export type { ZodToJsonSchemaConverterOptions };
@@ -2,24 +2,35 @@ import { Context } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerOptions } from '@orpc/server/standard';
3
3
  import { AnySchema } from '@orpc/contract';
4
4
  import { JSONSchema, SchemaConvertOptions, ConditionalSchemaConverter } from '@orpc/openapi';
5
- import { Interceptor, ThrowableError, Promisable } from '@orpc/shared';
6
- import * as _zod_core from '@zod/core';
7
- import { $ZodType, $input, $output } from '@zod/core';
5
+ import { Interceptor } from '@orpc/shared';
6
+ import * as zod_v4_core from 'zod/v4/core';
7
+ import { $ZodType, $input, $output } from 'zod/v4/core';
8
8
 
9
+ /**
10
+ * @deprecated Use [Smart Coercion Plugin](https://orpc.unnoq.com/docs/openapi/plugins/smart-coercion) instead.
11
+ */
9
12
  declare class experimental_ZodSmartCoercionPlugin<TContext extends Context> implements StandardHandlerPlugin<TContext> {
10
13
  #private;
11
14
  init(options: StandardHandlerOptions<TContext>): void;
12
15
  }
13
16
 
14
- interface experimental_ZodToJsonSchemaOptions {
17
+ interface ZodToJsonSchemaConverterOptions {
15
18
  /**
16
- * Max depth of lazy type, if it exceeds.
19
+ * Max depth of lazy type.
17
20
  *
18
- * Used anyJsonSchema (`{}`) when reach max depth
21
+ * Used anyJsonSchema (`{}`) when exceed max depth
19
22
  *
20
23
  * @default 2
21
24
  */
22
25
  maxLazyDepth?: number;
26
+ /**
27
+ * Max depth of nested types.
28
+ *
29
+ * Used anyJsonSchema (`{}`) when exceed max depth
30
+ *
31
+ * @default 10
32
+ */
33
+ maxStructureDepth?: number;
23
34
  /**
24
35
  * The schema to be used to represent the any | unknown type.
25
36
  *
@@ -46,18 +57,19 @@ interface experimental_ZodToJsonSchemaOptions {
46
57
  }, [
47
58
  required: boolean,
48
59
  jsonSchema: Exclude<JSONSchema, boolean>
49
- ], ThrowableError>[];
60
+ ]>[];
50
61
  }
51
- declare class experimental_ZodToJsonSchemaConverter implements ConditionalSchemaConverter {
62
+ declare class ZodToJsonSchemaConverter implements ConditionalSchemaConverter {
52
63
  #private;
53
64
  private readonly maxLazyDepth;
65
+ private readonly maxStructureDepth;
54
66
  private readonly anyJsonSchema;
55
67
  private readonly unsupportedJsonSchema;
56
68
  private readonly undefinedJsonSchema;
57
69
  private readonly interceptors;
58
- constructor(options?: experimental_ZodToJsonSchemaOptions);
70
+ constructor(options?: ZodToJsonSchemaConverterOptions);
59
71
  condition(schema: AnySchema | undefined): boolean;
60
- convert(schema: AnySchema | undefined, options: SchemaConvertOptions): Promisable<[required: boolean, jsonSchema: Exclude<JSONSchema, boolean>]>;
72
+ convert(schema: AnySchema | undefined, options: SchemaConvertOptions): [required: boolean, jsonSchema: Exclude<JSONSchema, boolean>];
61
73
  }
62
74
 
63
75
  /**
@@ -77,7 +89,7 @@ declare class experimental_ZodToJsonSchemaConverter implements ConditionalSchema
77
89
  * })
78
90
  * ```
79
91
  */
80
- declare const experimental_JSON_SCHEMA_REGISTRY: _zod_core.$ZodRegistry<{
92
+ declare const JSON_SCHEMA_REGISTRY: zod_v4_core.$ZodRegistry<{
81
93
  $anchor?: string;
82
94
  $comment?: string;
83
95
  $defs?: Record<string, JSONSchema>;
@@ -138,7 +150,7 @@ declare const experimental_JSON_SCHEMA_REGISTRY: _zod_core.$ZodRegistry<{
138
150
  unevaluatedProperties?: JSONSchema;
139
151
  uniqueItems?: boolean;
140
152
  writeOnly?: boolean;
141
- }, _zod_core.$ZodType<unknown, unknown>>;
153
+ }, zod_v4_core.$ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>>;
142
154
  /**
143
155
  * Zod registry for customizing generated JSON schema, only useful for .input
144
156
  *
@@ -156,7 +168,7 @@ declare const experimental_JSON_SCHEMA_REGISTRY: _zod_core.$ZodRegistry<{
156
168
  * })
157
169
  * ```
158
170
  */
159
- declare const experimental_JSON_SCHEMA_INPUT_REGISTRY: _zod_core.$ZodRegistry<{
171
+ declare const JSON_SCHEMA_INPUT_REGISTRY: zod_v4_core.$ZodRegistry<{
160
172
  $anchor?: string;
161
173
  $comment?: string;
162
174
  $defs?: Record<string, JSONSchema>;
@@ -217,7 +229,7 @@ declare const experimental_JSON_SCHEMA_INPUT_REGISTRY: _zod_core.$ZodRegistry<{
217
229
  unevaluatedProperties?: JSONSchema;
218
230
  uniqueItems?: boolean;
219
231
  writeOnly?: boolean;
220
- }, _zod_core.$ZodType<unknown, unknown>>;
232
+ }, zod_v4_core.$ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>>;
221
233
  /**
222
234
  * Zod registry for customizing generated JSON schema, only useful for .input
223
235
  *
@@ -235,7 +247,7 @@ declare const experimental_JSON_SCHEMA_INPUT_REGISTRY: _zod_core.$ZodRegistry<{
235
247
  * })
236
248
  * ```
237
249
  */
238
- declare const experimental_JSON_SCHEMA_OUTPUT_REGISTRY: _zod_core.$ZodRegistry<{
250
+ declare const JSON_SCHEMA_OUTPUT_REGISTRY: zod_v4_core.$ZodRegistry<{
239
251
  $anchor?: string;
240
252
  $comment?: string;
241
253
  $defs?: Record<string, JSONSchema>;
@@ -296,7 +308,7 @@ declare const experimental_JSON_SCHEMA_OUTPUT_REGISTRY: _zod_core.$ZodRegistry<{
296
308
  unevaluatedProperties?: JSONSchema;
297
309
  uniqueItems?: boolean;
298
310
  writeOnly?: boolean;
299
- }, _zod_core.$ZodType<unknown, unknown>>;
311
+ }, zod_v4_core.$ZodType<unknown, unknown, zod_v4_core.$ZodTypeInternals<unknown, unknown>>>;
300
312
 
301
- export { experimental_JSON_SCHEMA_INPUT_REGISTRY, experimental_JSON_SCHEMA_OUTPUT_REGISTRY, experimental_JSON_SCHEMA_REGISTRY, experimental_ZodSmartCoercionPlugin, experimental_ZodToJsonSchemaConverter };
302
- export type { experimental_ZodToJsonSchemaOptions };
313
+ export { JSON_SCHEMA_INPUT_REGISTRY, JSON_SCHEMA_OUTPUT_REGISTRY, JSON_SCHEMA_REGISTRY, ZodToJsonSchemaConverter, experimental_ZodSmartCoercionPlugin };
314
+ export type { ZodToJsonSchemaConverterOptions };
@@ -1,13 +1,14 @@
1
- import { isObject, guard, intercept } from '@orpc/shared';
1
+ import { isObject, guard, intercept, toArray } from '@orpc/shared';
2
+ import { JsonSchemaXNativeType } from '@orpc/json-schema';
2
3
  import { JSONSchemaFormat, JSONSchemaContentEncoding } from '@orpc/openapi';
3
- import { registry, globalRegistry } from '@zod/core';
4
+ import { registry, globalRegistry } from 'zod/v4/core';
4
5
 
5
6
  class experimental_ZodSmartCoercionPlugin {
6
7
  init(options) {
7
8
  options.clientInterceptors ??= [];
8
9
  options.clientInterceptors.unshift((options2) => {
9
10
  const inputSchema = options2.procedure["~orpc"].inputSchema;
10
- if (!inputSchema || inputSchema["~standard"].vendor !== "zod") {
11
+ if (!inputSchema || inputSchema["~standard"].vendor !== "zod" || !("_zod" in inputSchema)) {
11
12
  return options2.next();
12
13
  }
13
14
  const coercedInput = this.#coerce(inputSchema, options2.input);
@@ -100,8 +101,7 @@ class experimental_ZodSmartCoercionPlugin {
100
101
  }
101
102
  return value;
102
103
  }
103
- case "object":
104
- case "interface": {
104
+ case "object": {
105
105
  const object = schema;
106
106
  if (value === void 0) {
107
107
  return {};
@@ -165,9 +165,21 @@ class experimental_ZodSmartCoercionPlugin {
165
165
  return this.#coerce(union._zod.def.options[0], value);
166
166
  }
167
167
  if (isObject(value)) {
168
+ const discriminator = "discriminator" in union._zod.def && typeof union._zod.def.discriminator === "string" ? union._zod.def.discriminator : void 0;
168
169
  for (const option of union._zod.def.options) {
169
- if (option._zod.disc && this.#matchDiscriminators(value, option._zod.disc)) {
170
- return this.#coerce(option, value);
170
+ if (!option._zod.propValues) {
171
+ continue;
172
+ }
173
+ if (discriminator !== void 0) {
174
+ if (option._zod.propValues[discriminator]?.has(value[discriminator])) {
175
+ return this.#coerce(option, value);
176
+ }
177
+ } else {
178
+ for (const key in option._zod.propValues) {
179
+ if (option._zod.propValues[key]?.has(value[key])) {
180
+ return this.#coerce(option, value);
181
+ }
182
+ }
171
183
  }
172
184
  }
173
185
  }
@@ -207,10 +219,17 @@ class experimental_ZodSmartCoercionPlugin {
207
219
  return this.#coerce(pipe._zod.def.in, value);
208
220
  }
209
221
  case "default":
210
- case "catch": {
222
+ case "prefault": {
211
223
  const default_ = schema;
224
+ if (value === void 0) {
225
+ return value;
226
+ }
212
227
  return this.#coerce(default_._zod.def.innerType, value);
213
228
  }
229
+ case "catch": {
230
+ const catch_ = schema;
231
+ return this.#coerce(catch_._zod.def.innerType, value);
232
+ }
214
233
  case "lazy": {
215
234
  const lazy = schema;
216
235
  if (value !== void 0) {
@@ -248,64 +267,53 @@ class experimental_ZodSmartCoercionPlugin {
248
267
  }
249
268
  return value;
250
269
  }
251
- /**
252
- * This function is inspired from Zod, because it's not exported
253
- * https://github.com/colinhacks/zod/blob/v4/packages/core/src/schemas.ts#L1903C1-L1921C2
254
- */
255
- #matchDiscriminators(input, discs) {
256
- for (const [key, value] of discs) {
257
- const data = input[key];
258
- if (value.values.size && !value.values.has(data)) {
259
- return false;
260
- }
261
- if (value.maps.length === 0) {
262
- continue;
263
- }
264
- if (!isObject(data)) {
265
- return false;
266
- }
267
- for (const m of value.maps) {
268
- if (!this.#matchDiscriminators(data, m)) {
269
- return false;
270
- }
271
- }
272
- }
273
- return true;
274
- }
275
270
  }
276
271
 
277
- const experimental_JSON_SCHEMA_REGISTRY = registry();
278
- const experimental_JSON_SCHEMA_INPUT_REGISTRY = registry();
279
- const experimental_JSON_SCHEMA_OUTPUT_REGISTRY = registry();
272
+ const JSON_SCHEMA_REGISTRY = registry();
273
+ const JSON_SCHEMA_INPUT_REGISTRY = registry();
274
+ const JSON_SCHEMA_OUTPUT_REGISTRY = registry();
280
275
 
281
- class experimental_ZodToJsonSchemaConverter {
276
+ class ZodToJsonSchemaConverter {
282
277
  maxLazyDepth;
278
+ maxStructureDepth;
283
279
  anyJsonSchema;
284
280
  unsupportedJsonSchema;
285
281
  undefinedJsonSchema;
286
282
  interceptors;
287
283
  constructor(options = {}) {
288
284
  this.maxLazyDepth = options.maxLazyDepth ?? 2;
285
+ this.maxStructureDepth = options.maxStructureDepth ?? 10;
289
286
  this.anyJsonSchema = options.anyJsonSchema ?? {};
290
287
  this.unsupportedJsonSchema = options.unsupportedJsonSchema ?? { not: {} };
291
288
  this.undefinedJsonSchema = options.undefinedJsonSchema ?? { not: {} };
292
289
  this.interceptors = options.interceptors ?? [];
293
290
  }
294
291
  condition(schema) {
295
- return schema !== void 0 && schema["~standard"].vendor === "zod";
292
+ return schema !== void 0 && schema["~standard"].vendor === "zod" && "_zod" in schema;
296
293
  }
297
294
  convert(schema, options) {
298
- return this.#convert(schema, options, 0);
295
+ return this.#convert(schema, options, 0, 0);
299
296
  }
300
- #convert(schema, options, lazyDepth, isHandledCustomJSONSchema = false) {
297
+ #convert(schema, options, lazyDepth, structureDepth, isHandledCustomJSONSchema = false) {
301
298
  return intercept(
302
299
  this.interceptors,
303
300
  { schema, options, lazyDepth, isHandledCustomJSONSchema },
304
- async ({ schema: schema2, options: options2, lazyDepth: lazyDepth2, isHandledCustomJSONSchema: isHandledCustomJSONSchema2 }) => {
301
+ ({ schema: schema2, options: options2, lazyDepth: lazyDepth2, isHandledCustomJSONSchema: isHandledCustomJSONSchema2 }) => {
302
+ if (structureDepth > this.maxStructureDepth) {
303
+ return [false, this.anyJsonSchema];
304
+ }
305
+ if (!options2.minStructureDepthForRef || options2.minStructureDepthForRef <= structureDepth) {
306
+ const components = toArray(options2.components);
307
+ for (const component of components) {
308
+ if (component.schema === schema2 && component.allowedStrategies.includes(options2.strategy)) {
309
+ return [component.required, { $ref: component.ref }];
310
+ }
311
+ }
312
+ }
305
313
  if (!isHandledCustomJSONSchema2) {
306
314
  const customJSONSchema = this.#getCustomJsonSchema(schema2, options2);
307
315
  if (customJSONSchema) {
308
- const [required, json] = await this.#convert(schema2, options2, lazyDepth2, true);
316
+ const [required, json] = this.#convert(schema2, options2, lazyDepth2, structureDepth, true);
309
317
  return [required, { ...json, ...customJSONSchema }];
310
318
  }
311
319
  }
@@ -313,21 +321,28 @@ class experimental_ZodToJsonSchemaConverter {
313
321
  case "string": {
314
322
  const string = schema2;
315
323
  const json = { type: "string" };
316
- const { minimum, maximum, format, pattern, contentEncoding } = string._zod.computed;
317
- if (minimum !== void 0) {
324
+ const { minimum, maximum, format, patterns, contentEncoding } = string._zod.bag;
325
+ if (typeof minimum === "number") {
318
326
  json.minLength = minimum;
319
327
  }
320
- if (maximum !== void 0) {
328
+ if (typeof maximum === "number") {
321
329
  json.maxLength = maximum;
322
330
  }
323
- if (contentEncoding !== void 0) {
331
+ if (typeof contentEncoding === "string") {
324
332
  json.contentEncoding = this.#handleContentEncoding(contentEncoding);
325
333
  }
326
- if (format !== void 0 && format !== "regex" && json.contentEncoding === void 0) {
334
+ if (typeof format === "string" && format !== "regex" && json.contentEncoding === void 0) {
327
335
  json.format = this.#handleStringFormat(format);
328
336
  }
329
- if (pattern !== void 0 && json.contentEncoding === void 0 && json.format === void 0) {
330
- json.pattern = pattern.source;
337
+ if (patterns instanceof Set && json.contentEncoding === void 0 && json.format === void 0) {
338
+ for (const pattern of patterns) {
339
+ if (json.pattern === void 0) {
340
+ json.pattern = pattern.source;
341
+ } else {
342
+ json.allOf ??= [];
343
+ json.allOf.push({ pattern: pattern.source });
344
+ }
345
+ }
331
346
  }
332
347
  if (format === "jwt" && json.contentEncoding === void 0 && json.format === void 0 && json.pattern === void 0) {
333
348
  json.pattern = /^[\w-]+\.[\w-]+\.[\w-]+$/.source;
@@ -337,25 +352,23 @@ class experimental_ZodToJsonSchemaConverter {
337
352
  case "number": {
338
353
  const number = schema2;
339
354
  const json = { type: "number" };
340
- const { minimum, maximum, format, multipleOf, inclusive } = number._zod.computed;
341
- if (format?.includes("int")) {
355
+ const { minimum, maximum, format, multipleOf, exclusiveMaximum, exclusiveMinimum } = number._zod.bag;
356
+ if (typeof format === "string" && format?.includes("int")) {
342
357
  json.type = "integer";
343
358
  }
344
- if (minimum !== void 0) {
345
- if (inclusive) {
346
- json.minimum = minimum;
347
- } else {
348
- json.exclusiveMinimum = minimum;
349
- }
359
+ if (typeof minimum === "number") {
360
+ json.minimum = minimum;
350
361
  }
351
- if (maximum !== void 0) {
352
- if (inclusive) {
353
- json.maximum = maximum;
354
- } else {
355
- json.exclusiveMaximum = maximum;
356
- }
362
+ if (typeof maximum === "number") {
363
+ json.maximum = maximum;
364
+ }
365
+ if (typeof exclusiveMinimum === "number") {
366
+ json.exclusiveMinimum = exclusiveMinimum;
357
367
  }
358
- if (multipleOf !== void 0) {
368
+ if (typeof exclusiveMaximum === "number") {
369
+ json.exclusiveMaximum = exclusiveMaximum;
370
+ }
371
+ if (typeof multipleOf === "number") {
359
372
  json.multipleOf = multipleOf;
360
373
  }
361
374
  return [true, json];
@@ -364,10 +377,18 @@ class experimental_ZodToJsonSchemaConverter {
364
377
  return [true, { type: "boolean" }];
365
378
  }
366
379
  case "bigint": {
367
- return [true, { type: "string", pattern: "^-?[0-9]+$" }];
380
+ return [true, {
381
+ "type": "string",
382
+ "pattern": "^-?[0-9]+$",
383
+ "x-native-type": JsonSchemaXNativeType.BigInt
384
+ }];
368
385
  }
369
386
  case "date": {
370
- return [true, { type: "string", format: JSONSchemaFormat.DateTime }];
387
+ return [true, {
388
+ "type": "string",
389
+ "format": JSONSchemaFormat.DateTime,
390
+ "x-native-type": JsonSchemaXNativeType.Date
391
+ }];
371
392
  }
372
393
  case "null": {
373
394
  return [true, { type: "null" }];
@@ -388,21 +409,21 @@ class experimental_ZodToJsonSchemaConverter {
388
409
  case "array": {
389
410
  const array = schema2;
390
411
  const json = { type: "array" };
391
- const { minimum, maximum } = array._zod.computed;
392
- if (minimum !== void 0) {
412
+ const { minimum, maximum } = array._zod.bag;
413
+ if (typeof minimum === "number") {
393
414
  json.minItems = minimum;
394
415
  }
395
- if (maximum !== void 0) {
416
+ if (typeof maximum === "number") {
396
417
  json.maxItems = maximum;
397
418
  }
398
- json.items = this.#handleArrayItemJsonSchema(await this.#convert(array._zod.def.element, options2, lazyDepth2), options2);
419
+ json.items = this.#handleArrayItemJsonSchema(this.#convert(array._zod.def.element, options2, lazyDepth2, structureDepth + 1), options2);
399
420
  return [true, json];
400
421
  }
401
422
  case "object": {
402
423
  const object = schema2;
403
424
  const json = { type: "object" };
404
425
  for (const [key, value] of Object.entries(object._zod.def.shape)) {
405
- const [itemRequired, itemJson] = await this.#convert(value, options2, lazyDepth2);
426
+ const [itemRequired, itemJson] = this.#convert(value, options2, lazyDepth2, structureDepth + 1);
406
427
  json.properties ??= {};
407
428
  json.properties[key] = itemJson;
408
429
  if (itemRequired) {
@@ -414,7 +435,7 @@ class experimental_ZodToJsonSchemaConverter {
414
435
  if (object._zod.def.catchall._zod.def.type === "never") {
415
436
  json.additionalProperties = false;
416
437
  } else {
417
- const [_, addJson] = await this.#convert(object._zod.def.catchall, options2, lazyDepth2);
438
+ const [_, addJson] = this.#convert(object._zod.def.catchall, options2, lazyDepth2, structureDepth + 1);
418
439
  json.additionalProperties = addJson;
419
440
  }
420
441
  }
@@ -425,7 +446,7 @@ class experimental_ZodToJsonSchemaConverter {
425
446
  const anyOf = [];
426
447
  let required = true;
427
448
  for (const item of union._zod.def.options) {
428
- const [itemRequired, itemJson] = await this.#convert(item, options2, lazyDepth2);
449
+ const [itemRequired, itemJson] = this.#convert(item, options2, lazyDepth2, structureDepth);
429
450
  if (!itemRequired) {
430
451
  required = false;
431
452
  }
@@ -446,7 +467,7 @@ class experimental_ZodToJsonSchemaConverter {
446
467
  const json = { allOf: [] };
447
468
  let required = false;
448
469
  for (const item of [intersection._zod.def.left, intersection._zod.def.right]) {
449
- const [itemRequired, itemJson] = await this.#convert(item, options2, lazyDepth2);
470
+ const [itemRequired, itemJson] = this.#convert(item, options2, lazyDepth2, structureDepth);
450
471
  json.allOf.push(itemJson);
451
472
  if (itemRequired) {
452
473
  required = true;
@@ -458,16 +479,16 @@ class experimental_ZodToJsonSchemaConverter {
458
479
  const tuple = schema2;
459
480
  const json = { type: "array", prefixItems: [] };
460
481
  for (const item of tuple._zod.def.items) {
461
- json.prefixItems.push(this.#handleArrayItemJsonSchema(await this.#convert(item, options2, lazyDepth2), options2));
482
+ json.prefixItems.push(this.#handleArrayItemJsonSchema(this.#convert(item, options2, lazyDepth2, structureDepth + 1), options2));
462
483
  }
463
484
  if (tuple._zod.def.rest) {
464
- json.items = this.#handleArrayItemJsonSchema(await this.#convert(tuple._zod.def.rest, options2, lazyDepth2), options2);
485
+ json.items = this.#handleArrayItemJsonSchema(this.#convert(tuple._zod.def.rest, options2, lazyDepth2, structureDepth + 1), options2);
465
486
  }
466
- const { minimum, maximum } = tuple._zod.computed;
467
- if (minimum !== void 0) {
487
+ const { minimum, maximum } = tuple._zod.bag;
488
+ if (typeof minimum === "number") {
468
489
  json.minItems = minimum;
469
490
  }
470
- if (maximum !== void 0) {
491
+ if (typeof maximum === "number") {
471
492
  json.maxItems = maximum;
472
493
  }
473
494
  return [true, json];
@@ -475,31 +496,33 @@ class experimental_ZodToJsonSchemaConverter {
475
496
  case "record": {
476
497
  const record = schema2;
477
498
  const json = { type: "object" };
478
- json.propertyNames = (await this.#convert(record._zod.def.keyType, options2, lazyDepth2))[1];
479
- json.additionalProperties = (await this.#convert(record._zod.def.valueType, options2, lazyDepth2))[1];
499
+ json.propertyNames = this.#convert(record._zod.def.keyType, options2, lazyDepth2, structureDepth + 1)[1];
500
+ json.additionalProperties = this.#convert(record._zod.def.valueType, options2, lazyDepth2, structureDepth + 1)[1];
480
501
  return [true, json];
481
502
  }
482
503
  case "map": {
483
504
  const map = schema2;
484
505
  return [true, {
485
- type: "array",
486
- items: {
506
+ "type": "array",
507
+ "items": {
487
508
  type: "array",
488
509
  prefixItems: [
489
- this.#handleArrayItemJsonSchema(await this.#convert(map._zod.def.keyType, options2, lazyDepth2), options2),
490
- this.#handleArrayItemJsonSchema(await this.#convert(map._zod.def.valueType, options2, lazyDepth2), options2)
510
+ this.#handleArrayItemJsonSchema(this.#convert(map._zod.def.keyType, options2, lazyDepth2, structureDepth + 1), options2),
511
+ this.#handleArrayItemJsonSchema(this.#convert(map._zod.def.valueType, options2, lazyDepth2, structureDepth + 1), options2)
491
512
  ],
492
513
  maxItems: 2,
493
514
  minItems: 2
494
- }
515
+ },
516
+ "x-native-type": JsonSchemaXNativeType.Map
495
517
  }];
496
518
  }
497
519
  case "set": {
498
520
  const set = schema2;
499
521
  return [true, {
500
- type: "array",
501
- uniqueItems: true,
502
- items: this.#handleArrayItemJsonSchema(await this.#convert(set._zod.def.valueType, options2, lazyDepth2), options2)
522
+ "type": "array",
523
+ "uniqueItems": true,
524
+ "items": this.#handleArrayItemJsonSchema(this.#convert(set._zod.def.valueType, options2, lazyDepth2, structureDepth + 1), options2),
525
+ "x-native-type": JsonSchemaXNativeType.Set
503
526
  }];
504
527
  }
505
528
  case "enum": {
@@ -523,12 +546,14 @@ class experimental_ZodToJsonSchemaConverter {
523
546
  case "file": {
524
547
  const file = schema2;
525
548
  const oneOf = [];
526
- const { mime } = file._zod.computed;
527
- for (const type of mime ?? ["*/*"]) {
528
- oneOf.push({
529
- type: "string",
530
- contentMediaType: type
531
- });
549
+ const { mime } = file._zod.bag;
550
+ if (mime === void 0 || Array.isArray(mime) && mime.every((m) => typeof m === "string")) {
551
+ for (const type of mime ?? ["*/*"]) {
552
+ oneOf.push({
553
+ type: "string",
554
+ contentMediaType: type
555
+ });
556
+ }
532
557
  }
533
558
  return [true, oneOf.length === 1 ? oneOf[0] : { anyOf: oneOf }];
534
559
  }
@@ -537,40 +562,40 @@ class experimental_ZodToJsonSchemaConverter {
537
562
  }
538
563
  case "nullable": {
539
564
  const nullable = schema2;
540
- const [required, json] = await this.#convert(nullable._zod.def.innerType, options2, lazyDepth2);
565
+ const [required, json] = this.#convert(nullable._zod.def.innerType, options2, lazyDepth2, structureDepth);
541
566
  return [required, { anyOf: [json, { type: "null" }] }];
542
567
  }
543
568
  case "nonoptional": {
544
569
  const nonoptional = schema2;
545
- const [, json] = await this.#convert(nonoptional._zod.def.innerType, options2, lazyDepth2);
570
+ const [, json] = this.#convert(nonoptional._zod.def.innerType, options2, lazyDepth2, structureDepth);
546
571
  return [true, json];
547
572
  }
548
573
  case "success": {
549
574
  return [true, { type: "boolean" }];
550
575
  }
551
- case "default": {
576
+ case "default":
577
+ case "prefault": {
552
578
  const default_ = schema2;
553
- const [, json] = await this.#convert(default_._zod.def.innerType, options2, lazyDepth2);
579
+ const [, json] = this.#convert(default_._zod.def.innerType, options2, lazyDepth2, structureDepth);
554
580
  return [false, {
555
581
  ...json,
556
- default: default_._zod.def.defaultValue()
582
+ default: default_._zod.def.defaultValue
557
583
  }];
558
584
  }
559
585
  case "catch": {
560
586
  const catch_ = schema2;
561
- const [, json] = await this.#convert(catch_._zod.def.innerType, options2, lazyDepth2);
562
- return [false, json];
587
+ return this.#convert(catch_._zod.def.innerType, options2, lazyDepth2, structureDepth);
563
588
  }
564
589
  case "nan": {
565
590
  return [true, options2.strategy === "input" ? this.unsupportedJsonSchema : { type: "null" }];
566
591
  }
567
592
  case "pipe": {
568
593
  const pipe = schema2;
569
- return await this.#convert(options2.strategy === "input" ? pipe._zod.def.in : pipe._zod.def.out, options2, lazyDepth2);
594
+ return this.#convert(options2.strategy === "input" ? pipe._zod.def.in : pipe._zod.def.out, options2, lazyDepth2, structureDepth);
570
595
  }
571
596
  case "readonly": {
572
597
  const readonly_ = schema2;
573
- const [required, json] = await this.#convert(readonly_._zod.def.innerType, options2, lazyDepth2);
598
+ const [required, json] = this.#convert(readonly_._zod.def.innerType, options2, lazyDepth2, structureDepth);
574
599
  return [required, { ...json, readOnly: true }];
575
600
  }
576
601
  case "template_literal": {
@@ -582,15 +607,16 @@ class experimental_ZodToJsonSchemaConverter {
582
607
  }
583
608
  case "optional": {
584
609
  const optional = schema2;
585
- const [, json] = await this.#convert(optional._zod.def.innerType, options2, lazyDepth2);
610
+ const [, json] = this.#convert(optional._zod.def.innerType, options2, lazyDepth2, structureDepth);
586
611
  return [false, json];
587
612
  }
588
613
  case "lazy": {
589
614
  const lazy = schema2;
590
- if (lazyDepth2 >= this.maxLazyDepth) {
615
+ const currentLazyDepth = lazyDepth2 + 1;
616
+ if (currentLazyDepth > this.maxLazyDepth) {
591
617
  return [false, this.anyJsonSchema];
592
618
  }
593
- return await this.#convert(lazy._zod.def.getter(), options2, lazyDepth2 + 1);
619
+ return this.#convert(lazy._zod.def.getter(), options2, currentLazyDepth, structureDepth);
594
620
  }
595
621
  default: {
596
622
  schema2._zod.def.type;
@@ -601,25 +627,26 @@ class experimental_ZodToJsonSchemaConverter {
601
627
  );
602
628
  }
603
629
  #getCustomJsonSchema(schema, options) {
604
- if (options.strategy === "input" && experimental_JSON_SCHEMA_INPUT_REGISTRY.has(schema)) {
605
- return experimental_JSON_SCHEMA_INPUT_REGISTRY.get(schema);
630
+ if (options.strategy === "input" && JSON_SCHEMA_INPUT_REGISTRY.has(schema)) {
631
+ return JSON_SCHEMA_INPUT_REGISTRY.get(schema);
606
632
  }
607
- if (options.strategy === "output" && experimental_JSON_SCHEMA_OUTPUT_REGISTRY.has(schema)) {
608
- return experimental_JSON_SCHEMA_OUTPUT_REGISTRY.get(schema);
633
+ if (options.strategy === "output" && JSON_SCHEMA_OUTPUT_REGISTRY.has(schema)) {
634
+ return JSON_SCHEMA_OUTPUT_REGISTRY.get(schema);
609
635
  }
610
- if (experimental_JSON_SCHEMA_REGISTRY.has(schema)) {
611
- return experimental_JSON_SCHEMA_REGISTRY.get(schema);
636
+ if (JSON_SCHEMA_REGISTRY.has(schema)) {
637
+ return JSON_SCHEMA_REGISTRY.get(schema);
612
638
  }
613
639
  const global = globalRegistry.get(schema);
614
640
  if (global) {
615
641
  return {
642
+ title: global.title,
616
643
  description: global.description,
617
- examples: global.examples
644
+ examples: Array.isArray(global.examples) ? global.examples : void 0
618
645
  };
619
646
  }
620
647
  }
621
648
  #handleArrayItemJsonSchema([required, schema], options) {
622
- if (required || options.strategy === "input") {
649
+ if (required || options.strategy === "input" || schema.default !== void 0) {
623
650
  return schema;
624
651
  }
625
652
  if (schema === this.undefinedJsonSchema) {
@@ -650,4 +677,4 @@ class experimental_ZodToJsonSchemaConverter {
650
677
  }
651
678
  }
652
679
 
653
- export { experimental_JSON_SCHEMA_INPUT_REGISTRY, experimental_JSON_SCHEMA_OUTPUT_REGISTRY, experimental_JSON_SCHEMA_REGISTRY, experimental_ZodSmartCoercionPlugin, experimental_ZodToJsonSchemaConverter };
680
+ export { JSON_SCHEMA_INPUT_REGISTRY, JSON_SCHEMA_OUTPUT_REGISTRY, JSON_SCHEMA_REGISTRY, ZodToJsonSchemaConverter, experimental_ZodSmartCoercionPlugin };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/zod",
3
3
  "type": "module",
4
- "version": "0.0.0-next.cac5dd3",
4
+ "version": "0.0.0-next.caeb672",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -29,31 +29,19 @@
29
29
  "dist"
30
30
  ],
31
31
  "peerDependencies": {
32
- "@zod/core": ">=0.11.4",
33
- "zod": ">=3.24.2",
34
- "@orpc/server": "0.0.0-next.cac5dd3",
35
- "@orpc/contract": "0.0.0-next.cac5dd3"
36
- },
37
- "peerDependenciesMeta": {
38
- "@zod/core": {
39
- "optional": true
40
- },
41
- "zod": {
42
- "optional": true
43
- }
32
+ "zod": ">=3.25.0",
33
+ "@orpc/contract": "0.0.0-next.caeb672",
34
+ "@orpc/server": "0.0.0-next.caeb672"
44
35
  },
45
36
  "dependencies": {
46
37
  "escape-string-regexp": "^5.0.0",
47
38
  "wildcard-match": "^5.1.3",
48
- "@orpc/shared": "0.0.0-next.cac5dd3",
49
- "@orpc/openapi": "0.0.0-next.cac5dd3"
39
+ "@orpc/json-schema": "0.0.0-next.caeb672",
40
+ "@orpc/shared": "0.0.0-next.caeb672",
41
+ "@orpc/openapi": "0.0.0-next.caeb672"
50
42
  },
51
43
  "devDependencies": {
52
- "@zod/core": "^0.11.4",
53
- "@zod/mini": "^4.0.0-beta.20250505T012514",
54
- "zod": "^3.24.2",
55
- "zod-to-json-schema": "^3.24.5",
56
- "zod4": "npm:zod@^4.0.0-beta.20250505T012514"
44
+ "zod": "^4.0.5"
57
45
  },
58
46
  "scripts": {
59
47
  "build": "unbuild",