Reference

Matchers and targets

ConstructRole
all[T]Every leaf under parent T
anyOf(s1, …)Explicit state list
state[S] / event[E]Match by type (payload cases)
viaAnyOf / anyOfEvents / viaAllMulti-event edges
stay / stop / stop("reason")Self-loop or terminal

Aspects and effects

ConstructRole
@@ Aspect.timeout(d, e)Schedule timeout event on entry to target
@@ Aspect.overridingIntentional duplicate; last wins
.onEntrySync effect during send (failure → ActionFailedError)
.producingFork effect that returns another event
.onEnter / .onExit on AssemblyPer-state lifecycle hooks

Runtime layers

ServiceCommon layers
EventStoreInMemoryEventStore.layer, mechanoid-postgres
TimeoutStrategyfiber[Id], durable[Id] (+ TimeoutStore)
LockingStrategyoptimistic[Id], distributed[Id] (+ FSMInstanceLock)

Errors

ErrorWhen
InvalidTransitionErrorNo transition for state/event
FSMStoppedErrorFSM already stopped
ProcessingTimeoutErrorTimeout during event processing
ActionFailedErrorEntry / lifecycle action failed
PersistenceErrorStore operation failed
SequenceConflictErrorConcurrent modification at append
EventReplayErrorStored event does not match definition
LockingErrorDistributed lock busy / timeout

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)),
    DocsDiagrams.diagramConfig,
  )
}
RequestPaymentCancelShipDeliverConfirmPaymentPaymentTimeoutCancel
timeout: 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
}
Shipped

Dependencies: Scala 3, ZIO 2 (provided), optional mechanoid-postgres.

Next: Examples.