@orpc/zod 0.0.0-next.b6b0cc3 → 0.0.0-next.b6b8746

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
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
4
  import { registry, globalRegistry } from 'zod/v4/core';
4
5
 
@@ -7,7 +8,7 @@ class experimental_ZodSmartCoercionPlugin {
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
  }
@@ -255,64 +267,53 @@ class experimental_ZodSmartCoercionPlugin {
255
267
  }
256
268
  return value;
257
269
  }
258
- /**
259
- * This function is inspired from Zod, because it's not exported
260
- * https://github.com/colinhacks/zod/blob/v4/packages/core/src/schemas.ts#L1903C1-L1921C2
261
- */
262
- #matchDiscriminators(input, discs) {
263
- for (const [key, value] of discs) {
264
- const data = input[key];
265
- if (value.values.size && !value.values.has(data)) {
266
- return false;
267
- }
268
- if (value.maps.length === 0) {
269
- continue;
270
- }
271
- if (!isObject(data)) {
272
- return false;
273
- }
274
- for (const m of value.maps) {
275
- if (!this.#matchDiscriminators(data, m)) {
276
- return false;
277
- }
278
- }
279
- }
280
- return true;
281
- }
282
270
  }
283
271
 
284
- const experimental_JSON_SCHEMA_REGISTRY = registry();
285
- const experimental_JSON_SCHEMA_INPUT_REGISTRY = registry();
286
- 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();
287
275
 
288
- class experimental_ZodToJsonSchemaConverter {
276
+ class ZodToJsonSchemaConverter {
289
277
  maxLazyDepth;
278
+ maxStructureDepth;
290
279
  anyJsonSchema;
291
280
  unsupportedJsonSchema;
292
281
  undefinedJsonSchema;
293
282
  interceptors;
294
283
  constructor(options = {}) {
295
284
  this.maxLazyDepth = options.maxLazyDepth ?? 2;
285
+ this.maxStructureDepth = options.maxStructureDepth ?? 10;
296
286
  this.anyJsonSchema = options.anyJsonSchema ?? {};
297
287
  this.unsupportedJsonSchema = options.unsupportedJsonSchema ?? { not: {} };
298
288
  this.undefinedJsonSchema = options.undefinedJsonSchema ?? { not: {} };
299
289
  this.interceptors = options.interceptors ?? [];
300
290
  }
301
291
  condition(schema) {
302
- return schema !== void 0 && schema["~standard"].vendor === "zod";
292
+ return schema !== void 0 && schema["~standard"].vendor === "zod" && "_zod" in schema;
303
293
  }
304
294
  convert(schema, options) {
305
- return this.#convert(schema, options, 0);
295
+ return this.#convert(schema, options, 0, 0);
306
296
  }
307
- #convert(schema, options, lazyDepth, isHandledCustomJSONSchema = false) {
297
+ #convert(schema, options, lazyDepth, structureDepth, isHandledCustomJSONSchema = false) {
308
298
  return intercept(
309
299
  this.interceptors,
310
300
  { schema, options, lazyDepth, isHandledCustomJSONSchema },
311
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
+ }
312
313
  if (!isHandledCustomJSONSchema2) {
313
314
  const customJSONSchema = this.#getCustomJsonSchema(schema2, options2);
314
315
  if (customJSONSchema) {
315
- const [required, json] = this.#convert(schema2, options2, lazyDepth2, true);
316
+ const [required, json] = this.#convert(schema2, options2, lazyDepth2, structureDepth, true);
316
317
  return [required, { ...json, ...customJSONSchema }];
317
318
  }
318
319
  }
@@ -320,7 +321,7 @@ class experimental_ZodToJsonSchemaConverter {
320
321
  case "string": {
321
322
  const string = schema2;
322
323
  const json = { type: "string" };
323
- const { minimum, maximum, format, pattern, contentEncoding } = string._zod.bag;
324
+ const { minimum, maximum, format, patterns, contentEncoding } = string._zod.bag;
324
325
  if (typeof minimum === "number") {
325
326
  json.minLength = minimum;
326
327
  }
@@ -333,8 +334,15 @@ class experimental_ZodToJsonSchemaConverter {
333
334
  if (typeof format === "string" && format !== "regex" && json.contentEncoding === void 0) {
334
335
  json.format = this.#handleStringFormat(format);
335
336
  }
336
- if (pattern instanceof RegExp && json.contentEncoding === void 0 && json.format === void 0) {
337
- 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
+ }
338
346
  }
339
347
  if (format === "jwt" && json.contentEncoding === void 0 && json.format === void 0 && json.pattern === void 0) {
340
348
  json.pattern = /^[\w-]+\.[\w-]+\.[\w-]+$/.source;
@@ -369,10 +377,18 @@ class experimental_ZodToJsonSchemaConverter {
369
377
  return [true, { type: "boolean" }];
370
378
  }
371
379
  case "bigint": {
372
- return [true, { type: "string", pattern: "^-?[0-9]+$" }];
380
+ return [true, {
381
+ "type": "string",
382
+ "pattern": "^-?[0-9]+$",
383
+ "x-native-type": JsonSchemaXNativeType.BigInt
384
+ }];
373
385
  }
374
386
  case "date": {
375
- return [true, { type: "string", format: JSONSchemaFormat.DateTime }];
387
+ return [true, {
388
+ "type": "string",
389
+ "format": JSONSchemaFormat.DateTime,
390
+ "x-native-type": JsonSchemaXNativeType.Date
391
+ }];
376
392
  }
377
393
  case "null": {
378
394
  return [true, { type: "null" }];
@@ -400,14 +416,14 @@ class experimental_ZodToJsonSchemaConverter {
400
416
  if (typeof maximum === "number") {
401
417
  json.maxItems = maximum;
402
418
  }
403
- json.items = this.#handleArrayItemJsonSchema(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);
404
420
  return [true, json];
405
421
  }
406
422
  case "object": {
407
423
  const object = schema2;
408
424
  const json = { type: "object" };
409
425
  for (const [key, value] of Object.entries(object._zod.def.shape)) {
410
- const [itemRequired, itemJson] = this.#convert(value, options2, lazyDepth2);
426
+ const [itemRequired, itemJson] = this.#convert(value, options2, lazyDepth2, structureDepth + 1);
411
427
  json.properties ??= {};
412
428
  json.properties[key] = itemJson;
413
429
  if (itemRequired) {
@@ -419,7 +435,7 @@ class experimental_ZodToJsonSchemaConverter {
419
435
  if (object._zod.def.catchall._zod.def.type === "never") {
420
436
  json.additionalProperties = false;
421
437
  } else {
422
- const [_, addJson] = this.#convert(object._zod.def.catchall, options2, lazyDepth2);
438
+ const [_, addJson] = this.#convert(object._zod.def.catchall, options2, lazyDepth2, structureDepth + 1);
423
439
  json.additionalProperties = addJson;
424
440
  }
425
441
  }
@@ -430,7 +446,7 @@ class experimental_ZodToJsonSchemaConverter {
430
446
  const anyOf = [];
431
447
  let required = true;
432
448
  for (const item of union._zod.def.options) {
433
- const [itemRequired, itemJson] = this.#convert(item, options2, lazyDepth2);
449
+ const [itemRequired, itemJson] = this.#convert(item, options2, lazyDepth2, structureDepth + 1);
434
450
  if (!itemRequired) {
435
451
  required = false;
436
452
  }
@@ -444,14 +460,14 @@ class experimental_ZodToJsonSchemaConverter {
444
460
  }
445
461
  }
446
462
  }
447
- return [required, anyOf.length === 1 ? anyOf[0] : { anyOf }];
463
+ return [required, { anyOf }];
448
464
  }
449
465
  case "intersection": {
450
466
  const intersection = schema2;
451
467
  const json = { allOf: [] };
452
468
  let required = false;
453
469
  for (const item of [intersection._zod.def.left, intersection._zod.def.right]) {
454
- const [itemRequired, itemJson] = this.#convert(item, options2, lazyDepth2);
470
+ const [itemRequired, itemJson] = this.#convert(item, options2, lazyDepth2, structureDepth + 1);
455
471
  json.allOf.push(itemJson);
456
472
  if (itemRequired) {
457
473
  required = true;
@@ -463,10 +479,10 @@ class experimental_ZodToJsonSchemaConverter {
463
479
  const tuple = schema2;
464
480
  const json = { type: "array", prefixItems: [] };
465
481
  for (const item of tuple._zod.def.items) {
466
- json.prefixItems.push(this.#handleArrayItemJsonSchema(this.#convert(item, options2, lazyDepth2), options2));
482
+ json.prefixItems.push(this.#handleArrayItemJsonSchema(this.#convert(item, options2, lazyDepth2, structureDepth + 1), options2));
467
483
  }
468
484
  if (tuple._zod.def.rest) {
469
- json.items = this.#handleArrayItemJsonSchema(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);
470
486
  }
471
487
  const { minimum, maximum } = tuple._zod.bag;
472
488
  if (typeof minimum === "number") {
@@ -480,36 +496,45 @@ class experimental_ZodToJsonSchemaConverter {
480
496
  case "record": {
481
497
  const record = schema2;
482
498
  const json = { type: "object" };
483
- json.propertyNames = this.#convert(record._zod.def.keyType, options2, lazyDepth2)[1];
484
- json.additionalProperties = 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];
485
501
  return [true, json];
486
502
  }
487
503
  case "map": {
488
504
  const map = schema2;
489
505
  return [true, {
490
- type: "array",
491
- items: {
506
+ "type": "array",
507
+ "items": {
492
508
  type: "array",
493
509
  prefixItems: [
494
- this.#handleArrayItemJsonSchema(this.#convert(map._zod.def.keyType, options2, lazyDepth2), options2),
495
- this.#handleArrayItemJsonSchema(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)
496
512
  ],
497
513
  maxItems: 2,
498
514
  minItems: 2
499
- }
515
+ },
516
+ "x-native-type": JsonSchemaXNativeType.Map
500
517
  }];
501
518
  }
502
519
  case "set": {
503
520
  const set = schema2;
504
521
  return [true, {
505
- type: "array",
506
- uniqueItems: true,
507
- items: this.#handleArrayItemJsonSchema(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
508
526
  }];
509
527
  }
510
528
  case "enum": {
511
529
  const enum_ = schema2;
512
- return [true, { enum: Object.values(enum_._zod.def.entries) }];
530
+ const values = getEnumValues(enum_._zod.def.entries);
531
+ const json = { enum: values };
532
+ if (values.every((v) => typeof v === "string")) {
533
+ json.type = "string";
534
+ } else if (values.every((v) => Number.isFinite(v))) {
535
+ json.type = "number";
536
+ }
537
+ return [true, json];
513
538
  }
514
539
  case "literal": {
515
540
  const literal = schema2;
@@ -544,12 +569,12 @@ class experimental_ZodToJsonSchemaConverter {
544
569
  }
545
570
  case "nullable": {
546
571
  const nullable = schema2;
547
- const [required, json] = this.#convert(nullable._zod.def.innerType, options2, lazyDepth2);
572
+ const [required, json] = this.#convert(nullable._zod.def.innerType, options2, lazyDepth2, structureDepth);
548
573
  return [required, { anyOf: [json, { type: "null" }] }];
549
574
  }
550
575
  case "nonoptional": {
551
576
  const nonoptional = schema2;
552
- const [, json] = this.#convert(nonoptional._zod.def.innerType, options2, lazyDepth2);
577
+ const [, json] = this.#convert(nonoptional._zod.def.innerType, options2, lazyDepth2, structureDepth);
553
578
  return [true, json];
554
579
  }
555
580
  case "success": {
@@ -558,7 +583,7 @@ class experimental_ZodToJsonSchemaConverter {
558
583
  case "default":
559
584
  case "prefault": {
560
585
  const default_ = schema2;
561
- const [, json] = this.#convert(default_._zod.def.innerType, options2, lazyDepth2);
586
+ const [, json] = this.#convert(default_._zod.def.innerType, options2, lazyDepth2, structureDepth);
562
587
  return [false, {
563
588
  ...json,
564
589
  default: default_._zod.def.defaultValue
@@ -566,18 +591,24 @@ class experimental_ZodToJsonSchemaConverter {
566
591
  }
567
592
  case "catch": {
568
593
  const catch_ = schema2;
569
- return this.#convert(catch_._zod.def.innerType, options2, lazyDepth2);
594
+ return this.#convert(catch_._zod.def.innerType, options2, lazyDepth2, structureDepth);
570
595
  }
571
596
  case "nan": {
572
597
  return [true, options2.strategy === "input" ? this.unsupportedJsonSchema : { type: "null" }];
573
598
  }
574
599
  case "pipe": {
575
600
  const pipe = schema2;
576
- return this.#convert(options2.strategy === "input" ? pipe._zod.def.in : pipe._zod.def.out, options2, lazyDepth2);
601
+ return this.#convert(
602
+ // prefer out schema when in schema is preprocess/transform
603
+ options2.strategy === "input" && pipe._zod.def.in._zod.def.type !== "transform" ? pipe._zod.def.in : pipe._zod.def.out,
604
+ options2,
605
+ lazyDepth2,
606
+ structureDepth
607
+ );
577
608
  }
578
609
  case "readonly": {
579
610
  const readonly_ = schema2;
580
- const [required, json] = this.#convert(readonly_._zod.def.innerType, options2, lazyDepth2);
611
+ const [required, json] = this.#convert(readonly_._zod.def.innerType, options2, lazyDepth2, structureDepth);
581
612
  return [required, { ...json, readOnly: true }];
582
613
  }
583
614
  case "template_literal": {
@@ -589,15 +620,16 @@ class experimental_ZodToJsonSchemaConverter {
589
620
  }
590
621
  case "optional": {
591
622
  const optional = schema2;
592
- const [, json] = this.#convert(optional._zod.def.innerType, options2, lazyDepth2);
623
+ const [, json] = this.#convert(optional._zod.def.innerType, options2, lazyDepth2, structureDepth);
593
624
  return [false, json];
594
625
  }
595
626
  case "lazy": {
596
627
  const lazy = schema2;
597
- if (lazyDepth2 >= this.maxLazyDepth) {
628
+ const currentLazyDepth = lazyDepth2 + 1;
629
+ if (currentLazyDepth > this.maxLazyDepth) {
598
630
  return [false, this.anyJsonSchema];
599
631
  }
600
- return this.#convert(lazy._zod.def.getter(), options2, lazyDepth2 + 1);
632
+ return this.#convert(lazy._zod.def.getter(), options2, currentLazyDepth, structureDepth);
601
633
  }
602
634
  default: {
603
635
  schema2._zod.def.type;
@@ -608,20 +640,21 @@ class experimental_ZodToJsonSchemaConverter {
608
640
  );
609
641
  }
610
642
  #getCustomJsonSchema(schema, options) {
611
- if (options.strategy === "input" && experimental_JSON_SCHEMA_INPUT_REGISTRY.has(schema)) {
612
- return experimental_JSON_SCHEMA_INPUT_REGISTRY.get(schema);
643
+ if (options.strategy === "input" && JSON_SCHEMA_INPUT_REGISTRY.has(schema)) {
644
+ return JSON_SCHEMA_INPUT_REGISTRY.get(schema);
613
645
  }
614
- if (options.strategy === "output" && experimental_JSON_SCHEMA_OUTPUT_REGISTRY.has(schema)) {
615
- return experimental_JSON_SCHEMA_OUTPUT_REGISTRY.get(schema);
646
+ if (options.strategy === "output" && JSON_SCHEMA_OUTPUT_REGISTRY.has(schema)) {
647
+ return JSON_SCHEMA_OUTPUT_REGISTRY.get(schema);
616
648
  }
617
- if (experimental_JSON_SCHEMA_REGISTRY.has(schema)) {
618
- return experimental_JSON_SCHEMA_REGISTRY.get(schema);
649
+ if (JSON_SCHEMA_REGISTRY.has(schema)) {
650
+ return JSON_SCHEMA_REGISTRY.get(schema);
619
651
  }
620
652
  const global = globalRegistry.get(schema);
621
653
  if (global) {
622
654
  return {
655
+ title: global.title,
623
656
  description: global.description,
624
- examples: global.examples
657
+ examples: Array.isArray(global.examples) ? global.examples : void 0
625
658
  };
626
659
  }
627
660
  }
@@ -656,5 +689,10 @@ class experimental_ZodToJsonSchemaConverter {
656
689
  return Object.values(JSONSchemaContentEncoding).includes(contentEncoding) ? contentEncoding : void 0;
657
690
  }
658
691
  }
692
+ function getEnumValues(entries) {
693
+ const numericValues = Object.values(entries).filter((v) => typeof v === "number");
694
+ const values = Object.entries(entries).filter(([k, _]) => !numericValues.includes(+k)).map(([_, v]) => v);
695
+ return values;
696
+ }
659
697
 
660
- export { experimental_JSON_SCHEMA_INPUT_REGISTRY, experimental_JSON_SCHEMA_OUTPUT_REGISTRY, experimental_JSON_SCHEMA_REGISTRY, experimental_ZodSmartCoercionPlugin, experimental_ZodToJsonSchemaConverter };
698
+ export { JSON_SCHEMA_INPUT_REGISTRY, JSON_SCHEMA_OUTPUT_REGISTRY, JSON_SCHEMA_REGISTRY, ZodToJsonSchemaConverter, experimental_ZodSmartCoercionPlugin };
package/package.json CHANGED
@@ -1,16 +1,15 @@
1
1
  {
2
2
  "name": "@orpc/zod",
3
3
  "type": "module",
4
- "version": "0.0.0-next.b6b0cc3",
4
+ "version": "0.0.0-next.b6b8746",
5
5
  "license": "MIT",
6
- "homepage": "https://orpc.unnoq.com",
6
+ "homepage": "https://orpc.dev",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/unnoq/orpc.git",
9
+ "url": "git+https://github.com/middleapi/orpc.git",
10
10
  "directory": "packages/zod"
11
11
  },
12
12
  "keywords": [
13
- "unnoq",
14
13
  "orpc"
15
14
  ],
16
15
  "exports": {
@@ -29,19 +28,19 @@
29
28
  "dist"
30
29
  ],
31
30
  "peerDependencies": {
32
- "zod": ">=3.24.2",
33
- "@orpc/contract": "0.0.0-next.b6b0cc3",
34
- "@orpc/server": "0.0.0-next.b6b0cc3"
31
+ "zod": ">=3.25.0",
32
+ "@orpc/contract": "0.0.0-next.b6b8746",
33
+ "@orpc/server": "0.0.0-next.b6b8746"
35
34
  },
36
35
  "dependencies": {
37
36
  "escape-string-regexp": "^5.0.0",
38
- "wildcard-match": "^5.1.3",
39
- "@orpc/openapi": "0.0.0-next.b6b0cc3",
40
- "@orpc/shared": "0.0.0-next.b6b0cc3"
37
+ "wildcard-match": "^5.1.4",
38
+ "@orpc/openapi": "0.0.0-next.b6b8746",
39
+ "@orpc/shared": "0.0.0-next.b6b8746",
40
+ "@orpc/json-schema": "0.0.0-next.b6b8746"
41
41
  },
42
42
  "devDependencies": {
43
- "zod": "^3.25.11",
44
- "zod-to-json-schema": "^3.24.5"
43
+ "zod": "^4.3.6"
45
44
  },
46
45
  "scripts": {
47
46
  "build": "unbuild",