zod-compiler 2.0.1 → 2.0.2
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 +54 -52
- package/dist/core/codegen/build-path.d.ts.map +1 -1
- package/dist/core/codegen/build-path.js +89 -22
- package/dist/core/codegen/build-path.js.map +1 -1
- package/dist/core/codegen/context.d.ts +11 -10
- package/dist/core/codegen/context.d.ts.map +1 -1
- package/dist/core/codegen/context.js.map +1 -1
- package/dist/core/codegen/fast-path.d.ts +3 -1
- package/dist/core/codegen/fast-path.d.ts.map +1 -1
- package/dist/core/codegen/fast-path.js +7 -4
- package/dist/core/codegen/fast-path.js.map +1 -1
- package/dist/core/codegen/index.js +4 -3
- package/dist/core/codegen/index.js.map +1 -1
- package/dist/core/codegen/schemas/default.d.ts +10 -0
- package/dist/core/codegen/schemas/default.d.ts.map +1 -1
- package/dist/core/codegen/schemas/default.js +11 -0
- package/dist/core/codegen/schemas/default.js.map +1 -1
- package/dist/core/iife.d.ts +8 -0
- package/dist/core/iife.d.ts.map +1 -1
- package/dist/core/iife.js +8 -0
- package/dist/core/iife.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
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
|
|
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
|
|
@@ -26,7 +26,7 @@ loads pre-generated validators.
|
|
|
26
26
|
| | zod-compiler (build plugins / CLI) | Zod `z.compile()` |
|
|
27
27
|
| -------------------------------------- | -------------------------------------------- | --------------------------------------------- |
|
|
28
28
|
| Compilation | Build time (true AOT) | Runtime (`z.compile()` or the first parse) |
|
|
29
|
-
| Reported validation speedup | Up to
|
|
29
|
+
| Reported validation speedup | Up to 44x; up to 46x on rejected input | ~9x in Zod's headline example |
|
|
30
30
|
| Uses `new Function()` at runtime\* | No | Yes |
|
|
31
31
|
| Cold start | Fast; the validator is already generated | Pays for code generation at startup/first use |
|
|
32
32
|
| Strict CSP without `'unsafe-eval'` | Supported | Compilation is unavailable |
|
|
@@ -543,7 +543,7 @@ npx zod-compiler check src/schemas.ts --json --fail-under 80
|
|
|
543
543
|
|
|
544
544
|
## What Gets Compiled
|
|
545
545
|
|
|
546
|
-
### Fully Compiled (up to
|
|
546
|
+
### Fully Compiled (up to 44x faster)
|
|
547
547
|
|
|
548
548
|
Every Zod type except the fallbacks below: all primitives, `object` / `strictObject` / `looseObject`,
|
|
549
549
|
`array`, `tuple`, `record`, `set`, `map`, `union`, `discriminatedUnion`, `intersection`, `pipe`,
|
|
@@ -594,52 +594,52 @@ Schema-level `error` and `z.config()` maps are unaffected; for a per-call map us
|
|
|
594
594
|
|
|
595
595
|
| Scenario | Zod v3 | Zod v4 | **zod-compiler** | Typia | AJV | vs Zod v4 |
|
|
596
596
|
| ----------------------------------------------- | ------ | ------ | ---------------- | ----- | ----- | --------- |
|
|
597
|
-
| simple string | 12.
|
|
598
|
-
| string (min/max) |
|
|
599
|
-
| number (int+positive) |
|
|
600
|
-
| enum |
|
|
601
|
-
| bigint (min/max) | 11.
|
|
602
|
-
| tuple [string, int, bool] | 5.
|
|
603
|
-
| record\<string, number\> | 3.1M | 2.
|
|
604
|
-
| set\<string\> (5 items) | 3.
|
|
605
|
-
| set\<string\> (20 items) | 1.3M |
|
|
606
|
-
| map\<string, number\> (5 entries) | 2.0M | 1.3M | **13.
|
|
607
|
-
| map\<string, number\> (20 entries) |
|
|
608
|
-
| pipe (non-transform) | 8.
|
|
609
|
-
| discriminatedUnion (3 variants) | 3.3M | 5.2M | **
|
|
610
|
-
| discriminatedUnion (8 variants, rotating) | 2.6M | 4.4M | **
|
|
611
|
-
| plain union of 8 tagged objects (auto-discrim.) |
|
|
612
|
-
| strict object (DB row) | 1.
|
|
613
|
-
| medium object (valid) | 1.9M | 2.
|
|
614
|
-
| medium object (extra keys stripped) | 1.8M | 2.
|
|
615
|
-
| medium object (invalid) |
|
|
616
|
-
| large object (10 items) |
|
|
617
|
-
| large object (100 items) | 13K |
|
|
618
|
-
| readonly field (wrapper compiles away) | 3.0M | 6.6M | **
|
|
619
|
-
| readonly root object (rebuild + freeze) | 2.8M | 5.
|
|
620
|
-
| readonly array (delegates to Zod) | 3.
|
|
621
|
-
| recursive tree (7 nodes) |
|
|
622
|
-
| recursive tree (121 nodes) | 31K |
|
|
623
|
-
| nested recursion (7 nodes) |
|
|
624
|
-
| nested recursion (121 nodes) |
|
|
625
|
-
| deeply nested object (243 leaves) | 11K | 27K | **
|
|
626
|
-
| event log (combined) |
|
|
627
|
-
| object with transform (zero-capture) | 1.1M |
|
|
628
|
-
| array 10 × transform (zero-capture) |
|
|
629
|
-
| array 50 × transform (zero-capture) |
|
|
630
|
-
| object with captured transform | 1.2M |
|
|
631
|
-
| object with captured refine (cross-field) | 1.4M | 2.2M | **11.
|
|
632
|
-
| object with superRefine (cross-field) | 1.4M | 2.
|
|
633
|
-
| coerced query object (valid) | 1.8M | 2.9M | **5.
|
|
634
|
-
| coerced query object (invalid) | 1.0M |
|
|
635
|
-
| preprocessed query object (valid) |
|
|
636
|
-
| preprocessed query object (invalid) |
|
|
597
|
+
| simple string | 12.6M | 15.6M | **16.8M** | 17.2M | 17.4M | 1.1x |
|
|
598
|
+
| string (min/max) | 12.4M | 7.4M | **17.4M** | 17.6M | 15.5M | 2.4x |
|
|
599
|
+
| number (int+positive) | 11.8M | 9.4M | **17.2M** | 16.3M | 17.4M | 1.8x |
|
|
600
|
+
| enum | 12.1M | 14.7M | **17.4M** | 17.4M | 17.2M | 1.2x |
|
|
601
|
+
| bigint (min/max) | 11.2M | 7.9M | **16.7M** | — | — | 2.1x |
|
|
602
|
+
| tuple [string, int, bool] | 5.6M | 7.3M | **17.4M** | 16.4M | 15.7M | 2.4x |
|
|
603
|
+
| record\<string, number\> | 3.1M | 2.6M | **12.7M** | 12.0M | 15.2M | 5.0x |
|
|
604
|
+
| set\<string\> (5 items) | 3.7M | 2.3M | **15.2M** | — | — | 6.6x |
|
|
605
|
+
| set\<string\> (20 items) | 1.3M | 680K | **12.1M** | — | — | **18x** |
|
|
606
|
+
| map\<string, number\> (5 entries) | 2.0M | 1.3M | **13.2M** | — | — | **10x** |
|
|
607
|
+
| map\<string, number\> (20 entries) | 637K | 347K | **8.5M** | — | — | **24x** |
|
|
608
|
+
| pipe (non-transform) | 8.7M | 4.7M | **17.3M** | — | — | 3.6x |
|
|
609
|
+
| discriminatedUnion (3 variants) | 3.3M | 5.2M | **17.2M** | 16.1M | 7.8M | 3.3x |
|
|
610
|
+
| discriminatedUnion (8 variants, rotating) | 2.6M | 4.4M | **10.3M** | — | — | 2.3x |
|
|
611
|
+
| plain union of 8 tagged objects (auto-discrim.) | 356K | 1.3M | **10.2M** | — | — | 7.7x |
|
|
612
|
+
| strict object (DB row) | 1.8M | 3.0M | **11.3M** | — | — | 3.8x |
|
|
613
|
+
| medium object (valid) | 1.9M | 2.2M | **9.4M** | 11.2M | 7.5M | 4.2x |
|
|
614
|
+
| medium object (extra keys stripped) | 1.8M | 2.0M | **9.8M** | — | — | 4.9x |
|
|
615
|
+
| medium object (invalid) | 534K | 372K | **15.5M** | 2.9M | 7.5M | **42x** |
|
|
616
|
+
| large object (10 items) | 120K | 163K | **5.1M** | 5.9M | 1.2M | **31x** |
|
|
617
|
+
| large object (100 items) | 13K | 17K | **766K** | 1.3M | 127K | **44x** |
|
|
618
|
+
| readonly field (wrapper compiles away) | 3.0M | 6.6M | **16.7M** | — | — | 2.5x |
|
|
619
|
+
| readonly root object (rebuild + freeze) | 2.8M | 5.5M | **12.9M** | — | — | 2.3x |
|
|
620
|
+
| readonly array (delegates to Zod) | 3.9M | 4.3M | **4.2M** | — | — | 1.0x |
|
|
621
|
+
| recursive tree (7 nodes) | 581K | 1.0M | **7.5M** | 11.6M | 4.7M | 7.4x |
|
|
622
|
+
| recursive tree (121 nodes) | 31K | 57K | **777K** | 1.9M | 371K | **14x** |
|
|
623
|
+
| nested recursion (7 nodes) | 395K | 683K | **7.9M** | 11.2M | 3.0M | **12x** |
|
|
624
|
+
| nested recursion (121 nodes) | 24K | 42K | **805K** | 1.6M | 204K | **19x** |
|
|
625
|
+
| deeply nested object (243 leaves) | 11K | 27K | **825K** | 1.0M | 125K | **30x** |
|
|
626
|
+
| event log (combined) | 363K | 795K | **7.3M** | — | — | 9.2x |
|
|
627
|
+
| object with transform (zero-capture) | 1.1M | 1.9M | **6.7M** | — | — | 3.5x |
|
|
628
|
+
| array 10 × transform (zero-capture) | 121K | 206K | **4.1M** | — | — | **20x** |
|
|
629
|
+
| array 50 × transform (zero-capture) | 26K | 41K | **1.0M** | — | — | **25x** |
|
|
630
|
+
| object with captured transform | 1.2M | 7.9M | **15.9M** | — | — | 2.0x |
|
|
631
|
+
| object with captured refine (cross-field) | 1.4M | 2.2M | **11.5M** | — | — | 5.2x |
|
|
632
|
+
| object with superRefine (cross-field) | 1.4M | 2.1M | **9.2M** | — | — | 4.3x |
|
|
633
|
+
| coerced query object (valid) | 1.8M | 2.9M | **5.3M** | — | — | 1.9x |
|
|
634
|
+
| coerced query object (invalid) | 1.0M | 826K | **10.3M** | — | — | **12x** |
|
|
635
|
+
| preprocessed query object (valid) | 433K | 1.7M | **5.3M** | — | — | 3.2x |
|
|
636
|
+
| preprocessed query object (invalid) | 392K | 782K | **12.3M** | — | — | **16x** |
|
|
637
637
|
| stringbool config object (valid) | — | 2.8M | **6.3M** | — | — | 2.2x |
|
|
638
|
-
| stringbool config object (invalid) | — |
|
|
639
|
-
| custom/instanceof request (valid) | 965K | 3.1M | **
|
|
640
|
-
| custom/instanceof request (invalid) |
|
|
641
|
-
| disjoint object intersection (valid) | 1.4M | 1.6M | **9.
|
|
642
|
-
| disjoint object intersection (invalid) |
|
|
638
|
+
| stringbool config object (invalid) | — | 682K | **13.2M** | — | — | **19x** |
|
|
639
|
+
| custom/instanceof request (valid) | 965K | 3.1M | **9.9M** | — | — | 3.2x |
|
|
640
|
+
| custom/instanceof request (invalid) | 786K | 928K | **13.2M** | — | — | **14x** |
|
|
641
|
+
| disjoint object intersection (valid) | 1.4M | 1.6M | **9.5M** | — | — | 5.9x |
|
|
642
|
+
| disjoint object intersection (invalid) | 488K | 331K | **15.3M** | — | — | **46x** |
|
|
643
643
|
|
|
644
644
|
_ops/s, higher is better. `vp test bench` on an Apple M4 Max (zod 4.5.2, zod v3 3.23.8, typia 12, ajv 8),
|
|
645
645
|
best of three runs. The harness costs ~60 ns per iteration, so the fastest rows sit at that floor and gaps
|
|
@@ -662,10 +662,12 @@ failure, and is deferred until `.error` is read. A `z.object()` strips, so it in
|
|
|
662
662
|
single pass that validates and rebuilds together and bails on the first failure, covering the reshaping
|
|
663
663
|
idioms too (array size checks, `.refine()`, `.default()`, `.trim()`, `.transform()`).
|
|
664
664
|
|
|
665
|
-
Regexes are pre-compiled with bounded repeats unrolled, checks run cheapest-first
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
665
|
+
Regexes are pre-compiled with bounded repeats unrolled, checks run cheapest-first on both passes (so a
|
|
666
|
+
payload with a wrong boolean is rejected before its email is scanned, whichever was declared first),
|
|
667
|
+
discriminated unions dispatch through a `switch` on the tag (plain tagged unions are auto-discriminated
|
|
668
|
+
into it, on the stripping pass as well as the fast check, and the selected option re-checks neither
|
|
669
|
+
object-ness nor the tag), and oversized check functions are split to stay within V8's optimizer budget.
|
|
670
|
+
`.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
671
|
plain-object guard exits on one comparison for an ordinary object, and a case-insensitive `stringbool`
|
|
670
672
|
looks its input up verbatim before paying for `toLowerCase()`. Stripping objects, native coercions,
|
|
671
673
|
`stringbool`, defaults, string rewrites, context-free preprocessors and synchronous transforms validate
|
|
@@ -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":";;;;iBAuMgB,eAAe,IAAI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiCnB,kBAAkB,IAAI;;;;;;;;;;;iBAmFtB,sBAAsB,IAAI;;;;;;iBAmD1B,cAAc,IAAI,UAAU,KAAK"}
|
|
@@ -2,7 +2,7 @@ import { declareFastTemps, emitEffectCallable, emitEffectFn, emitPooledConstant,
|
|
|
2
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 { 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,
|
|
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
|
-
|
|
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
|
|
505
|
-
|
|
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,
|
|
577
|
+
const propBuilt = build(propIR, slot, childGen);
|
|
511
578
|
if (propBuilt === null) return null;
|
|
512
579
|
code += propBuilt.code;
|
|
513
|
-
|
|
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 = "";
|
|
@@ -750,15 +818,14 @@ function buildCoercedPrimitive(ir, input, g) {
|
|
|
750
818
|
* `slowDefault` emits, reading the value off the retained schema so a
|
|
751
819
|
* reference-typed default keeps zod's identity (one shared object, not a copy).
|
|
752
820
|
*
|
|
753
|
-
* The substituted value is not validated
|
|
754
|
-
*
|
|
755
|
-
*
|
|
756
|
-
*
|
|
821
|
+
* The substituted value is not validated. The by-reference fast form refuses
|
|
822
|
+
* `undefined` for that reason (it would hand the input back, not the default);
|
|
823
|
+
* the acceptance form `.is()` runs accepts it, as the schema does — see
|
|
824
|
+
* fastDefault.
|
|
757
825
|
*/
|
|
758
826
|
function buildDefault(ir, input, g) {
|
|
759
827
|
const inner = build(ir.inner, input, g);
|
|
760
828
|
if (inner === null) return null;
|
|
761
|
-
g.ctx.buildSubstitutesValue = true;
|
|
762
829
|
const out = local(g, "bq");
|
|
763
830
|
const value = defaultValueExpr(ir);
|
|
764
831
|
const reapply = needsPostInnerDefault(ir) ? `if(${out}===undefined){${out}=${value};}` : "";
|