Statetrooper
StateTrooper is a Go package that provides a finite state machine (FSM) for managing states. It allows you to define and enforce state transitions based on predefined rules.
Install / Use
/learn @hishamk/StatetrooperREADME
Tiny, no frills finite state machine for Go

StateTrooper is a Go package that provides a finite state machine (FSM) for managing states. It allows you to define and enforce state transitions based on predefined rules.
⚠️ Please keep in mind that StateTrooper is still under active development and therefore full backward compatibility is not guaranteed before reaching v1.0.0.
Features
- Generic support for different comparable types.
- Transition history with metadata. History size configurable.
- Thread safe.
- Super minimal - no triggers/events or actions/callbacks. For my use case I just needed a structured, serializable way to constrain and track state transitions.
- Is able to generate Mermaid.js diagram descriptions for the transition rules and transition history.
Rules diagram:

Transition history diagram:

Installation
To install StateTrooper, use the following command:
go get github.com/hishamk/statetrooper
Usage
Import the statetrooper package into your Go code:
import "github.com/hishamk/statetrooper"
Create an instance of the FSM with the desired state enum type, initial state and maximum transition history size:
fsm := statetrooper.NewFSM[CustomStateEnum](CustomStateEnumA, 10)
Add valid transitions between states. AddRule takes variadic parameters for the allowed states:
// Created -> Picked or Canceled
AddRule(StatusCreated, StatusPicked, StatusCanceled)
// Picked -> Packed or Canceled
AddRule(StatusPicked, StatusPacked, StatusCanceled)
// Packed -> Shipped
AddRule(StatusPacked, StatusShipped)
// Shipped -> Delivered
AddRule(StatusShipped, StatusDelivered)
// Canceled -> Reinstated
AddRule(StatusCanceled, StatusReinstated)
// Reinstated -> Picked or Canceled
AddRule(StatusReinstated, StatusPicked, StatusCanceled)
Check if a transition from the current state to the target state is valid:
canTransition := fsm.CanTransition(targetState)
Transition the entity from the current state to the target state with no metadata:
newState, err := fsm.Transition(targetState, nil)
if err != nil {
// Handle the error
}
Transition the entity from the current state to the target state with metadata:
newState, err := fsm.Transition(
CustomStateEnumB,
map[string]string{
"requested_by": "Mahmoud",
"logic_version": "1.0",
})
Generate Mermaid.js rules diagram:
diagram, _ :=order.State.GenerateMermaidRulesDiagram()
In order to generate a diagram, the states type must have a String() method. Ensure that the formatted string returned does not contain any invalid characters for Mermaid.
Use the generated Mermaid code with your Mermaid visualizer to generate the diagram.
graph LR;
shipped;
canceled;
reinstated;
created;
picked;
packed;
created --> picked;
created --> canceled;
picked --> packed;
picked --> canceled;
packed --> shipped;
shipped --> delivered;
canceled --> reinstated;
reinstated --> picked;
reinstated --> canceled;

Generate Mermaid.js transition history diagram:
diagram, _ :=order.State.GenerateMermaidTransitionHistoryDiagram()
graph TD;
packed;
shipped;
delivered;
created;
picked;
canceled;
reinstated;
created -->|1| picked;
picked -->|2| canceled;
canceled -->|3| reinstated;
reinstated -->|4| picked;
picked -->|5| packed;
packed -->|6| shipped;
shipped -->|7| delivered;

Benchmarks
| Benchmark | Operations | Time per Operation | Memory Allocated per Operation | | ---------------------------- | ---------- | ------------------ | ------------------------------ | | Benchmark_singleTransition | 5,166,985 | 273.8 ns/op | 314 allocs/op | | Benchmark_twoTransitions | 2,835,214 | 513.6 ns/op | 577 allocs/op | | Benchmark_accessCurrentState | 75,695,847 | 14.36 ns/op | 0 allocs/op | | Benchmark_accessCurrentStateWithMetadata | 3,881,552 | 312.2 ns/op | 336 B/op | | Benchmark_accessCurrentStateWithEmptyMetadata | 55,361,556 | 22.37 ns/op | 0 B/op | | Benchmark_accessTransitions | 39,356,628 | 28.74 ns/op | 48 allocs/op | | Benchmark_marshalJSON | 1,000,000 | 1,174 ns/op | 384 allocs/op | | Benchmark_unmarshalJSON | 318,949 | 3,741 ns/op | 1,240 allocs/op |
Example
Here's an example usage with a custom entity struct and state enum:
type OrderStatusEnum string
// Enum values for the custom entity
const (
StatusCreated OrderStatusEnum = "created"
StatusPicked OrderStatusEnum = "picked"
StatusPacked OrderStatusEnum = "packed"
StatusShipped OrderStatusEnum = "shipped"
StatusDelivered OrderStatusEnum = "delivered"
StatusCanceled OrderStatusEnum = "canceled"
StatusReinstated OrderStatusEnum = "reinstated"
)
// Order represents a custom entity with its current state
type Order struct {
State *statetrooper.FSM[OrderStatusEnum]
}
func main() {
// Create a new order with the initial state
order := &Order{State: statetrooper.NewFSM[OrderStatusEnum](StatusCreated, 10)}
// Define the valid state transitions for the order
// Created -> Picked or Canceled
order.State.AddRule(StatusCreated, StatusPicked, StatusCanceled)
// Picked -> Packed or Canceled
order.State.AddRule(StatusPicked, StatusPacked, StatusCanceled)
// Packed -> Shipped
order.State.AddRule(StatusPacked, StatusShipped)
// Shipped -> Delivered
order.State.AddRule(StatusShipped, StatusDelivered)
// Canceled -> Reinstated
order.State.AddRule(StatusCanceled, StatusReinstated)
// Reinstated -> Picked or Canceled
order.State.AddRule(StatusReinstated, StatusPicked, StatusCanceled)
// Check if a transition is valid
canTransition := order.State.CanTransition(StatusPicked)
fmt.Printf("Can transition to %s: %t\n", StatusPicked, canTransition)
// Transition to picked
_, err := order.State.Transition(StatusPicked, nil)
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// Check if a transition to canceled is valid
canTransition = order.State.CanTransition(StatusCanceled)
fmt.Printf("Can transition to %s: %t\n", StatusCanceled, canTransition)
// Transition to canceled
_, err = order.State.Transition(StatusCanceled, nil)
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// Check if we can resinstate the order
canTransition = order.State.CanTransition(StatusReinstated)
fmt.Printf("Can transition to %s: %t\n", StatusReinstated, canTransition)
// Transition to reinstated
_, err = order.State.Transition(StatusReinstated, nil)
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// Transition to picked
_, err = order.State.Transition(StatusPicked, nil)
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// Transition to packed
_, err = order.State.Transition(StatusPacked, nil)
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// Transition to shipped
_, err = order.State.Transition(
StatusShipped,
map[string]string{
"carrier": "Aramex",
"tracking_number": "1234567890",
})
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// Transition to delivered
_, err = order.State.Transition(StatusDelivered, nil)
if err != nil {
fmt.Println("Transition error:", err)
} else {
fmt.Println("Transition successful. Current state:", order.State.CurrentState())
}
// print the current FSM data
fmt.Println("Current FSM data:", order.State)
}
Note that states can be defined using any comparable type, such as strings, int, etc e.g.:
// CustomStateEnum represents the state enum for the custom entity
type CustomStateEnum int
// Enum values for the custom entity
const (
CustomStateEnumA CustomStateEnum = iota
CustomStateEnumB
CustomStateEnumC
)
func (e CustomStateEnum) String() string {
return fmt.Sprintf("%d", e)
}
Accessing current state metadata
Use CurrentStateWithMetadata when you need both the entity's state and the metadata from the transition that set it. The method returns the current state and a copy of the metadata if the last transition supplied any.
state, metadata := order.State.CurrentStateWithMetadata()
fmt.Printf("Current state: %s, metadata: %v\n", state, metadata)
Serialization
Current state, transition history and any metadata can be
Related Skills
node-connect
337.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
xurl
337.4kA CLI tool for making authenticated requests to the X (Twitter) API. Use this skill when you need to post tweets, reply, quote, search, read posts, manage followers, send DMs, upload media, or interact with any X API v2 endpoint.
frontend-design
83.2kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
337.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).

