/** * `repeat="N"` / `repeat="A..B"` — emit SEVERAL values per row instead of one. * * The whole difficulty is determinism. Every engine here computes a row without * computing its predecessors (that is what makes streaming and `--jobs` * correct), which requires a FIXED number of PRNG draws per row. A variable * repeat count would break that. * * The answer is to decide the LENGTHS first, as an exact quota, before any * value exists. The total number of slots is then a known number, so nothing * is generated and thrown away — which is what keeps `` * percentages exact even when the list length varies. Rows stay independent * because slots are grouped by length: a row finds its slice from its own * position, never from a running total over its predecessors. * Spec: docs/specs/2026-07-19-repeated-values-lists-and-mix-ground-truth.md §3. */ import { type AccumulateOp } from './accumulate.js'; /** Upper bound on `repeat`, so a big value cannot silently make a run 1000× slower. */ export declare const MAX_REPEAT = 64; /** Joins repeated values in text output when `separator` is not given. */ export declare const DEFAULT_SEPARATOR = ","; export interface RepeatSpec { /** * `lengths=`: the share of rows that get each possible length, `min` first. * * Without it every length is equally likely, and "equally" here is exact * rather than approximate — the lengths are laid out as a Hamilton quota, so * `repeat="0..5"` over 20,000 rows gives 16.66% to each of the six, with no * sampling noise at all. That is the wrong shape for every real one-to-many * relationship: orders per customer, visits per patient, transactions per * account are all heavy-tailed, most parents have one or two children and a * few have twenty. * * The shares go HERE, in the spec, rather than into a per-row draw, because a * per-row count would break the property this whole file exists to protect — * see the header. Deciding the lengths first, as a quota, keeps rows * independent AND makes the shape exact: `lengths="40,25,15,10,7,3"` means 40% * of parents have none, not "about 40%". */ readonly lengths?: readonly number[] | undefined; /** Fewest values per row; may be 0 (empty list). */ readonly min: number; /** Most values per row — also the per-row draw budget. */ readonly max: number; readonly separator: string; /** `accumulate=`: the list is replaced by its running total before joining. */ readonly accumulate?: AccumulateOp | undefined; /** * `distinct=`: the row's values are drawn WITHOUT replacement, so a cell * cannot hold the same value twice. * * This changes the regime the column is built in, which is the whole reason * `percent` is refused beside it. Ordinarily a listed column lays its values * out over the entire run as an exact quota; under `distinct` it draws per * row instead, because keeping an exact whole-run quota AND a per-row * guarantee at once costs either streaming or the randomness of the sample. * The trade is deliberate: frequencies stay approximate, rows stay * independent, and `--jobs` keeps working. */ readonly distinct?: boolean | undefined; } /** Bounded retries before a `distinct` draw admits it cannot find a fresh value. */ export declare const DISTINCT_MAX_TRIES = 64; export declare class RepeatError extends Error { readonly name = "RepeatError"; } /** * Generator types that cannot carry `repeat`, and why. Refused by the validator * so BOTH engines behave the same — the in-RAM builder could technically do * these, and letting it silently diverge from the default engine is the exact * "accepted and quietly ignored" trap TDC128 exists for. * * The reason is structural rather than unfinished work: * * - **`increment` / `decrement` / `timeseries` / `pattern`** are positional: * the value is a function of the row index. Under a VARIABLE repeat, the * index an element should get depends on the total length of every earlier * row — which is random. That makes the row un-computable on its own, and * row independence is what streaming and `--jobs` are built on. */ export declare const REPEAT_UNSUPPORTED: Readonly>; /** Why `repeat` is refused on this generator type, or undefined when allowed. */ export declare function repeatUnsupportedReason(genType: string): string | undefined; /** * Parse `repeat` / `separator`. Returns undefined when `repeat` is absent, in * which case the generator keeps its plain one-value-per-row behaviour. */ export declare function parseRepeat(attrs: Record): RepeatSpec | undefined; /** * `lengths="40,25,15,10,7,3"` — one share per possible length, `min` first. * * Refused rather than repaired when the count is wrong or the shares do not sum * to 100: a fan-out written with five shares for six lengths is a config whose * author had a shape in mind, and guessing which of the six they forgot would be * the sort of silent repair this project spends its time removing. The sum rule * is `percent=`'s, deliberately — one arithmetic for shares everywhere. */ export declare function parseLengths(raw: string | undefined, min: number, max: number): readonly number[] | undefined; /** `distinct="true"`. Anything other than the two words is refused by the validator. */ export declare function readDistinct(attrs: Record): boolean; /** * Draw `keep` DIFFERENT values from a weighted list, using one uniform per * attempt off the row's own stream. * * Weights survive — a frequent name is still more likely to be picked first — * but the exact whole-run quota does not, which is the documented price of * `distinct` and the reason `percent` may not appear beside it. * * The caller has already been told (by the validator) that the pool is big * enough, so running out here means a pool that only became known at run time. * That case throws rather than returning a short list: a list quietly shorter * than `repeat` asked for is exactly the silent-and-wrong outcome this whole * feature exists to prevent. */ export declare function drawDistinct(values: readonly string[], weights: readonly number[], keep: number, nextUniform: () => number, describePool: () => string): string[]; /** * How many values row `i` keeps, from one uniform in [0, 1). A fixed range maps * every draw to the same count — the draw is still spent, so the budget (and * therefore the row's independence) does not depend on the attribute's shape. */ export declare function repeatCountFrom(uniform: number, spec: RepeatSpec): number; /** * Lengths first, then filling — the plan that makes a VARIABLE repeat keep * exact percentages. * * The naive way gives every row `max` slots and throws away the ones past `N`. * The discarded slots already consumed quota, so the declared split stops * coming out exact. Deciding the lengths UP FRONT removes the waste entirely: * the total number of slots is then a known number, and the value quota is * planned over exactly that many. * * Row independence survives because slots are grouped BY LENGTH. All rows of * length L share one contiguous block, and the block sizes follow from the * length quota — which is computed once, before any row. So a row finds its * slots from its own position alone, never from a running total over its * predecessors (which would be the thing that breaks streaming and `--jobs`). * This is the same rank-within-a-subset trick `parent=` already uses. */ export interface RepeatPlan { readonly spec: RepeatSpec; /** Exact total number of value slots across all rows. */ readonly totalSlots: number; /** How many values the row at permuted position `p` keeps. */ lengthAt(p: number): number; /** First slot index owned by the row at permuted position `p`. */ slotStartAt(p: number): number; } /** * Build the plan. `prng` is consumed only for the length quota's rounding, so * it must be a dedicated stream — never the one producing values. */ export declare function planRepeat(spec: RepeatSpec, rowCount: number, counts: readonly number[]): RepeatPlan; /** * The shares `planRepeat` quotas by: `lengths=` when the config gave one, an * even split otherwise. * * Every caller — the in-memory builder, the keyed-draw layout and the streaming * builder — asks this one function, so a declared shape reaches all three * without any of them knowing it exists. */ export declare function repeatLengthPercents(spec: RepeatSpec): number[]; /** * Take one row's slice out of a flat buffer holding `max` values per row, and * join it. `values` is laid out row-major: row i occupies `[i*max, (i+1)*max)`. */ export declare function joinRepeatRow(values: readonly string[], row: number, keep: number, spec: RepeatSpec): string; /** * The last step every repeat list goes through: accumulate, then join. * * One function rather than two copies because there are exactly two places a * list becomes a cell — here for the streaming engines and inside * {@link buildRepeatedValues} for the in-memory one — and a running total that * appeared on one engine and not the other is the failure this shape prevents. */ export declare function joinPartsOpt(parts: readonly string[] | undefined, spec: RepeatSpec): string | undefined; export declare function joinParts(parts: readonly string[], spec: RepeatSpec): string; /** * Produce `count` rows of repeated values. * * `buildFlat` is the caller's ordinary "give me N values" builder — it already * applies `anomaly`/`missing`/formatting per value, which is precisely why * those modifiers come out per ELEMENT here with no extra work. * * The draw order is fixed and must not be rearranged: all `count` length draws * first, then the elements. Both engines rely on this being stable. * * `flagTextOut`, when given, receives the anomaly label per row — itself a list * parallel to the values, so it says WHICH element of a batch spiked rather * than merely that one did. */ export declare function buildRepeatedValues(spec: RepeatSpec, count: number, prng: () => number, buildFlat: (n: number, flagsOut?: boolean[]) => string[], flagTextOut?: string[]): string[]; /** * The same generator with `repeat` removed. The streaming layer resolves each * element itself, so the per-row builder it delegates to must not repeat again. */ export declare function withoutRepeat(gen: { type: string; attrs: Record; }): { type: string; attrs: Record; }; //# sourceMappingURL=repeat.d.ts.map