Store<EM extends EventMapBase, R extends string, S extends Record<R, any>>

class
store/Store.ts:76

implements StoreInstance<R, S, EM>

Public Store surface.

The concrete Store implements this as StoreInstance<R, DeepReadonly<S>, EM>.

Type parameters

NameTypeDescription
EMextends EventMapBaseReducer name union.
Rextends stringState record (already readonly at the call site).
Sextends Record<R, any>Event map.

Constructor

constructor

Creates a store from a StoreSpec.

signature
new<EM, R, S>(spec): Store<EM, R, S>

Creates a store from a StoreSpec.

Type parameters

NameTypeDescription
EMextends EventMapBase
Rextends string
Sextends Record<R, any>

Parameters

NameTypeDescription
specStoreSpec<R, S, EM>Store configuration (name, reducers, middleware, optional effects).

Returns

Store<EM, R, S>

Properties

NameTypeDescription
namestringStore name (used by DevTools & diagnostics).

Methods

__devtoolsIntrospect

Returns a structured introspection snapshot for DevTools UIs.

signature
__devtoolsIntrospect(): { atomic: { property: string; reducer: string }[]; coarse: number; dedupHits: number; effects: { channel: string; description?: string; name?: string; type: string }[]; event: { channel: string; phase: string; type: string }[]; middleware: { description?: string; name?: string; when?: unknown }[]; queueDepth: number; reducers: { name: string; when: When<EM> | undefined }[] }

Returns a structured introspection snapshot for DevTools UIs.

Returns

{ atomic: { property: string; reducer: string }[]; coarse: number; dedupHits: number; effects: { channel: string; description?: string; name?: string; type: string }[]; event: { channel: string; phase: string; type: string }[]; middleware: { description?: string; name?: string; when?: unknown }[]; queueDepth: number; reducers: { name: string; when: When<EM> | undefined }[] }

static buildAncestorPaths

Builds ancestor paths for a dotted path. For "a.b.c", returns ["a", "a.b", "a.b.c"]. Leading dots are trimmed.

signature
buildAncestorPaths(path): string[]

Builds ancestor paths for a dotted path. For "a.b.c", returns ["a", "a.b", "a.b.c"]. Leading dots are trimmed.

Parameters

NameTypeDescription
pathstringDotted path string.

Returns

string[]Array of ancestor paths.

Example

example
Store.buildAncestorPaths('x.y.z'); // ['x','x.y','x.y.z']

connect

Connects a **fine-grained** listener to a dotted path under a slice.

signature
connect(spec, h): () => void

Connects a **fine-grained** listener to a dotted path under a slice.

Parameters

spec — object with:

NameTypeDescription
propertystring
reducerR
NameTypeDescription
h(chg: Change) => voidHandler receiving a Change with { oldValue, newValue, path }.

Returns

() => voidUnsubscribe function.

Example

example
const off = store.connect(
  { reducer: 'todos', property: 'items.0.title' },
  (chg) => console.log('title changed:', chg.newValue)
);
off();

Example

example
// Listen to any item title change
const off = store.connect(
  { reducer: 'todos', property: 'items.*.title' },
  (chg) => console.log('some title changed')
);

dispose

Cleanup resources (timers, etc.) when disposing the store. Call this if you're dynamically creating/destroying stores.

signature
dispose(): void

Cleanup resources (timers, etc.) when disposing the store. Call this if you're dynamically creating/destroying stores.

Returns

void

Example

example
const store = createStore({ ... });
// later
store.dispose();

emit

Emit a typed event (channel, type, payload). Returns a promise that resolves when the event has been processed.

signature
emit<C, T>(channel, type, payload, opts?): Promise<void>

Emits a typed event (channel, type, payload). Events are queued and processed **sequentially** (FIFO). **Pipeline per event:** the *reduce phase* (steps 1-4) runs **synchronously**, so getState() reflects the change as soon as emit() returns; the *effect phase* (step 5) runs afterwards, asynchronously. 1. **Deduplication** (opt-in) - Skip when content-dedup is enabled (dedupWindowMs > 0) or a matching dedupKey recurs; off by default 2. **Middleware** (sync) - Pre-reducer hooks; may cancel by returning false 3. **Reducers** (sync) - state updates + fine-grained path notifications 4. **Subscribers + coarse** (sync) - event subscribers (fire-and-forget) then coarse listeners (only if state changed) 5. **Effects** (async) - side-effects keyed by (channel, type); the returned promise resolves once they complete **Change Detection**: Uses reference equality (===) on this.state to determine if any slice changed. Works because forwardEvent creates a new state reference via shallow spread when any slice changes.

Type parameters

NameTypeDescription
Cextends stringChannel key in EM.
Textends stringType key within channel C.

Parameters

NameTypeDescription
channelCChannel name.
NameTypeDescription
typeTEvent type name.
NameTypeDescription
payloadEM[C][T]Payload typed as EM[C][T].
NameTypeDescription
opts?EmitOptionsOptional per-emit options (e.g. dedupKey for identity-based dedup).

Returns

Promise<void>A promise that resolves once this event's effects have finished. State is already updated synchronously before emit() returns.

Example

example
await store.emit('ui', 'increment', 1);

Example

example
store.registerMiddleware((state, event) => {
  if (event.type === 'dangerous') return false; // cancel
  return true; // allow
});

await store.emit('ui', 'dangerous', null); // cancelled, no state change

getState

Returns the current immutable state snapshot.

signature
getState(): DeepReadonly<S>

Returns the current immutable state snapshot.

Returns

DeepReadonly<S>Deep-readonly state object.

Example

example
const state = store.getState();
console.log(state.counter.value);

hotReplace

Convenience API to replace **any subset** of store parts (HMR patterns).

signature
hotReplace(partial): void

Convenience API to replace **any subset** of store parts (HMR patterns).

Parameters

partial — object with:

NameTypeDescription
effects?EffectSpec<DeepReadonly<S>, EM>[]
middleware?MiddlewareFunction<DeepReadonly<S>, EM>[]
preserveState?boolean
reducer?Record<R, ReducerSpec<S[R], EM>>

Returns

void

Example

example
store.hotReplace({
  reducer: newReducers,
  middleware: newMiddleware,
  effects: newEffects,
  preserveState: true
});

instrument

Registers an instrumentation observer. See StoreInstance.instrument.

signature
instrument(observer): Unsubscribe

Registers an instrumentation observer. See StoreInstance.instrument.

Parameters

NameTypeDescription
observerInstrumentationObserver<EM>

Returns

Unsubscribe

onEffect

Convenience helper to register an **effect** filtered by a single (channel, type) pair.

signature
onEffect<C, T>(channel, type, handler): () => void

Convenience helper to register an **effect** filtered by a single (channel, type) pair.

Type parameters

NameTypeDescription
Cextends stringChannel key within EM.
Textends stringEvent type key within channel C.

Parameters

NameTypeDescription
channelCChannel to filter.
NameTypeDescription
typeTEvent type to filter.
NameTypeDescription
handler(payload: EM[C][T], getState: () => DeepReadonly<S>, emit: Emit<EM>, event: Event<EM, C, T>) => void | Promise<void>Effect handler (payload, getState, emit, event).

Returns

() => voidUnsubscribe/teardown function.

Example

example
const off = store.onEffect('ui', 'increment', async (n, get, emit) => {
  if (n > 10) await emit('ui', 'increment', -10);
});
// later
off();

onEvent

Subscribe to events by channel and type. Event subscriptions are intended for the View layer (e.g., React components) to react to events without affecting the event flow. They are fire-and-forget and cannot cancel event propagation. **Phases:** - 'committed' (default): Events that passed middleware and reached reducers. Notified after reducers, before effects. - 'uncommitted': Events rejected by middleware. Notified immediately after rejection. - 'all': Both committed and uncommitted events. Handler receives the phase parameter to distinguish between the two.

signature
onEvent<C, T>(channel, type, handler, phase): Unsubscribe

Subscribe to events by channel and type. Event subscriptions are intended for the View layer (e.g., React components) to react to events without affecting the event flow. They are fire-and-forget and cannot cancel event propagation. **Phases:** - 'committed' (default): Events that passed middleware and reached reducers. Notified after reducers, before effects. - 'uncommitted': Events rejected by middleware. Notified immediately after rejection. - 'all': Both committed and uncommitted events. Handler receives the phase parameter to distinguish between the two.

Type parameters

NameTypeDescription
Cextends stringChannel key within EM.
Textends stringEvent type key within channel C.

Parameters

NameTypeDescription
channelCChannel to subscribe to.
NameTypeDescription
typeTEvent type to subscribe to.
NameTypeDescription
handlerNarrowedEventHandler<DeepReadonly<S>, EM, C, T>Handler function (event, getState, emit, phase).
NameTypeDescription
phaseEventPhaseEvent phase to subscribe to (default: 'committed').

Returns

UnsubscribeUnsubscribe function.

Example

example
const off = store.onEvent('ui', 'save', (event, getState, emit, phase) => {
  console.log('Save committed:', event.payload);
});
off();

Example

example
store.onEvent('ui', 'delete', (event, getState, emit, phase) => {
  console.log('Delete was rejected by middleware');
}, 'uncommitted');

Example

example
store.onEvent('ui', 'action', (event, getState, emit, phase) => {
  console.log('Action:', phase); // 'committed' or 'uncommitted'
}, 'all');

registerEffect

Registers an **effect** (stateless async event consumer) that runs after reducers. Effects are **keyed** by (channel, type) for O(1) lookup (no scanning all effects).

signature
registerEffect(spec): () => void

Registers an **effect** (stateless async event consumer) that runs after reducers. Effects are **keyed** by (channel, type) for O(1) lookup (no scanning all effects).

Parameters

NameTypeDescription
specEffectSpec<DeepReadonly<S>, EM>Effect specification with events (EventKeys) and effect (handler).

Returns

() => voidUnsubscribe function.

Example

example
const off = store.registerEffect({
  events: [['ui', 'increment']],
  effect: async (evt, getState, emit) => {
    console.log('increment', evt.payload, getState().counter.value);
  }
});
off();

Example

example
store.registerEffect({
  events: [['ui', 'increment'], ['ui', 'decrement']],
  effect: async (evt, getState, emit) => {
    // Runs for both increment and decrement
    await saveToServer(getState());
  }
});

registerMiddleware

Registers a middleware (runs **before** reducers).

signature
registerMiddleware(mw): Unsubscribe

Registers a middleware (runs **before** reducers).

Parameters

NameTypeDescription
mwMiddlewareFunction<DeepReadonly<S>, EM>Middleware (state, event, emit) => boolean|Promise<boolean>. Return false to cancel event propagation.

Returns

UnsubscribeUnsubscribe function that removes this middleware.

Example

example
const off = store.registerMiddleware(async (state, event) => {
  console.log('Event:', event.channel, event.type, event.payload);
  return true; // allow
});
off();

Example

example
store.registerMiddleware((state, event) => {
  if (event.type === 'forbidden') return false; // cancel
  return true;
});

registerReducer

Dynamically **adds** a named slice reducer at runtime.

signature
registerReducer(name, spec): () => void

Dynamically **adds** a named slice reducer at runtime.

Parameters

NameTypeDescription
namestringNew slice name (must not already exist).
NameTypeDescription
specReducerSpec<any, EM>Reducer spec (state, events, reducer).

Returns

() => voidDisposer function that **removes** the slice (and its state).

Example

example
const dispose = store.registerReducer('filters', {
  state: { q: '' },
  events: [['ui', 'setQuery']],
  reducer(s, evt) {
    return evt.type === 'setQuery' ? { q: evt.payload } : s;
  }
});
// Later:
dispose();

replaceEffects

Replaces all registered **effects** (HMR-friendly).

signature
replaceEffects(next): void

Replaces all registered **effects** (HMR-friendly).

Parameters

NameTypeDescription
nextEffectSpec<DeepReadonly<S>, EM>[]New effects array (as EffectSpecs).

Returns

void

Example

example
if (import.meta.hot) {
  import.meta.hot.accept('./effects', (newModule) => {
    store.replaceEffects(newModule.effects);
  });
}

replaceMiddleware

Replaces the **entire** middleware pipeline (HMR-friendly).

signature
replaceMiddleware(next): void

Replaces the **entire** middleware pipeline (HMR-friendly).

Parameters

NameTypeDescription
nextMiddlewareFunction<DeepReadonly<S>, EM>[]New middleware array.

Returns

void

Example

example
if (import.meta.hot) {
  import.meta.hot.accept('./middleware', (newModule) => {
    store.replaceMiddleware(newModule.middleware);
  });
}

replaceReducers

Replaces the entire **reducer set** (HMR-friendly).

signature
replaceReducers(next, opts): void

Replaces the entire **reducer set** (HMR-friendly).

Parameters

NameTypeDescription
nextRecord<R, ReducerSpec<S[R], EM>>Map of slice specs keyed by slice name.

opts — object with:

NameTypeDescription
preserveState?boolean

Returns

void

Example

example
if (import.meta.hot) {
  import.meta.hot.accept('./reducers', (newModule) => {
    store.replaceReducers(newModule.reducers, { preserveState: true });
  });
}

subscribe

Subscribes to **coarse-grained** commits (called once per successful event, only if state changed). **Use Case**: React's useSyncExternalStore or similar external store integrations.

signature
subscribe(fn): () => void

Subscribes to **coarse-grained** commits (called once per successful event, only if state changed). **Use Case**: React's useSyncExternalStore or similar external store integrations.

Parameters

NameTypeDescription
fn() => voidListener invoked after reducers/effects have run and state has changed.

Returns

() => voidUnsubscribe function.

Example

example
const off = store.subscribe(() => console.log('state committed'));
// Later:
off();