zod-compiler 1.28.0 → 2.0.1

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 (97) hide show
  1. package/README.md +176 -148
  2. package/dist/core/codegen/build-path.d.ts +12 -1
  3. package/dist/core/codegen/build-path.d.ts.map +1 -1
  4. package/dist/core/codegen/build-path.js +102 -21
  5. package/dist/core/codegen/build-path.js.map +1 -1
  6. package/dist/core/codegen/context.d.ts +31 -16
  7. package/dist/core/codegen/context.d.ts.map +1 -1
  8. package/dist/core/codegen/context.js +43 -18
  9. package/dist/core/codegen/context.js.map +1 -1
  10. package/dist/core/codegen/emit-issue.d.ts +17 -20
  11. package/dist/core/codegen/emit-issue.d.ts.map +1 -1
  12. package/dist/core/codegen/emit-issue.js +13 -13
  13. package/dist/core/codegen/emit-issue.js.map +1 -1
  14. package/dist/core/codegen/index.d.ts.map +1 -1
  15. package/dist/core/codegen/index.js +9 -7
  16. package/dist/core/codegen/index.js.map +1 -1
  17. package/dist/core/codegen/issue-decls.d.ts +83 -7
  18. package/dist/core/codegen/issue-decls.d.ts.map +1 -1
  19. package/dist/core/codegen/issue-decls.js +93 -10
  20. package/dist/core/codegen/issue-decls.js.map +1 -1
  21. package/dist/core/codegen/schemas/array.js +1 -1
  22. package/dist/core/codegen/schemas/array.js.map +1 -1
  23. package/dist/core/codegen/schemas/bigint.d.ts.map +1 -1
  24. package/dist/core/codegen/schemas/bigint.js +15 -4
  25. package/dist/core/codegen/schemas/bigint.js.map +1 -1
  26. package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
  27. package/dist/core/codegen/schemas/catch.js +3 -2
  28. package/dist/core/codegen/schemas/catch.js.map +1 -1
  29. package/dist/core/codegen/schemas/discriminated-union.d.ts +6 -2
  30. package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
  31. package/dist/core/codegen/schemas/discriminated-union.js +16 -56
  32. package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
  33. package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
  34. package/dist/core/codegen/schemas/effect.js +4 -2
  35. package/dist/core/codegen/schemas/effect.js.map +1 -1
  36. package/dist/core/codegen/schemas/file.js +2 -8
  37. package/dist/core/codegen/schemas/file.js.map +1 -1
  38. package/dist/core/codegen/schemas/number.js +4 -3
  39. package/dist/core/codegen/schemas/number.js.map +1 -1
  40. package/dist/core/codegen/schemas/object.d.ts +11 -1
  41. package/dist/core/codegen/schemas/object.d.ts.map +1 -1
  42. package/dist/core/codegen/schemas/object.js +58 -14
  43. package/dist/core/codegen/schemas/object.js.map +1 -1
  44. package/dist/core/codegen/schemas/readonly.d.ts.map +1 -1
  45. package/dist/core/codegen/schemas/readonly.js.map +1 -1
  46. package/dist/core/codegen/schemas/record.d.ts.map +1 -1
  47. package/dist/core/codegen/schemas/record.js +8 -3
  48. package/dist/core/codegen/schemas/record.js.map +1 -1
  49. package/dist/core/codegen/schemas/sizeable.d.ts +49 -3
  50. package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -1
  51. package/dist/core/codegen/schemas/sizeable.js +101 -8
  52. package/dist/core/codegen/schemas/sizeable.js.map +1 -1
  53. package/dist/core/codegen/schemas/string-bool.d.ts +12 -1
  54. package/dist/core/codegen/schemas/string-bool.d.ts.map +1 -1
  55. package/dist/core/codegen/schemas/string-bool.js +19 -4
  56. package/dist/core/codegen/schemas/string-bool.js.map +1 -1
  57. package/dist/core/codegen/schemas/string.d.ts.map +1 -1
  58. package/dist/core/codegen/schemas/string.js +60 -26
  59. package/dist/core/codegen/schemas/string.js.map +1 -1
  60. package/dist/core/codegen/schemas/tuple.d.ts +14 -11
  61. package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
  62. package/dist/core/codegen/schemas/tuple.js +99 -49
  63. package/dist/core/codegen/schemas/tuple.js.map +1 -1
  64. package/dist/core/codegen/well-known-regex.d.ts +26 -1
  65. package/dist/core/codegen/well-known-regex.d.ts.map +1 -1
  66. package/dist/core/codegen/well-known-regex.js +31 -4
  67. package/dist/core/codegen/well-known-regex.js.map +1 -1
  68. package/dist/core/extract/checks.d.ts.map +1 -1
  69. package/dist/core/extract/checks.js +1 -0
  70. package/dist/core/extract/checks.js.map +1 -1
  71. package/dist/core/extract/extractors/intersection.js +21 -6
  72. package/dist/core/extract/extractors/intersection.js.map +1 -1
  73. package/dist/core/extract/extractors/object.d.ts.map +1 -1
  74. package/dist/core/extract/extractors/object.js +25 -9
  75. package/dist/core/extract/extractors/object.js.map +1 -1
  76. package/dist/core/extract/extractors/optional.d.ts.map +1 -1
  77. package/dist/core/extract/extractors/optional.js +1 -28
  78. package/dist/core/extract/extractors/optional.js.map +1 -1
  79. package/dist/core/extract/extractors/record.js +9 -3
  80. package/dist/core/extract/extractors/record.js.map +1 -1
  81. package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
  82. package/dist/core/extract/extractors/tuple.js +16 -20
  83. package/dist/core/extract/extractors/tuple.js.map +1 -1
  84. package/dist/core/extract/types.d.ts +16 -8
  85. package/dist/core/extract/types.d.ts.map +1 -1
  86. package/dist/core/iife.d.ts +2 -1
  87. package/dist/core/iife.d.ts.map +1 -1
  88. package/dist/core/iife.js +2 -1
  89. package/dist/core/iife.js.map +1 -1
  90. package/dist/core/types.d.ts +70 -21
  91. package/dist/core/types.d.ts.map +1 -1
  92. package/dist/jit.d.ts.map +1 -1
  93. package/dist/jit.js +47 -12
  94. package/dist/jit.js.map +1 -1
  95. package/dist/runtime.d.ts +4 -1
  96. package/dist/runtime.js +14 -11
  97. package/package.json +2 -2
package/README.md CHANGED
@@ -2,19 +2,43 @@
2
2
 
3
3
  **Compile Zod schemas into zero-overhead validation functions at build time.**
4
4
 
5
- Keep your existing Zod schemas. Get **1.2-43x faster** validation, and up to **190x** on rejected
5
+ Keep your existing Zod schemas. Get **up to 42x faster** validation, and up to **47x** on rejected
6
6
  input. No code changes required.
7
7
 
8
+ Requires **Zod ≥ 4.5**. Compiled output reproduces 4.5's semantics exactly, down to code-point string
9
+ lengths, symbol-keyed shapes and tuple issue order, so it does not match earlier 4.x releases. Stay on
10
+ zod-compiler 1.x for Zod 4.0–4.4.
11
+
8
12
  - [What Gets Compiled](#what-gets-compiled)
9
13
  - [Schema Hoisting](#schema-hoisting)
14
+ - [z.compile vs zod-compiler](#zcompile-vs-zod-compiler)
10
15
  - [Benchmark](#benchmark)
11
16
 
12
17
  > [!NOTE]
13
18
  > zod-compiler has been tested to work in large projects with tens of thousands of Zod schemas.
14
19
 
20
+ ## z.compile vs zod-compiler
21
+
22
+ Both generate optimized JavaScript. Zod's [`z.compile()`](https://zod.dev/compile) does it at runtime
23
+ with `new Function()` (JIT); zod-compiler's plugins and CLI do it at build time (AOT), so production
24
+ loads pre-generated validators.
25
+
26
+ | | zod-compiler (build plugins / CLI) | Zod `z.compile()` |
27
+ | -------------------------------------- | -------------------------------------------- | --------------------------------------------- |
28
+ | Compilation | Build time (true AOT) | Runtime (`z.compile()` or the first parse) |
29
+ | Reported validation speedup | Up to 42x; up to 47x on rejected input | ~9x in Zod's headline example |
30
+ | Uses `new Function()` at runtime\* | No | Yes |
31
+ | Cold start | Fast; the validator is already generated | Pays for code generation at startup/first use |
32
+ | Strict CSP without `'unsafe-eval'` | Supported | Compilation is unavailable |
33
+ | Compiler shipped in the runtime bundle | No; only validators and runtime helpers ship | Yes; about 7 KB gzipped according to Zod |
34
+
35
+ \*zod-compiler's optional [`jit()`](#4-runtime-compilation-no-build-step) and
36
+ [Node.js register hook](#5-nodejs-register-hook) use `new Function()` and have the same runtime
37
+ code-generation and CSP trade-offs as Zod's `z.compile()`.
38
+
15
39
  ## Usage
16
40
 
17
- Five ways to use zod-compiler pick one:
41
+ Five ways to use zod-compiler. Pick one:
18
42
 
19
43
  ### 1. Automatic Mode (Default)
20
44
 
@@ -46,7 +70,7 @@ export const CreateUserSchema = z.object({
46
70
  Use them as usual. Methods are installed on the original schema object, so `.shape`, `._zod`, Standard
47
71
  Schema, `instanceof` and `z.toJSONSchema()` keep working.
48
72
 
49
- Compiled schemas also expose **`.is(input): input is T`** a zero-allocation drop-in for
73
+ Compiled schemas also expose **`.is(input): input is T`**, a zero-allocation drop-in for
50
74
  `safeParse(x).success`.
51
75
 
52
76
  ### 2. compile() (Explicit)
@@ -70,7 +94,8 @@ validateUser.parse(data);
70
94
  validateUser.safeParse(data);
71
95
  ```
72
96
 
73
- `compile()` and auto mode coexist. Pair with `schemas: "explicit"` to make `compile()` the _only_ path — no automatic detection, no build-time execution of plain schema files.
97
+ `compile()` and auto mode coexist. Pair with `schemas: "explicit"` to make `compile()` the _only_ path:
98
+ no automatic detection, no build-time execution of plain schema files.
74
99
 
75
100
  ### 3. CLI (No Bundler)
76
101
 
@@ -95,7 +120,7 @@ npx zod-compiler generate src/ --emit compact
95
120
 
96
121
  ### 4. Runtime Compilation (No Build Step)
97
122
 
98
- `jit()` runs the same pipeline in-process, for `tsx`, `ts-node`, Jest anywhere no plugin fires:
123
+ `jit()` runs the same pipeline in-process for `tsx`, `ts-node`, Jest, and anywhere else no plugin fires:
99
124
 
100
125
  ```typescript
101
126
  import { jit } from "zod-compiler/jit";
@@ -104,12 +129,12 @@ export const UserSchema = jit(z.object({ name: z.string().min(1), email: z.email
104
129
  ```
105
130
 
106
131
  Same validators a build emits, installed on the schema object, so Zod interop is unchanged.
107
- Compilation is lazy 0.1-0.3 ms on a schema's first parse; `{ eager: true }` compiles up front,
108
- `jitAll(namespace)` takes a whole module.
132
+ Compilation is lazy, costing 0.1-0.3 ms on a schema's first parse. `{ eager: true }` compiles up front
133
+ and `jitAll(namespace)` takes a whole module.
109
134
 
110
135
  The cost is the import: ~570 KB of codegen and `acorn`, **~10 ms of module load**. That suits a
111
- long-lived process, not a CLI, a cold serverless handler or a browser use the build plugin there.
112
- Libraries should ship plain Zod and let the app decide.
136
+ long-lived process, not a CLI, a cold serverless handler or a browser. Use the build plugin there, and
137
+ have libraries ship plain Zod so the app can decide.
113
138
 
114
139
  Needs `new Function`, as Zod's own object fast-path does. `z.config({ jitless: true })` and a CSP
115
140
  that blocks eval both leave a working plain-Zod schema.
@@ -130,11 +155,11 @@ also chains with TypeScript runners:
130
155
  node --import zod-compiler/register --import tsx src/server.ts
131
156
  ```
132
157
 
133
- This is runtime JIT instrumentation, not the AOT source rewriting performed by the Vite, Rsbuild, and
134
- other build plugins. The hook identifies exported schema bindings and registers their live Zod objects;
135
- validators are generated in-process, lazily on first use. It does not execute modules twice and adds no
136
- transform cache beyond Node's module cache. Use a build plugin or the CLI when generated validator code
137
- must exist before Node starts or runtime `new Function` is unavailable.
158
+ This is runtime JIT instrumentation, not the AOT source rewriting the build plugins perform. The hook
159
+ registers the live Zod objects behind exported schema bindings and generates validators in-process on
160
+ first use. It does not execute modules twice, and adds no cache beyond Node's own module cache. Use a
161
+ build plugin or the CLI when validator code must exist before Node starts, or when `new Function` is
162
+ unavailable at runtime.
138
163
 
139
164
  Optional settings come from `zod-compiler.json` in the working directory:
140
165
 
@@ -150,9 +175,9 @@ Optional settings come from `zod-compiler.json` in the working directory:
150
175
  }
151
176
  ```
152
177
 
153
- `output: "compact"` preserves the Zod schema and compiled valid-input fast path while delegating cold
154
- error production to Zod. Full `"schema"` output remains the default. `"bag"` is unavailable because a
155
- load hook cannot replace already-linked ESM export bindings safely.
178
+ `output: "compact"` keeps the Zod schema and the compiled fast path, delegating cold error production
179
+ to Zod. Full `"schema"` output stays the default. `"bag"` is unavailable here: a load hook cannot safely
180
+ replace already-linked ESM export bindings.
156
181
 
157
182
  ## Build Plugin
158
183
 
@@ -172,8 +197,8 @@ load hook cannot replace already-linked ESM export bindings safely.
172
197
  | Bun | `import zodCompiler from "zod-compiler/bun"` |
173
198
  | Farm | `import zodCompiler from "zod-compiler/farm"` |
174
199
 
175
- Turbopack takes a loader rather than a plugin see [Next.js (Turbopack)](#nextjs-turbopack). Metro
176
- has neither see [React Native / Expo](#react-native--expo).
200
+ Turbopack takes a loader rather than a plugin; see [Next.js (Turbopack)](#nextjs-turbopack). Metro has
201
+ neither; see [React Native / Expo](#react-native--expo).
177
202
 
178
203
  ### Options
179
204
 
@@ -182,13 +207,13 @@ has neither — see [React Native / Expo](#react-native--expo).
182
207
  | `schemas` | `"auto" \| "explicit"` | `"auto"` | `"auto"` compiles every exported schema (and hoisted in-function ones); `"explicit"` only `compile()` calls |
183
208
  | `include` | `string[]` | — | Only process files matching these path globs |
184
209
  | `exclude` | `string[]` | — | Skip files matching these path globs |
185
- | `output` | `"schema" \| "bag" \| "compact"` | `"schema"` | What a compiled export evaluates to see [Compact Output](#compact-output-output-compact) |
210
+ | `output` | `"schema" \| "bag" \| "compact"` | `"schema"` | What a compiled export evaluates to; see [Compact Output](#compact-output-output-compact) |
186
211
  | `verbose` | `boolean` | `false` | Log per-schema compilation status |
187
- | `hoist` | `boolean` | `true` | Move schemas built inside functions to module scope see [Schema Hoisting](#schema-hoisting) |
212
+ | `hoist` | `boolean` | `true` | Move schemas built inside functions to module scope; see [Schema Hoisting](#schema-hoisting) |
188
213
  | `apply` | `"build" \| "serve" \| "all"` | builds + Vitest | **Vite only**: when the plugin runs |
189
- | `codegenMode` | `"lean" \| "inline"` | auto | `"inline"` emits helpers per file; needed for transpile-only esbuild see [SWC](#swc) |
214
+ | `codegenMode` | `"lean" \| "inline"` | auto | `"inline"` emits helpers per file; needed for transpile-only esbuild (see [SWC](#swc)) |
190
215
  | `cache` | `boolean \| string` | `true` | Persistent transform cache in `node_modules/.cache/zod-compiler` |
191
- | `parallel` | `boolean \| number` | `false` | Run transforms on worker threads see [Parallel Transforms](#parallel-transforms) |
216
+ | `parallel` | `boolean \| number` | `false` | Run transforms on worker threads; see [Parallel Transforms](#parallel-transforms) |
192
217
 
193
218
  ```typescript
194
219
  zodCompiler({
@@ -208,10 +233,9 @@ export default defineConfig({
208
233
  });
209
234
  ```
210
235
 
211
- > **Note:** Vitest is detected automatically (via the `VITEST` env var), so
212
- > tests compile and exercise the same validators that ship to production
213
- > including their performance. Pass `apply: "build"` if you want tests to use
214
- > the plain Zod fallback instead.
236
+ > **Note:** Vitest is detected automatically (via the `VITEST` env var), so tests exercise the same
237
+ > validators that ship to production, performance included. Pass `apply: "build"` to have tests use the
238
+ > plain Zod fallback instead.
215
239
 
216
240
  ### Bun
217
241
 
@@ -223,7 +247,7 @@ import zodCompiler from "zod-compiler/bun";
223
247
  await Bun.build({ entrypoints: ["./src/index.tsx"], outdir: "./dist", plugins: [zodCompiler()] });
224
248
  ```
225
249
 
226
- For code run straight from source (`bun run src/server.ts`) no build plugin fires — use
250
+ No build plugin fires for code run straight from source (`bun run src/server.ts`). Use
227
251
  [`jit()`](#4-runtime-compilation-no-build-step) to compile in-process, or the
228
252
  [CLI](#3-cli-no-bundler) to compile ahead of time.
229
253
 
@@ -243,22 +267,22 @@ function getSchema() {
243
267
  ```
244
268
 
245
269
  Only expressions built from imported bindings and literals move; anything touching locals, `this` or
246
- `new Date()` stays put. Combinator chains on imported schemas qualify via `schemaNamePattern`
247
- (default `/ZodSchema$/`).
270
+ `new Date()` stays put. Combinator chains on imported schemas qualify via `schemaNamePattern` (default
271
+ `/ZodSchema$/`).
248
272
 
249
- In auto mode hoisted schemas also **compile**, rescuing the schema that never leaves a function (a
250
- slonik query, a tRPC input) and so is invisible to export scanning: ~16,700 ns → ~14 ns per call.
273
+ In auto mode hoisted schemas also **compile**, rescuing schemas that never leave a function (a slonik
274
+ query, a tRPC input) and are therefore invisible to export scanning: ~16,700 ns → ~14 ns per call.
251
275
 
252
276
  ### Bundle Size & Cross-File Dedup
253
277
 
254
278
  Validators share a runtime helper layer imported from one module, so each helper appears once per
255
- bundle. Schemas in a file sharing a structurally identical sub-shape emit its error walk once
256
- **19-28% raw / 10-18% gzipped**, scaling with how much the file repeats.
279
+ bundle. Schemas in a file that share a structurally identical sub-shape emit its error walk once, worth
280
+ **19-28% raw / 10-18% gzipped** and scaling with how much the file repeats.
257
281
 
258
282
  Build plugins serve that module from a resolve hook (`virtual:zod-compiler/runtime`, or
259
283
  `__zod-compiler-runtime__` on webpack and rspack, which reject the `virtual:` scheme). A loader host
260
284
  has no hook, so [Turbopack](#nextjs-turbopack) imports the same code from the real subpath
261
- `zod-compiler/runtime` instead opt-in there, since it only pays off where the host bundles that
285
+ `zod-compiler/runtime` instead. It is opt-in there, since it only pays off where the host bundles that
262
286
  import rather than leaving it external.
263
287
 
264
288
  **Transpile-only esbuild builds** (no `--bundle`) never fire the bundler's resolve hooks, so the
@@ -273,7 +297,7 @@ Set `output: "bag"` to also drop the retained Zod schema when you don't need `.s
273
297
 
274
298
  ### Next.js (Turbopack)
275
299
 
276
- Turbopack the default since Next.js 16 [runs webpack loaders but no webpack
300
+ Turbopack, the default since Next.js 16, [runs webpack loaders but no webpack
277
301
  plugins](https://nextjs.org/docs/app/api-reference/turbopack#webpack-plugins), so use the loader
278
302
  entry point:
279
303
 
@@ -300,20 +324,20 @@ const nextConfig: NextConfig = {
300
324
  export default nextConfig;
301
325
  ```
302
326
 
303
- Automatic mode, unchanged sources, `next dev` and `next build`. Options go in the object form
304
- `loaders: [{ loader: "zod-compiler/turbopack", options: { verbose: true } }]` and must be plain
305
- JSON, so `hoist.schemaNamePattern` takes a string, not a RegExp.
327
+ Automatic mode, unchanged sources, `next dev` and `next build`. Options go in the object form,
328
+ `loaders: [{ loader: "zod-compiler/turbopack", options: { verbose: true } }]`, and must be plain JSON,
329
+ so `hoist.schemaNamePattern` takes a string, not a RegExp.
306
330
 
307
331
  Three things worth knowing:
308
332
 
309
333
  - **Keep the `content` pattern loose.** Narrowing it to `"zod"` skips `zod/v4`, `zod/mini` and the
310
- `zod-compiler` import behind `schemas: "explicit"` those files just quietly stay uncompiled.
334
+ `zod-compiler` import behind `schemas: "explicit"`, leaving those files quietly uncompiled.
311
335
  - **`codegenMode: "lean"` is App-Router-only.** It shares one copy of the helpers across the bundle,
312
336
  but Pages Router server code externalizes `node_modules` imports unless
313
337
  [`bundlePagesRouterDependencies`](https://nextjs.org/docs/pages/api-reference/config/next-config-js/bundlePagesRouterDependencies)
314
338
  is on, so a devDependency install throws `ERR_MODULE_NOT_FOUND` in production.
315
- - **A `"use server"` file can only export async functions**, so keep schemas there inside a function
316
- [hoisting](#schema-hoisting) still compiles them. `"use client"` modules need nothing special.
339
+ - **A `"use server"` file can only export async functions**, so keep schemas there inside a function.
340
+ [Hoisting](#schema-hoisting) still compiles them. `"use client"` modules need nothing special.
317
341
 
318
342
  Turbopack caches loader results itself, so cache `.next/cache` in CI rather than
319
343
  `node_modules/.cache/zod-compiler`. `next dev --webpack` / `next build --webpack` still work, with
@@ -339,25 +363,25 @@ Defaults `codegenMode` to `"inline"` (SWC has no virtual-module hook); pass
339
363
 
340
364
  ### React Native / Expo
341
365
 
342
- There is no Metro plugin unplugin has no Metro adapter. Use the [CLI](#3-cli-no-bundler); Metro
366
+ There is no Metro plugin, since unplugin has no Metro adapter. Use the [CLI](#3-cli-no-bundler); Metro
343
367
  bundles what it emits as ordinary source:
344
368
 
345
369
  ```bash
346
370
  npx zod-compiler generate src/schemas/ -o src/schemas/compiled/ --watch
347
371
  ```
348
372
 
349
- Worth the step: **Hermes ships no JIT and no `new Function`**, so Zod's own object fast path is
350
- unavailable on device and [`jit()`](#4-runtime-compilation-no-build-step) cannot run there at all.
373
+ The step pays for itself: **Hermes ships no JIT and no `new Function`**, so Zod's own object fast path
374
+ is unavailable on device and [`jit()`](#4-runtime-compilation-no-build-step) cannot run there at all.
351
375
 
352
- Keep schema modules free of `react-native` and `expo-*` imports, transitively discovery executes
353
- each file and its import graph in Node (in both modes), and one that throws falls back to runtime
354
- Zod silently.
376
+ Keep schema modules free of `react-native` and `expo-*` imports, transitively. Discovery executes each
377
+ file and its import graph in Node (in both modes), and one that throws falls back to runtime Zod
378
+ silently.
355
379
 
356
380
  ### Compact Output (`output: "compact"`)
357
381
 
358
382
  Compiles the fast path and delegates the cold error path to the retained Zod schema, dropping
359
- **~73% raw / ~71% gzipped** on 50 distinct schemas. The hot path is unchanged and errors are Zod's own;
360
- only reading `.error` invokes Zod. Mutually exclusive with `output: "bag"`.
383
+ **~73% raw / ~71% gzipped** across 50 distinct schemas. The hot path is unchanged and errors are Zod's
384
+ own; only reading `.error` invokes Zod. Mutually exclusive with `output: "bag"`.
361
385
 
362
386
  ```typescript
363
387
  zodCompiler({ output: "compact" });
@@ -365,18 +389,16 @@ zodCompiler({ output: "compact" });
365
389
 
366
390
  ### Workers and Serverless Startup
367
391
 
368
- Workers often construct every imported schema during module initialization, even when an isolate only
369
- validates a few of them. Compiling all of those schemas can improve validation while increasing bundle
370
- size and startup work. Compact output reduces compiler-generated error-path code, but still retains the
371
- original Zod schema and does not make eager schema construction lazy.
372
-
373
- Automatic discovery remains the default. If an application has a clear schema boundary, narrow
374
- `include` or use `schemas: "explicit"` to avoid compiling intermediate exports.
392
+ Workers construct every imported schema at module init, even when an isolate validates only a few.
393
+ Compiling all of them buys validation speed at the cost of bundle size and startup work. Compact output
394
+ trims the generated error path but still retains the Zod schema, and does not make eager construction
395
+ lazy.
375
396
 
376
- Use `output: "bag"` only when consumers do not need Zod APIs such as `.shape`, `.extend()`, `.meta()`,
377
- or `z.toJSONSchema()`; it can omit the retained schema entirely.
397
+ If your app has a clear schema boundary, narrow `include` or use `schemas: "explicit"` to skip
398
+ intermediate exports. Use `output: "bag"` only where consumers need no Zod APIs (`.shape`, `.extend()`,
399
+ `.meta()`, `z.toJSONSchema()`); it can drop the retained schema entirely.
378
400
 
379
- Measure startup separately from validation throughput using the target deployment and bundle.
401
+ Measure startup separately from validation throughput, on the target deployment and bundle.
380
402
 
381
403
  ### Auto Mode: Side Effects Warning
382
404
 
@@ -384,8 +406,8 @@ Auto mode executes files to inspect their exports, so a file with schema-shaped
384
406
  effects runs them at build time. Limit the scan with `include`.
385
407
 
386
408
  For the common `env.ts` that validates `process.env` and exits, zod-compiler sets
387
- `process.env.ZOD_COMPILER` during discovery and intercepts `process.exit`, so the build never crashes
388
- those files just fall back to runtime Zod. To keep them compiled, guard on it:
409
+ `process.env.ZOD_COMPILER` during discovery and intercepts `process.exit`, so the build never crashes.
410
+ Those files fall back to runtime Zod. To keep them compiled, guard on it:
389
411
 
390
412
  ```typescript
391
413
  if (!process.env.ZOD_COMPILER) {
@@ -396,12 +418,12 @@ if (!process.env.ZOD_COMPILER) {
396
418
  With `@t3-oss/env-*`, pass `skipValidation: !!process.env.ZOD_COMPILER`.
397
419
 
398
420
  A schema whose SHAPE branches on an env var is baked at build time, and the cache key does not include
399
- the environment give each environment its own `cache` directory if you share one across them.
421
+ the environment. Give each environment its own `cache` directory if you share one across them.
400
422
 
401
423
  ### Large projects and CI
402
424
 
403
425
  Discovery executes each schema file inside the bundler's process, so the **first cold run** is the
404
- expensive one later runs hit the persistent cache.
426
+ expensive one. Later runs hit the persistent cache.
405
427
 
406
428
  ```yaml
407
429
  - uses: actions/cache@v4
@@ -415,10 +437,10 @@ never mention `zod` cost nothing.
415
437
 
416
438
  ### Parallel Transforms
417
439
 
418
- Discovery runs one file at a time on the bundler's own thread executions are serialized so
419
- concurrent transforms cannot double-execute a shared dependency. `parallel` moves whole transforms
420
- onto worker threads instead, each with its own loader and module cache, which is what makes running
421
- them at the same time sound.
440
+ Discovery runs one file at a time on the bundler's own thread, serializing executions so concurrent
441
+ transforms cannot double-execute a shared dependency. `parallel` moves whole transforms onto worker
442
+ threads instead, each with its own loader and module cache, which is what makes running them at the
443
+ same time sound.
422
444
 
423
445
  ```typescript
424
446
  zodCompiler({ parallel: true }); // one worker per core, less one, capped at 4
@@ -427,34 +449,34 @@ zodCompiler({ parallel: 2 }); // or pick the count yourself
427
449
 
428
450
  **Whether it pays depends on your import graph, not your core count.** A module shared by many
429
451
  schema files is executed once in-process and once _per worker_ here. Files with independent graphs
430
- win; files chained through each other can lose. Both rows below are 120 files of 8 schemas each, on
431
- 12 performance cores the only difference is whether the files import one another:
452
+ win; files chained through each other can lose. Both rows below are 120 files of 8 schemas each on
453
+ 12 performance cores, differing only in whether the files import one another:
432
454
 
433
455
  | Transform (120 files) | in-process | n=2 | n=4 | n=8 | n=12 |
434
456
  | --------------------- | ---------: | -------: | -------: | -------: | -------: |
435
457
  | independent graphs | 3,633 ms | 2,263 ms | 1,508 ms | 1,786 ms | 2,119 ms |
436
458
  | 120-deep import chain | 945 ms | 977 ms | 1,045 ms | 1,796 ms | 3,332 ms |
437
459
 
438
- So measure before adopting it `ZOD_COMPILER_TIMING=1` prints the per-phase breakdown, and the
439
- `discover` line is the one workers move. Throughput peaks around four workers and declines past it:
440
- beyond that point every extra worker re-executes more graph, holds another copy of it in memory, and
441
- adds to the generated source that the single receiving thread has to deserialize.
460
+ So measure before adopting it. `ZOD_COMPILER_TIMING=1` prints the per-phase breakdown, and `discover`
461
+ is the line workers move. Throughput peaks around four workers and declines past it: every extra worker
462
+ re-executes more graph, holds another copy in memory, and adds to the generated source that the single
463
+ receiving thread has to deserialize.
442
464
 
443
- Emitted code, sourcemaps and cache entries are identical either way `parallel` is not part of the
444
- cache key, so a parallel build and a serial one share the same cache. The disk cache and dependency
445
- crawling stay on the bundler thread, and if a worker cannot start or dies mid-build its file is
446
- retried in-process rather than failing the build.
465
+ Emitted code, sourcemaps and cache entries are identical either way. `parallel` is not part of the
466
+ cache key, so parallel and serial builds share one cache. Disk caching and dependency crawling stay on
467
+ the bundler thread, and a worker that cannot start or dies mid-build has its file retried in-process
468
+ rather than failing the build.
447
469
 
448
- A **warm cache still beats parallelism**, and costs no memory reach for `parallel` for the cold
449
- runs the cache cannot help with.
470
+ A **warm cache still beats parallelism** and costs no memory. Reach for `parallel` on the cold runs the
471
+ cache cannot help with.
450
472
 
451
473
  ## Framework Examples
452
474
 
453
- Nothing framework-specific is needed exported schemas are compiled in place, so anything accepting
454
- a Zod schema picks up the compiled version:
475
+ Nothing framework-specific is needed. Exported schemas are compiled in place, so anything accepting a
476
+ Zod schema picks up the compiled version:
455
477
 
456
478
  ```typescript
457
- // tRPC no .input(compile(...)) needed
479
+ // tRPC: no .input(compile(...)) needed
458
480
  t.procedure.input(CreateUserSchema).mutation(({ input }) => createUser(input));
459
481
 
460
482
  // Hono
@@ -464,7 +486,7 @@ app.post("/users", zValidator("json", UserSchema), (c) => c.json(c.req.valid("js
464
486
  useForm({ resolver: zodResolver(SignupSchema) });
465
487
  ```
466
488
 
467
- The same applies to any [Standard Schema](https://standardschema.dev) consumer `~standard.validate`
489
+ The same applies to any [Standard Schema](https://standardschema.dev) consumer: `~standard.validate`
468
490
  routes through the compiled validator.
469
491
 
470
492
  Compiled methods live on the schema object, so Zod's functional API (`z.safeParse(Schema, x)`) and a
@@ -521,9 +543,9 @@ npx zod-compiler check src/schemas.ts --json --fail-under 80
521
543
 
522
544
  ## What Gets Compiled
523
545
 
524
- ### Fully Compiled (1.2-43x faster)
546
+ ### Fully Compiled (up to 42x faster)
525
547
 
526
- Every Zod type except the fallbacks below all primitives, `object` / `strictObject` / `looseObject`,
548
+ Every Zod type except the fallbacks below: all primitives, `object` / `strictObject` / `looseObject`,
527
549
  `array`, `tuple`, `record`, `set`, `map`, `union`, `discriminatedUnion`, `intersection`, `pipe`,
528
550
  the `optional` / `nullable` / `readonly` / `default` / `catch` / `coerce` wrappers, `templateLiteral`,
529
551
  recursive `lazy` (self, mutual and nested), `custom` / `instanceof`, and
@@ -546,7 +568,7 @@ A schema delegates to Zod when it reaches JavaScript the generated code cannot r
546
568
  | Dynamic error maps, unresolvable `z.lazy()` | Not knowable at build time |
547
569
 
548
570
  Everything else compiles, including context-free `preprocess` callbacks and
549
- `transform`/`refine`/`superRefine` whether or not the callback captures a zero-capture one is inlined,
571
+ `transform`/`refine`/`superRefine` whether or not the callback captures. A zero-capture one is inlined,
550
572
  a capturing one called by reference. Delegation is per-sub-schema: one uncompilable field goes to Zod,
551
573
  not the whole object. Run `zod-compiler check` to see what compiled.
552
574
 
@@ -557,7 +579,7 @@ Three things differ by design:
557
579
 
558
580
  | Behavior | Zod | zod-compiler |
559
581
  | ------------------------- | ----------------------------------------------- | ------------------------------------------------------- |
560
- | Record key iteration | All own keys (`Reflect.ownKeys`) | Own enumerable **string** keys only |
582
+ | Record key iteration | Own enumerable keys, symbols included | Own enumerable **string** keys only |
561
583
  | Container output identity | A fresh array / set / map / object | The input container, by reference (array holes survive) |
562
584
  | Per-call parse params | `safeParse(x, { error, reportInput })` honoured | Ignored; global `z.config()` maps still apply |
563
585
 
@@ -572,59 +594,61 @@ Schema-level `error` and `z.config()` maps are unaffected; for a per-call map us
572
594
 
573
595
  | Scenario | Zod v3 | Zod v4 | **zod-compiler** | Typia | AJV | vs Zod v4 |
574
596
  | ----------------------------------------------- | ------ | ------ | ---------------- | ----- | ----- | --------- |
575
- | simple string | 12.6M | 14.3M | **16.5M** | 17.8M | 17.6M | 1.2x |
576
- | string (min/max) | 12.5M | 7.5M | **15.9M** | 17.0M | 15.2M | 2.1x |
577
- | number (int+positive) | 12.3M | 7.8M | **16.6M** | 16.8M | 17.3M | 2.1x |
578
- | enum | 11.7M | 12.1M | **16.2M** | 17.7M | 17.3M | 1.3x |
579
- | bigint (min/max) | 12.0M | 7.7M | **15.9M** | — | — | 2.1x |
580
- | tuple [string, int, bool] | 5.8M | 6.5M | **15.7M** | 17.1M | 16.3M | 2.4x |
581
- | record\<string, number\> | 3.2M | 2.8M | **15.2M** | 12.1M | 15.2M | 5.5x |
582
- | set\<string\> (5 items) | 3.7M | 2.3M | **14.7M** | — | — | 6.4x |
583
- | set\<string\> (20 items) | 1.3M | 683K | **12.0M** | — | — | **18x** |
584
- | map\<string, number\> (5 entries) | 2.1M | 1.4M | **13.1M** | — | — | 9.6x |
585
- | map\<string, number\> (20 entries) | 635K | 362K | **8.3M** | — | — | **23x** |
586
- | pipe (non-transform) | 8.6M | 5.6M | **15.9M** | — | — | 2.8x |
587
- | discriminatedUnion (3 variants) | 3.3M | 4.0M | **15.8M** | 15.5M | 7.7M | 4.0x |
588
- | discriminatedUnion (8 variants, rotating) | 2.7M | 3.4M | **9.2M** | — | — | 2.7x |
589
- | plain union of 8 tagged objects (auto-discrim.) | 363K | 632K | **9.1M** | — | — | **14x** |
590
- | strict object (DB row) | 1.8M | 3.1M | **10.9M** | — | — | 3.5x |
591
- | medium object (valid) | 1.9M | 2.4M | **9.7M** | 11.2M | 7.7M | 4.1x |
592
- | medium object (extra keys stripped) | 1.8M | 2.3M | **9.4M** | — | — | 4.2x |
593
- | medium object (invalid) | 504K | 80K | **14.7M** | 2.9M | 7.7M | **184x** |
594
- | large object (10 items) | 122K | 166K | **5.3M** | 5.9M | 1.2M | **32x** |
595
- | large object (100 items) | 13K | 18K | **781K** | 1.3M | 125K | **43x** |
596
- | readonly field (wrapper compiles away) | 3.1M | 4.4M | **15.7M** | — | — | 3.6x |
597
- | readonly root object (rebuild + freeze) | 2.9M | 3.8M | **12.2M** | — | — | 3.2x |
598
- | readonly array (delegates to Zod) | 3.9M | 2.9M | **2.9M** | — | — | 1.0x |
599
- | recursive tree (7 nodes) | 569K | 2.1M | **8.2M** | 11.6M | 4.8M | 3.9x |
600
- | recursive tree (121 nodes) | 32K | 135K | **800K** | 1.9M | 372K | 5.9x |
601
- | nested recursion (7 nodes) | 391K | 1.0M | **7.9M** | 11.1M | 3.1M | 7.8x |
602
- | nested recursion (121 nodes) | 24K | 62K | **818K** | 1.6M | 218K | **13x** |
603
- | deeply nested object (243 leaves) | 11K | 20K | **828K** | 1.1M | 117K | **42x** |
604
- | event log (combined) | 368K | 609K | **8.2M** | — | — | **13x** |
605
- | object with transform (zero-capture) | 1.2M | 1.9M | **6.5M** | — | — | 3.4x |
606
- | array 10 × transform (zero-capture) | 121K | 214K | **4.2M** | — | — | **20x** |
607
- | array 50 × transform (zero-capture) | 25K | 44K | **1.0M** | — | — | **24x** |
608
- | object with captured transform | 1.4M | 6.3M | **15.1M** | — | — | 2.4x |
609
- | object with captured refine (cross-field) | 1.6M | 2.4M | **15.3M** | — | — | 6.3x |
610
- | object with superRefine (cross-field) | 1.6M | 2.3M | **11.6M** | — | — | 5.0x |
611
- | coerced query object (valid) | 1.9M | 2.3M | **5.2M** | — | — | 2.2x |
612
- | coerced query object (invalid) | 1.1M | 162K | **10.1M** | — | — | **62x** |
613
- | preprocessed query object (valid) | 426K | 1.6M | **5.3M** | — | — | 3.4x |
614
- | preprocessed query object (invalid) | 387K | 149K | **12.4M** | — | — | **84x** |
615
- | stringbool config object (valid) | — | 1.9M | **6.0M** | — | — | 3.1x |
616
- | stringbool config object (invalid) | — | 129K | **13.1M** | — | — | **101x** |
617
- | custom/instanceof request (valid) | 894K | 3.0M | **8.6M** | — | — | 2.9x |
618
- | custom/instanceof request (invalid) | 755K | 158K | **8.5M** | — | — | **54x** |
619
- | disjoint object intersection (valid) | 1.4M | 1.7M | **9.7M** | — | — | 5.7x |
620
- | disjoint object intersection (invalid) | 548K | 80K | **15.3M** | — | — | **190x** |
621
-
622
- _ops/s, higher is better. `vp test bench` on an Apple M4 Max (zod 4.3.6, zod v3 3.23.8, typia 12, ajv 8),
623
- best of three runs. The harness costs ~55 ns per iteration, so the fastest rows sit at that floor and gaps
597
+ | simple string | 12.7M | 15.8M | **16.6M** | 17.4M | 17.4M | 1.0x |
598
+ | string (min/max) | 11.9M | 7.4M | **17.5M** | 17.3M | 15.6M | 2.4x |
599
+ | number (int+positive) | 12.0M | 9.6M | **16.9M** | 16.9M | 16.9M | 1.8x |
600
+ | enum | 11.2M | 15.4M | **17.4M** | 16.9M | 17.0M | 1.1x |
601
+ | bigint (min/max) | 11.8M | 8.3M | **17.0M** | — | — | 2.1x |
602
+ | tuple [string, int, bool] | 5.7M | 7.6M | **17.9M** | 16.8M | 16.0M | 2.4x |
603
+ | record\<string, number\> | 3.1M | 2.5M | **12.6M** | 12.0M | 15.3M | 5.0x |
604
+ | set\<string\> (5 items) | 3.6M | 2.2M | **16.0M** | — | — | 7.2x |
605
+ | set\<string\> (20 items) | 1.3M | 661K | **12.4M** | — | — | **19x** |
606
+ | map\<string, number\> (5 entries) | 2.0M | 1.3M | **13.6M** | — | — | **11x** |
607
+ | map\<string, number\> (20 entries) | 618K | 338K | **8.7M** | — | — | **26x** |
608
+ | pipe (non-transform) | 8.9M | 4.7M | **17.3M** | — | — | 3.7x |
609
+ | discriminatedUnion (3 variants) | 3.3M | 5.2M | **16.2M** | 15.9M | 7.5M | 3.1x |
610
+ | discriminatedUnion (8 variants, rotating) | 2.6M | 4.4M | **9.5M** | — | — | 2.2x |
611
+ | plain union of 8 tagged objects (auto-discrim.) | 348K | 1.3M | **10.2M** | — | — | 8.1x |
612
+ | strict object (DB row) | 1.7M | 3.0M | **11.1M** | — | — | 3.6x |
613
+ | medium object (valid) | 1.9M | 2.4M | **9.9M** | 10.8M | 7.5M | 4.2x |
614
+ | medium object (extra keys stripped) | 1.8M | 2.1M | **9.8M** | — | — | 4.7x |
615
+ | medium object (invalid) | 511K | 359K | **15.7M** | 2.9M | 7.5M | **44x** |
616
+ | large object (10 items) | 117K | 175K | **5.3M** | 5.7M | 1.2M | **30x** |
617
+ | large object (100 items) | 13K | 18K | **779K** | 1.3M | 127K | **42x** |
618
+ | readonly field (wrapper compiles away) | 3.0M | 6.6M | **15.7M** | — | — | 2.4x |
619
+ | readonly root object (rebuild + freeze) | 2.8M | 5.4M | **12.8M** | — | — | 2.4x |
620
+ | readonly array (delegates to Zod) | 3.8M | 4.2M | **4.1M** | — | — | 1.0x |
621
+ | recursive tree (7 nodes) | 564K | 994K | **7.9M** | 12.1M | 4.7M | 7.9x |
622
+ | recursive tree (121 nodes) | 31K | 55K | **783K** | 1.9M | 361K | **14x** |
623
+ | nested recursion (7 nodes) | 383K | 667K | **7.9M** | 11.2M | 2.9M | **12x** |
624
+ | nested recursion (121 nodes) | 23K | 41K | **817K** | 1.6M | 206K | **20x** |
625
+ | deeply nested object (243 leaves) | 11K | 27K | **803K** | 1.0M | 122K | **30x** |
626
+ | event log (combined) | 371K | 782K | **7.5M** | — | — | 9.6x |
627
+ | object with transform (zero-capture) | 1.1M | 2.0M | **7.2M** | — | — | 3.7x |
628
+ | array 10 × transform (zero-capture) | 122K | 203K | **4.2M** | — | — | **21x** |
629
+ | array 50 × transform (zero-capture) | 25K | 42K | **1.0M** | — | — | **25x** |
630
+ | object with captured transform | 1.2M | 8.2M | **16.2M** | — | — | 2.0x |
631
+ | object with captured refine (cross-field) | 1.4M | 2.2M | **11.2M** | — | — | 5.0x |
632
+ | object with superRefine (cross-field) | 1.4M | 2.2M | **9.3M** | — | — | 4.3x |
633
+ | coerced query object (valid) | 1.8M | 2.9M | **5.4M** | — | — | 1.9x |
634
+ | coerced query object (invalid) | 1.0M | 828K | **10.3M** | — | — | **12x** |
635
+ | preprocessed query object (valid) | 401K | 1.7M | **5.2M** | — | — | 3.0x |
636
+ | preprocessed query object (invalid) | 378K | 755K | **12.7M** | — | — | **17x** |
637
+ | stringbool config object (valid) | — | 2.8M | **6.3M** | — | — | 2.2x |
638
+ | stringbool config object (invalid) | — | 670K | **13.8M** | — | — | **21x** |
639
+ | custom/instanceof request (valid) | 965K | 3.1M | **10.4M** | — | — | 3.3x |
640
+ | custom/instanceof request (invalid) | 784K | 930K | **10.2M** | — | — | **11x** |
641
+ | disjoint object intersection (valid) | 1.4M | 1.6M | **9.8M** | — | — | 6.0x |
642
+ | disjoint object intersection (invalid) | 493K | 327K | **15.4M** | — | — | **47x** |
643
+
644
+ _ops/s, higher is better. `vp test bench` on an Apple M4 Max (zod 4.5.2, zod v3 3.23.8, typia 12, ajv 8),
645
+ best of three runs. The harness costs ~60 ns per iteration, so the fastest rows sit at that floor and gaps
624
646
  between the AOT columns there are noise, not real._
625
647
 
626
648
  Nested objects, arrays and recursive types gain the most. Rejection is fast because a failed
627
- `safeParse` defers building the error until `.error` is read.
649
+ `safeParse` defers building the error until `.error` is read. Zod 4.5 stopped capturing a stack trace on
650
+ that path too, so its own rejected-input rows are several times faster than 4.3's and the gap there is
651
+ narrower than it was.
628
652
 
629
653
  ```bash
630
654
  vp run benchmark # run locally
@@ -632,23 +656,27 @@ vp run benchmark # run locally
632
656
 
633
657
  ### Performance Architecture
634
658
 
635
- An eligible schema compiles to a **fast path** one `&&` chain validating the whole input with zero
636
- allocations, reused by `.is()` and `parse()` plus a **slow path** that collects errors, run only on
637
- failure and deferred until `.error` is read. A `z.object()` strips, so it instead compiles to a single
638
- pass that validates and rebuilds together, bailing on the first failure including the reshaping
639
- idioms (array size checks, `.refine()`, `.default()`, `.trim()`, `.transform()`).
659
+ An eligible schema compiles to a **fast path**, one `&&` chain validating the whole input with zero
660
+ allocations and reused by `.is()` and `parse()`, plus a **slow path** that collects errors, runs only on
661
+ failure, and is deferred until `.error` is read. A `z.object()` strips, so it instead compiles to a
662
+ single pass that validates and rebuilds together and bails on the first failure, covering the reshaping
663
+ idioms too (array size checks, `.refine()`, `.default()`, `.trim()`, `.transform()`).
640
664
 
641
665
  Regexes are pre-compiled with bounded repeats unrolled, checks run cheapest-first, discriminated unions
642
- dispatch through a jump table (plain tagged unions are auto-discriminated into it), and oversized check
643
- functions are split to stay within V8's optimizer budget. Stripping objects, native coercions,
666
+ dispatch through a `switch` on the tag (plain tagged unions are auto-discriminated into it, on the
667
+ stripping pass as well as the fast check), and oversized check functions are split to stay within V8's
668
+ optimizer budget. `z.email()` runs as a single linear scan instead of a backtracking regex, a record's
669
+ plain-object guard exits on one comparison for an ordinary object, and a case-insensitive `stringbool`
670
+ looks its input up verbatim before paying for `toLowerCase()`. Stripping objects, native coercions,
644
671
  `stringbool`, defaults, string rewrites, context-free preprocessors and synchronous transforms validate
645
672
  and build their output in one pass. An intersection of two objects with disjoint keys compiles to that
646
673
  same single pass over the merged shape, and `z.custom()` / `z.instanceof()` compile to a direct predicate
647
674
  call.
648
675
 
649
- Where success is cheaper to compile than failure, only the verdict and output are compiled: intersections
650
- and `custom` keep the original Zod schema to construct issues, so a rejection still reports exactly what
651
- Zod would — including an intersection's one-issue-per-side shape without slowing the hot path.
676
+ Where success is cheaper to compile than failure, only the verdict and output are compiled.
677
+ Intersections and `custom` keep the original Zod schema to construct issues, so a rejection still
678
+ reports exactly what Zod would, an intersection's one-issue-per-side shape included, without slowing the
679
+ hot path.
652
680
 
653
681
  ## Development
654
682
 
@@ -33,6 +33,17 @@ declare function rebuildsOutput(ir: SchemaIR): boolean;
33
33
  * size-gated `__fo_` split) to fix a shortcut that only the root takes.
34
34
  */
35
35
  declare function fastResultIsInput(ir: SchemaIR): boolean;
36
+ /**
37
+ * Does anything STRICTLY BELOW `ir` hand back a container needing a `__proto__`
38
+ * scrub?
39
+ *
40
+ * The root's by-reference shortcut can filter the value it returns
41
+ * (`data: __zcPs(input)`), but that only reaches the OUTER container — a nested
42
+ * one is never touched, because the shortcut does not walk. So a schema with a
43
+ * scrub-needing descendant keeps the eager slow walk, which scrubs at every
44
+ * level. Only the root's own scrub is shortcut-compatible.
45
+ */
46
+ declare function nestedNeedsProtoScrub(ir: SchemaIR): boolean;
36
47
  /**
37
48
  * Host the whole schema as `function NAME(input){…}` returning the built value
38
49
  * or the FAIL sentinel. Returns the function name, or null when the schema is
@@ -40,5 +51,5 @@ declare function fastResultIsInput(ir: SchemaIR): boolean;
40
51
  */
41
52
  declare function generateBuild(ir: SchemaIR, ctx: CodeGenContext): string | null;
42
53
  //#endregion
43
- export { fastResultIsInput, generateBuild, rebuildsOutput };
54
+ export { fastResultIsInput, generateBuild, nestedNeedsProtoScrub, rebuildsOutput };
44
55
  //# sourceMappingURL=build-path.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-path.d.ts","names":[],"sources":["../../../src/core/codegen/build-path.ts"],"mappings":";;;;iBA2JgB,eAAe,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,kBAAkB,IAAI;;;;;;iBA2GtB,cAAc,IAAI,UAAU,KAAK"}
1
+ {"version":3,"file":"build-path.d.ts","names":[],"sources":["../../../src/core/codegen/build-path.ts"],"mappings":";;;;iBAyLgB,eAAe,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,kBAAkB,IAAI;;;;;;;;;;;iBAmFtB,sBAAsB,IAAI;;;;;;iBAmD1B,cAAc,IAAI,UAAU,KAAK"}