Running FSMs
FSMRuntime[Id, S, E] is the unified execution surface:
Id: instance id (Unitfor simple FSMs, orString/UUIDwhen persisted)S/E: state and event types
Simple runtime
machine.start(initialState) creates an in-memory runtime. The FSM stops when the scope closes.
send returns a transition outcome (Goto, Stay, or Stop). Missing transitions raise
InvalidTransitionError.
{
enum MyState derives Finite:
case Initial, Running, Done
enum MyEvent derives Finite:
case Start, Finish
import MyState.*, MyEvent.*
val machine = Machine(
assembly[MyState, MyEvent](
Initial via Start to Running,
Running via Finish to Done,
)
)
Mermoid.diagram(
MermaidVisualizer.stateDiagram(machine, Some(Initial)),
DocsDiagrams.diagramConfig,
)
}{
enum MyState derives Finite:
case Initial, Running, Done
enum MyEvent derives Finite:
case Start, Finish
import MyState.*, MyEvent.*
val machine = Machine(
assembly[MyState, MyEvent](
Initial via Start to Running,
Running via Finish to Done,
)
)
ZIO.scoped {
for
fsm <- machine.start(Initial)
outcome <- fsm.send(Start)
state <- fsm.currentState
hist <- fsm.history
yield (outcome.result.toString, state.toString, hist.length)
}.asDoc
}(Goto(Running),Running,1){
enum MyState derives Finite:
case Initial, Running, Done
enum MyEvent derives Finite:
case Start, Finish
import MyState.*, MyEvent.*
val machine = Machine(
assembly[MyState, MyEvent](
Initial via Start to Running,
Running via Finish to Done,
)
)
ZIO.scoped {
for
fsm <- machine.start(Initial)
failed <- fsm.send(Finish).either
yield failed
}.asDoc
}Left(InvalidTransitionError(Initial,Finish,No transition defined))Persistent runtime
FSMRuntime(id, machine, initial) needs three environment services:
| Service | Role |
|---|---|
EventStore[Id, S, E] | Events and snapshots |
TimeoutStrategy[Id] | Fiber or durable timeouts |
LockingStrategy[Id] | Optimistic or distributed locking |
{
enum OrderState derives Finite:
case Pending, Paid, Shipped
enum OrderEvent derives Finite:
case Pay, Ship
import OrderState.*, OrderEvent.*
val orderMachine = Machine(
assembly[OrderState, OrderEvent](
Pending via Pay to Paid,
Paid via Ship to Shipped,
)
)
val orderId: OrderId = "order-1"
ZIO
.scoped {
for
fsm <- FSMRuntime(orderId, orderMachine, Pending)
_ <- fsm.send(Pay)
state <- fsm.currentState
yield state
}
.provide(
InMemoryEventStore.layer[OrderId, OrderState, OrderEvent],
TimeoutStrategy.fiber[OrderId],
LockingStrategy.optimistic[OrderId],
)
.asDoc
}PaidNext: Persistence for recover-on-construct and snapshots.