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
@@ -318,6 +318,83 @@ test("url validations", () => {
318
318
  expect(() => url.parse("https://")).toThrow();
319
319
  });
320
320
 
321
+ test("url preserves original input", () => {
322
+ const url = z.string().url();
323
+
324
+ // Test the specific case from the user report
325
+ const input = "https://example.com?key=NUXOmHqWNVTapJkJJHw8BfD155AuqhH_qju_5fNmQ4ZHV7u8";
326
+ const output = url.parse(input);
327
+ expect(output).toBe(input); // Should preserve the original input exactly
328
+
329
+ // Test other cases where URL constructor would normalize
330
+ expect(url.parse("https://example.com?foo=bar")).toBe("https://example.com?foo=bar");
331
+ expect(url.parse("http://example.com?test=123")).toBe("http://example.com?test=123");
332
+ expect(url.parse("https://sub.example.com?param=value&other=data")).toBe(
333
+ "https://sub.example.com?param=value&other=data"
334
+ );
335
+
336
+ // Test cases with trailing slashes are preserved
337
+ expect(url.parse("https://example.com/")).toBe("https://example.com/");
338
+ expect(url.parse("https://example.com/path/")).toBe("https://example.com/path/");
339
+
340
+ // Test cases with paths and query parameters
341
+ expect(url.parse("https://example.com/path?query=param")).toBe("https://example.com/path?query=param");
342
+ });
343
+
344
+ test("url trims whitespace", () => {
345
+ const url = z.string().url();
346
+
347
+ // Test trimming whitespace from URLs
348
+ expect(url.parse(" https://example.com ")).toBe("https://example.com");
349
+ expect(url.parse(" https://example.com/path?query=param ")).toBe("https://example.com/path?query=param");
350
+ expect(url.parse("\t\nhttps://example.com\t\n")).toBe("https://example.com");
351
+ expect(url.parse(" https://example.com?key=value ")).toBe("https://example.com?key=value");
352
+
353
+ // Test that URLs without extra whitespace are unchanged
354
+ expect(url.parse("https://example.com")).toBe("https://example.com");
355
+ expect(url.parse("https://example.com/path")).toBe("https://example.com/path");
356
+ });
357
+
358
+ test("url normalize flag", () => {
359
+ const normalizeUrl = z.url({ normalize: true });
360
+ const preserveUrl = z.url(); // normalize: false/undefined by default
361
+
362
+ // Test that normalize flag causes URL normalization
363
+ expect(normalizeUrl.parse("https://example.com?key=value")).toBe("https://example.com/?key=value");
364
+ expect(normalizeUrl.parse("http://example.com?test=123")).toBe("http://example.com/?test=123");
365
+
366
+ // Test with already normalized URLs
367
+ expect(normalizeUrl.parse("https://example.com/")).toBe("https://example.com/");
368
+ expect(normalizeUrl.parse("https://example.com/path?query=param")).toBe("https://example.com/path?query=param");
369
+
370
+ // Test complex URLs with normalization
371
+ expect(normalizeUrl.parse("https://example.com/../?key=value")).toBe("https://example.com/?key=value");
372
+ expect(normalizeUrl.parse("https://example.com/./path?key=value")).toBe("https://example.com/path?key=value");
373
+
374
+ // Compare with non-normalize behavior
375
+ expect(preserveUrl.parse("https://example.com?key=value")).toBe("https://example.com?key=value");
376
+ expect(preserveUrl.parse("http://example.com?test=123")).toBe("http://example.com?test=123");
377
+
378
+ // Test trimming with normalize
379
+ expect(normalizeUrl.parse(" https://example.com?key=value ")).toBe("https://example.com/?key=value");
380
+ expect(preserveUrl.parse(" https://example.com?key=value ")).toBe("https://example.com?key=value");
381
+ });
382
+
383
+ test("url normalize with hostname and protocol constraints", () => {
384
+ const constrainedNormalizeUrl = z.url({
385
+ normalize: true,
386
+ protocol: /^https$/,
387
+ hostname: /^example\.com$/,
388
+ });
389
+
390
+ // Test that normalization works with constraints
391
+ expect(constrainedNormalizeUrl.parse("https://example.com?key=value")).toBe("https://example.com/?key=value");
392
+
393
+ // Test that constraints are still enforced
394
+ expect(() => constrainedNormalizeUrl.parse("http://example.com?key=value")).toThrow();
395
+ expect(() => constrainedNormalizeUrl.parse("https://other.com?key=value")).toThrow();
396
+ });
397
+
321
398
  test("httpurl", () => {
322
399
  const httpUrl = z.url({
323
400
  protocol: /^https?$/,
@@ -464,8 +541,8 @@ test(`bad uuid`, () => {
464
541
  "9491d710-3185-0e06-bea0-6a2f275345e0",
465
542
  "9491d710-3185-5e06-0ea0-6a2f275345e0",
466
543
  "d89e7b01-7598-ed11-9d7a-0022489382fd", // new sequential id
467
- "b3ce60f8-e8b9-40f5-1150-172ede56ff74", // Variant 0 - RFC 4122: Reserved, NCS backward compatibility
468
- "92e76bf9-28b3-4730-cd7f-cb6bc51f8c09", // Variant 2 - RFC 4122: Reserved, Microsoft Corporation backward compatibility
544
+ "b3ce60f8-e8b9-40f5-1150-172ede56ff74", // Variant 0 - RFC 9562/4122: Reserved, NCS backward compatibility
545
+ "92e76bf9-28b3-4730-cd7f-cb6bc51f8c09", // Variant 2 - RFC 9562/4122: Reserved, Microsoft Corporation backward compatibility
469
546
  "invalid uuid",
470
547
  "9491d710-3185-4e06-bea0-6a2f275345e0X",
471
548
  "ffffffff-ffff-ffff-ffff-ffffffffffff",
@@ -481,8 +558,8 @@ test("good guid", () => {
481
558
  for (const goodGuid of [
482
559
  "9491d710-3185-4e06-bea0-6a2f275345e0",
483
560
  "d89e7b01-7598-ed11-9d7a-0022489382fd", // new sequential id
484
- "b3ce60f8-e8b9-40f5-1150-172ede56ff74", // Variant 0 - RFC 4122: Reserved, NCS backward compatibility
485
- "92e76bf9-28b3-4730-cd7f-cb6bc51f8c09", // Variant 2 - RFC 4122: Reserved, Microsoft Corporation backward compatibility
561
+ "b3ce60f8-e8b9-40f5-1150-172ede56ff74", // Variant 0 - RFC 9562/4122: Reserved, NCS backward compatibility
562
+ "92e76bf9-28b3-4730-cd7f-cb6bc51f8c09", // Variant 2 - RFC 9562/4122: Reserved, Microsoft Corporation backward compatibility
486
563
  "00000000-0000-0000-0000-000000000000",
487
564
  "ffffffff-ffff-ffff-ffff-ffffffffffff",
488
565
  ]) {
@@ -6,6 +6,7 @@ const hello = z.templateLiteral(["hello"]);
6
6
  const world = z.templateLiteral(["", z.literal("world")]);
7
7
  const one = z.templateLiteral([1]);
8
8
  const two = z.templateLiteral(["", z.literal(2)]);
9
+ const onePointOne = z.templateLiteral([z.literal(1.1)]);
9
10
  const truee = z.templateLiteral([true]);
10
11
  const anotherTrue = z.templateLiteral(["", z.literal(true)]);
11
12
  const falsee = z.templateLiteral([false]);
@@ -289,6 +290,7 @@ test("template literal parsing - success - basic cases", () => {
289
290
  world.parse("world");
290
291
  one.parse("1");
291
292
  two.parse("2");
293
+ onePointOne.parse("1.1");
292
294
  truee.parse("true");
293
295
  anotherTrue.parse("true");
294
296
  falsee.parse("false");
@@ -381,6 +383,7 @@ test("template literal parsing - failure - basic cases", () => {
381
383
  expect(() => one.parse("2")).toThrow();
382
384
  expect(() => one.parse("12")).toThrow();
383
385
  expect(() => one.parse("21")).toThrow();
386
+ expect(() => onePointOne.parse("1s1")).toThrow();
384
387
  expect(() => two.parse("1")).toThrow();
385
388
  expect(() => two.parse("21")).toThrow();
386
389
  expect(() => two.parse("12")).toThrow();
@@ -1952,6 +1952,7 @@ test("input type", () => {
1952
1952
  "required": [
1953
1953
  "a",
1954
1954
  "d",
1955
+ "f",
1955
1956
  "g",
1956
1957
  ],
1957
1958
  "type": "object",
@@ -248,3 +248,113 @@ test("async short circuit on dirty", async () => {
248
248
  ]]
249
249
  `);
250
250
  });
251
+
252
+ test("do not continue by default", () => {
253
+ const A = z
254
+ .string()
255
+ .transform((val, ctx) => {
256
+ ctx.addIssue({
257
+ code: "custom",
258
+ message: `custom error`,
259
+ });
260
+ ctx.addIssue({
261
+ code: "custom",
262
+ message: `custom error`,
263
+ });
264
+ return val;
265
+ })
266
+ .pipe(z.number() as any);
267
+ expect(A.safeParse("asdf")).toMatchInlineSnapshot(`
268
+ {
269
+ "error": [ZodError: [
270
+ {
271
+ "code": "custom",
272
+ "message": "custom error",
273
+ "path": []
274
+ },
275
+ {
276
+ "code": "custom",
277
+ "message": "custom error",
278
+ "path": []
279
+ }
280
+ ]],
281
+ "success": false,
282
+ }
283
+ `);
284
+
285
+ const B = z
286
+ .string()
287
+ .transform((val, ctx) => {
288
+ ctx.issues.push({
289
+ code: "custom",
290
+ message: `custom error`,
291
+ input: val,
292
+ });
293
+ ctx.issues.push({
294
+ code: "custom",
295
+ message: `custom error`,
296
+ input: val,
297
+ });
298
+ return val;
299
+ })
300
+ .pipe(z.number() as any);
301
+ expect(B.safeParse("asdf")).toMatchInlineSnapshot(`
302
+ {
303
+ "error": [ZodError: [
304
+ {
305
+ "code": "custom",
306
+ "message": "custom error",
307
+ "path": []
308
+ },
309
+ {
310
+ "code": "custom",
311
+ "message": "custom error",
312
+ "path": []
313
+ }
314
+ ]],
315
+ "success": false,
316
+ }
317
+ `);
318
+
319
+ const C = z
320
+ .string()
321
+ .transform((val, ctx) => {
322
+ ctx.issues.push({
323
+ code: "custom",
324
+ message: `custom error`,
325
+ input: val,
326
+ continue: true,
327
+ });
328
+ ctx.issues.push({
329
+ code: "custom",
330
+ message: `custom error`,
331
+ input: val,
332
+ continue: true,
333
+ });
334
+ return val;
335
+ })
336
+ .pipe(z.number() as any);
337
+ expect(C.safeParse("asdf")).toMatchInlineSnapshot(`
338
+ {
339
+ "error": [ZodError: [
340
+ {
341
+ "code": "custom",
342
+ "message": "custom error",
343
+ "path": []
344
+ },
345
+ {
346
+ "code": "custom",
347
+ "message": "custom error",
348
+ "path": []
349
+ },
350
+ {
351
+ "expected": "number",
352
+ "code": "invalid_type",
353
+ "path": [],
354
+ "message": "Invalid input: expected number, received string"
355
+ }
356
+ ]],
357
+ "success": false,
358
+ }
359
+ `);
360
+ });
@@ -27,7 +27,7 @@ test("return valid over invalid", () => {
27
27
  });
28
28
 
29
29
  test("return errors from both union arms", () => {
30
- const result = z.union([z.number(), z.string().refine(() => false)]).safeParse("a");
30
+ const result = z.union([z.number(), z.boolean()]).safeParse("a");
31
31
  expect(result.success).toEqual(false);
32
32
  if (!result.success) {
33
33
  expect(result.error.issues).toMatchInlineSnapshot(`
@@ -45,8 +45,9 @@ test("return errors from both union arms", () => {
45
45
  ],
46
46
  [
47
47
  {
48
- "code": "custom",
49
- "message": "Invalid input",
48
+ "code": "invalid_type",
49
+ "expected": "boolean",
50
+ "message": "Invalid input: expected boolean, received string",
50
51
  "path": [],
51
52
  },
52
53
  ],
@@ -92,3 +93,44 @@ test("union values", () => {
92
93
  }
93
94
  `);
94
95
  });
96
+
97
+ test("non-aborted errors", () => {
98
+ const zItemTest = z.union([
99
+ z.object({
100
+ date: z.number(),
101
+ startDate: z.optional(z.null()),
102
+ endDate: z.optional(z.null()),
103
+ }),
104
+ z
105
+ .object({
106
+ date: z.optional(z.null()),
107
+ startDate: z.number(),
108
+ endDate: z.number(),
109
+ })
110
+ .refine((data) => data.startDate !== data.endDate, {
111
+ error: "startDate and endDate must be different",
112
+ path: ["endDate"],
113
+ }),
114
+ ]);
115
+
116
+ const res = zItemTest.safeParse({
117
+ date: null,
118
+ startDate: 1,
119
+ endDate: 1,
120
+ });
121
+
122
+ expect(res).toMatchInlineSnapshot(`
123
+ {
124
+ "error": [ZodError: [
125
+ {
126
+ "code": "custom",
127
+ "path": [
128
+ "endDate"
129
+ ],
130
+ "message": "startDate and endDate must be different"
131
+ }
132
+ ]],
133
+ "success": false,
134
+ }
135
+ `);
136
+ });
@@ -1128,12 +1128,12 @@ export interface $ZodCheckMimeTypeDef extends $ZodCheckDef {
1128
1128
  mime: util.MimeTypes[];
1129
1129
  }
1130
1130
 
1131
- export interface $ZodCheckMimeTypeInternals<T extends File = File> extends $ZodCheckInternals<T> {
1131
+ export interface $ZodCheckMimeTypeInternals<T extends schemas.File = schemas.File> extends $ZodCheckInternals<T> {
1132
1132
  def: $ZodCheckMimeTypeDef;
1133
1133
  issc: errors.$ZodIssueInvalidValue;
1134
1134
  }
1135
1135
 
1136
- export interface $ZodCheckMimeType<T extends File = File> extends $ZodCheck<T> {
1136
+ export interface $ZodCheckMimeType<T extends schemas.File = schemas.File> extends $ZodCheck<T> {
1137
1137
  _zod: $ZodCheckMimeTypeInternals<T>;
1138
1138
  }
1139
1139
 
@@ -1,6 +1,7 @@
1
1
  import type { $ZodCheck, $ZodStringFormats } from "./checks.js";
2
2
  import { $constructor } from "./core.js";
3
3
  import type { $ZodType } from "./schemas.js";
4
+ import type { StandardSchemaV1 } from "./standard-schema.js";
4
5
  import * as util from "./util.js";
5
6
 
6
7
  ///////////////////////////
@@ -162,7 +163,7 @@ type RawIssue<T extends $ZodIssueBase> = util.Flatten<
162
163
  readonly input?: unknown;
163
164
  /** The schema or check that originated this issue. */
164
165
  readonly inst?: $ZodType | $ZodCheck;
165
- /** @deprecated Internal use only. If `true`, Zod will continue executing validation despite this issue. */
166
+ /** If `true`, Zod will continue executing validation despite this issue. */
166
167
  readonly continue?: boolean | undefined;
167
168
  } & Record<string, any>
168
169
  >;
@@ -196,13 +197,8 @@ const initializer = (inst: $ZodError, def: $ZodIssue[]): void => {
196
197
  value: def,
197
198
  enumerable: false,
198
199
  });
199
- Object.defineProperty(inst, "message", {
200
- get() {
201
- return JSON.stringify(def, util.jsonStringifyReplacer, 2);
202
- },
203
- enumerable: true,
204
- // configurable: false,
205
- });
200
+ inst.message = JSON.stringify(def, util.jsonStringifyReplacer, 2);
201
+
206
202
  Object.defineProperty(inst, "toString", {
207
203
  value: () => inst.message,
208
204
  enumerable: false,
@@ -391,8 +387,9 @@ export function treeifyError<T>(error: $ZodError, _mapper?: any) {
391
387
  * ✖ Invalid input: expected number
392
388
  * ```
393
389
  */
394
- export function toDotPath(path: (string | number | symbol)[]): string {
390
+ export function toDotPath(_path: readonly (string | number | symbol | StandardSchemaV1.PathSegment)[]): string {
395
391
  const segs: string[] = [];
392
+ const path: PropertyKey[] = _path.map((seg: any) => (typeof seg === "object" ? seg.key : seg));
396
393
  for (const seg of path) {
397
394
  if (typeof seg === "number") segs.push(`[${seg}]`);
398
395
  else if (typeof seg === "symbol") segs.push(`[${JSON.stringify(String(seg))}]`);
@@ -406,14 +403,10 @@ export function toDotPath(path: (string | number | symbol)[]): string {
406
403
  return segs.join("");
407
404
  }
408
405
 
409
- interface BaseError {
410
- issues: $ZodIssueBase[];
411
- }
412
-
413
- export function prettifyError(error: BaseError): string {
406
+ export function prettifyError(error: StandardSchemaV1.FailureResult): string {
414
407
  const lines: string[] = [];
415
408
  // sort by path length
416
- const issues = [...error.issues].sort((a, b) => a.path.length - b.path.length);
409
+ const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
417
410
 
418
411
  // Process each issue
419
412
  for (const issue of issues) {
@@ -16,7 +16,7 @@ export const extendedDuration: RegExp =
16
16
  /** A regex for any UUID-like identifier: 8-4-4-4-12 hex pattern */
17
17
  export const guid: RegExp = /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})$/;
18
18
 
19
- /** Returns a regex for validating an RFC 4122 UUID.
19
+ /** Returns a regex for validating an RFC 9562/4122 UUID.
20
20
  *
21
21
  * @param version Optionally specify a version 1-8. If no version is specified, all versions are supported. */
22
22
  export const uuid = (version?: number | undefined): RegExp => {
@@ -23,7 +23,7 @@ export type $replace<Meta, S extends $ZodType> = Meta extends $output
23
23
  ? { [K in keyof Meta]: $replace<Meta[K], S> }
24
24
  : Meta;
25
25
 
26
- type MetadataType = Record<string, unknown> | undefined;
26
+ type MetadataType = object | undefined;
27
27
  export class $ZodRegistry<Meta extends MetadataType = MetadataType, Schema extends $ZodType = $ZodType> {
28
28
  _meta!: Meta;
29
29
  _schema!: Schema;
@@ -68,7 +68,8 @@ export class $ZodRegistry<Meta extends MetadataType = MetadataType, Schema exten
68
68
  if (p) {
69
69
  const pm: any = { ...(this.get(p) ?? {}) };
70
70
  delete pm.id; // do not inherit id
71
- return { ...pm, ...this._map.get(schema) } as any;
71
+ const f = { ...pm, ...this._map.get(schema) } as any;
72
+ return Object.keys(f).length ? f : undefined;
72
73
  }
73
74
  return this._map.get(schema) as any;
74
75
  }