zod-compiler 1.28.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -53
- package/dist/core/codegen/build-path.d.ts +12 -1
- package/dist/core/codegen/build-path.d.ts.map +1 -1
- package/dist/core/codegen/build-path.js +47 -9
- package/dist/core/codegen/build-path.js.map +1 -1
- package/dist/core/codegen/context.d.ts +31 -16
- package/dist/core/codegen/context.d.ts.map +1 -1
- package/dist/core/codegen/context.js +43 -18
- package/dist/core/codegen/context.js.map +1 -1
- package/dist/core/codegen/emit-issue.d.ts +17 -20
- package/dist/core/codegen/emit-issue.d.ts.map +1 -1
- package/dist/core/codegen/emit-issue.js +13 -13
- package/dist/core/codegen/emit-issue.js.map +1 -1
- package/dist/core/codegen/index.d.ts.map +1 -1
- package/dist/core/codegen/index.js +9 -7
- package/dist/core/codegen/index.js.map +1 -1
- package/dist/core/codegen/issue-decls.d.ts +41 -7
- package/dist/core/codegen/issue-decls.d.ts.map +1 -1
- package/dist/core/codegen/issue-decls.js +49 -9
- package/dist/core/codegen/issue-decls.js.map +1 -1
- package/dist/core/codegen/schemas/array.js +1 -1
- package/dist/core/codegen/schemas/array.js.map +1 -1
- package/dist/core/codegen/schemas/bigint.d.ts.map +1 -1
- package/dist/core/codegen/schemas/bigint.js +15 -4
- package/dist/core/codegen/schemas/bigint.js.map +1 -1
- package/dist/core/codegen/schemas/catch.d.ts.map +1 -1
- package/dist/core/codegen/schemas/catch.js +3 -2
- package/dist/core/codegen/schemas/catch.js.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.d.ts.map +1 -1
- package/dist/core/codegen/schemas/discriminated-union.js +2 -1
- package/dist/core/codegen/schemas/discriminated-union.js.map +1 -1
- package/dist/core/codegen/schemas/effect.d.ts.map +1 -1
- package/dist/core/codegen/schemas/effect.js +4 -2
- package/dist/core/codegen/schemas/effect.js.map +1 -1
- package/dist/core/codegen/schemas/file.js +2 -8
- package/dist/core/codegen/schemas/file.js.map +1 -1
- package/dist/core/codegen/schemas/number.js +4 -3
- package/dist/core/codegen/schemas/number.js.map +1 -1
- package/dist/core/codegen/schemas/object.d.ts +11 -1
- package/dist/core/codegen/schemas/object.d.ts.map +1 -1
- package/dist/core/codegen/schemas/object.js +58 -14
- package/dist/core/codegen/schemas/object.js.map +1 -1
- package/dist/core/codegen/schemas/readonly.d.ts.map +1 -1
- package/dist/core/codegen/schemas/readonly.js.map +1 -1
- package/dist/core/codegen/schemas/record.d.ts.map +1 -1
- package/dist/core/codegen/schemas/record.js +8 -3
- package/dist/core/codegen/schemas/record.js.map +1 -1
- package/dist/core/codegen/schemas/sizeable.d.ts +49 -3
- package/dist/core/codegen/schemas/sizeable.d.ts.map +1 -1
- package/dist/core/codegen/schemas/sizeable.js +101 -8
- package/dist/core/codegen/schemas/sizeable.js.map +1 -1
- package/dist/core/codegen/schemas/string.d.ts.map +1 -1
- package/dist/core/codegen/schemas/string.js +45 -16
- package/dist/core/codegen/schemas/string.js.map +1 -1
- package/dist/core/codegen/schemas/tuple.d.ts +14 -11
- package/dist/core/codegen/schemas/tuple.d.ts.map +1 -1
- package/dist/core/codegen/schemas/tuple.js +99 -49
- package/dist/core/codegen/schemas/tuple.js.map +1 -1
- package/dist/core/codegen/well-known-regex.d.ts +7 -0
- package/dist/core/codegen/well-known-regex.d.ts.map +1 -1
- package/dist/core/codegen/well-known-regex.js +10 -3
- package/dist/core/codegen/well-known-regex.js.map +1 -1
- package/dist/core/extract/checks.d.ts.map +1 -1
- package/dist/core/extract/checks.js +1 -0
- package/dist/core/extract/checks.js.map +1 -1
- package/dist/core/extract/extractors/intersection.js +21 -6
- package/dist/core/extract/extractors/intersection.js.map +1 -1
- package/dist/core/extract/extractors/object.d.ts.map +1 -1
- package/dist/core/extract/extractors/object.js +25 -9
- package/dist/core/extract/extractors/object.js.map +1 -1
- package/dist/core/extract/extractors/optional.d.ts.map +1 -1
- package/dist/core/extract/extractors/optional.js +1 -28
- package/dist/core/extract/extractors/optional.js.map +1 -1
- package/dist/core/extract/extractors/record.js +9 -3
- package/dist/core/extract/extractors/record.js.map +1 -1
- package/dist/core/extract/extractors/tuple.d.ts.map +1 -1
- package/dist/core/extract/extractors/tuple.js +16 -20
- package/dist/core/extract/extractors/tuple.js.map +1 -1
- package/dist/core/extract/types.d.ts +16 -8
- package/dist/core/extract/types.d.ts.map +1 -1
- package/dist/core/iife.d.ts +2 -1
- package/dist/core/iife.d.ts.map +1 -1
- package/dist/core/iife.js +2 -1
- package/dist/core/iife.js.map +1 -1
- package/dist/core/types.d.ts +70 -21
- package/dist/core/types.d.ts.map +1 -1
- package/dist/jit.d.ts.map +1 -1
- package/dist/jit.js +47 -12
- package/dist/jit.js.map +1 -1
- package/dist/runtime.d.ts +3 -1
- package/dist/runtime.js +12 -10
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,9 +2,13 @@
|
|
|
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.
|
|
5
|
+
Keep your existing Zod schemas. Get **1.1-46x faster** validation, and up to **41x** on rejected
|
|
6
6
|
input. No code changes required.
|
|
7
7
|
|
|
8
|
+
Requires **Zod ≥ 4.5**. The compiled output reproduces 4.5's semantics exactly — code-point string
|
|
9
|
+
lengths, symbol-keyed shapes, tuple issue order — so it does not match earlier 4.x releases. Stay on
|
|
10
|
+
zod-compiler 1.x if you are on Zod 4.0–4.4.
|
|
11
|
+
|
|
8
12
|
- [What Gets Compiled](#what-gets-compiled)
|
|
9
13
|
- [Schema Hoisting](#schema-hoisting)
|
|
10
14
|
- [Benchmark](#benchmark)
|
|
@@ -521,7 +525,7 @@ npx zod-compiler check src/schemas.ts --json --fail-under 80
|
|
|
521
525
|
|
|
522
526
|
## What Gets Compiled
|
|
523
527
|
|
|
524
|
-
### Fully Compiled (1.
|
|
528
|
+
### Fully Compiled (1.1-46x faster)
|
|
525
529
|
|
|
526
530
|
Every Zod type except the fallbacks below — all primitives, `object` / `strictObject` / `looseObject`,
|
|
527
531
|
`array`, `tuple`, `record`, `set`, `map`, `union`, `discriminatedUnion`, `intersection`, `pipe`,
|
|
@@ -557,7 +561,7 @@ Three things differ by design:
|
|
|
557
561
|
|
|
558
562
|
| Behavior | Zod | zod-compiler |
|
|
559
563
|
| ------------------------- | ----------------------------------------------- | ------------------------------------------------------- |
|
|
560
|
-
| Record key iteration |
|
|
564
|
+
| Record key iteration | Own enumerable keys, symbols included | Own enumerable **string** keys only |
|
|
561
565
|
| Container output identity | A fresh array / set / map / object | The input container, by reference (array holes survive) |
|
|
562
566
|
| Per-call parse params | `safeParse(x, { error, reportInput })` honoured | Ignored; global `z.config()` maps still apply |
|
|
563
567
|
|
|
@@ -572,59 +576,61 @@ Schema-level `error` and `z.config()` maps are unaffected; for a per-call map us
|
|
|
572
576
|
|
|
573
577
|
| Scenario | Zod v3 | Zod v4 | **zod-compiler** | Typia | AJV | vs Zod v4 |
|
|
574
578
|
| ----------------------------------------------- | ------ | ------ | ---------------- | ----- | ----- | --------- |
|
|
575
|
-
| simple string |
|
|
576
|
-
| string (min/max) |
|
|
577
|
-
| number (int+positive) |
|
|
578
|
-
| enum |
|
|
579
|
-
| bigint (min/max) |
|
|
580
|
-
| tuple [string, int, bool] |
|
|
581
|
-
| record\<string, number\> |
|
|
582
|
-
| set\<string\> (5 items) |
|
|
583
|
-
| set\<string\> (20 items) |
|
|
584
|
-
| map\<string, number\> (5 entries) |
|
|
585
|
-
| map\<string, number\> (20 entries) |
|
|
586
|
-
| pipe (non-transform) |
|
|
587
|
-
| discriminatedUnion (3 variants) |
|
|
588
|
-
| discriminatedUnion (8 variants, rotating) |
|
|
589
|
-
| plain union of 8 tagged objects (auto-discrim.) |
|
|
590
|
-
| strict object (DB row) | 1.
|
|
591
|
-
| medium object (valid) | 1.
|
|
592
|
-
| medium object (extra keys stripped) | 1.
|
|
593
|
-
| medium object (invalid) |
|
|
594
|
-
| large object (10 items) |
|
|
595
|
-
| large object (100 items) |
|
|
596
|
-
| readonly field (wrapper compiles away) |
|
|
597
|
-
| readonly root object (rebuild + freeze) | 2.
|
|
598
|
-
| readonly array (delegates to Zod) |
|
|
599
|
-
| recursive tree (7 nodes) |
|
|
600
|
-
| recursive tree (121 nodes) |
|
|
601
|
-
| nested recursion (7 nodes) |
|
|
602
|
-
| nested recursion (121 nodes) |
|
|
603
|
-
| deeply nested object (243 leaves) |
|
|
604
|
-
| event log (combined) |
|
|
605
|
-
| object with transform (zero-capture) |
|
|
606
|
-
| array 10 × transform (zero-capture) |
|
|
607
|
-
| array 50 × transform (zero-capture) |
|
|
608
|
-
| object with captured transform |
|
|
609
|
-
| object with captured refine (cross-field) |
|
|
610
|
-
| object with superRefine (cross-field) | 1.
|
|
611
|
-
| coerced query object (valid) | 1.
|
|
612
|
-
| coerced query object (invalid) |
|
|
613
|
-
| preprocessed query object (valid) |
|
|
614
|
-
| preprocessed query object (invalid) |
|
|
615
|
-
| stringbool config object (valid) | — |
|
|
616
|
-
| stringbool config object (invalid) | — |
|
|
617
|
-
| custom/instanceof request (valid) |
|
|
618
|
-
| custom/instanceof request (invalid) |
|
|
619
|
-
| disjoint object intersection (valid) |
|
|
620
|
-
| disjoint object intersection (invalid) |
|
|
621
|
-
|
|
622
|
-
_ops/s, higher is better. `vp test bench` on an Apple
|
|
623
|
-
best of three runs. The harness costs ~
|
|
579
|
+
| simple string | 8.5M | 10.4M | **11.0M** | 11.1M | 11.2M | 1.1x |
|
|
580
|
+
| string (min/max) | 8.2M | 4.9M | **10.8M** | 11.0M | 10.0M | 2.2x |
|
|
581
|
+
| number (int+positive) | 7.9M | 6.7M | **10.7M** | 11.1M | 11.4M | 1.6x |
|
|
582
|
+
| enum | 8.0M | 9.4M | **11.1M** | 11.2M | 11.3M | 1.2x |
|
|
583
|
+
| bigint (min/max) | 7.6M | 5.6M | **10.9M** | — | — | 2.0x |
|
|
584
|
+
| tuple [string, int, bool] | 3.9M | 4.8M | **10.9M** | 10.6M | 10.1M | 2.3x |
|
|
585
|
+
| record\<string, number\> | 2.2M | 1.8M | **8.0M** | 7.3M | 9.6M | 4.6x |
|
|
586
|
+
| set\<string\> (5 items) | 2.5M | 1.5M | **9.9M** | — | — | 6.8x |
|
|
587
|
+
| set\<string\> (20 items) | 918K | 445K | **7.7M** | — | — | **17x** |
|
|
588
|
+
| map\<string, number\> (5 entries) | 1.4M | 864K | **8.7M** | — | — | **10x** |
|
|
589
|
+
| map\<string, number\> (20 entries) | 448K | 236K | **5.5M** | — | — | **24x** |
|
|
590
|
+
| pipe (non-transform) | 5.9M | 3.2M | **10.5M** | — | — | 3.3x |
|
|
591
|
+
| discriminatedUnion (3 variants) | 2.3M | 3.6M | **10.7M** | 10.3M | 5.4M | 3.0x |
|
|
592
|
+
| discriminatedUnion (8 variants, rotating) | 1.8M | 3.1M | **6.6M** | — | — | 2.1x |
|
|
593
|
+
| plain union of 8 tagged objects (auto-discrim.) | 244K | 898K | **6.1M** | — | — | 6.8x |
|
|
594
|
+
| strict object (DB row) | 1.2M | 2.0M | **7.0M** | — | — | 3.6x |
|
|
595
|
+
| medium object (valid) | 1.3M | 1.5M | **5.6M** | 6.9M | 4.7M | 3.9x |
|
|
596
|
+
| medium object (extra keys stripped) | 1.2M | 1.3M | **5.7M** | — | — | 4.3x |
|
|
597
|
+
| medium object (invalid) | 359K | 263K | **9.8M** | 2.0M | 5.1M | **37x** |
|
|
598
|
+
| large object (10 items) | 82K | 110K | **3.6M** | 3.8M | 765K | **33x** |
|
|
599
|
+
| large object (100 items) | 9K | 12K | **531K** | 759K | 82K | **46x** |
|
|
600
|
+
| readonly field (wrapper compiles away) | 2.2M | 4.7M | **11.0M** | — | — | 2.3x |
|
|
601
|
+
| readonly root object (rebuild + freeze) | 2.1M | 4.0M | **8.6M** | — | — | 2.1x |
|
|
602
|
+
| readonly array (delegates to Zod) | 2.8M | 3.0M | **2.9M** | — | — | 1.0x |
|
|
603
|
+
| recursive tree (7 nodes) | 407K | 727K | **5.3M** | 7.4M | 2.9M | 7.3x |
|
|
604
|
+
| recursive tree (121 nodes) | 23K | 42K | **510K** | 1.3M | 232K | **12x** |
|
|
605
|
+
| nested recursion (7 nodes) | 280K | 489K | **4.6M** | 7.2M | 1.8M | 9.3x |
|
|
606
|
+
| nested recursion (121 nodes) | 17K | 30K | **461K** | 1.1M | 126K | **15x** |
|
|
607
|
+
| deeply nested object (243 leaves) | 8K | 20K | **327K** | 666K | 83K | **17x** |
|
|
608
|
+
| event log (combined) | 257K | 575K | **4.9M** | — | — | 8.6x |
|
|
609
|
+
| object with transform (zero-capture) | 752K | 1.3M | **4.4M** | — | — | 3.4x |
|
|
610
|
+
| array 10 × transform (zero-capture) | 89K | 137K | **2.7M** | — | — | **20x** |
|
|
611
|
+
| array 50 × transform (zero-capture) | 18K | 28K | **689K** | — | — | **25x** |
|
|
612
|
+
| object with captured transform | 862K | 5.4M | **9.6M** | — | — | 1.8x |
|
|
613
|
+
| object with captured refine (cross-field) | 995K | 1.3M | **7.3M** | — | — | 5.6x |
|
|
614
|
+
| object with superRefine (cross-field) | 1.0M | 1.4M | **5.9M** | — | — | 4.3x |
|
|
615
|
+
| coerced query object (valid) | 1.2M | 2.0M | **3.6M** | — | — | 1.8x |
|
|
616
|
+
| coerced query object (invalid) | 754K | 603K | **6.8M** | — | — | **11x** |
|
|
617
|
+
| preprocessed query object (valid) | 302K | 1.1M | **3.6M** | — | — | 3.2x |
|
|
618
|
+
| preprocessed query object (invalid) | 268K | 574K | **7.8M** | — | — | **14x** |
|
|
619
|
+
| stringbool config object (valid) | — | 2.0M | **3.7M** | — | — | 1.9x |
|
|
620
|
+
| stringbool config object (invalid) | — | 497K | **8.6M** | — | — | **17x** |
|
|
621
|
+
| custom/instanceof request (valid) | 689K | 2.0M | **5.6M** | — | — | 2.7x |
|
|
622
|
+
| custom/instanceof request (invalid) | 527K | 624K | **5.7M** | — | — | 9.1x |
|
|
623
|
+
| disjoint object intersection (valid) | 946K | 1.0M | **5.7M** | — | — | 5.5x |
|
|
624
|
+
| disjoint object intersection (invalid) | 343K | 240K | **9.8M** | — | — | **41x** |
|
|
625
|
+
|
|
626
|
+
_ops/s, higher is better. `vp test bench` on an Apple M1 Max (zod 4.5.2, zod v3 3.23.8, typia 12, ajv 8),
|
|
627
|
+
best of three runs. The harness costs ~90 ns per iteration, so the fastest rows sit at that floor and gaps
|
|
624
628
|
between the AOT columns there are noise, not real._
|
|
625
629
|
|
|
626
630
|
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.
|
|
631
|
+
`safeParse` defers building the error until `.error` is read. Zod 4.5 stopped capturing a stack trace on
|
|
632
|
+
that path too, so its own rejected-input rows are several times faster than 4.3's and the gap there is
|
|
633
|
+
narrower than it was.
|
|
628
634
|
|
|
629
635
|
```bash
|
|
630
636
|
vp run benchmark # run locally
|
|
@@ -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":";;;;
|
|
1
|
+
{"version":3,"file":"build-path.d.ts","names":[],"sources":["../../../src/core/codegen/build-path.ts"],"mappings":";;;;iBA8KgB,eAAe,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,kBAAkB,IAAI;;;;;;;;;;;iBAmFtB,sBAAsB,IAAI;;;;;;iBAmD1B,cAAc,IAAI,UAAU,KAAK"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { declareFastTemps, emitEffectCallable, emitEffectFn, emitPooledConstant, emitRuntimeHelper, emitTemp, escapeString, hasMutation, keyMembershipTest, literalToJs, outputAlwaysDefined, rejectsUndefined } from "./context.js";
|
|
2
|
-
import { ZC_HOP_DECL, ZC_PLAIN_DECL } from "./issue-decls.js";
|
|
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";
|
|
3
3
|
import { defaultValueExpr, needsPostInnerDefault } from "./schemas/default.js";
|
|
4
4
|
import { estimateFastCost, predictedInlineSize } from "./fast-size.js";
|
|
5
|
+
import { parsedProperties } from "./schemas/object.js";
|
|
5
6
|
import { innerAppliesDefaultOnUndefined } from "./schemas/optional.js";
|
|
6
7
|
import { fastStringCheck } from "./schemas/string.js";
|
|
7
8
|
import { emitStringBoolMap, stringBoolUsesInline } from "./schemas/string-bool.js";
|
|
@@ -20,7 +21,7 @@ import { createFastGen, generateFast } from "./fast-path.js";
|
|
|
20
21
|
* Resolving the ref against its target closes the cycle, and iterating to a
|
|
21
22
|
* fixpoint settles the mutual dependency between the two.
|
|
22
23
|
*/
|
|
23
|
-
function rebuildSet(root) {
|
|
24
|
+
function rebuildSet(root, includeProtoScrub = true) {
|
|
24
25
|
const targets = /* @__PURE__ */ new Map([[0, root]]);
|
|
25
26
|
const nodes = [];
|
|
26
27
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -38,7 +39,7 @@ function rebuildSet(root) {
|
|
|
38
39
|
for (const node of nodes) {
|
|
39
40
|
if (rebuilds.has(node)) continue;
|
|
40
41
|
const target = node.type === "recursiveRef" ? targets.get(node.refId ?? 0) : void 0;
|
|
41
|
-
if (node.type === "object" && node.stripUnknownKeys === true || node.type === "readonly" && node.freeze === true || node.type === "default" || node.type === "stringBool" || node.type === "string" && (node.coerce === true || node.checks.some((c) => c.kind === "overwrite_effect")) || (node.type === "number" || node.type === "boolean" || node.type === "bigint" || node.type === "date") && node.coerce === true || node.type === "effect" || target !== void 0 && rebuilds.has(target) || children(node).some((child) => rebuilds.has(child))) {
|
|
42
|
+
if (node.type === "object" && node.stripUnknownKeys === true || node.type === "readonly" && node.freeze === true || node.type === "default" || node.type === "tuple" && tupleRewritesShortInput(node) || includeProtoScrub && needsProtoScrub(node) || node.type === "stringBool" || node.type === "string" && (node.coerce === true || node.checks.some((c) => c.kind === "overwrite_effect")) || (node.type === "number" || node.type === "boolean" || node.type === "bigint" || node.type === "date") && node.coerce === true || node.type === "effect" || target !== void 0 && rebuilds.has(target) || children(node).some((child) => rebuilds.has(child))) {
|
|
42
43
|
rebuilds.add(node);
|
|
43
44
|
changed = true;
|
|
44
45
|
}
|
|
@@ -81,6 +82,7 @@ function rebuildsOutput(ir) {
|
|
|
81
82
|
*/
|
|
82
83
|
function fastResultIsInput(ir) {
|
|
83
84
|
if (rebuildsOutput(ir)) return false;
|
|
85
|
+
if (needsProtoScrub(ir)) return false;
|
|
84
86
|
const seen = /* @__PURE__ */ new Set();
|
|
85
87
|
const ordered = (node) => {
|
|
86
88
|
if (seen.has(node)) return false;
|
|
@@ -122,6 +124,25 @@ function mutatesHere(ir) {
|
|
|
122
124
|
function superRefines(checks) {
|
|
123
125
|
return checks !== void 0 && checks.some((c) => c.kind === "super_refine_effect");
|
|
124
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Does anything STRICTLY BELOW `ir` hand back a container needing a `__proto__`
|
|
129
|
+
* scrub?
|
|
130
|
+
*
|
|
131
|
+
* The root's by-reference shortcut can filter the value it returns
|
|
132
|
+
* (`data: __zcPs(input)`), but that only reaches the OUTER container — a nested
|
|
133
|
+
* one is never touched, because the shortcut does not walk. So a schema with a
|
|
134
|
+
* scrub-needing descendant keeps the eager slow walk, which scrubs at every
|
|
135
|
+
* level. Only the root's own scrub is shortcut-compatible.
|
|
136
|
+
*/
|
|
137
|
+
function nestedNeedsProtoScrub(ir) {
|
|
138
|
+
const seen = /* @__PURE__ */ new Set();
|
|
139
|
+
const walk = (node) => {
|
|
140
|
+
if (seen.has(node)) return false;
|
|
141
|
+
seen.add(node);
|
|
142
|
+
return children(node).some((child) => needsProtoScrub(child) || walk(child));
|
|
143
|
+
};
|
|
144
|
+
return walk(ir);
|
|
145
|
+
}
|
|
125
146
|
function children(ir) {
|
|
126
147
|
switch (ir.type) {
|
|
127
148
|
case "object": return ir.catchall ? [...Object.values(ir.properties), ir.catchall] : Object.values(ir.properties);
|
|
@@ -153,6 +174,7 @@ function children(ir) {
|
|
|
153
174
|
function generateBuild(ir, ctx) {
|
|
154
175
|
const rebuilds = rebuildSet(ir);
|
|
155
176
|
if (!rebuilds.has(ir) || mutatesBeyondStrip(ir)) return null;
|
|
177
|
+
if (needsProtoScrub(ir) && !rebuildSet(ir, false).has(ir)) return null;
|
|
156
178
|
const fail = emitFailSentinel(ctx);
|
|
157
179
|
const scope = {
|
|
158
180
|
temps: [],
|
|
@@ -214,6 +236,7 @@ function build(ir, input, g) {
|
|
|
214
236
|
};
|
|
215
237
|
}
|
|
216
238
|
}
|
|
239
|
+
if (needsProtoScrub(ir) && !children(ir).some((child) => g.rebuilds.has(child))) return passthrough(ir, input, g);
|
|
217
240
|
const before = g.scope.used;
|
|
218
241
|
const out = buildInline(ir, input, {
|
|
219
242
|
...g,
|
|
@@ -396,14 +419,25 @@ function hostPassthrough(ir, g) {
|
|
|
396
419
|
g.ctx.preamble.push(`function ${name}(${param}){${declareFastTemps(scope)}return ${expr === "true" ? param : `(${expr})?${param}:${g.fail}`};}`);
|
|
397
420
|
return name;
|
|
398
421
|
}
|
|
399
|
-
/**
|
|
422
|
+
/**
|
|
423
|
+
* Validate in place with the Fast Path and hand the input straight back —
|
|
424
|
+
* filtered through `__zcPs` for a container zod would have stripped an own
|
|
425
|
+
* `__proto__` from (see ZC_PROTO_SCRUB_DECL). The filter copies only when the
|
|
426
|
+
* key is present, so the ordinary value is still returned by reference.
|
|
427
|
+
*/
|
|
400
428
|
function passthrough(ir, input, g) {
|
|
401
429
|
const expr = generateFast(ir, createFastGen(input, g.ctx, true, g.scope));
|
|
402
430
|
if (expr === null) return null;
|
|
403
|
-
return {
|
|
404
|
-
|
|
431
|
+
const guard = expr === "true" ? "" : `if(!(${expr}))return ${g.fail};`;
|
|
432
|
+
if (!needsProtoScrub(ir)) return {
|
|
433
|
+
code: guard,
|
|
405
434
|
value: input
|
|
406
435
|
};
|
|
436
|
+
const slot = local(g, "bs");
|
|
437
|
+
return {
|
|
438
|
+
code: `${guard}${slot}=${emitRuntimeHelper(g.ctx, "__zcPs", ZC_PROTO_SCRUB_DECL)}(${input});`,
|
|
439
|
+
value: slot
|
|
440
|
+
};
|
|
407
441
|
}
|
|
408
442
|
/**
|
|
409
443
|
* Rebuild from the declared keys. Sound for a stripping object (that IS the
|
|
@@ -432,7 +466,9 @@ function buildReadonly(ir, input, g) {
|
|
|
432
466
|
function buildObject(ir, input, g) {
|
|
433
467
|
if (ir.catchall !== void 0) return null;
|
|
434
468
|
if (ir.stripUnknownKeys !== true && ir.strict !== true) return null;
|
|
469
|
+
if (ir.skipAbsentKeys !== void 0 && ir.skipAbsentKeys.length > 0) return null;
|
|
435
470
|
if (ir.suppressAbsentKeys !== void 0 && ir.suppressAbsentKeys.length > 0) return null;
|
|
471
|
+
const nonoptional = new Set(ir.nonoptionalKeys ?? []);
|
|
436
472
|
const refines = ir.checks ?? [];
|
|
437
473
|
if (refines.some((check) => check.kind !== "refine_effect")) return null;
|
|
438
474
|
let code = `if(typeof ${input}!=="object"||${input}===null||Array.isArray(${input}))return ${g.fail};`;
|
|
@@ -441,9 +477,10 @@ function buildObject(ir, input, g) {
|
|
|
441
477
|
code += `for(${keyVar} in ${input}){if(!(${keyMembershipTest(g.ctx, Object.keys(ir.properties), keyVar)}))return ${g.fail};}`;
|
|
442
478
|
}
|
|
443
479
|
const slots = [];
|
|
444
|
-
for (const [key, propIR] of
|
|
480
|
+
for (const [key, propIR] of parsedProperties(ir)) {
|
|
445
481
|
const keyStr = escapeString(key);
|
|
446
482
|
const slot = local(g, "bv");
|
|
483
|
+
if (nonoptional.has(key)) code += `if(!(${keyStr} in ${input}))return ${g.fail};`;
|
|
447
484
|
code += `${slot}=${input}[${keyStr}];`;
|
|
448
485
|
const propBuilt = build(propIR, slot, g);
|
|
449
486
|
if (propBuilt === null) return null;
|
|
@@ -505,6 +542,7 @@ function buildArray(ir, input, g) {
|
|
|
505
542
|
}
|
|
506
543
|
function buildTuple(ir, input, g) {
|
|
507
544
|
if (ir.rest !== null) return null;
|
|
545
|
+
if (ir.optStart !== ir.items.length) return null;
|
|
508
546
|
if (ir.items.some((item) => !rejectsUndefined(item))) return null;
|
|
509
547
|
let code = `if(!Array.isArray(${input})||${input}.length!==${ir.items.length})return ${g.fail};`;
|
|
510
548
|
const values = [];
|
|
@@ -703,6 +741,6 @@ function local(g, prefix) {
|
|
|
703
741
|
return name;
|
|
704
742
|
}
|
|
705
743
|
//#endregion
|
|
706
|
-
export { fastResultIsInput, generateBuild, rebuildsOutput };
|
|
744
|
+
export { fastResultIsInput, generateBuild, nestedNeedsProtoScrub, rebuildsOutput };
|
|
707
745
|
|
|
708
746
|
//# sourceMappingURL=build-path.js.map
|