Skip to content
@tde.io/plexis  ·  npm install @tde.io/plexis  ·  npm  ·  GitHub

Reactive Subscriptions

domain.subscribe() registers a listener that is called every time the domain completes an event. It returns a cleanup function. This is the framework-agnostic reactive seam — use it to trigger UI updates, log side effects, or synchronize external state after each event.

import { defineDomain, when, on, target, terminal } from '@tde.io/plexis';
const order = defineDomain('order', () => {
when('pending', () => {
on('submit', target('processing'));
});
when('processing', terminal());
return { context: { orderId: 'ord_1' }, initial: 'pending' };
});
// Register a listener — called after each completed event
const unsubscribe = order.subscribe((snapshot) => {
console.log(`Phase: ${snapshot.phase}`);
console.log(`Context:`, snapshot.context);
console.log(`History length: ${snapshot.historyLength}`);
});
await order.follow('submit');
// → Phase: processing
// → Context: { orderId: 'ord_1' }
// → History length: 1

The listener receives a DomainSnapshot{ phase, context, historyLength }. It is called synchronously after step 8 of the follow() lifecycle (after history is recorded).

subscribe() returns a zero-argument function. Call it to stop receiving updates. This is important for long-lived domains used in components or services that are later destroyed.

const unsubscribe = order.subscribe((snap) => {
externalStore.set(snap);
});
// Later, when the component or service is torn down
unsubscribe();

Multiple subscribers can be registered on the same domain. Each subscribe() call returns its own cleanup function; you must call each one separately.

domain.snapshot() returns the current snapshot without subscribing. Use it to read the current phase once, or to seed a UI framework’s initial value before subscribing to updates.

// Read once for initialization
const initial = order.snapshot();
// Then subscribe for updates
const unsubscribe = order.subscribe((snap) => {
render(snap);
});
const log = order.subscribe((snap) => console.log('log:', snap.phase));
const store = order.subscribe((snap) => externalStore.set(snap));
await order.follow('submit');
// Both listeners are called
log(); // stop logging
// store listener still active
store(); // stop store sync

Listeners are called after each completed event. Because a terminal phase cannot be exited, no further events happen after entering one — so the subscriber is called once when the terminal is reached and then never again.

order.subscribe((snap) => {
if (snap.phase === 'processing') {
cleanup(); // safe to call — 'processing' is terminal, this fires exactly once
}
});
await order.follow('submit'); // listener fires with phase: 'processing'
// No more follows are possible from a terminal phase

For framework-specific bindings that build on subscribe and snapshot, see the React integration and Vue integration. For the DomainSnapshot type and subscribe() signature, see the Domain Instance reference.