zod 4.3.0 → 4.3.2
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/intersection.test.ts +32 -5
- package/src/v4/classic/tests/object.test.ts +19 -2
- package/src/v4/core/schemas.ts +31 -4
- package/src/v4/core/util.ts +8 -2
- package/src/v4/core/versions.ts +1 -1
- package/v4/core/schemas.cjs +31 -4
- package/v4/core/schemas.js +31 -4
- package/v4/core/util.cjs +8 -2
- package/v4/core/util.js +8 -2
- package/v4/core/versions.cjs +1 -1
- package/v4/core/versions.js +1 -1
package/package.json
CHANGED
|
@@ -27,17 +27,44 @@ test("object intersection: loose", () => {
|
|
|
27
27
|
expect(() => C.parse({ a: "foo" })).toThrow();
|
|
28
28
|
});
|
|
29
29
|
|
|
30
|
-
test("object intersection: strict", () => {
|
|
30
|
+
test("object intersection: strict + strip", () => {
|
|
31
31
|
const A = z.strictObject({ a: z.string() });
|
|
32
32
|
const B = z.object({ b: z.string() });
|
|
33
33
|
|
|
34
|
-
const C = z.intersection(A, B);
|
|
34
|
+
const C = z.intersection(A, B);
|
|
35
35
|
type C = z.infer<typeof C>;
|
|
36
36
|
expectTypeOf<C>().toEqualTypeOf<{ a: string } & { b: string }>();
|
|
37
|
-
const data = { a: "foo", b: "foo", c: "extra" };
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
expect(
|
|
38
|
+
// Keys recognized by either side should work
|
|
39
|
+
expect(C.parse({ a: "foo", b: "bar" })).toEqual({ a: "foo", b: "bar" });
|
|
40
|
+
|
|
41
|
+
// Extra keys are stripped (follows strip behavior from B)
|
|
42
|
+
expect(C.parse({ a: "foo", b: "bar", c: "extra" })).toEqual({ a: "foo", b: "bar" });
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("object intersection: strict + strict", () => {
|
|
46
|
+
const A = z.strictObject({ a: z.string() });
|
|
47
|
+
const B = z.strictObject({ b: z.string() });
|
|
48
|
+
|
|
49
|
+
const C = z.intersection(A, B);
|
|
50
|
+
|
|
51
|
+
// Keys recognized by either side should work
|
|
52
|
+
expect(C.parse({ a: "foo", b: "bar" })).toEqual({ a: "foo", b: "bar" });
|
|
53
|
+
|
|
54
|
+
// Keys unrecognized by BOTH sides should error
|
|
55
|
+
const result = C.safeParse({ a: "foo", b: "bar", c: "extra" });
|
|
56
|
+
expect(result.error?.issues).toMatchInlineSnapshot(`
|
|
57
|
+
[
|
|
58
|
+
{
|
|
59
|
+
"code": "unrecognized_keys",
|
|
60
|
+
"keys": [
|
|
61
|
+
"c",
|
|
62
|
+
],
|
|
63
|
+
"message": "Unrecognized key: "c"",
|
|
64
|
+
"path": [],
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
`);
|
|
41
68
|
});
|
|
42
69
|
|
|
43
70
|
test("deep intersection", () => {
|
|
@@ -602,14 +602,31 @@ test("index signature in shape", () => {
|
|
|
602
602
|
expectTypeOf<schema>().toEqualTypeOf<Record<string, string>>();
|
|
603
603
|
});
|
|
604
604
|
|
|
605
|
-
test("
|
|
605
|
+
test("extend() on object with refinements should throw when overwriting properties", () => {
|
|
606
606
|
const schema = z
|
|
607
607
|
.object({
|
|
608
608
|
a: z.string(),
|
|
609
609
|
})
|
|
610
610
|
.refine(() => true);
|
|
611
611
|
|
|
612
|
-
expect(() => schema.extend({
|
|
612
|
+
expect(() => schema.extend({ a: z.number() })).toThrow();
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
test("extend() on object with refinements should not throw when adding new properties", () => {
|
|
616
|
+
const schema = z
|
|
617
|
+
.object({
|
|
618
|
+
a: z.string(),
|
|
619
|
+
})
|
|
620
|
+
.refine((data) => data.a.length > 0);
|
|
621
|
+
|
|
622
|
+
// Should not throw since 'b' doesn't overlap with 'a'
|
|
623
|
+
const extended = schema.extend({ b: z.number() });
|
|
624
|
+
|
|
625
|
+
// Verify the extended schema works correctly
|
|
626
|
+
expect(extended.parse({ a: "hello", b: 42 })).toEqual({ a: "hello", b: 42 });
|
|
627
|
+
|
|
628
|
+
// Verify the original refinement still applies
|
|
629
|
+
expect(() => extended.parse({ a: "", b: 42 })).toThrow();
|
|
613
630
|
});
|
|
614
631
|
|
|
615
632
|
test("safeExtend() on object with refinements should not throw", () => {
|
package/src/v4/core/schemas.ts
CHANGED
|
@@ -2460,12 +2460,39 @@ function mergeValues(
|
|
|
2460
2460
|
}
|
|
2461
2461
|
|
|
2462
2462
|
function handleIntersectionResults(result: ParsePayload, left: ParsePayload, right: ParsePayload): ParsePayload {
|
|
2463
|
-
|
|
2464
|
-
|
|
2463
|
+
// Track which side(s) report each key as unrecognized
|
|
2464
|
+
const unrecKeys = new Map<string, { l?: true; r?: true }>();
|
|
2465
|
+
let unrecIssue: errors.$ZodRawIssue | undefined;
|
|
2466
|
+
|
|
2467
|
+
for (const iss of left.issues) {
|
|
2468
|
+
if (iss.code === "unrecognized_keys") {
|
|
2469
|
+
unrecIssue ??= iss;
|
|
2470
|
+
for (const k of iss.keys) {
|
|
2471
|
+
if (!unrecKeys.has(k)) unrecKeys.set(k, {});
|
|
2472
|
+
unrecKeys.get(k)!.l = true;
|
|
2473
|
+
}
|
|
2474
|
+
} else {
|
|
2475
|
+
result.issues.push(iss);
|
|
2476
|
+
}
|
|
2465
2477
|
}
|
|
2466
|
-
|
|
2467
|
-
|
|
2478
|
+
|
|
2479
|
+
for (const iss of right.issues) {
|
|
2480
|
+
if (iss.code === "unrecognized_keys") {
|
|
2481
|
+
for (const k of iss.keys) {
|
|
2482
|
+
if (!unrecKeys.has(k)) unrecKeys.set(k, {});
|
|
2483
|
+
unrecKeys.get(k)!.r = true;
|
|
2484
|
+
}
|
|
2485
|
+
} else {
|
|
2486
|
+
result.issues.push(iss);
|
|
2487
|
+
}
|
|
2468
2488
|
}
|
|
2489
|
+
|
|
2490
|
+
// Report only keys unrecognized by BOTH sides
|
|
2491
|
+
const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);
|
|
2492
|
+
if (bothKeys.length && unrecIssue) {
|
|
2493
|
+
result.issues.push({ ...unrecIssue, keys: bothKeys });
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2469
2496
|
if (util.aborted(result)) return result;
|
|
2470
2497
|
|
|
2471
2498
|
const merged = mergeValues(left.value, right.value);
|
package/src/v4/core/util.ts
CHANGED
|
@@ -656,7 +656,14 @@ export function extend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): an
|
|
|
656
656
|
const checks = schema._zod.def.checks;
|
|
657
657
|
const hasChecks = checks && checks.length > 0;
|
|
658
658
|
if (hasChecks) {
|
|
659
|
-
|
|
659
|
+
// Only throw if new shape overlaps with existing shape
|
|
660
|
+
// Use getOwnPropertyDescriptor to check key existence without accessing values
|
|
661
|
+
const existingShape = schema._zod.def.shape;
|
|
662
|
+
for (const key in shape) {
|
|
663
|
+
if (Object.getOwnPropertyDescriptor(existingShape, key) !== undefined) {
|
|
664
|
+
throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
665
|
+
}
|
|
666
|
+
}
|
|
660
667
|
}
|
|
661
668
|
|
|
662
669
|
const def = mergeDefs(schema._zod.def, {
|
|
@@ -665,7 +672,6 @@ export function extend(schema: schemas.$ZodObject, shape: schemas.$ZodShape): an
|
|
|
665
672
|
assignProp(this, "shape", _shape); // self-caching
|
|
666
673
|
return _shape;
|
|
667
674
|
},
|
|
668
|
-
checks: [],
|
|
669
675
|
});
|
|
670
676
|
return clone(schema, def) as any;
|
|
671
677
|
}
|
package/src/v4/core/versions.ts
CHANGED
package/v4/core/schemas.cjs
CHANGED
|
@@ -1214,11 +1214,38 @@ function mergeValues(a, b) {
|
|
|
1214
1214
|
return { valid: false, mergeErrorPath: [] };
|
|
1215
1215
|
}
|
|
1216
1216
|
function handleIntersectionResults(result, left, right) {
|
|
1217
|
-
|
|
1218
|
-
|
|
1217
|
+
// Track which side(s) report each key as unrecognized
|
|
1218
|
+
const unrecKeys = new Map();
|
|
1219
|
+
let unrecIssue;
|
|
1220
|
+
for (const iss of left.issues) {
|
|
1221
|
+
if (iss.code === "unrecognized_keys") {
|
|
1222
|
+
unrecIssue ?? (unrecIssue = iss);
|
|
1223
|
+
for (const k of iss.keys) {
|
|
1224
|
+
if (!unrecKeys.has(k))
|
|
1225
|
+
unrecKeys.set(k, {});
|
|
1226
|
+
unrecKeys.get(k).l = true;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
result.issues.push(iss);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
for (const iss of right.issues) {
|
|
1234
|
+
if (iss.code === "unrecognized_keys") {
|
|
1235
|
+
for (const k of iss.keys) {
|
|
1236
|
+
if (!unrecKeys.has(k))
|
|
1237
|
+
unrecKeys.set(k, {});
|
|
1238
|
+
unrecKeys.get(k).r = true;
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
else {
|
|
1242
|
+
result.issues.push(iss);
|
|
1243
|
+
}
|
|
1219
1244
|
}
|
|
1220
|
-
|
|
1221
|
-
|
|
1245
|
+
// Report only keys unrecognized by BOTH sides
|
|
1246
|
+
const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);
|
|
1247
|
+
if (bothKeys.length && unrecIssue) {
|
|
1248
|
+
result.issues.push({ ...unrecIssue, keys: bothKeys });
|
|
1222
1249
|
}
|
|
1223
1250
|
if (util.aborted(result))
|
|
1224
1251
|
return result;
|
package/v4/core/schemas.js
CHANGED
|
@@ -1183,11 +1183,38 @@ function mergeValues(a, b) {
|
|
|
1183
1183
|
return { valid: false, mergeErrorPath: [] };
|
|
1184
1184
|
}
|
|
1185
1185
|
function handleIntersectionResults(result, left, right) {
|
|
1186
|
-
|
|
1187
|
-
|
|
1186
|
+
// Track which side(s) report each key as unrecognized
|
|
1187
|
+
const unrecKeys = new Map();
|
|
1188
|
+
let unrecIssue;
|
|
1189
|
+
for (const iss of left.issues) {
|
|
1190
|
+
if (iss.code === "unrecognized_keys") {
|
|
1191
|
+
unrecIssue ?? (unrecIssue = iss);
|
|
1192
|
+
for (const k of iss.keys) {
|
|
1193
|
+
if (!unrecKeys.has(k))
|
|
1194
|
+
unrecKeys.set(k, {});
|
|
1195
|
+
unrecKeys.get(k).l = true;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
else {
|
|
1199
|
+
result.issues.push(iss);
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
for (const iss of right.issues) {
|
|
1203
|
+
if (iss.code === "unrecognized_keys") {
|
|
1204
|
+
for (const k of iss.keys) {
|
|
1205
|
+
if (!unrecKeys.has(k))
|
|
1206
|
+
unrecKeys.set(k, {});
|
|
1207
|
+
unrecKeys.get(k).r = true;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
else {
|
|
1211
|
+
result.issues.push(iss);
|
|
1212
|
+
}
|
|
1188
1213
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1214
|
+
// Report only keys unrecognized by BOTH sides
|
|
1215
|
+
const bothKeys = [...unrecKeys].filter(([, f]) => f.l && f.r).map(([k]) => k);
|
|
1216
|
+
if (bothKeys.length && unrecIssue) {
|
|
1217
|
+
result.issues.push({ ...unrecIssue, keys: bothKeys });
|
|
1191
1218
|
}
|
|
1192
1219
|
if (util.aborted(result))
|
|
1193
1220
|
return result;
|
package/v4/core/util.cjs
CHANGED
|
@@ -440,7 +440,14 @@ function extend(schema, shape) {
|
|
|
440
440
|
const checks = schema._zod.def.checks;
|
|
441
441
|
const hasChecks = checks && checks.length > 0;
|
|
442
442
|
if (hasChecks) {
|
|
443
|
-
|
|
443
|
+
// Only throw if new shape overlaps with existing shape
|
|
444
|
+
// Use getOwnPropertyDescriptor to check key existence without accessing values
|
|
445
|
+
const existingShape = schema._zod.def.shape;
|
|
446
|
+
for (const key in shape) {
|
|
447
|
+
if (Object.getOwnPropertyDescriptor(existingShape, key) !== undefined) {
|
|
448
|
+
throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
449
|
+
}
|
|
450
|
+
}
|
|
444
451
|
}
|
|
445
452
|
const def = mergeDefs(schema._zod.def, {
|
|
446
453
|
get shape() {
|
|
@@ -448,7 +455,6 @@ function extend(schema, shape) {
|
|
|
448
455
|
assignProp(this, "shape", _shape); // self-caching
|
|
449
456
|
return _shape;
|
|
450
457
|
},
|
|
451
|
-
checks: [],
|
|
452
458
|
});
|
|
453
459
|
return clone(schema, def);
|
|
454
460
|
}
|
package/v4/core/util.js
CHANGED
|
@@ -382,7 +382,14 @@ export function extend(schema, shape) {
|
|
|
382
382
|
const checks = schema._zod.def.checks;
|
|
383
383
|
const hasChecks = checks && checks.length > 0;
|
|
384
384
|
if (hasChecks) {
|
|
385
|
-
|
|
385
|
+
// Only throw if new shape overlaps with existing shape
|
|
386
|
+
// Use getOwnPropertyDescriptor to check key existence without accessing values
|
|
387
|
+
const existingShape = schema._zod.def.shape;
|
|
388
|
+
for (const key in shape) {
|
|
389
|
+
if (Object.getOwnPropertyDescriptor(existingShape, key) !== undefined) {
|
|
390
|
+
throw new Error("Cannot overwrite keys on object schemas containing refinements. Use `.safeExtend()` instead.");
|
|
391
|
+
}
|
|
392
|
+
}
|
|
386
393
|
}
|
|
387
394
|
const def = mergeDefs(schema._zod.def, {
|
|
388
395
|
get shape() {
|
|
@@ -390,7 +397,6 @@ export function extend(schema, shape) {
|
|
|
390
397
|
assignProp(this, "shape", _shape); // self-caching
|
|
391
398
|
return _shape;
|
|
392
399
|
},
|
|
393
|
-
checks: [],
|
|
394
400
|
});
|
|
395
401
|
return clone(schema, def);
|
|
396
402
|
}
|
package/v4/core/versions.cjs
CHANGED
package/v4/core/versions.js
CHANGED