Reference
Matchers and targets
| Construct | Role |
|---|---|
all[T] | Every leaf under parent T |
anyOf(s1, …) | Explicit state list |
state[S] / event[E] | Match by type (payload cases) |
viaAnyOf / anyOfEvents / viaAll | Multi-event edges |
stay / stop / stop("reason") | Self-loop or terminal |
.to[Leaf] { (s, e) => ... } | Compute Goto payload for that leaf |
.to(stay) { (s, e) => ... } | Rewrite payload, Stay lifecycle (no timeout reset) |
Aspects and effects
| Construct | Role |
|---|---|
@@ Aspect.timeout(d, e) / @@ Aspect.timeout(e)(d) | Named timeout on entry to target; stack to arm several |
@@ Aspect.overriding | Intentional duplicate; last wins |
.onEntry | Sync effect during send (failure → ActionFailedError) |
.producing | Fork effect that returns another event |
.onEnter / .onExit on Assembly | Per-state lifecycle hooks |
Graph queries
| Construct | Role |
|---|---|
MachineGraph.hasEdge(machine, from, event) | Assembly edge? Not reducer success |
MachineGraph.destLeaf(machine, from, event) | Goto leaf, or current leaf for Stay/Stop; None if no edge |
Runtime layers
| Service | Common layers |
|---|---|
EventStore | InMemoryEventStore.layer, mechanoid-postgres |
TimeoutStrategy | fiber[Id], durable[Id] (+ TimeoutStore) |
LockingStrategy | optimistic[Id], distributed[Id] (+ FSMInstanceLock) |
InstanceIndex | InMemoryInstanceIndex.layer, PostgresInstanceIndex, IndexedDbInstanceIndex |
AliasExtractor | derived[S], apply, none |
IndexExtractor | derived[S] (@index / @indexCreated / @indexUpdated / @indexRank), apply (nested payload) |
Alias.of[S] | Member select: Alias.of[InitiativeState].campaign(id) |
IndexQuery.of[S] | Member select plus .only(state[Archived]) / .only(all[Active]) / .require.project |
Errors
| Error | When |
|---|---|
InvalidTransitionError | No transition for state/event |
PayloadLeafMismatchError | Reducer built a different Finite leaf than declared |
FSMStoppedError | FSM already stopped |
ProcessingTimeoutError | Timeout during event processing |
ActionFailedError | Entry / lifecycle action failed |
PersistenceError | Store operation failed |
SequenceConflictError | Concurrent modification at append |
EventReplayError | Stored event does not match definition |
LockingError | Distributed lock busy / timeout |
UniqueAliasError | Alias already bound to a different instance |
AliasNotFoundError | lookup / resolve found no binding |
InvalidIndexQuery | find with both startAfter and startBefore, or since with a Rank sort |
Compact machine
Timeout, cancel-from-many, and a durable timeout layer in one path:
{
enum OrderState derives Finite:
case Pending, AwaitingPayment, Paid, Shipped, Delivered, Cancelled
enum OrderEvent derives Finite:
case RequestPayment, ConfirmPayment, Ship, Deliver, Cancel, PaymentTimeout
import OrderState.*, OrderEvent.*
val orderMachine = Machine(
assembly[OrderState, OrderEvent](
(Pending via RequestPayment to AwaitingPayment) @@ Aspect.timeout(30.minutes, PaymentTimeout),
AwaitingPayment via ConfirmPayment to Paid,
Paid via Ship to Shipped,
Shipped via Deliver to Delivered,
AwaitingPayment via PaymentTimeout to Cancelled,
anyOf(Pending, AwaitingPayment) via Cancel to Cancelled,
)
)
Mermoid.diagram(
MermaidVisualizer.stateDiagram(orderMachine, Some(Pending)),
Mermoid.chalkboard,
)
}timeout PaymentTimeout: 30m
{
enum OrderState derives Finite:
case Pending, AwaitingPayment, Paid, Shipped, Delivered, Cancelled
enum OrderEvent derives Finite:
case RequestPayment, ConfirmPayment, Ship, Deliver, Cancel, PaymentTimeout
import OrderState.*, OrderEvent.*
val orderMachine = Machine(
assembly[OrderState, OrderEvent](
(Pending via RequestPayment to AwaitingPayment) @@ Aspect.timeout(30.minutes, PaymentTimeout),
AwaitingPayment via ConfirmPayment to Paid,
Paid via Ship to Shipped,
Shipped via Deliver to Delivered,
AwaitingPayment via PaymentTimeout to Cancelled,
anyOf(Pending, AwaitingPayment) via Cancel to Cancelled,
)
)
val orderId: OrderId = "order-ref-1"
ZIO
.scoped {
for
fsm <- FSMRuntime(orderId, orderMachine, Pending)
_ <- fsm.send(RequestPayment)
_ <- fsm.send(ConfirmPayment)
_ <- fsm.send(Ship)
state <- fsm.currentState
yield state
}
.provide(
InMemoryEventStore.layer[OrderId, OrderState, OrderEvent],
ZLayer.fromZIO(InMemoryTimeoutStore.make[OrderId]),
TimeoutStrategy.durable[OrderId],
LockingStrategy.optimistic[OrderId],
)
.asDoc
}ShippedDependencies: Scala 3, ZIO 2 (provided), optional mechanoid-postgres.
Next: Examples.