zod 4.1.13-beta.0 → 4.1.13

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.
Files changed (87) hide show
  1. package/index.cjs +17 -10
  2. package/index.d.cts +4 -3
  3. package/index.d.ts +4 -3
  4. package/index.js +4 -7
  5. package/package.json +1 -1
  6. package/src/index.ts +4 -9
  7. package/src/v4/classic/checks.ts +1 -0
  8. package/src/v4/classic/schemas.ts +20 -0
  9. package/src/v4/classic/tests/continuability.test.ts +22 -0
  10. package/src/v4/classic/tests/describe-meta-checks.test.ts +27 -0
  11. package/src/v4/classic/tests/index.test.ts +55 -1
  12. package/src/v4/classic/tests/promise.test.ts +1 -1
  13. package/src/v4/classic/tests/readonly.test.ts +1 -1
  14. package/src/v4/classic/tests/record.test.ts +141 -9
  15. package/src/v4/classic/tests/registries.test.ts +5 -1
  16. package/src/v4/classic/tests/string.test.ts +72 -0
  17. package/src/v4/classic/tests/template-literal.test.ts +8 -0
  18. package/src/v4/classic/tests/to-json-schema.test.ts +97 -0
  19. package/src/v4/classic/tests/tuple.test.ts +18 -0
  20. package/src/v4/classic/tests/url.test.ts +13 -0
  21. package/src/v4/core/api.ts +45 -0
  22. package/src/v4/core/core.ts +23 -17
  23. package/src/v4/core/regexes.ts +6 -1
  24. package/src/v4/core/registries.ts +12 -1
  25. package/src/v4/core/schemas.ts +49 -32
  26. package/src/v4/core/tests/extend.test.ts +42 -1
  27. package/src/v4/core/tests/locales/he.test.ts +379 -0
  28. package/src/v4/core/tests/locales/nl.test.ts +46 -0
  29. package/src/v4/core/tests/record-constructor.test.ts +67 -0
  30. package/src/v4/core/tests/recursive-tuples.test.ts +45 -0
  31. package/src/v4/core/to-json-schema.ts +55 -91
  32. package/src/v4/core/util.ts +11 -0
  33. package/src/v4/core/versions.ts +1 -1
  34. package/src/v4/locales/en.ts +1 -0
  35. package/src/v4/locales/he.ts +202 -71
  36. package/src/v4/locales/nl.ts +10 -10
  37. package/src/v4/mini/iso.ts +4 -4
  38. package/src/v4/mini/schemas.ts +17 -0
  39. package/src/v4/mini/tests/functions.test.ts +0 -38
  40. package/src/v4/mini/tests/index.test.ts +24 -1
  41. package/src/v4/mini/tests/string.test.ts +32 -0
  42. package/v3/ZodError.d.cts +1 -1
  43. package/v3/ZodError.d.ts +1 -1
  44. package/v4/classic/checks.cjs +2 -1
  45. package/v4/classic/checks.d.cts +1 -1
  46. package/v4/classic/checks.d.ts +1 -1
  47. package/v4/classic/checks.js +1 -1
  48. package/v4/classic/schemas.cjs +15 -2
  49. package/v4/classic/schemas.d.cts +8 -0
  50. package/v4/classic/schemas.d.ts +8 -0
  51. package/v4/classic/schemas.js +12 -0
  52. package/v4/core/api.cjs +40 -0
  53. package/v4/core/api.d.cts +7 -0
  54. package/v4/core/api.d.ts +7 -0
  55. package/v4/core/api.js +36 -0
  56. package/v4/core/core.cjs +20 -18
  57. package/v4/core/core.js +20 -18
  58. package/v4/core/regexes.cjs +31 -2
  59. package/v4/core/regexes.d.cts +1 -0
  60. package/v4/core/regexes.d.ts +1 -0
  61. package/v4/core/regexes.js +5 -0
  62. package/v4/core/registries.cjs +3 -1
  63. package/v4/core/registries.js +3 -1
  64. package/v4/core/schemas.cjs +31 -32
  65. package/v4/core/schemas.d.cts +12 -2
  66. package/v4/core/schemas.d.ts +12 -2
  67. package/v4/core/schemas.js +29 -30
  68. package/v4/core/to-json-schema.cjs +55 -92
  69. package/v4/core/to-json-schema.js +55 -92
  70. package/v4/core/util.cjs +11 -0
  71. package/v4/core/util.d.cts +1 -0
  72. package/v4/core/util.d.ts +1 -0
  73. package/v4/core/util.js +10 -0
  74. package/v4/core/versions.cjs +1 -1
  75. package/v4/core/versions.js +1 -1
  76. package/v4/locales/en.cjs +1 -0
  77. package/v4/locales/en.js +1 -0
  78. package/v4/locales/he.cjs +177 -66
  79. package/v4/locales/he.js +177 -66
  80. package/v4/locales/nl.cjs +8 -8
  81. package/v4/locales/nl.js +8 -8
  82. package/v4/mini/iso.cjs +4 -4
  83. package/v4/mini/iso.js +4 -4
  84. package/v4/mini/schemas.cjs +13 -2
  85. package/v4/mini/schemas.d.cts +6 -0
  86. package/v4/mini/schemas.d.ts +6 -0
  87. package/v4/mini/schemas.js +10 -0
@@ -132,6 +132,30 @@ describe("toJSONSchema", () => {
132
132
  "type": "string",
133
133
  }
134
134
  `);
135
+ expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(`
136
+ {
137
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
138
+ "format": "mac",
139
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
140
+ "type": "string",
141
+ }
142
+ `);
143
+ expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(`
144
+ {
145
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
146
+ "format": "mac",
147
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
148
+ "type": "string",
149
+ }
150
+ `);
151
+ expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(`
152
+ {
153
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
154
+ "format": "mac",
155
+ "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$",
156
+ "type": "string",
157
+ }
158
+ `);
135
159
  expect(z.toJSONSchema(z.uuid())).toMatchInlineSnapshot(`
136
160
  {
137
161
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -357,6 +381,31 @@ describe("toJSONSchema", () => {
357
381
  }
358
382
  `);
359
383
 
384
+ expect(z.toJSONSchema(z.mac())).toMatchInlineSnapshot(`
385
+ {
386
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
387
+ "format": "mac",
388
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
389
+ "type": "string",
390
+ }
391
+ `);
392
+ expect(z.toJSONSchema(z.mac({ delimiter: ":" }))).toMatchInlineSnapshot(`
393
+ {
394
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
395
+ "format": "mac",
396
+ "pattern": "^(?:[0-9A-F]{2}:){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$",
397
+ "type": "string",
398
+ }
399
+ `);
400
+ expect(z.toJSONSchema(z.mac({ delimiter: "-" }))).toMatchInlineSnapshot(`
401
+ {
402
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
403
+ "format": "mac",
404
+ "pattern": "^(?:[0-9A-F]{2}-){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}-){5}[0-9a-f]{2}$",
405
+ "type": "string",
406
+ }
407
+ `);
408
+
360
409
  expect(z.toJSONSchema(z.base64())).toMatchInlineSnapshot(`
361
410
  {
362
411
  "$schema": "https://json-schema.org/draft/2020-12/schema",
@@ -657,6 +706,54 @@ describe("toJSONSchema", () => {
657
706
  `);
658
707
  });
659
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
+
660
757
  test("intersections", () => {
661
758
  const schema = z.intersection(z.object({ name: z.string() }), z.object({ age: z.number() }));
662
759
 
@@ -145,6 +145,24 @@ test("tuple with optional elements followed by required", () => {
145
145
  }
146
146
  });
147
147
 
148
+ test("tuple with all optional elements", () => {
149
+ const allOptionalTuple = z.tuple([z.string().optional(), z.number().optional(), z.boolean().optional()]);
150
+ expectTypeOf<typeof allOptionalTuple._output>().toEqualTypeOf<[string?, number?, boolean?]>();
151
+
152
+ // Empty array should be valid (all items optional)
153
+ expect(allOptionalTuple.parse([])).toEqual([]);
154
+
155
+ // Partial arrays should be valid
156
+ expect(allOptionalTuple.parse(["hello"])).toEqual(["hello"]);
157
+ expect(allOptionalTuple.parse(["hello", 42])).toEqual(["hello", 42]);
158
+
159
+ // Full array should be valid
160
+ expect(allOptionalTuple.parse(["hello", 42, true])).toEqual(["hello", 42, true]);
161
+
162
+ // Array that's too long should fail
163
+ expect(() => allOptionalTuple.parse(["hello", 42, true, "extra"])).toThrow();
164
+ });
165
+
148
166
  test("tuple with rest schema", () => {
149
167
  const myTuple = z.tuple([z.string(), z.number()]).rest(z.boolean());
150
168
  expect(myTuple.parse(["asdf", 1234, true, false, true])).toEqual(["asdf", 1234, true, false, true]);
@@ -0,0 +1,13 @@
1
+ import { expect, expectTypeOf, test } from "vitest";
2
+ import * as z from "zod/v4";
3
+
4
+ test("type inference", () => {
5
+ const schema = z.string().array();
6
+ expectTypeOf<z.infer<typeof schema>>().toEqualTypeOf<string[]>();
7
+ });
8
+
9
+ test("url regex", () => {
10
+ expect((z.url({ hostname: /^example\.com$/ }).safeParse("http://example.org/").error?.issues[0] as any).pattern).toBe(
11
+ "^example\\.com$"
12
+ );
13
+ });
@@ -1,6 +1,7 @@
1
1
  import * as checks from "./checks.js";
2
2
  import type * as core from "./core.js";
3
3
  import type * as errors from "./errors.js";
4
+ import * as registries from "./registries.js";
4
5
  import * as schemas from "./schemas.js";
5
6
  import * as util from "./util.js";
6
7
 
@@ -345,6 +346,22 @@ export function _ipv6<T extends schemas.$ZodIPv6>(
345
346
  });
346
347
  }
347
348
 
349
+ // MAC
350
+ export type $ZodMACParams = StringFormatParams<schemas.$ZodMAC, "pattern" | "when">;
351
+ export type $ZodCheckMACParams = CheckStringFormatParams<schemas.$ZodMAC, "pattern" | "when">;
352
+ export function _mac<T extends schemas.$ZodMAC>(
353
+ Class: util.SchemaClass<T>,
354
+ params?: string | $ZodMACParams | $ZodCheckMACParams
355
+ ): T {
356
+ return new Class({
357
+ type: "string",
358
+ format: "mac",
359
+ check: "string_format",
360
+ abort: false,
361
+ ...util.normalizeParams(params),
362
+ });
363
+ }
364
+
348
365
  // CIDRv4
349
366
  export type $ZodCIDRv4Params = StringFormatParams<schemas.$ZodCIDRv4, "pattern" | "when">;
350
367
  export type $ZodCheckCIDRv4Params = CheckStringFormatParams<schemas.$ZodCIDRv4, "pattern" | "when">;
@@ -1036,6 +1053,10 @@ export function _toLowerCase(): checks.$ZodCheckOverwrite<string> {
1036
1053
  export function _toUpperCase(): checks.$ZodCheckOverwrite<string> {
1037
1054
  return _overwrite((input) => input.toUpperCase());
1038
1055
  }
1056
+ // slugify
1057
+ export function _slugify(): checks.$ZodCheckOverwrite<string> {
1058
+ return _overwrite((input) => util.slugify(input));
1059
+ }
1039
1060
 
1040
1061
  /////// collections ///////
1041
1062
 
@@ -1518,6 +1539,30 @@ export function _check<O = unknown>(fn: schemas.CheckFn<O>, params?: string | $Z
1518
1539
  return ch;
1519
1540
  }
1520
1541
 
1542
+ export function describe<T>(description: string): checks.$ZodCheck<T> {
1543
+ const ch = new checks.$ZodCheck({ check: "describe" });
1544
+ ch._zod.onattach = [
1545
+ (inst) => {
1546
+ const existing = registries.globalRegistry.get(inst) ?? {};
1547
+ registries.globalRegistry.add(inst, { ...existing, description });
1548
+ },
1549
+ ];
1550
+ ch._zod.check = () => {}; // no-op check
1551
+ return ch;
1552
+ }
1553
+
1554
+ export function meta<T>(metadata: registries.GlobalMeta): checks.$ZodCheck<T> {
1555
+ const ch = new checks.$ZodCheck({ check: "meta" });
1556
+ ch._zod.onattach = [
1557
+ (inst) => {
1558
+ const existing = registries.globalRegistry.get(inst) ?? {};
1559
+ registries.globalRegistry.add(inst, { ...existing, ...metadata });
1560
+ },
1561
+ ];
1562
+ ch._zod.check = () => {}; // no-op check
1563
+ return ch;
1564
+ }
1565
+
1521
1566
  // export type $ZodCustomParams = CheckTypeParams<schemas.$ZodCustom, "fn">
1522
1567
 
1523
1568
  ///////// STRINGBOOL /////////
@@ -19,29 +19,35 @@ export /*@__NO_SIDE_EFFECTS__*/ function $constructor<T extends ZodTrait, D = T[
19
19
  initializer: (inst: T, def: D) => void,
20
20
  params?: { Parent?: typeof Class }
21
21
  ): $constructor<T, D> {
22
- // const initCache = new WeakMap<any, boolean>();
23
22
  function init(inst: T, def: D) {
24
- // if (initCache.get(def)) {
25
- // console.log("skipping init", name);
26
- // return;
27
- // }
28
- // console.log("init", name);
29
- // initCache.set(def, true);
30
- Object.defineProperty(inst, "_zod", {
31
- value: inst._zod ?? {},
32
- enumerable: false,
33
- });
34
-
35
- inst._zod.traits ??= new Set();
23
+ if (!inst._zod) {
24
+ Object.defineProperty(inst, "_zod", {
25
+ value: {
26
+ def,
27
+ constr: _,
28
+ traits: new Set(),
29
+ },
30
+ enumerable: false,
31
+ });
32
+ }
33
+
34
+ if (inst._zod.traits.has(name)) {
35
+ return;
36
+ }
36
37
 
37
38
  inst._zod.traits.add(name);
39
+
38
40
  initializer(inst, def);
41
+
39
42
  // support prototype modifications
40
- for (const k in _.prototype) {
41
- if (!(k in inst)) Object.defineProperty(inst, k, { value: _.prototype[k].bind(inst) });
43
+ const proto = _.prototype;
44
+ const keys = Object.keys(proto);
45
+ for (let i = 0; i < keys.length; i++) {
46
+ const k = keys[i]!;
47
+ if (!(k in inst)) {
48
+ (inst as any)[k] = proto[k].bind(inst);
49
+ }
42
50
  }
43
- inst._zod.constr = _;
44
- inst._zod.def = def;
45
51
  }
46
52
 
47
53
  // doesn't work if Parent has a constructor with arguments
@@ -1,3 +1,5 @@
1
+ import * as util from "./util.js";
2
+
1
3
  export const cuid: RegExp = /^[cC][^\s-]{8,}$/;
2
4
  export const cuid2: RegExp = /^[0-9a-z]+$/;
3
5
  export const ulid: RegExp = /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/;
@@ -59,7 +61,10 @@ export const ipv4: RegExp =
59
61
  /^(?:(?: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])$/;
60
62
  export const ipv6: RegExp =
61
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}|:))$/;
62
-
64
+ export const mac = (delimiter?: string): RegExp => {
65
+ const escapedDelim = util.escapeRegex(delimiter ?? ":");
66
+ return new RegExp(`^(?:[0-9A-F]{2}${escapedDelim}){5}[0-9A-F]{2}$|^(?:[0-9a-f]{2}${escapedDelim}){5}[0-9a-f]{2}$`);
67
+ };
63
68
  export const cidrv4: RegExp =
64
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])$/;
65
70
  export const cidrv6: RegExp =
@@ -94,4 +94,15 @@ export function registry<T extends MetadataType = MetadataType, S extends $ZodTy
94
94
  return new $ZodRegistry<T, S>();
95
95
  }
96
96
 
97
- export const globalRegistry: $ZodRegistry<GlobalMeta> = /*@__PURE__*/ registry<GlobalMeta>();
97
+ interface GlobalThisWithRegistry {
98
+ /**
99
+ * The globalRegistry instance shared across both CommonJS and ESM builds.
100
+ * By attaching the registry to `globalThis`, this property ensures a single, deduplicated instance
101
+ * is used regardless of whether the package is loaded via `require` (CJS) or `import` (ESM).
102
+ * This prevents dual package hazards and keeps registry state consistent.
103
+ */
104
+ __zod_globalRegistry?: $ZodRegistry<GlobalMeta>;
105
+ }
106
+
107
+ (globalThis as GlobalThisWithRegistry).__zod_globalRegistry ??= registry<GlobalMeta>();
108
+ export const globalRegistry: $ZodRegistry<GlobalMeta> = (globalThis as GlobalThisWithRegistry).__zod_globalRegistry!;
@@ -741,10 +741,8 @@ export interface $ZodIPv4 extends $ZodType {
741
741
  export const $ZodIPv4: core.$constructor<$ZodIPv4> = /*@__PURE__*/ core.$constructor("$ZodIPv4", (inst, def): void => {
742
742
  def.pattern ??= regexes.ipv4;
743
743
  $ZodStringFormat.init(inst, def);
744
- inst._zod.onattach.push((inst) => {
745
- const bag = inst._zod.bag as $ZodStringInternals<unknown>["bag"];
746
- bag.format = `ipv4`;
747
- });
744
+
745
+ inst._zod.bag.format = `ipv4`;
748
746
  });
749
747
 
750
748
  ////////////////////////////// ZodIPv6 //////////////////////////////
@@ -765,10 +763,7 @@ export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constru
765
763
  def.pattern ??= regexes.ipv6;
766
764
  $ZodStringFormat.init(inst, def);
767
765
 
768
- inst._zod.onattach.push((inst) => {
769
- const bag = inst._zod.bag as $ZodStringInternals<unknown>["bag"];
770
- bag.format = `ipv6`;
771
- });
766
+ inst._zod.bag.format = `ipv6`;
772
767
 
773
768
  inst._zod.check = (payload) => {
774
769
  try {
@@ -787,6 +782,26 @@ export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constru
787
782
  };
788
783
  });
789
784
 
785
+ ////////////////////////////// ZodMAC //////////////////////////////
786
+ export interface $ZodMACDef extends $ZodStringFormatDef<"mac"> {
787
+ delimiter?: string;
788
+ }
789
+
790
+ export interface $ZodMACInternals extends $ZodStringFormatInternals<"mac"> {
791
+ def: $ZodMACDef;
792
+ }
793
+
794
+ export interface $ZodMAC extends $ZodType {
795
+ _zod: $ZodMACInternals;
796
+ }
797
+
798
+ export const $ZodMAC: core.$constructor<$ZodMAC> = /*@__PURE__*/ core.$constructor("$ZodMAC", (inst, def): void => {
799
+ def.pattern ??= regexes.mac(def.delimiter);
800
+ $ZodStringFormat.init(inst, def);
801
+
802
+ inst._zod.bag.format = `mac`;
803
+ });
804
+
790
805
  ////////////////////////////// ZodCIDRv4 //////////////////////////////
791
806
 
792
807
  export interface $ZodCIDRv4Def extends $ZodStringFormatDef<"cidrv4"> {
@@ -879,9 +894,7 @@ export const $ZodBase64: core.$constructor<$ZodBase64> = /*@__PURE__*/ core.$con
879
894
  def.pattern ??= regexes.base64;
880
895
  $ZodStringFormat.init(inst, def);
881
896
 
882
- inst._zod.onattach.push((inst) => {
883
- inst._zod.bag.contentEncoding = "base64";
884
- });
897
+ inst._zod.bag.contentEncoding = "base64";
885
898
 
886
899
  inst._zod.check = (payload) => {
887
900
  if (isValidBase64(payload.value)) return;
@@ -918,9 +931,7 @@ export const $ZodBase64URL: core.$constructor<$ZodBase64URL> = /*@__PURE__*/ cor
918
931
  def.pattern ??= regexes.base64url;
919
932
  $ZodStringFormat.init(inst, def);
920
933
 
921
- inst._zod.onattach.push((inst) => {
922
- inst._zod.bag.contentEncoding = "base64url";
923
- });
934
+ inst._zod.bag.contentEncoding = "base64url";
924
935
 
925
936
  inst._zod.check = (payload) => {
926
937
  if (isValidBase64URL(payload.value)) return;
@@ -1065,8 +1076,8 @@ export interface $ZodNumber<Input = unknown> extends $ZodType {
1065
1076
 
1066
1077
  export const $ZodNumber: core.$constructor<$ZodNumber> = /*@__PURE__*/ core.$constructor("$ZodNumber", (inst, def) => {
1067
1078
  $ZodType.init(inst, def);
1068
- inst._zod.pattern = inst._zod.bag.pattern ?? regexes.number;
1069
1079
 
1080
+ inst._zod.pattern = inst._zod.bag.pattern ?? regexes.number;
1070
1081
  inst._zod.parse = (payload, _ctx) => {
1071
1082
  if (def.coerce)
1072
1083
  try {
@@ -1113,10 +1124,10 @@ export interface $ZodNumberFormat extends $ZodType {
1113
1124
  }
1114
1125
 
1115
1126
  export const $ZodNumberFormat: core.$constructor<$ZodNumberFormat> = /*@__PURE__*/ core.$constructor(
1116
- "$ZodNumber",
1127
+ "$ZodNumberFormat",
1117
1128
  (inst, def) => {
1118
1129
  checks.$ZodCheckNumberFormat.init(inst, def);
1119
- $ZodNumber.init(inst, def); // no format checksp
1130
+ $ZodNumber.init(inst, def); // no format checks
1120
1131
  }
1121
1132
  );
1122
1133
 
@@ -1237,7 +1248,7 @@ export interface $ZodBigIntFormat extends $ZodType {
1237
1248
  }
1238
1249
 
1239
1250
  export const $ZodBigIntFormat: core.$constructor<$ZodBigIntFormat> = /*@__PURE__*/ core.$constructor(
1240
- "$ZodBigInt",
1251
+ "$ZodBigIntFormat",
1241
1252
  (inst, def) => {
1242
1253
  checks.$ZodCheckBigIntFormat.init(inst, def);
1243
1254
  $ZodBigInt.init(inst, def); // no format checks
@@ -1792,7 +1803,7 @@ function handleCatchall(
1792
1803
  const keySet = def.keySet;
1793
1804
  const _catchall = def.catchall!._zod;
1794
1805
  const t = _catchall.def.type;
1795
- for (const key of Object.keys(input)) {
1806
+ for (const key in input) {
1796
1807
  if (keySet.has(key)) continue;
1797
1808
  if (t === "never") {
1798
1809
  unrecognized.push(key);
@@ -2420,7 +2431,6 @@ export interface $ZodTuple<
2420
2431
  export const $ZodTuple: core.$constructor<$ZodTuple> = /*@__PURE__*/ core.$constructor("$ZodTuple", (inst, def) => {
2421
2432
  $ZodType.init(inst, def);
2422
2433
  const items = def.items;
2423
- const optStart = items.length - [...items].reverse().findIndex((item) => item._zod.optin !== "optional");
2424
2434
 
2425
2435
  inst._zod.parse = (payload, ctx) => {
2426
2436
  const input = payload.value;
@@ -2437,6 +2447,9 @@ export const $ZodTuple: core.$constructor<$ZodTuple> = /*@__PURE__*/ core.$const
2437
2447
  payload.value = [];
2438
2448
  const proms: Promise<any>[] = [];
2439
2449
 
2450
+ const reversedIndex = [...items].reverse().findIndex((item) => item._zod.optin !== "optional");
2451
+ const optStart = reversedIndex === -1 ? 0 : items.length - reversedIndex;
2452
+
2440
2453
  if (!def.rest) {
2441
2454
  const tooBig = input.length > items.length;
2442
2455
  const tooSmall = input.length < optStart - 1;
@@ -2590,11 +2603,13 @@ export const $ZodRecord: core.$constructor<$ZodRecord> = /*@__PURE__*/ core.$con
2590
2603
 
2591
2604
  const proms: Promise<any>[] = [];
2592
2605
 
2593
- if (def.keyType._zod.values) {
2594
- const values = def.keyType._zod.values!;
2606
+ const values = def.keyType._zod.values;
2607
+ if (values) {
2595
2608
  payload.value = {};
2609
+ const recordKeys = new Set<string | symbol>();
2596
2610
  for (const key of values) {
2597
2611
  if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
2612
+ recordKeys.add(typeof key === "number" ? key.toString() : key);
2598
2613
  const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
2599
2614
 
2600
2615
  if (result instanceof Promise) {
@@ -2617,7 +2632,7 @@ export const $ZodRecord: core.$constructor<$ZodRecord> = /*@__PURE__*/ core.$con
2617
2632
 
2618
2633
  let unrecognized!: string[];
2619
2634
  for (const key in input) {
2620
- if (!values.has(key)) {
2635
+ if (!recordKeys.has(key)) {
2621
2636
  unrecognized = unrecognized ?? [];
2622
2637
  unrecognized.push(key);
2623
2638
  }
@@ -2942,7 +2957,8 @@ export const $ZodLiteral: core.$constructor<$ZodLiteral> = /*@__PURE__*/ core.$c
2942
2957
  throw new Error("Cannot create literal schema with no valid values");
2943
2958
  }
2944
2959
 
2945
- inst._zod.values = new Set<util.Literal>(def.values);
2960
+ const values = new Set<util.Literal>(def.values);
2961
+ inst._zod.values = values;
2946
2962
  inst._zod.pattern = new RegExp(
2947
2963
  `^(${def.values
2948
2964
 
@@ -2952,7 +2968,7 @@ export const $ZodLiteral: core.$constructor<$ZodLiteral> = /*@__PURE__*/ core.$c
2952
2968
 
2953
2969
  inst._zod.parse = (payload, _ctx) => {
2954
2970
  const input = payload.value;
2955
- if (inst._zod.values.has(input)) {
2971
+ if (values.has(input)) {
2956
2972
  return payload;
2957
2973
  }
2958
2974
  payload.issues.push({
@@ -3779,8 +3795,8 @@ export const $ZodReadonly: core.$constructor<$ZodReadonly> = /*@__PURE__*/ core.
3779
3795
  $ZodType.init(inst, def);
3780
3796
  util.defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
3781
3797
  util.defineLazy(inst._zod, "values", () => def.innerType._zod.values);
3782
- util.defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
3783
- util.defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
3798
+ util.defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin);
3799
+ util.defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout);
3784
3800
 
3785
3801
  inst._zod.parse = (payload, ctx) => {
3786
3802
  if (ctx.direction === "backward") {
@@ -4112,7 +4128,7 @@ export interface $ZodPromiseDef<T extends SomeType = $ZodType> extends $ZodTypeD
4112
4128
  }
4113
4129
 
4114
4130
  export interface $ZodPromiseInternals<T extends SomeType = $ZodType>
4115
- extends $ZodTypeInternals<core.output<T>, util.MaybeAsync<core.input<T>>> {
4131
+ extends $ZodTypeInternals<Promise<core.output<T>>, util.MaybeAsync<core.input<T>>> {
4116
4132
  def: $ZodPromiseDef<T>;
4117
4133
  isst: never;
4118
4134
  }
@@ -4172,10 +4188,10 @@ export const $ZodLazy: core.$constructor<$ZodLazy> = /*@__PURE__*/ core.$constru
4172
4188
  // return () => _innerType;
4173
4189
  // });
4174
4190
  util.defineLazy(inst._zod, "innerType", () => def.getter() as $ZodType);
4175
- util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType._zod.pattern);
4176
- util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType._zod.propValues);
4177
- util.defineLazy(inst._zod, "optin", () => inst._zod.innerType._zod.optin ?? undefined);
4178
- util.defineLazy(inst._zod, "optout", () => inst._zod.innerType._zod.optout ?? undefined);
4191
+ util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod?.pattern);
4192
+ util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod?.propValues);
4193
+ util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod?.optin ?? undefined);
4194
+ util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod?.optout ?? undefined);
4179
4195
  inst._zod.parse = (payload, ctx) => {
4180
4196
  const inner = inst._zod.innerType;
4181
4197
  return inner._zod.run(payload, ctx);
@@ -4306,6 +4322,7 @@ export type $ZodStringFormatTypes =
4306
4322
  | $ZodISODuration
4307
4323
  | $ZodIPv4
4308
4324
  | $ZodIPv6
4325
+ | $ZodMAC
4309
4326
  | $ZodCIDRv4
4310
4327
  | $ZodCIDRv6
4311
4328
  | $ZodBase64
@@ -1,4 +1,4 @@
1
- import { test } from "vitest";
1
+ import { expect, test } from "vitest";
2
2
  import * as z from "zod/v4";
3
3
 
4
4
  test("extend chaining preserves and overrides properties", () => {
@@ -16,3 +16,44 @@ test("extend chaining preserves and overrides properties", () => {
16
16
 
17
17
  schema3.parse({ email: "test@example.com" });
18
18
  });
19
+
20
+ test("extend with constructor field in shape", () => {
21
+ const baseSchema = z.object({
22
+ name: z.string(),
23
+ });
24
+
25
+ const extendedSchema = baseSchema.extend({
26
+ constructor: z.string(),
27
+ age: z.number(),
28
+ });
29
+
30
+ const result = extendedSchema.parse({
31
+ name: "John",
32
+ constructor: "Person",
33
+ age: 30,
34
+ });
35
+
36
+ expect(result).toEqual({
37
+ name: "John",
38
+ constructor: "Person",
39
+ age: 30,
40
+ });
41
+
42
+ const testCases = [
43
+ { name: "Test", constructor: 123, age: 25 },
44
+ { name: "Test", constructor: null, age: 25 },
45
+ { name: "Test", constructor: true, age: 25 },
46
+ { name: "Test", constructor: {}, age: 25 },
47
+ ];
48
+
49
+ for (const testCase of testCases) {
50
+ const anyConstructorSchema = baseSchema.extend({
51
+ constructor: z.any(),
52
+ age: z.number(),
53
+ });
54
+
55
+ expect(() => anyConstructorSchema.parse(testCase)).not.toThrow();
56
+ const parsed = anyConstructorSchema.parse(testCase);
57
+ expect(parsed).toEqual(testCase);
58
+ }
59
+ });