@umpire/store
@umpire/store is the generic foundation for store-backed adapters. It expects a strict store contract:
getState()returns the full store snapshotsubscribe((next, prev) => unsubscribe)provides both current and previous state
That strictness is intentional. It keeps transition-aware foul detection in one place and lets thin ecosystem packages normalize into the same contract.
Install
Section titled “Install”yarn add @umpire/core @umpire/storefromStore()
Section titled “fromStore()”function fromStore< S, F extends Record<string, FieldDef>, C extends Record<string, unknown> = Record<string, unknown>,>( ump: Umpire<F, C>, store: { getState(): S subscribe(listener: (state: S, prevState: S) => void): () => void }, options: { select: (state: S) => InputValues<F> conditions?: (state: S) => C },): UmpireStore<F>Return Surface
Section titled “Return Surface”interface UmpireStore<F extends Record<string, FieldDef>> { field(name: keyof F & string): FieldStatus get fouls(): Foul<F>[] getAvailability(): AvailabilityMap<F> subscribe(listener: (availability: AvailabilityMap<F>) => void): () => void destroy(): void}Mapping Your Store with select()
Section titled “Mapping Your Store with select()”select maps your store state to the flat { [fieldName]: value } shape Umpire expects. conditions carries external context rules can read but that isn’t a field itself (plan tier, user role, feature flags). Neither has to come from the same slice.
See Selection for the full breakdown of patterns.
Shim Packages
Section titled “Shim Packages”@umpire/zustandis a zero-shim re-export because Zustand already satisfies(next, prev).@umpire/reduxtracks previous state internally and delegates here.@umpire/piniasnapshots previous$stateand delegates here.@umpire/tanstack-storesnapshots previous.stateand delegates here.@umpire/vuexsnapshots previousstateand delegates here.
Signal-Based Stores
Section titled “Signal-Based Stores”Signal-first libraries like Jotai, Valtio, MobX, and Preact signals are not covered by this package. They do not expose the same getState()/subscribe() surface, so they belong in a different bridge layer alongside @umpire/signals.