zod 4.3.0-canary.20251222T205904 → 4.3.0-canary.20251223T023855
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/object.test.ts +14 -0
- package/src/v4/classic/tests/partial.test.ts +87 -0
- package/src/v4/classic/tests/pickomit.test.ts +75 -0
- package/src/v4/classic/tests/recursive-types.test.ts +1 -1
- package/src/v4/core/tests/extend.test.ts +3 -3
- package/src/v4/core/util.ts +21 -5
- package/v4/core/util.cjs +18 -5
- package/v4/core/util.js +18 -5
package/package.json
CHANGED
|
@@ -435,6 +435,20 @@ test("extend() should have power to override existing key", () => {
|
|
|
435
435
|
expectTypeOf<PersonWithNumberAsLastName>().toEqualTypeOf<{ firstName: string; lastName: number }>();
|
|
436
436
|
});
|
|
437
437
|
|
|
438
|
+
test("safeExtend() should have power to override existing key", () => {
|
|
439
|
+
const PersonWithMinLastName = personToExtend.safeExtend({
|
|
440
|
+
lastName: z.string().min(3),
|
|
441
|
+
});
|
|
442
|
+
type PersonWithMinLastName = z.infer<typeof PersonWithMinLastName>;
|
|
443
|
+
|
|
444
|
+
const expected = { firstName: "f", lastName: "abc" };
|
|
445
|
+
const actual = PersonWithMinLastName.parse(expected);
|
|
446
|
+
|
|
447
|
+
expect(actual).toEqual(expected);
|
|
448
|
+
expect(() => PersonWithMinLastName.parse({ firstName: "f", lastName: "ab" })).toThrow();
|
|
449
|
+
expectTypeOf<PersonWithMinLastName>().toEqualTypeOf<{ firstName: string; lastName: string }>();
|
|
450
|
+
});
|
|
451
|
+
|
|
438
452
|
test("safeExtend() maintains refinements", () => {
|
|
439
453
|
const schema = z.object({ name: z.string().min(1) });
|
|
440
454
|
const extended = schema.safeExtend({ name: z.string().min(2) });
|
|
@@ -338,3 +338,90 @@ test("optional with check", () => {
|
|
|
338
338
|
}
|
|
339
339
|
`);
|
|
340
340
|
});
|
|
341
|
+
|
|
342
|
+
test("partial - throws error on schema with refinements", () => {
|
|
343
|
+
const baseSchema = z.object({
|
|
344
|
+
id: z.string(),
|
|
345
|
+
name: z.string(),
|
|
346
|
+
items: z.string().array(),
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
const refinedSchema = baseSchema.superRefine((val, ctx) => {
|
|
350
|
+
if (val.items.length === 0) {
|
|
351
|
+
ctx.addIssue({
|
|
352
|
+
message: "Must have at least one item",
|
|
353
|
+
code: "custom",
|
|
354
|
+
path: ["items"],
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(() => refinedSchema.partial()).toThrow(".partial() cannot be used on object schemas containing refinements");
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
test("partial - throws error on schema with refine", () => {
|
|
363
|
+
const baseSchema = z.object({
|
|
364
|
+
password: z.string(),
|
|
365
|
+
confirmPassword: z.string(),
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
|
|
369
|
+
message: "Passwords must match",
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
expect(() => refinedSchema.partial()).toThrow(".partial() cannot be used on object schemas containing refinements");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
test("required - preserves refinements", () => {
|
|
376
|
+
const baseSchema = z.object({
|
|
377
|
+
name: z.string().optional(),
|
|
378
|
+
age: z.number().optional(),
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
const refinedSchema = baseSchema.superRefine((val, ctx) => {
|
|
382
|
+
if (val.name === "admin") {
|
|
383
|
+
ctx.addIssue({
|
|
384
|
+
message: "Name cannot be admin",
|
|
385
|
+
code: "custom",
|
|
386
|
+
path: ["name"],
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const requiredSchema = refinedSchema.required();
|
|
392
|
+
|
|
393
|
+
// The refinement should still be applied
|
|
394
|
+
const result = requiredSchema.safeParse({ name: "admin", age: 25 });
|
|
395
|
+
expect(result.success).toBe(false);
|
|
396
|
+
if (!result.success) {
|
|
397
|
+
expect(result.error.issues[0].message).toBe("Name cannot be admin");
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Valid data should pass
|
|
401
|
+
const validResult = requiredSchema.safeParse({ name: "user", age: 25 });
|
|
402
|
+
expect(validResult.success).toBe(true);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
test("required - refinement is executed on required schema", () => {
|
|
406
|
+
const baseSchema = z.object({
|
|
407
|
+
password: z.string().optional(),
|
|
408
|
+
confirmPassword: z.string().optional(),
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
|
|
412
|
+
message: "Passwords must match",
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
const requiredSchema = refinedSchema.required();
|
|
416
|
+
|
|
417
|
+
// Mismatched passwords should fail refinement
|
|
418
|
+
const result = requiredSchema.safeParse({ password: "abc", confirmPassword: "xyz" });
|
|
419
|
+
expect(result.success).toBe(false);
|
|
420
|
+
if (!result.success) {
|
|
421
|
+
expect(result.error.issues[0].message).toBe("Passwords must match");
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Matching passwords should pass
|
|
425
|
+
const validResult = requiredSchema.safeParse({ password: "abc", confirmPassword: "abc" });
|
|
426
|
+
expect(validResult.success).toBe(true);
|
|
427
|
+
});
|
|
@@ -125,3 +125,78 @@ test("pick/omit/required/partial - do not allow unknown keys", () => {
|
|
|
125
125
|
// @ts-expect-error
|
|
126
126
|
expect(() => schema.partial({ $unknown: true }).safeParse({})).toThrow();
|
|
127
127
|
});
|
|
128
|
+
|
|
129
|
+
test("pick - throws error on schema with refinements", () => {
|
|
130
|
+
const baseSchema = z.object({
|
|
131
|
+
id: z.string(),
|
|
132
|
+
name: z.string(),
|
|
133
|
+
items: z.string().array(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const refinedSchema = baseSchema.superRefine((val, ctx) => {
|
|
137
|
+
if (val.items.length === 0) {
|
|
138
|
+
ctx.addIssue({
|
|
139
|
+
message: "Must have at least one item",
|
|
140
|
+
code: "custom",
|
|
141
|
+
path: ["items"],
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
expect(() => refinedSchema.pick({ name: true })).toThrow(
|
|
147
|
+
".pick() cannot be used on object schemas containing refinements"
|
|
148
|
+
);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("omit - throws error on schema with refinements", () => {
|
|
152
|
+
const baseSchema = z.object({
|
|
153
|
+
id: z.string(),
|
|
154
|
+
name: z.string(),
|
|
155
|
+
items: z.string().array(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const refinedSchema = baseSchema.superRefine((val, ctx) => {
|
|
159
|
+
if (val.items.length === 0) {
|
|
160
|
+
ctx.addIssue({
|
|
161
|
+
message: "Must have at least one item",
|
|
162
|
+
code: "custom",
|
|
163
|
+
path: ["items"],
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
expect(() => refinedSchema.omit({ id: true })).toThrow(
|
|
169
|
+
".omit() cannot be used on object schemas containing refinements"
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("pick - throws error on schema with refine", () => {
|
|
174
|
+
const baseSchema = z.object({
|
|
175
|
+
password: z.string(),
|
|
176
|
+
confirmPassword: z.string(),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
|
|
180
|
+
message: "Passwords must match",
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
expect(() => refinedSchema.pick({ password: true })).toThrow(
|
|
184
|
+
".pick() cannot be used on object schemas containing refinements"
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("omit - throws error on schema with refine", () => {
|
|
189
|
+
const baseSchema = z.object({
|
|
190
|
+
password: z.string(),
|
|
191
|
+
confirmPassword: z.string(),
|
|
192
|
+
email: z.string(),
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
const refinedSchema = baseSchema.refine((data) => data.password === data.confirmPassword, {
|
|
196
|
+
message: "Passwords must match",
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
expect(() => refinedSchema.omit({ email: true })).toThrow(
|
|
200
|
+
".omit() cannot be used on object schemas containing refinements"
|
|
201
|
+
);
|
|
202
|
+
});
|
|
@@ -308,7 +308,7 @@ test("object utilities with recursive types", () => {
|
|
|
308
308
|
},
|
|
309
309
|
});
|
|
310
310
|
|
|
311
|
-
// Test extend
|
|
311
|
+
// Test extend with new keys (extend throws when overwriting existing keys)
|
|
312
312
|
const NodeOne = NodeBase.extend({
|
|
313
313
|
name: z.literal("nodeOne"),
|
|
314
314
|
get children() {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
2
|
import * as z from "zod/v4";
|
|
3
3
|
|
|
4
|
-
test("
|
|
4
|
+
test("safeExtend chaining preserves and overrides properties", () => {
|
|
5
5
|
const schema1 = z.object({
|
|
6
6
|
email: z.string(),
|
|
7
7
|
});
|
|
8
8
|
|
|
9
|
-
const schema2 = schema1.
|
|
9
|
+
const schema2 = schema1.safeExtend({
|
|
10
10
|
email: schema1.shape.email.check(z.email()),
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
const schema3 = schema2.
|
|
13
|
+
const schema3 = schema2.safeExtend({
|
|
14
14
|
email: schema2.shape.email.or(z.literal("")),
|
|
15
15
|
});
|
|
16
16
|
|
package/src/v4/core/util.ts
CHANGED
|
@@ -593,6 +593,12 @@ export const BIGINT_FORMAT_RANGES: Record<checks.$ZodBigIntFormats, [bigint, big
|
|
|
593
593
|
export function pick(schema: schemas.$ZodObject, mask: Record<string, unknown>): any {
|
|
594
594
|
const currDef = schema._zod.def;
|
|
595
595
|
|
|
596
|
+
const checks = currDef.checks;
|
|
597
|
+
const hasChecks = checks && checks.length > 0;
|
|
598
|
+
if (hasChecks) {
|
|
599
|
+
throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
600
|
+
}
|
|
601
|
+
|
|
596
602
|
const def = mergeDefs(schema._zod.def, {
|
|
597
603
|
get shape() {
|
|
598
604
|
const newShape: Writeable<schemas.$ZodShape> = {};
|
|
@@ -616,6 +622,12 @@ export function pick(schema: schemas.$ZodObject, mask: Record<string, unknown>):
|
|
|
616
622
|
export function omit(schema: schemas.$ZodObject, mask: object): any {
|
|
617
623
|
const currDef = schema._zod.def;
|
|
618
624
|
|
|
625
|
+
const checks = currDef.checks;
|
|
626
|
+
const hasChecks = checks && checks.length > 0;
|
|
627
|
+
if (hasChecks) {
|
|
628
|
+
throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
629
|
+
}
|
|
630
|
+
|
|
619
631
|
const def = mergeDefs(schema._zod.def, {
|
|
620
632
|
get shape() {
|
|
621
633
|
const newShape: Writeable<schemas.$ZodShape> = { ...schema._zod.def.shape };
|
|
@@ -662,15 +674,13 @@ export function safeExtend(schema: schemas.$ZodObject, shape: schemas.$ZodShape)
|
|
|
662
674
|
if (!isPlainObject(shape)) {
|
|
663
675
|
throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
664
676
|
}
|
|
665
|
-
const def = {
|
|
666
|
-
...schema._zod.def,
|
|
677
|
+
const def = mergeDefs(schema._zod.def, {
|
|
667
678
|
get shape() {
|
|
668
679
|
const _shape = { ...schema._zod.def.shape, ...shape };
|
|
669
680
|
assignProp(this, "shape", _shape); // self-caching
|
|
670
681
|
return _shape;
|
|
671
682
|
},
|
|
672
|
-
|
|
673
|
-
} as any;
|
|
683
|
+
});
|
|
674
684
|
return clone(schema, def) as any;
|
|
675
685
|
}
|
|
676
686
|
|
|
@@ -695,6 +705,13 @@ export function partial(
|
|
|
695
705
|
schema: schemas.$ZodObject,
|
|
696
706
|
mask: object | undefined
|
|
697
707
|
): any {
|
|
708
|
+
const currDef = schema._zod.def;
|
|
709
|
+
const checks = currDef.checks;
|
|
710
|
+
const hasChecks = checks && checks.length > 0;
|
|
711
|
+
if (hasChecks) {
|
|
712
|
+
throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
713
|
+
}
|
|
714
|
+
|
|
698
715
|
const def = mergeDefs(schema._zod.def, {
|
|
699
716
|
get shape() {
|
|
700
717
|
const oldShape = schema._zod.def.shape;
|
|
@@ -770,7 +787,6 @@ export function required(
|
|
|
770
787
|
assignProp(this, "shape", shape); // self-caching
|
|
771
788
|
return shape;
|
|
772
789
|
},
|
|
773
|
-
checks: [],
|
|
774
790
|
});
|
|
775
791
|
|
|
776
792
|
return clone(schema, def) as any;
|
package/v4/core/util.cjs
CHANGED
|
@@ -384,6 +384,11 @@ exports.BIGINT_FORMAT_RANGES = {
|
|
|
384
384
|
};
|
|
385
385
|
function pick(schema, mask) {
|
|
386
386
|
const currDef = schema._zod.def;
|
|
387
|
+
const checks = currDef.checks;
|
|
388
|
+
const hasChecks = checks && checks.length > 0;
|
|
389
|
+
if (hasChecks) {
|
|
390
|
+
throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
391
|
+
}
|
|
387
392
|
const def = mergeDefs(schema._zod.def, {
|
|
388
393
|
get shape() {
|
|
389
394
|
const newShape = {};
|
|
@@ -404,6 +409,11 @@ function pick(schema, mask) {
|
|
|
404
409
|
}
|
|
405
410
|
function omit(schema, mask) {
|
|
406
411
|
const currDef = schema._zod.def;
|
|
412
|
+
const checks = currDef.checks;
|
|
413
|
+
const hasChecks = checks && checks.length > 0;
|
|
414
|
+
if (hasChecks) {
|
|
415
|
+
throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
416
|
+
}
|
|
407
417
|
const def = mergeDefs(schema._zod.def, {
|
|
408
418
|
get shape() {
|
|
409
419
|
const newShape = { ...schema._zod.def.shape };
|
|
@@ -445,15 +455,13 @@ function safeExtend(schema, shape) {
|
|
|
445
455
|
if (!isPlainObject(shape)) {
|
|
446
456
|
throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
447
457
|
}
|
|
448
|
-
const def = {
|
|
449
|
-
...schema._zod.def,
|
|
458
|
+
const def = mergeDefs(schema._zod.def, {
|
|
450
459
|
get shape() {
|
|
451
460
|
const _shape = { ...schema._zod.def.shape, ...shape };
|
|
452
461
|
assignProp(this, "shape", _shape); // self-caching
|
|
453
462
|
return _shape;
|
|
454
463
|
},
|
|
455
|
-
|
|
456
|
-
};
|
|
464
|
+
});
|
|
457
465
|
return clone(schema, def);
|
|
458
466
|
}
|
|
459
467
|
function merge(a, b) {
|
|
@@ -471,6 +479,12 @@ function merge(a, b) {
|
|
|
471
479
|
return clone(a, def);
|
|
472
480
|
}
|
|
473
481
|
function partial(Class, schema, mask) {
|
|
482
|
+
const currDef = schema._zod.def;
|
|
483
|
+
const checks = currDef.checks;
|
|
484
|
+
const hasChecks = checks && checks.length > 0;
|
|
485
|
+
if (hasChecks) {
|
|
486
|
+
throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
487
|
+
}
|
|
474
488
|
const def = mergeDefs(schema._zod.def, {
|
|
475
489
|
get shape() {
|
|
476
490
|
const oldShape = schema._zod.def.shape;
|
|
@@ -540,7 +554,6 @@ function required(Class, schema, mask) {
|
|
|
540
554
|
assignProp(this, "shape", shape); // self-caching
|
|
541
555
|
return shape;
|
|
542
556
|
},
|
|
543
|
-
checks: [],
|
|
544
557
|
});
|
|
545
558
|
return clone(schema, def);
|
|
546
559
|
}
|
package/v4/core/util.js
CHANGED
|
@@ -327,6 +327,11 @@ export const BIGINT_FORMAT_RANGES = {
|
|
|
327
327
|
};
|
|
328
328
|
export function pick(schema, mask) {
|
|
329
329
|
const currDef = schema._zod.def;
|
|
330
|
+
const checks = currDef.checks;
|
|
331
|
+
const hasChecks = checks && checks.length > 0;
|
|
332
|
+
if (hasChecks) {
|
|
333
|
+
throw new Error(".pick() cannot be used on object schemas containing refinements");
|
|
334
|
+
}
|
|
330
335
|
const def = mergeDefs(schema._zod.def, {
|
|
331
336
|
get shape() {
|
|
332
337
|
const newShape = {};
|
|
@@ -347,6 +352,11 @@ export function pick(schema, mask) {
|
|
|
347
352
|
}
|
|
348
353
|
export function omit(schema, mask) {
|
|
349
354
|
const currDef = schema._zod.def;
|
|
355
|
+
const checks = currDef.checks;
|
|
356
|
+
const hasChecks = checks && checks.length > 0;
|
|
357
|
+
if (hasChecks) {
|
|
358
|
+
throw new Error(".omit() cannot be used on object schemas containing refinements");
|
|
359
|
+
}
|
|
350
360
|
const def = mergeDefs(schema._zod.def, {
|
|
351
361
|
get shape() {
|
|
352
362
|
const newShape = { ...schema._zod.def.shape };
|
|
@@ -388,15 +398,13 @@ export function safeExtend(schema, shape) {
|
|
|
388
398
|
if (!isPlainObject(shape)) {
|
|
389
399
|
throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
390
400
|
}
|
|
391
|
-
const def = {
|
|
392
|
-
...schema._zod.def,
|
|
401
|
+
const def = mergeDefs(schema._zod.def, {
|
|
393
402
|
get shape() {
|
|
394
403
|
const _shape = { ...schema._zod.def.shape, ...shape };
|
|
395
404
|
assignProp(this, "shape", _shape); // self-caching
|
|
396
405
|
return _shape;
|
|
397
406
|
},
|
|
398
|
-
|
|
399
|
-
};
|
|
407
|
+
});
|
|
400
408
|
return clone(schema, def);
|
|
401
409
|
}
|
|
402
410
|
export function merge(a, b) {
|
|
@@ -414,6 +422,12 @@ export function merge(a, b) {
|
|
|
414
422
|
return clone(a, def);
|
|
415
423
|
}
|
|
416
424
|
export function partial(Class, schema, mask) {
|
|
425
|
+
const currDef = schema._zod.def;
|
|
426
|
+
const checks = currDef.checks;
|
|
427
|
+
const hasChecks = checks && checks.length > 0;
|
|
428
|
+
if (hasChecks) {
|
|
429
|
+
throw new Error(".partial() cannot be used on object schemas containing refinements");
|
|
430
|
+
}
|
|
417
431
|
const def = mergeDefs(schema._zod.def, {
|
|
418
432
|
get shape() {
|
|
419
433
|
const oldShape = schema._zod.def.shape;
|
|
@@ -483,7 +497,6 @@ export function required(Class, schema, mask) {
|
|
|
483
497
|
assignProp(this, "shape", shape); // self-caching
|
|
484
498
|
return shape;
|
|
485
499
|
},
|
|
486
|
-
checks: [],
|
|
487
500
|
});
|
|
488
501
|
return clone(schema, def);
|
|
489
502
|
}
|