zod 4.0.5 → 4.0.6

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 (86) hide show
  1. package/package.json +1 -1
  2. package/src/v3/tests/string.test.ts +2 -2
  3. package/src/v3/types.ts +3 -1
  4. package/src/v4/classic/errors.ts +9 -2
  5. package/src/v4/classic/schemas.ts +11 -9
  6. package/src/v4/classic/tests/catch.test.ts +4 -5
  7. package/src/v4/classic/tests/discriminated-unions.test.ts +12 -0
  8. package/src/v4/classic/tests/error-utils.test.ts +43 -0
  9. package/src/v4/classic/tests/literal.test.ts +25 -0
  10. package/src/v4/classic/tests/partial.test.ts +193 -0
  11. package/src/v4/classic/tests/pickomit.test.ts +5 -5
  12. package/src/v4/classic/tests/preprocess.test.ts +4 -15
  13. package/src/v4/classic/tests/record.test.ts +15 -1
  14. package/src/v4/classic/tests/recursive-types.test.ts +67 -0
  15. package/src/v4/classic/tests/string.test.ts +81 -4
  16. package/src/v4/classic/tests/template-literal.test.ts +3 -0
  17. package/src/v4/classic/tests/to-json-schema.test.ts +1 -0
  18. package/src/v4/classic/tests/transform.test.ts +110 -0
  19. package/src/v4/classic/tests/union.test.ts +45 -3
  20. package/src/v4/core/checks.ts +2 -2
  21. package/src/v4/core/errors.ts +8 -15
  22. package/src/v4/core/regexes.ts +1 -1
  23. package/src/v4/core/registries.ts +3 -2
  24. package/src/v4/core/schemas.ts +90 -98
  25. package/src/v4/core/to-json-schema.ts +1 -0
  26. package/src/v4/core/util.ts +175 -115
  27. package/src/v4/core/versions.ts +1 -1
  28. package/src/v4/locales/bg.ts +136 -0
  29. package/src/v4/locales/da.ts +141 -0
  30. package/src/v4/locales/index.ts +2 -0
  31. package/src/v4/locales/is.ts +127 -0
  32. package/src/v4/mini/schemas.ts +3 -1
  33. package/v3/types.cjs +2 -0
  34. package/v3/types.d.cts +4 -1
  35. package/v3/types.d.ts +4 -1
  36. package/v3/types.js +2 -0
  37. package/v4/classic/errors.cjs +9 -2
  38. package/v4/classic/errors.js +9 -2
  39. package/v4/classic/schemas.cjs +5 -3
  40. package/v4/classic/schemas.d.cts +3 -3
  41. package/v4/classic/schemas.d.ts +3 -3
  42. package/v4/classic/schemas.js +5 -3
  43. package/v4/core/checks.d.cts +2 -2
  44. package/v4/core/checks.d.ts +2 -2
  45. package/v4/core/errors.cjs +4 -9
  46. package/v4/core/errors.d.cts +4 -6
  47. package/v4/core/errors.d.ts +4 -6
  48. package/v4/core/errors.js +4 -9
  49. package/v4/core/regexes.cjs +1 -1
  50. package/v4/core/regexes.d.cts +1 -1
  51. package/v4/core/regexes.d.ts +1 -1
  52. package/v4/core/regexes.js +1 -1
  53. package/v4/core/registries.cjs +2 -1
  54. package/v4/core/registries.d.cts +1 -1
  55. package/v4/core/registries.d.ts +1 -1
  56. package/v4/core/registries.js +2 -1
  57. package/v4/core/schemas.cjs +47 -87
  58. package/v4/core/schemas.d.cts +9 -4
  59. package/v4/core/schemas.d.ts +9 -4
  60. package/v4/core/schemas.js +47 -87
  61. package/v4/core/to-json-schema.cjs +1 -0
  62. package/v4/core/to-json-schema.js +1 -0
  63. package/v4/core/util.cjs +163 -112
  64. package/v4/core/util.d.cts +1 -0
  65. package/v4/core/util.d.ts +1 -0
  66. package/v4/core/util.js +162 -112
  67. package/v4/core/versions.cjs +1 -1
  68. package/v4/core/versions.js +1 -1
  69. package/v4/locales/bg.cjs +156 -0
  70. package/v4/locales/bg.d.cts +5 -0
  71. package/v4/locales/bg.d.ts +5 -0
  72. package/v4/locales/bg.js +128 -0
  73. package/v4/locales/da.cjs +157 -0
  74. package/v4/locales/da.d.cts +4 -0
  75. package/v4/locales/da.d.ts +4 -0
  76. package/v4/locales/da.js +131 -0
  77. package/v4/locales/index.cjs +5 -1
  78. package/v4/locales/index.d.cts +2 -0
  79. package/v4/locales/index.d.ts +2 -0
  80. package/v4/locales/index.js +2 -0
  81. package/v4/locales/is.cjs +145 -0
  82. package/v4/locales/is.d.cts +5 -0
  83. package/v4/locales/is.d.ts +5 -0
  84. package/v4/locales/is.js +117 -0
  85. package/v4/mini/schemas.cjs +3 -1
  86. package/v4/mini/schemas.js +3 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zod",
3
- "version": "4.0.5",
3
+ "version": "4.0.6",
4
4
  "type": "module",
5
5
  "author": "Colin McDonnell <zod@colinhacks.com>",
6
6
  "description": "TypeScript-first schema declaration and validation library with static type inference",
@@ -342,8 +342,8 @@ test("uuid", () => {
342
342
  uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0");
343
343
  uuid.parse("d89e7b01-7598-ed11-9d7a-0022489382fd"); // new sequential id
344
344
  uuid.parse("00000000-0000-0000-0000-000000000000");
345
- uuid.parse("b3ce60f8-e8b9-40f5-1150-172ede56ff74"); // Variant 0 - RFC 4122: Reserved, NCS backward compatibility
346
- uuid.parse("92e76bf9-28b3-4730-cd7f-cb6bc51f8c09"); // Variant 2 - RFC 4122: Reserved, Microsoft Corporation backward compatibility
345
+ uuid.parse("b3ce60f8-e8b9-40f5-1150-172ede56ff74"); // Variant 0 - RFC 9562/4122: Reserved, NCS backward compatibility
346
+ uuid.parse("92e76bf9-28b3-4730-cd7f-cb6bc51f8c09"); // Variant 2 - RFC 9562/4122: Reserved, Microsoft Corporation backward compatibility
347
347
  const result = uuid.safeParse("9491d710-3185-4e06-bea0-6a2f275345e0X");
348
348
  expect(result.success).toEqual(false);
349
349
  if (!result.success) {
package/src/v3/types.ts CHANGED
@@ -705,6 +705,7 @@ function isValidJWT(jwt: string, alg?: string): boolean {
705
705
  .replace(/-/g, "+")
706
706
  .replace(/_/g, "/")
707
707
  .padEnd(header.length + ((4 - (header.length % 4)) % 4), "=");
708
+ // @ts-ignore
708
709
  const decoded = JSON.parse(atob(base64));
709
710
  if (typeof decoded !== "object" || decoded === null) return false;
710
711
  if ("typ" in decoded && decoded?.typ !== "JWT") return false;
@@ -875,6 +876,7 @@ export class ZodString extends ZodType<string, ZodStringDef, string> {
875
876
  }
876
877
  } else if (check.kind === "url") {
877
878
  try {
879
+ // @ts-ignore
878
880
  new URL(input.data);
879
881
  } catch {
880
882
  ctx = this._getOrReturnCtx(input, ctx);
@@ -2454,7 +2456,7 @@ export class ZodObject<
2454
2456
  Output = objectOutputType<T, Catchall, UnknownKeys>,
2455
2457
  Input = objectInputType<T, Catchall, UnknownKeys>,
2456
2458
  > extends ZodType<Output, ZodObjectDef<T, UnknownKeys, Catchall>, Input> {
2457
- private _cached: { shape: T; keys: string[] } | null = null;
2459
+ _cached: { shape: T; keys: string[] } | null = null;
2458
2460
 
2459
2461
  _getCached(): { shape: T; keys: string[] } {
2460
2462
  if (this._cached !== null) return this._cached;
@@ -1,5 +1,6 @@
1
1
  import * as core from "../core/index.js";
2
2
  import { $ZodError } from "../core/index.js";
3
+ import * as util from "../core/util.js";
3
4
 
4
5
  /** @deprecated Use `z.core.$ZodIssue` from `@zod/core` instead, especially if you are building a library on top of Zod. */
5
6
  export type ZodIssue = core.$ZodIssue;
@@ -34,11 +35,17 @@ const initializer = (inst: ZodError, issues: core.$ZodIssue[]) => {
34
35
  // enumerable: false,
35
36
  },
36
37
  addIssue: {
37
- value: (issue: any) => inst.issues.push(issue),
38
+ value: (issue: any) => {
39
+ inst.issues.push(issue);
40
+ inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
41
+ },
38
42
  // enumerable: false,
39
43
  },
40
44
  addIssues: {
41
- value: (issues: any) => inst.issues.push(...issues),
45
+ value: (issues: any) => {
46
+ inst.issues.push(...issues);
47
+ inst.message = JSON.stringify(inst.issues, util.jsonStringifyReplacer, 2);
48
+ },
42
49
  // enumerable: false,
43
50
  },
44
51
  isEmpty: {
@@ -61,8 +61,6 @@ export interface ZodType<
61
61
 
62
62
  // refinements
63
63
  refine(check: (arg: core.output<this>) => unknown | Promise<unknown>, params?: string | core.$ZodCustomParams): this;
64
- /** @deprecated Use [`.check()`](https://zod.dev/api?id=check) instead.
65
- */
66
64
  superRefine(
67
65
  refinement: (arg: core.output<this>, ctx: RefinementCtx<core.output<this>>) => void | Promise<void>
68
66
  ): this;
@@ -1070,9 +1068,7 @@ export interface ZodObject<
1070
1068
  /** This is the default behavior. This method call is likely unnecessary. */
1071
1069
  strip(): ZodObject<Shape, core.$strip>;
1072
1070
 
1073
- extend<U extends core.$ZodLooseShape & Partial<Record<keyof Shape, core.SomeType>>>(
1074
- shape: U
1075
- ): ZodObject<util.Extend<Shape, U>, Config>;
1071
+ extend<U extends core.$ZodLooseShape>(shape: U): ZodObject<util.Extend<Shape, U>, Config>;
1076
1072
 
1077
1073
  /**
1078
1074
  * @deprecated Use [`A.extend(B.shape)`](https://zod.dev/api?id=extend) instead.
@@ -1132,7 +1128,6 @@ export const ZodObject: core.$constructor<ZodObject> = /*@__PURE__*/ core.$const
1132
1128
  inst.keyof = () => _enum(Object.keys(inst._zod.def.shape)) as any;
1133
1129
  inst.catchall = (catchall) => inst.clone({ ...inst._zod.def, catchall: catchall as any as core.$ZodType }) as any;
1134
1130
  inst.passthrough = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
1135
- // inst.nonstrict = () => inst.clone({ ...inst._zod.def, catchall: api.unknown() });
1136
1131
  inst.loose = () => inst.clone({ ...inst._zod.def, catchall: unknown() });
1137
1132
  inst.strict = () => inst.clone({ ...inst._zod.def, catchall: never() });
1138
1133
  inst.strip = () => inst.clone({ ...inst._zod.def, catchall: undefined });
@@ -1224,6 +1219,7 @@ export interface ZodDiscriminatedUnion<Options extends readonly core.SomeType[]
1224
1219
  extends ZodUnion<Options>,
1225
1220
  core.$ZodDiscriminatedUnion<Options> {
1226
1221
  _zod: core.$ZodDiscriminatedUnionInternals<Options>;
1222
+ def: core.$ZodDiscriminatedUnionDef<Options>;
1227
1223
  }
1228
1224
  export const ZodDiscriminatedUnion: core.$constructor<ZodDiscriminatedUnion> = /*@__PURE__*/ core.$constructor(
1229
1225
  "ZodDiscriminatedUnion",
@@ -1351,9 +1347,11 @@ export function partialRecord<Key extends core.$ZodRecordKey, Value extends core
1351
1347
  valueType: Value,
1352
1348
  params?: string | core.$ZodRecordParams
1353
1349
  ): ZodRecord<Key & core.$partial, Value> {
1350
+ const k = core.clone(keyType);
1351
+ k._zod.values = undefined;
1354
1352
  return new ZodRecord({
1355
1353
  type: "record",
1356
- keyType: union([keyType, never()]),
1354
+ keyType: k,
1357
1355
  valueType: valueType as any,
1358
1356
  ...util.normalizeParams(params),
1359
1357
  }) as any;
@@ -1585,7 +1583,7 @@ export const ZodTransform: core.$constructor<ZodTransform> = /*@__PURE__*/ core.
1585
1583
  _issue.code ??= "custom";
1586
1584
  _issue.input ??= payload.value;
1587
1585
  _issue.inst ??= inst;
1588
- _issue.continue ??= true;
1586
+ // _issue.continue ??= true;
1589
1587
  payload.issues.push(util.issue(_issue));
1590
1588
  }
1591
1589
  };
@@ -1839,12 +1837,16 @@ export function pipe(in_: core.SomeType, out: core.SomeType) {
1839
1837
  // ZodReadonly
1840
1838
  export interface ZodReadonly<T extends core.SomeType = core.$ZodType>
1841
1839
  extends _ZodType<core.$ZodReadonlyInternals<T>>,
1842
- core.$ZodReadonly<T> {}
1840
+ core.$ZodReadonly<T> {
1841
+ unwrap(): T;
1842
+ }
1843
1843
  export const ZodReadonly: core.$constructor<ZodReadonly> = /*@__PURE__*/ core.$constructor(
1844
1844
  "ZodReadonly",
1845
1845
  (inst, def) => {
1846
1846
  core.$ZodReadonly.init(inst, def);
1847
1847
  ZodType.init(inst, def);
1848
+
1849
+ inst.unwrap = () => inst._zod.def.innerType;
1848
1850
  }
1849
1851
  );
1850
1852
 
@@ -1,6 +1,5 @@
1
1
  import { expect, expectTypeOf, test } from "vitest";
2
2
  import { z } from "zod/v4";
3
- import type { util } from "zod/v4/core";
4
3
 
5
4
  test("basic catch", () => {
6
5
  expect(z.string().catch("default").parse(undefined)).toBe("default");
@@ -45,7 +44,7 @@ test("catch with transform", () => {
45
44
  expect(stringWithDefault.unwrap().out).toBeInstanceOf(z.ZodTransform);
46
45
 
47
46
  type inp = z.input<typeof stringWithDefault>;
48
- expectTypeOf<inp>().toEqualTypeOf<string | util.Whatever>();
47
+ expectTypeOf<inp>().toEqualTypeOf<string>();
49
48
  type out = z.output<typeof stringWithDefault>;
50
49
  expectTypeOf<out>().toEqualTypeOf<string>();
51
50
  });
@@ -59,7 +58,7 @@ test("catch on existing optional", () => {
59
58
  expect(stringWithDefault.unwrap().unwrap()).toBeInstanceOf(z.ZodString);
60
59
 
61
60
  type inp = z.input<typeof stringWithDefault>;
62
- expectTypeOf<inp>().toEqualTypeOf<string | undefined | util.Whatever>();
61
+ expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
63
62
  type out = z.output<typeof stringWithDefault>;
64
63
  expectTypeOf<out>().toEqualTypeOf<string | undefined>();
65
64
  });
@@ -68,7 +67,7 @@ test("optional on catch", () => {
68
67
  const stringWithDefault = z.string().catch("asdf").optional();
69
68
 
70
69
  type inp = z.input<typeof stringWithDefault>;
71
- expectTypeOf<inp>().toEqualTypeOf<string | util.Whatever>();
70
+ expectTypeOf<inp>().toEqualTypeOf<string | undefined>();
72
71
  type out = z.output<typeof stringWithDefault>;
73
72
  expectTypeOf<out>().toEqualTypeOf<string | undefined>();
74
73
  });
@@ -102,7 +101,7 @@ test("nested", () => {
102
101
  inner: "asdf",
103
102
  });
104
103
  type input = z.input<typeof outer>;
105
- expectTypeOf<input>().toEqualTypeOf<{ inner: string | util.Whatever } | util.Whatever>();
104
+ expectTypeOf<input>().toEqualTypeOf<{ inner: string }>();
106
105
  type out = z.output<typeof outer>;
107
106
 
108
107
  expectTypeOf<out>().toEqualTypeOf<{ inner: string }>();
@@ -646,3 +646,15 @@ test("pipes", () => {
646
646
  v: 2,
647
647
  });
648
648
  });
649
+
650
+ test("def", () => {
651
+ const schema = z.discriminatedUnion(
652
+ "type",
653
+ [z.object({ type: z.literal("play") }), z.object({ type: z.literal("pause") })],
654
+ { unionFallback: true }
655
+ );
656
+
657
+ expect(schema.def).toBeDefined();
658
+ expect(schema.def.discriminator).toEqual("type");
659
+ expect(schema.def.unionFallback).toEqual(true);
660
+ });
@@ -550,3 +550,46 @@ test("disc union treeify/format", () => {
550
550
  }
551
551
  `);
552
552
  });
553
+
554
+ test("update message after adding issues", () => {
555
+ const e = new z.ZodError([]);
556
+ e.addIssue({
557
+ code: "custom",
558
+ message: "message",
559
+ input: "asdf",
560
+ path: [],
561
+ });
562
+ expect(e.message).toMatchInlineSnapshot(`
563
+ "[
564
+ {
565
+ "code": "custom",
566
+ "message": "message",
567
+ "input": "asdf",
568
+ "path": []
569
+ }
570
+ ]"
571
+ `);
572
+
573
+ e.addIssue({
574
+ code: "custom",
575
+ message: "message",
576
+ input: "asdf",
577
+ path: [],
578
+ });
579
+ expect(e.message).toMatchInlineSnapshot(`
580
+ "[
581
+ {
582
+ "code": "custom",
583
+ "message": "message",
584
+ "input": "asdf",
585
+ "path": []
586
+ },
587
+ {
588
+ "code": "custom",
589
+ "message": "message",
590
+ "input": "asdf",
591
+ "path": []
592
+ }
593
+ ]"
594
+ `);
595
+ });
@@ -90,3 +90,28 @@ test("readonly", () => {
90
90
  const a = ["asdf"] as const;
91
91
  z.literal(a);
92
92
  });
93
+
94
+ test("literal pattern", () => {
95
+ expect(z.literal(1.1)._zod.pattern).toMatchInlineSnapshot(`/\\^\\(1\\\\\\.1\\)\\$/`);
96
+
97
+ expect(z.templateLiteral([z.literal(1.1)]).safeParse("1.1")).toMatchInlineSnapshot(`
98
+ {
99
+ "data": "1.1",
100
+ "success": true,
101
+ }
102
+ `);
103
+ expect(z.templateLiteral([z.literal(1.1)]).safeParse("1n1")).toMatchInlineSnapshot(`
104
+ {
105
+ "error": [ZodError: [
106
+ {
107
+ "code": "invalid_format",
108
+ "format": "template_literal",
109
+ "pattern": "^(1\\\\.1)$",
110
+ "path": [],
111
+ "message": "Invalid input"
112
+ }
113
+ ]],
114
+ "success": false,
115
+ }
116
+ `);
117
+ });
@@ -145,3 +145,196 @@ test("partial with mask -- ignore falsy values", async () => {
145
145
  masked.parse({ country: "US" });
146
146
  await masked.parseAsync({ country: "US" });
147
147
  });
148
+
149
+ test("catch/prefault/default", () => {
150
+ const mySchema = z.object({
151
+ a: z.string().catch("catch value").optional(),
152
+ b: z.string().default("default value").optional(),
153
+ c: z.string().prefault("prefault value").optional(),
154
+ d: z.string().catch("catch value"),
155
+ e: z.string().default("default value"),
156
+ f: z.string().prefault("prefault value"),
157
+ });
158
+
159
+ expect(mySchema.parse({})).toMatchInlineSnapshot(`
160
+ {
161
+ "b": "default value",
162
+ "c": "prefault value",
163
+ "d": "catch value",
164
+ "e": "default value",
165
+ "f": "prefault value",
166
+ }
167
+ `);
168
+
169
+ expect(mySchema.parse({}, { jitless: true })).toMatchInlineSnapshot(`
170
+ {
171
+ "b": "default value",
172
+ "c": "prefault value",
173
+ "d": "catch value",
174
+ "e": "default value",
175
+ "f": "prefault value",
176
+ }
177
+ `);
178
+ });
179
+
180
+ test("handleOptionalObjectResult branches", () => {
181
+ const mySchema = z.object({
182
+ // Branch: input[key] === undefined, key not in input, caught error
183
+ caughtMissing: z.string().catch("caught").optional(),
184
+ // Branch: input[key] === undefined, key in input, caught error
185
+ caughtUndefined: z.string().catch("caught").optional(),
186
+ // Branch: input[key] === undefined, key not in input, validation issues
187
+ issueMissing: z.string().min(5).optional(),
188
+ // Branch: input[key] === undefined, key in input, validation issues
189
+ issueUndefined: z.string().min(5).optional(),
190
+ // Branch: input[key] === undefined, validation returns undefined
191
+ validUndefined: z.string().optional(),
192
+ // Branch: input[key] === undefined, non-undefined result (default/transform)
193
+ defaultValue: z.string().default("default").optional(),
194
+ // Branch: input[key] defined, caught error
195
+ caughtDefined: z.string().catch("caught").optional(),
196
+ // Branch: input[key] defined, validation issues
197
+ issueDefined: z.string().min(5).optional(),
198
+ // Branch: input[key] defined, validation returns undefined
199
+ validDefinedUndefined: z
200
+ .string()
201
+ .transform(() => undefined)
202
+ .optional(),
203
+ // Branch: input[key] defined, non-undefined value
204
+ validDefined: z.string().optional(),
205
+ });
206
+
207
+ // Test input[key] === undefined cases
208
+ const result1 = mySchema.parse(
209
+ {
210
+ // caughtMissing: not present (key not in input)
211
+ caughtUndefined: undefined, // key in input
212
+ // issueMissing: not present (key not in input)
213
+ issueUndefined: undefined, // key in input
214
+ validUndefined: undefined,
215
+ // defaultValue: not present, will get default
216
+ },
217
+ { jitless: true }
218
+ );
219
+
220
+ expect(result1).toEqual({
221
+ caughtUndefined: undefined,
222
+ issueUndefined: undefined,
223
+ validUndefined: undefined,
224
+ defaultValue: "default",
225
+ });
226
+
227
+ // Test input[key] defined cases (successful)
228
+ const result2 = mySchema.parse(
229
+ {
230
+ caughtDefined: 123, // invalid type, should catch
231
+ validDefinedUndefined: "test", // transforms to undefined
232
+ validDefined: "valid", // valid value
233
+ },
234
+ { jitless: true }
235
+ );
236
+
237
+ expect(result2).toEqual({
238
+ caughtDefined: "caught",
239
+ validDefinedUndefined: undefined,
240
+ validDefined: "valid",
241
+ defaultValue: "default",
242
+ });
243
+
244
+ // Test validation issues are properly reported (input[key] defined, validation fails)
245
+ expect(() =>
246
+ mySchema.parse(
247
+ {
248
+ issueDefined: "abc", // too short
249
+ },
250
+ { jitless: true }
251
+ )
252
+ ).toThrow();
253
+ });
254
+
255
+ test("fastpass vs non-fastpass consistency", () => {
256
+ const mySchema = z.object({
257
+ caughtMissing: z.string().catch("caught").optional(),
258
+ caughtUndefined: z.string().catch("caught").optional(),
259
+ issueMissing: z.string().min(5).optional(),
260
+ issueUndefined: z.string().min(5).optional(),
261
+ validUndefined: z.string().optional(),
262
+ defaultValue: z.string().default("default").optional(),
263
+ caughtDefined: z.string().catch("caught").optional(),
264
+ validDefinedUndefined: z
265
+ .string()
266
+ .transform(() => undefined)
267
+ .optional(),
268
+ validDefined: z.string().optional(),
269
+ });
270
+
271
+ const input = {
272
+ caughtUndefined: undefined,
273
+ issueUndefined: undefined,
274
+ validUndefined: undefined,
275
+ caughtDefined: 123,
276
+ validDefinedUndefined: "test",
277
+ validDefined: "valid",
278
+ };
279
+
280
+ // Test both paths produce identical results
281
+ const jitlessResult = mySchema.parse(input, { jitless: true });
282
+ const fastpassResult = mySchema.parse(input);
283
+
284
+ expect(jitlessResult).toEqual(fastpassResult);
285
+ expect(jitlessResult).toEqual({
286
+ caughtUndefined: undefined,
287
+ issueUndefined: undefined,
288
+ validUndefined: undefined,
289
+ defaultValue: "default",
290
+ caughtDefined: "caught",
291
+ validDefinedUndefined: undefined,
292
+ validDefined: "valid",
293
+ });
294
+ });
295
+
296
+ test("optional with check", () => {
297
+ const baseSchema = z
298
+ .string()
299
+ .optional()
300
+ .check(({ value, ...ctx }) => {
301
+ ctx.issues.push({
302
+ code: "custom",
303
+ input: value,
304
+ message: "message",
305
+ });
306
+ });
307
+
308
+ // this correctly fails
309
+ expect(baseSchema.safeParse(undefined)).toMatchInlineSnapshot(`
310
+ {
311
+ "error": [ZodError: [
312
+ {
313
+ "code": "custom",
314
+ "message": "message",
315
+ "path": []
316
+ }
317
+ ]],
318
+ "success": false,
319
+ }
320
+ `);
321
+
322
+ const schemaObject = z.object({
323
+ date: baseSchema,
324
+ });
325
+
326
+ expect(schemaObject.safeParse({ date: undefined })).toMatchInlineSnapshot(`
327
+ {
328
+ "error": [ZodError: [
329
+ {
330
+ "code": "custom",
331
+ "message": "message",
332
+ "path": [
333
+ "date"
334
+ ]
335
+ }
336
+ ]],
337
+ "success": false,
338
+ }
339
+ `);
340
+ });
@@ -114,14 +114,14 @@ test("pick/omit/required/partial - do not allow unknown keys", () => {
114
114
  age: z.number(),
115
115
  });
116
116
 
117
- expect(() => schema.pick({ name: true, asdf: true })).toThrow();
117
+ expect(() => schema.pick({ name: true, asdf: true }).safeParse({})).toThrow();
118
118
 
119
119
  // @ts-expect-error
120
- expect(() => schema.pick({ $unknown: true })).toThrow();
120
+ expect(() => schema.pick({ $unknown: true }).safeParse({})).toThrow();
121
121
  // @ts-expect-error
122
- expect(() => schema.omit({ $unknown: true })).toThrow();
122
+ expect(() => schema.omit({ $unknown: true }).safeParse({})).toThrow();
123
123
  // @ts-expect-error
124
- expect(() => schema.required({ $unknown: true })).toThrow();
124
+ expect(() => schema.required({ $unknown: true }).safeParse({})).toThrow();
125
125
  // @ts-expect-error
126
- expect(() => schema.partial({ $unknown: true })).toThrow();
126
+ expect(() => schema.partial({ $unknown: true }).safeParse({})).toThrow();
127
127
  });
@@ -73,17 +73,18 @@ test("preprocess ctx.addIssue with parse", () => {
73
73
  `);
74
74
  });
75
75
 
76
- test("preprocess ctx.addIssue non-fatal by default", () => {
76
+ test("preprocess ctx.addIssue fatal by default", () => {
77
77
  const schema = z.preprocess((data, ctx) => {
78
78
  ctx.addIssue({
79
79
  code: "custom",
80
80
  message: `custom error`,
81
81
  });
82
+
82
83
  return data;
83
84
  }, z.string());
84
85
  const result = schema.safeParse(1234);
85
86
 
86
- expect(result.error!.issues).toHaveLength(2);
87
+ expect(result.error!.issues).toHaveLength(1);
87
88
  expect(result).toMatchInlineSnapshot(`
88
89
  {
89
90
  "error": [ZodError: [
@@ -91,12 +92,6 @@ test("preprocess ctx.addIssue non-fatal by default", () => {
91
92
  "code": "custom",
92
93
  "message": "custom error",
93
94
  "path": []
94
- },
95
- {
96
- "expected": "string",
97
- "code": "invalid_type",
98
- "path": [],
99
- "message": "Invalid input: expected string, received number"
100
95
  }
101
96
  ]],
102
97
  "success": false,
@@ -175,7 +170,7 @@ test("z.NEVER in preprocess", () => {
175
170
  expectTypeOf<foo>().toEqualTypeOf<number>();
176
171
  const result = foo.safeParse(undefined);
177
172
 
178
- expect(result.error!.issues).toHaveLength(2);
173
+ expect(result.error!.issues).toHaveLength(1);
179
174
  expect(result).toMatchInlineSnapshot(`
180
175
  {
181
176
  "error": [ZodError: [
@@ -183,12 +178,6 @@ test("z.NEVER in preprocess", () => {
183
178
  "code": "custom",
184
179
  "message": "bad",
185
180
  "path": []
186
- },
187
- {
188
- "expected": "number",
189
- "code": "invalid_type",
190
- "path": [],
191
- "message": "Invalid input: expected number, received object"
192
181
  }
193
182
  ]],
194
183
  "success": false,
@@ -336,7 +336,21 @@ test("partial record", () => {
336
336
  type schema = z.infer<typeof schema>;
337
337
  expectTypeOf<schema>().toEqualTypeOf<Partial<Record<string, string>>>();
338
338
 
339
- const Keys = z.enum(["id", "name", "email"]).or(z.never());
339
+ const Keys = z.enum(["id", "name", "email"]); //.or(z.never());
340
340
  const Person = z.partialRecord(Keys, z.string());
341
341
  expectTypeOf<z.infer<typeof Person>>().toEqualTypeOf<Partial<Record<"id" | "name" | "email", string>>>();
342
+
343
+ Person.parse({
344
+ id: "123",
345
+ // name: "John",
346
+ // email: "john@example.com",
347
+ });
348
+
349
+ Person.parse({
350
+ // id: "123",
351
+ // name: "John",
352
+ email: "john@example.com",
353
+ });
354
+
355
+ expect(Person.def.keyType._zod.def.type).toEqual("enum");
342
356
  });
@@ -260,6 +260,73 @@ test("mutual recursion with meta", () => {
260
260
  expectTypeOf<B>().toEqualTypeOf<_B>();
261
261
  });
262
262
 
263
+ test("object utilities with recursive types", () => {
264
+ const NodeBase = z.object({
265
+ id: z.string(),
266
+ name: z.string(),
267
+ get children() {
268
+ return z.array(Node).optional();
269
+ },
270
+ });
271
+
272
+ // Test extend
273
+ const NodeOne = NodeBase.extend({
274
+ name: z.literal("nodeOne"),
275
+ get children() {
276
+ return z.array(Node);
277
+ },
278
+ });
279
+
280
+ const NodeTwo = NodeBase.extend({
281
+ name: z.literal("nodeTwo"),
282
+ get children() {
283
+ return z.array(Node);
284
+ },
285
+ });
286
+
287
+ // Test pick
288
+ const PickedNode = NodeBase.pick({ id: true, name: true });
289
+
290
+ // Test omit
291
+ const OmittedNode = NodeBase.omit({ children: true });
292
+
293
+ // Test merge
294
+ const ExtraProps = {
295
+ metadata: z.string(),
296
+ get parent() {
297
+ return Node.optional();
298
+ },
299
+ };
300
+ const MergedNode = NodeBase.extend(ExtraProps);
301
+
302
+ // Test partial
303
+ const PartialNode = NodeBase.partial();
304
+ const PartialMaskedNode = NodeBase.partial({ name: true });
305
+
306
+ // Test required (assuming NodeBase has optional fields)
307
+ const OptionalNodeBase = z.object({
308
+ id: z.string().optional(),
309
+ name: z.string().optional(),
310
+ get children() {
311
+ return z.array(Node).optional();
312
+ },
313
+ });
314
+ const RequiredNode = OptionalNodeBase.required();
315
+ const RequiredMaskedNode = OptionalNodeBase.required({ id: true });
316
+
317
+ const Node = z.union([
318
+ NodeOne,
319
+ NodeTwo,
320
+ PickedNode,
321
+ OmittedNode,
322
+ MergedNode,
323
+ PartialNode,
324
+ PartialMaskedNode,
325
+ RequiredNode,
326
+ RequiredMaskedNode,
327
+ ]);
328
+ });
329
+
263
330
  test("recursion compatibility", () => {
264
331
  // array
265
332
  const A = z.object({