Effects
Effects API
@naikidev/commiq-effects provides structured side effects triggered by store events. Effects run outside the store's command processing loop and support cancellation, restart, and debounce patterns.
Installation
pnpm add @naikidev/commiq-effectsnpm install @naikidev/commiq-effectsyarn add @naikidev/commiq-effectsbun add @naikidev/commiq-effectsBasic Usage
import { createStore, createEvent, createCommand, sealStore } from "@naikidev/commiq";
import { createEffects } from "@naikidev/commiq-effects";
const store = createStore({ results: [] });
const searchRequested = createEvent<string>("searchRequested");
store.addCommandHandler("search", (ctx, cmd) => {
ctx.emit(searchRequested, cmd.data);
});
store.addCommandHandler("setResults", (ctx, cmd) => {
ctx.setState({ results: cmd.data });
});
const sealed = sealStore(store);
const effects = createEffects(sealed);
effects.on(searchRequested, async (query, ctx) => {
const res = await fetch(`/api/search?q=${query}`, { signal: ctx.signal });
const data = await res.json();
ctx.queue(createCommand("setResults", data));
}, { restartOnNew: true });createEffects(store)
Creates an effects manager attached to a sealed store. Returns an Effects handle.
const effects = createEffects(sealedStore);effects.on(eventDef, handler, options?)
Registers an effect handler that runs when the given event is emitted.
effects.on(myEvent, (data, ctx) => {
// data is the event payload
// ctx.signal is an AbortSignal for cancellation
// ctx.queue() dispatches commands to the store
}, options);Effect Context (EffectContext)
| Property | Type | Description |
|---|---|---|
queue | (command: Command) => void | Queue a command on the store |
signal | AbortSignal | Signals when the effect is cancelled |
Options
| Option | Type | Default | Description |
|---|---|---|---|
restartOnNew | boolean | false | Cancel the running effect if the same event fires again |
cancelOn | EventDef | — | Cancel the running effect when this event fires |
debounce | number | — | Delay execution in ms; resets on each new trigger (last-wins) |
effects.destroy()
Aborts all running effects, clears pending debounce timers, and disconnects from the store stream.
effects.destroy();Cancellation
Effects receive an AbortSignal via ctx.signal. Use it to cancel in-flight work:
effects.on(fetchRequested, async (url, ctx) => {
const res = await fetch(url, { signal: ctx.signal });
const data = await res.json();
ctx.queue(createCommand("setData", data));
}, { restartOnNew: true });When an effect is cancelled (via restartOnNew, cancelOn, or destroy), AbortError exceptions are silently swallowed.
Cancel on Event
Use cancelOn to abort a running effect when a specific event fires:
const uploadStarted = createEvent("uploadStarted");
const uploadCancelled = createEvent("uploadCancelled");
effects.on(uploadStarted, async (file, ctx) => {
await uploadFile(file, { signal: ctx.signal });
ctx.queue(createCommand("uploadComplete", file.name));
}, { cancelOn: uploadCancelled });Debounce
Delay effect execution. Each new trigger resets the timer — only the last event's data is used:
effects.on(inputChanged, async (query, ctx) => {
const results = await search(query, { signal: ctx.signal });
ctx.queue(createCommand("setResults", results));
}, { debounce: 300 });