# poc-lazy-zod — results

Generated by `nx run poc-lazy-zod:bench` (or `tsx src/bench/{build,run,report}.ts`).

## Files

- `results.json` — latest raw runs (whichever mode ran last)
- `summary.json` — latest aggregated stats + gate verdict
- `results-default.json` / `summary-default.json` — default V8 run
- `results-no-lazy.json` / `summary-no-lazy.json` — `--no-lazy` control run
- `bundle-sizes.json` — eager vs lazy bundle sizes
- `eager.cjs` / `lazy.cjs` — bundled entries (minified CJS, zod bundled in, target=node22)

## Decision gate (5 criteria, all must hold)

Over N=30 interleaved runs, warmup=3:

| Metric                   | Threshold                        |
| ------------------------ | -------------------------------- |
| Cold-start median        | lazy ≤ 0.6 × eager (≥40% faster) |
| Cold-start p95           | lazy ≤ 0.7 × eager               |
| First-parse median delta | ≤ +5 ms absolute                 |
| Parse-all(steady) delta  | within ±5%                       |
| Bundle size delta        | ≤ +2%                            |

"Parse-all(steady)" = second pass over every schema, after all have materialized.
First-pass parse-all is expected to be large for lazy (that's where deferred
construction is paid) and is reported but NOT gated.

## Findings (node v24.14.0, 1515 schemas)

### Default V8 (no flags)

| Metric             | Eager (median) | Lazy (median) | Delta      |
| ------------------ | -------------- | ------------- | ---------- |
| cold-start         | 387 ms         | 7.1 ms        | **−98.2%** |
| cold-start (p95)   | 428 ms         | 7.6 ms        | −98.2%     |
| first-parse        | 0.45 ms        | 1.47 ms       | +1.02 ms   |
| parse-all (1st)    | 235 ms         | 616 ms        | +162%      |
| parse-all (steady) | 31.0 ms        | 31.0 ms       | +0.2%      |
| bundle size        | 1.69 MB        | 1.70 MB       | +0.64%     |

**All 5 gates PASS. Verdict: PROCEED.**

### `--no-lazy` V8 control

Confirms the speedup is NOT hidden by V8's internal lazy-parse of code.

| Metric             | Eager (median) | Lazy (median) | Delta    |
| ------------------ | -------------- | ------------- | -------- |
| cold-start         | 399 ms         | 6.6 ms        | −98.3%   |
| cold-start (p95)   | 541 ms         | 7.0 ms        | −98.7%   |
| first-parse        | 0.22 ms        | 0.83 ms       | +0.61 ms |
| parse-all (steady) | 30.7 ms        | 30.8 ms       | +0.4%    |

Eager cold-start is marginally slower with `--no-lazy` (~400 ms vs 387 ms),
lazy is unchanged. The 54× cold-start speedup is real, not a V8 artifact.

## How the lazy wrapper achieves this

`src/lazy/lazyZ.ts` — `LazyZodSchema<T>` holds only a factory closure
`() => ZodType`. On the first call to `.parse()` / `.safeParse()` etc:

1. Invoke the factory, cache the resulting Zod schema.
2. **Self-patch** the instance: assign `this.parse = inner.parse.bind(inner)`,
   same for `safeParse`, `parseAsync`, `safeParseAsync`. Subsequent parses
   go through a bound-function call directly into the real Zod method, with
   no wrapper-level property access.

This is why `parse-all(steady) == parse-all(eager)` to within 0.3%: the
wrapper is gone from the hot path after the first call. The only cost that
remains is the materialization paid on first-parse of each schema (~1 ms).

## Per-request cost model

For an edge worker serving many requests from a single cold start:

- **Cold start savings**: 380 ms, paid once.
- **Per-schema materialization**: ~1 ms, paid once per schema actually used.
- **Ongoing per-parse overhead**: 0 (self-patch eliminates it).

So on a worker that handles N requests touching K distinct schemas:
`savings ≈ 380 ms − (K × 1 ms)`. Net positive until K > 380 (far beyond any
realistic tool surface).

## Machine context

- macOS (Darwin 24.6.0), Node v24.14.0
- 1515 schemas (15 hand-authored mirroring real FrontMCP shapes + 1500
  programmatically generated with seeded PRNG across 6 archetypes)
- Bundle config: esbuild, `platform:'node'`, `format:'cjs'`, `target:'node22'`,
  `minify:true`, `treeShaking:true`, `keepNames:true`, zod bundled in
- Runs are interleaved (eager, lazy, eager, lazy, …) to control for
  thermal / frequency drift

## Reproducing

```sh
cd apps/poc-lazy-zod
tsx src/schemas/generate.ts   # re-generate schemas (only if you change descriptors)
tsx src/bench/build.ts        # bundle both entries
tsx src/bench/run.ts          # run N=30 interleaved
tsx src/bench/report.ts       # print table + write summary.json

# V8 lazy-parse control
POC_NODE_ARGS="--no-lazy" tsx src/bench/run.ts
tsx src/bench/report.ts
```

Env vars: `POC_N` (default 30), `POC_WARMUP` (default 3), `POC_NODE_ARGS` (extra node flags).
