zod 4.5.3 → 4.6.0

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 (348) hide show
  1. package/compile.cjs +19 -0
  2. package/index.cjs +19 -0
  3. package/locales/index.cjs +19 -0
  4. package/mini/index.cjs +19 -0
  5. package/package.json +8 -7
  6. package/src/v3/helpers/errorUtil.ts +1 -1
  7. package/src/v4/classic/checks.ts +0 -1
  8. package/src/v4/classic/external.ts +2 -0
  9. package/src/v4/classic/from-json-schema.ts +248 -25
  10. package/src/v4/classic/schemas.ts +255 -164
  11. package/src/v4/classic/tests/assignability.test.ts +59 -9
  12. package/src/v4/classic/tests/bigint.test.ts +6 -0
  13. package/src/v4/classic/tests/continuability.test.ts +2 -2
  14. package/src/v4/classic/tests/cyclic-data.test.ts +316 -0
  15. package/src/v4/classic/tests/default.test.ts +46 -0
  16. package/src/v4/classic/tests/enum.test.ts +9 -0
  17. package/src/v4/classic/tests/error.test.ts +130 -1
  18. package/src/v4/classic/tests/firstparty.test.ts +4 -0
  19. package/src/v4/classic/tests/from-json-schema.test.ts +157 -0
  20. package/src/v4/classic/tests/iban.test.ts +81 -0
  21. package/src/v4/classic/tests/instance-footprint.test.ts +18 -2
  22. package/src/v4/classic/tests/instanceof.test.ts +27 -7
  23. package/src/v4/classic/tests/issue-schema.test.ts +88 -56
  24. package/src/v4/classic/tests/object.test.ts +56 -0
  25. package/src/v4/classic/tests/optin-ladder.test.ts +3 -2
  26. package/src/v4/classic/tests/properties.test.ts +225 -0
  27. package/src/v4/classic/tests/recursive-types.test.ts +1 -1
  28. package/src/v4/classic/tests/standard-schema.test.ts +19 -0
  29. package/src/v4/classic/tests/string.test.ts +25 -0
  30. package/src/v4/classic/tests/template-literal.test.ts +33 -1
  31. package/src/v4/classic/tests/to-json-schema.test.ts +147 -3
  32. package/src/v4/classic/tests/validate.test.ts +569 -0
  33. package/src/v4/core/api.ts +30 -6
  34. package/src/v4/core/checks.ts +1 -101
  35. package/src/v4/core/compile.ts +193 -81
  36. package/src/v4/core/doc.ts +6 -2
  37. package/src/v4/core/errors.ts +3 -6
  38. package/src/v4/core/json-schema-generator.ts +2 -2
  39. package/src/v4/core/json-schema-processors.ts +226 -68
  40. package/src/v4/core/memoizer.ts +162 -36
  41. package/src/v4/core/parse.ts +37 -16
  42. package/src/v4/core/regexes.ts +10 -4
  43. package/src/v4/core/schemas.ts +314 -83
  44. package/src/v4/core/tests/cached.test.ts +54 -0
  45. package/src/v4/core/tests/compile-generative.test.ts +445 -0
  46. package/src/v4/core/tests/compile.test.ts +222 -2
  47. package/src/v4/core/tests/email-regex.test.ts +140 -0
  48. package/src/v4/core/tests/polyfill-collision.test.ts +46 -0
  49. package/src/v4/core/to-json-schema.ts +10 -8
  50. package/src/v4/core/util.ts +222 -147
  51. package/src/v4/core/versions.ts +2 -2
  52. package/src/v4/core/visit.ts +11 -0
  53. package/src/v4/locales/ar.ts +1 -0
  54. package/src/v4/locales/az.ts +1 -0
  55. package/src/v4/locales/be.ts +1 -0
  56. package/src/v4/locales/bg.ts +1 -0
  57. package/src/v4/locales/bn.ts +1 -0
  58. package/src/v4/locales/ca.ts +1 -0
  59. package/src/v4/locales/ckb.ts +1 -0
  60. package/src/v4/locales/cs.ts +1 -0
  61. package/src/v4/locales/da.ts +1 -0
  62. package/src/v4/locales/de.ts +1 -0
  63. package/src/v4/locales/el.ts +1 -0
  64. package/src/v4/locales/en.ts +1 -0
  65. package/src/v4/locales/eo.ts +1 -0
  66. package/src/v4/locales/es.ts +1 -0
  67. package/src/v4/locales/fa.ts +1 -0
  68. package/src/v4/locales/fi.ts +1 -0
  69. package/src/v4/locales/fr-CA.ts +1 -0
  70. package/src/v4/locales/fr.ts +1 -0
  71. package/src/v4/locales/gu.ts +1 -0
  72. package/src/v4/locales/he.ts +1 -0
  73. package/src/v4/locales/hi.ts +1 -0
  74. package/src/v4/locales/hr.ts +1 -0
  75. package/src/v4/locales/hu.ts +1 -0
  76. package/src/v4/locales/hy.ts +1 -0
  77. package/src/v4/locales/id.ts +1 -0
  78. package/src/v4/locales/is.ts +1 -0
  79. package/src/v4/locales/it.ts +1 -0
  80. package/src/v4/locales/ja.ts +1 -0
  81. package/src/v4/locales/ka.ts +1 -0
  82. package/src/v4/locales/km.ts +1 -0
  83. package/src/v4/locales/kn.ts +1 -0
  84. package/src/v4/locales/ko.ts +1 -0
  85. package/src/v4/locales/lt.ts +1 -0
  86. package/src/v4/locales/mk.ts +1 -0
  87. package/src/v4/locales/ms.ts +1 -0
  88. package/src/v4/locales/ne.ts +1 -0
  89. package/src/v4/locales/nl.ts +1 -0
  90. package/src/v4/locales/nn.ts +1 -0
  91. package/src/v4/locales/no.ts +1 -0
  92. package/src/v4/locales/ota.ts +1 -0
  93. package/src/v4/locales/pl.ts +1 -0
  94. package/src/v4/locales/ps.ts +1 -0
  95. package/src/v4/locales/pt-BR.ts +1 -0
  96. package/src/v4/locales/pt.ts +1 -0
  97. package/src/v4/locales/ro.ts +1 -0
  98. package/src/v4/locales/ru.ts +1 -0
  99. package/src/v4/locales/sk.ts +1 -0
  100. package/src/v4/locales/sl.ts +1 -0
  101. package/src/v4/locales/sv.ts +1 -0
  102. package/src/v4/locales/ta.ts +1 -0
  103. package/src/v4/locales/th.ts +1 -0
  104. package/src/v4/locales/tk.ts +1 -0
  105. package/src/v4/locales/tr.ts +1 -0
  106. package/src/v4/locales/uk.ts +1 -0
  107. package/src/v4/locales/ur.ts +1 -0
  108. package/src/v4/locales/uz.ts +1 -0
  109. package/src/v4/locales/vi.ts +1 -0
  110. package/src/v4/locales/yo.ts +1 -0
  111. package/src/v4/locales/zh-CN.ts +1 -0
  112. package/src/v4/locales/zh-TW.ts +1 -0
  113. package/src/v4/mini/checks.ts +0 -1
  114. package/src/v4/mini/external.ts +2 -0
  115. package/src/v4/mini/schemas.ts +38 -2
  116. package/src/v4/mini/tests/checks.test.ts +1 -1
  117. package/src/v4/mini/tests/computed.test.ts +11 -17
  118. package/src/v4/mini/tests/index.test.ts +8 -0
  119. package/src/v4/mini/tests/string.test.ts +16 -0
  120. package/v3/ZodError.cjs +19 -0
  121. package/v3/errors.cjs +19 -0
  122. package/v3/external.cjs +19 -0
  123. package/v3/helpers/enumUtil.cjs +19 -0
  124. package/v3/helpers/errorUtil.cjs +20 -1
  125. package/v3/helpers/errorUtil.js +1 -1
  126. package/v3/helpers/parseUtil.cjs +19 -0
  127. package/v3/helpers/partialUtil.cjs +19 -0
  128. package/v3/helpers/typeAliases.cjs +19 -0
  129. package/v3/helpers/util.cjs +19 -0
  130. package/v3/index.cjs +19 -0
  131. package/v3/standard-schema.cjs +19 -0
  132. package/v3/types.cjs +19 -0
  133. package/v4/classic/checks.cjs +20 -2
  134. package/v4/classic/checks.d.cts +1 -1
  135. package/v4/classic/checks.d.ts +1 -1
  136. package/v4/classic/checks.js +1 -1
  137. package/v4/classic/coerce.cjs +19 -0
  138. package/v4/classic/compat.cjs +19 -0
  139. package/v4/classic/deep-partial.cjs +19 -0
  140. package/v4/classic/errors.cjs +19 -0
  141. package/v4/classic/external.cjs +22 -1
  142. package/v4/classic/external.d.cts +1 -1
  143. package/v4/classic/external.d.ts +1 -1
  144. package/v4/classic/external.js +1 -1
  145. package/v4/classic/from-json-schema.cjs +264 -24
  146. package/v4/classic/from-json-schema.js +245 -24
  147. package/v4/classic/in-out.cjs +19 -0
  148. package/v4/classic/index.cjs +19 -0
  149. package/v4/classic/iso.cjs +19 -0
  150. package/v4/classic/parse.cjs +19 -0
  151. package/v4/classic/schemas.cjs +100 -31
  152. package/v4/classic/schemas.d.cts +17 -1
  153. package/v4/classic/schemas.d.ts +17 -1
  154. package/v4/classic/schemas.js +77 -29
  155. package/v4/core/api.cjs +37 -2
  156. package/v4/core/api.d.cts +6 -4
  157. package/v4/core/api.d.ts +6 -4
  158. package/v4/core/api.js +17 -2
  159. package/v4/core/checks.cjs +19 -96
  160. package/v4/core/checks.d.cts +1 -1
  161. package/v4/core/checks.d.ts +1 -1
  162. package/v4/core/checks.js +0 -96
  163. package/v4/core/compile.cjs +164 -65
  164. package/v4/core/compile.d.cts +18 -4
  165. package/v4/core/compile.d.ts +18 -4
  166. package/v4/core/compile.js +145 -66
  167. package/v4/core/core.cjs +19 -0
  168. package/v4/core/doc.cjs +26 -2
  169. package/v4/core/doc.js +7 -2
  170. package/v4/core/errors.cjs +21 -5
  171. package/v4/core/errors.js +2 -5
  172. package/v4/core/index.cjs +19 -0
  173. package/v4/core/json-schema-generator.cjs +20 -1
  174. package/v4/core/json-schema-generator.js +2 -2
  175. package/v4/core/json-schema-processors.cjs +215 -63
  176. package/v4/core/json-schema-processors.d.cts +17 -0
  177. package/v4/core/json-schema-processors.d.ts +17 -0
  178. package/v4/core/json-schema-processors.js +195 -64
  179. package/v4/core/json-schema.cjs +19 -0
  180. package/v4/core/memoizer.cjs +204 -36
  181. package/v4/core/memoizer.js +162 -36
  182. package/v4/core/parse.cjs +56 -16
  183. package/v4/core/parse.js +37 -16
  184. package/v4/core/regexes.cjs +29 -6
  185. package/v4/core/regexes.d.cts +2 -0
  186. package/v4/core/regexes.d.ts +2 -0
  187. package/v4/core/regexes.js +8 -4
  188. package/v4/core/registries.cjs +19 -0
  189. package/v4/core/schemas.cjs +285 -66
  190. package/v4/core/schemas.d.cts +34 -3
  191. package/v4/core/schemas.d.ts +34 -3
  192. package/v4/core/schemas.js +264 -65
  193. package/v4/core/standard-schema.cjs +19 -0
  194. package/v4/core/to-json-schema.cjs +29 -7
  195. package/v4/core/to-json-schema.d.cts +7 -3
  196. package/v4/core/to-json-schema.d.ts +7 -3
  197. package/v4/core/to-json-schema.js +8 -7
  198. package/v4/core/util.cjs +181 -143
  199. package/v4/core/util.d.cts +25 -4
  200. package/v4/core/util.d.ts +25 -4
  201. package/v4/core/util.js +160 -143
  202. package/v4/core/versions.cjs +21 -2
  203. package/v4/core/versions.d.cts +1 -1
  204. package/v4/core/versions.d.ts +1 -1
  205. package/v4/core/versions.js +2 -2
  206. package/v4/core/visit.cjs +31 -0
  207. package/v4/core/visit.js +12 -0
  208. package/v4/index.cjs +19 -0
  209. package/v4/locales/ar.cjs +1 -0
  210. package/v4/locales/ar.js +1 -0
  211. package/v4/locales/az.cjs +1 -0
  212. package/v4/locales/az.js +1 -0
  213. package/v4/locales/be.cjs +1 -0
  214. package/v4/locales/be.js +1 -0
  215. package/v4/locales/bg.cjs +1 -0
  216. package/v4/locales/bg.js +1 -0
  217. package/v4/locales/bn.cjs +1 -0
  218. package/v4/locales/bn.js +1 -0
  219. package/v4/locales/ca.cjs +1 -0
  220. package/v4/locales/ca.js +1 -0
  221. package/v4/locales/ckb.cjs +1 -0
  222. package/v4/locales/ckb.js +1 -0
  223. package/v4/locales/cs.cjs +1 -0
  224. package/v4/locales/cs.js +1 -0
  225. package/v4/locales/da.cjs +1 -0
  226. package/v4/locales/da.js +1 -0
  227. package/v4/locales/de.cjs +1 -0
  228. package/v4/locales/de.js +1 -0
  229. package/v4/locales/el.cjs +1 -0
  230. package/v4/locales/el.js +1 -0
  231. package/v4/locales/en.cjs +1 -0
  232. package/v4/locales/en.js +1 -0
  233. package/v4/locales/eo.cjs +1 -0
  234. package/v4/locales/eo.js +1 -0
  235. package/v4/locales/es.cjs +1 -0
  236. package/v4/locales/es.js +1 -0
  237. package/v4/locales/fa.cjs +1 -0
  238. package/v4/locales/fa.js +1 -0
  239. package/v4/locales/fi.cjs +1 -0
  240. package/v4/locales/fi.js +1 -0
  241. package/v4/locales/fr-CA.cjs +1 -0
  242. package/v4/locales/fr-CA.js +1 -0
  243. package/v4/locales/fr.cjs +1 -0
  244. package/v4/locales/fr.js +1 -0
  245. package/v4/locales/gu.cjs +1 -0
  246. package/v4/locales/gu.js +1 -0
  247. package/v4/locales/he.cjs +1 -0
  248. package/v4/locales/he.js +1 -0
  249. package/v4/locales/hi.cjs +1 -0
  250. package/v4/locales/hi.js +1 -0
  251. package/v4/locales/hr.cjs +1 -0
  252. package/v4/locales/hr.js +1 -0
  253. package/v4/locales/hu.cjs +1 -0
  254. package/v4/locales/hu.js +1 -0
  255. package/v4/locales/hy.cjs +1 -0
  256. package/v4/locales/hy.js +1 -0
  257. package/v4/locales/id.cjs +1 -0
  258. package/v4/locales/id.js +1 -0
  259. package/v4/locales/index.cjs +19 -0
  260. package/v4/locales/is.cjs +1 -0
  261. package/v4/locales/is.js +1 -0
  262. package/v4/locales/it.cjs +1 -0
  263. package/v4/locales/it.js +1 -0
  264. package/v4/locales/ja.cjs +1 -0
  265. package/v4/locales/ja.js +1 -0
  266. package/v4/locales/ka.cjs +1 -0
  267. package/v4/locales/ka.js +1 -0
  268. package/v4/locales/km.cjs +1 -0
  269. package/v4/locales/km.js +1 -0
  270. package/v4/locales/kn.cjs +1 -0
  271. package/v4/locales/kn.js +1 -0
  272. package/v4/locales/ko.cjs +1 -0
  273. package/v4/locales/ko.js +1 -0
  274. package/v4/locales/lt.cjs +1 -0
  275. package/v4/locales/lt.js +1 -0
  276. package/v4/locales/mk.cjs +1 -0
  277. package/v4/locales/mk.js +1 -0
  278. package/v4/locales/ms.cjs +1 -0
  279. package/v4/locales/ms.js +1 -0
  280. package/v4/locales/ne.cjs +1 -0
  281. package/v4/locales/ne.js +1 -0
  282. package/v4/locales/nl.cjs +1 -0
  283. package/v4/locales/nl.js +1 -0
  284. package/v4/locales/nn.cjs +1 -0
  285. package/v4/locales/nn.js +1 -0
  286. package/v4/locales/no.cjs +1 -0
  287. package/v4/locales/no.js +1 -0
  288. package/v4/locales/ota.cjs +1 -0
  289. package/v4/locales/ota.js +1 -0
  290. package/v4/locales/pl.cjs +1 -0
  291. package/v4/locales/pl.js +1 -0
  292. package/v4/locales/ps.cjs +1 -0
  293. package/v4/locales/ps.js +1 -0
  294. package/v4/locales/pt-BR.cjs +1 -0
  295. package/v4/locales/pt-BR.js +1 -0
  296. package/v4/locales/pt.cjs +1 -0
  297. package/v4/locales/pt.js +1 -0
  298. package/v4/locales/ro.cjs +1 -0
  299. package/v4/locales/ro.js +1 -0
  300. package/v4/locales/ru.cjs +1 -0
  301. package/v4/locales/ru.js +1 -0
  302. package/v4/locales/sk.cjs +1 -0
  303. package/v4/locales/sk.js +1 -0
  304. package/v4/locales/sl.cjs +1 -0
  305. package/v4/locales/sl.js +1 -0
  306. package/v4/locales/sv.cjs +1 -0
  307. package/v4/locales/sv.js +1 -0
  308. package/v4/locales/ta.cjs +1 -0
  309. package/v4/locales/ta.js +1 -0
  310. package/v4/locales/th.cjs +1 -0
  311. package/v4/locales/th.js +1 -0
  312. package/v4/locales/tk.cjs +1 -0
  313. package/v4/locales/tk.js +1 -0
  314. package/v4/locales/tr.cjs +1 -0
  315. package/v4/locales/tr.js +1 -0
  316. package/v4/locales/uk.cjs +1 -0
  317. package/v4/locales/uk.js +1 -0
  318. package/v4/locales/ur.cjs +1 -0
  319. package/v4/locales/ur.js +1 -0
  320. package/v4/locales/uz.cjs +1 -0
  321. package/v4/locales/uz.js +1 -0
  322. package/v4/locales/vi.cjs +1 -0
  323. package/v4/locales/vi.js +1 -0
  324. package/v4/locales/yo.cjs +1 -0
  325. package/v4/locales/yo.js +1 -0
  326. package/v4/locales/zh-CN.cjs +1 -0
  327. package/v4/locales/zh-CN.js +1 -0
  328. package/v4/locales/zh-TW.cjs +1 -0
  329. package/v4/locales/zh-TW.js +1 -0
  330. package/v4/mini/checks.cjs +20 -2
  331. package/v4/mini/checks.d.cts +1 -1
  332. package/v4/mini/checks.d.ts +1 -1
  333. package/v4/mini/checks.js +1 -1
  334. package/v4/mini/coerce.cjs +19 -0
  335. package/v4/mini/deep-partial.cjs +19 -0
  336. package/v4/mini/external.cjs +22 -1
  337. package/v4/mini/external.d.cts +1 -1
  338. package/v4/mini/external.d.ts +1 -1
  339. package/v4/mini/external.js +1 -1
  340. package/v4/mini/in-out.cjs +19 -0
  341. package/v4/mini/index.cjs +19 -0
  342. package/v4/mini/iso.cjs +19 -0
  343. package/v4/mini/parse.cjs +19 -0
  344. package/v4/mini/schemas.cjs +42 -4
  345. package/v4/mini/schemas.d.cts +8 -0
  346. package/v4/mini/schemas.d.ts +8 -0
  347. package/v4/mini/schemas.js +19 -2
  348. package/v4-mini/index.cjs +19 -0
@@ -1,6 +1,6 @@
1
1
  import type * as errors from "./errors.js";
2
- import type { $ZodMemoizer, $ZodType, ParseContextInternal, ParsePayload } from "./schemas.js";
3
- import type * as util from "./util.js";
2
+ import type { $ZodMemoizer, $ZodType, $ZodTypeDef, ParseContextInternal, ParsePayload } from "./schemas.js";
3
+ import * as util from "./util.js";
4
4
 
5
5
  export class $ZodCyclicError extends Error {
6
6
  constructor() {
@@ -16,9 +16,10 @@ interface Entry {
16
16
  }
17
17
 
18
18
  interface State {
19
- buckets: Map<$ZodType, Map<object, Entry>>;
19
+ /** Weak throughout because `attach` caches a bucket on the schema instance, which outlives the parse. */
20
+ buckets: WeakMap<$ZodType, WeakMap<object, Entry>>;
20
21
  /** Nodes a back-edge resolved to before they finished. */
21
- backEdges: Set<object> | undefined;
22
+ backEdges: WeakSet<object> | undefined;
22
23
  }
23
24
 
24
25
  /** Keyed off the context object every schema in one parse call already shares. */
@@ -27,6 +28,11 @@ type WithState = { [STATE]?: State };
27
28
 
28
29
  const NO_ISSUES: errors.$ZodRawIssue[] = [];
29
30
 
31
+ // a value a cycle can close through; callables count, since z.properties asserts on one
32
+ function isRef(value: unknown): value is object {
33
+ return value !== null && (typeof value === "object" || typeof value === "function");
34
+ }
35
+
30
36
  // Receivers prefix paths in place, so the cache and every hand-out need their own copies.
31
37
  function cloneIssues(issues: errors.$ZodRawIssue[]): errors.$ZodRawIssue[] {
32
38
  return issues.map((iss) => (iss.path ? { ...iss, path: iss.path.slice() } : { ...iss }));
@@ -34,38 +40,153 @@ function cloneIssues(issues: errors.$ZodRawIssue[]): errors.$ZodRawIssue[] {
34
40
 
35
41
  const recursive: WeakMap<object, boolean> = /*@__PURE__*/ new WeakMap();
36
42
 
43
+ /** What the walk established, in order of certainty: ordered so the strongest answer among children wins. */
44
+ const NONE = 0;
45
+ const ASSUMED = 1;
46
+ const PROVEN = 2;
47
+ type Answer = typeof NONE | typeof ASSUMED | typeof PROVEN;
48
+
37
49
  /** Whether this schema's subtree contains a cycle, so one parse can re-enter it. */
38
- function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
50
+ function isRecursive(inst: $ZodType, stack: Set<object>, resolve: boolean): Answer {
39
51
  const cached = recursive.get(inst);
40
- if (cached !== undefined) return cached;
52
+ if (cached !== undefined) return cached ? PROVEN : NONE;
41
53
  // Relative to the walk in progress, so not cached.
42
- if (stack.has(inst)) return true;
54
+ if (stack.has(inst)) return PROVEN;
43
55
  stack.add(inst);
44
56
 
45
- let result = false;
57
+ let result: Answer = NONE;
46
58
  const check = (child: any) => {
47
- if (!result && child?._zod && isRecursive(child, stack)) result = true;
59
+ if (result !== PROVEN && child?._zod) {
60
+ const answer = isRecursive(child, stack, resolve);
61
+ if (answer > result) result = answer;
62
+ }
63
+ };
64
+
65
+ // `Reflect.ownKeys` rather than `Object.keys`, so a cycle through a declared symbol key is still seen
66
+ const shape = (sh: object, spread: boolean): Answer => {
67
+ let answer: Answer = NONE;
68
+ for (const key of Reflect.ownKeys(sh)) {
69
+ const desc = Object.getOwnPropertyDescriptor(sh, key)!;
70
+ // an object resolves its shape by spread, so a key it does not enumerate is never parsed; `z.properties` reads every own key and so keeps them all
71
+ if (spread && !desc.enumerable) continue;
72
+ // resolving runs user code, and a factory mints a fresh subtree per read, so an edge the walk can't follow counts as a cycle
73
+ const child = desc.get ? ASSUMED : desc.value?._zod ? isRecursive(desc.value, stack, resolve) : NONE;
74
+ if (child > answer) answer = child;
75
+ }
76
+ return answer;
77
+ };
78
+
79
+ const merge = (answer: Answer) => {
80
+ if (answer > result) result = answer;
48
81
  };
49
82
 
50
83
  const def = inst._zod.def as any;
51
- if (def.type === "lazy") {
52
- check((inst as any)._zod.innerType);
53
- } else {
54
- // $ZodObject redefines `shape` as a non-enumerable accessor, so `for...in` misses it.
55
- const shape = def.shape;
56
- // `for...in` skips symbols, so a cycle through a declared symbol key would read as non-recursive
57
- if (shape) for (const key of Reflect.ownKeys(shape)) check(shape[key]);
58
- for (const key in def) {
59
- const value = def[key];
60
- if (!value || typeof value !== "object") continue;
61
- if (value._zod) check(value);
62
- else if (Array.isArray(value)) for (const el of value) check(el);
84
+ const kind = def.type as $ZodTypeDef["type"];
85
+ switch (kind) {
86
+ case "object": {
87
+ const raw = util.rawShape(def);
88
+ // a def with no raw shape answers `shape` from an accessor of its own, and running that can mint a whole fresh subtree
89
+ merge(raw ? shape(raw, true) : ASSUMED);
90
+ check(def.catchall);
91
+ break;
92
+ }
93
+ case "properties":
94
+ merge(shape(def.shape, false));
95
+ break;
96
+ case "array":
97
+ check(def.element);
98
+ break;
99
+ case "tuple":
100
+ for (const el of def.items) check(el);
101
+ check(def.rest);
102
+ break;
103
+ case "record":
104
+ case "map":
105
+ check(def.keyType);
106
+ check(def.valueType);
107
+ break;
108
+ case "set":
109
+ check(def.valueType);
110
+ break;
111
+ case "union":
112
+ for (const el of def.options) check(el);
113
+ break;
114
+ case "intersection":
115
+ check(def.left);
116
+ check(def.right);
117
+ break;
118
+ case "optional":
119
+ case "nullable":
120
+ case "default":
121
+ case "prefault":
122
+ case "catch":
123
+ case "readonly":
124
+ case "nonoptional":
125
+ case "promise":
126
+ case "success":
127
+ check(def.innerType);
128
+ break;
129
+ case "pipe":
130
+ check(def.in);
131
+ check(def.out);
132
+ break;
133
+ case "function":
134
+ check(def.input);
135
+ check(def.output);
136
+ break;
137
+ // `$ZodLazy` caches its inner on the def, so a resolved edge is followed exactly
138
+ case "lazy": {
139
+ const inner = def._cachedInner ?? (resolve ? (inst as any)._zod.innerType : undefined);
140
+ // walked with resolution off: one hop sees past the deferral, and a lazy that yields only another unresolved lazy is generative, so it stops there
141
+ merge(inner ? isRecursive(inner, stack, false) : ASSUMED);
142
+ break;
143
+ }
144
+ // a leaf by choice: `parts` are regex fragments, not data positions
145
+ case "template_literal":
146
+ // leaves
147
+ case "string":
148
+ case "number":
149
+ case "int":
150
+ case "boolean":
151
+ case "bigint":
152
+ case "symbol":
153
+ case "undefined":
154
+ case "null":
155
+ case "void":
156
+ case "never":
157
+ case "any":
158
+ case "unknown":
159
+ case "date":
160
+ case "nan":
161
+ case "enum":
162
+ case "literal":
163
+ case "file":
164
+ case "transform":
165
+ case "custom":
166
+ break;
167
+ default: {
168
+ // a new built-in kind becomes a compile error here
169
+ kind satisfies never;
170
+ // a user-defined kind can still hold children, and only its author knows where, so fall back to scanning the def — skipping accessors, since reading one can run user code
171
+ for (const key in def) {
172
+ const desc = Object.getOwnPropertyDescriptor(def, key);
173
+ if (!desc || desc.get) continue;
174
+ const value = desc.value;
175
+ if (!value || typeof value !== "object") continue;
176
+ if (value._zod) check(value);
177
+ else if (Array.isArray(value)) for (const el of value) check(el);
178
+ }
63
179
  }
64
180
  }
65
181
 
66
182
  stack.delete(inst);
67
- recursive.set(inst, result);
68
- return result;
183
+ return settle(inst, result);
184
+ }
185
+
186
+ /** An assumed answer must not outlive the resolution that settles it, so only a certain one is cached. */
187
+ function settle(inst: $ZodType, answer: Answer): Answer {
188
+ if (answer !== ASSUMED) recursive.set(inst, answer === PROVEN);
189
+ return answer;
69
190
  }
70
191
 
71
192
  /**
@@ -75,20 +196,21 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
75
196
  * generated fast path has no context to key on.
76
197
  */
77
198
  export function isRecursiveSchema(inst: $ZodType): boolean {
78
- return isRecursive(inst, new Set());
199
+ // z.compile never parses, so nothing would ever resolve a lazy for it; it runs once and already treats a throw here as recursive
200
+ return isRecursive(inst, new Set(), true) !== NONE;
79
201
  }
80
202
 
81
- function bucketFor(state: State, inst: $ZodType): Map<object, Entry> {
203
+ function bucketFor(state: State, inst: $ZodType): WeakMap<object, Entry> {
82
204
  let bucket = state.buckets.get(inst);
83
205
  if (!bucket) {
84
- bucket = new Map();
206
+ bucket = new WeakMap();
85
207
  state.buckets.set(inst, bucket);
86
208
  }
87
209
  return bucket;
88
210
  }
89
211
 
90
212
  // Set immediately before delegating to core and cleared immediately after, so `alloc` registers only for a visit this module is driving.
91
- let handoff: Map<object, Entry> | undefined;
213
+ let handoff: WeakMap<object, Entry> | undefined;
92
214
 
93
215
  // Allocated but unfinished entries. `alloc` and the matching pop both happen in the synchronous part of a parse, so they nest even when children are async, and one stack serves every schema.
94
216
  const open: Entry[] = [];
@@ -120,9 +242,10 @@ const memo: $ZodMemoizer = {
120
242
 
121
243
  attach(inst) {
122
244
  let isRecursiveInst: boolean | undefined;
123
- // `bucket` memoized for one parse; a recursive schema is re-entered many times and its bucket never changes
245
+ let rechecked = false;
246
+ // a recursive schema is re-entered many times per parse and its bucket never changes
124
247
  let lastCtx: object | undefined;
125
- let lastBucket: Map<object, Entry> | undefined;
248
+ let lastBucket: WeakMap<object, Entry> | undefined;
126
249
 
127
250
  // Wraps `parse` in a deferred so it sees the container's final parse. Core's own deferred copies `parse` into `run` when there are no checks, and it ran first, so `run` is patched to match; with checks, `run` reads `parse` dynamically.
128
251
  inst._zod.deferred ??= [];
@@ -131,25 +254,28 @@ const memo: $ZodMemoizer = {
131
254
 
132
255
  const wrapped = (payload: ParsePayload, ctx: ParseContextInternal): util.MaybeAsync<ParsePayload> => {
133
256
  if (isRecursiveInst === undefined) {
134
- isRecursiveInst = isRecursive(inst, new Set());
135
- if (!isRecursiveInst) {
257
+ const walked = isRecursive(inst, new Set(), false);
258
+ if (walked === NONE) {
136
259
  // Nothing here can ever fire, so take it back out.
137
260
  inst._zod.parse = base;
138
261
  if (inst._zod.run === wrapped) inst._zod.run = base;
139
262
  return base(payload, ctx);
140
263
  }
264
+ // this parse resolves the deferred edges on its own path, so ask once more before latching
265
+ if (walked === PROVEN || rechecked) isRecursiveInst = true;
266
+ else rechecked = true;
141
267
  }
142
268
 
143
269
  const input = payload.value;
144
- if (input === null || typeof input !== "object") return base(payload, ctx);
270
+ if (!isRef(input)) return base(payload, ctx);
145
271
 
146
272
  let state = (ctx as WithState)[STATE];
147
273
  if (!state) {
148
- state = { buckets: new Map(), backEdges: undefined };
274
+ state = { buckets: new WeakMap(), backEdges: undefined };
149
275
  (ctx as WithState)[STATE] = state;
150
276
  }
151
277
 
152
- let bucket: Map<object, Entry>;
278
+ let bucket: WeakMap<object, Entry>;
153
279
  if (lastCtx === ctx) {
154
280
  bucket = lastBucket!;
155
281
  } else {
@@ -166,7 +292,7 @@ const memo: $ZodMemoizer = {
166
292
  } else {
167
293
  // Still being parsed: its own checks cover it, so skip them here.
168
294
  payload.memo = true;
169
- state.backEdges ??= new Set();
295
+ state.backEdges ??= new WeakSet();
170
296
  state.backEdges.add(hit.value as object);
171
297
  }
172
298
  return payload;
@@ -204,5 +330,5 @@ export function memoizer(): $ZodMemoizer {
204
330
  /** Whether this value is a node a back-edge resolved to before it finished. */
205
331
  export function isBackEdge(ctx: object, value: unknown): boolean {
206
332
  const backEdges = (ctx as WithState)[STATE]?.backEdges;
207
- return backEdges !== undefined && value !== null && typeof value === "object" && backEdges.has(value);
333
+ return backEdges !== undefined && isRef(value) && backEdges.has(value);
208
334
  }
@@ -75,15 +75,33 @@ export const _safeParse: (_Err: $ZodErrorClass) => $SafeParse = (_Err) => (schem
75
75
  throw new core.$ZodAsyncError();
76
76
  }
77
77
 
78
- return result.issues.length
79
- ? {
80
- success: false,
81
- error: new (_Err ?? errors.$ZodError)(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config()))),
82
- }
83
- : ({ success: true, data: result.value } as any);
78
+ return result.issues.length ? failure(_Err, result.issues, ctx) : ({ success: true, data: result.value } as any);
84
79
  };
85
80
  export const safeParse: $SafeParse = /* @__PURE__*/ _safeParse(errors.$ZodRealError);
86
81
 
82
+ // the error is built on the first read of `error`: finalizing the issues and constructing the instance is most of a failing parse, and a caller that only branches on `success` never pays it. a getter in the literal keeps this small; the alternative, one shared accessor descriptor plus a hidden state slot, reads ~15% faster but costs ~75 B gzipped in every bundle
83
+ function failure(Err: $ZodErrorClass, issues: errors.$ZodRawIssue[], ctx: schemas.ParseContextInternal): any {
84
+ let error: errors.$ZodError | undefined;
85
+ return {
86
+ success: false,
87
+ get error() {
88
+ if (!error) {
89
+ error = new Err(issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())));
90
+ // finalizeIssue drops `input`, so the built error holds nothing; keeping the raw issues past this point pins the parsed value for the life of the result
91
+ issues = undefined as any;
92
+ ctx = undefined as any;
93
+ }
94
+ return error;
95
+ },
96
+ set error(e: errors.$ZodError) {
97
+ error = e;
98
+ // a replacement makes the getter's branch unreachable, so the captures have to go here too
99
+ issues = undefined as any;
100
+ ctx = undefined as any;
101
+ },
102
+ };
103
+ }
104
+
87
105
  export type $SafeParseAsync = <T extends schemas.$ZodType>(
88
106
  schema: T,
89
107
  value: unknown,
@@ -95,12 +113,7 @@ export const _safeParseAsync: (_Err: $ZodErrorClass) => $SafeParseAsync = (_Err)
95
113
  let result = schema._zod.run({ value, issues: [] }, ctx);
96
114
  if (result instanceof Promise) result = await result;
97
115
 
98
- return result.issues.length
99
- ? {
100
- success: false,
101
- error: new _Err(result.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config()))),
102
- }
103
- : ({ success: true, data: result.value } as any);
116
+ return result.issues.length ? failure(_Err, result.issues, ctx) : ({ success: true, data: result.value } as any);
104
117
  };
105
118
 
106
119
  export const safeParseAsync: $SafeParseAsync = /* @__PURE__*/ _safeParseAsync(errors.$ZodRealError);
@@ -110,7 +123,7 @@ const COMPILE_INVALID = /* @__PURE__ */ Symbol.for("zod.compile.invalid");
110
123
  const COMPILE_FALLBACK = /* @__PURE__ */ Symbol.for("zod.compile.fallback");
111
124
 
112
125
  interface CompiledBag {
113
- validator?: (input: unknown) => unknown;
126
+ validator?: ((input: unknown) => unknown) & { definite?: boolean | undefined };
114
127
  fallbackRun?: (payload: schemas.ParsePayload, ctx: schemas.ParseContextInternal) => unknown;
115
128
  }
116
129
 
@@ -127,7 +140,11 @@ export const validate: $Validate = ((
127
140
  _ctx?: schemas.ParseContext<errors.$ZodIssue>
128
141
  ): boolean => {
129
142
  const validator = (schema._zod.bag as CompiledBag).validator;
130
- if (validator !== undefined && validator(value) !== COMPILE_INVALID) return true;
143
+ if (validator !== undefined) {
144
+ if (validator(value) !== COMPILE_INVALID) return true;
145
+ // a definite sentinel means the runtime would reject, so skip the re-parse; a ctx can still change the answer
146
+ if (validator.definite === true && _ctx === undefined) return false;
147
+ }
131
148
  return validateFallback(schema, value, _ctx);
132
149
  }) as $Validate;
133
150
 
@@ -136,7 +153,9 @@ function validateFallback(
136
153
  value: unknown,
137
154
  _ctx?: schemas.ParseContext<errors.$ZodIssue>
138
155
  ): boolean {
139
- const ctx: schemas.ParseContextInternal = _ctx ? { ..._ctx, async: false } : { async: false };
156
+ const ctx: schemas.ParseContextInternal = _ctx
157
+ ? { ..._ctx, async: false, abortEarly: true }
158
+ : { async: false, abortEarly: true };
140
159
  const fallbackRun = (schema._zod.bag as CompiledBag).fallbackRun;
141
160
  let result: unknown;
142
161
  if (fallbackRun) {
@@ -160,7 +179,9 @@ export type $ValidateAsync = <T extends schemas.$ZodType>(
160
179
 
161
180
  // no fast path: the compiler keeps async parses on the runtime, because a promise-returning callback that is not declared async compiles to a throw
162
181
  export const validateAsync: $ValidateAsync = async (schema, value, _ctx) => {
163
- const ctx: schemas.ParseContextInternal = _ctx ? { ..._ctx, async: true } : { async: true };
182
+ const ctx: schemas.ParseContextInternal = _ctx
183
+ ? { ..._ctx, async: true, abortEarly: true }
184
+ : { async: true, abortEarly: true };
164
185
  let result: unknown = schema._zod.run({ value, issues: [] }, ctx);
165
186
  if (result instanceof Promise) result = await result;
166
187
  return (result as schemas.ParsePayload).issues.length === 0;
@@ -42,7 +42,7 @@ export const uuid7: RegExp = /*@__PURE__*/ uuid(7);
42
42
 
43
43
  /** Practical email validation */
44
44
  export const email: RegExp =
45
- /^(?!\.)(?!.*\.\.)([A-Za-z0-9_'+\-\.]*)[A-Za-z0-9_+-]@([A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
45
+ /^(?:[A-Za-z0-9_'+\-]+\.)*[A-Za-z0-9_'+\-]*[A-Za-z0-9_+-]@(?:[A-Za-z0-9][A-Za-z0-9\-]*\.)+[A-Za-z]{2,}$/;
46
46
 
47
47
  /** Equivalent to the HTML5 input[type=email] validation implemented by browsers. Source: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/email */
48
48
  export const html5Email: RegExp =
@@ -60,8 +60,8 @@ export const browserEmail: RegExp =
60
60
  /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
61
61
  // from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
62
62
 
63
- // Single character class, not an alternation: the two properties overlap (U+1F9B0-U+1F9B3), so `(A|B)+` backtracks exponentially on a failed match.
64
- const _emoji: string = `^[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
63
+ // Single character class, not an alternation: the two properties overlap (U+1F9B0-U+1F9B3), so `(A|B)+` backtracks exponentially on a failed match. The leading lookahead then demands one anchor — a pictograph, a regional indicator, or the enclosing keycap — because `\p{Emoji_Component}` on its own covers ASCII digits, `#`, `*`, ZWJ, variation selectors and skin tone modifiers, none of which is an emoji without a base.
64
+ const _emoji: string = `^(?=[\\s\\S]*[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3])[\\p{Extended_Pictographic}\\p{Emoji_Component}]+$`;
65
65
  export function emoji(): RegExp {
66
66
  return new RegExp(_emoji, "u");
67
67
  }
@@ -81,7 +81,7 @@ export const cidrv6: RegExp =
81
81
 
82
82
  // https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript
83
83
  export const base64: RegExp = /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/;
84
- export const base64url: RegExp = /^[A-Za-z0-9_-]*$/;
84
+ export const base64url: RegExp = /^(?:[A-Za-z0-9_-]{4})*(?:[A-Za-z0-9_-]{2,3})?$/;
85
85
 
86
86
  // based on https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
87
87
  // export const hostname: RegExp = /^([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+$/;
@@ -98,6 +98,9 @@ export const e164: RegExp = /^\+[1-9]\d{6,14}$/;
98
98
  // Credit card shape: 12–19 digits, optionally separated by single spaces or single hyphens. ISO/IEC 7812 caps the PAN at 19 digits; 12 is the shortest issued length (Maestro).
99
99
  export const creditCard: RegExp = /^\d(?:[ -]?\d){11,18}$/;
100
100
 
101
+ // iban electronic format: 2-letter country, check digits 02-98 (the only values `98 - remainder` can produce), 11-30 bban characters
102
+ export const iban: RegExp = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
103
+
101
104
  const dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
102
105
 
103
106
  /** Anchors a pattern source. The interpolation lives here rather than at the call site because
@@ -147,6 +150,9 @@ export function datetime(args: {
147
150
  return new RegExp(`^${dateSource}T(?:${timeRegex})$`);
148
151
  }
149
152
 
153
+ // the unbounded form of `string()` as a literal, so every plain string shares one instance instead of building its own
154
+ export const anyString: RegExp = /^[\s\S]{0,}$/;
155
+
150
156
  export const string = (params?: { minimum?: number | undefined; maximum?: number | undefined }): RegExp => {
151
157
  const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
152
158
  return new RegExp(`^${regex}$`);