@zupertools/form-core
v0.4.0
Published
Framework-agnostic core for zuperform
Maintainers
Readme

zuperform (core)
The framework-agnostic core of zuperform. It handles form state, validation, dirty/touched tracking, and dot-path field access. It has no UI framework dependency, only Zod.
What's in here
createFormStore(schema, defaultValues)- creates a reactive store that holds form values, errors, and touched state. The store uses a subscribe/snapshot pattern compatible withuseSyncExternalStoreand similar primitives in other frameworks.validateAll(schema, values)- runssafeParseAsyncand returns both the result and a flatRecord<string, string>of errors keyed by dot-path.validateField(schema, values, path)- extracts the field's own schema and parses just that value, so async refinements on other fields don't block it.getSchemaAtPath(schema, path)- traverses a Zod schema by dot-path and returns the schema at that location, unwrappingoptional,nullable, anddefaultwrappers along the way.coerceToSchema(schema, rawValue)- coerces a raw DOM string value to the type the schema expects. Handles Zod numbers, booleans, and dates.stringifyValue(value, inputType)- takes the value and input type to return a string ready for thevalueattribute of an input element.getIn(obj, path)/setIn(obj, path, value)- immutable dot-path read and write helpers.getLeafValue(obj, path)- wrapper aroundgetInthat returns the value asLeafValue.flattenPaths(obj)- returns all leaf paths in a nested object as a flat array of dot-path strings.deepEqual(a, b)- structural equality check used internally for dirty tracking.reverseMapDeps(deps)- reverse maps a deps object.
Building an adapter
The main thing you need is createFormStore. It returns a FormStore object:
import { createFormStore } from '@zupertools/form-core'
const store = createFormStore(schema, defaultValues)
// Read state
store.getSnapshot() // { values, errors, touched }
store.getErrors()
store.getValues()
store.getValue(path)
store.isDirty(path)
store.isTouched(path)
// Write state
store.setValue(path, value)
store.setRawValue(path, rawValue) // coerces rawValue against the field's schema first
store.touch(path)
store.reset(nextValues?)
store.resetField(path, nextValue?)
// Validation
store.validate() // full async parse, returns ZodSafeParseResult
store.validateField(path) // async, returns error message or undefined
store.setFieldError(path, message, append?)
store.setIssues(issues, merge?)
store.clearFieldErrors(path)
// Subscribe to changes (compatible with useSyncExternalStore or similar)
const unsubscribe = store.subscribe(() => {
const { values, errors, touched } = store.getSnapshot()
})Validation is async throughout (validate and validateField both return promises), so schemas with .refine(async ...) work without any extra config. The core has no notion of debouncing; that's left to the adapter, since only the adapter knows about user interaction timing.
License
MIT
