zod-compiler 2.0.1 → 2.0.3

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 (71) hide show
  1. package/README.md +62 -58
  2. package/dist/cli/commands/check.d.ts.map +1 -1
  3. package/dist/cli/commands/check.js +5 -0
  4. package/dist/cli/commands/check.js.map +1 -1
  5. package/dist/core/codegen/build-path.d.ts.map +1 -1
  6. package/dist/core/codegen/build-path.js +107 -28
  7. package/dist/core/codegen/build-path.js.map +1 -1
  8. package/dist/core/codegen/context.d.ts +21 -11
  9. package/dist/core/codegen/context.d.ts.map +1 -1
  10. package/dist/core/codegen/context.js +15 -1
  11. package/dist/core/codegen/context.js.map +1 -1
  12. package/dist/core/codegen/fast-path.d.ts +3 -1
  13. package/dist/core/codegen/fast-path.d.ts.map +1 -1
  14. package/dist/core/codegen/fast-path.js +7 -4
  15. package/dist/core/codegen/fast-path.js.map +1 -1
  16. package/dist/core/codegen/index.js +4 -3
  17. package/dist/core/codegen/index.js.map +1 -1
  18. package/dist/core/codegen/issue-decls.d.ts +67 -5
  19. package/dist/core/codegen/issue-decls.d.ts.map +1 -1
  20. package/dist/core/codegen/issue-decls.js +72 -7
  21. package/dist/core/codegen/issue-decls.js.map +1 -1
  22. package/dist/core/codegen/schemas/array.d.ts.map +1 -1
  23. package/dist/core/codegen/schemas/array.js +3 -3
  24. package/dist/core/codegen/schemas/array.js.map +1 -1
  25. package/dist/core/codegen/schemas/default.d.ts +10 -0
  26. package/dist/core/codegen/schemas/default.d.ts.map +1 -1
  27. package/dist/core/codegen/schemas/default.js +11 -0
  28. package/dist/core/codegen/schemas/default.js.map +1 -1
  29. package/dist/core/codegen/schemas/effect.d.ts +13 -1
  30. package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
  31. package/dist/core/codegen/schemas/effect.js +26 -3
  32. package/dist/core/codegen/schemas/effect.js.map +1 -1
  33. package/dist/core/codegen/schemas/fallback.d.ts +17 -1
  34. package/dist/core/codegen/schemas/fallback.d.ts.map +1 -1
  35. package/dist/core/codegen/schemas/fallback.js +31 -15
  36. package/dist/core/codegen/schemas/fallback.js.map +1 -1
  37. package/dist/core/codegen/schemas/number.d.ts.map +1 -1
  38. package/dist/core/codegen/schemas/number.js +35 -35
  39. package/dist/core/codegen/schemas/number.js.map +1 -1
  40. package/dist/core/codegen/schemas/object.d.ts.map +1 -1
  41. package/dist/core/codegen/schemas/object.js +64 -29
  42. package/dist/core/codegen/schemas/object.js.map +1 -1
  43. package/dist/core/codegen/schemas/string.d.ts.map +1 -1
  44. package/dist/core/codegen/schemas/string.js +3 -3
  45. package/dist/core/codegen/schemas/string.js.map +1 -1
  46. package/dist/core/codegen/schemas/template-literal.d.ts.map +1 -1
  47. package/dist/core/codegen/schemas/template-literal.js +1 -1
  48. package/dist/core/codegen/schemas/template-literal.js.map +1 -1
  49. package/dist/core/extract/index.d.ts.map +1 -1
  50. package/dist/core/extract/index.js +2 -0
  51. package/dist/core/extract/index.js.map +1 -1
  52. package/dist/core/extract/zod-version.d.ts +78 -0
  53. package/dist/core/extract/zod-version.d.ts.map +1 -0
  54. package/dist/core/extract/zod-version.js +109 -0
  55. package/dist/core/extract/zod-version.js.map +1 -0
  56. package/dist/core/iife.d.ts +8 -0
  57. package/dist/core/iife.d.ts.map +1 -1
  58. package/dist/core/iife.js +8 -0
  59. package/dist/core/iife.js.map +1 -1
  60. package/dist/jit.d.ts.map +1 -1
  61. package/dist/jit.js +9 -1
  62. package/dist/jit.js.map +1 -1
  63. package/dist/runtime.d.ts +3 -0
  64. package/dist/runtime.js +4 -1
  65. package/dist/unplugin/hoist-compile.d.ts.map +1 -1
  66. package/dist/unplugin/hoist-compile.js +3 -1
  67. package/dist/unplugin/hoist-compile.js.map +1 -1
  68. package/dist/unplugin/transform.d.ts.map +1 -1
  69. package/dist/unplugin/transform.js +5 -0
  70. package/dist/unplugin/transform.js.map +1 -1
  71. package/package.json +1 -1
package/README.md CHANGED
@@ -2,12 +2,14 @@
2
2
 
3
3
  **Compile Zod schemas into zero-overhead validation functions at build time.**
4
4
 
5
- Keep your existing Zod schemas. Get **up to 42x faster** validation, and up to **47x** on rejected
5
+ Keep your existing Zod schemas. Get **up to 44x faster** validation, and up to **46x** on rejected
6
6
  input. No code changes required.
7
7
 
8
8
  Requires **Zod ≥ 4.5**. Compiled output reproduces 4.5's semantics exactly, down to code-point string
9
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.
10
+ zod-compiler 1.x for Zod 4.0–4.4. An older Zod is refused with an explicit error (the build plugin
11
+ and `jit()` warn once and leave the schemas as plain Zod) rather than compiled into validators that
12
+ disagree with it.
11
13
 
12
14
  - [What Gets Compiled](#what-gets-compiled)
13
15
  - [Schema Hoisting](#schema-hoisting)
@@ -26,7 +28,7 @@ loads pre-generated validators.
26
28
  | | zod-compiler (build plugins / CLI) | Zod `z.compile()` |
27
29
  | -------------------------------------- | -------------------------------------------- | --------------------------------------------- |
28
30
  | 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 |
31
+ | Reported validation speedup | Up to 44x; up to 46x on rejected input | ~9x in Zod's headline example |
30
32
  | Uses `new Function()` at runtime\* | No | Yes |
31
33
  | Cold start | Fast; the validator is already generated | Pays for code generation at startup/first use |
32
34
  | Strict CSP without `'unsafe-eval'` | Supported | Compilation is unavailable |
@@ -543,7 +545,7 @@ npx zod-compiler check src/schemas.ts --json --fail-under 80
543
545
 
544
546
  ## What Gets Compiled
545
547
 
546
- ### Fully Compiled (up to 42x faster)
548
+ ### Fully Compiled (up to 44x faster)
547
549
 
548
550
  Every Zod type except the fallbacks below: all primitives, `object` / `strictObject` / `looseObject`,
549
551
  `array`, `tuple`, `record`, `set`, `map`, `union`, `discriminatedUnion`, `intersection`, `pipe`,
@@ -577,11 +579,11 @@ not the whole object. Run `zod-compiler check` to see what compiled.
577
579
  Compiled validators match Zod on verdicts, output data and error messages, including issue ordering.
578
580
  Three things differ by design:
579
581
 
580
- | Behavior | Zod | zod-compiler |
581
- | ------------------------- | ----------------------------------------------- | ------------------------------------------------------- |
582
- | Record key iteration | Own enumerable keys, symbols included | Own enumerable **string** keys only |
583
- | Container output identity | A fresh array / set / map / object | The input container, by reference (array holes survive) |
584
- | Per-call parse params | `safeParse(x, { error, reportInput })` honoured | Ignored; global `z.config()` maps still apply |
582
+ | Behavior | Zod | zod-compiler |
583
+ | ------------------------- | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
584
+ | Record key iteration | Own enumerable keys, symbols included | Own enumerable **string** keys only |
585
+ | Container output identity | A fresh array / set / map / object | The input container, by reference (array holes and the input's key order survive); a rebuilt object is fresh and in zod's key order |
586
+ | Per-call parse params | `safeParse(x, { error, reportInput })` honoured | Ignored; global `z.config()` maps still apply |
585
587
 
586
588
  Schema-level `error` and `z.config()` maps are unaffected; for a per-call map use
587
589
  `z.safeParse(Schema, x, params)`.
@@ -594,52 +596,52 @@ Schema-level `error` and `z.config()` maps are unaffected; for a per-call map us
594
596
 
595
597
  | Scenario | Zod v3 | Zod v4 | **zod-compiler** | Typia | AJV | vs Zod v4 |
596
598
  | ----------------------------------------------- | ------ | ------ | ---------------- | ----- | ----- | --------- |
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** |
599
+ | simple string | 12.6M | 15.6M | **16.8M** | 17.2M | 17.4M | 1.1x |
600
+ | string (min/max) | 12.4M | 7.4M | **17.4M** | 17.6M | 15.5M | 2.4x |
601
+ | number (int+positive) | 11.8M | 9.4M | **17.2M** | 16.3M | 17.4M | 1.8x |
602
+ | enum | 12.1M | 14.7M | **17.4M** | 17.4M | 17.2M | 1.2x |
603
+ | bigint (min/max) | 11.2M | 7.9M | **16.7M** | — | — | 2.1x |
604
+ | tuple [string, int, bool] | 5.6M | 7.3M | **17.4M** | 16.4M | 15.7M | 2.4x |
605
+ | record\<string, number\> | 3.1M | 2.6M | **12.7M** | 12.0M | 15.2M | 5.0x |
606
+ | set\<string\> (5 items) | 3.7M | 2.3M | **15.2M** | — | — | 6.6x |
607
+ | set\<string\> (20 items) | 1.3M | 680K | **12.1M** | — | — | **18x** |
608
+ | map\<string, number\> (5 entries) | 2.0M | 1.3M | **13.2M** | — | — | **10x** |
609
+ | map\<string, number\> (20 entries) | 637K | 347K | **8.5M** | — | — | **24x** |
610
+ | pipe (non-transform) | 8.7M | 4.7M | **17.3M** | — | — | 3.6x |
611
+ | discriminatedUnion (3 variants) | 3.3M | 5.2M | **17.2M** | 16.1M | 7.8M | 3.3x |
612
+ | discriminatedUnion (8 variants, rotating) | 2.6M | 4.4M | **10.3M** | — | — | 2.3x |
613
+ | plain union of 8 tagged objects (auto-discrim.) | 356K | 1.3M | **10.2M** | — | — | 7.7x |
614
+ | strict object (DB row) | 1.8M | 3.0M | **11.3M** | — | — | 3.8x |
615
+ | medium object (valid) | 1.9M | 2.2M | **9.4M** | 11.2M | 7.5M | 4.2x |
616
+ | medium object (extra keys stripped) | 1.8M | 2.0M | **9.8M** | — | — | 4.9x |
617
+ | medium object (invalid) | 534K | 372K | **15.5M** | 2.9M | 7.5M | **42x** |
618
+ | large object (10 items) | 120K | 163K | **5.1M** | 5.9M | 1.2M | **31x** |
619
+ | large object (100 items) | 13K | 17K | **766K** | 1.3M | 127K | **44x** |
620
+ | readonly field (wrapper compiles away) | 3.0M | 6.6M | **16.7M** | — | — | 2.5x |
621
+ | readonly root object (rebuild + freeze) | 2.8M | 5.5M | **12.9M** | — | — | 2.3x |
622
+ | readonly array (delegates to Zod) | 3.9M | 4.3M | **4.2M** | — | — | 1.0x |
623
+ | recursive tree (7 nodes) | 581K | 1.0M | **7.5M** | 11.6M | 4.7M | 7.4x |
624
+ | recursive tree (121 nodes) | 31K | 57K | **777K** | 1.9M | 371K | **14x** |
625
+ | nested recursion (7 nodes) | 395K | 683K | **7.9M** | 11.2M | 3.0M | **12x** |
626
+ | nested recursion (121 nodes) | 24K | 42K | **805K** | 1.6M | 204K | **19x** |
627
+ | deeply nested object (243 leaves) | 11K | 27K | **825K** | 1.0M | 125K | **30x** |
628
+ | event log (combined) | 363K | 795K | **7.3M** | — | — | 9.2x |
629
+ | object with transform (zero-capture) | 1.1M | 1.9M | **6.7M** | — | — | 3.5x |
630
+ | array 10 × transform (zero-capture) | 121K | 206K | **4.1M** | — | — | **20x** |
631
+ | array 50 × transform (zero-capture) | 26K | 41K | **1.0M** | — | — | **25x** |
632
+ | object with captured transform | 1.2M | 7.9M | **15.9M** | — | — | 2.0x |
633
+ | object with captured refine (cross-field) | 1.4M | 2.2M | **11.5M** | — | — | 5.2x |
634
+ | object with superRefine (cross-field) | 1.4M | 2.1M | **9.2M** | — | — | 4.3x |
635
+ | coerced query object (valid) | 1.8M | 2.9M | **5.3M** | — | — | 1.9x |
636
+ | coerced query object (invalid) | 1.0M | 826K | **10.3M** | — | — | **12x** |
637
+ | preprocessed query object (valid) | 433K | 1.7M | **5.3M** | — | — | 3.2x |
638
+ | preprocessed query object (invalid) | 392K | 782K | **12.3M** | — | — | **16x** |
637
639
  | 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** |
640
+ | stringbool config object (invalid) | — | 682K | **13.2M** | — | — | **19x** |
641
+ | custom/instanceof request (valid) | 965K | 3.1M | **9.9M** | — | — | 3.2x |
642
+ | custom/instanceof request (invalid) | 786K | 928K | **13.2M** | — | — | **14x** |
643
+ | disjoint object intersection (valid) | 1.4M | 1.6M | **9.5M** | — | — | 5.9x |
644
+ | disjoint object intersection (invalid) | 488K | 331K | **15.3M** | — | — | **46x** |
643
645
 
644
646
  _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
647
  best of three runs. The harness costs ~60 ns per iteration, so the fastest rows sit at that floor and gaps
@@ -662,10 +664,12 @@ failure, and is deferred until `.error` is read. A `z.object()` strips, so it in
662
664
  single pass that validates and rebuilds together and bails on the first failure, covering the reshaping
663
665
  idioms too (array size checks, `.refine()`, `.default()`, `.trim()`, `.transform()`).
664
666
 
665
- Regexes are pre-compiled with bounded repeats unrolled, checks run cheapest-first, discriminated unions
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
667
+ Regexes are pre-compiled with bounded repeats unrolled, checks run cheapest-first on both passes (so a
668
+ payload with a wrong boolean is rejected before its email is scanned, whichever was declared first),
669
+ discriminated unions dispatch through a `switch` on the tag (plain tagged unions are auto-discriminated
670
+ into it, on the stripping pass as well as the fast check, and the selected option re-checks neither
671
+ object-ness nor the tag), and oversized check functions are split to stay within V8's optimizer budget.
672
+ `.is()` stays a zero-allocation predicate on schemas with `.default()` fields. `z.email()` runs as a single linear scan instead of a backtracking regex, a record's
669
673
  plain-object guard exits on one comparison for an ordinary object, and a case-insensitive `stringbool`
670
674
  looks its input up verbatim before paying for `toLowerCase()`. Stripping objects, native coercions,
671
675
  `stringbool`, defaults, string rewrites, context-free preprocessors and synchronous transforms validate
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","names":[],"sources":["../../../src/cli/commands/check.ts"],"mappings":";UAkFiB;EACf;EACA;EACA;EACA;EACA;;iBAGoB,SAAS,SAAS,eAAe"}
1
+ {"version":3,"file":"check.d.ts","names":[],"sources":["../../../src/cli/commands/check.ts"],"mappings":";UAmFiB;EACf;EACA;EACA;EACA;EACA;;iBAGoB,SAAS,SAAS,eAAe"}
@@ -1,4 +1,5 @@
1
1
  import { discoverSchemas } from "../../discovery.js";
2
+ import { isUnsupportedZodVersionError } from "../../core/extract/zod-version.js";
2
3
  import { extractSchema } from "../../core/extract/index.js";
3
4
  import { getErrorMessage } from "../errors.js";
4
5
  import { createColors, logger } from "../logger.js";
@@ -86,6 +87,10 @@ async function runCheck(options) {
86
87
  try {
87
88
  ir = extractSchema(s.schema, []);
88
89
  } catch (e) {
90
+ if (isUnsupportedZodVersionError(e)) {
91
+ logger.error(getErrorMessage(e));
92
+ process.exit(1);
93
+ }
89
94
  err(`${c.red}error${c.reset} Failed to extract "${s.exportName}" in ${relPath}: ${getErrorMessage(e)}`);
90
95
  continue;
91
96
  }
@@ -1 +1 @@
1
- {"version":3,"file":"check.js","names":[],"sources":["../../../src/cli/commands/check.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport type { DiagnosticNode, DiagnosticResult } from \"../../core/diagnostic.js\";\nimport { diagnoseSchema } from \"../../core/diagnostic.js\";\nimport type { RefEntry } from \"../../core/extract/index.js\";\nimport { extractSchema } from \"../../core/extract/index.js\";\nimport type { DiscoveredSchema, SchemaIR } from \"../../core/types.js\";\nimport { discoverSchemas } from \"../../discovery.js\";\nimport { getErrorMessage } from \"../errors.js\";\nimport type { Colors } from \"../logger.js\";\nimport { createColors, logger } from \"../logger.js\";\n\n// ─── Tree Rendering ─────────────────────────────────────────────────────────\n\nfunction renderTree(node: DiagnosticNode, c: Colors, prefix = \"\", isLast = true): string[] {\n const lines: string[] = [];\n const connector = prefix === \"\" ? \"\" : isLast ? \"└─ \" : \"├─ \";\n const childPrefix = prefix === \"\" ? \"\" : prefix + (isLast ? \" \" : \"│ \");\n\n const statusIcon = node.status === \"compiled\" ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;\n const typeLabel = `${c.bold}${node.type}${c.reset}`;\n const pathLabel = node.path && node.path !== \"(root)\" ? ` ${c.dim}${node.path}${c.reset}` : \"\";\n const reasonLabel =\n node.status === \"fallback\" && node.reason ? ` ${c.yellow}(${node.reason})${c.reset}` : \"\";\n\n lines.push(`${prefix}${connector}${statusIcon} ${typeLabel}${pathLabel}${reasonLabel}`);\n\n if (node.status === \"fallback\" && node.hint) {\n const hintPrefix = `${childPrefix} `;\n lines.push(`${hintPrefix}${c.dim}hint: ${node.hint}${c.reset}`);\n }\n\n for (const [i, child] of node.children.entries()) {\n const last = i === node.children.length - 1;\n lines.push(...renderTree(child, c, childPrefix, last));\n }\n\n return lines;\n}\n\n// ─── JSON Output ────────────────────────────────────────────────────────────\n\ninterface JsonSchemaReport {\n exportName: string;\n coverage: { total: number; compilable: number; percent: number };\n fastPath: { eligible: boolean; blocker?: string };\n fallbacks: { reason: string; path: string; hint: string }[];\n}\n\ninterface JsonReport {\n file: string;\n schemas: JsonSchemaReport[];\n}\n\nfunction buildJsonReport(\n filePath: string,\n results: { exportName: string; diagnostic: DiagnosticResult }[],\n): JsonReport {\n return {\n file: filePath,\n schemas: results.map(({ exportName, diagnostic }) => {\n const report: JsonSchemaReport = {\n exportName,\n coverage: {\n total: diagnostic.total,\n compilable: diagnostic.compilable,\n percent: diagnostic.coveragePct,\n },\n fastPath: { eligible: diagnostic.fastPathEligible },\n fallbacks: diagnostic.fallbacks,\n };\n if (diagnostic.fastPathBlocker) {\n report.fastPath.blocker = diagnostic.fastPathBlocker;\n }\n return report;\n }),\n };\n}\n\n// ─── Command ────────────────────────────────────────────────────────────────\n\nexport interface CheckOptions {\n inputs: string[];\n json: boolean;\n failUnder: number | undefined;\n noColor: boolean;\n autoDiscover: boolean;\n}\n\nexport async function runCheck(options: CheckOptions): Promise<void> {\n // oxlint-disable-next-line no-console -- CLI output\n const out = console.log;\n // oxlint-disable-next-line no-console -- CLI output\n const err = console.error;\n const colorEnabled = !options.noColor && process.stdout.isTTY === true;\n const c = createColors(colorEnabled);\n const files: string[] = [];\n\n for (const input of options.inputs) {\n const absPath = path.resolve(input);\n const stat = await fs.promises.stat(absPath).catch(() => null);\n\n if (!stat) {\n logger.error(`File not found: ${input}`);\n process.exit(1);\n }\n\n if (stat.isDirectory()) {\n logger.error(\"check command expects file paths, not directories.\");\n process.exit(1);\n }\n\n files.push(absPath);\n }\n\n const allJsonReports: JsonReport[] = [];\n let minCoverage = 100;\n let schemasProcessed = 0;\n\n for (const filePath of files) {\n const relPath = path.relative(process.cwd(), filePath);\n\n let schemas: DiscoveredSchema[];\n try {\n schemas = await discoverSchemas(filePath, { autoDiscover: options.autoDiscover });\n } catch (e) {\n logger.error(`Failed to load ${relPath}: ${getErrorMessage(e)}`);\n process.exit(1);\n }\n\n if (schemas.length === 0) {\n const hint = options.autoDiscover\n ? \"no Zod schemas exported\"\n : \"no compile() calls found (use --schemas auto to scan plain Zod exports)\";\n logger.warn(`${relPath}: ${hint}`);\n continue;\n }\n\n const results: { exportName: string; diagnostic: DiagnosticResult }[] = [];\n\n for (const s of schemas) {\n let ir: SchemaIR;\n try {\n // Collect refs exactly as the compile pipeline does. A callback that\n // captures is compiled by REFERENCE through an `__rf[]` entry, which\n // only happens when a ref table is present — extracting without one\n // reports every captured refine/transform and every superRefine as a\n // fallback, so `check` claimed 0% on schemas `generate` compiles in\n // full and told the user to rewrite code that was already fast.\n const refEntries: RefEntry[] = [];\n ir = extractSchema(s.schema, refEntries);\n } catch (e) {\n err(\n `${c.red}error${c.reset} Failed to extract \"${s.exportName}\" in ${relPath}: ${getErrorMessage(e)}`,\n );\n continue;\n }\n const diagnostic = diagnoseSchema(ir);\n results.push({ exportName: s.exportName, diagnostic });\n schemasProcessed++;\n\n if (diagnostic.coveragePct < minCoverage) {\n minCoverage = diagnostic.coveragePct;\n }\n }\n\n if (options.json) {\n allJsonReports.push(buildJsonReport(relPath, results));\n } else {\n out(`\\n${c.bold}${relPath}${c.reset}`);\n\n for (const { exportName, diagnostic } of results) {\n const pctColor = diagnostic.coveragePct === 100 ? c.green : c.yellow;\n const fpLabel = diagnostic.fastPathEligible\n ? `${c.green}Fast Path: eligible${c.reset}`\n : `${c.yellow}Fast Path: ineligible${c.reset}${c.dim} (${diagnostic.fastPathBlocker})${c.reset}`;\n\n out(\n `\\n ${c.cyan}${exportName}${c.reset} — ${pctColor}${diagnostic.coveragePct}%${c.reset} compiled (${diagnostic.compilable}/${diagnostic.total} nodes) | ${fpLabel}`,\n );\n\n const treeLines = renderTree(diagnostic.root, c, \" \");\n for (const line of treeLines) {\n out(line);\n }\n\n if (diagnostic.fallbacks.length > 0) {\n out(`\\n ${c.yellow}Fallbacks:${c.reset}`);\n for (const fb of diagnostic.fallbacks) {\n out(` ${c.red}✗${c.reset} ${fb.path} — ${fb.reason}`);\n out(` ${c.dim}${fb.hint}${c.reset}`);\n }\n }\n }\n }\n }\n\n if (options.json) {\n out(JSON.stringify(allJsonReports, null, 2));\n }\n\n if (options.failUnder !== undefined) {\n if (schemasProcessed === 0) {\n logger.error(\"No schemas were analyzed — cannot satisfy --fail-under\");\n process.exit(1);\n }\n if (minCoverage < options.failUnder) {\n logger.error(`Coverage ${minCoverage}% is below threshold ${options.failUnder}%`);\n process.exit(1);\n }\n } else if (schemasProcessed === 0) {\n process.exit(1);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"check.js","names":[],"sources":["../../../src/cli/commands/check.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport process from \"node:process\";\nimport type { DiagnosticNode, DiagnosticResult } from \"../../core/diagnostic.js\";\nimport { diagnoseSchema } from \"../../core/diagnostic.js\";\nimport type { RefEntry } from \"../../core/extract/index.js\";\nimport { extractSchema } from \"../../core/extract/index.js\";\nimport { isUnsupportedZodVersionError } from \"../../core/extract/zod-version.js\";\nimport type { DiscoveredSchema, SchemaIR } from \"../../core/types.js\";\nimport { discoverSchemas } from \"../../discovery.js\";\nimport { getErrorMessage } from \"../errors.js\";\nimport type { Colors } from \"../logger.js\";\nimport { createColors, logger } from \"../logger.js\";\n\n// ─── Tree Rendering ─────────────────────────────────────────────────────────\n\nfunction renderTree(node: DiagnosticNode, c: Colors, prefix = \"\", isLast = true): string[] {\n const lines: string[] = [];\n const connector = prefix === \"\" ? \"\" : isLast ? \"└─ \" : \"├─ \";\n const childPrefix = prefix === \"\" ? \"\" : prefix + (isLast ? \" \" : \"│ \");\n\n const statusIcon = node.status === \"compiled\" ? `${c.green}✓${c.reset}` : `${c.red}✗${c.reset}`;\n const typeLabel = `${c.bold}${node.type}${c.reset}`;\n const pathLabel = node.path && node.path !== \"(root)\" ? ` ${c.dim}${node.path}${c.reset}` : \"\";\n const reasonLabel =\n node.status === \"fallback\" && node.reason ? ` ${c.yellow}(${node.reason})${c.reset}` : \"\";\n\n lines.push(`${prefix}${connector}${statusIcon} ${typeLabel}${pathLabel}${reasonLabel}`);\n\n if (node.status === \"fallback\" && node.hint) {\n const hintPrefix = `${childPrefix} `;\n lines.push(`${hintPrefix}${c.dim}hint: ${node.hint}${c.reset}`);\n }\n\n for (const [i, child] of node.children.entries()) {\n const last = i === node.children.length - 1;\n lines.push(...renderTree(child, c, childPrefix, last));\n }\n\n return lines;\n}\n\n// ─── JSON Output ────────────────────────────────────────────────────────────\n\ninterface JsonSchemaReport {\n exportName: string;\n coverage: { total: number; compilable: number; percent: number };\n fastPath: { eligible: boolean; blocker?: string };\n fallbacks: { reason: string; path: string; hint: string }[];\n}\n\ninterface JsonReport {\n file: string;\n schemas: JsonSchemaReport[];\n}\n\nfunction buildJsonReport(\n filePath: string,\n results: { exportName: string; diagnostic: DiagnosticResult }[],\n): JsonReport {\n return {\n file: filePath,\n schemas: results.map(({ exportName, diagnostic }) => {\n const report: JsonSchemaReport = {\n exportName,\n coverage: {\n total: diagnostic.total,\n compilable: diagnostic.compilable,\n percent: diagnostic.coveragePct,\n },\n fastPath: { eligible: diagnostic.fastPathEligible },\n fallbacks: diagnostic.fallbacks,\n };\n if (diagnostic.fastPathBlocker) {\n report.fastPath.blocker = diagnostic.fastPathBlocker;\n }\n return report;\n }),\n };\n}\n\n// ─── Command ────────────────────────────────────────────────────────────────\n\nexport interface CheckOptions {\n inputs: string[];\n json: boolean;\n failUnder: number | undefined;\n noColor: boolean;\n autoDiscover: boolean;\n}\n\nexport async function runCheck(options: CheckOptions): Promise<void> {\n // oxlint-disable-next-line no-console -- CLI output\n const out = console.log;\n // oxlint-disable-next-line no-console -- CLI output\n const err = console.error;\n const colorEnabled = !options.noColor && process.stdout.isTTY === true;\n const c = createColors(colorEnabled);\n const files: string[] = [];\n\n for (const input of options.inputs) {\n const absPath = path.resolve(input);\n const stat = await fs.promises.stat(absPath).catch(() => null);\n\n if (!stat) {\n logger.error(`File not found: ${input}`);\n process.exit(1);\n }\n\n if (stat.isDirectory()) {\n logger.error(\"check command expects file paths, not directories.\");\n process.exit(1);\n }\n\n files.push(absPath);\n }\n\n const allJsonReports: JsonReport[] = [];\n let minCoverage = 100;\n let schemasProcessed = 0;\n\n for (const filePath of files) {\n const relPath = path.relative(process.cwd(), filePath);\n\n let schemas: DiscoveredSchema[];\n try {\n schemas = await discoverSchemas(filePath, { autoDiscover: options.autoDiscover });\n } catch (e) {\n logger.error(`Failed to load ${relPath}: ${getErrorMessage(e)}`);\n process.exit(1);\n }\n\n if (schemas.length === 0) {\n const hint = options.autoDiscover\n ? \"no Zod schemas exported\"\n : \"no compile() calls found (use --schemas auto to scan plain Zod exports)\";\n logger.warn(`${relPath}: ${hint}`);\n continue;\n }\n\n const results: { exportName: string; diagnostic: DiagnosticResult }[] = [];\n\n for (const s of schemas) {\n let ir: SchemaIR;\n try {\n // Collect refs exactly as the compile pipeline does. A callback that\n // captures is compiled by REFERENCE through an `__rf[]` entry, which\n // only happens when a ref table is present — extracting without one\n // reports every captured refine/transform and every superRefine as a\n // fallback, so `check` claimed 0% on schemas `generate` compiles in\n // full and told the user to rewrite code that was already fast.\n const refEntries: RefEntry[] = [];\n ir = extractSchema(s.schema, refEntries);\n } catch (e) {\n // An unsupported zod fails every schema identically, and nothing after\n // it can compile: report it once and stop, rather than once per export\n // followed by a coverage table that says 0% everywhere.\n if (isUnsupportedZodVersionError(e)) {\n logger.error(getErrorMessage(e));\n process.exit(1);\n }\n err(\n `${c.red}error${c.reset} Failed to extract \"${s.exportName}\" in ${relPath}: ${getErrorMessage(e)}`,\n );\n continue;\n }\n const diagnostic = diagnoseSchema(ir);\n results.push({ exportName: s.exportName, diagnostic });\n schemasProcessed++;\n\n if (diagnostic.coveragePct < minCoverage) {\n minCoverage = diagnostic.coveragePct;\n }\n }\n\n if (options.json) {\n allJsonReports.push(buildJsonReport(relPath, results));\n } else {\n out(`\\n${c.bold}${relPath}${c.reset}`);\n\n for (const { exportName, diagnostic } of results) {\n const pctColor = diagnostic.coveragePct === 100 ? c.green : c.yellow;\n const fpLabel = diagnostic.fastPathEligible\n ? `${c.green}Fast Path: eligible${c.reset}`\n : `${c.yellow}Fast Path: ineligible${c.reset}${c.dim} (${diagnostic.fastPathBlocker})${c.reset}`;\n\n out(\n `\\n ${c.cyan}${exportName}${c.reset} — ${pctColor}${diagnostic.coveragePct}%${c.reset} compiled (${diagnostic.compilable}/${diagnostic.total} nodes) | ${fpLabel}`,\n );\n\n const treeLines = renderTree(diagnostic.root, c, \" \");\n for (const line of treeLines) {\n out(line);\n }\n\n if (diagnostic.fallbacks.length > 0) {\n out(`\\n ${c.yellow}Fallbacks:${c.reset}`);\n for (const fb of diagnostic.fallbacks) {\n out(` ${c.red}✗${c.reset} ${fb.path} — ${fb.reason}`);\n out(` ${c.dim}${fb.hint}${c.reset}`);\n }\n }\n }\n }\n }\n\n if (options.json) {\n out(JSON.stringify(allJsonReports, null, 2));\n }\n\n if (options.failUnder !== undefined) {\n if (schemasProcessed === 0) {\n logger.error(\"No schemas were analyzed — cannot satisfy --fail-under\");\n process.exit(1);\n }\n if (minCoverage < options.failUnder) {\n logger.error(`Coverage ${minCoverage}% is below threshold ${options.failUnder}%`);\n process.exit(1);\n }\n } else if (schemasProcessed === 0) {\n process.exit(1);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"build-path.d.ts","names":[],"sources":["../../../src/core/codegen/build-path.ts"],"mappings":";;;;iBAuMgB,eAAe,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,kBAAkB,IAAI;;;;;;;;;;;iBAmFtB,sBAAsB,IAAI;;;;;;iBAmD1B,cAAc,IAAI,UAAU,KAAK"}
@@ -1,8 +1,8 @@
1
1
  import { declareFastTemps, emitEffectCallable, emitEffectFn, emitPooledConstant, emitRuntimeHelper, emitTemp, escapeString, hasMutation, keyMembershipTest, literalToJs, needsProtoScrub, outputAlwaysDefined, rejectsUndefined, tupleRewritesShortInput } from "./context.js";
2
- import { ZC_HOP_DECL, ZC_PLAIN_DECL, ZC_PROTO_SCRUB_DECL } from "./issue-decls.js";
2
+ import { ZC_ASYNC_DECL, ZC_HOP_DECL, ZC_PLAIN_DECL, ZC_PROTO_SCRUB_DECL } from "./issue-decls.js";
3
3
  import { defaultValueExpr, needsPostInnerDefault } from "./schemas/default.js";
4
4
  import { detectUnionDiscriminator } from "./schemas/discriminated-union.js";
5
- import { estimateFastCost, predictedInlineSize } from "./fast-size.js";
5
+ import { estimateFastCost, orderByRuntimeCost, predictedInlineSize } from "./fast-size.js";
6
6
  import { parsedProperties } from "./schemas/object.js";
7
7
  import { innerAppliesDefaultOnUndefined } from "./schemas/optional.js";
8
8
  import { fastStringCheck } from "./schemas/string.js";
@@ -220,7 +220,11 @@ function emitFailSentinel(ctx) {
220
220
  * and 354 KB on the deep fixtures — far past the bytecode size where V8 stops
221
221
  * running TurboFan on it, which would forfeit the speed this path exists for.
222
222
  */
223
- function build(ir, input, g) {
223
+ function build(ir, input, caller) {
224
+ const g = caller.discSkipKey !== void 0 && ir.type !== "object" && ir.type !== "readonly" && ir.type !== "zodDelegate" ? {
225
+ ...caller,
226
+ discSkipKey: void 0
227
+ } : caller;
224
228
  if (ir.type === "recursiveRef") return buildRecursiveCall(ir.refId ?? 0, input, g);
225
229
  if (ir.type === "recursionTarget") return buildRecursionTarget(ir, input, g);
226
230
  if (!g.rebuilds.has(ir)) return passthrough(ir, input, g);
@@ -246,8 +250,56 @@ function build(ir, input, g) {
246
250
  if (out !== null) g.scope.used = before + out.code.length;
247
251
  return out;
248
252
  }
253
+ /**
254
+ * The order an object's properties are validated in: cheapest first, as the
255
+ * fast path orders its conjuncts (see estimateRuntimeCost), so rejected input
256
+ * is decided by a type guard rather than by the most expensive check that
257
+ * happens to be declared first — `safeParse` on a payload with a wrong boolean
258
+ * used to cost the email scan before it looked at the boolean. Accepted input
259
+ * runs every property whatever the order, and the output literal is assembled
260
+ * in shape order regardless, so a passing parse is unchanged.
261
+ *
262
+ * Properties whose parse can be OBSERVED to run are pinned: they keep their
263
+ * declaration order among themselves and run after every free property, so
264
+ * two transforms sharing state still fire in zod's order on a successful
265
+ * parse, and a rejected one fires neither (a failing parse never guaranteed
266
+ * that its effects ran — this pass bails at the first bad check). What counts
267
+ * as observable is user code and value construction: a transform or
268
+ * preprocess, a `.default()` (a function default is invoked on read), a
269
+ * `.catch()`, a coercion (`Number(x)` reaches a `valueOf`), an overwrite, a
270
+ * delegate, and a recursion edge, whose target is not inspected. A `.refine()`
271
+ * is not pinned — it is a predicate, and the fast path already reorders it.
272
+ */
273
+ function orderedForBuild(properties, ctx) {
274
+ const pinned = properties.filter(([, propIR]) => orderSensitive(propIR));
275
+ if (pinned.length === properties.length) return properties;
276
+ return [...orderByRuntimeCost(properties.filter(([, propIR]) => !orderSensitive(propIR)), ([, propIR]) => propIR, ctx), ...pinned];
277
+ }
278
+ /** Does running this subtree do anything a caller could observe besides the verdict? */
279
+ function orderSensitive(ir) {
280
+ switch (ir.type) {
281
+ case "effect":
282
+ case "default":
283
+ case "catch":
284
+ case "fallback":
285
+ case "zodDelegate":
286
+ case "recursiveRef":
287
+ case "recursionTarget": return true;
288
+ case "string":
289
+ if (ir.coerce === true || ir.checks.some((c) => c.kind === "overwrite_effect")) return true;
290
+ break;
291
+ case "number":
292
+ case "boolean":
293
+ case "bigint":
294
+ case "date":
295
+ if (ir.coerce === true) return true;
296
+ break;
297
+ default: break;
298
+ }
299
+ return children(ir).some(orderSensitive);
300
+ }
249
301
  /** Host `ir`'s build in its own function over a fresh parameter; returns its name. */
250
- function hostBuild(ir, g) {
302
+ function hostBuild(ir, g, discSkipKey) {
251
303
  const param = emitTemp(g.ctx, "bp");
252
304
  const scope = {
253
305
  temps: [],
@@ -256,7 +308,8 @@ function hostBuild(ir, g) {
256
308
  const inner = build(ir, param, {
257
309
  ...g,
258
310
  extractable: false,
259
- scope
311
+ scope,
312
+ discSkipKey
260
313
  });
261
314
  if (inner === null) return null;
262
315
  const name = emitTemp(g.ctx, "vb");
@@ -415,7 +468,7 @@ function buildDispatch(discriminator, cases, options, input, g) {
415
468
  if (fn === void 0) {
416
469
  const option = options[index];
417
470
  if (option === void 0) return null;
418
- const hosted = g.rebuilds.has(option) ? hostBuild(option, g) : hostPassthrough(option, g);
471
+ const hosted = g.rebuilds.has(option) ? hostBuild(option, g, discriminator) : hostPassthrough(option, g, discriminator);
419
472
  if (hosted === null) return null;
420
473
  fn = hosted;
421
474
  hostedByOption.set(index, fn);
@@ -432,13 +485,13 @@ function buildDispatch(discriminator, cases, options, input, g) {
432
485
  * Host a non-rebuilding option as `value-or-FAIL`, so a union can probe it with
433
486
  * the same protocol as a rebuilding one.
434
487
  */
435
- function hostPassthrough(ir, g) {
488
+ function hostPassthrough(ir, g, discSkipKey) {
436
489
  const param = emitTemp(g.ctx, "bp");
437
490
  const scope = {
438
491
  temps: [],
439
492
  used: 0
440
493
  };
441
- const expr = generateFast(ir, createFastGen(param, g.ctx, true, scope));
494
+ const expr = generateFast(ir, createFastGen(param, g.ctx, true, scope, discSkipKey));
442
495
  if (expr === null) return null;
443
496
  const name = emitTemp(g.ctx, "vp");
444
497
  g.ctx.preamble.push(`function ${name}(${param}){${declareFastTemps(scope)}return ${expr === "true" ? param : `(${expr})?${param}:${g.fail}`};}`);
@@ -451,7 +504,7 @@ function hostPassthrough(ir, g) {
451
504
  * key is present, so the ordinary value is still returned by reference.
452
505
  */
453
506
  function passthrough(ir, input, g) {
454
- const expr = generateFast(ir, createFastGen(input, g.ctx, true, g.scope));
507
+ const expr = generateFast(ir, createFastGen(input, g.ctx, true, g.scope, g.discSkipKey));
455
508
  if (expr === null) return null;
456
509
  const guard = expr === "true" ? "" : `if(!(${expr}))return ${g.fail};`;
457
510
  if (!needsProtoScrub(ir)) return {
@@ -496,26 +549,41 @@ function buildObject(ir, input, g) {
496
549
  const nonoptional = new Set(ir.nonoptionalKeys ?? []);
497
550
  const refines = ir.checks ?? [];
498
551
  if (refines.some((check) => check.kind !== "refine_effect")) return null;
499
- let code = `if(typeof ${input}!=="object"||${input}===null||Array.isArray(${input}))return ${g.fail};`;
552
+ const skipKey = g.discSkipKey;
553
+ const childGen = skipKey === void 0 ? g : {
554
+ ...g,
555
+ discSkipKey: void 0
556
+ };
557
+ let code = skipKey === void 0 ? `if(typeof ${input}!=="object"||${input}===null||Array.isArray(${input}))return ${g.fail};` : "";
500
558
  if (ir.strict === true) {
501
559
  const keyVar = local(g, "bk");
502
560
  code += `for(${keyVar} in ${input}){if(!(${keyMembershipTest(g.ctx, Object.keys(ir.properties), keyVar)}))return ${g.fail};}`;
503
561
  }
504
- const slots = [];
505
- for (const [key, propIR] of parsedProperties(ir)) {
562
+ const properties = parsedProperties(ir);
563
+ const built = /* @__PURE__ */ new Map();
564
+ for (const [key, propIR] of orderedForBuild(properties, g.ctx)) {
506
565
  const keyStr = escapeString(key);
507
566
  const slot = local(g, "bv");
567
+ if (key === skipKey && !g.rebuilds.has(propIR)) {
568
+ code += `${slot}=${input}[${keyStr}];`;
569
+ built.set(key, {
570
+ code: "",
571
+ value: slot
572
+ });
573
+ continue;
574
+ }
508
575
  if (nonoptional.has(key)) code += `if(!(${keyStr} in ${input}))return ${g.fail};`;
509
576
  code += `${slot}=${input}[${keyStr}];`;
510
- const propBuilt = build(propIR, slot, g);
577
+ const propBuilt = build(propIR, slot, childGen);
511
578
  if (propBuilt === null) return null;
512
579
  code += propBuilt.code;
513
- slots.push({
514
- always: outputAlwaysDefined(propIR),
515
- keyStr,
516
- value: propBuilt.value
517
- });
580
+ built.set(key, propBuilt);
518
581
  }
582
+ const slots = properties.map(([key, propIR]) => ({
583
+ always: outputAlwaysDefined(propIR),
584
+ keyStr: escapeString(key),
585
+ value: built.get(key).value
586
+ }));
519
587
  const out = local(g, "bo");
520
588
  const literal = [];
521
589
  let appends = "";
@@ -529,12 +597,23 @@ function buildObject(ir, input, g) {
529
597
  appends += slot.always ? `${out}[${slot.keyStr}]=${slot.value};` : `if(${slot.value}!==undefined||(${slot.keyStr} in ${input})){${out}[${slot.keyStr}]=${slot.value};}`;
530
598
  }
531
599
  code += `${out}={${literal.join(",")}};${appends}`;
532
- for (const check of refines) code += `if(!${emitEffectCallable(g.ctx, check)}(${out}))return ${g.fail};`;
600
+ for (const check of refines) code += buildRefine(check, out, g);
533
601
  return {
534
602
  code,
535
603
  value: out
536
604
  };
537
605
  }
606
+ /**
607
+ * A `.refine()` predicate on the build path: FAIL on a falsy verdict, and
608
+ * zod's synchronous-parse error on a Promise — which is truthy, so an
609
+ * unguarded test would accept the input (see ZC_ASYNC_DECL). The verdict is
610
+ * bound to a local so the Promise test reads it once.
611
+ */
612
+ function buildRefine(check, value, g) {
613
+ const asy = emitRuntimeHelper(g.ctx, "__zcAsy", ZC_ASYNC_DECL);
614
+ const verdict = local(g, "br");
615
+ return `${verdict}=${emitEffectCallable(g.ctx, check)}(${value});if(${verdict} instanceof Promise)${asy}();if(!${verdict})return ${g.fail};`;
616
+ }
538
617
  function buildArray(ir, input, g) {
539
618
  let sizes = "";
540
619
  const refines = [];
@@ -559,7 +638,7 @@ function buildArray(ir, input, g) {
559
638
  const inner = build(ir.element, elem, g);
560
639
  if (inner === null) return null;
561
640
  let code = `if(!Array.isArray(${input}))return ${g.fail};` + sizes + `${out}=new Array(${input}.length);for(${index}=0;${index}<${input}.length;${index}++){${elem}=${input}[${index}];${inner.code}${out}[${index}]=${inner.value};}`;
562
- for (const check of refines) code += `if(!${emitEffectCallable(g.ctx, check)}(${out}))return ${g.fail};`;
641
+ for (const check of refines) code += buildRefine(check, out, g);
563
642
  return {
564
643
  code,
565
644
  value: out
@@ -610,12 +689,13 @@ function buildRecord(ir, input, g) {
610
689
  * (see extractPipe), so there is no parse context to reproduce.
611
690
  */
612
691
  function buildEffect(ir, input, g) {
692
+ const asy = emitRuntimeHelper(g.ctx, "__zcAsy", ZC_ASYNC_DECL);
613
693
  if (ir.effectKind === "preprocess") {
614
694
  const value = local(g, "bpv");
615
695
  const inner = build(ir.inner, value, g);
616
696
  if (inner === null) return null;
617
697
  return {
618
- code: `${value}=${emitEffectCallable(g.ctx, ir)}(${input});${inner.code}`,
698
+ code: `${value}=${emitEffectCallable(g.ctx, ir)}(${input});if(${value} instanceof Promise)${asy}();${inner.code}`,
619
699
  value: inner.value
620
700
  };
621
701
  }
@@ -623,7 +703,7 @@ function buildEffect(ir, input, g) {
623
703
  if (inner === null) return null;
624
704
  const out = local(g, "bx");
625
705
  return {
626
- code: `${inner.code}${out}=${emitEffectCallable(g.ctx, ir)}(${inner.value});`,
706
+ code: `${inner.code}${out}=${emitEffectCallable(g.ctx, ir)}(${inner.value});if(${out} instanceof Promise)${asy}();`,
627
707
  value: out
628
708
  };
629
709
  }
@@ -647,7 +727,7 @@ function buildString(ir, input, g) {
647
727
  code += `${value}=${emitEffectFn(g.ctx, check.source)}(${value});`;
648
728
  break;
649
729
  case "refine_effect":
650
- code += `if(!${emitEffectCallable(g.ctx, check)}(${value}))return ${g.fail};`;
730
+ code += buildRefine(check, value, g);
651
731
  break;
652
732
  case "super_refine_effect": return null;
653
733
  default: {
@@ -750,15 +830,14 @@ function buildCoercedPrimitive(ir, input, g) {
750
830
  * `slowDefault` emits, reading the value off the retained schema so a
751
831
  * reference-typed default keeps zod's identity (one shared object, not a copy).
752
832
  *
753
- * The substituted value is not validated, so it makes the fast path a PARTIAL
754
- * predicate (`fc(undefined)` is false where the schema accepts) which is why
755
- * this records {@link CodeGenContext.buildSubstitutesValue}, on which
756
- * `generateValidator` withholds `.is()`.
833
+ * The substituted value is not validated. The by-reference fast form refuses
834
+ * `undefined` for that reason (it would hand the input back, not the default);
835
+ * the acceptance form `.is()` runs accepts it, as the schema does — see
836
+ * fastDefault.
757
837
  */
758
838
  function buildDefault(ir, input, g) {
759
839
  const inner = build(ir.inner, input, g);
760
840
  if (inner === null) return null;
761
- g.ctx.buildSubstitutesValue = true;
762
841
  const out = local(g, "bq");
763
842
  const value = defaultValueExpr(ir);
764
843
  const reapply = needsPostInnerDefault(ir) ? `if(${out}===undefined){${out}=${value};}` : "";