/** * @module types * Core TypeScript interfaces and types for the zod-mock library. */ import type { ZodTypeAny, input, z } from "zod"; import type { createPrng } from "./prng.js"; import type { LocaleData, Prng } from "@zod4-mock/locale-core"; import type { WorldTrace } from "./trace.js"; export type { LocaleData, LastNamePrefix, Currency, Prng } from "@zod4-mock/locale-core"; /** * In-memory store of every record generated within one world, keyed by Zod * schema reference. Matchers reach across schemas through it (`pick`, `all`, * `find`) to keep generated data mutually consistent. */ export interface Registry { store(schema: T, item: input): void; all(schema: T): z.infer[]; pick(schema: T): z.infer; filter(schema: T, predicate: (item: z.infer) => boolean): z.infer[]; find(schema: T, predicate: (item: z.infer) => boolean): z.infer | undefined; count(schema: ZodTypeAny): number; } import type * as gen from "./generators/data/index.js"; type BoundModule = { [K in keyof T]: T[K] extends (prng: Prng, ...args: infer P) => infer R ? (...args: P) => R : T[K]; }; export type CoreGenerators = { [K in keyof typeof gen]: BoundModule<(typeof gen)[K]>; }; /** * The built-in `generators` namespace with the field-seeded `Prng` pre-bound * as the first argument of every generator. Exposed on `ctx.gen` so matchers * call `ctx.gen.person.firstName()` instead of passing a `Prng` by hand. */ export type BoundGenerators = CoreGenerators; /** * The per-field context handed to every generator and matcher. Carries the * field-seeded `prng`, the bound `gen` namespace, registry access, the * relation resolver, accumulated sibling values (`current`), and the active * locale for one field of one record. */ export interface GeneratorContext { /** * A PRNG forked for the current field path. Using this PRNG ensures * per-field stability: adding a field doesn't disturb existing fields. */ readonly prng: Prng; /** * Generators namespace with the field-seeded PRNG pre-applied. * Call ctx.gen.person.firstName() instead of generators.person.firstName(ctx.prng). */ readonly gen: BoundGenerators; /** * For derived schemas (registered with `from:`), this holds the source * schema record currently driving generation. `undefined` for primary schemas. */ readonly source: unknown; readonly registry: Registry; readonly fieldPath: string; readonly optionalProbability?: number; /** * Default [min, max] length for unconstrained arrays generated by the * schema-based fallback. Inherits the per-call `generate({ defaultArrayLength })` * value (or the world-level option) so nested `generateZodArray` calls honour it. */ readonly defaultArrayLength: readonly [number, number]; /** * B136 — when this field carries an ARRAY `options.overrides` value, the * override's length, threaded so the field's array generation produces * exactly that many base elements (override length wins; schema bounds / * `defaultArrayLength` govern only the no-override count). Engine-internal * plumbing with no user call path; `undefined` on every non-array-override * field, leaving the no-override length resolution unchanged. * @internal */ readonly overrideArrayLength?: number; /** * When generating nested object fields, holds the partial sibling-field values * accumulated so far. Used internally for gender-aware name generation. */ readonly current: Partial; /** * Resolves a related schema instance declared in the schema's `relations`. * Auto-provisions one if the registry is empty. * * Exception — **self-referential relations** (the relation points at the same * schema being generated, e.g. a category whose parent is another category): * these are not auto-provisioned (that would recurse forever). The first * record has no related instance and `related()` returns `undefined`; later * records reference earlier ones. Guard the empty case in the matcher, e.g. * `ctx.related("parent")?.id ?? null`. * * `related.many(name, count)` resolves `count` distinct related records (a * one-to-many pick), auto-provisioning the shortfall and returning a stable, * record-scoped, deterministic set. Self-referential relations are not * auto-provisioned, so `.many` returns at most the available distinct records. */ readonly related: { >(relationName: string): T; many(relationName: string, count: number): T[]; }; /** * Generates a value using the full world engine (honoring matchers and registry). */ generate(schema: S, options?: GenerateOptions>): z.infer; /** * Maximum recursion depth. */ readonly recursionLimit: number; /** * Active locale, used by generators for locale-specific data (names, words, etc.). * Defaults to `en` when created via `createWorld()`. */ readonly locale: LocaleData; } /** * The {@link GeneratorContext} variant passed to `withSchema` matchers, with * `related()` typed from the schema's declared `relations` and `source` typed * from its `from:` binding. For a primary schema `source` is `undefined`. */ export type MatcherCtx = Record, TSource = undefined, TOutput = any> = Omit, "related" | "source"> & { readonly source: TSource; readonly related: { (name: K): input; (name: string): Record; many(name: K, count: number): input[]; many(name: string, count: number): T[]; }; }; /** * A field-name generator registered via `world.withGenerators`. Receives the * field's Zod schema and its {@link GeneratorContext} and returns the value for * that field; matched case-insensitively by field name. */ export type KeyGenerator = (schema: ZodTypeAny, ctx: GeneratorContext) => T; /** * A per-schema map of field name → generator, registered via * `world.withKeyMap`. Each entry is typed against the matching field of the * schema's input shape and receives a {@link GeneratorContext}. */ export type SchemaKeyMap = { [K in keyof input]?: (ctx: GeneratorContext) => input[K]; }; /** * Recursively makes every property of `T` optional. Used to type * `GenerateOptions.overrides`, so a caller may override only the nested fields * they care about. */ export type DeepPartial = T extends object ? { [K in keyof T]?: DeepPartial; } : T; /** * World-wide generation defaults shared by `WorldOptions` (set once) and * `GenerateOptions` (override per call). */ export interface GenerationDefaults { /** Seed for this call's PRNG; same seed + schema yields the same value. */ readonly seed?: number; /** Probability (0–1) that an optional field is present. */ readonly optionalProbability?: number; /** Default `[min, max]` length for unconstrained arrays. */ readonly defaultArrayLength?: readonly [number, number]; /** Maximum recursion depth for self-referential / nested schemas. */ readonly recursionLimit?: number; /** Locale supplying names, words, and other locale-specific data. */ readonly locale?: LocaleData; } /** * Per-call options for `generate` / `world.generate`: `overrides` (deep-merged * onto the result), a `transform` post-processor, the `seed`, and tuning knobs * for optional probability, array length, recursion, and registry writes. */ export interface GenerateOptions extends GenerationDefaults { /** * Partial values deep-merged onto the generated result, overriding any field. * An array override sets the array element count: the result has exactly * `override.length` elements, and `overrides[i]` deep-merges onto the i-th * generated base element (un-overridden siblings preserved; a sparse hole / * `undefined` slot leaves that element fully generated). The override length * wins even over an explicit `.length(N)`; schema bounds (`.length()` / * `.min()` / `.max()`) and `defaultArrayLength` govern only the no-override * count. */ readonly overrides?: DeepPartial; /** Post-processor applied to the generated value before it is returned/stored. */ readonly transform?: (data: T) => T; /** * Source record driving generation of a derived schema. * @internal */ readonly source?: any; /** * Dotted field path used to seed per-field PRNG forks. * @internal */ readonly fieldPath?: string; /** * Explicit PRNG instance to use instead of one derived from `seed`. * @internal */ readonly prng?: ReturnType; /** * Explicit per-record seed index for a registered-primary record. Threaded by * the array generators (B135) so the i-th element of an array of a registered * schema seeds from `reg#` regardless of the `store` toggle: * under `store: false` the registry write is suppressed, so the default * `registry.count + pending` derivation freezes and every sibling collapses to * one index. Engine-internal plumbing with no user call path. * @internal */ readonly recordIndex?: number; /** * When `false`, suppress the registry write for this `world.generate` call — * useful for ephemeral generation (search-bucket envelopes, paginated * responses, one-off fixtures). Propagates through nested generation so inner * registered schemas are also not stored. Ignored by `world.get` (its create * path must always store) and by `world.populate` (its purpose is to populate * the registry). Default `true`. */ readonly store?: boolean; /** * When `false`, bypass the derived-schema upsert keyed by `(schema, source)`: * generate a fresh record even if a derived record already exists for this * source. Has no effect on schemas without `from:`. Default `true` (upsert). */ readonly unique?: boolean; } /** * Options passed to `createWorld`. Sets the master `seed` and world-wide * defaults — optional-field probability, unconstrained-array length, custom * key generators, recursion limit, and the active locale. */ export interface WorldOptions extends GenerationDefaults { /** Custom field-name generators, matched case-insensitively by field name. */ readonly generators?: Record; /** * Opt in to provenance capture for {@link World.trace}. When omitted (or * `false`), `world.trace()` still returns the registry projection — one * {@link TraceNode} per stored record — but no per-field or relation-edge * provenance is captured. (At the current stub the flag is plumbing only: * field and edge capture land in later cards, so an enabled trace returns the * same shape as a default one.) */ readonly trace?: boolean; } /** * A relation entry on `SchemaOpts.relations`. Either a bare Zod schema (the * historic form) or an object `{ schema, where? }` that filters the candidate * pool by a predicate (B11). The predicate sees `z.infer` — the * registry-read shape (per B7). */ export type RelationEntry = TRelation | { readonly schema: TRelation; readonly where?: (item: z.infer) => boolean; }; /** * Options for withSchema. * - If `from` is provided, the schema is "derived" and matchers receive `ctx.source`. * - If `from` is omitted, the schema is "primary" and `ctx.source` is undefined. */ export interface SchemaOpts = Record> { from?: TSource; /** * Identity field on the source record used to key the per-pair derived-schema * upsert. When omitted, identity falls back to reference equality on `source`. * Declared at registration only — not overridable per `generate` call. */ sourceKey?: TSource extends ZodTypeAny ? keyof input & string : never; relations?: { [K in keyof TRelations]: RelationEntry; }; matchers?: { [K in keyof input]?: (ctx: MatcherCtx : undefined, input>) => input[K]; }; } /** * Per-field resolution record returned by `world.explain(schema)`. * * `generator` is a stable identifier (e.g. `'person.firstName'`, * `'matcher:'`, `'schema-based'`); `reason` is a short human-readable * explanation of which step of the pipeline picked it. */ export interface FieldExplanation { readonly generator: string; readonly reason: string; } /** * Per-relation record on `ExplainResult.relations`. `schema` is the leaf * `def.type` of the related schema (e.g. `'object'`, `'lazy'`); `where` * reports whether the relation entry was the B11 object form * `{ schema, where }` with a predicate. */ export interface RelationExplanation { readonly schema: string; readonly where: "present" | "none"; } /** * Structured introspection result for `world.explain(schema)`. The `fields` * map is keyed by top-level field name in schema-shape order; the * `relations` map is keyed by relation name (empty `{}` when none). The * `toString()` method produces a human-readable aligned table — see * B16-R7. */ export interface ExplainResult { readonly fields: { readonly [K in keyof z.infer & string]: FieldExplanation; }; readonly relations: { readonly [relName: string]: RelationExplanation; }; toString(): string; } /** * One deterministic generation session, built fluently from `createWorld`. * Register schemas (`withSchema`), wire generators (`withGenerators`, * `withKeyMap`), produce data (`generate`, `get`, `populate`, `populateFrom`), * and introspect resolution (`explain`) or provenance (`trace`); all records * live in its `registry`. */ export interface World { /** * Register a schema with optional matchers, relations, or a source binding. * * Primary (no from:): * world.withSchema(PersonSchema, { matchers: { email: (ctx) => ... } }) * * Relational (with relations:): * world.withSchema(FileSchema, { * relations: { owner: PersonSchema }, * matchers: { ownerId: (ctx) => ctx.related("owner").personId }, * }) * * Derived (with from:): * world.withSchema(SummarySchema, { * from: PersonSchema, * matchers: { id: (ctx) => ctx.source.personId }, * }) * * The same output schema can be registered multiple times, each with a * different `from:` binding, to represent multiple source types. */ withSchema = Record>(schema: TSchema, opts?: SchemaOpts): this; /** * Register additional key-based generators. Calls are additive. * Keys are matched case-insensitively against field names. */ withGenerators(map: Record): this; /** * Bind field generators to a specific schema (legacy API, kept for compat). */ withKeyMap(schema: T, map: SchemaKeyMap): this; /** * Generate a value from a Zod schema. * * - Object schema → single generated object * - z.array(schema) → array respecting min/max/length constraints * - Registered derived schemas → one record per source */ generate(schema: TSchema, options?: GenerateOptions>): z.infer; /** * Find an existing stored record matching `predicate`, or generate one. * * Search the registry for the first stored record (insertion order) for which * every key in `predicate` matches the record's value — shallow keys by value, * nested-object values by deep equality. If one is found it is returned by * reference. Otherwise a new record is generated via the `generate` overrides * path with `predicate` supplied as `overrides` (so the predicate wins over * matchers), stored in the registry for `schema`, and returned. * * An absent or empty predicate matches everything: returns the first stored * record if any exist, else generates-and-stores one. Deterministic for a * given seed and idempotent for a repeated predicate. */ get(schema: TSchema, predicate?: Partial>): z.infer; /** * Pre-generate `count` instances of the schema and store them in the registry. * Returns `this` for fluent chaining. * * An optional per-record `factory` may be supplied. When given, it is invoked * once per record with the 0-based index and must return `GenerateOptions` * (`overrides`, `transform`, etc.) for that record. The returned options flow * through the normal generate pipeline. */ populate(schema: TSchema, count: number, factory?: (index: number) => GenerateOptions>): this; /** * Iterate the source bucket and call `world.generate(derivedSchema, { source })` * once per source record (filtered by `predicate` if supplied). The optional * `factory` receives the source record itself (`z.infer`, contrast * `populate`'s index-based factory) and returns `GenerateOptions` * that flow through the delegated `generate` call. * * Idempotence comes from B8's per-`(derivedSchema, identity(source))` upsert: * re-running `populateFrom` with the same arguments leaves the derived bucket * unchanged. The source bucket is snapshotted at call start — mid-loop * inserts to it are visible only to the next `populateFrom` call. * * Always writes (like `populate`): a factory return containing * `store: false` is silently stripped. Returns `this` for fluent chaining. */ populateFrom(derivedSchema: TDerived, sourceSchema: TSource, predicate?: (item: z.infer) => boolean, factory?: (source: z.infer) => GenerateOptions>): this; /** * Read-only debug helper: for each top-level field of `schema`, report * which step of the resolution pipeline (matcher → withKeyMap → * withGenerators → exact-key → key-pattern → schema-based) would resolve * the field, and a short reason. Returns a structured `ExplainResult` with * a `toString()` formatter for human-readable output. PRNG-neutral and * registry-neutral — calling `explain` does not consume the world PRNG, * does not write to the registry, and does not advance any generation * counter (B16). */ explain(schema: TSchema): ExplainResult; /** * Return the provenance structure for everything generated so far in this * world's lifetime — a plain, JSON-serializable {@link WorldTrace}. * * Emits one {@link TraceNode} per stored registry record (in registration * order), each carrying the record's stable id, its value, and its * registration polarity (`node` for primary, `derived` for * derived, with `derivedFrom` set on derived nodes). `seed` echoes the world's * root seed. * * Provenance capture is opt-in via `createWorld({ trace: true })`. At the * current stub every node's `fields` and the trace's `edges` are empty (field * and relation-edge capture land in later cards), so an enabled trace returns * the same shape as a default one. */ trace(): WorldTrace; /** Access to all data generated and stored in this world. */ readonly registry: Registry; /** Internal PRNG for testing and stability checks. */ readonly prng: ReturnType; } //# sourceMappingURL=types.d.ts.map