zod 4.1.1 → 4.1.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod",
3
- "version": "4.1.1",
3
+ "version": "4.1.3",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -27,6 +27,7 @@ export {
27
27
  flattenError,
28
28
  toJSONSchema,
29
29
  TimePrecision,
30
+ util,
30
31
  NEVER,
31
32
  } from "../core/index.js";
32
33
 
@@ -2,7 +2,7 @@ import { expect, test } from "vitest";
2
2
  import * as z from "zod/v4";
3
3
 
4
4
  // ============================================================================
5
- // Number/BigInt Codecs
5
+ // stringToNumber
6
6
  // ============================================================================
7
7
 
8
8
  const stringToNumber = () =>
@@ -11,128 +11,6 @@ const stringToNumber = () =>
11
11
  encode: (num) => num.toString(),
12
12
  });
13
13
 
14
- const stringToInt = () =>
15
- z.codec(z.string(), z.int(), {
16
- decode: (str) => Number.parseInt(str, 10),
17
- encode: (num) => num.toString(),
18
- });
19
-
20
- const stringToBigInt = () =>
21
- z.codec(z.string(), z.bigint(), {
22
- decode: (str) => BigInt(str),
23
- encode: (bigint) => bigint.toString(),
24
- });
25
-
26
- const numberToBigInt = () =>
27
- z.codec(z.int(), z.bigint(), {
28
- decode: (num) => BigInt(num),
29
- encode: (bigint) => Number(bigint),
30
- });
31
-
32
- // ============================================================================
33
- // Date/Duration Codecs
34
- // ============================================================================
35
-
36
- const isoDatetimeToDate = () =>
37
- z.codec(z.iso.datetime(), z.date(), {
38
- decode: (isoString) => new Date(isoString),
39
- encode: (date) => date.toISOString(),
40
- });
41
-
42
- const epochSecondsToDate = () =>
43
- z.codec(z.int().min(0), z.date(), {
44
- decode: (seconds) => new Date(seconds * 1000),
45
- encode: (date) => Math.floor(date.getTime() / 1000),
46
- });
47
-
48
- const epochMillisToDate = () =>
49
- z.codec(z.int().min(0), z.date(), {
50
- decode: (millis) => new Date(millis),
51
- encode: (date) => date.getTime(),
52
- });
53
-
54
- // ============================================================================
55
- // JSON Codec
56
- // ============================================================================
57
-
58
- const json = <T extends z.ZodTypeAny>(schema: T) =>
59
- z.codec(z.string(), schema, {
60
- decode: (jsonString) => JSON.parse(jsonString),
61
- encode: (value) => JSON.stringify(value),
62
- });
63
-
64
- // ============================================================================
65
- // Text/Bytes Codecs
66
- // ============================================================================
67
-
68
- const utf8ToBytes = () =>
69
- z.codec(z.string(), z.instanceof(Uint8Array), {
70
- decode: (str) => new TextEncoder().encode(str),
71
- encode: (bytes) => new TextDecoder().decode(bytes),
72
- });
73
-
74
- const bytesToUtf8 = () =>
75
- z.codec(z.instanceof(Uint8Array), z.string(), {
76
- decode: (bytes) => new TextDecoder().decode(bytes),
77
- encode: (str) => new TextEncoder().encode(str),
78
- });
79
-
80
- // ============================================================================
81
- // Binary-to-text Codecs
82
- // ============================================================================
83
-
84
- // Using utility functions from z.core.util
85
-
86
- const base64 = () =>
87
- z.codec(z.base64(), z.instanceof(Uint8Array), {
88
- decode: (base64String) => z.core.util.base64ToUint8Array(base64String),
89
- encode: (bytes) => z.core.util.uint8ArrayToBase64(bytes),
90
- });
91
-
92
- const base64urlToBytes = () =>
93
- z.codec(z.base64url(), z.instanceof(Uint8Array), {
94
- decode: (base64urlString) => z.core.util.base64urlToUint8Array(base64urlString),
95
- encode: (bytes) => z.core.util.uint8ArrayToBase64url(bytes),
96
- });
97
-
98
- const hexToBytes = () =>
99
- z.codec(z.hex(), z.instanceof(Uint8Array), {
100
- decode: (hexString) => z.core.util.hexToUint8Array(hexString),
101
- encode: (bytes) => z.core.util.uint8ArrayToHex(bytes),
102
- });
103
-
104
- // ============================================================================
105
- // URL Codecs
106
- // ============================================================================
107
-
108
- const stringToURL = () =>
109
- z.codec(z.url(), z.instanceof(URL), {
110
- decode: (urlString) => new URL(urlString),
111
- encode: (url) => url.href,
112
- });
113
-
114
- const stringToHttpURL = () =>
115
- z.codec(z.httpUrl(), z.instanceof(URL), {
116
- decode: (urlString) => new URL(urlString),
117
- encode: (url) => url.href,
118
- });
119
-
120
- const uriComponent = () =>
121
- z.codec(z.string(), z.string(), {
122
- decode: (encodedString) => decodeURIComponent(encodedString),
123
- encode: (decodedString) => encodeURIComponent(decodedString),
124
- });
125
-
126
- // ============================================================================
127
- // Boolean Codec
128
- // ============================================================================
129
-
130
- const stringToBoolean = (options?: { truthy?: string[]; falsy?: string[] }) => z.stringbool(options);
131
-
132
- // ============================================================================
133
- // Tests
134
- // ============================================================================
135
-
136
14
  test("stringToNumber codec", () => {
137
15
  const codec = stringToNumber();
138
16
 
@@ -152,6 +30,16 @@ test("stringToNumber codec", () => {
152
30
  expect(roundTrip).toBe("3.14159");
153
31
  });
154
32
 
33
+ // ============================================================================
34
+ // stringToInt
35
+ // ============================================================================
36
+
37
+ const stringToInt = () =>
38
+ z.codec(z.string(), z.int(), {
39
+ decode: (str) => Number.parseInt(str, 10),
40
+ encode: (num) => num.toString(),
41
+ });
42
+
155
43
  test("stringToInt codec", () => {
156
44
  const codec = stringToInt();
157
45
 
@@ -171,6 +59,16 @@ test("stringToInt codec", () => {
171
59
  expect(roundTrip).toBe("999");
172
60
  });
173
61
 
62
+ // ============================================================================
63
+ // stringToBigInt
64
+ // ============================================================================
65
+
66
+ const stringToBigInt = () =>
67
+ z.codec(z.string(), z.bigint(), {
68
+ decode: (str) => BigInt(str),
69
+ encode: (bigint) => bigint.toString(),
70
+ });
71
+
174
72
  test("stringToBigInt codec", () => {
175
73
  const codec = stringToBigInt();
176
74
 
@@ -190,6 +88,16 @@ test("stringToBigInt codec", () => {
190
88
  expect(roundTrip).toBe("987654321098765432109876543210");
191
89
  });
192
90
 
91
+ // ============================================================================
92
+ // numberToBigInt
93
+ // ============================================================================
94
+
95
+ const numberToBigInt = () =>
96
+ z.codec(z.int(), z.bigint(), {
97
+ decode: (num) => BigInt(num),
98
+ encode: (bigint) => Number(bigint),
99
+ });
100
+
193
101
  test("numberToBigInt codec", () => {
194
102
  const codec = numberToBigInt();
195
103
 
@@ -209,6 +117,16 @@ test("numberToBigInt codec", () => {
209
117
  expect(roundTrip).toBe(999);
210
118
  });
211
119
 
120
+ // ============================================================================
121
+ // isoDatetimeToDate
122
+ // ============================================================================
123
+
124
+ const isoDatetimeToDate = () =>
125
+ z.codec(z.iso.datetime(), z.date(), {
126
+ decode: (isoString) => new Date(isoString),
127
+ encode: (date) => date.toISOString(),
128
+ });
129
+
212
130
  test("isoDatetimeToDate codec", () => {
213
131
  const codec = isoDatetimeToDate();
214
132
 
@@ -227,6 +145,16 @@ test("isoDatetimeToDate codec", () => {
227
145
  expect(roundTrip).toBe("2024-12-25T15:45:30.123Z");
228
146
  });
229
147
 
148
+ // ============================================================================
149
+ // epochSecondsToDate
150
+ // ============================================================================
151
+
152
+ const epochSecondsToDate = () =>
153
+ z.codec(z.int().min(0), z.date(), {
154
+ decode: (seconds) => new Date(seconds * 1000),
155
+ encode: (date) => Math.floor(date.getTime() / 1000),
156
+ });
157
+
230
158
  test("epochSecondsToDate codec", () => {
231
159
  const codec = epochSecondsToDate();
232
160
 
@@ -245,6 +173,16 @@ test("epochSecondsToDate codec", () => {
245
173
  expect(roundTrip).toBe(1640995200);
246
174
  });
247
175
 
176
+ // ============================================================================
177
+ // epochMillisToDate
178
+ // ============================================================================
179
+
180
+ const epochMillisToDate = () =>
181
+ z.codec(z.int().min(0), z.date(), {
182
+ decode: (millis) => new Date(millis),
183
+ encode: (date) => date.getTime(),
184
+ });
185
+
248
186
  test("epochMillisToDate codec", () => {
249
187
  const codec = epochMillisToDate();
250
188
 
@@ -263,8 +201,30 @@ test("epochMillisToDate codec", () => {
263
201
  expect(roundTrip).toBe(1640995200123);
264
202
  });
265
203
 
204
+ // ============================================================================
205
+ // json
206
+ // ============================================================================
207
+
208
+ const jsonCodec = <T extends z.core.$ZodType>(schema: T) =>
209
+ z.codec(z.string(), schema, {
210
+ decode: (jsonString, ctx) => {
211
+ try {
212
+ return JSON.parse(jsonString);
213
+ } catch (err: any) {
214
+ ctx.issues.push({
215
+ code: "invalid_format",
216
+ format: "json",
217
+ input: jsonString,
218
+ message: err.message,
219
+ });
220
+ return z.NEVER;
221
+ }
222
+ },
223
+ encode: (value) => JSON.stringify(value),
224
+ });
225
+
266
226
  test("json codec", () => {
267
- const codec = json(z.object({ name: z.string(), age: z.number() }));
227
+ const codec = jsonCodec(z.object({ name: z.string(), age: z.number() }));
268
228
 
269
229
  // Test decode
270
230
  const decoded = z.decode(codec, '{"name":"Alice","age":30}');
@@ -281,6 +241,16 @@ test("json codec", () => {
281
241
  expect(JSON.parse(roundTrip)).toEqual(JSON.parse(original));
282
242
  });
283
243
 
244
+ // ============================================================================
245
+ // utf8ToBytes
246
+ // ============================================================================
247
+
248
+ const utf8ToBytes = () =>
249
+ z.codec(z.string(), z.instanceof(Uint8Array), {
250
+ decode: (str) => new TextEncoder().encode(str),
251
+ encode: (bytes) => new TextDecoder().decode(bytes),
252
+ });
253
+
284
254
  test("utf8ToBytes codec", () => {
285
255
  const codec = utf8ToBytes();
286
256
 
@@ -299,6 +269,16 @@ test("utf8ToBytes codec", () => {
299
269
  expect(roundTrip).toBe(original);
300
270
  });
301
271
 
272
+ // ============================================================================
273
+ // bytesToUtf8
274
+ // ============================================================================
275
+
276
+ const bytesToUtf8 = () =>
277
+ z.codec(z.instanceof(Uint8Array), z.string(), {
278
+ decode: (bytes) => new TextDecoder().decode(bytes),
279
+ encode: (str) => new TextEncoder().encode(str),
280
+ });
281
+
302
282
  test("bytesToUtf8 codec", () => {
303
283
  const codec = bytesToUtf8();
304
284
 
@@ -318,6 +298,16 @@ test("bytesToUtf8 codec", () => {
318
298
  expect(roundTrip).toEqual(original);
319
299
  });
320
300
 
301
+ // ============================================================================
302
+ // base64
303
+ // ============================================================================
304
+
305
+ const base64 = () =>
306
+ z.codec(z.base64(), z.instanceof(Uint8Array), {
307
+ decode: (base64String) => z.util.base64ToUint8Array(base64String),
308
+ encode: (bytes) => z.util.uint8ArrayToBase64(bytes),
309
+ });
310
+
321
311
  test("base64 codec", () => {
322
312
  const codec = base64();
323
313
 
@@ -336,6 +326,16 @@ test("base64 codec", () => {
336
326
  expect(roundTrip).toBe(original);
337
327
  });
338
328
 
329
+ // ============================================================================
330
+ // base64urlToBytes
331
+ // ============================================================================
332
+
333
+ const base64urlToBytes = () =>
334
+ z.codec(z.base64url(), z.instanceof(Uint8Array), {
335
+ decode: (base64urlString) => z.util.base64urlToUint8Array(base64urlString),
336
+ encode: (bytes) => z.util.uint8ArrayToBase64url(bytes),
337
+ });
338
+
339
339
  test("base64urlToBytes codec", () => {
340
340
  const codec = base64urlToBytes();
341
341
 
@@ -354,6 +354,16 @@ test("base64urlToBytes codec", () => {
354
354
  expect(roundTrip).toBe(original);
355
355
  });
356
356
 
357
+ // ============================================================================
358
+ // hexToBytes
359
+ // ============================================================================
360
+
361
+ const hexToBytes = () =>
362
+ z.codec(z.hex(), z.instanceof(Uint8Array), {
363
+ decode: (hexString) => z.util.hexToUint8Array(hexString),
364
+ encode: (bytes) => z.util.uint8ArrayToHex(bytes),
365
+ });
366
+
357
367
  test("hexToBytes codec", () => {
358
368
  const codec = hexToBytes();
359
369
 
@@ -376,6 +386,16 @@ test("hexToBytes codec", () => {
376
386
  expect(roundTrip).toBe("deadbeef");
377
387
  });
378
388
 
389
+ // ============================================================================
390
+ // stringToURL
391
+ // ============================================================================
392
+
393
+ const stringToURL = () =>
394
+ z.codec(z.url(), z.instanceof(URL), {
395
+ decode: (urlString) => new URL(urlString),
396
+ encode: (url) => url.href,
397
+ });
398
+
379
399
  test("stringToURL codec", () => {
380
400
  const codec = stringToURL();
381
401
 
@@ -396,6 +416,16 @@ test("stringToURL codec", () => {
396
416
  expect(roundTrip).toBe(original);
397
417
  });
398
418
 
419
+ // ============================================================================
420
+ // stringToHttpURL
421
+ // ============================================================================
422
+
423
+ const stringToHttpURL = () =>
424
+ z.codec(z.httpUrl(), z.instanceof(URL), {
425
+ decode: (urlString) => new URL(urlString),
426
+ encode: (url) => url.href,
427
+ });
428
+
399
429
  test("stringToHttpURL codec", () => {
400
430
  const codec = stringToHttpURL();
401
431
 
@@ -419,6 +449,16 @@ test("stringToHttpURL codec", () => {
419
449
  expect(roundTrip).toBe(original);
420
450
  });
421
451
 
452
+ // ============================================================================
453
+ // uriComponent
454
+ // ============================================================================
455
+
456
+ const uriComponent = () =>
457
+ z.codec(z.string(), z.string(), {
458
+ decode: (encodedString) => decodeURIComponent(encodedString),
459
+ encode: (decodedString) => encodeURIComponent(decodedString),
460
+ });
461
+
422
462
  test("uriComponent codec", () => {
423
463
  const codec = uriComponent();
424
464
 
@@ -442,6 +482,12 @@ test("uriComponent codec", () => {
442
482
  expect(decodedComplex).toBe(complex);
443
483
  });
444
484
 
485
+ // ============================================================================
486
+ // stringToBoolean
487
+ // ============================================================================
488
+
489
+ const stringToBoolean = (options?: { truthy?: string[]; falsy?: string[] }) => z.stringbool(options);
490
+
445
491
  test("stringToBoolean codec", () => {
446
492
  const codec = stringToBoolean();
447
493
 
@@ -469,6 +515,10 @@ test("stringToBoolean codec", () => {
469
515
  expect(z.encode(customCodec, false)).toBe("no");
470
516
  });
471
517
 
518
+ // ============================================================================
519
+ // Error Handling Tests
520
+ // ============================================================================
521
+
472
522
  // Test error cases - these test input validation, not transform errors
473
523
  test("codec input validation", () => {
474
524
  // Test invalid base64 format
@@ -495,25 +545,10 @@ test("codec input validation", () => {
495
545
  // Test transform errors - these test errors added by transform functions
496
546
  test("codec transform error handling", () => {
497
547
  // JSON codec that can fail during transform
498
- const jsonCodec = z.codec(z.string(), z.json(), {
499
- decode: (jsonString, ctx) => {
500
- try {
501
- return JSON.parse(jsonString);
502
- } catch (err: any) {
503
- ctx.issues.push({
504
- code: "invalid_format",
505
- format: "json",
506
- input: jsonString,
507
- message: err.message,
508
- });
509
- return z.NEVER;
510
- }
511
- },
512
- encode: (value) => JSON.stringify(value),
513
- });
548
+ const anyJSON = jsonCodec(z.json());
514
549
 
515
550
  // Test successful JSON parsing
516
- const validResult = z.safeDecode(jsonCodec, '{"valid": "json"}');
551
+ const validResult = z.safeDecode(anyJSON, '{"valid": "json"}');
517
552
  expect(validResult.success).toBe(true);
518
553
  if (validResult.success) {
519
554
  expect(validResult.data).toEqual({ valid: "json" });
@@ -521,7 +556,7 @@ test("codec transform error handling", () => {
521
556
 
522
557
  // Test invalid JSON that should create a single "invalid_format" issue
523
558
  // Verifies that the transform error aborts before reaching the output schema
524
- const invalidResult = z.safeDecode(jsonCodec, '{"invalid":,}');
559
+ const invalidResult = z.safeDecode(anyJSON, '{"invalid":,}');
525
560
  expect(invalidResult.success).toBe(false);
526
561
  if (!invalidResult.success) {
527
562
  expect(invalidResult.error.issues).toMatchInlineSnapshot(`
@@ -571,6 +571,19 @@ describe("toJSONSchema", () => {
571
571
  `);
572
572
  });
573
573
 
574
+ test("number with exclusive min-max openapi", () => {
575
+ const schema = z.number().lt(100).gt(1);
576
+ expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
577
+ {
578
+ "exclusiveMaximum": true,
579
+ "exclusiveMinimum": true,
580
+ "maximum": 100,
581
+ "minimum": 1,
582
+ "type": "number",
583
+ }
584
+ `);
585
+ });
586
+
574
587
  test("arrays", () => {
575
588
  expect(z.toJSONSchema(z.array(z.string()))).toMatchInlineSnapshot(`
576
589
  {
@@ -652,7 +665,37 @@ describe("toJSONSchema", () => {
652
665
  `);
653
666
  });
654
667
 
668
+ test("record openapi", () => {
669
+ const schema = z.record(z.string(), z.boolean());
670
+ expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
671
+ {
672
+ "additionalProperties": {
673
+ "type": "boolean",
674
+ },
675
+ "type": "object",
676
+ }
677
+ `);
678
+ });
679
+
655
680
  test("tuple", () => {
681
+ const schema = z.tuple([z.string(), z.number()]);
682
+ expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
683
+ {
684
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
685
+ "prefixItems": [
686
+ {
687
+ "type": "string",
688
+ },
689
+ {
690
+ "type": "number",
691
+ },
692
+ ],
693
+ "type": "array",
694
+ }
695
+ `);
696
+ });
697
+
698
+ test("tuple with rest", () => {
656
699
  const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
657
700
  expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
658
701
  {
@@ -673,6 +716,139 @@ describe("toJSONSchema", () => {
673
716
  `);
674
717
  });
675
718
 
719
+ test("tuple openapi", () => {
720
+ const schema = z.tuple([z.string(), z.number()]);
721
+ expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
722
+ {
723
+ "items": {
724
+ "anyOf": [
725
+ {
726
+ "type": "string",
727
+ },
728
+ {
729
+ "type": "number",
730
+ },
731
+ ],
732
+ },
733
+ "maxItems": 2,
734
+ "minItems": 2,
735
+ "type": "array",
736
+ }
737
+ `);
738
+ });
739
+
740
+ test("tuple with rest openapi", () => {
741
+ const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
742
+ expect(z.toJSONSchema(schema, { target: "openapi-3.0" })).toMatchInlineSnapshot(`
743
+ {
744
+ "items": {
745
+ "anyOf": [
746
+ {
747
+ "type": "string",
748
+ },
749
+ {
750
+ "type": "number",
751
+ },
752
+ {
753
+ "type": "boolean",
754
+ },
755
+ ],
756
+ },
757
+ "minItems": 2,
758
+ "type": "array",
759
+ }
760
+ `);
761
+ });
762
+
763
+ test("tuple draft-7", () => {
764
+ const schema = z.tuple([z.string(), z.number()]);
765
+ expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
766
+ {
767
+ "$schema": "http://json-schema.org/draft-07/schema#",
768
+ "items": [
769
+ {
770
+ "type": "string",
771
+ },
772
+ {
773
+ "type": "number",
774
+ },
775
+ ],
776
+ "type": "array",
777
+ }
778
+ `);
779
+ });
780
+
781
+ test("tuple with rest draft-7", () => {
782
+ const schema = z.tuple([z.string(), z.number()]).rest(z.boolean());
783
+ expect(z.toJSONSchema(schema, { target: "draft-7", io: "input" })).toMatchInlineSnapshot(`
784
+ {
785
+ "$schema": "http://json-schema.org/draft-07/schema#",
786
+ "additionalItems": {
787
+ "type": "boolean",
788
+ },
789
+ "items": [
790
+ {
791
+ "type": "string",
792
+ },
793
+ {
794
+ "type": "number",
795
+ },
796
+ ],
797
+ "type": "array",
798
+ }
799
+ `);
800
+ });
801
+
802
+ test("tuple with rest draft-7 - issue #5151 regression test", () => {
803
+ // This test addresses issue #5151: tuple with rest elements and ids
804
+ // in draft-7 had incorrect internal path handling affecting complex scenarios
805
+ const primarySchema = z.string().meta({ id: "primary" });
806
+ const restSchema = z.number().meta({ id: "rest" });
807
+ const testSchema = z.tuple([primarySchema], restSchema);
808
+
809
+ // Test both final output structure AND internal path handling
810
+ const capturedPaths: string[] = [];
811
+ const result = z.toJSONSchema(testSchema, {
812
+ target: "draft-7",
813
+ override: (ctx) => capturedPaths.push(ctx.path.join("/")),
814
+ });
815
+
816
+ // Verify correct draft-7 structure with metadata extraction
817
+ expect(result).toMatchInlineSnapshot(`
818
+ {
819
+ "$schema": "http://json-schema.org/draft-07/schema#",
820
+ "additionalItems": {
821
+ "$ref": "#/definitions/rest",
822
+ },
823
+ "definitions": {
824
+ "primary": {
825
+ "id": "primary",
826
+ "type": "string",
827
+ },
828
+ "rest": {
829
+ "id": "rest",
830
+ "type": "number",
831
+ },
832
+ },
833
+ "items": [
834
+ {
835
+ "$ref": "#/definitions/primary",
836
+ },
837
+ ],
838
+ "type": "array",
839
+ }
840
+ `);
841
+
842
+ // Verify internal paths are correct (this was the actual bug)
843
+ expect(capturedPaths).toContain("items/0"); // prefix items should use "items" path
844
+ expect(capturedPaths).toContain("additionalItems"); // rest should use "additionalItems" path
845
+ expect(capturedPaths).not.toContain("prefixItems/0"); // should not use draft-2020-12 paths
846
+
847
+ // Structural validations
848
+ expect(Array.isArray(result.items)).toBe(true);
849
+ expect(result.additionalItems).toBeDefined();
850
+ });
851
+
676
852
  test("promise", () => {
677
853
  const schema = z.promise(z.string());
678
854
  expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
@@ -1496,7 +1672,9 @@ test("unrepresentable literal values are ignored", () => {
1496
1672
  }
1497
1673
  `);
1498
1674
 
1499
- const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), { unrepresentable: "any" });
1675
+ const b = z.z.toJSONSchema(z.literal([undefined, null, 5, BigInt(1324)]), {
1676
+ unrepresentable: "any",
1677
+ });
1500
1678
  expect(b).toMatchInlineSnapshot(`
1501
1679
  {
1502
1680
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1508,7 +1686,9 @@ test("unrepresentable literal values are ignored", () => {
1508
1686
  }
1509
1687
  `);
1510
1688
 
1511
- const c = z.z.toJSONSchema(z.literal([undefined]), { unrepresentable: "any" });
1689
+ const c = z.z.toJSONSchema(z.literal([undefined]), {
1690
+ unrepresentable: "any",
1691
+ });
1512
1692
  expect(c).toMatchInlineSnapshot(`
1513
1693
  {
1514
1694
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -1791,7 +1971,9 @@ test("basic registry", () => {
1791
1971
  myRegistry.add(User, { id: "User" });
1792
1972
  myRegistry.add(Post, { id: "Post" });
1793
1973
 
1794
- const result = z.z.toJSONSchema(myRegistry, { uri: (id) => `https://example.com/${id}.json` });
1974
+ const result = z.z.toJSONSchema(myRegistry, {
1975
+ uri: (id) => `https://example.com/${id}.json`,
1976
+ });
1795
1977
  expect(result).toMatchInlineSnapshot(`
1796
1978
  {
1797
1979
  "schemas": {
@@ -183,7 +183,7 @@ export class JSONSchemaGenerator {
183
183
  else json.type = "number";
184
184
 
185
185
  if (typeof exclusiveMinimum === "number") {
186
- if (this.target === "draft-4") {
186
+ if (this.target === "draft-4" || this.target === "openapi-3.0") {
187
187
  json.minimum = exclusiveMinimum;
188
188
  json.exclusiveMinimum = true;
189
189
  } else {
@@ -199,7 +199,7 @@ export class JSONSchemaGenerator {
199
199
  }
200
200
 
201
201
  if (typeof exclusiveMaximum === "number") {
202
- if (this.target === "draft-4") {
202
+ if (this.target === "draft-4" || this.target === "openapi-3.0") {
203
203
  json.maximum = exclusiveMaximum;
204
204
  json.exclusiveMaximum = true;
205
205
  } else {
@@ -368,35 +368,47 @@ export class JSONSchemaGenerator {
368
368
  case "tuple": {
369
369
  const json: JSONSchema.ArraySchema = _json as any;
370
370
  json.type = "array";
371
+
372
+ const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
373
+ const restPath =
374
+ this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
375
+
371
376
  const prefixItems = def.items.map((x, i) =>
372
- this.process(x, { ...params, path: [...params.path, "prefixItems", i] })
377
+ this.process(x, {
378
+ ...params,
379
+ path: [...params.path, prefixPath, i],
380
+ })
373
381
  );
382
+ const rest = def.rest
383
+ ? this.process(def.rest, {
384
+ ...params,
385
+ path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
386
+ })
387
+ : null;
388
+
374
389
  if (this.target === "draft-2020-12") {
375
390
  json.prefixItems = prefixItems;
391
+ if (rest) {
392
+ json.items = rest;
393
+ }
394
+ } else if (this.target === "openapi-3.0") {
395
+ json.items = {
396
+ anyOf: [...prefixItems],
397
+ };
398
+ if (rest) {
399
+ json.items.anyOf!.push(rest);
400
+ }
401
+ json.minItems = prefixItems.length;
402
+ if (!rest) {
403
+ json.maxItems = prefixItems.length;
404
+ }
376
405
  } else {
377
406
  json.items = prefixItems;
378
- }
379
-
380
- if (def.rest) {
381
- const rest = this.process(def.rest, {
382
- ...params,
383
- path: [...params.path, "items"],
384
- });
385
- if (this.target === "draft-2020-12") {
386
- json.items = rest;
387
- } else {
407
+ if (rest) {
388
408
  json.additionalItems = rest;
389
409
  }
390
410
  }
391
411
 
392
- // additionalItems
393
- if (def.rest) {
394
- json.items = this.process(def.rest, {
395
- ...params,
396
- path: [...params.path, "items"],
397
- });
398
- }
399
-
400
412
  // length
401
413
  const { minimum, maximum } = schema._zod.bag as {
402
414
  minimum?: number;
@@ -409,7 +421,7 @@ export class JSONSchemaGenerator {
409
421
  case "record": {
410
422
  const json: JSONSchema.ObjectSchema = _json as any;
411
423
  json.type = "object";
412
- if (this.target !== "draft-4") {
424
+ if (this.target === "draft-7" || this.target === "draft-2020-12") {
413
425
  json.propertyNames = this.process(def.keyType, {
414
426
  ...params,
415
427
  path: [...params.path, "propertyNames"],
@@ -1,5 +1,5 @@
1
1
  export const version = {
2
2
  major: 4,
3
3
  minor: 1,
4
- patch: 1 as number,
4
+ patch: 3 as number,
5
5
  } as const;
@@ -19,6 +19,7 @@ export {
19
19
  flattenError,
20
20
  toJSONSchema,
21
21
  TimePrecision,
22
+ util,
22
23
  NEVER,
23
24
  } from "../core/index.js";
24
25
 
@@ -29,7 +29,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
29
29
  return (mod && mod.__esModule) ? mod : { "default": mod };
30
30
  };
31
31
  Object.defineProperty(exports, "__esModule", { value: true });
32
- exports.coerce = exports.iso = exports.ZodISODuration = exports.ZodISOTime = exports.ZodISODate = exports.ZodISODateTime = exports.locales = exports.NEVER = exports.TimePrecision = exports.toJSONSchema = exports.flattenError = exports.formatError = exports.prettifyError = exports.treeifyError = exports.regexes = exports.clone = exports.$brand = exports.$input = exports.$output = exports.config = exports.registry = exports.globalRegistry = exports.core = void 0;
32
+ exports.coerce = exports.iso = exports.ZodISODuration = exports.ZodISOTime = exports.ZodISODate = exports.ZodISODateTime = exports.locales = exports.NEVER = exports.util = exports.TimePrecision = exports.toJSONSchema = exports.flattenError = exports.formatError = exports.prettifyError = exports.treeifyError = exports.regexes = exports.clone = exports.$brand = exports.$input = exports.$output = exports.config = exports.registry = exports.globalRegistry = exports.core = void 0;
33
33
  exports.core = __importStar(require("../core/index.cjs"));
34
34
  __exportStar(require("./schemas.cjs"), exports);
35
35
  __exportStar(require("./checks.cjs"), exports);
@@ -55,6 +55,7 @@ Object.defineProperty(exports, "formatError", { enumerable: true, get: function
55
55
  Object.defineProperty(exports, "flattenError", { enumerable: true, get: function () { return index_js_2.flattenError; } });
56
56
  Object.defineProperty(exports, "toJSONSchema", { enumerable: true, get: function () { return index_js_2.toJSONSchema; } });
57
57
  Object.defineProperty(exports, "TimePrecision", { enumerable: true, get: function () { return index_js_2.TimePrecision; } });
58
+ Object.defineProperty(exports, "util", { enumerable: true, get: function () { return index_js_2.util; } });
58
59
  Object.defineProperty(exports, "NEVER", { enumerable: true, get: function () { return index_js_2.NEVER; } });
59
60
  exports.locales = __importStar(require("../locales/index.cjs"));
60
61
  // iso
@@ -5,7 +5,7 @@ export * from "./errors.cjs";
5
5
  export * from "./parse.cjs";
6
6
  export * from "./compat.cjs";
7
7
  export type { infer, output, input } from "../core/index.cjs";
8
- export { globalRegistry, type GlobalMeta, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, NEVER, } from "../core/index.cjs";
8
+ export { globalRegistry, type GlobalMeta, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, util, NEVER, } from "../core/index.cjs";
9
9
  export * as locales from "../locales/index.cjs";
10
10
  export { ZodISODateTime, ZodISODate, ZodISOTime, ZodISODuration } from "./iso.cjs";
11
11
  export * as iso from "./iso.cjs";
@@ -5,7 +5,7 @@ export * from "./errors.js";
5
5
  export * from "./parse.js";
6
6
  export * from "./compat.js";
7
7
  export type { infer, output, input } from "../core/index.js";
8
- export { globalRegistry, type GlobalMeta, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, NEVER, } from "../core/index.js";
8
+ export { globalRegistry, type GlobalMeta, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, util, NEVER, } from "../core/index.js";
9
9
  export * as locales from "../locales/index.js";
10
10
  export { ZodISODateTime, ZodISODate, ZodISOTime, ZodISODuration } from "./iso.js";
11
11
  export * as iso from "./iso.js";
@@ -8,7 +8,7 @@ export * from "./compat.js";
8
8
  import { config } from "../core/index.js";
9
9
  import en from "../locales/en.js";
10
10
  config(en());
11
- export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, NEVER, } from "../core/index.js";
11
+ export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, util, NEVER, } from "../core/index.js";
12
12
  export * as locales from "../locales/index.js";
13
13
  // iso
14
14
  // must be exported from top-level
@@ -101,7 +101,7 @@ class JSONSchemaGenerator {
101
101
  else
102
102
  json.type = "number";
103
103
  if (typeof exclusiveMinimum === "number") {
104
- if (this.target === "draft-4") {
104
+ if (this.target === "draft-4" || this.target === "openapi-3.0") {
105
105
  json.minimum = exclusiveMinimum;
106
106
  json.exclusiveMinimum = true;
107
107
  }
@@ -119,7 +119,7 @@ class JSONSchemaGenerator {
119
119
  }
120
120
  }
121
121
  if (typeof exclusiveMaximum === "number") {
122
- if (this.target === "draft-4") {
122
+ if (this.target === "draft-4" || this.target === "openapi-3.0") {
123
123
  json.maximum = exclusiveMaximum;
124
124
  json.exclusiveMaximum = true;
125
125
  }
@@ -288,32 +288,42 @@ class JSONSchemaGenerator {
288
288
  case "tuple": {
289
289
  const json = _json;
290
290
  json.type = "array";
291
- const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
291
+ const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
292
+ const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
293
+ const prefixItems = def.items.map((x, i) => this.process(x, {
294
+ ...params,
295
+ path: [...params.path, prefixPath, i],
296
+ }));
297
+ const rest = def.rest
298
+ ? this.process(def.rest, {
299
+ ...params,
300
+ path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
301
+ })
302
+ : null;
292
303
  if (this.target === "draft-2020-12") {
293
304
  json.prefixItems = prefixItems;
305
+ if (rest) {
306
+ json.items = rest;
307
+ }
308
+ }
309
+ else if (this.target === "openapi-3.0") {
310
+ json.items = {
311
+ anyOf: [...prefixItems],
312
+ };
313
+ if (rest) {
314
+ json.items.anyOf.push(rest);
315
+ }
316
+ json.minItems = prefixItems.length;
317
+ if (!rest) {
318
+ json.maxItems = prefixItems.length;
319
+ }
294
320
  }
295
321
  else {
296
322
  json.items = prefixItems;
297
- }
298
- if (def.rest) {
299
- const rest = this.process(def.rest, {
300
- ...params,
301
- path: [...params.path, "items"],
302
- });
303
- if (this.target === "draft-2020-12") {
304
- json.items = rest;
305
- }
306
- else {
323
+ if (rest) {
307
324
  json.additionalItems = rest;
308
325
  }
309
326
  }
310
- // additionalItems
311
- if (def.rest) {
312
- json.items = this.process(def.rest, {
313
- ...params,
314
- path: [...params.path, "items"],
315
- });
316
- }
317
327
  // length
318
328
  const { minimum, maximum } = schema._zod.bag;
319
329
  if (typeof minimum === "number")
@@ -325,7 +335,7 @@ class JSONSchemaGenerator {
325
335
  case "record": {
326
336
  const json = _json;
327
337
  json.type = "object";
328
- if (this.target !== "draft-4") {
338
+ if (this.target === "draft-7" || this.target === "draft-2020-12") {
329
339
  json.propertyNames = this.process(def.keyType, {
330
340
  ...params,
331
341
  path: [...params.path, "propertyNames"],
@@ -97,7 +97,7 @@ export class JSONSchemaGenerator {
97
97
  else
98
98
  json.type = "number";
99
99
  if (typeof exclusiveMinimum === "number") {
100
- if (this.target === "draft-4") {
100
+ if (this.target === "draft-4" || this.target === "openapi-3.0") {
101
101
  json.minimum = exclusiveMinimum;
102
102
  json.exclusiveMinimum = true;
103
103
  }
@@ -115,7 +115,7 @@ export class JSONSchemaGenerator {
115
115
  }
116
116
  }
117
117
  if (typeof exclusiveMaximum === "number") {
118
- if (this.target === "draft-4") {
118
+ if (this.target === "draft-4" || this.target === "openapi-3.0") {
119
119
  json.maximum = exclusiveMaximum;
120
120
  json.exclusiveMaximum = true;
121
121
  }
@@ -284,32 +284,42 @@ export class JSONSchemaGenerator {
284
284
  case "tuple": {
285
285
  const json = _json;
286
286
  json.type = "array";
287
- const prefixItems = def.items.map((x, i) => this.process(x, { ...params, path: [...params.path, "prefixItems", i] }));
287
+ const prefixPath = this.target === "draft-2020-12" ? "prefixItems" : "items";
288
+ const restPath = this.target === "draft-2020-12" ? "items" : this.target === "openapi-3.0" ? "items" : "additionalItems";
289
+ const prefixItems = def.items.map((x, i) => this.process(x, {
290
+ ...params,
291
+ path: [...params.path, prefixPath, i],
292
+ }));
293
+ const rest = def.rest
294
+ ? this.process(def.rest, {
295
+ ...params,
296
+ path: [...params.path, restPath, ...(this.target === "openapi-3.0" ? [def.items.length] : [])],
297
+ })
298
+ : null;
288
299
  if (this.target === "draft-2020-12") {
289
300
  json.prefixItems = prefixItems;
301
+ if (rest) {
302
+ json.items = rest;
303
+ }
304
+ }
305
+ else if (this.target === "openapi-3.0") {
306
+ json.items = {
307
+ anyOf: [...prefixItems],
308
+ };
309
+ if (rest) {
310
+ json.items.anyOf.push(rest);
311
+ }
312
+ json.minItems = prefixItems.length;
313
+ if (!rest) {
314
+ json.maxItems = prefixItems.length;
315
+ }
290
316
  }
291
317
  else {
292
318
  json.items = prefixItems;
293
- }
294
- if (def.rest) {
295
- const rest = this.process(def.rest, {
296
- ...params,
297
- path: [...params.path, "items"],
298
- });
299
- if (this.target === "draft-2020-12") {
300
- json.items = rest;
301
- }
302
- else {
319
+ if (rest) {
303
320
  json.additionalItems = rest;
304
321
  }
305
322
  }
306
- // additionalItems
307
- if (def.rest) {
308
- json.items = this.process(def.rest, {
309
- ...params,
310
- path: [...params.path, "items"],
311
- });
312
- }
313
323
  // length
314
324
  const { minimum, maximum } = schema._zod.bag;
315
325
  if (typeof minimum === "number")
@@ -321,7 +331,7 @@ export class JSONSchemaGenerator {
321
331
  case "record": {
322
332
  const json = _json;
323
333
  json.type = "object";
324
- if (this.target !== "draft-4") {
334
+ if (this.target === "draft-7" || this.target === "draft-2020-12") {
325
335
  json.propertyNames = this.process(def.keyType, {
326
336
  ...params,
327
337
  path: [...params.path, "propertyNames"],
@@ -4,5 +4,5 @@ exports.version = void 0;
4
4
  exports.version = {
5
5
  major: 4,
6
6
  minor: 1,
7
- patch: 1,
7
+ patch: 3,
8
8
  };
@@ -1,5 +1,5 @@
1
1
  export const version = {
2
2
  major: 4,
3
3
  minor: 1,
4
- patch: 1,
4
+ patch: 3,
5
5
  };
@@ -26,7 +26,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
26
26
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.coerce = exports.ZodMiniISODuration = exports.ZodMiniISOTime = exports.ZodMiniISODate = exports.ZodMiniISODateTime = exports.iso = exports.locales = exports.NEVER = exports.TimePrecision = exports.toJSONSchema = exports.flattenError = exports.formatError = exports.prettifyError = exports.treeifyError = exports.regexes = exports.clone = exports.$brand = exports.$input = exports.$output = exports.config = exports.registry = exports.globalRegistry = exports.core = void 0;
29
+ exports.coerce = exports.ZodMiniISODuration = exports.ZodMiniISOTime = exports.ZodMiniISODate = exports.ZodMiniISODateTime = exports.iso = exports.locales = exports.NEVER = exports.util = exports.TimePrecision = exports.toJSONSchema = exports.flattenError = exports.formatError = exports.prettifyError = exports.treeifyError = exports.regexes = exports.clone = exports.$brand = exports.$input = exports.$output = exports.config = exports.registry = exports.globalRegistry = exports.core = void 0;
30
30
  exports.core = __importStar(require("../core/index.cjs"));
31
31
  __exportStar(require("./parse.cjs"), exports);
32
32
  __exportStar(require("./schemas.cjs"), exports);
@@ -46,6 +46,7 @@ Object.defineProperty(exports, "formatError", { enumerable: true, get: function
46
46
  Object.defineProperty(exports, "flattenError", { enumerable: true, get: function () { return index_js_1.flattenError; } });
47
47
  Object.defineProperty(exports, "toJSONSchema", { enumerable: true, get: function () { return index_js_1.toJSONSchema; } });
48
48
  Object.defineProperty(exports, "TimePrecision", { enumerable: true, get: function () { return index_js_1.TimePrecision; } });
49
+ Object.defineProperty(exports, "util", { enumerable: true, get: function () { return index_js_1.util; } });
49
50
  Object.defineProperty(exports, "NEVER", { enumerable: true, get: function () { return index_js_1.NEVER; } });
50
51
  exports.locales = __importStar(require("../locales/index.cjs"));
51
52
  /** A special constant with type `never` */
@@ -3,7 +3,7 @@ export * from "./parse.cjs";
3
3
  export * from "./schemas.cjs";
4
4
  export * from "./checks.cjs";
5
5
  export type { infer, output, input } from "../core/index.cjs";
6
- export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, NEVER, } from "../core/index.cjs";
6
+ export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, util, NEVER, } from "../core/index.cjs";
7
7
  export * as locales from "../locales/index.cjs";
8
8
  /** A special constant with type `never` */
9
9
  export * as iso from "./iso.cjs";
@@ -3,7 +3,7 @@ export * from "./parse.js";
3
3
  export * from "./schemas.js";
4
4
  export * from "./checks.js";
5
5
  export type { infer, output, input } from "../core/index.js";
6
- export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, NEVER, } from "../core/index.js";
6
+ export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, util, NEVER, } from "../core/index.js";
7
7
  export * as locales from "../locales/index.js";
8
8
  /** A special constant with type `never` */
9
9
  export * as iso from "./iso.js";
@@ -2,7 +2,7 @@ export * as core from "../core/index.js";
2
2
  export * from "./parse.js";
3
3
  export * from "./schemas.js";
4
4
  export * from "./checks.js";
5
- export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, NEVER, } from "../core/index.js";
5
+ export { globalRegistry, registry, config, $output, $input, $brand, clone, regexes, treeifyError, prettifyError, formatError, flattenError, toJSONSchema, TimePrecision, util, NEVER, } from "../core/index.js";
6
6
  export * as locales from "../locales/index.js";
7
7
  /** A special constant with type `never` */
8
8
  // export const NEVER = {} as never;