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 +1 -1
- package/src/v4/classic/tests/index.test.ts +31 -0
- package/src/v4/core/schemas.ts +6 -6
- package/src/v4/core/tests/extend.test.ts +42 -1
- package/src/v4/core/tests/record-constructor.test.ts +67 -0
- package/src/v4/core/tests/recursive-tuples.test.ts +45 -0
- package/src/v4/core/util.ts +2 -0
- package/v4/core/schemas.cjs +6 -6
- package/v4/core/schemas.js +6 -6
- package/v4/core/util.cjs +2 -0
- package/v4/core/util.js +2 -0
package/package.json
CHANGED
|
@@ -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", () => {
|
package/src/v4/core/schemas.ts
CHANGED
|
@@ -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
|
|
3797
|
-
util.defineLazy(inst._zod, "optout", () => def.innerType
|
|
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
|
|
4190
|
-
util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod
|
|
4191
|
-
util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod
|
|
4192
|
-
util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod
|
|
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
|
+
});
|
package/src/v4/core/util.ts
CHANGED
|
@@ -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;
|
package/v4/core/schemas.cjs
CHANGED
|
@@ -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
|
|
1784
|
-
util.defineLazy(inst._zod, "optout", () => def.innerType
|
|
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
|
|
1945
|
-
util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod
|
|
1946
|
-
util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod
|
|
1947
|
-
util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod
|
|
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);
|
package/v4/core/schemas.js
CHANGED
|
@@ -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
|
|
1753
|
-
util.defineLazy(inst._zod, "optout", () => def.innerType
|
|
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
|
|
1914
|
-
util.defineLazy(inst._zod, "propValues", () => inst._zod.innerType?._zod
|
|
1915
|
-
util.defineLazy(inst._zod, "optin", () => inst._zod.innerType?._zod
|
|
1916
|
-
util.defineLazy(inst._zod, "optout", () => inst._zod.innerType?._zod
|
|
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
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)
|