Skip to content

State Machine

Entities with a status enum can enforce valid state transitions at compile time with [StateMachine<TEnum>]. The source generator turns the enum’s [TransitionFrom] declarations into guarded transition methods, so an invalid transition is a typed error — never an unchecked status assignment. The attribute itself is listed in Attributes; this page is the full guide. For how transitions fit a mutation, see the Mutation Pipeline.

Annotate the enum members with the states they may be reached from, mark the initial state, and put [StateMachine<TEnum>] on the entity:

public enum ReservationStatus
{
[InitialState]
Pending,
[TransitionFrom(ReservationStatus.Pending)]
Confirmed,
[TransitionFrom(ReservationStatus.Confirmed)]
PaymentReceived,
[TransitionFrom(ReservationStatus.Confirmed)]
[TransitionFrom(ReservationStatus.PaymentReceived)]
CheckedIn,
[TransitionFrom(ReservationStatus.CheckedIn)]
CheckedOut,
[TransitionFrom(ReservationStatus.Pending)]
[TransitionFrom(ReservationStatus.Confirmed)]
[TransitionFrom(ReservationStatus.PaymentReceived)]
Cancelled,
[TransitionFrom(ReservationStatus.Pending)]
[TransitionFrom(ReservationStatus.Confirmed)]
NoShow
}
[Entity<Guid>]
[StateMachine<ReservationStatus>]
public partial class Reservation : IEntity<Guid>
{
public ReservationStatus Status { get; private set; } = ReservationStatus.Pending;
}

The source generator produces, on the entity:

  • TransitionTo(newState) — returns VoidResult<IError> (success, or a transition error)
  • CanTransitionTo(newState) — returns bool
  • AllowedTransitions — the set of valid target states from the current state
public VoidResult<IError> Confirm()
{
var result = TransitionTo(ReservationStatus.Confirmed);
if (result.IsSuccess)
{
RaiseEvent(new ReservationConfirmed(Id, GuestId, PropertyId));
}
return result;
}

Invalid transitions return a typed error rather than throwing — handle it like any other Result failure. Because the transition graph is fixed at compile time, adding a new state or edge is a one-line enum change, and any code path that would reach an unreachable state simply cannot compile a valid TransitionTo for it.