zod 4.5.4 → 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 (347) 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 +274 -0
  15. package/src/v4/classic/tests/enum.test.ts +9 -0
  16. package/src/v4/classic/tests/error.test.ts +130 -1
  17. package/src/v4/classic/tests/firstparty.test.ts +4 -0
  18. package/src/v4/classic/tests/from-json-schema.test.ts +157 -0
  19. package/src/v4/classic/tests/iban.test.ts +81 -0
  20. package/src/v4/classic/tests/instance-footprint.test.ts +18 -2
  21. package/src/v4/classic/tests/instanceof.test.ts +27 -7
  22. package/src/v4/classic/tests/issue-schema.test.ts +88 -56
  23. package/src/v4/classic/tests/object.test.ts +56 -0
  24. package/src/v4/classic/tests/optin-ladder.test.ts +3 -2
  25. package/src/v4/classic/tests/properties.test.ts +225 -0
  26. package/src/v4/classic/tests/recursive-types.test.ts +1 -1
  27. package/src/v4/classic/tests/standard-schema.test.ts +19 -0
  28. package/src/v4/classic/tests/string.test.ts +25 -0
  29. package/src/v4/classic/tests/template-literal.test.ts +33 -1
  30. package/src/v4/classic/tests/to-json-schema.test.ts +147 -3
  31. package/src/v4/classic/tests/validate.test.ts +569 -0
  32. package/src/v4/core/api.ts +30 -6
  33. package/src/v4/core/checks.ts +1 -101
  34. package/src/v4/core/compile.ts +193 -81
  35. package/src/v4/core/doc.ts +6 -2
  36. package/src/v4/core/errors.ts +3 -6
  37. package/src/v4/core/json-schema-generator.ts +2 -2
  38. package/src/v4/core/json-schema-processors.ts +226 -68
  39. package/src/v4/core/memoizer.ts +78 -28
  40. package/src/v4/core/parse.ts +37 -16
  41. package/src/v4/core/regexes.ts +10 -4
  42. package/src/v4/core/schemas.ts +314 -83
  43. package/src/v4/core/tests/cached.test.ts +54 -0
  44. package/src/v4/core/tests/compile-generative.test.ts +445 -0
  45. package/src/v4/core/tests/compile.test.ts +222 -2
  46. package/src/v4/core/tests/email-regex.test.ts +140 -0
  47. package/src/v4/core/tests/polyfill-collision.test.ts +46 -0
  48. package/src/v4/core/to-json-schema.ts +10 -8
  49. package/src/v4/core/util.ts +222 -147
  50. package/src/v4/core/versions.ts +2 -2
  51. package/src/v4/core/visit.ts +11 -0
  52. package/src/v4/locales/ar.ts +1 -0
  53. package/src/v4/locales/az.ts +1 -0
  54. package/src/v4/locales/be.ts +1 -0
  55. package/src/v4/locales/bg.ts +1 -0
  56. package/src/v4/locales/bn.ts +1 -0
  57. package/src/v4/locales/ca.ts +1 -0
  58. package/src/v4/locales/ckb.ts +1 -0
  59. package/src/v4/locales/cs.ts +1 -0
  60. package/src/v4/locales/da.ts +1 -0
  61. package/src/v4/locales/de.ts +1 -0
  62. package/src/v4/locales/el.ts +1 -0
  63. package/src/v4/locales/en.ts +1 -0
  64. package/src/v4/locales/eo.ts +1 -0
  65. package/src/v4/locales/es.ts +1 -0
  66. package/src/v4/locales/fa.ts +1 -0
  67. package/src/v4/locales/fi.ts +1 -0
  68. package/src/v4/locales/fr-CA.ts +1 -0
  69. package/src/v4/locales/fr.ts +1 -0
  70. package/src/v4/locales/gu.ts +1 -0
  71. package/src/v4/locales/he.ts +1 -0
  72. package/src/v4/locales/hi.ts +1 -0
  73. package/src/v4/locales/hr.ts +1 -0
  74. package/src/v4/locales/hu.ts +1 -0
  75. package/src/v4/locales/hy.ts +1 -0
  76. package/src/v4/locales/id.ts +1 -0
  77. package/src/v4/locales/is.ts +1 -0
  78. package/src/v4/locales/it.ts +1 -0
  79. package/src/v4/locales/ja.ts +1 -0
  80. package/src/v4/locales/ka.ts +1 -0
  81. package/src/v4/locales/km.ts +1 -0
  82. package/src/v4/locales/kn.ts +1 -0
  83. package/src/v4/locales/ko.ts +1 -0
  84. package/src/v4/locales/lt.ts +1 -0
  85. package/src/v4/locales/mk.ts +1 -0
  86. package/src/v4/locales/ms.ts +1 -0
  87. package/src/v4/locales/ne.ts +1 -0
  88. package/src/v4/locales/nl.ts +1 -0
  89. package/src/v4/locales/nn.ts +1 -0
  90. package/src/v4/locales/no.ts +1 -0
  91. package/src/v4/locales/ota.ts +1 -0
  92. package/src/v4/locales/pl.ts +1 -0
  93. package/src/v4/locales/ps.ts +1 -0
  94. package/src/v4/locales/pt-BR.ts +1 -0
  95. package/src/v4/locales/pt.ts +1 -0
  96. package/src/v4/locales/ro.ts +1 -0
  97. package/src/v4/locales/ru.ts +1 -0
  98. package/src/v4/locales/sk.ts +1 -0
  99. package/src/v4/locales/sl.ts +1 -0
  100. package/src/v4/locales/sv.ts +1 -0
  101. package/src/v4/locales/ta.ts +1 -0
  102. package/src/v4/locales/th.ts +1 -0
  103. package/src/v4/locales/tk.ts +1 -0
  104. package/src/v4/locales/tr.ts +1 -0
  105. package/src/v4/locales/uk.ts +1 -0
  106. package/src/v4/locales/ur.ts +1 -0
  107. package/src/v4/locales/uz.ts +1 -0
  108. package/src/v4/locales/vi.ts +1 -0
  109. package/src/v4/locales/yo.ts +1 -0
  110. package/src/v4/locales/zh-CN.ts +1 -0
  111. package/src/v4/locales/zh-TW.ts +1 -0
  112. package/src/v4/mini/checks.ts +0 -1
  113. package/src/v4/mini/external.ts +2 -0
  114. package/src/v4/mini/schemas.ts +38 -2
  115. package/src/v4/mini/tests/checks.test.ts +1 -1
  116. package/src/v4/mini/tests/computed.test.ts +11 -17
  117. package/src/v4/mini/tests/index.test.ts +8 -0
  118. package/src/v4/mini/tests/string.test.ts +16 -0
  119. package/v3/ZodError.cjs +19 -0
  120. package/v3/errors.cjs +19 -0
  121. package/v3/external.cjs +19 -0
  122. package/v3/helpers/enumUtil.cjs +19 -0
  123. package/v3/helpers/errorUtil.cjs +20 -1
  124. package/v3/helpers/errorUtil.js +1 -1
  125. package/v3/helpers/parseUtil.cjs +19 -0
  126. package/v3/helpers/partialUtil.cjs +19 -0
  127. package/v3/helpers/typeAliases.cjs +19 -0
  128. package/v3/helpers/util.cjs +19 -0
  129. package/v3/index.cjs +19 -0
  130. package/v3/standard-schema.cjs +19 -0
  131. package/v3/types.cjs +19 -0
  132. package/v4/classic/checks.cjs +20 -2
  133. package/v4/classic/checks.d.cts +1 -1
  134. package/v4/classic/checks.d.ts +1 -1
  135. package/v4/classic/checks.js +1 -1
  136. package/v4/classic/coerce.cjs +19 -0
  137. package/v4/classic/compat.cjs +19 -0
  138. package/v4/classic/deep-partial.cjs +19 -0
  139. package/v4/classic/errors.cjs +19 -0
  140. package/v4/classic/external.cjs +22 -1
  141. package/v4/classic/external.d.cts +1 -1
  142. package/v4/classic/external.d.ts +1 -1
  143. package/v4/classic/external.js +1 -1
  144. package/v4/classic/from-json-schema.cjs +264 -24
  145. package/v4/classic/from-json-schema.js +245 -24
  146. package/v4/classic/in-out.cjs +19 -0
  147. package/v4/classic/index.cjs +19 -0
  148. package/v4/classic/iso.cjs +19 -0
  149. package/v4/classic/parse.cjs +19 -0
  150. package/v4/classic/schemas.cjs +100 -31
  151. package/v4/classic/schemas.d.cts +17 -1
  152. package/v4/classic/schemas.d.ts +17 -1
  153. package/v4/classic/schemas.js +77 -29
  154. package/v4/core/api.cjs +37 -2
  155. package/v4/core/api.d.cts +6 -4
  156. package/v4/core/api.d.ts +6 -4
  157. package/v4/core/api.js +17 -2
  158. package/v4/core/checks.cjs +19 -96
  159. package/v4/core/checks.d.cts +1 -1
  160. package/v4/core/checks.d.ts +1 -1
  161. package/v4/core/checks.js +0 -96
  162. package/v4/core/compile.cjs +164 -65
  163. package/v4/core/compile.d.cts +18 -4
  164. package/v4/core/compile.d.ts +18 -4
  165. package/v4/core/compile.js +145 -66
  166. package/v4/core/core.cjs +19 -0
  167. package/v4/core/doc.cjs +26 -2
  168. package/v4/core/doc.js +7 -2
  169. package/v4/core/errors.cjs +21 -5
  170. package/v4/core/errors.js +2 -5
  171. package/v4/core/index.cjs +19 -0
  172. package/v4/core/json-schema-generator.cjs +20 -1
  173. package/v4/core/json-schema-generator.js +2 -2
  174. package/v4/core/json-schema-processors.cjs +215 -63
  175. package/v4/core/json-schema-processors.d.cts +17 -0
  176. package/v4/core/json-schema-processors.d.ts +17 -0
  177. package/v4/core/json-schema-processors.js +195 -64
  178. package/v4/core/json-schema.cjs +19 -0
  179. package/v4/core/memoizer.cjs +114 -23
  180. package/v4/core/memoizer.js +72 -23
  181. package/v4/core/parse.cjs +56 -16
  182. package/v4/core/parse.js +37 -16
  183. package/v4/core/regexes.cjs +29 -6
  184. package/v4/core/regexes.d.cts +2 -0
  185. package/v4/core/regexes.d.ts +2 -0
  186. package/v4/core/regexes.js +8 -4
  187. package/v4/core/registries.cjs +19 -0
  188. package/v4/core/schemas.cjs +285 -66
  189. package/v4/core/schemas.d.cts +34 -3
  190. package/v4/core/schemas.d.ts +34 -3
  191. package/v4/core/schemas.js +264 -65
  192. package/v4/core/standard-schema.cjs +19 -0
  193. package/v4/core/to-json-schema.cjs +29 -7
  194. package/v4/core/to-json-schema.d.cts +7 -3
  195. package/v4/core/to-json-schema.d.ts +7 -3
  196. package/v4/core/to-json-schema.js +8 -7
  197. package/v4/core/util.cjs +181 -143
  198. package/v4/core/util.d.cts +25 -4
  199. package/v4/core/util.d.ts +25 -4
  200. package/v4/core/util.js +160 -143
  201. package/v4/core/versions.cjs +21 -2
  202. package/v4/core/versions.d.cts +1 -1
  203. package/v4/core/versions.d.ts +1 -1
  204. package/v4/core/versions.js +2 -2
  205. package/v4/core/visit.cjs +31 -0
  206. package/v4/core/visit.js +12 -0
  207. package/v4/index.cjs +19 -0
  208. package/v4/locales/ar.cjs +1 -0
  209. package/v4/locales/ar.js +1 -0
  210. package/v4/locales/az.cjs +1 -0
  211. package/v4/locales/az.js +1 -0
  212. package/v4/locales/be.cjs +1 -0
  213. package/v4/locales/be.js +1 -0
  214. package/v4/locales/bg.cjs +1 -0
  215. package/v4/locales/bg.js +1 -0
  216. package/v4/locales/bn.cjs +1 -0
  217. package/v4/locales/bn.js +1 -0
  218. package/v4/locales/ca.cjs +1 -0
  219. package/v4/locales/ca.js +1 -0
  220. package/v4/locales/ckb.cjs +1 -0
  221. package/v4/locales/ckb.js +1 -0
  222. package/v4/locales/cs.cjs +1 -0
  223. package/v4/locales/cs.js +1 -0
  224. package/v4/locales/da.cjs +1 -0
  225. package/v4/locales/da.js +1 -0
  226. package/v4/locales/de.cjs +1 -0
  227. package/v4/locales/de.js +1 -0
  228. package/v4/locales/el.cjs +1 -0
  229. package/v4/locales/el.js +1 -0
  230. package/v4/locales/en.cjs +1 -0
  231. package/v4/locales/en.js +1 -0
  232. package/v4/locales/eo.cjs +1 -0
  233. package/v4/locales/eo.js +1 -0
  234. package/v4/locales/es.cjs +1 -0
  235. package/v4/locales/es.js +1 -0
  236. package/v4/locales/fa.cjs +1 -0
  237. package/v4/locales/fa.js +1 -0
  238. package/v4/locales/fi.cjs +1 -0
  239. package/v4/locales/fi.js +1 -0
  240. package/v4/locales/fr-CA.cjs +1 -0
  241. package/v4/locales/fr-CA.js +1 -0
  242. package/v4/locales/fr.cjs +1 -0
  243. package/v4/locales/fr.js +1 -0
  244. package/v4/locales/gu.cjs +1 -0
  245. package/v4/locales/gu.js +1 -0
  246. package/v4/locales/he.cjs +1 -0
  247. package/v4/locales/he.js +1 -0
  248. package/v4/locales/hi.cjs +1 -0
  249. package/v4/locales/hi.js +1 -0
  250. package/v4/locales/hr.cjs +1 -0
  251. package/v4/locales/hr.js +1 -0
  252. package/v4/locales/hu.cjs +1 -0
  253. package/v4/locales/hu.js +1 -0
  254. package/v4/locales/hy.cjs +1 -0
  255. package/v4/locales/hy.js +1 -0
  256. package/v4/locales/id.cjs +1 -0
  257. package/v4/locales/id.js +1 -0
  258. package/v4/locales/index.cjs +19 -0
  259. package/v4/locales/is.cjs +1 -0
  260. package/v4/locales/is.js +1 -0
  261. package/v4/locales/it.cjs +1 -0
  262. package/v4/locales/it.js +1 -0
  263. package/v4/locales/ja.cjs +1 -0
  264. package/v4/locales/ja.js +1 -0
  265. package/v4/locales/ka.cjs +1 -0
  266. package/v4/locales/ka.js +1 -0
  267. package/v4/locales/km.cjs +1 -0
  268. package/v4/locales/km.js +1 -0
  269. package/v4/locales/kn.cjs +1 -0
  270. package/v4/locales/kn.js +1 -0
  271. package/v4/locales/ko.cjs +1 -0
  272. package/v4/locales/ko.js +1 -0
  273. package/v4/locales/lt.cjs +1 -0
  274. package/v4/locales/lt.js +1 -0
  275. package/v4/locales/mk.cjs +1 -0
  276. package/v4/locales/mk.js +1 -0
  277. package/v4/locales/ms.cjs +1 -0
  278. package/v4/locales/ms.js +1 -0
  279. package/v4/locales/ne.cjs +1 -0
  280. package/v4/locales/ne.js +1 -0
  281. package/v4/locales/nl.cjs +1 -0
  282. package/v4/locales/nl.js +1 -0
  283. package/v4/locales/nn.cjs +1 -0
  284. package/v4/locales/nn.js +1 -0
  285. package/v4/locales/no.cjs +1 -0
  286. package/v4/locales/no.js +1 -0
  287. package/v4/locales/ota.cjs +1 -0
  288. package/v4/locales/ota.js +1 -0
  289. package/v4/locales/pl.cjs +1 -0
  290. package/v4/locales/pl.js +1 -0
  291. package/v4/locales/ps.cjs +1 -0
  292. package/v4/locales/ps.js +1 -0
  293. package/v4/locales/pt-BR.cjs +1 -0
  294. package/v4/locales/pt-BR.js +1 -0
  295. package/v4/locales/pt.cjs +1 -0
  296. package/v4/locales/pt.js +1 -0
  297. package/v4/locales/ro.cjs +1 -0
  298. package/v4/locales/ro.js +1 -0
  299. package/v4/locales/ru.cjs +1 -0
  300. package/v4/locales/ru.js +1 -0
  301. package/v4/locales/sk.cjs +1 -0
  302. package/v4/locales/sk.js +1 -0
  303. package/v4/locales/sl.cjs +1 -0
  304. package/v4/locales/sl.js +1 -0
  305. package/v4/locales/sv.cjs +1 -0
  306. package/v4/locales/sv.js +1 -0
  307. package/v4/locales/ta.cjs +1 -0
  308. package/v4/locales/ta.js +1 -0
  309. package/v4/locales/th.cjs +1 -0
  310. package/v4/locales/th.js +1 -0
  311. package/v4/locales/tk.cjs +1 -0
  312. package/v4/locales/tk.js +1 -0
  313. package/v4/locales/tr.cjs +1 -0
  314. package/v4/locales/tr.js +1 -0
  315. package/v4/locales/uk.cjs +1 -0
  316. package/v4/locales/uk.js +1 -0
  317. package/v4/locales/ur.cjs +1 -0
  318. package/v4/locales/ur.js +1 -0
  319. package/v4/locales/uz.cjs +1 -0
  320. package/v4/locales/uz.js +1 -0
  321. package/v4/locales/vi.cjs +1 -0
  322. package/v4/locales/vi.js +1 -0
  323. package/v4/locales/yo.cjs +1 -0
  324. package/v4/locales/yo.js +1 -0
  325. package/v4/locales/zh-CN.cjs +1 -0
  326. package/v4/locales/zh-CN.js +1 -0
  327. package/v4/locales/zh-TW.cjs +1 -0
  328. package/v4/locales/zh-TW.js +1 -0
  329. package/v4/mini/checks.cjs +20 -2
  330. package/v4/mini/checks.d.cts +1 -1
  331. package/v4/mini/checks.d.ts +1 -1
  332. package/v4/mini/checks.js +1 -1
  333. package/v4/mini/coerce.cjs +19 -0
  334. package/v4/mini/deep-partial.cjs +19 -0
  335. package/v4/mini/external.cjs +22 -1
  336. package/v4/mini/external.d.cts +1 -1
  337. package/v4/mini/external.d.ts +1 -1
  338. package/v4/mini/external.js +1 -1
  339. package/v4/mini/in-out.cjs +19 -0
  340. package/v4/mini/index.cjs +19 -0
  341. package/v4/mini/iso.cjs +19 -0
  342. package/v4/mini/parse.cjs +19 -0
  343. package/v4/mini/schemas.cjs +42 -4
  344. package/v4/mini/schemas.d.cts +8 -0
  345. package/v4/mini/schemas.d.ts +8 -0
  346. package/v4/mini/schemas.js +19 -2
  347. package/v4-mini/index.cjs +19 -0
@@ -1,6 +1,6 @@
1
1
  import type * as errors from "./errors.js";
2
2
  import type { $ZodMemoizer, $ZodType, $ZodTypeDef, ParseContextInternal, ParsePayload } from "./schemas.js";
3
- import type * as util from "./util.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,28 +40,59 @@ 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
84
  const kind = def.type as $ZodTypeDef["type"];
52
85
  switch (kind) {
53
86
  case "object": {
54
- // `Reflect.ownKeys` rather than `Object.keys`, so a cycle through a declared symbol key is still seen
55
- for (const key of Reflect.ownKeys(def.shape)) check(def.shape[key]);
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);
56
90
  check(def.catchall);
57
91
  break;
58
92
  }
93
+ case "properties":
94
+ merge(shape(def.shape, false));
95
+ break;
59
96
  case "array":
60
97
  check(def.element);
61
98
  break;
@@ -97,10 +134,13 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
97
134
  check(def.input);
98
135
  check(def.output);
99
136
  break;
100
- // reading `_zod.innerType` resolves the getter once and caches it
101
- case "lazy":
102
- check((inst as any)._zod.innerType);
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);
103
142
  break;
143
+ }
104
144
  // a leaf by choice: `parts` are regex fragments, not data positions
105
145
  case "template_literal":
106
146
  // leaves
@@ -140,8 +180,13 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
140
180
  }
141
181
 
142
182
  stack.delete(inst);
143
- recursive.set(inst, result);
144
- 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;
145
190
  }
146
191
 
147
192
  /**
@@ -151,20 +196,21 @@ function isRecursive(inst: $ZodType, stack: Set<object>): boolean {
151
196
  * generated fast path has no context to key on.
152
197
  */
153
198
  export function isRecursiveSchema(inst: $ZodType): boolean {
154
- 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;
155
201
  }
156
202
 
157
- function bucketFor(state: State, inst: $ZodType): Map<object, Entry> {
203
+ function bucketFor(state: State, inst: $ZodType): WeakMap<object, Entry> {
158
204
  let bucket = state.buckets.get(inst);
159
205
  if (!bucket) {
160
- bucket = new Map();
206
+ bucket = new WeakMap();
161
207
  state.buckets.set(inst, bucket);
162
208
  }
163
209
  return bucket;
164
210
  }
165
211
 
166
212
  // Set immediately before delegating to core and cleared immediately after, so `alloc` registers only for a visit this module is driving.
167
- let handoff: Map<object, Entry> | undefined;
213
+ let handoff: WeakMap<object, Entry> | undefined;
168
214
 
169
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.
170
216
  const open: Entry[] = [];
@@ -196,9 +242,10 @@ const memo: $ZodMemoizer = {
196
242
 
197
243
  attach(inst) {
198
244
  let isRecursiveInst: boolean | undefined;
199
- // `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
200
247
  let lastCtx: object | undefined;
201
- let lastBucket: Map<object, Entry> | undefined;
248
+ let lastBucket: WeakMap<object, Entry> | undefined;
202
249
 
203
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.
204
251
  inst._zod.deferred ??= [];
@@ -207,25 +254,28 @@ const memo: $ZodMemoizer = {
207
254
 
208
255
  const wrapped = (payload: ParsePayload, ctx: ParseContextInternal): util.MaybeAsync<ParsePayload> => {
209
256
  if (isRecursiveInst === undefined) {
210
- isRecursiveInst = isRecursive(inst, new Set());
211
- if (!isRecursiveInst) {
257
+ const walked = isRecursive(inst, new Set(), false);
258
+ if (walked === NONE) {
212
259
  // Nothing here can ever fire, so take it back out.
213
260
  inst._zod.parse = base;
214
261
  if (inst._zod.run === wrapped) inst._zod.run = base;
215
262
  return base(payload, ctx);
216
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;
217
267
  }
218
268
 
219
269
  const input = payload.value;
220
- if (input === null || typeof input !== "object") return base(payload, ctx);
270
+ if (!isRef(input)) return base(payload, ctx);
221
271
 
222
272
  let state = (ctx as WithState)[STATE];
223
273
  if (!state) {
224
- state = { buckets: new Map(), backEdges: undefined };
274
+ state = { buckets: new WeakMap(), backEdges: undefined };
225
275
  (ctx as WithState)[STATE] = state;
226
276
  }
227
277
 
228
- let bucket: Map<object, Entry>;
278
+ let bucket: WeakMap<object, Entry>;
229
279
  if (lastCtx === ctx) {
230
280
  bucket = lastBucket!;
231
281
  } else {
@@ -242,7 +292,7 @@ const memo: $ZodMemoizer = {
242
292
  } else {
243
293
  // Still being parsed: its own checks cover it, so skip them here.
244
294
  payload.memo = true;
245
- state.backEdges ??= new Set();
295
+ state.backEdges ??= new WeakSet();
246
296
  state.backEdges.add(hit.value as object);
247
297
  }
248
298
  return payload;
@@ -280,5 +330,5 @@ export function memoizer(): $ZodMemoizer {
280
330
  /** Whether this value is a node a back-edge resolved to before it finished. */
281
331
  export function isBackEdge(ctx: object, value: unknown): boolean {
282
332
  const backEdges = (ctx as WithState)[STATE]?.backEdges;
283
- return backEdges !== undefined && value !== null && typeof value === "object" && backEdges.has(value);
333
+ return backEdges !== undefined && isRef(value) && backEdges.has(value);
284
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}$`);