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
@@ -9,6 +9,7 @@ import {
9
9
  isValidBase64URL,
10
10
  isValidCIDRv6,
11
11
  isValidCreditCard,
12
+ isValidIBAN,
12
13
  isValidIPv6,
13
14
  isValidJWT,
14
15
  mergeValues,
@@ -17,11 +18,12 @@ import {
17
18
  urlHostnameOk,
18
19
  urlProtocolOk,
19
20
  } from "./schemas.js";
20
- import type { ParseContextInternal, ParsePayload, SomeType } from "./schemas.js";
21
+ import type { $ZodProperties, $ZodPropertiesDef, ParseContextInternal, ParsePayload, SomeType } from "./schemas.js";
21
22
  import * as util from "./util.js";
22
23
 
23
- /** Sentinel value returned by the compiled fast path when validation fails. Internal. */
24
+ /** @internal Sentinel the compiled fast path returns when validation fails. */
24
25
  export const INVALID: unique symbol = Symbol.for("zod.compile.invalid");
26
+ /** @internal */
25
27
  export type INVALID = typeof INVALID;
26
28
 
27
29
  // Set on the parse ctx when a compiled wrapper falls back to the runtime, so nested compiled wrappers skip their fast paths for the rest of that parse.
@@ -33,7 +35,11 @@ interface CompileFnOptions {
33
35
  assertOnly?: boolean | undefined;
34
36
  }
35
37
 
36
- type CompiledFn<T> = ((input: unknown) => T | INVALID) & { code?: string | undefined };
38
+ type CompiledFn<T> = ((input: unknown) => T | INVALID) & {
39
+ code?: string | undefined;
40
+ /** False when this function's INVALID does not strictly mean "the runtime would reject". */
41
+ definite?: boolean | undefined;
42
+ };
37
43
 
38
44
  /** Raised when the schema contains async refinements or transforms. Surfaces only under `compile(schema, { strict: true })`. */
39
45
  export class ZodCompileAsyncError extends Error {
@@ -64,6 +70,8 @@ interface CompileContext {
64
70
  constants: Map<string, unknown>;
65
71
  constantCounter: number;
66
72
  varCounter: number;
73
+ /** Cleared when a construct can return INVALID for something the interpreter throws on, so `validate` keeps its fallback. */
74
+ definite: boolean;
67
75
  }
68
76
 
69
77
  // Union of all check types we support in AOT compilation
@@ -81,6 +89,7 @@ type SupportedCheck =
81
89
  | checks.$ZodCheckLengthEquals
82
90
  | checks.$ZodCheckStringFormat
83
91
  | checks.$ZodCheckProperty
92
+ | $ZodProperties
84
93
  | checks.$ZodCheckMimeType
85
94
  | checks.$ZodCheckOverwrite
86
95
  | { _zod: { def: { check: "custom"; fn?: (value: unknown) => boolean }; check?: (payload: unknown) => unknown } };
@@ -90,9 +99,9 @@ type SupportedCheck =
90
99
  * dropped. A schema the flag cannot express reuses the parser, which still answers correctly — it
91
100
  * just builds a value nothing reads.
92
101
  */
93
- function compileValidator(schema: SomeType, parser: (input: unknown) => unknown): (input: unknown) => unknown {
102
+ function compileValidator(schema: SomeType, parser: CompiledFn<unknown>): CompiledFn<unknown> {
94
103
  try {
95
- return compileFn(schema, { assertOnly: true }) as (input: unknown) => unknown;
104
+ return compileFn(schema, { assertOnly: true }) as CompiledFn<unknown>;
96
105
  } catch {
97
106
  return parser;
98
107
  }
@@ -117,53 +126,9 @@ export interface CompileOptions {
117
126
  export function compile<T extends SomeType>(schema: T, options?: CompileOptions): T {
118
127
  try {
119
128
  const parser = compileFn(schema);
120
- const clone = util.clone(schema as any) as T;
121
-
122
- // Capture the source-of-truth runtime eagerly. If schema._zod.run is itself a shim installed by global-mode (`__originalRun` set), unwrap past it. Otherwise capturing the live property lazily would let a later self- replacement of schema._zod.run feed our wrapper back into itself.
123
- const liveRun = schema._zod.run as ((p: ParsePayload, c: ParseContextInternal) => any) & {
124
- __originalRun?: (p: ParsePayload, c: ParseContextInternal) => any;
125
- };
126
- const originalRun = liveRun.__originalRun ?? liveRun;
127
-
128
- // Delegate to the *original* schema's run on bypass/fallback (not the
129
- // clone's). The original closed over its own `inst` at construction time;
130
- // issue payloads use that reference to derive things like the class name
131
- // for `z.instanceof(Test)`. Calling the clone's freshly-initialized run
132
- // would push issues with `inst === clone`, producing diverging error
133
- // messages from the original schema.
134
- const wrapped = (payload: ParsePayload, ctx: ParseContextInternal): any => {
135
- if (
136
- ctx?.async ||
137
- ctx?.direction === "backward" ||
138
- ctx?.skipChecks ||
139
- (ctx as Record<symbol, unknown> | undefined)?.[FALLBACK_FLAG]
140
- ) {
141
- return originalRun(payload, ctx);
142
- }
143
-
144
- // A memoized back-edge: only the runtime can close a reference cycle, and a transform on one must raise $ZodCyclicError from its own parse.
145
- if (ctx && isBackEdge(ctx, payload.value)) {
146
- return originalRun(payload, ctx);
147
- }
148
-
149
- const out = parser(payload.value);
150
- if (out !== INVALID) {
151
- payload.value = out;
152
- return payload;
153
- }
154
- // Mark this parse as runtime-driven: under global mode every nested schema carries its own compiled wrapper, and without the flag the parent's runtime fallback re-enters each child's fast path, running user callbacks a third time on invalid input.
155
- if (ctx) (ctx as Record<symbol, unknown>)[FALLBACK_FLAG] = true;
156
- return originalRun(payload, ctx);
157
- };
158
- // Let later compiles of (or through) this run unwrap to the true runtime — both the global shim and repeated z.compile calls rely on this. The bag also carries the parser and the validator, so the standalone validate can skip the payload and wrapper on the happy path.
159
- (wrapped as { __originalRun?: typeof originalRun }).__originalRun = originalRun;
160
- clone._zod.bag.fallbackRun = originalRun;
161
- clone._zod.bag.validator = compileValidator(schema, parser as (input: unknown) => unknown);
162
- clone._zod.run = wrapped;
163
-
164
- // The fast parse/safeParse closures fall back through the source schema's methods. If the source is shim- or wrapper-managed, those methods route into a compiled run and would execute user callbacks a third time on invalid input — the plain method → wrapper path is exactly 2x, so skip.
165
- if (!liveRun.__originalRun) installCompiledUserMethods(clone, schema, parser);
166
-
129
+ const clone = withParser(schema, parser);
130
+ // withParser leaves the parser as its own validator; the generated assert-only variant is better when we have one
131
+ clone._zod.bag.validator = compileValidator(schema, parser as CompiledFn<unknown>);
167
132
  return clone;
168
133
  } catch (err) {
169
134
  if (options?.strict) throw err;
@@ -172,6 +137,68 @@ export function compile<T extends SomeType>(schema: T, options?: CompileOptions)
172
137
  }
173
138
  }
174
139
 
140
+ /**
141
+ * Install an already-generated parser as a schema's fast path. Returns a clone; the original is
142
+ * unchanged.
143
+ *
144
+ * The parser takes the input and returns the parsed value, or `INVALID` to hand the parse to the
145
+ * runtime. It must be synchronous and forward-direction, and it must build fresh output rather than
146
+ * return its input — Zod cannot check either, and a wrong *success* is returned to the caller as-is.
147
+ *
148
+ * `compile()` is the ordinary entry point. This is for a build-time or native compiler that produces
149
+ * a parser where `new Function` is unavailable.
150
+ */
151
+ export function withParser<T extends SomeType>(schema: T, parser: (input: unknown) => core.output<T> | INVALID): T {
152
+ // generated code never receives the parse context, so only the runtime can close a reference cycle; compileFn refuses these too
153
+ if (isRecursiveSchema(schema as any)) {
154
+ throw new ZodCompileUnsupportedError("a schema whose subtree contains a reference cycle");
155
+ }
156
+ const clone = util.clone(schema as any) as T;
157
+
158
+ // Capture the source-of-truth runtime eagerly. If schema._zod.run is itself a shim installed by global-mode (`__originalRun` set), unwrap past it. Otherwise capturing the live property lazily would let a later self- replacement of schema._zod.run feed our wrapper back into itself.
159
+ const liveRun = schema._zod.run as ((p: ParsePayload, c: ParseContextInternal) => any) & {
160
+ __originalRun?: (p: ParsePayload, c: ParseContextInternal) => any;
161
+ };
162
+ const originalRun = liveRun.__originalRun ?? liveRun;
163
+
164
+ // Delegate to the *original* schema's run on bypass/fallback, not the clone's. The original closed over its own `inst` at construction time, and issue payloads use that reference to derive things like the class name for `z.instanceof(Test)`; calling the clone's freshly-initialized run would push issues with `inst === clone` and diverge from the original schema's error messages.
165
+ const wrapped = (payload: ParsePayload, ctx: ParseContextInternal): any => {
166
+ if (
167
+ ctx?.async ||
168
+ ctx?.direction === "backward" ||
169
+ ctx?.skipChecks ||
170
+ (ctx as Record<symbol, unknown> | undefined)?.[FALLBACK_FLAG]
171
+ ) {
172
+ return originalRun(payload, ctx);
173
+ }
174
+
175
+ // A memoized back-edge: only the runtime can close a reference cycle, and a transform on one must raise $ZodCyclicError from its own parse.
176
+ if (ctx && isBackEdge(ctx, payload.value)) {
177
+ return originalRun(payload, ctx);
178
+ }
179
+
180
+ const out = parser(payload.value);
181
+ if (out !== INVALID) {
182
+ payload.value = out;
183
+ return payload;
184
+ }
185
+ // Mark this parse as runtime-driven: under global mode every nested schema carries its own compiled wrapper, and without the flag the parent's runtime fallback re-enters each child's fast path, running user callbacks a third time on invalid input.
186
+ if (ctx) (ctx as Record<symbol, unknown>)[FALLBACK_FLAG] = true;
187
+ return originalRun(payload, ctx);
188
+ };
189
+ // Let later compiles of (or through) this run unwrap to the true runtime — both the global shim and repeated z.compile calls rely on this. The bag also carries the parser and the validator, so the standalone validate can skip the payload and wrapper on the happy path.
190
+ (wrapped as { __originalRun?: typeof originalRun }).__originalRun = originalRun;
191
+ clone._zod.bag.fallbackRun = originalRun;
192
+ // a supplied parser answers `validate` too: one implementation means parse and validate cannot disagree, and its undefined `definite` keeps the runtime re-parse on every rejection
193
+ clone._zod.bag.validator = parser as CompiledFn<unknown>;
194
+ clone._zod.run = wrapped;
195
+
196
+ // The fast parse/safeParse closures fall back through the source schema's methods. If the source is shim- or wrapper-managed, those methods route into a compiled run and would execute user callbacks a third time on invalid input — the plain method → wrapper path is exactly 2x, so skip.
197
+ if (!liveRun.__originalRun) installCompiledUserMethods(clone, schema, parser as CompiledFn<core.output<T>>);
198
+
199
+ return clone;
200
+ }
201
+
175
202
  function installCompiledUserMethods<T extends SomeType>(
176
203
  target: T,
177
204
  source: T,
@@ -204,9 +231,8 @@ function installCompiledUserMethods<T extends SomeType>(
204
231
  }
205
232
 
206
233
  /**
207
- * Generate the standalone compiled function: a parser by default, a validator under
208
- * `assertOnly`. Returns either the parsed value, `true` where nothing reads the output,
209
- * or the `INVALID` sentinel. Internal — consumers should use `compile()`.
234
+ * @internal Generate the standalone compiled function: a parser by default, a validator under
235
+ * `assertOnly`. Returns the parsed value, `true` where nothing reads the output, or `INVALID`. Consumers use `compile()`.
210
236
  */
211
237
  export function compileFn<T extends SomeType>(schema: T, options?: CompileFnOptions): CompiledFn<core.output<T>> {
212
238
  // Cycle-breaking is keyed on the parse context, which generated code never receives. `shape` can be a getter that throws (z.pick() with an unrecognized mask key), so treat "can't tell" as recursive.
@@ -222,6 +248,7 @@ export function compileFn<T extends SomeType>(schema: T, options?: CompileFnOpti
222
248
  constants: new Map(),
223
249
  constantCounter: 0,
224
250
  varCounter: 0,
251
+ definite: true,
225
252
  };
226
253
 
227
254
  const doc = new Doc(["input"]);
@@ -253,6 +280,7 @@ export function compileFn<T extends SomeType>(schema: T, options?: CompileFnOpti
253
280
  if (options?.debug) {
254
281
  fn.code = fullCode;
255
282
  }
283
+ fn.definite = ctx.definite;
256
284
  return fn;
257
285
  }
258
286
 
@@ -266,6 +294,12 @@ function addConstant(ctx: CompileContext, value: unknown): string {
266
294
  return name;
267
295
  }
268
296
 
297
+ /** Hoists a user-supplied callback. Anything the schema's author wrote can throw, and generated code can reject an earlier sibling before ever reaching it, so this clears `definite` — a rejection is then no longer proof that the interpreter would have rejected rather than thrown. */
298
+ function addUserConstant(ctx: CompileContext, fn: unknown): string {
299
+ ctx.definite = false;
300
+ return addConstant(ctx, fn);
301
+ }
302
+
269
303
  function newVar(ctx: CompileContext): string {
270
304
  return `v${ctx.varCounter++}`;
271
305
  }
@@ -322,6 +356,8 @@ function compileChild(
322
356
  }
323
357
 
324
358
  function emitRuntimeIsland(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string {
359
+ // an islanded child answers INVALID for an async run too, which the interpreter throws for
360
+ ctx.definite = false;
325
361
  const schemaConst = addConstant(ctx, schema);
326
362
  const runConst = addConstant(ctx, runtimeRun);
327
363
  const outVar = newVar(ctx);
@@ -418,6 +454,9 @@ function generateChecks(doc: Doc, ctx: CompileContext, schema: SomeType, accesso
418
454
  case "property":
419
455
  generatePropertyCheck(doc, ctx, def, currentAccessor);
420
456
  break;
457
+ case "properties":
458
+ generatePropertiesChecks(doc, ctx, def, currentAccessor, false);
459
+ break;
421
460
  case "overwrite": {
422
461
  // Overwrite transforms the value - create new variable for transformed result
423
462
  const newAccessor = newVar(ctx);
@@ -573,6 +612,35 @@ function generateMimeTypeCheck(
573
612
  }
574
613
  }
575
614
 
615
+ // asserts each named property in place; children compile assert-only because z.properties never rebuilds its input
616
+ function generatePropertiesChecks(
617
+ doc: Doc,
618
+ ctx: CompileContext,
619
+ def: $ZodPropertiesDef,
620
+ accessor: string,
621
+ schemaRole: boolean
622
+ ): void {
623
+ // a custom `when` gates the assertion at runtime; inside a union a wrongly-run branch is absorbed as a branch failure rather than falling back, so refuse at codegen the way the check role does
624
+ if (def.when) {
625
+ throw new ZodCompileUnsupportedError(`check with a custom "when" condition`);
626
+ }
627
+ // matches the runtime gate for whichever role this is: a schema rejects a primitive outright, a check only a nullish value
628
+ doc.write(
629
+ schemaRole
630
+ ? `if (${accessor} === null || (typeof ${accessor} !== "object" && typeof ${accessor} !== "function")) return INVALID;`
631
+ : `if (${accessor} == null) return INVALID;`
632
+ );
633
+ const shape = def.shape as Record<string | symbol, SomeType>;
634
+ for (const key of Reflect.ownKeys(shape)) {
635
+ // a symbol has no source literal, so it is hoisted as a constant
636
+ const keyExpr = typeof key === "symbol" ? addConstant(ctx, key) : util.esc(key);
637
+ // cache the property read so a getter runs exactly once, matching the runtime
638
+ const inputVar = newVar(ctx);
639
+ doc.write(`const ${inputVar} = ${accessor}[${keyExpr}];`);
640
+ compileChild(doc, ctx, shape[key]!, inputVar, false);
641
+ }
642
+ }
643
+
576
644
  function generatePropertyCheck(
577
645
  doc: Doc,
578
646
  ctx: CompileContext,
@@ -626,7 +694,7 @@ function generateCustomRefineCheck(doc: Doc, ctx: CompileContext, check: CustomC
626
694
  if (isAsyncFunction(def.fn)) {
627
695
  throw new ZodCompileAsyncError("z.compile: async .refine() predicates are not supported");
628
696
  }
629
- const fnConst = addConstant(ctx, def.fn);
697
+ const fnConst = addUserConstant(ctx, def.fn);
630
698
  const throwAsyncConst = addConstant(ctx, throwAsync);
631
699
  const resVar = newVar(ctx);
632
700
  doc.write(`const ${resVar} = ${fnConst}(${accessor});`);
@@ -653,7 +721,7 @@ function generateCustomRefineCheck(doc: Doc, ctx: CompileContext, check: CustomC
653
721
  if (result instanceof Promise) throwAsync();
654
722
  return fakePayload.issues.length === 0 ? fakePayload.value : INVALID;
655
723
  };
656
- const helperConst = addConstant(ctx, helperFn);
724
+ const helperConst = addUserConstant(ctx, helperFn);
657
725
  const outVar = newVar(ctx);
658
726
  doc.write(`const ${outVar} = ${helperConst}(${accessor});`);
659
727
  doc.write(`if (${outVar} === INVALID) return INVALID;`);
@@ -743,6 +811,11 @@ function generateStringFormatCheck(doc: Doc, ctx: CompileContext, def: StringFor
743
811
  doc.write(`if (!${validator}(${accessor})) return INVALID;`);
744
812
  return accessor;
745
813
  }
814
+ if (fmt === "iban") {
815
+ const validator = addConstant(ctx, isValidIBAN);
816
+ doc.write(`if (!${validator}(${accessor})) return INVALID;`);
817
+ return accessor;
818
+ }
746
819
  const formatDef = def as unknown as { normalize?: boolean; hostname?: unknown; protocol?: unknown };
747
820
  if (
748
821
  fmt === "url" ||
@@ -861,6 +934,7 @@ type SupportedSchemaType =
861
934
  | "lazy"
862
935
  | "pipe"
863
936
  | "custom"
937
+ | "properties"
864
938
  | "transform"
865
939
  | "catch";
866
940
 
@@ -1005,6 +1079,10 @@ function generateCheck(
1005
1079
  case "custom":
1006
1080
  typeAccessor = generateCustomCheck(doc, ctx, schema, accessor);
1007
1081
  break;
1082
+ case "properties":
1083
+ generatePropertiesChecks(doc, ctx, (schema as $ZodProperties)._zod.def, accessor, true);
1084
+ typeAccessor = accessor;
1085
+ break;
1008
1086
  case "transform":
1009
1087
  typeAccessor = generateTransformCheck(doc, ctx, schema, accessor);
1010
1088
  break;
@@ -1833,6 +1911,8 @@ function literalEquality(ctx: CompileContext, accessor: string, value: unknown):
1833
1911
 
1834
1912
  function generateIntersectionCheck(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string {
1835
1913
  const def = schema._zod.def as unknown as { left: SomeType; right: SomeType };
1914
+ // An unmergeable merge, and a child failing before the merge is even reached, both return INVALID for a case the interpreter answers with a throw.
1915
+ ctx.definite = false;
1836
1916
  const leftOutput = compileChild(doc, ctx, def.left, accessor);
1837
1917
  const rightOutput = compileChild(doc, ctx, def.right, accessor);
1838
1918
 
@@ -1912,15 +1992,15 @@ function generateRecordCheck(doc: Doc, ctx: CompileContext, schema: SomeType, ac
1912
1992
  // The runtime runs it against each own enumerable key and writes the value
1913
1993
  // under the key it produced, so compile it once and call it per key.
1914
1994
  const isLoose = (def as { mode?: string }).mode === "loose";
1915
- const keyFast = addConstant(ctx, compileFn(def.keyType));
1995
+ // the key compiles in its own context, so carry its verdict out: a key schema that can answer INVALID undecidably makes this validator undecidable too
1996
+ const keyFn = compileFn(def.keyType);
1997
+ if (keyFn.definite === false) ctx.definite = false;
1998
+ const keyFast = addConstant(ctx, keyFn);
1916
1999
  const numericConst = addConstant(ctx, regexes.number);
1917
- const propIsEnumerableConst = addConstant(ctx, Object.prototype.propertyIsEnumerable);
1918
2000
  const outKeyVar = newVar(ctx);
1919
2001
 
1920
- doc.write(`for (const ${kVar} of Reflect.ownKeys(${accessor})) {`);
1921
- doc.indented((d) => {
1922
- d.write(`if (${kVar} === "__proto__") continue;`);
1923
- d.write(`if (!${propIsEnumerableConst}.call(${accessor}, ${kVar})) continue;`);
2002
+ // the body runs once per string key and once per symbol key, since a key schema can accept symbols
2003
+ emitOwnKeys(doc, ctx, accessor, kVar, (d) => {
1924
2004
  d.write(`let ${outKeyVar} = ${keyFast}(${kVar});`);
1925
2005
  // Numeric-string retry, mirroring the runtime: a key the schema rejects as a string is tried again as a number, so z.record(z.number(), …) matches the numeric keys JavaScript stringified on the way in.
1926
2006
  d.write(
@@ -1940,24 +2020,56 @@ function generateRecordCheck(doc: Doc, ctx: CompileContext, schema: SomeType, ac
1940
2020
  const valOutput = compileChild(d, ctx, def.valueType, valueVar);
1941
2021
  d.write(`${outputVar}[${outKeyVar}] = ${valOutput};`);
1942
2022
  });
1943
- doc.write(`}`);
1944
2023
  return outputVar;
1945
2024
  }
1946
2025
 
1947
- // Plain z.string() keys: iterate enumerable own keys and validate each value. Runtime uses Reflect.ownKeys so symbol keys participate in validation; matching that here prevents silently accepting objects with enumerable Symbol keys under z.record(z.string(), ...).
1948
- const propIsEnumerable = addConstant(ctx, Object.prototype.propertyIsEnumerable);
1949
- doc.write(`for (const ${kVar} of Reflect.ownKeys(${accessor})) {`);
2026
+ // Plain z.string() keys: every own enumerable string key's value is validated, and an own enumerable symbol key fails the string key schema, as it does in the runtime's Reflect.ownKeys walk.
2027
+ emitOwnKeys(
2028
+ doc,
2029
+ ctx,
2030
+ accessor,
2031
+ kVar,
2032
+ (d) => {
2033
+ d.write(`const ${valVar} = ${accessor}[${kVar}];`);
2034
+ const valOutput = compileChild(d, ctx, def.valueType, valVar);
2035
+ d.write(`${outputVar}[${kVar}] = ${valOutput};`);
2036
+ },
2037
+ `return INVALID;`
2038
+ );
2039
+
2040
+ return outputVar;
2041
+ }
2042
+
2043
+ // Walks the own enumerable keys of a plain object in Reflect.ownKeys order — strings from getOwnPropertyNames, then symbols — instead of Reflect.ownKeys, whose accumulator made that walk 3–6x the cost of the loop it fed. Both snapshots are taken before any value is read and every key is rechecked with propertyIsEnumerable when visited, exactly the runtime's walk, so a getter that adds, deletes, hides or reveals a key mid-walk sees the runtime's verdict; for-in and Object.keys were no faster and each lost a case (for-in enumerates the prototype chain after the own keys, Object.keys drops a key that is non-enumerable at snapshot time). `body` is written once for the string loop and once for the symbol loop unless `onSymbol` replaces the latter.
2044
+ function emitOwnKeys(
2045
+ doc: Doc,
2046
+ ctx: CompileContext,
2047
+ accessor: string,
2048
+ kVar: string,
2049
+ body: (d: Doc) => void,
2050
+ onSymbol?: string
2051
+ ): void {
2052
+ const propIsEnumerableConst = addConstant(ctx, Object.prototype.propertyIsEnumerable);
2053
+ const symsVar = newVar(ctx);
2054
+ const keysVar = newVar(ctx);
2055
+ const iVar = newVar(ctx);
2056
+ doc.write(`const ${symsVar} = Object.getOwnPropertySymbols(${accessor});`);
2057
+ doc.write(`const ${keysVar} = Object.getOwnPropertyNames(${accessor});`);
2058
+ doc.write(`for (let ${iVar} = 0; ${iVar} < ${keysVar}.length; ${iVar}++) {`);
1950
2059
  doc.indented((d) => {
1951
- d.write(`if (${kVar} === "__proto__") continue;`);
1952
- d.write(`if (!${propIsEnumerable}.call(${accessor}, ${kVar})) continue;`);
1953
- d.write(`if (typeof ${kVar} !== "string") return INVALID;`);
1954
- d.write(`const ${valVar} = ${accessor}[${kVar}];`);
1955
- const valOutput = compileChild(d, ctx, def.valueType, valVar);
1956
- d.write(`${outputVar}[${kVar}] = ${valOutput};`);
2060
+ d.write(`const ${kVar} = ${keysVar}[${iVar}];`);
2061
+ d.write(`if (${kVar} === "__proto__" || !${propIsEnumerableConst}.call(${accessor}, ${kVar})) continue;`);
2062
+ body(d);
2063
+ });
2064
+ doc.write(`}`);
2065
+ doc.write(`for (let ${iVar} = 0; ${iVar} < ${symsVar}.length; ${iVar}++) {`);
2066
+ doc.indented((d) => {
2067
+ d.write(`const ${kVar} = ${symsVar}[${iVar}];`);
2068
+ d.write(`if (!${propIsEnumerableConst}.call(${accessor}, ${kVar})) continue;`);
2069
+ if (onSymbol) d.write(onSymbol);
2070
+ else body(d);
1957
2071
  });
1958
2072
  doc.write(`}`);
1959
-
1960
- return outputVar;
1961
2073
  }
1962
2074
 
1963
2075
  function literalPropertyKey(ctx: CompileContext, key: string | symbol): string {
@@ -2027,7 +2139,7 @@ function generateTemplateLiteralCheck(doc: Doc, ctx: CompileContext, schema: Som
2027
2139
  function generateLazyCheck(doc: Doc, ctx: CompileContext, schema: SomeType, accessor: string): string {
2028
2140
  // For lazy schemas, we use a cached parser that falls back to runtime Zod parsing This handles recursive schemas correctly by avoiding infinite compilation loops
2029
2141
  const def = schema._zod.def as unknown as { getter: () => SomeType };
2030
- const getterConst = addConstant(ctx, def.getter);
2142
+ const getterConst = addUserConstant(ctx, def.getter);
2031
2143
  const cacheConst = addConstant(ctx, { parser: null as ((input: unknown) => unknown) | null });
2032
2144
 
2033
2145
  doc.write(`if (!${cacheConst}.parser) {`);
@@ -2060,7 +2172,7 @@ function generatePipeCheck(doc: Doc, ctx: CompileContext, schema: SomeType, acce
2060
2172
  const inputOutput = generateCheck(doc, ctx, def.in, accessor);
2061
2173
 
2062
2174
  if (def.transform) {
2063
- // Apply transform and validate output. The transform may read its second `payload` argument (codec transforms like z.stringbool() push issues there) so wrap the call in a helper that spoofs a payload. Pushed issues signal INVALID and the wrapper falls back to the runtime.
2175
+ // Apply transform and validate output. The transform may read its second `payload` argument (codec transforms like z.stringbool() push issues there) so wrap the call in a helper that spoofs a payload. Pushed issues signal INVALID and the wrapper falls back to the runtime, and a plain function handing back a promise answers INVALID too — union parity, but not a decidable rejection at the top level.
2064
2176
  if (isAsyncFunction(def.transform)) {
2065
2177
  throw new ZodCompileAsyncError("z.compile: async transforms in pipes are not supported");
2066
2178
  }
@@ -2073,7 +2185,7 @@ function generatePipeCheck(doc: Doc, ctx: CompileContext, schema: SomeType, acce
2073
2185
  if (result instanceof Promise) return INVALID;
2074
2186
  return fakePayload.issues.length === 0 ? result : INVALID;
2075
2187
  };
2076
- const helperConst = addConstant(ctx, helperFn);
2188
+ const helperConst = addUserConstant(ctx, helperFn);
2077
2189
  const transformedVar = newVar(ctx);
2078
2190
  doc.write(`const ${transformedVar} = ${helperConst}(${inputOutput});`);
2079
2191
  doc.write(`if (${transformedVar} === INVALID) return INVALID;`);
@@ -2101,7 +2213,7 @@ function generateCustomCheck(doc: Doc, ctx: CompileContext, schema: SomeType, ac
2101
2213
  throw new ZodCompileAsyncError("z.compile: async custom predicates are not supported");
2102
2214
  }
2103
2215
  // Custom schema with a predicate function (e.g. z.instanceof). `isAsyncFunction` above is syntactic, so a plain function returning a promise reaches here, and a promise is truthy — it would read as a pass where the interpreter throws.
2104
- const fnConst = addConstant(ctx, def.fn);
2216
+ const fnConst = addUserConstant(ctx, def.fn);
2105
2217
  const throwAsyncConst = addConstant(ctx, throwAsync);
2106
2218
  const resVar = newVar(ctx);
2107
2219
  doc.write(`const ${resVar} = ${fnConst}(${accessor});`);
@@ -2146,7 +2258,7 @@ function generateCatchCheck(doc: Doc, ctx: CompileContext, schema: SomeType, acc
2146
2258
  doc.write(`})();`);
2147
2259
 
2148
2260
  const innerConst = addConstant(ctx, def.innerType);
2149
- const catchConst = addConstant(ctx, def.catchValue);
2261
+ const catchConst = addUserConstant(ctx, def.catchValue);
2150
2262
  const catchHelperConst = addConstant(ctx, runtimeCatch);
2151
2263
  doc.write(`if (${outputVar} === INVALID) {`);
2152
2264
  doc.indented((d) => {
@@ -2177,7 +2289,7 @@ function generateTransformCheck(doc: Doc, ctx: CompileContext, schema: SomeType,
2177
2289
  if (result instanceof Promise) return INVALID;
2178
2290
  return fakePayload.issues.length === 0 ? result : INVALID;
2179
2291
  };
2180
- const helperConst = addConstant(ctx, helperFn);
2292
+ const helperConst = addUserConstant(ctx, helperFn);
2181
2293
  const outputVar = newVar(ctx);
2182
2294
  doc.write(`const ${outputVar} = ${helperConst}(${accessor});`);
2183
2295
  doc.write(`if (${outputVar} === INVALID) return INVALID;`);
@@ -12,10 +12,14 @@ export class Doc {
12
12
  this.closed = closed;
13
13
  }
14
14
 
15
+ // the compiler catches a child's throw and keeps writing into this doc, so the indent has to unwind with it
15
16
  indented(fn: (doc: Doc) => void) {
16
17
  this.indent += 1;
17
- fn(this);
18
- this.indent -= 1;
18
+ try {
19
+ fn(this);
20
+ } finally {
21
+ this.indent -= 1;
22
+ }
19
23
  }
20
24
 
21
25
  write(fn: ModeWriter): void;
@@ -213,7 +213,7 @@ type RawIssue<T extends $ZodIssueBase> = T extends any
213
213
  export type $ZodRawIssue<T extends $ZodIssueBase = $ZodIssue> = $ZodInternalIssue<T>;
214
214
 
215
215
  export interface $ZodErrorMap<T extends $ZodIssueBase = $ZodIssue> {
216
- // biome-ignore lint:
216
+ // biome-ignore lint/style/useShorthandFunctionType: interface form is public API
217
217
  (issue: $ZodRawIssue<T>): { message: string } | string | undefined | null;
218
218
  }
219
219
 
@@ -251,7 +251,6 @@ const _messageDesc: PropertyDescriptor = {
251
251
  enumerable: true,
252
252
  configurable: true,
253
253
  };
254
- const _zodDesc: PropertyDescriptor = { value: undefined, enumerable: false };
255
254
  const _issuesDesc: PropertyDescriptor = { value: undefined, enumerable: false };
256
255
 
257
256
  /* Prototypes that already carry the lazy `toString`. Seeded with the
@@ -261,12 +260,10 @@ const _installedToString = /* @__PURE__ */ new WeakSet<object>([Object.prototype
261
260
 
262
261
  const initializer = (inst: $ZodError, def: $ZodIssue[]): void => {
263
262
  inst.name = "$ZodError";
264
- _zodDesc.value = inst._zod;
265
- Object.defineProperty(inst, "_zod", _zodDesc);
263
+ // `_zod` is already non-enumerable: $constructor's init defined it with this same descriptor
266
264
  _issuesDesc.value = def;
267
265
  Object.defineProperty(inst, "issues", _issuesDesc);
268
- // Clear the shared slots; a retained `value` pins the last error's issues.
269
- _zodDesc.value = undefined;
266
+ // Clear the shared slot; a retained `value` pins the last error's issues.
270
267
  _issuesDesc.value = undefined;
271
268
  Object.defineProperty(inst, "message", _messageDesc);
272
269
 
@@ -10,7 +10,7 @@ import {
10
10
  extractDefs,
11
11
  finalize,
12
12
  initializeContext,
13
- process,
13
+ processSchema,
14
14
  } from "./to-json-schema.js";
15
15
 
16
16
  /**
@@ -102,7 +102,7 @@ export class JSONSchemaGenerator {
102
102
  * This must be called before emit().
103
103
  */
104
104
  process(schema: schemas.$ZodType, _params: ProcessParams = { path: [], schemaPath: [] }): JSONSchema.BaseSchema {
105
- return process(schema, this.ctx, _params);
105
+ return processSchema(schema, this.ctx, _params);
106
106
  }
107
107
 
108
108
  /**