Designing the Phase Graph
A domain’s phase graph is declared in the setup function. Getting the structure right — especially initial, terminal phases, and reachability — determines whether the flow models your business domain cleanly and whether strict mode stays silent.
Choosing initial
Section titled “Choosing initial”The initial field in the setup return value names the phase the domain starts in when first created. It must match a phase you registered with when().
import { defineDomain, when, on, target, terminal } from '@tde.io/plexis';
const subscription = defineDomain('subscription', () => { when('trial', () => { on('activate', target('active')); on('expire', target('expired')); }); when('active', () => { on('cancel', target('cancelled')); }); when('expired', terminal()); when('cancelled', terminal());
// 'trial' is the starting point — must be a registered phase return { context: {}, initial: 'trial' };});
console.log(subscription.phase); // 'trial'If initial names a phase that was not registered, the runtime throws PlexisError with code UNKNOWN_INITIAL_PHASE when the domain is created.
Declaring terminal phases
Section titled “Declaring terminal phases”A terminal phase is one the domain can enter but never leave. Declare it by passing terminal() as the second argument to when(). The domain will not follow any event while in a terminal phase (following returns status: 'ignored').
when('fulfilled', terminal());when('cancelled', terminal());Terminal phases do not need events registered inside them. Any on() calls inside a terminal phase’s setup function are ignored at runtime.
Unreachable phases
Section titled “Unreachable phases”An unreachable phase is one that no event points to and that is not the initial phase. The domain can never enter it through normal operation.
const broken = defineDomain('broken', () => { when('active', () => { on('done', target('done')); }); when('done', terminal()); // Nobody moves to 'orphan' — it is unreachable when('orphan', terminal()); return { context: {}, initial: 'active' };});Without strict mode, unreachable phases are silently ignored. With strict: true, the runtime rejects them at creation time.
Strict mode
Section titled “Strict mode”Set strict: true in the setup return value to turn graph integrity problems into errors at definition time rather than silent runtime surprises.
const order = defineDomain('order', () => { when('pending', () => { on('submit', target('processing')); }); when('processing', terminal()); // 'ghost' is unreachable when('ghost', terminal());
return { context: {}, initial: 'pending', strict: true }; // Throws PlexisError at creation: UNREACHABLE_PHASE 'ghost'});Strict mode rejects:
- An
initialthat names an unregistered phase. - Event targets that name unregistered phases.
- Phases that are not reachable from
initialand are notinitialthemselves.
Reading the graph
Section titled “Reading the graph”After construction, domain.graph.describe() returns a GraphDescriptor with all nodes and edges. Use it to audit reachability in tests or tooling.
const graph = subscription.graph.describe();const unreachable = graph.nodes.filter( (n) => !graph.entryNodes.some((e) => e.nodeId === n.id) && !graph.edges.some((e) => e.to.nodeId === n.id),);console.log(unreachable.map((n) => n.id)); // []For the full DomainGraph API, see the Domain Instance reference and Graph Introspection in Patterns.