zod-compiler 1.27.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 +95 -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/dist/swc.d.ts +5 -1
- package/dist/swc.d.ts.map +1 -1
- package/dist/swc.js.map +1 -1
- package/dist/turbopack.d.ts +6 -1
- package/dist/turbopack.d.ts.map +1 -1
- package/dist/turbopack.js.map +1 -1
- package/dist/unplugin/disk-cache.d.ts.map +1 -1
- package/dist/unplugin/disk-cache.js +1 -0
- package/dist/unplugin/disk-cache.js.map +1 -1
- package/dist/unplugin/index.d.ts.map +1 -1
- package/dist/unplugin/index.js +56 -9
- package/dist/unplugin/index.js.map +1 -1
- package/dist/unplugin/pool-worker.d.ts +1 -0
- package/dist/unplugin/pool-worker.js +116 -0
- package/dist/unplugin/pool-worker.js.map +1 -0
- package/dist/unplugin/pool.d.ts +162 -0
- package/dist/unplugin/pool.d.ts.map +1 -0
- package/dist/unplugin/pool.js +445 -0
- package/dist/unplugin/pool.js.map +1 -0
- package/dist/unplugin/transform.d.ts +2 -1
- package/dist/unplugin/transform.d.ts.map +1 -1
- package/dist/unplugin/transform.js +1 -1
- package/dist/unplugin/transform.js.map +1 -1
- package/dist/unplugin/types.d.ts +36 -1
- package/dist/unplugin/types.d.ts.map +1 -1
- package/dist/unplugin/types.js.map +1 -1
- 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)
|
|
@@ -188,6 +192,7 @@ has neither — see [React Native / Expo](#react-native--expo).
|
|
|
188
192
|
| `apply` | `"build" \| "serve" \| "all"` | builds + Vitest | **Vite only**: when the plugin runs |
|
|
189
193
|
| `codegenMode` | `"lean" \| "inline"` | auto | `"inline"` emits helpers per file; needed for transpile-only esbuild — see [SWC](#swc) |
|
|
190
194
|
| `cache` | `boolean \| string` | `true` | Persistent transform cache in `node_modules/.cache/zod-compiler` |
|
|
195
|
+
| `parallel` | `boolean \| number` | `false` | Run transforms on worker threads — see [Parallel Transforms](#parallel-transforms) |
|
|
191
196
|
|
|
192
197
|
```typescript
|
|
193
198
|
zodCompiler({
|
|
@@ -412,6 +417,41 @@ expensive one — later runs hit the persistent cache.
|
|
|
412
417
|
Scope discovery with `include`; set `ZOD_COMPILER_TIMING=1` for a per-phase breakdown. Files that
|
|
413
418
|
never mention `zod` cost nothing.
|
|
414
419
|
|
|
420
|
+
### Parallel Transforms
|
|
421
|
+
|
|
422
|
+
Discovery runs one file at a time on the bundler's own thread — executions are serialized so
|
|
423
|
+
concurrent transforms cannot double-execute a shared dependency. `parallel` moves whole transforms
|
|
424
|
+
onto worker threads instead, each with its own loader and module cache, which is what makes running
|
|
425
|
+
them at the same time sound.
|
|
426
|
+
|
|
427
|
+
```typescript
|
|
428
|
+
zodCompiler({ parallel: true }); // one worker per core, less one, capped at 4
|
|
429
|
+
zodCompiler({ parallel: 2 }); // or pick the count yourself
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Whether it pays depends on your import graph, not your core count.** A module shared by many
|
|
433
|
+
schema files is executed once in-process and once _per worker_ here. Files with independent graphs
|
|
434
|
+
win; files chained through each other can lose. Both rows below are 120 files of 8 schemas each, on
|
|
435
|
+
12 performance cores — the only difference is whether the files import one another:
|
|
436
|
+
|
|
437
|
+
| Transform (120 files) | in-process | n=2 | n=4 | n=8 | n=12 |
|
|
438
|
+
| --------------------- | ---------: | -------: | -------: | -------: | -------: |
|
|
439
|
+
| independent graphs | 3,633 ms | 2,263 ms | 1,508 ms | 1,786 ms | 2,119 ms |
|
|
440
|
+
| 120-deep import chain | 945 ms | 977 ms | 1,045 ms | 1,796 ms | 3,332 ms |
|
|
441
|
+
|
|
442
|
+
So measure before adopting it — `ZOD_COMPILER_TIMING=1` prints the per-phase breakdown, and the
|
|
443
|
+
`discover` line is the one workers move. Throughput peaks around four workers and declines past it:
|
|
444
|
+
beyond that point every extra worker re-executes more graph, holds another copy of it in memory, and
|
|
445
|
+
adds to the generated source that the single receiving thread has to deserialize.
|
|
446
|
+
|
|
447
|
+
Emitted code, sourcemaps and cache entries are identical either way — `parallel` is not part of the
|
|
448
|
+
cache key, so a parallel build and a serial one share the same cache. The disk cache and dependency
|
|
449
|
+
crawling stay on the bundler thread, and if a worker cannot start or dies mid-build its file is
|
|
450
|
+
retried in-process rather than failing the build.
|
|
451
|
+
|
|
452
|
+
A **warm cache still beats parallelism**, and costs no memory — reach for `parallel` for the cold
|
|
453
|
+
runs the cache cannot help with.
|
|
454
|
+
|
|
415
455
|
## Framework Examples
|
|
416
456
|
|
|
417
457
|
Nothing framework-specific is needed — exported schemas are compiled in place, so anything accepting
|
|
@@ -485,7 +525,7 @@ npx zod-compiler check src/schemas.ts --json --fail-under 80
|
|
|
485
525
|
|
|
486
526
|
## What Gets Compiled
|
|
487
527
|
|
|
488
|
-
### Fully Compiled (1.
|
|
528
|
+
### Fully Compiled (1.1-46x faster)
|
|
489
529
|
|
|
490
530
|
Every Zod type except the fallbacks below — all primitives, `object` / `strictObject` / `looseObject`,
|
|
491
531
|
`array`, `tuple`, `record`, `set`, `map`, `union`, `discriminatedUnion`, `intersection`, `pipe`,
|
|
@@ -521,7 +561,7 @@ Three things differ by design:
|
|
|
521
561
|
|
|
522
562
|
| Behavior | Zod | zod-compiler |
|
|
523
563
|
| ------------------------- | ----------------------------------------------- | ------------------------------------------------------- |
|
|
524
|
-
| Record key iteration |
|
|
564
|
+
| Record key iteration | Own enumerable keys, symbols included | Own enumerable **string** keys only |
|
|
525
565
|
| Container output identity | A fresh array / set / map / object | The input container, by reference (array holes survive) |
|
|
526
566
|
| Per-call parse params | `safeParse(x, { error, reportInput })` honoured | Ignored; global `z.config()` maps still apply |
|
|
527
567
|
|
|
@@ -536,59 +576,61 @@ Schema-level `error` and `z.config()` maps are unaffected; for a per-call map us
|
|
|
536
576
|
|
|
537
577
|
| Scenario | Zod v3 | Zod v4 | **zod-compiler** | Typia | AJV | vs Zod v4 |
|
|
538
578
|
| ----------------------------------------------- | ------ | ------ | ---------------- | ----- | ----- | --------- |
|
|
539
|
-
| simple string |
|
|
540
|
-
| string (min/max) |
|
|
541
|
-
| number (int+positive) |
|
|
542
|
-
| enum |
|
|
543
|
-
| bigint (min/max) |
|
|
544
|
-
| tuple [string, int, bool] |
|
|
545
|
-
| record\<string, number\> |
|
|
546
|
-
| set\<string\> (5 items) |
|
|
547
|
-
| set\<string\> (20 items) |
|
|
548
|
-
| map\<string, number\> (5 entries) |
|
|
549
|
-
| map\<string, number\> (20 entries) |
|
|
550
|
-
| pipe (non-transform) |
|
|
551
|
-
| discriminatedUnion (3 variants) |
|
|
552
|
-
| discriminatedUnion (8 variants, rotating) |
|
|
553
|
-
| plain union of 8 tagged objects (auto-discrim.) |
|
|
554
|
-
| strict object (DB row) | 1.
|
|
555
|
-
| medium object (valid) | 1.
|
|
556
|
-
| medium object (extra keys stripped) | 1.
|
|
557
|
-
| medium object (invalid) |
|
|
558
|
-
| large object (10 items) |
|
|
559
|
-
| large object (100 items) |
|
|
560
|
-
| readonly field (wrapper compiles away) |
|
|
561
|
-
| readonly root object (rebuild + freeze) | 2.
|
|
562
|
-
| readonly array (delegates to Zod) |
|
|
563
|
-
| recursive tree (7 nodes) |
|
|
564
|
-
| recursive tree (121 nodes) |
|
|
565
|
-
| nested recursion (7 nodes) |
|
|
566
|
-
| nested recursion (121 nodes) |
|
|
567
|
-
| deeply nested object (243 leaves) |
|
|
568
|
-
| event log (combined) |
|
|
569
|
-
| object with transform (zero-capture) |
|
|
570
|
-
| array 10 × transform (zero-capture) |
|
|
571
|
-
| array 50 × transform (zero-capture) |
|
|
572
|
-
| object with captured transform |
|
|
573
|
-
| object with captured refine (cross-field) |
|
|
574
|
-
| object with superRefine (cross-field) | 1.
|
|
575
|
-
| coerced query object (valid) | 1.
|
|
576
|
-
| coerced query object (invalid) |
|
|
577
|
-
| preprocessed query object (valid) |
|
|
578
|
-
| preprocessed query object (invalid) |
|
|
579
|
-
| stringbool config object (valid) | — |
|
|
580
|
-
| stringbool config object (invalid) | — |
|
|
581
|
-
| custom/instanceof request (valid) |
|
|
582
|
-
| custom/instanceof request (invalid) |
|
|
583
|
-
| disjoint object intersection (valid) |
|
|
584
|
-
| disjoint object intersection (invalid) |
|
|
585
|
-
|
|
586
|
-
_ops/s, higher is better. `vp test bench` on an Apple
|
|
587
|
-
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
|
|
588
628
|
between the AOT columns there are noise, not real._
|
|
589
629
|
|
|
590
630
|
Nested objects, arrays and recursive types gain the most. Rejection is fast because a failed
|
|
591
|
-
`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.
|
|
592
634
|
|
|
593
635
|
```bash
|
|
594
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
|