/** * Public library API — the `TDC` class. * * This is the user-facing facade described in docs/vision/17-library-api.md: * construct with either `configFile` or `configString` plus optional runtime * overrides, then pick a terminal operation: * * - `writeFile(path)` — render the configured output and write it to `path` * (Node-only; uses `node:fs`). * - `toString()` — render and return the output as a single string. * - `toIterator()` — yield one text row per iteration for streaming. * - `toStream()` — expose text output as a Node.js Readable stream. * - `toArray()` / `iterate()` / `getAt()` — expose materialised * sequences as plain objects, ignoring text-output wrappers. * * Evaluation is lazy: the constructor parses and stores the tree, but * rendering happens only when a terminal is invoked. The same TDC * instance may be terminal-invoked multiple times; results are * deterministic per (config, seed, now). */ import { Readable } from 'node:stream'; import { type Diagnostic } from '../errors/index.js'; import { type EngineChoice } from '../processor/render.js'; import { type TdcColumns, type TdcObjectRow } from './object.js'; /** * Batch size for streaming writes. One write per card is one syscall per row; * accumulating ~1 MB before flushing cuts that by ~1000× while keeping peak * memory bounded (the buffer, not the whole output). */ export declare const WRITE_BATCH_BYTES: number; export interface TdcOptions { /** * Called as a run advances — the live wire for a progress bar. * * Phases: 'uniq-scan' (every row's tuple hashed), 'uniq-sort' (piles * sorted; `done` counts piles), 'uniq-repair' (repeats verified and * rearranged — usually the longest on a large run), 'render' (rows * written). Reports are * throttled at the source, about one per half-percent of a phase. This is * what TDC Studio and any service driving the engine listen to; the CLI's * `--progress` turns the same wire into a status file. */ readonly onProgress?: (progress: { readonly phase: 'uniq-scan' | 'uniq-sort' | 'uniq-repair' | 'render'; readonly done: number; readonly total: number; }) => void; /** Path to a DSL file on disk (preferred for larger configs). */ readonly configFile?: string; /** Inline DSL source string (preferred for small / dynamic configs). */ readonly configString?: string; /** Override the seed declared in ``. */ readonly seed?: string; /** Override the count declared in ``. */ readonly count?: number; /** Override the default locale — beats what `` declares. */ readonly locale?: string; /** * The locale used when the config does not name one — the project's * `tdcv2.config.json`, not the command line. * * `locale` OVERRIDES what `` declares; this only fills in when * `` declares nothing. Folding a project default into `locale` made a * config that says `local="ru"` produce English, silently, which is the worst * shape a bug can take: the run succeeds and the data is wrong. */ readonly defaultLocale?: string; /** Freeze the "now" clock used by date-based templates (ms since epoch). */ readonly now?: number; /** Base directory for relative data-source `src` paths. */ readonly baseDir?: string; /** Extra folders searched by `src="@data/..."` and relative file sources. */ readonly dataPaths?: readonly string[]; /** Legacy: force the streaming (Engine 2) engine. Prefer `mode`. */ readonly stream?: boolean; /** User mode: "memory" (Engine 1) or "disk" (Engine 2/3 auto). Overrides ``. */ readonly mode?: 'memory' | 'disk'; /** * Advanced: force a specific engine (1/2/3). Highest precedence. * * NAMING an engine means it refuses rather than quietly running another — * including engine 3 on a `` too tight for its bounded repair, which * used to fall back to the in-memory engine without a word, so a benchmark of * engine 3 was a benchmark of engine 1. `mode` says what the run may COST * instead, and a config that says that may still be handed to another engine. */ readonly engine?: 1 | 2 | 3; } export interface TdcPreflightOptions { /** * `materialized` matches `toString()` and includes the full output string * in the estimate. `streaming` matches `toIterator()`, `toStream()`, * `writeFile()`, and the CLI; sequence arrays are still materialized. */ readonly output?: 'materialized' | 'streaming'; } export declare class TDC { private readonly tree; private readonly options; private readonly baseDir; /** * Original DSL source. Exposed so callers can pass it to * `formatDiagnostics` if they catch `TdcDiagnosticError` later on — * rare in practice (the constructor already throws on errors) but * useful when consuming warnings via `diagnostics`. */ readonly source: string; /** * Every diagnostic produced by the parser + validator, including * non-fatal warnings (which are not thrown). Callers can surface * these to a log / editor / CLI stderr. */ readonly diagnostics: readonly Diagnostic[]; /** * Effective seed, resolved once and cached so every terminal * (`toString`, `toIterator`, `writeFile`, …) uses the SAME seed and * produces identical output. Populated lazily by `seedInfo()`. */ private cachedSeed?; /** Loaded data-pack registry (address -> list). Empty when no packs. */ private readonly packs; constructor(options: TdcOptions); /** Render and return the complete textual output. */ toString(): string; /** * What a worker thread needs to reproduce this run's rows. * * NOT the command line. `locale` is present only when the caller actually * asked for one, so a config's own `local=` survives; `dataPaths` is the * POST-cascade list, so a folder from `tdcv2.config.json` — and every pack * installed into it — reaches the worker; `baseDir` is the config file's own * directory, so a relative `src=` resolves where it did single-threaded. * * Each of those was missing from the parallel path, and the run came out in * the wrong language, with the bundled packs, unable to find its own files. * Silently, and without a flag: parallelism turns itself on by row count. * * @internal Used by the CLI's parallel path; not part of the published API. */ workerOptions(): { readonly source: string; readonly seed: string; readonly count: number; readonly locale: string | undefined; readonly defaultLocale: string | undefined; readonly dataPaths: readonly string[] | undefined; readonly baseDir: string; readonly engine: 1 | 2 | 3 | undefined; }; /** * Whether this config contains a `type="http"` generator, which makes a * network call and so can only be rendered on the async path * ({@link toStringAsync}). The CLI checks this to pick the right path. */ usesHttp(): boolean; /** * Async render — required when the config uses a `type="http"` generator, * identical to {@link toString} otherwise. The result is non-deterministic * when http is used: re-running does not reproduce. */ toStringAsync(): Promise; /** * Check whether the configured run might run out of memory. * * The estimate is rough: it assumes typical short-string cells and * typical per-card output size. For unusual configs (e.g. a single * huge templated string per card) the real usage may differ. The * function is fast — it does not actually run the render. * * Returns a diagnostic when the estimate is a large share of, or above, * the machine's TOTAL RAM (not the OS's instantaneous free number, which * under-reports usable memory — see `memory-estimate.ts`). Returns * `undefined` when the run is comfortably safe. Callers (CLI) typically * print the diagnostic and, when severity is 'error', abort before * rendering. */ preflight(options?: TdcPreflightOptions): Diagnostic | undefined; /** * Whether this run uses Engine 2 (streaming). True when `{ stream: true }` * was passed (CLI `--stream`) or `` is declared. Engine 2 * is O(#sequence slots) memory, so a huge `count` is not a memory risk. */ private isStreamEngine; /** * Which engine this run uses (1 in-memory, 2 streaming, 3 exact-on-disk), * resolved with the same precedence + disk-mode routing as the renderer. */ private resolveEngine; /** * The engine this run uses and why, in words — what `check` prints, and what * a run says when engine 3 was asked for and engine 1 runs the config. * * @internal Engine choice is a cost, not a contract — see `usesStreamEngine`. */ engineChoice(): EngineChoice; /** Resolved card count (CLI/API override beats env). Public for the parallel CLI. */ effectiveCount(): number; /** * How many records this run produces — the name the other four use. * * The same number `effectiveCount()` gives. Four implementations call it `count` * and the reference called it something else, which is the wrong way round for a * library read beside examples written in other languages. `effectiveCount()` * keeps working and is not deprecated. */ count(): number; /** * Whether this run uses Engine 2 (streaming) — required for parallel generation. * * @internal Engine choice is a cost, not a contract: it changes as the router learns, * and a caller branching on it would break on a release that only got faster. The CLI * asks `usesSeekableEngine` for the same reason and nothing else asks this one. */ usesStreamEngine(): boolean; /** * Whether this run resolves each row on its own — Engine 2 or Engine 3. * * What parallel generation actually needs. Both engines compute row `i` from * `i`, so a thread can be given a range and produce exactly the bytes the * whole-file run produces there. Engine 3 was left out of that for a while * and it cost every percent-plus-uniq config the ability to split at all, * which is exactly the kind of config large enough to want it. * * @internal Used by the CLI to decide whether `--jobs` can split the run; not part of * the published API, for the reason `usesStreamEngine` gives. */ usesSeekableEngine(): boolean; /** Resolved card count — CLI/API override beats env, env beats default. */ private resolveCount; /** * Count how many entries the sequence registry will have: every * simple sequence contributes 1, every compound sequence contributes * one entry per field, plus the 4 built-ins. */ private countSequenceSlots; /** * Write to disk from a config that must be prepared asynchronously. * * `type="http"` is the only one today. The CLI used to send it down a single * `toStringAsync()` path that ignored the file extension, so `-o out.parquet` * wrote the TEXT rendering under a `.parquet` name and exited 0 — the worst * shape a failure can take, because everything downstream trusted the name. */ writeFileAsync(path: string): Promise; /** * Render and write the textual output to disk. * * This is the streaming variant — chunks are written as they're * produced, so peak memory stays bounded regardless of `count`. The * file extension is for the caller's convenience only; the output * format is determined by the DSL (see docs/vision/04-output-formats.md). */ writeFile(path: string): void; /** * Stream the output in chunks. Each yielded string is either a * fixture block (before / after the main loop) or one full rendered * card. Memory stays bounded by the largest single card plus the * pre-materialised sequence registry — the whole output string is * never held at once. * * Concatenation of all yielded chunks equals `toString()` byte for * byte. */ toIterator(): Generator; /** * Return text output as a Node.js Readable stream. * * The stream is backed by a fresh `toIterator()` call, so every terminal * invocation remains deterministic and independent. This is the ergonomic * API for `tdc.toStream().pipe(fs.createWriteStream(...))`. */ toStream(): Readable; /** * Return generated rows as plain objects keyed by sequence name. * * This mode ignores // output wrappers. Simple * sequences become scalar properties, compound sequences become nested * objects, and parent-filtered rows use `undefined` for values that do * not apply to the current card. */ toArray(): TdcObjectRow[]; /** * Return the run as COLUMNS rather than rows. * * A column of numbers comes back as a `Float64Array`, everything else as a * plain array — the type says which, so a caller reading a numeric column * never has to check for a label hiding in it. Compound and pool sequences * contribute one key per field, spelled `Name.field`. * * Like `toArray()`, this ignores the // wrappers: it is the * generated data, not the file the data would have been written into. */ toColumns(): TdcColumns; /** Stream object rows one by one. */ iterate(): Generator; /** Return one object row by zero-based index. */ getAt(index: number): TdcObjectRow; /** * Resolve the effective seed and report whether it was auto-generated. * * Priority: explicit `seed` option (CLI/API) → `` in the * DSL → a freshly generated random seed. The result is cached so every * render terminal reuses it — this is what makes a seedless config * reproducible: the CLI prints the generated seed, and passing it back * as `--seed` reproduces the exact output. * * `generated` is true only in the third case (nothing was specified), * which is the signal the CLI uses to tell the user which seed to * record. */ seedInfo(): { readonly seed: string; readonly generated: boolean; }; private renderOptions; } //# sourceMappingURL=tdc.d.ts.map