A codec FAMILY: one declaration of a codec as a function of per-branch parameters, memoized so each distinct parameter list builds exactly once. This is how a field whose contract varies with a condition (per-resolution aspect ratios, a per-tier bound) stays churn-free without hand-rolling a dictionary of pre-built variants:
const AR = defFamily((res: Resolution) => aspectRatioCodec({ options: TABLE[res], default: '16:9' })); // in resolve: f.field('aspectRatio', AR(resolution))
Parameters are the cache key (JSON-serialized), so they must be
function-free — primitives or plain config objects — and should come from
a finite set: each distinct combination is cached for the module's
lifetime. cachedFactory is this exact mechanism with a custom key
extractor; they are ONE idea, and may fold into one export in 0.4.0.
The public surface — the GRAPH MODEL and its runtime, deliberately small:
form-graph/defs, defFamily, and the types an author writes against.FormStoreis type-only — stores are created viagraph.createStore(), never constructed directly.The ENGINE is not exported at all — defineForm, FormDefinition, codec(), Fields/FieldOptions, the resolver internals. Every form is expressible as a graph (the generation-scale hub included, proven by the parity suite), so the engine is an implementation layer: package-internal code and the corpus import it from its modules directly; the exports map makes it unreachable for consumers.