form-graph
    Preparing search index...

    Interface FieldDef<T, M, O>

    PROTOTYPE E — the field as ONE FUNCTION returning its whole definition.

    There are no channels: no meta patch, no constrain mechanism, no refine hook, no codec-vs-options split. A field is (ctx, ext) => definition, where the definition carries everything — schemas (full zod, inline, conditional), default, meta, scope, correction policy — or null when the field does not exist this pass.

    const g = defineGraph() .field('steps', (ctx) => ctx.distilled ? null : slider({ min: 10, max: 50, default: 30, presets: ctx.draft ? DRAFT_PRESETS : PRESETS, })) .field('prompt', (ctx) => ({ input: z.string().optional(), output: z.string().refine((v) => !ctx.required || v.trim().length > 0), default: '', }));

    Performance model: the definition function runs every pass (cheap object construction). Schema construction is what costs (~25–35µs per codec, measured) — the def helpers (slider/enumOf/textOf) cache their schemas automatically, keyed on the exact values the schemas are built from, so there is nothing to declare and staleness is impossible. Raw inline zod rebuilds per pass; wrap a hot raw definition in defFamily if a profile ever says so.

    interface FieldDef<T, M = undefined, O extends SchemaLike<T> = SchemaLike<T>> {
        coerce?: (raw: unknown) => T;
        correct?: (
            value: T,
        ) =>
            | { detail?: Record<string, unknown>; reason: string; value: T }
            | undefined;
        default?: T | (() => T);
        emit?: string | false;
        input?: SchemaLike<unknown>;
        meta?: M | ((value: T) => M);
        output: O;
        scope?: Scope;
        toOutput?: (value: T) => unknown;
        refine?(output: O & Refinable<T>): SchemaLike<T>;
    }

    Type Parameters

    Index
    coerce?: (raw: unknown) => T
    correct?: (
        value: T,
    ) =>
        | { detail?: Record<string, unknown>; reason: string; value: T }
        | undefined
    default?: T | (() => T)
    emit?: string | false

    Wire disposition: a string emits this key's value under that name in parsed data; false keeps it out of parsed data entirely (it still resolves, guards, and holds intent). Use the pair for a SELECTION the form keeps and a DERIVED value the wire carries — never overwrite one key with two facts.

    input?: SchemaLike<unknown>

    Deliberately untyped against T: the input pass is LENIENT — it may accept fragments (an id where the state holds a full record) that only become T after coercion or later enrichment. output is the contract; typing input against T forced a cast at every lenient definition.

    meta?: M | ((value: T) => M)
    output: O
    scope?: Scope
    toOutput?: (value: T) => unknown
    • Per-pass narrowing over the CACHED base: receives this def's output schema and returns the narrowed one, judged live each pass (one small wrapper + one safeParse). Use this instead of rebuilding output inline per pass — a fresh output schema every pass is exactly what the codec-churn warning names.

      Parameters

      • output: O & Refinable<T>

      Returns SchemaLike<T>