Form Store Architecture#
Formisch's state management is built around a tree of field stores rooted at the form store. Every form is an InternalFormStore that extends InternalObjectStore , meaning the form itself is the root object node of the same hierarchical structure used by all nested fields.
Store Hierarchy and Field Store Types#
Each node in the tree is one of three InternalFieldStore variants, distinguished by a kind discriminant:
kind | Interface | children type | Use case |
|---|---|---|---|
'object' | InternalObjectStore | Record<string, InternalFieldStore> | Schema objects |
'array' | InternalArrayStore | InternalFieldStore[] | Dynamic arrays and tuples |
'value' | InternalValueStore | (none) | Leaf fields (string, number, boolean, date) |
The tree is built once by initializeFieldStore, which recurses into schema entries and array items to populate every children record before any user interaction occurs.
Path Types for Field Traversal#
Paths are typed as readonly (string | number)[] — Path is readonly PathKey[] where PathKey = string | number. The compile-time utility types enforce validity:
ValidPath<TValue, TPath>— rejects or autocorrects a path against the schema's input type.FieldPath<TValue>— the union of all addressable paths inside a given type (exact for the first 5 nesting levels, thenRequiredPath).PathValue<TValue, TPath>— extracts the value type at a given path, used to type the return value ofgetInputand the input argument ofsetInput.
At runtime, getFieldStore(formStore, path) traverses children[key] for each segment — object nodes via string keys, array nodes via numeric indices .
Dual Element Arrays: elements / initialElements#
Every field store (all three kinds) carries two parallel element arrays :
elements— the live list of DOM elements (HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement) currently registered to the field.initialElements— a snapshot that starts as the same array reference aselements.
The duality exists to keep focus and file-input reset correct after array reorder operations (copyItemState/swapItemState), which physically move elements between field stores. On reset, each store's elements is restored to its initialElements reference, targeting the original element instead of the one that was swapped in.
Framework adapters manage the lifecycle. In React, useField registers elements via a ref callback on mount and prunes disconnected elements in a cleanup effect. Crucially, the cleanup keeps initialElements in sync only when no reorder has occurred (i.e., when elements === initialElements still holds) .
get* / set* API Conventions#
All public methods in @formisch/methods follow a consistent dual-signature pattern: call without a path to operate on the entire form, or pass { path: [...] } to target a single field. TypeScript overloads enforce that the return/input type narrows to the correct sub-tree value.
Examples:
getInput(form)/getInput(form, { path })— reads form-level or field-level input.setInput(form, { input })/setInput(form, { path, input })— writes to form or field, then triggers validation.isDirty(form)/isDirty(form, { path })— checks dirty state at form or field level.
Internally, every method resolves to a getFieldStore call when a path is provided, then delegates to a core primitive (getFieldInput, setFieldInput, getFieldBool, etc.) exported from packages/core/src/field/index.ts. When no path is given, the form store itself (which implements InternalObjectStore) is passed directly, so no traversal is needed.
Key Entry Points#
| Purpose | File |
|---|---|
| Form store type | packages/core/src/types/form/form.ts |
| Field store types (all 3 kinds) | packages/core/src/types/field/field.ts |
| Path type utilities | packages/core/src/types/path/path.ts |
| Store initialization | packages/core/src/field/initializeFieldStore/initializeFieldStore.ts |
| Store traversal | packages/core/src/field/getFieldStore/getFieldStore.ts |
| Core field primitives (index) | packages/core/src/field/index.ts |
| React element lifecycle | frameworks/react/src/hooks/useField/useField.ts |