Skip to content
import { Store } from '@hurum/core'

Creates a store builder from an initial state object. Returns a chainable builder that is also a StoreDefinition — you can call .create() at any point or continue chaining.

function Store<TState extends Record<string, unknown>>(
config: { state: TState },
): StoreBuilder<Record<string, never>, TState, Record<string, never>>
ParameterTypeDescription
config.stateRecord<string, unknown>The initial raw state. Each key becomes a state field.

A StoreBuilder with chainable methods.

const CounterStore = Store({ state: { count: 0, multiplier: 2 } })
.on(CounterEvent.incremented, (state, { amount }) => ({
...state,
count: state.count + amount,
}))
.computed({
doubled: (state) => state.count * 2,
})
.intents(CounterIntents)
.executors(IncrExec, DecrExec)
const store = CounterStore.create()

Registers a state reducer for a single event type.

.on(
event: EventCreator<TType, TPayload>,
handler: (state: State, payload: TPayload) => RawState,
): StoreBuilder

The state parameter includes raw state, computed values, and resolved nested state. The return value must be the raw state shape only (no computed fields).

.on(namespace, handlers) — namespace handler

Section titled “.on(namespace, handlers) — namespace handler”

Registers multiple handlers from an event namespace at once.

.on(
events: EventNamespace,
handlers: { [K in keyof EventNamespace]?: (state: State, payload: Payload) => RawState },
): StoreBuilder
// Per-event
.on(CartEvent.itemAdded, (state, { item }) => ({
...state,
items: [...state.items, item],
}))
// Namespace
.on(CartEvent, {
itemAdded: (state, { item }) => ({ ...state, items: [...state.items, item] }),
cleared: () => ({ items: [] }),
})

Defines derived state fields that recalculate when their dependencies change.

.computed<C extends Record<string, (state: ResolvedState) => unknown>>(
def: C,
): StoreBuilder

See the Computed API reference for details on dependency tracking and behavior.

.computed({
total: (state) => state.items.reduce((sum, item) => sum + item.price, 0),
isEmpty: (state) => state.items.length === 0,
})

Registers an intents container. This enables store.send.intentName() shorthand.

.intents(container: IntentsContainer): StoreBuilder

Registers one or more executors. Each executor’s command is matched at dispatch time.

.executors(...execs: Executor[]): StoreBuilder
.intents(CartIntents)
.executors(AddItemExec, RemoveItemExec, CheckoutExec)

Declares the dependency types for this store. Dependencies are injected at Store.create({ deps }).

.deps<D extends Record<string, unknown>>(): StoreBuilder
.deps<{ repo: CartRepo; analytics: Analytics }>()

Maps parent dependencies to a nested child store’s dependencies.

.childDeps<K extends keyof State & string>(
key: K,
mapper: (deps: TDeps) => ChildDeps,
): StoreBuilder
.childDeps('transaction', (deps) => ({
transactionRepo: deps.repo.transactions,
}))

Registers a relay handler that transforms one event into zero or more other events. Used for cross-store event coordination.

.relay(
event: EventCreator<TType, TPayload>,
handler: (event: EventInstance, state: State) => EventInstance[],
): StoreBuilder
.relay(ChildEvent.completed, (event, state) => {
if (state.items.length === 0) {
return [ParentEvent.allCompleted()]
}
return []
})

Registers one or more middleware instances or factories.

.middleware(...mws: (Middleware | MiddlewareFactory)[]): StoreBuilder

Instantiates the store. Returns a live StoreInstance.

.create(options?: StoreCreateOptions): StoreInstance

PropertyTypeDescription
initialStatePartial<ResolvedState>Override initial state values. Deep merged with the default state.
depsPartial<TDeps>Inject dependencies. Shallow merged.
const store = MyStore.create({
initialState: { count: 10 },
deps: { repo: new CartRepo(apiClient) },
})

A live store instance returned by .create(). This is the primary runtime API.

MethodSignatureDescription
send(prepared: PreparedIntent) => IntentRefDispatch a prepared intent. Returns an IntentRef for cancellation.
send(intent: IntentDescriptor, payload) => IntentRefDispatch with explicit intent descriptor and payload.
send.intentName(payload) => IntentRefNamed shorthand. Available when intents are registered via .intents().
cancel(ref: IntentRef) => voidCancel a specific running intent by its ref.
cancelAll() => voidCancel all running intents.
getState() => StateGet the current combined state (raw + computed + nested).
subscribe(cb: (state) => void) => () => voidSubscribe to state changes. Returns an unsubscribe function.
subscribe('events', cb: (event) => void) => () => voidSubscribe to raw event emissions.
selector(fn: (state) => T) => Selector<T>Create a memoized derived state selector.
dispose() => voidCleanup: cancels all intents, disposes nested children, clears listeners.
PropertyTypeDescription
scopeScopeOf<TRawState>Access nested child store instances. Keys match the nested field names.

There are three ways to dispatch an intent:

// 1. PreparedIntent (recommended)
store.send(CartIntents.addItem({ item }))
// 2. Named shorthand
store.send.addItem({ item })
// 3. Descriptor + payload (legacy)
store.send(CartIntents.addItem, { item })

All three return an IntentRef.


After store.dispose():

  • All running intents are cancelled.
  • All nested child stores are disposed.
  • All listeners are cleared.
  • Calling store.send() throws an error.
  • Calling emit() from an already-running executor is silently ignored.

subscribe(cb) fires synchronously whenever state changes. The callback receives the full combined state (raw + computed + nested). Returns an unsubscribe function.

subscribe('events', cb) fires for every event applied to the store, including events bubbled from nested children.


  • The store builder is immutable. Each method returns a new builder instance.
  • The builder itself is a StoreDefinition — you can pass it directly to testing APIs like TestStore().
  • initialState uses deep merge. Nested objects are merged recursively. deps uses shallow merge.
  • Sending an intent after disposal throws. This is intentional — it surfaces lifecycle bugs early.