zod 4.2.0-canary.20251118T055019 → 4.2.0-canary.20251118T055751

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.2.0-canary.20251118T055019",
3
+ "version": "4.2.0-canary.20251118T055751",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -208,7 +208,7 @@ test("continuability", () => {
208
208
  "message": "Invalid MAC address",
209
209
  "origin": "string",
210
210
  "path": [],
211
- "pattern": "/^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$/",
211
+ "pattern": "/^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$/",
212
212
  },
213
213
  {
214
214
  "code": "custom",
@@ -574,7 +574,7 @@ test("regexes", () => {
574
574
  `"^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$"`
575
575
  );
576
576
  expect(mac._zod.pattern.source).toMatchInlineSnapshot(
577
- `"^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$"`
577
+ `"^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$"`
578
578
  );
579
579
  expect(ulid._zod.pattern.source).toMatchInlineSnapshot(`"^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$"`);
580
580
  expect(uuid._zod.pattern.source).toMatchInlineSnapshot(
@@ -136,7 +136,7 @@ describe("toJSONSchema", () => {
136
136
  {
137
137
  "$schema": "https://json-schema.org/draft/2020-12/schema",
138
138
  "format": "mac",
139
- "pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
139
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
140
140
  "type": "string",
141
141
  }
142
142
  `);
@@ -144,7 +144,7 @@ describe("toJSONSchema", () => {
144
144
  {
145
145
  "$schema": "https://json-schema.org/draft/2020-12/schema",
146
146
  "format": "mac",
147
- "pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
147
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
148
148
  "type": "string",
149
149
  }
150
150
  `);
@@ -152,7 +152,7 @@ describe("toJSONSchema", () => {
152
152
  {
153
153
  "$schema": "https://json-schema.org/draft/2020-12/schema",
154
154
  "format": "mac",
155
- "pattern": "^(([0-9A-F]{2}(-)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(-)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
155
+ "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$",
156
156
  "type": "string",
157
157
  }
158
158
  `);
@@ -385,7 +385,7 @@ describe("toJSONSchema", () => {
385
385
  {
386
386
  "$schema": "https://json-schema.org/draft/2020-12/schema",
387
387
  "format": "mac",
388
- "pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
388
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
389
389
  "type": "string",
390
390
  }
391
391
  `);
@@ -393,7 +393,7 @@ describe("toJSONSchema", () => {
393
393
  {
394
394
  "$schema": "https://json-schema.org/draft/2020-12/schema",
395
395
  "format": "mac",
396
- "pattern": "^(([0-9A-F]{2}(:)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(:)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
396
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
397
397
  "type": "string",
398
398
  }
399
399
  `);
@@ -401,7 +401,7 @@ describe("toJSONSchema", () => {
401
401
  {
402
402
  "$schema": "https://json-schema.org/draft/2020-12/schema",
403
403
  "format": "mac",
404
- "pattern": "^(([0-9A-F]{2}(-)[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(-)[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$",
404
+ "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$",
405
405
  "type": "string",
406
406
  }
407
407
  `);
@@ -706,6 +706,54 @@ describe("toJSONSchema", () => {
706
706
  `);
707
707
  });
708
708
 
709
+ test("discriminated unions", () => {
710
+ const schema = z.discriminatedUnion("type", [
711
+ z.object({ type: z.literal("success"), data: z.string() }),
712
+ z.object({ type: z.literal("error"), message: z.string() }),
713
+ ]);
714
+ expect(z.toJSONSchema(schema)).toMatchInlineSnapshot(`
715
+ {
716
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
717
+ "oneOf": [
718
+ {
719
+ "additionalProperties": false,
720
+ "properties": {
721
+ "data": {
722
+ "type": "string",
723
+ },
724
+ "type": {
725
+ "const": "success",
726
+ "type": "string",
727
+ },
728
+ },
729
+ "required": [
730
+ "type",
731
+ "data",
732
+ ],
733
+ "type": "object",
734
+ },
735
+ {
736
+ "additionalProperties": false,
737
+ "properties": {
738
+ "message": {
739
+ "type": "string",
740
+ },
741
+ "type": {
742
+ "const": "error",
743
+ "type": "string",
744
+ },
745
+ },
746
+ "required": [
747
+ "type",
748
+ "message",
749
+ ],
750
+ "type": "object",
751
+ },
752
+ ],
753
+ }
754
+ `);
755
+ });
756
+
709
757
  test("intersections", () => {
710
758
  const schema = z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));
711
759
 
@@ -63,9 +63,7 @@ export const ipv6: RegExp =
63
63
  /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
64
64
  export const mac = (delimiter?: string): RegExp => {
65
65
  const escapedDelim = util.escapeRegex(delimiter ?? ":");
66
- return new RegExp(
67
- `^(([0-9A-F]{2}(${escapedDelim})[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(${escapedDelim})[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$`
68
- );
66
+ return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
69
67
  };
70
68
  export const cidrv4: RegExp =
71
69
  /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -330,13 +330,20 @@ export class JSONSchemaGenerator {
330
330
  }
331
331
  case "union": {
332
332
  const json: JSONSchema.BaseSchema = _json as any;
333
+ // Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
334
+ // because the discriminator field ensures mutual exclusivity between options in JSON Schema
335
+ const isDiscriminated = (def as any).discriminator !== undefined;
333
336
  const options = def.options.map((x, i) =>
334
337
  this.process(x, {
335
338
  ...params,
336
- path: [...params.path, "anyOf", i],
339
+ path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
337
340
  })
338
341
  );
339
- json.anyOf = options;
342
+ if (isDiscriminated) {
343
+ json.oneOf = options;
344
+ } else {
345
+ json.anyOf = options;
346
+ }
340
347
  break;
341
348
  }
342
349
  case "intersection": {
@@ -72,7 +72,7 @@ exports.ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(?
72
72
  exports.ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
73
73
  const mac = (delimiter) => {
74
74
  const escapedDelim = util.escapeRegex(delimiter ?? ":");
75
- return new RegExp(`^(([0-9A-F]{2}(${escapedDelim})[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(${escapedDelim})[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$`);
75
+ return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
76
76
  };
77
77
  exports.mac = mac;
78
78
  exports.cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
@@ -41,7 +41,7 @@ export const ipv4 = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.)
41
41
  export const ipv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:))$/;
42
42
  export const mac = (delimiter) => {
43
43
  const escapedDelim = util.escapeRegex(delimiter ?? ":");
44
- return new RegExp(`^(([0-9A-F]{2}(${escapedDelim})[0-9A-F]{2}(\\3[0-9A-F]{2}){4})|([0-9a-f]{2}(${escapedDelim})[0-9a-f]{2}(\\6[0-9a-f]{2}){4}))$`);
44
+ return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
45
45
  };
46
46
  export const cidrv4 = /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/;
47
47
  export const cidrv6 = /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;
@@ -252,11 +252,19 @@ class JSONSchemaGenerator {
252
252
  }
253
253
  case "union": {
254
254
  const json = _json;
255
+ // Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
256
+ // because the discriminator field ensures mutual exclusivity between options in JSON Schema
257
+ const isDiscriminated = def.discriminator !== undefined;
255
258
  const options = def.options.map((x, i) => this.process(x, {
256
259
  ...params,
257
- path: [...params.path, "anyOf", i],
260
+ path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
258
261
  }));
259
- json.anyOf = options;
262
+ if (isDiscriminated) {
263
+ json.oneOf = options;
264
+ }
265
+ else {
266
+ json.anyOf = options;
267
+ }
260
268
  break;
261
269
  }
262
270
  case "intersection": {
@@ -248,11 +248,19 @@ export class JSONSchemaGenerator {
248
248
  }
249
249
  case "union": {
250
250
  const json = _json;
251
+ // Discriminated unions use oneOf (exactly one match) instead of anyOf (one or more matches)
252
+ // because the discriminator field ensures mutual exclusivity between options in JSON Schema
253
+ const isDiscriminated = def.discriminator !== undefined;
251
254
  const options = def.options.map((x, i) => this.process(x, {
252
255
  ...params,
253
- path: [...params.path, "anyOf", i],
256
+ path: [...params.path, isDiscriminated ? "oneOf" : "anyOf", i],
254
257
  }));
255
- json.anyOf = options;
258
+ if (isDiscriminated) {
259
+ json.oneOf = options;
260
+ }
261
+ else {
262
+ json.anyOf = options;
263
+ }
256
264
  break;
257
265
  }
258
266
  case "intersection": {