Single Path
Every state change flows through Intent → Command → Executor → Event → Store. No shortcuts, no exceptions.
Single Path
Every state change flows through Intent → Command → Executor → Event → Store. No shortcuts, no exceptions.
Predictable
Events record facts. State transitions are pure functions. You always know what happened and why.
TypeScript-First
Full type inference from executor inputs to event payloads. No manual type annotations needed.
Testable at Every Layer
Five test utilities target specific layers: TestStore, TestExecutor, TestReducer, TestComputed, TestIntent.
Intent (what the user wants to do) → Command (what to execute) → CommandExecutor (side-effect boundary) → emit(Event) (record what happened) → Store.on (pure state transition) → Computed (derived state, recalculated eagerly) → Subscribers notifiedWhether it’s a simple counter increment or a complex async API call with error handling, every state change follows this exact path.
| Package | Description |
|---|---|
@hurum/core | Core state management. Framework-agnostic, zero dependencies. |
@hurum/react | React bindings: Provider, useStore, Store.use.*, withProvider. |
import { Store, Events, Event, CommandExecutor, Intents, Intent } from '@hurum/core'
// 1. Define events (what happened)const CounterEvent = Events('Counter', { incremented: Event<{ amount: number }>(),})
// 2. Define executor (side-effect boundary)const [IncrementCommand, IncrementExecutor] = CommandExecutor< { amount: number }>((command, { emit }) => { emit(CounterEvent.incremented(command))})
// 3. Define intent (what the user wants)const CounterIntents = Intents('Counter', { plusClicked: Intent(IncrementCommand),})
// 4. Define store (state + transitions + everything)const CounterStore = Store({ state: { count: 0 } }) .on(CounterEvent, { incremented: (state, { amount }) => ({ ...state, count: state.count + amount }) }) .computed({ doubled: (state) => state.count * 2 }) .intents(CounterIntents) .executors(IncrementExecutor)// 5. Use in Reactfunction Counter() { const count = CounterStore.use.count() const doubled = CounterStore.use.doubled()
return ( <div> <p>{count} (doubled: {doubled})</p> <button onClick={() => CounterStore.send.plusClicked({ amount: 1 })}>+1</button> </div> )}