/** * @module world/bind-generators * * Generator binding layer — extracted from `engine.ts` under B60, then * re-shaped under B97 to a **lazy per-namespace holder** pattern. * * ## B97 — lazy per-namespace getters with mutable `{ prng, ctx }` holder * * Pre-B97, `engine.ts` rebuilt all 14 generator namespaces (each with ~10 * helper closures) on **every field** of every record — `makeFieldCtx` * called `bindNamespace` 14 times per field. A 4-field schema therefore * allocated ~560 closures per `generate()` call. That cost showed up in * `pnpm --filter=@zod4-mock/site bench` as a ~7× slowdown vs the pre-B36 * (lazy Proxy) shape — bisected to commit `9717326`. * * The fix is structural: one `FieldState` holder per outer `generate()` call, * one shared `BoundGenerators` (`buildLazyGen`) built lazily on top, and the * field loop **mutates** the holder's `prng` / `ctx` in place before each * pipeline step. Each namespace is a property-getter that materialises its * closures **on first touch** and caches the result on the object — * subsequent touches in the same call read the cache. The bound closures * read `state.prng` / `state.ctx` at **call time** (through the holder * reference), not at bind time, so per-field state swaps without rebuilding. * * Why per-namespace and not per-method? Matchers typically touch a small * handful of namespaces (`person`, `internet`, `location`, …); per-method * granularity would create ~10 getters per touched namespace for marginal * win on a workload that already short-circuits to zero materialisations * when nothing reads `ctx.gen`. * * ## Invariants * * - **D4 / D10 determinism** — the holder reads `state.prng` at call time, * so a per-field PRNG fork is observed by every matcher that calls * `ctx.gen..()` from that field. No bind-time snapshotting. * - **B40 ctx-forwarding contract** — preserved byte-identically. The four * `CTX_SLOTS` buckets keep their meanings: * - `number` — bucket 1 / 3: inject `ctx` at the declared slot * index when the caller didn't supply one. * - `"no-args-only"` — bucket 2: inject `ctx` only when the caller * passes zero args. The Gender-string-without-locale * residual is preserved (out of scope for B97). * - absent — bucket 4: forward args verbatim, no `ctx` * injection. * - **D1 no `any`** — `BoundGenerators` is the typed `CoreGenerators` * produced by `Object.defineProperty`-installed getters. * * ## Test seam (B97-R3) * * The module exposes a private `bindCount` counter that increments each * time a namespace's closures are materialised, plus `__getBindCount` / * `__resetBindCount` / `__bindCount(world)` helpers used by the regression * tests. The counter is **not** part of the public API (no export from * `src/index.ts`). */ import type { GeneratorContext, BoundGenerators, Prng } from "../types.js"; export interface FieldState { prng: Prng; ctx: GeneratorContext; /** * Owning `WorldImpl` reference — used solely for the test-only * `__bindCount(world)` instrumentation seam so per-world counts work * even when materialisation happens outside `withFieldStateScope` (e.g. * `populate` calls `generateAndStorePrimary` directly). */ world: object; } export declare const __getBindCount: () => number; export declare const __resetBindCount: () => void; /** * Test-only — returns the cumulative bind count for a given world. Counts * are attributed via `FieldState.world` at materialisation time, so each * world's count is independent. Returns `0` for worlds that have never * materialised a namespace. */ export declare function __bindCount(world: unknown): number; export declare function bindNamespace>>(ns: T, nsName: string, state: FieldState): { [K in keyof T]: T[K] extends (prng: Prng, ...args: infer P) => infer R ? (...args: P) => R : T[K]; }; export declare function buildLazyGen(state: FieldState): BoundGenerators; //# sourceMappingURL=bind-generators.d.ts.map