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

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.20251118T055751",
3
+ "version": "4.2.0-canary.20251118T063547",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": "Colin McDonnell <zod@colinhacks.com>",
@@ -786,6 +786,37 @@ test("isPlainObject", () => {
786
786
  expect(z.core.util.isPlainObject("string")).toEqual(false);
787
787
  expect(z.core.util.isPlainObject(123)).toEqual(false);
788
788
  expect(z.core.util.isPlainObject(Symbol())).toEqual(false);
789
+ expect(z.core.util.isPlainObject({ constructor: "string" })).toEqual(true);
790
+ expect(z.core.util.isPlainObject({ constructor: 123 })).toEqual(true);
791
+ expect(z.core.util.isPlainObject({ constructor: null })).toEqual(true);
792
+ expect(z.core.util.isPlainObject({ constructor: undefined })).toEqual(true);
793
+ expect(z.core.util.isPlainObject({ constructor: true })).toEqual(true);
794
+ expect(z.core.util.isPlainObject({ constructor: {} })).toEqual(true);
795
+ expect(z.core.util.isPlainObject({ constructor: [] })).toEqual(true);
796
+ });
797
+
798
+ test("shallowClone with constructor field", () => {
799
+ const objWithConstructor = { constructor: "string", key: "value" };
800
+ const cloned = z.core.util.shallowClone(objWithConstructor);
801
+
802
+ expect(cloned).toEqual(objWithConstructor);
803
+ expect(cloned).not.toBe(objWithConstructor);
804
+ expect(cloned.constructor).toBe("string");
805
+ expect(cloned.key).toBe("value");
806
+
807
+ const testCases = [
808
+ { constructor: 123, data: "test" },
809
+ { constructor: null, data: "test" },
810
+ { constructor: true, data: "test" },
811
+ { constructor: {}, data: "test" },
812
+ { constructor: [], data: "test" },
813
+ ];
814
+
815
+ for (const testCase of testCases) {
816
+ const clonedCase = z.core.util.shallowClone(testCase);
817
+ expect(clonedCase).toEqual(testCase);
818
+ expect(clonedCase).not.toBe(testCase);
819
+ }
789
820
  });
790
821
 
791
822
  test("def typing", () => {
@@ -3793,8 +3793,8 @@ export const $ZodReadonly: core.$constructor<$ZodReadonly> = /*@__PURE__*/ core.
3793
3793
  $ZodType.init(inst, def);
3794
3794
  util.defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
3795
3795
  util.defineLazy(inst._zod, "values", () => def.innerType._zod.values);
3796
- util.defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
3797
- util.defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
3796
+ util.defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin);
3797
+ util.defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout);
3798
3798
 
3799
3799
  inst._zod.parse = (payload, ctx) => {
3800
3800
  if (ctx.direction === "backward") {
@@ -4186,10 +4186,10 @@ export const $ZodLazy: core.$constructor<$ZodLazy> = /*@__PURE__*/ core.$constru
4186
4186
  // return () => _innerType;
4187
4187
  // });
4188
4188
  util.defineLazy(inst._zod, "innerType", () => def.getter() as $ZodType);
4189
- util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod.pattern);
4190
- util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod.propValues);
4191
- util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod.optin ?? undefined);
4192
- util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod.optout ?? undefined);
4189
+ util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod?.pattern);
4190
+ util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod?.propValues);
4191
+ util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod?.optin ?? undefined);
4192
+ util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod?.optout ?? undefined);
4193
4193
  inst._zod.parse = (payload, ctx) => {
4194
4194
  const inner = inst._zod.innerType;
4195
4195
  return inner._zod.run(payload, ctx);
@@ -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
+ });
@@ -0,0 +1,67 @@
1
+ import { expect, test } from "vitest";
2
+ import * as z from "zod/v4";
3
+
4
+ test("record should parse objects with non-function constructor field", () => {
5
+ const schema = z.record(z.string(), z.any());
6
+
7
+ expect(() => schema.parse({ constructor: "string", key: "value" })).not.toThrow();
8
+
9
+ const result1 = schema.parse({ constructor: "string", key: "value" });
10
+ expect(result1).toEqual({ constructor: "string", key: "value" });
11
+
12
+ expect(() => schema.parse({ constructor: 123, key: "value" })).not.toThrow();
13
+
14
+ const result2 = schema.parse({ constructor: 123, key: "value" });
15
+ expect(result2).toEqual({ constructor: 123, key: "value" });
16
+
17
+ expect(() => schema.parse({ constructor: null, key: "value" })).not.toThrow();
18
+
19
+ const result3 = schema.parse({ constructor: null, key: "value" });
20
+ expect(result3).toEqual({ constructor: null, key: "value" });
21
+
22
+ expect(() => schema.parse({ constructor: {}, key: "value" })).not.toThrow();
23
+
24
+ const result4 = schema.parse({ constructor: {}, key: "value" });
25
+ expect(result4).toEqual({ constructor: {}, key: "value" });
26
+
27
+ expect(() => schema.parse({ constructor: [], key: "value" })).not.toThrow();
28
+
29
+ const result5 = schema.parse({ constructor: [], key: "value" });
30
+ expect(result5).toEqual({ constructor: [], key: "value" });
31
+
32
+ expect(() => schema.parse({ constructor: true, key: "value" })).not.toThrow();
33
+
34
+ const result6 = schema.parse({ constructor: true, key: "value" });
35
+ expect(result6).toEqual({ constructor: true, key: "value" });
36
+ });
37
+
38
+ test("record should still work with normal objects", () => {
39
+ const schema = z.record(z.string(), z.string());
40
+
41
+ expect(() => schema.parse({ normalKey: "value" })).not.toThrow();
42
+
43
+ const result1 = schema.parse({ normalKey: "value" });
44
+ expect(result1).toEqual({ normalKey: "value" });
45
+
46
+ expect(() => schema.parse({ key1: "value1", key2: "value2" })).not.toThrow();
47
+
48
+ const result2 = schema.parse({ key1: "value1", key2: "value2" });
49
+ expect(result2).toEqual({ key1: "value1", key2: "value2" });
50
+ });
51
+
52
+ test("record should validate values according to schema even with constructor field", () => {
53
+ const stringSchema = z.record(z.string(), z.string());
54
+
55
+ expect(() => stringSchema.parse({ constructor: "string", key: "value" })).not.toThrow();
56
+
57
+ expect(() => stringSchema.parse({ constructor: 123, key: "value" })).toThrow();
58
+ });
59
+
60
+ test("record should work with different key types and constructor field", () => {
61
+ const enumSchema = z.record(z.enum(["constructor", "key"]), z.string());
62
+
63
+ expect(() => enumSchema.parse({ constructor: "value1", key: "value2" })).not.toThrow();
64
+
65
+ const result = enumSchema.parse({ constructor: "value1", key: "value2" });
66
+ expect(result).toEqual({ constructor: "value1", key: "value2" });
67
+ });
@@ -0,0 +1,45 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import * as z from "zod/v4";
3
+
4
+ describe("Recursive Tuples Regression #5089", () => {
5
+ it("creates recursive tuple without crash", () => {
6
+ expect(() => {
7
+ const y = z.lazy((): any => z.tuple([y, y]).or(z.string()));
8
+ }).not.toThrow();
9
+ });
10
+
11
+ it("parses recursive tuple data correctly", () => {
12
+ const y = z.lazy((): any => z.tuple([y, y]).or(z.string()));
13
+
14
+ // Base case
15
+ expect(y.parse("hello")).toBe("hello");
16
+
17
+ // Recursive cases
18
+ expect(() => y.parse(["a", "b"])).not.toThrow();
19
+ expect(() => y.parse(["a", ["b", "c"]])).not.toThrow();
20
+ });
21
+
22
+ it("matches #5089 expected behavior", () => {
23
+ // Exact code from the issue
24
+ expect(() => {
25
+ const y = z.lazy((): any => z.tuple([y, y]).or(z.string()));
26
+ y.parse(["a", ["b", "c"]]);
27
+ }).not.toThrow();
28
+ });
29
+
30
+ it("handles workaround pattern", () => {
31
+ // Alternative pattern from issue discussion
32
+ expect(() => {
33
+ const y = z.lazy((): any => z.string().or(z.lazy(() => z.tuple([y, y]))));
34
+ y.parse(["a", ["b", "c"]]);
35
+ }).not.toThrow();
36
+ });
37
+
38
+ it("recursive arrays still work (comparison)", () => {
39
+ const y = z.lazy((): any => z.array(y).or(z.string()));
40
+
41
+ expect(y.parse("hello")).toBe("hello");
42
+ expect(y.parse(["hello", "world"])).toEqual(["hello", "world"]);
43
+ expect(y.parse(["a", ["b", "c"]])).toEqual(["a", ["b", "c"]]);
44
+ });
45
+ });
@@ -381,6 +381,8 @@ export function isPlainObject(o: any): o is Record<PropertyKey, unknown> {
381
381
  const ctor = o.constructor;
382
382
  if (ctor === undefined) return true;
383
383
 
384
+ if (typeof ctor !== "function") return true;
385
+
384
386
  // modified prototype
385
387
  const prot = ctor.prototype;
386
388
  if (isObject(prot) === false) return false;
@@ -1780,8 +1780,8 @@ exports.$ZodReadonly = core.$constructor("$ZodReadonly", (inst, def) => {
1780
1780
  exports.$ZodType.init(inst, def);
1781
1781
  util.defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
1782
1782
  util.defineLazy(inst._zod, "values", () => def.innerType._zod.values);
1783
- util.defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
1784
- util.defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
1783
+ util.defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin);
1784
+ util.defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout);
1785
1785
  inst._zod.parse = (payload, ctx) => {
1786
1786
  if (ctx.direction === "backward") {
1787
1787
  return def.innerType._zod.run(payload, ctx);
@@ -1941,10 +1941,10 @@ exports.$ZodLazy = core.$constructor("$ZodLazy", (inst, def) => {
1941
1941
  // return () => _innerType;
1942
1942
  // });
1943
1943
  util.defineLazy(inst._zod, "innerType", () => def.getter());
1944
- util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod.pattern);
1945
- util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod.propValues);
1946
- util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod.optin ?? undefined);
1947
- util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod.optout ?? undefined);
1944
+ util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod?.pattern);
1945
+ util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod?.propValues);
1946
+ util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod?.optin ?? undefined);
1947
+ util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod?.optout ?? undefined);
1948
1948
  inst._zod.parse = (payload, ctx) => {
1949
1949
  const inner = inst._zod.innerType;
1950
1950
  return inner._zod.run(payload, ctx);
@@ -1749,8 +1749,8 @@ export const $ZodReadonly = /*@__PURE__*/ core.$constructor("$ZodReadonly", (ins
1749
1749
  $ZodType.init(inst, def);
1750
1750
  util.defineLazy(inst._zod, "propValues", () => def.innerType._zod.propValues);
1751
1751
  util.defineLazy(inst._zod, "values", () => def.innerType._zod.values);
1752
- util.defineLazy(inst._zod, "optin", () => def.innerType._zod.optin);
1753
- util.defineLazy(inst._zod, "optout", () => def.innerType._zod.optout);
1752
+ util.defineLazy(inst._zod, "optin", () => def.innerType?._zod?.optin);
1753
+ util.defineLazy(inst._zod, "optout", () => def.innerType?._zod?.optout);
1754
1754
  inst._zod.parse = (payload, ctx) => {
1755
1755
  if (ctx.direction === "backward") {
1756
1756
  return def.innerType._zod.run(payload, ctx);
@@ -1910,10 +1910,10 @@ export const $ZodLazy = /*@__PURE__*/ core.$constructor("$ZodLazy", (inst, def)
1910
1910
  // return () => _innerType;
1911
1911
  // });
1912
1912
  util.defineLazy(inst._zod, "innerType", () => def.getter());
1913
- util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod.pattern);
1914
- util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod.propValues);
1915
- util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod.optin ?? undefined);
1916
- util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod.optout ?? undefined);
1913
+ util.defineLazy(inst._zod, "pattern", () => inst._zod.innerType?._zod?.pattern);
1914
+ util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod?.propValues);
1915
+ util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod?.optin ?? undefined);
1916
+ util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod?.optout ?? undefined);
1917
1917
  inst._zod.parse = (payload, ctx) => {
1918
1918
  const inner = inst._zod.innerType;
1919
1919
  return inner._zod.run(payload, ctx);
package/v4/core/util.cjs CHANGED
@@ -215,6 +215,8 @@ function isPlainObject(o) {
215
215
  const ctor = o.constructor;
216
216
  if (ctor === undefined)
217
217
  return true;
218
+ if (typeof ctor !== "function")
219
+ return true;
218
220
  // modified prototype
219
221
  const prot = ctor.prototype;
220
222
  if (isObject(prot) === false)
package/v4/core/util.js CHANGED
@@ -160,6 +160,8 @@ export function isPlainObject(o) {
160
160
  const ctor = o.constructor;
161
161
  if (ctor === undefined)
162
162
  return true;
163
+ if (typeof ctor !== "function")
164
+ return true;
163
165
  // modified prototype
164
166
  const prot = ctor.prototype;
165
167
  if (isObject(prot) === false)