Skip to content
import { logger, persist, devtools, undoRedo } from '@hurum/core'

Middleware observes store lifecycle events. Middleware is read-only — it must not modify state or emit events.

type Middleware = {
name: string
onEvent?: (event: EventInstance, state: Record<string, unknown>) => void
onStateChange?: (prev: Record<string, unknown>, next: Record<string, unknown>) => void
onIntentStart?: (intent: IntentDescriptor, payload: unknown) => void
onIntentEnd?: (intent: IntentDescriptor, payload: unknown) => void
onError?: (error: Error, context: { intent: IntentDescriptor; payload?: unknown; command?: Command }) => void
}
HookWhen it fires
onEventAfter an event is applied to the store. Receives the event and the new state.
onStateChangeAfter state changes. Receives previous and next state.
onIntentStartWhen an intent begins execution.
onIntentEndWhen an intent finishes (success or failure).
onErrorWhen an executor throws an error. Receives the error and context (intent, payload, command).
const analytics: Middleware = {
name: 'analytics',
onIntentStart: (intent, payload) => {
trackEvent('intent_started', { mode: intent.mode })
},
onError: (error) => {
reportError(error)
},
}
const MyStore = Store({ state: { ... } })
.middleware(analytics)

An alternative to providing a Middleware object directly. The factory pattern is useful when middleware needs configuration options.

interface MiddlewareFactory {
create(): Middleware
readonly name: string
}

Both Middleware and MiddlewareFactory are accepted by .middleware().


Console logging middleware. Logs events, state changes, intent lifecycle, and errors.

function logger(options?: LoggerOptions): Middleware
OptionTypeDescription
filter(event: EventInstance) => booleanOnly log events that pass the filter.
import { logger } from '@hurum/core'
const MyStore = Store({ state: { count: 0 } })
.middleware(logger({ filter: (e) => !e.type.startsWith('Timer/') }))

Persistence middleware. Saves state to storage on every state change. Returns a handle with the middleware and a hydration function.

function persist(options: PersistOptions): PersistHandle

PersistOptions

OptionTypeDefaultDescription
keystringStorage key. Required.
storagePick<Storage, 'getItem' | 'setItem'>localStorageStorage backend.
serialize(state) => stringJSON.stringifyCustom serializer.
deserialize(raw: string) => Record<string, unknown>JSON.parseCustom deserializer.
pickstring[]Only persist these state keys.

PersistHandle

PropertyTypeDescription
middlewareMiddlewareThe middleware instance to register.
getPersistedState() => Record<string, unknown> | nullRead the last persisted state from storage.
import { persist } from '@hurum/core'
const storage = persist({ key: 'my-store', pick: ['todos', 'settings'] })
const MyStore = Store({ state: { todos: [], settings: {} } })
.middleware(storage.middleware)
// Hydrate from storage on startup
const store = MyStore.create({
initialState: storage.getPersistedState() ?? undefined,
})

Redux DevTools Extension integration. Sends events and state to the browser extension for inspection.

function devtools(options?: DevToolsOptions): DevToolsHandle

DevToolsOptions

OptionTypeDefaultDescription
namestring'Hurum Store'Display name in the DevTools panel.

DevToolsHandle

PropertyTypeDescription
middlewareMiddlewareThe middleware instance to register.
connect(store: { getState: () => Record<string, unknown> }) => voidCall after Store.create() to initialize the DevTools connection.
import { devtools } from '@hurum/core'
const dt = devtools({ name: 'Cart' })
const CartStore = Store({ state: { items: [] } })
.middleware(dt.middleware)
const store = CartStore.create()
dt.connect(store)

Undo/redo middleware. Tracks state history for in-app time-travel.

function undoRedo(options?: UndoRedoOptions): UndoRedoHandle

UndoRedoOptions

OptionTypeDefaultDescription
maxHistorynumber50Maximum number of state snapshots to retain.

UndoRedoHandle

PropertyTypeDescription
middlewareMiddlewareThe middleware instance to register.
undo() => Record<string, unknown> | nullRestore the previous state. Returns null if at the beginning.
redo() => Record<string, unknown> | nullRestore the next state. Returns null if at the end.
canUndo() => booleanWhether an undo operation is possible.
canRedo() => booleanWhether a redo operation is possible.
getHistory() => readonly Record<string, unknown>[]The full state history.
getPosition() => numberThe current position in the history.
import { undoRedo } from '@hurum/core'
const history = undoRedo({ maxHistory: 30 })
const EditorStore = Store({ state: { content: '' } })
.middleware(history.middleware)
const store = EditorStore.create()
// Later, in response to user actions:
const prevState = history.undo()
const nextState = history.redo()

  • Middleware hooks fire synchronously in registration order.
  • Middleware is purely observational. Mutating state or emitting events from middleware is unsupported and will cause undefined behavior.
  • Multiple middleware instances can be registered via .middleware(a, b, c) or chained .middleware(a).middleware(b).
  • Built-in middleware that returns a handle (persist, devtools, undoRedo) separates the middleware instance from its control API. Register the .middleware property; use the handle for interaction.