Skip to content
Hurum Hurum

HURUMHURUM

Predictable state machines for TypeScript. Every state change follows one path.

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 notified

Whether it’s a simple counter increment or a complex async API call with error handling, every state change follows this exact path.

PackageDescription
@hurum/coreCore state management. Framework-agnostic, zero dependencies.
@hurum/reactReact 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 React
function 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>
)
}

Get started →