Transit: Build fast and type-safe state machines

I’m excited to share Transit, a PureScript library for building fast and type-safe state machines.

What is Transit?

Transit lets you specify state machines using a type-level DSL. The compiler then ensures your implementation is complete and correct—no missing cases, no invalid transitions, no documentation drift.

Quick Example

Here’s a simple door state machine:

-- Define your state machine specification at the type level
type DoorTransit =
  Transit
    :* ("DoorOpen" :@ "Close" >| "DoorClosed")
    :* ("DoorClosed" :@ "Open" >| "DoorOpen")

-- Write the update function - the compiler checks it matches the spec!
update :: State -> Msg -> State
update = mkUpdate @DoorTransit
  ( match @"DoorOpen" @"Close" \_ _ ->
      return @"DoorClosed"
  )
  ( match @"DoorClosed" @"Open" \_ _ ->
      return @"DoorOpen"
  )

If you forget to handle a transition or add one that’s not in your specification, you’ll get a compile error.

Key Features

  • Type-Level DSL - Specify state transitions as types
  • Compile-Time Verification - The compiler ensures your update function is complete and valid
  • Automatic Diagrams - Generate state diagrams from your specification
  • Graph Analysis - Analyze state machine properties programmatically
  • Performance - Optimized runtime with zero overhead for type-level machinery
  • Advanced Patterns - Support for monadic updates, error handling, and more

Example: Multi-Way Transitions

Transit supports transitions with multiple possible outcomes:

type CountDownTransit =
  Transit
    :* ("Idle" :@ "Start" >| "Counting")
    :* ("Done" :@ "Reset" >| "Idle")
    :* ( "Counting" :@ "Tick"
          >| "Counting"  -- continue counting
          >| "Done"      -- or finish
       )

update :: State -> Msg -> State
update = mkUpdate @CountDownTransit
  -- ... handlers ...
  ( match @"Counting" @"Tick" \state _ ->
      if (state.count - 1) == 0
        then return @"Done"
        else return @"Counting" { count: state.count - 1 }
  )

The compiler verifies that you only return states listed in your specification.

Resources

Installation

spago install transit

Design Philosophy

If you’re familiar with Servant from Haskell, Transit follows a similar philosophy: just as Servant uses a type-level REST API specification to ensure type-safe routing and generate OpenAPI docs, Transit uses a type-level state machine specification to ensure type-safe update functions and generate state diagrams.


I’d love to hear your thoughts, questions, or feedback!

3 Likes