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.
Basic subscription
Section titled “Basic subscription”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 eventconst 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: 1The listener receives a DomainSnapshot — { phase, context, historyLength }. It is called synchronously after step 8 of the follow() lifecycle (after history is recorded).
The unsubscribe function
Section titled “The unsubscribe function”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 downunsubscribe();Multiple subscribers can be registered on the same domain. Each subscribe() call returns its own cleanup function; you must call each one separately.
Reading a snapshot outside a listener
Section titled “Reading a snapshot outside a listener”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 initializationconst initial = order.snapshot();
// Then subscribe for updatesconst unsubscribe = order.subscribe((snap) => { render(snap);});Multiple subscribers
Section titled “Multiple subscribers”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 activestore(); // stop store syncNo-op on terminal phases
Section titled “No-op on terminal phases”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 phaseFor 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.