zod 4.6.2 → 4.6.4

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 (267) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/v4/classic/checks.ts +1 -0
  4. package/src/v4/classic/schemas.ts +12 -29
  5. package/src/v4/classic/tests/assignability.test.ts +1 -0
  6. package/src/v4/classic/tests/cyclic-data.test.ts +2 -11
  7. package/src/v4/classic/tests/firstparty.test.ts +0 -4
  8. package/src/v4/classic/tests/instance-footprint.test.ts +22 -0
  9. package/src/v4/classic/tests/instanceof.test.ts +1 -1
  10. package/src/v4/classic/tests/number.test.ts +39 -0
  11. package/src/v4/classic/tests/properties.test.ts +34 -137
  12. package/src/v4/classic/tests/string.test.ts +70 -1
  13. package/src/v4/classic/tests/to-json-schema.test.ts +87 -35
  14. package/src/v4/core/api.ts +21 -29
  15. package/src/v4/core/checks.ts +61 -1
  16. package/src/v4/core/compile.ts +35 -24
  17. package/src/v4/core/core.ts +1 -3
  18. package/src/v4/core/json-schema-processors.ts +9 -63
  19. package/src/v4/core/memoizer.ts +2 -5
  20. package/src/v4/core/regexes.ts +4 -0
  21. package/src/v4/core/schemas.ts +36 -109
  22. package/src/v4/core/tests/compile-differential.test.ts +35 -0
  23. package/src/v4/core/tests/compile.test.ts +177 -0
  24. package/src/v4/core/tests/url-no-canparse.test.ts +118 -16
  25. package/src/v4/core/to-json-schema.ts +1 -1
  26. package/src/v4/core/util.ts +0 -4
  27. package/src/v4/core/versions.ts +1 -1
  28. package/src/v4/core/visit.ts +0 -11
  29. package/src/v4/locales/ar.ts +1 -0
  30. package/src/v4/locales/az.ts +1 -0
  31. package/src/v4/locales/be.ts +1 -0
  32. package/src/v4/locales/bg.ts +1 -0
  33. package/src/v4/locales/bn.ts +1 -0
  34. package/src/v4/locales/ca.ts +1 -0
  35. package/src/v4/locales/ckb.ts +1 -0
  36. package/src/v4/locales/cs.ts +1 -0
  37. package/src/v4/locales/da.ts +1 -0
  38. package/src/v4/locales/de.ts +1 -0
  39. package/src/v4/locales/el.ts +1 -0
  40. package/src/v4/locales/en.ts +1 -0
  41. package/src/v4/locales/eo.ts +1 -0
  42. package/src/v4/locales/es.ts +1 -0
  43. package/src/v4/locales/fa.ts +1 -0
  44. package/src/v4/locales/fi.ts +1 -0
  45. package/src/v4/locales/fr-CA.ts +1 -0
  46. package/src/v4/locales/fr.ts +1 -0
  47. package/src/v4/locales/gu.ts +1 -0
  48. package/src/v4/locales/he.ts +1 -0
  49. package/src/v4/locales/hi.ts +1 -0
  50. package/src/v4/locales/hr.ts +1 -0
  51. package/src/v4/locales/hu.ts +1 -0
  52. package/src/v4/locales/hy.ts +1 -0
  53. package/src/v4/locales/id.ts +1 -0
  54. package/src/v4/locales/is.ts +1 -0
  55. package/src/v4/locales/it.ts +1 -0
  56. package/src/v4/locales/ja.ts +1 -0
  57. package/src/v4/locales/ka.ts +1 -0
  58. package/src/v4/locales/km.ts +1 -0
  59. package/src/v4/locales/kn.ts +1 -0
  60. package/src/v4/locales/ko.ts +1 -0
  61. package/src/v4/locales/lt.ts +1 -0
  62. package/src/v4/locales/mk.ts +1 -0
  63. package/src/v4/locales/ms.ts +1 -0
  64. package/src/v4/locales/ne.ts +1 -0
  65. package/src/v4/locales/nl.ts +1 -0
  66. package/src/v4/locales/nn.ts +1 -0
  67. package/src/v4/locales/no.ts +1 -0
  68. package/src/v4/locales/ota.ts +1 -0
  69. package/src/v4/locales/pl.ts +1 -0
  70. package/src/v4/locales/ps.ts +1 -0
  71. package/src/v4/locales/pt-BR.ts +1 -0
  72. package/src/v4/locales/pt.ts +1 -0
  73. package/src/v4/locales/ro.ts +1 -0
  74. package/src/v4/locales/ru.ts +1 -0
  75. package/src/v4/locales/sk.ts +1 -0
  76. package/src/v4/locales/sl.ts +1 -0
  77. package/src/v4/locales/sv.ts +1 -0
  78. package/src/v4/locales/ta.ts +1 -0
  79. package/src/v4/locales/tg.ts +1 -0
  80. package/src/v4/locales/th.ts +1 -0
  81. package/src/v4/locales/tk.ts +1 -0
  82. package/src/v4/locales/tr.ts +1 -0
  83. package/src/v4/locales/uk.ts +1 -0
  84. package/src/v4/locales/ur.ts +1 -0
  85. package/src/v4/locales/uz.ts +1 -0
  86. package/src/v4/locales/vi.ts +1 -0
  87. package/src/v4/locales/yo.ts +1 -0
  88. package/src/v4/locales/zh-CN.ts +1 -0
  89. package/src/v4/locales/zh-TW.ts +1 -0
  90. package/src/v4/mini/checks.ts +1 -0
  91. package/src/v4/mini/schemas.ts +12 -23
  92. package/src/v4/mini/tests/index.test.ts +56 -0
  93. package/src/v4/mini/tests/string.test.ts +7 -0
  94. package/v4/classic/checks.cjs +2 -1
  95. package/v4/classic/checks.d.cts +1 -1
  96. package/v4/classic/checks.d.ts +1 -1
  97. package/v4/classic/checks.js +1 -1
  98. package/v4/classic/schemas.cjs +11 -15
  99. package/v4/classic/schemas.d.cts +2 -6
  100. package/v4/classic/schemas.d.ts +2 -6
  101. package/v4/classic/schemas.js +9 -13
  102. package/v4/core/api.cjs +11 -23
  103. package/v4/core/api.d.cts +8 -4
  104. package/v4/core/api.d.ts +8 -4
  105. package/v4/core/api.js +11 -23
  106. package/v4/core/checks.cjs +32 -1
  107. package/v4/core/checks.d.cts +16 -0
  108. package/v4/core/checks.d.ts +16 -0
  109. package/v4/core/checks.js +31 -0
  110. package/v4/core/compile.cjs +12 -17
  111. package/v4/core/compile.js +13 -18
  112. package/v4/core/core.cjs +1 -1
  113. package/v4/core/core.js +1 -1
  114. package/v4/core/json-schema-processors.cjs +8 -44
  115. package/v4/core/json-schema-processors.d.cts +0 -1
  116. package/v4/core/json-schema-processors.d.ts +0 -1
  117. package/v4/core/json-schema-processors.js +8 -43
  118. package/v4/core/memoizer.cjs +2 -5
  119. package/v4/core/memoizer.js +2 -5
  120. package/v4/core/regexes.cjs +4 -2
  121. package/v4/core/regexes.d.cts +1 -0
  122. package/v4/core/regexes.d.ts +1 -0
  123. package/v4/core/regexes.js +2 -0
  124. package/v4/core/schemas.cjs +33 -73
  125. package/v4/core/schemas.d.cts +7 -22
  126. package/v4/core/schemas.d.ts +7 -22
  127. package/v4/core/schemas.js +30 -72
  128. package/v4/core/to-json-schema.cjs +0 -1
  129. package/v4/core/to-json-schema.d.cts +0 -3
  130. package/v4/core/to-json-schema.d.ts +0 -3
  131. package/v4/core/to-json-schema.js +1 -1
  132. package/v4/core/util.cjs +0 -4
  133. package/v4/core/util.js +0 -4
  134. package/v4/core/versions.cjs +1 -1
  135. package/v4/core/versions.js +1 -1
  136. package/v4/core/visit.cjs +0 -12
  137. package/v4/core/visit.js +0 -12
  138. package/v4/locales/ar.cjs +1 -0
  139. package/v4/locales/ar.js +1 -0
  140. package/v4/locales/az.cjs +1 -0
  141. package/v4/locales/az.js +1 -0
  142. package/v4/locales/be.cjs +1 -0
  143. package/v4/locales/be.js +1 -0
  144. package/v4/locales/bg.cjs +1 -0
  145. package/v4/locales/bg.js +1 -0
  146. package/v4/locales/bn.cjs +1 -0
  147. package/v4/locales/bn.js +1 -0
  148. package/v4/locales/ca.cjs +1 -0
  149. package/v4/locales/ca.js +1 -0
  150. package/v4/locales/ckb.cjs +1 -0
  151. package/v4/locales/ckb.js +1 -0
  152. package/v4/locales/cs.cjs +1 -0
  153. package/v4/locales/cs.js +1 -0
  154. package/v4/locales/da.cjs +1 -0
  155. package/v4/locales/da.js +1 -0
  156. package/v4/locales/de.cjs +1 -0
  157. package/v4/locales/de.js +1 -0
  158. package/v4/locales/el.cjs +1 -0
  159. package/v4/locales/el.js +1 -0
  160. package/v4/locales/en.cjs +1 -0
  161. package/v4/locales/en.js +1 -0
  162. package/v4/locales/eo.cjs +1 -0
  163. package/v4/locales/eo.js +1 -0
  164. package/v4/locales/es.cjs +1 -0
  165. package/v4/locales/es.js +1 -0
  166. package/v4/locales/fa.cjs +1 -0
  167. package/v4/locales/fa.js +1 -0
  168. package/v4/locales/fi.cjs +1 -0
  169. package/v4/locales/fi.js +1 -0
  170. package/v4/locales/fr-CA.cjs +1 -0
  171. package/v4/locales/fr-CA.js +1 -0
  172. package/v4/locales/fr.cjs +1 -0
  173. package/v4/locales/fr.js +1 -0
  174. package/v4/locales/gu.cjs +1 -0
  175. package/v4/locales/gu.js +1 -0
  176. package/v4/locales/he.cjs +1 -0
  177. package/v4/locales/he.js +1 -0
  178. package/v4/locales/hi.cjs +1 -0
  179. package/v4/locales/hi.js +1 -0
  180. package/v4/locales/hr.cjs +1 -0
  181. package/v4/locales/hr.js +1 -0
  182. package/v4/locales/hu.cjs +1 -0
  183. package/v4/locales/hu.js +1 -0
  184. package/v4/locales/hy.cjs +1 -0
  185. package/v4/locales/hy.js +1 -0
  186. package/v4/locales/id.cjs +1 -0
  187. package/v4/locales/id.js +1 -0
  188. package/v4/locales/is.cjs +1 -0
  189. package/v4/locales/is.js +1 -0
  190. package/v4/locales/it.cjs +1 -0
  191. package/v4/locales/it.js +1 -0
  192. package/v4/locales/ja.cjs +1 -0
  193. package/v4/locales/ja.js +1 -0
  194. package/v4/locales/ka.cjs +1 -0
  195. package/v4/locales/ka.js +1 -0
  196. package/v4/locales/km.cjs +1 -0
  197. package/v4/locales/km.js +1 -0
  198. package/v4/locales/kn.cjs +1 -0
  199. package/v4/locales/kn.js +1 -0
  200. package/v4/locales/ko.cjs +1 -0
  201. package/v4/locales/ko.js +1 -0
  202. package/v4/locales/lt.cjs +1 -0
  203. package/v4/locales/lt.js +1 -0
  204. package/v4/locales/mk.cjs +1 -0
  205. package/v4/locales/mk.js +1 -0
  206. package/v4/locales/ms.cjs +1 -0
  207. package/v4/locales/ms.js +1 -0
  208. package/v4/locales/ne.cjs +1 -0
  209. package/v4/locales/ne.js +1 -0
  210. package/v4/locales/nl.cjs +1 -0
  211. package/v4/locales/nl.js +1 -0
  212. package/v4/locales/nn.cjs +1 -0
  213. package/v4/locales/nn.js +1 -0
  214. package/v4/locales/no.cjs +1 -0
  215. package/v4/locales/no.js +1 -0
  216. package/v4/locales/ota.cjs +1 -0
  217. package/v4/locales/ota.js +1 -0
  218. package/v4/locales/pl.cjs +1 -0
  219. package/v4/locales/pl.js +1 -0
  220. package/v4/locales/ps.cjs +1 -0
  221. package/v4/locales/ps.js +1 -0
  222. package/v4/locales/pt-BR.cjs +1 -0
  223. package/v4/locales/pt-BR.js +1 -0
  224. package/v4/locales/pt.cjs +1 -0
  225. package/v4/locales/pt.js +1 -0
  226. package/v4/locales/ro.cjs +1 -0
  227. package/v4/locales/ro.js +1 -0
  228. package/v4/locales/ru.cjs +1 -0
  229. package/v4/locales/ru.js +1 -0
  230. package/v4/locales/sk.cjs +1 -0
  231. package/v4/locales/sk.js +1 -0
  232. package/v4/locales/sl.cjs +1 -0
  233. package/v4/locales/sl.js +1 -0
  234. package/v4/locales/sv.cjs +1 -0
  235. package/v4/locales/sv.js +1 -0
  236. package/v4/locales/ta.cjs +1 -0
  237. package/v4/locales/ta.js +1 -0
  238. package/v4/locales/tg.cjs +1 -0
  239. package/v4/locales/tg.js +1 -0
  240. package/v4/locales/th.cjs +1 -0
  241. package/v4/locales/th.js +1 -0
  242. package/v4/locales/tk.cjs +1 -0
  243. package/v4/locales/tk.js +1 -0
  244. package/v4/locales/tr.cjs +1 -0
  245. package/v4/locales/tr.js +1 -0
  246. package/v4/locales/uk.cjs +1 -0
  247. package/v4/locales/uk.js +1 -0
  248. package/v4/locales/ur.cjs +1 -0
  249. package/v4/locales/ur.js +1 -0
  250. package/v4/locales/uz.cjs +1 -0
  251. package/v4/locales/uz.js +1 -0
  252. package/v4/locales/vi.cjs +1 -0
  253. package/v4/locales/vi.js +1 -0
  254. package/v4/locales/yo.cjs +1 -0
  255. package/v4/locales/yo.js +1 -0
  256. package/v4/locales/zh-CN.cjs +1 -0
  257. package/v4/locales/zh-CN.js +1 -0
  258. package/v4/locales/zh-TW.cjs +1 -0
  259. package/v4/locales/zh-TW.js +1 -0
  260. package/v4/mini/checks.cjs +2 -1
  261. package/v4/mini/checks.d.cts +1 -1
  262. package/v4/mini/checks.d.ts +1 -1
  263. package/v4/mini/checks.js +1 -1
  264. package/v4/mini/schemas.cjs +11 -13
  265. package/v4/mini/schemas.d.cts +1 -4
  266. package/v4/mini/schemas.d.ts +1 -4
  267. package/v4/mini/schemas.js +9 -11
@@ -96,8 +96,7 @@ export interface $ZodTypeDef {
96
96
  | "promise"
97
97
  | "lazy"
98
98
  | "function"
99
- | "custom"
100
- | "properties";
99
+ | "custom";
101
100
  error?: errors.$ZodErrorMap<never> | undefined;
102
101
  checks?: checks.$ZodCheck<never>[];
103
102
  }
@@ -523,10 +522,30 @@ export interface $ZodURL extends $ZodType {
523
522
 
524
523
  /** The `://` guard rejected the input before the URL constructor saw it. */
525
524
  export const URL_BAD_FORMAT = 1;
526
- /** The URL constructor rejected the input. */
525
+ /** The URL parser rejected the input. */
527
526
  export const URL_UNPARSEABLE = 2;
528
527
 
529
- /** Parses a URL for `$ZodURL`, applying the one guard the URL constructor cannot express. Returns the parsed URL, or a code naming the stage that rejected it — the runtime needs that distinction to pick an issue note, and compiled code only needs to know it is not a URL. */
528
+ export function canParseURL(input: string): boolean {
529
+ try {
530
+ if (typeof URL !== "undefined" && typeof URL.canParse === "function") return URL.canParse(input);
531
+ new URL(input);
532
+ return true;
533
+ } catch {
534
+ return false;
535
+ }
536
+ }
537
+
538
+ export function validateURL(
539
+ trimmed: string,
540
+ def: Pick<$ZodURLDef, "protocol" | "hostname" | "normalize">
541
+ ): URL | true | typeof URL_BAD_FORMAT | typeof URL_UNPARSEABLE {
542
+ if (!("normalize" in def) && !("hostname" in def) && !("protocol" in def)) {
543
+ return canParseURL(trimmed) || URL_UNPARSEABLE;
544
+ }
545
+ return parseURLObject(trimmed, def);
546
+ }
547
+
548
+ /** Parses a URL while preserving the non-normalizing HTTP guard. */
530
549
  export function parseURLObject(
531
550
  trimmed: string,
532
551
  def: Pick<$ZodURLDef, "protocol" | "normalize">
@@ -537,6 +556,10 @@ export function parseURLObject(
537
556
  }
538
557
 
539
558
  try {
559
+ if (typeof URL !== "undefined") {
560
+ const URLStatic = URL as typeof URL & { parse?: (input: string) => URL | null };
561
+ if (typeof URLStatic.parse === "function") return URLStatic.parse(trimmed) ?? URL_UNPARSEABLE;
562
+ }
540
563
  // @ts-ignore
541
564
  return new URL(trimmed);
542
565
  } catch {
@@ -567,7 +590,7 @@ export const $ZodURL: core.$constructor<$ZodURL> = /*@__PURE__*/ core.$construct
567
590
  try {
568
591
  // Trim whitespace from input
569
592
  const trimmed = payload.value.trim();
570
- const url = parseURLObject(trimmed, def);
593
+ const url = validateURL(trimmed, def);
571
594
 
572
595
  if (url === URL_BAD_FORMAT) {
573
596
  payload.issues.push({
@@ -592,6 +615,11 @@ export const $ZodURL: core.$constructor<$ZodURL> = /*@__PURE__*/ core.$construct
592
615
  return;
593
616
  }
594
617
 
618
+ if (url === true) {
619
+ payload.value = stripTabAndNewline(trimmed);
620
+ return;
621
+ }
622
+
595
623
  if (def.hostname && !urlHostnameOk(url, def.hostname)) {
596
624
  payload.issues.push({
597
625
  code: "invalid_format",
@@ -886,13 +914,7 @@ const ipv6Alphabet = /^[0-9a-fA-F:.]+$/;
886
914
 
887
915
  export function isValidIPv6(value: string): boolean {
888
916
  if (!ipv6Alphabet.test(value)) return false;
889
- try {
890
- // @ts-ignore
891
- new URL(`http://[${value}]`);
892
- return true;
893
- } catch {
894
- return false;
895
- }
917
+ return canParseURL(`http://[${value}]`);
896
918
  }
897
919
 
898
920
  export const $ZodIPv6: core.$constructor<$ZodIPv6> = /*@__PURE__*/ core.$constructor("$ZodIPv6", (inst, def): void => {
@@ -5096,101 +5118,6 @@ function handleRefineResult(result: unknown, payload: ParsePayload, input: unkno
5096
5118
  }
5097
5119
  }
5098
5120
 
5099
- ////////////////////////////////////////
5100
- ////////////////////////////////////////
5101
- ////////// //////////
5102
- ////////// $ZodProperties //////////
5103
- ////////// //////////
5104
- ////////////////////////////////////////
5105
- ////////////////////////////////////////
5106
- export interface $ZodPropertiesDef<Shape extends $ZodShape = $ZodShape> extends $ZodTypeDef, checks.$ZodCheckDef {
5107
- type: "properties";
5108
- check: "properties";
5109
- shape: Shape;
5110
- }
5111
-
5112
- // both sides infer the INPUT type: the shape is asserted and its results discarded, so a default, catch or transform inside it would make an output-typed inference a lie. The check side widens a literal to its primitive, so spreading over a `string` property still type-checks (#6520).
5113
- export interface $ZodPropertiesInternals<Shape extends $ZodShape = $ZodShape>
5114
- extends $ZodTypeInternals<$InferObjectInput<Shape, {}>, $InferObjectInput<Shape, {}>>,
5115
- checks.$ZodCheckInternals<{ -readonly [k in keyof Shape]: util.Widen<core.input<Shape[k]>> }> {
5116
- def: $ZodPropertiesDef<Shape>;
5117
- isst: errors.$ZodIssueInvalidType;
5118
- issc: errors.$ZodIssue;
5119
- }
5120
-
5121
- export interface $ZodProperties<Shape extends $ZodShape = $ZodShape> extends $ZodType {
5122
- _zod: $ZodPropertiesInternals<Shape>;
5123
- // yields the schema itself, so pre-4.6 `.check(...z.properties(shape))` spread call sites keep working
5124
- [Symbol.iterator](): Iterator<this>;
5125
- }
5126
-
5127
- // asserts in place: the child result's value is discarded, matching z.property(), because a nested object or array schema rebuilds its output even when nothing transformed
5128
- function handlePropertiesResult(result: ParsePayload, payload: ParsePayload, key: string | symbol): void {
5129
- if (result.issues.length) {
5130
- payload.issues.push(...util.prefixIssues(key, result.issues));
5131
- }
5132
- }
5133
-
5134
- export const $ZodProperties: core.$constructor<$ZodProperties> = /*@__PURE__*/ core.$constructor(
5135
- "$ZodProperties",
5136
- (inst, def) => {
5137
- // $ZodType.init prepends an instance that already carries the $ZodCheck trait to its own `checks`, which would run the shape a second time and cost the parse context. Initializing the check trait after it keeps the schema role in `parse`, where the context arrives.
5138
- $ZodType.init(inst, def);
5139
- checks.$ZodCheck.init(inst, def);
5140
-
5141
- const memo = core.globalConfig.memoizer;
5142
- memo?.attach(inst);
5143
-
5144
- // key and schema snapshotted together: reading one live and the other cached lets a later mutation of the caller's shape object pair a stale key with a missing schema
5145
- let entries!: [string | symbol, $ZodType][];
5146
- const runShape = (payload: ParsePayload, ctx: ParseContextInternal): util.MaybeAsync<void> => {
5147
- entries ??= Reflect.ownKeys(def.shape).map((key) => [key, (def.shape as any)[key] as $ZodType]);
5148
- const input = payload.value as any;
5149
- let proms: Promise<any>[] | undefined;
5150
- for (const [key, schema] of entries) {
5151
- const result = schema._zod.run({ value: input[key], issues: [] }, ctx);
5152
- if (result instanceof Promise) {
5153
- proms ??= [];
5154
- proms.push(result.then((result) => handlePropertiesResult(result, payload, key)));
5155
- } else {
5156
- handlePropertiesResult(result, payload, key);
5157
- }
5158
- }
5159
- if (proms) return Promise.all(proms).then(() => undefined);
5160
- return undefined;
5161
- };
5162
-
5163
- inst._zod.parse = (payload, ctx) => {
5164
- const input = payload.value;
5165
- // as a schema this is the type gate, and it infers an object shape, so a primitive is a type error. A function passes: its properties read like any other object's, and z.instanceof allows one.
5166
- if (input === null || (typeof input !== "object" && typeof input !== "function")) {
5167
- payload.issues.push({ expected: "object", code: "invalid_type", input, inst });
5168
- return payload;
5169
- }
5170
- // both sides declare the shape's input type, so the assertion runs forward in either direction; encoding the children backward would reject the very type this schema claims to take
5171
- if (ctx.direction === "backward") ctx = { ...ctx, direction: "forward" };
5172
- // the input is its own output here, so it registers as its own memo entry: a cycle re-entering this node hits the bucket instead of recursing forever
5173
- if (memo) memo.alloc(inst, payload, input, ctx);
5174
- const result = runShape(payload, ctx);
5175
- return result instanceof Promise ? result.then(() => payload) : payload;
5176
- };
5177
-
5178
- // as a check the base schema already typed the value, so this only asserts the properties — which read on a primitive too, matching z.property() on a string's length. It gets no context of its own, so a cycle through a spread schema is not tracked.
5179
- inst._zod.check = (payload) => {
5180
- if (payload.value == null) {
5181
- payload.issues.push({ expected: "object", code: "invalid_type", input: payload.value, inst });
5182
- return undefined;
5183
- }
5184
- return runShape(payload, {});
5185
- };
5186
- },
5187
- {
5188
- *[Symbol.iterator]() {
5189
- yield this;
5190
- },
5191
- }
5192
- );
5193
-
5194
5121
  export type $ZodTypes =
5195
5122
  | $ZodString
5196
5123
  | $ZodNumber
@@ -5223,7 +5150,6 @@ export type $ZodTypes =
5223
5150
  | $ZodPrefault
5224
5151
  | $ZodTemplateLiteral
5225
5152
  | $ZodCustom
5226
- | $ZodProperties
5227
5153
  | $ZodTransform
5228
5154
  | $ZodNonOptional
5229
5155
  | $ZodReadonly
@@ -5262,4 +5188,5 @@ export type $ZodStringFormatTypes =
5262
5188
  | $ZodJWT
5263
5189
  | $ZodCustomStringFormat<"hex">
5264
5190
  | $ZodCustomStringFormat<util.HashFormat>
5265
- | $ZodCustomStringFormat<"hostname">;
5191
+ | $ZodCustomStringFormat<"hostname">
5192
+ | $ZodCustomStringFormat<"currency_code">;
@@ -677,6 +677,41 @@ test("url options match the runtime across the whole option matrix", () => {
677
677
  for (const schema of schemas) differential(schema, inputs);
678
678
  });
679
679
 
680
+ test("assert-only URL generation skips unused output without skipping chained transforms", () => {
681
+ const sanitized = "https://example.com";
682
+ const withNewline = "https://exa\nmple.com";
683
+ differential(z.url().length(sanitized.length), [sanitized, withNewline, "https://example.com/long"]);
684
+ differential(z.string().url().length(sanitized.length), [sanitized, withNewline, "https://example.com/long"]);
685
+
686
+ const URLStatic = URL as typeof URL & { parse: (input: string) => URL | null };
687
+ const descriptor = Object.getOwnPropertyDescriptor(URLStatic, "parse")!;
688
+ const marker = new Error("href read");
689
+ Object.defineProperty(URLStatic, "parse", {
690
+ configurable: true,
691
+ value: () =>
692
+ Object.create(URL.prototype, {
693
+ href: {
694
+ get() {
695
+ throw marker;
696
+ },
697
+ },
698
+ }) as URL,
699
+ });
700
+ try {
701
+ const schema = z.url({ normalize: true });
702
+ const parser = compileFn(schema);
703
+ const validator = compileFn(schema, { assertOnly: true });
704
+ expect(validator("https://example.com")).toBe(true);
705
+ expect(z.validate(compile(z.url({ normalize: true })), "https://example.com")).toBe(true);
706
+ expect(z.validate(compile(z.object({ url: z.url({ normalize: true }) })), { url: "https://example.com" })).toBe(
707
+ true
708
+ );
709
+ expect(() => parser("https://example.com")).toThrow(marker);
710
+ } finally {
711
+ Object.defineProperty(URLStatic, "parse", descriptor);
712
+ }
713
+ });
714
+
680
715
  test("empty strict object compiles", () => {
681
716
  // With no keys the unknown-key condition is empty, and `if () return INVALID;` is a syntax error the single top-level `new Function` rejects — too late for compileChild to island, so the whole tree lost its fast path.
682
717
  differential(z.strictObject({}), [{}, { a: 1 }, Object.create({ inherited: 1 })]);
@@ -1787,6 +1787,183 @@ test("compiled records walk own enumerable keys without Reflect.ownKeys", () =>
1787
1787
  expect(compile(plain).parse(revealing())).toStrictEqual({ a: 1, b: 2 });
1788
1788
  });
1789
1789
 
1790
+ test("validate honors custom checks composed with format traits", () => {
1791
+ const postProcessor = z.core.globalConfig.postProcessor;
1792
+ z.core.globalConfig.postProcessor = undefined;
1793
+ try {
1794
+ const CustomEmail = z.core.$constructor<z.core.$ZodEmail>("CustomEmail", (inst, def) => {
1795
+ inst._zod.check = (payload) => {
1796
+ if (payload.value !== "custom") payload.issues.push({ code: "custom", input: payload.value });
1797
+ };
1798
+ z.core.$ZodEmail.init(inst, def);
1799
+ });
1800
+ const schema = new CustomEmail({ type: "string", format: "email", check: "string_format" });
1801
+ for (const [input, valid] of [
1802
+ ["custom", true],
1803
+ ["test@example.com", false],
1804
+ [42, false],
1805
+ ] as const) {
1806
+ expect(z.safeParse(schema, input).success).toBe(valid);
1807
+ expect(z.validate(schema, input)).toBe(valid);
1808
+ }
1809
+ } finally {
1810
+ z.core.globalConfig.postProcessor = postProcessor;
1811
+ }
1812
+ });
1813
+
1814
+ test("validate honors standalone format checks", () => {
1815
+ const cases = [
1816
+ [z.iso.datetime(), "2021-01-01T00:00:00Z"],
1817
+ [z.iso.date(), "2021-01-01"],
1818
+ [z.iso.time(), "00:00:00"],
1819
+ [z.iso.duration(), "P1Y2M3DT4H5M6S"],
1820
+ [z.email(), "test@example.com"],
1821
+ [z.uuid(), "20354d7a-e4fe-47af-8ff6-187bca92f3f9"],
1822
+ [z.ipv4(), "192.168.0.1"],
1823
+ ] as const;
1824
+
1825
+ for (const [schema, valid] of cases) {
1826
+ expect(schema._zod.traits.has("$ZodStringFormat")).toBe(true);
1827
+ expect(schema instanceof z.core.$ZodStringFormat).toBe(true);
1828
+ expect(z.validate(schema, valid)).toBe(true);
1829
+ expect(z.validate(schema, "invalid")).toBe(false);
1830
+ expect(z.validate(schema, 1)).toBe(false);
1831
+ }
1832
+
1833
+ const source = z.email();
1834
+ const refined = source.refine((value) => value.startsWith("x"));
1835
+ const asyncRefined = source.refine(async () => true);
1836
+ const coercedDef = Object.assign(Object.create({ coerce: true }), source._zod.def);
1837
+ const coerced = source.clone(coercedDef);
1838
+ const checkedDef = Object.assign(Object.create({ checks: refined._zod.def.checks }), source._zod.def);
1839
+ const inheritedChecked = source.clone(checkedDef);
1840
+ expect(z.validate(refined, "test@example.com")).toBe(false);
1841
+ expect(z.validate(refined, "x@example.com")).toBe(true);
1842
+ expect(z.validate(inheritedChecked, "test@example.com")).toBe(false);
1843
+ expect(() => z.validate(asyncRefined, "test@example.com")).toThrow($ZodAsyncError);
1844
+ expect(z.validate(coerced, { toString: () => "test@example.com" })).toBe(true);
1845
+ });
1846
+
1847
+ test("runtime validate preserves contexts, getters, errors, and regex state", () => {
1848
+ const postProcessor = z.core.globalConfig.postProcessor;
1849
+ z.core.globalConfig.postProcessor = undefined;
1850
+ try {
1851
+ const source = z.email();
1852
+ expect(z.validate(source, "invalid", { skipChecks: true } as any)).toBe(true);
1853
+
1854
+ let whenReads = 0;
1855
+ const whenProto = Object.defineProperty({}, "when", {
1856
+ enumerable: true,
1857
+ get() {
1858
+ whenReads++;
1859
+ return () => false;
1860
+ },
1861
+ });
1862
+ const whenDef = Object.assign(Object.create(whenProto), source._zod.def);
1863
+ const gated = source.clone(whenDef as typeof source._zod.def);
1864
+ expect(whenReads).toBe(0);
1865
+ expect(z.validate(gated, "invalid")).toBe(true);
1866
+ expect(whenReads).toBeGreaterThan(0);
1867
+
1868
+ let errorReads = 0;
1869
+ const errorDef = Object.defineProperties({}, Object.getOwnPropertyDescriptors(source._zod.def));
1870
+ Object.defineProperty(errorDef, "error", {
1871
+ enumerable: true,
1872
+ get() {
1873
+ errorReads++;
1874
+ return () => "custom";
1875
+ },
1876
+ });
1877
+ const customError = source.clone(errorDef as typeof source._zod.def);
1878
+ expect(z.validate(customError, "invalid")).toBe(false);
1879
+ expect(errorReads).toBe(0);
1880
+ const result = customError.safeParse("invalid");
1881
+ expect(errorReads).toBe(0);
1882
+ if (result.success) expect.unreachable();
1883
+ expect(result.error.issues[0]?.message).toBe("custom");
1884
+ expect(errorReads).toBeGreaterThan(0);
1885
+
1886
+ const pattern = /^a$/g;
1887
+ const cloned = source.clone({ ...source._zod.def, pattern });
1888
+ for (let i = 0; i < 3; i++) expect(z.validate(cloned, "a")).toBe(true);
1889
+ expect(z.validate(cloned, "b")).toBe(false);
1890
+ expect(pattern.lastIndex).toBe(0);
1891
+ const replacement = /^b$/g;
1892
+ cloned._zod.def.pattern = replacement;
1893
+ expect(z.validate(cloned, "a")).toBe(false);
1894
+ expect(z.validate(cloned, "b")).toBe(true);
1895
+ } finally {
1896
+ z.core.globalConfig.postProcessor = postProcessor;
1897
+ }
1898
+ });
1899
+
1900
+ test("validate preserves live pattern accessor reads", () => {
1901
+ const postProcessor = z.core.globalConfig.postProcessor;
1902
+ z.core.globalConfig.postProcessor = undefined;
1903
+ try {
1904
+ const schema = z.email();
1905
+ let reads = 0;
1906
+ Object.defineProperty(schema.def, "pattern", {
1907
+ get: () => (++reads % 2 === 1 ? /^a$/ : /^b$/),
1908
+ });
1909
+ expect(schema.safeParse("b").success).toBe(true);
1910
+ expect(reads).toBe(2);
1911
+ reads = 0;
1912
+ expect(z.validate(schema, "b")).toBe(true);
1913
+ expect(reads).toBe(2);
1914
+ } finally {
1915
+ z.core.globalConfig.postProcessor = postProcessor;
1916
+ }
1917
+ });
1918
+
1919
+ test.each(["when", "coerce"] as const)("runtime validate honors live %s options", (option) => {
1920
+ const postProcessor = z.core.globalConfig.postProcessor;
1921
+ z.core.globalConfig.postProcessor = undefined;
1922
+ try {
1923
+ for (const inherited of [false, true]) {
1924
+ const schema = z.email();
1925
+ const input = option === "when" ? "invalid" : { toString: () => "test@example.com" };
1926
+ expect(z.validate(schema, input)).toBe(false);
1927
+
1928
+ const target = inherited ? Object.create(Object.getPrototypeOf(schema.def)) : schema.def;
1929
+ let reads = 0;
1930
+ Object.defineProperty(target, option, {
1931
+ get() {
1932
+ reads++;
1933
+ return option === "when" ? () => false : true;
1934
+ },
1935
+ });
1936
+ if (inherited) Object.setPrototypeOf(schema.def, target);
1937
+ expect(reads).toBe(0);
1938
+ expect(z.validate(schema, input)).toBe(true);
1939
+ expect(reads).toBeGreaterThan(0);
1940
+ expect(schema.safeParse(input).success).toBe(true);
1941
+ }
1942
+ } finally {
1943
+ z.core.globalConfig.postProcessor = postProcessor;
1944
+ }
1945
+ });
1946
+
1947
+ test("validate uses compiled validators and runtime fallbacks", () => {
1948
+ const compiled = compile(z.email());
1949
+ expect((compiled._zod.bag as { validator?: unknown }).validator).toBeTypeOf("function");
1950
+ expect(z.validate(compiled, "test@example.com")).toBe(true);
1951
+ expect(z.validate(compiled, "invalid")).toBe(false);
1952
+ expect(z.validate(compiled, "invalid", { skipChecks: true } as any)).toBe(true);
1953
+
1954
+ const source = z.email();
1955
+ const installed = withParser(source, () => INVALID);
1956
+ const bag = installed._zod.bag as { fallbackRun: typeof source._zod.run };
1957
+ const originalRun = bag.fallbackRun;
1958
+ let fallbackRuns = 0;
1959
+ bag.fallbackRun = (payload, ctx) => {
1960
+ fallbackRuns++;
1961
+ return originalRun(payload, ctx);
1962
+ };
1963
+ expect(z.validate(installed, "invalid")).toBe(false);
1964
+ expect(fallbackRuns).toBeGreaterThan(0);
1965
+ });
1966
+
1790
1967
  // withParser: a parser Zod did not generate, installed on the same wrapper compile() uses.
1791
1968
 
1792
1969
  // tags its output so a test fails loudly if the runtime ran instead of the supplied parser
@@ -1,31 +1,133 @@
1
1
  import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
2
2
 
3
- // schemas.ts resolves URL.canParse on first use, not at module load. Resetting the module registry gives the re-imported copy a fresh, unresolved `urlCanParseImpl`, so deleting URL.canParse before the first parse still selects the inline fallback branch (URL.canParse may be undefined on older runtimes).
4
- describe("z.url() with URL.canParse absent", () => {
5
- const original = URL.canParse;
3
+ const URLDescriptor = Object.getOwnPropertyDescriptor(globalThis, "URL")!;
4
+ const NativeURL = globalThis.URL;
5
+ const canParseDescriptor = Object.getOwnPropertyDescriptor(NativeURL, "canParse")!;
6
+ const parseDescriptor = Object.getOwnPropertyDescriptor(NativeURL, "parse")!;
6
7
 
8
+ function restoreURL() {
9
+ Object.defineProperty(globalThis, "URL", URLDescriptor);
10
+ Object.defineProperty(NativeURL, "canParse", canParseDescriptor);
11
+ Object.defineProperty(NativeURL, "parse", parseDescriptor);
12
+ }
13
+
14
+ describe("URL parser fallbacks", () => {
7
15
  beforeEach(() => {
16
+ restoreURL();
8
17
  vi.resetModules();
9
- // @ts-expect-error force the fallback branch (URL.canParse may be undefined on older runtimes)
10
- delete URL.canParse;
11
18
  });
12
19
 
13
- afterEach(() => {
14
- URL.canParse = original;
20
+ afterEach(restoreURL);
21
+
22
+ test("uses the boolean and object APIs on modern runtimes", async () => {
23
+ const canParse = vi.spyOn(NativeURL, "canParse");
24
+ const URLStatic = NativeURL as typeof URL & { parse: (input: string) => URL | null };
25
+ const parse = vi.spyOn(URLStatic, "parse");
26
+ const z = await import("../../index.js");
27
+
28
+ expect(z.validate(z.url(), "https://example.com")).toBe(true);
29
+ expect(canParse).toHaveBeenCalledOnce();
30
+ expect(parse).not.toHaveBeenCalled();
31
+
32
+ canParse.mockClear();
33
+ expect(z.validate(z.url({ normalize: true }), "https://example.com")).toBe(true);
34
+ expect(parse).toHaveBeenCalledOnce();
35
+ expect(canParse).not.toHaveBeenCalled();
36
+
37
+ parse.mockClear();
38
+ expect(z.validate(z.ipv6(), "2001:db8::1")).toBe(true);
39
+ expect(canParse).toHaveBeenCalledOnce();
40
+ expect(parse).not.toHaveBeenCalled();
15
41
  });
16
42
 
17
- test("z.url() validates correctly without URL.canParse (runtime)", async () => {
18
- expect(URL.canParse).toBeUndefined();
43
+ test("URL fast-path selection preserves live option getter reads", async () => {
19
44
  const z = await import("../../index.js");
20
- expect(z.url().safeParse("https://example.com").success).toBe(true);
21
- expect(z.url().safeParse("not a url").success).toBe(false);
45
+ const postProcessor = z.core.globalConfig.postProcessor;
46
+ z.core.globalConfig.postProcessor = undefined;
47
+ try {
48
+ for (const inherited of [false, true]) {
49
+ const schema = z.url();
50
+ const target = inherited ? Object.create(Object.getPrototypeOf(schema.def)) : schema.def;
51
+ let reads = 0;
52
+ Object.defineProperty(target, "hostname", {
53
+ get: () => (++reads === 1 ? /^example\.com$/ : undefined),
54
+ });
55
+ if (inherited) Object.setPrototypeOf(schema.def, target);
56
+ expect(z.validate(schema, "https://example.com")).toBe(false);
57
+ expect(reads).toBe(2);
58
+ reads = 0;
59
+ expect(schema.safeParse("https://example.com").success).toBe(false);
60
+ expect(reads).toBe(2);
61
+ }
62
+ } finally {
63
+ z.core.globalConfig.postProcessor = postProcessor;
64
+ }
65
+ });
66
+
67
+ test("the exported object parser still returns a URL", async () => {
68
+ const { parseURLObject } = await import("../schemas.js");
69
+ const result = parseURLObject("https://example.com", {});
70
+ expect(result).toBeInstanceOf(NativeURL);
71
+ if (typeof result === "number") expect.unreachable();
72
+ expect(result.hostname).toBe("example.com");
73
+ expect(parseURLObject("invalid", {})).toBe(2);
22
74
  });
23
75
 
24
- test("z.url() validates correctly without URL.canParse (compiled)", async () => {
25
- expect(URL.canParse).toBeUndefined();
76
+ test("falls back when URL.canParse is absent", async () => {
77
+ Reflect.deleteProperty(NativeURL, "canParse");
26
78
  const z = await import("../../index.js");
27
- const compiled = z.compile(z.url());
28
- expect(compiled.safeParse("https://example.com").success).toBe(true);
29
- expect(compiled.safeParse("not a url").success).toBe(false);
79
+ const cases = [
80
+ [z.url(), "https://example.com", "not a url"],
81
+ [z.ipv6(), "2001:db8::1", "not-an-ip"],
82
+ [z.cidrv6(), "2001:db8::/32", "not-an-ip/32"],
83
+ ] as const;
84
+
85
+ for (const [schema, valid, invalid] of cases) {
86
+ for (const subject of [schema, z.compile(schema)]) {
87
+ expect(z.validate(subject, valid)).toBe(true);
88
+ expect(z.validate(subject, invalid)).toBe(false);
89
+ expect(subject.safeParse(valid).success).toBe(true);
90
+ expect(subject.safeParse(invalid).success).toBe(false);
91
+ }
92
+ }
93
+ });
94
+
95
+ test("falls back when URL.parse is absent", async () => {
96
+ Reflect.deleteProperty(NativeURL, "parse");
97
+ const z = await import("../../index.js");
98
+ const cases = [
99
+ [z.url({ normalize: true }), "https://example.com", "not a url"],
100
+ [z.url({ hostname: /^example\.com$/ }), "https://example.com", "https://other.com"],
101
+ [z.httpUrl(), "https://example.com", "mailto:a@example.com"],
102
+ ] as const;
103
+
104
+ for (const [schema, valid, invalid] of cases) {
105
+ for (const subject of [schema, z.compile(schema)]) {
106
+ expect(z.validate(subject, valid)).toBe(true);
107
+ expect(z.validate(subject, invalid)).toBe(false);
108
+ expect(subject.safeParse(valid).success).toBe(true);
109
+ expect(subject.safeParse(invalid).success).toBe(false);
110
+ }
111
+ }
112
+ });
113
+
114
+ test("rejects without a global URL constructor", async () => {
115
+ const z = await import("../../index.js");
116
+ const cases = [
117
+ [z.url(), "https://example.com"],
118
+ [z.url({ normalize: true }), "https://example.com"],
119
+ [z.ipv6(), "2001:db8::1"],
120
+ [z.cidrv6(), "2001:db8::/32"],
121
+ ] as const;
122
+ const subjects = cases.flatMap(([schema, input]) => [
123
+ [schema, input] as const,
124
+ [z.compile(schema), input] as const,
125
+ ]);
126
+ Object.defineProperty(globalThis, "URL", { configurable: true, value: undefined });
127
+
128
+ for (const [schema, input] of subjects) {
129
+ expect(z.validate(schema, input)).toBe(false);
130
+ expect(schema.safeParse(input).success).toBe(false);
131
+ }
30
132
  });
31
133
  });
@@ -757,7 +757,7 @@ export function finalize<T extends schemas.$ZodType>(
757
757
  }
758
758
  }
759
759
 
760
- export function isTransforming(
760
+ function isTransforming(
761
761
  _schema: schemas.$ZodType,
762
762
  _ctx?: {
763
763
  seen: Set<schemas.$ZodType>;
@@ -1125,10 +1125,6 @@ export function members(proto: object, table: object): void {
1125
1125
  // a method materializes bound on first read, which is what keeps a detached member working: `const opt = schema.optional; opt()`
1126
1126
  else defineBound(proto, key, desc.value);
1127
1127
  }
1128
- // for..in sees no symbol keys, so well-known members like Symbol.iterator install here
1129
- for (const sym of Object.getOwnPropertySymbols(table)) {
1130
- defineBound(proto, sym, (table as any)[sym]);
1131
- }
1132
1128
  }
1133
1129
 
1134
1130
  /** Shadows a prototype member with an own value, so a getter that builds from the instance runs once. */
@@ -1,5 +1,5 @@
1
1
  export const version = {
2
2
  major: 4,
3
3
  minor: 6,
4
- patch: 2 as number,
4
+ patch: 4 as number,
5
5
  } as const;
@@ -158,17 +158,6 @@ export function visit(schema: schemas.SomeType, fnOrHandlers: VisitFn | VisitHan
158
158
  const { _cachedInner, ...rest } = def;
159
159
  return clone(s, { ...rest, getter: () => run(original()) });
160
160
  }
161
- case "properties": {
162
- const oldShape = def.shape as Record<string | symbol, AnyZod>;
163
- let changed = false;
164
- const newShape: Record<string | symbol, AnyZod> = {};
165
- for (const k of Reflect.ownKeys(oldShape)) {
166
- const mapped = run(oldShape[k]!);
167
- if (mapped !== oldShape[k]) changed = true;
168
- newShape[k] = mapped;
169
- }
170
- return changed ? clone(s, { ...def, shape: newShape }) : s;
171
- }
172
161
  // A leaf by choice: `parts` are regex fragments, not data positions.
173
162
  case "template_literal":
174
163
  // Leaves.
@@ -46,6 +46,7 @@ const error: () => errors.$ZodErrorMap = () => {
46
46
  json_string: "نَص على هيئة JSON",
47
47
  e164: "رقم هاتف بمعيار E.164",
48
48
  credit_card: "رقم بطاقة الائتمان",
49
+ currency_code: "رمز العملة",
49
50
  iban: "IBAN",
50
51
  jwt: "JWT",
51
52
  template_literal: "مدخل",
@@ -46,6 +46,7 @@ const error: () => errors.$ZodErrorMap = () => {
46
46
  json_string: "JSON string",
47
47
  e164: "E.164 number",
48
48
  credit_card: "kredit kartı nömrəsi",
49
+ currency_code: "valyuta kodu",
49
50
  iban: "IBAN",
50
51
  jwt: "JWT",
51
52
  template_literal: "input",
@@ -109,6 +109,7 @@ const error: () => errors.$ZodErrorMap = () => {
109
109
  json_string: "JSON радок",
110
110
  e164: "нумар E.164",
111
111
  credit_card: "нумар крэдытнай карты",
112
+ currency_code: "код валюты",
112
113
  iban: "IBAN",
113
114
  jwt: "JWT",
114
115
  template_literal: "увод",
@@ -46,6 +46,7 @@ const error: () => errors.$ZodErrorMap = () => {
46
46
  json_string: "JSON низ",
47
47
  e164: "E.164 номер",
48
48
  credit_card: "номер на кредитна карта",
49
+ currency_code: "код на валута",
49
50
  iban: "IBAN",
50
51
  jwt: "JWT",
51
52
  template_literal: "вход",