v3.x
Major release with breaking changes, declarative constraints, and full PICT-format model support.
Breaking Changes
- The
lengthoption has been renamed tostrength. - The
seedoption (used bysorters.hash) has been renamed tosalt. - The default export has been removed. Use
import { make } from "covertable"instead ofimport make from "covertable". PictConstraintsLexeris no longer exported. Use the newPictModelclass instead.PictModel.errors(string[]) has been replaced withPictModel.issues(PictModelIssue[]). Each issue carriesseverity,source,index,line, andmessage.preFilterandpostFilterhave been replaced by declarativeconstraints. See Constraint Logic and Migration Guide.
Why replace preFilter with constraints?
preFilter was a simple, flexible callback — you could write any JavaScript/Python function to accept or reject a row. So why give up that freedom?
The problem is that preFilter is a black box. The engine can only call it and observe true or false. It cannot reason about why a row was rejected, which fields the filter depends on, or whether a partial row could ever satisfy it. This leads to:
- Blind trial and error — The engine must try every candidate and discard failures. With many constraints and factors, this becomes extremely slow.
- No early pruning — Even when a pair like
(Region=US, Currency=EUR)is obviously impossible, the engine cannot detect this until the row is fully filled. - No constraint propagation — Chain constraints (e.g.
Language=ja → Region∈{JP,APAC}→Currency∈{JPY,USD}) cannot be followed ahead of time, leading to dead-end rows that waste computation.
Declarative constraints solve this by giving the engine visibility into the constraint structure:
- Three-valued logic — When a referenced field is not yet set, the result is
null(unknown) rather thanfalse. The engine can defer judgment instead of rejecting prematurely. extractKeys()— Each constraint declares its dependencies, so the engine only re-evaluates constraints when relevant fields change.- Forward checking — Before committing a pair, the engine prunes the domains of unfilled factors. If any domain becomes empty, the pair is rejected without filling the entire row.
- Initial pruning — Infeasible pairs are detected and removed before generation even starts, reducing the search space.
The result: a 22-factor, 53-constraint model (heavy.pict) that would churn for minutes with preFilter now completes in seconds with 100% pairwise coverage.
preFilter / postFilter still work for backward compatibility, but constraints is the recommended approach for v3.
New Features
- Declarative constraints — The new
constraintsoption acceptsCondition[]objects evaluated under Kleene three-valued logic. Replaces opaquepreFilterfunctions with structured, analyzable constraint trees. - Forward checking — Constraint propagation prunes infeasible pairs before and during generation. Detects constraint chain conflicts (e.g.
Language=de→Region=EU→Currency IN {EUR,GBP}). - Peer propagation — Extends forward checking to handle multi-value domain bottlenecks.
- Controller.stats — Generation statistics including
totalPairs,prunedPairs,coveredPairs,progress,rowCount,uncoveredPairs, andcompletions. PictModel— Parses a complete PICT-format model (parameters, sub-models, constraints, invalid values, weights) and generates rows directly. Exported fromcovertable/pict.- Sub-models — The
subModelsoption (and PICT{ A, B } @ Nsyntax) lets you apply a different N-wise strength to a specific group of factors. - Weights — The
weightsoption (and PICTvalue (N)syntax) biases value selection during the row-completion phase. - Presets — The
presetsoption lets you specify rows that must appear in the output. Equivalent to PICT's/e:fileseeding feature. - Invalid values — PICT's
~valuesyntax marks values as negative-test inputs. At most one invalid value per row. Output includes~prefix. - Aliases — PICT's
value | aliassyntax lets you refer to a value by multiple names in constraints. - Unconditional constraints — PICT-style invariants without an
IFclause (e.g.[A] <> [B];). caseInsensitiveoption —PictModelmatches PICT's default case-insensitive behavior.weightsByValuehelper — Convert value-keyed weights into index-keyed form.- Custom comparer — Override comparison operators for constraint evaluation.
- Rescue phase — Remaining uncovered pairs are retried individually after greedy + close.
- NeverMatch with diagnostics — When
make()can't cover all pairs, it throwsNeverMatchwithuncoveredPairsdetailing which pairs failed and which constraints are involved.
Tooling
- Build system migrated from webpack to Vite (library mode, dual ESM/CJS output).
- Package manager switched to pnpm.
- TypeScript upgraded to 5.x.
- Interactive Compatible PICT online tool with Web Worker support.