Reusable definitions
There is no reuse machinery — definitions and graphs are ordinary values, so reuse is ordinary code. Three patterns cover everything the demos and the generation-scale ports needed:
1. Definition factories
A function returning a definition, parameterized by whatever varies:
const toppingsDef = (budget: number) => ({
input: z.array(z.string()).optional(),
output: z.array(z.string()),
default: [] as string[],
meta: { budget },
correct: (picked: string[]) => trimToBudget(picked, budget),
});
.field('toppings', (c) => toppingsDef(SIZES[c.size].budget)) Helpers used inside a factory keep their automatic schema caching; a factory that hand-builds
zod pays construction per distinct call — wrap the schema part in defFamily if it's ever hot.
2. Sections are just graphs, mounted with .use()
There is no section concept to learn: a reusable section IS a defineGraph. If
it needs facts from wherever it gets mounted, it declares them as its Ext — at
the mount point, the parent's ext with the c-so-far merged over it is what the child
receives, so a need is satisfied by a prior field or by the parent's own c._ext. Its fields,
registry, and effects join the chain.
export const contact = defineGraph()
.field('email', EMAIL)
.field('isBusiness', boolOf())
.field('company', (c) => (c.isBusiness ? COMPANY : null))
.effect(contactRules); // the section's own coupling rides the mount
// another graph NEEDS what contact declares — that's its Ext:
export const payment = defineGraph<{ isBusiness: boolean }>()
.field('paymentMethod', (c) => enumOf({
options: METHODS,
default: 'card',
gate: { invoice: !c._ext.isBusiness && 'invoice_requires_business' },
}));
// mounting is chain-linear; payment's need is met by contact's field:
const graph = defineGraph()
.use(contact)
.field('billingSameAsShipping', boolOf({ default: true }))
.use(payment); A missing REQUIRED need is a type error at the mount point naming the key; optional needs
read as undefined when nothing upstream declares them. Hubs mount the same way
— .use() accepts anything graph-shaped.
3. The same section, mounted more than once
Field keys are unique form-wide, so mounting one section twice needs a key PREFIX — a
transform a standalone graph can't express. For that, .use() also takes a plain
function (use(fn) is fn(g)), and an optional when makes the whole mount conditional (keys go optional):
g.use((g) => withAddress(g, 'shipping')); // required keys g.use((g) => withAddress(g, 'billing', (c) => !c.billingSameAsShipping));
The checkout demo composes all three patterns into one chain; the LTX/Wan generation ports
compose shared prefixes and suffixes across five version graphs. Effects that belong to a
section attach with .effect(unit) and ride into the form's reconcile via graph.effects.