{"id":38815383,"url":"https://github.com/hishamk/statetrooper","last_synced_at":"2026-01-17T12:59:35.860Z","repository":{"id":175911971,"uuid":"654596314","full_name":"hishamk/statetrooper","owner":"hishamk","description":"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.","archived":false,"fork":false,"pushed_at":"2023-06-22T14:51:28.000Z","size":199,"stargazers_count":209,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-06-21T14:30:33.795Z","etag":null,"topics":["fsm","go","golang","state-machine","state-management"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hishamk.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-16T13:30:33.000Z","updated_at":"2024-05-17T20:25:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"ca854dcf-3fb7-43e8-a34a-018c01a346ec","html_url":"https://github.com/hishamk/statetrooper","commit_stats":null,"previous_names":["hishamk/statetrooper"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/hishamk/statetrooper","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hishamk%2Fstatetrooper","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hishamk%2Fstatetrooper/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hishamk%2Fstatetrooper/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hishamk%2Fstatetrooper/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hishamk","download_url":"https://codeload.github.com/hishamk/statetrooper/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hishamk%2Fstatetrooper/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28508941,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-17T11:50:55.898Z","status":"ssl_error","status_checked_at":"2026-01-17T11:50:55.569Z","response_time":85,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["fsm","go","golang","state-machine","state-management"],"created_at":"2026-01-17T12:59:35.768Z","updated_at":"2026-01-17T12:59:35.851Z","avatar_url":"https://github.com/hishamk.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"_Tiny, no frills finite state machine for Go_\n![](st-logo.png)\n\n[![GoDoc](https://godoc.org/github.com/hishamk/statetrooper?status.png)](https://pkg.go.dev/github.com/hishamk/statetrooper?tab=doc)\n![GitHub tag (latest SemVer pre-release)](https://img.shields.io/github/v/tag/hishamk/statetrooper)\n\n[![Go Coverage](https://github.com/hishamk/statetrooper/wiki/coverage.svg)](https://raw.githack.com/wiki/hishamk/statetrooper/coverage.html)\n[![Go Report Card](https://goreportcard.com/badge/github.com/hishamk/statetrooper)](https://goreportcard.com/report/github.com/hishamk/statetrooper)\n[![MIT](https://img.shields.io/github/license/hishamk/statetrooper)](https://img.shields.io/github/license/hishamk/statetrooper) ![Code size](https://img.shields.io/github/languages/code-size/hishamk/statetrooper)\n\nStateTrooper 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.\n\n\u003e ⚠️ Please keep in mind that StateTrooper is still under active development\n\u003e and therefore full backward compatibility is not guaranteed before reaching v1.0.0.\n\n## Features\n\n- Generic support for different comparable types.\n- Transition history with metadata. History size configurable.\n- Thread safe.\n- 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.\n- Is able to generate [Mermaid.js](https://mermaid.js.org) diagram descriptions for the transition rules and transition history.\n\n_Rules diagram:_\n\n![Mermaid.js rules diagram](order-rules-diagram.png)\n\n_Transition history diagram:_\n\n![Mermaid.js transition history diagram](order-th-diagram.png)\n\n## Installation\n\nTo install StateTrooper, use the following command:\n\n```shell\ngo get github.com/hishamk/statetrooper\n```\n\n## Usage\n\nImport the `statetrooper` package into your Go code:\n\n```go\nimport \"github.com/hishamk/statetrooper\"\n```\n\nCreate an instance of the FSM with the desired state enum type, initial state and maximum transition history size:\n\n```go\nfsm := statetrooper.NewFSM[CustomStateEnum](CustomStateEnumA, 10)\n```\n\nAdd valid transitions between states. AddRule takes variadic parameters for the allowed states:\n\n```go\n// Created -\u003e Picked or Canceled\nAddRule(StatusCreated, StatusPicked, StatusCanceled)\n// Picked -\u003e Packed or Canceled\nAddRule(StatusPicked, StatusPacked, StatusCanceled)\n// Packed -\u003e Shipped\nAddRule(StatusPacked, StatusShipped)\n// Shipped -\u003e Delivered\nAddRule(StatusShipped, StatusDelivered)\n// Canceled -\u003e Reinstated\nAddRule(StatusCanceled, StatusReinstated)\n// Reinstated -\u003e Picked or Canceled\nAddRule(StatusReinstated, StatusPicked, StatusCanceled)\n```\n\nCheck if a transition from the current state to the target state is valid:\n\n```go\ncanTransition := fsm.CanTransition(targetState)\n```\n\nTransition the entity from the current state to the target state with no metadata:\n\n```go\nnewState, err := fsm.Transition(targetState, nil)\nif err != nil {\n    // Handle the error\n}\n```\n\nTransition the entity from the current state to the target state with metadata:\n\n```go\nnewState, err := fsm.Transition(\n\tCustomStateEnumB,\n\tmap[string]string{\n\t\t\"requested_by\":  \"Mahmoud\",\n\t\t\"logic_version\": \"1.0\",\n\t})\n```\n\nGenerate Mermaid.js rules diagram:\n\n```go\ndiagram, _ :=order.State.GenerateMermaidRulesDiagram()\n```\n\n_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.\n\n_Use the generated Mermaid code with your Mermaid visualizer to generate the diagram._\n\n```markdown\ngraph LR;\nshipped;\ncanceled;\nreinstated;\ncreated;\npicked;\npacked;\ncreated --\u003e picked;\ncreated --\u003e canceled;\npicked --\u003e packed;\npicked --\u003e canceled;\npacked --\u003e shipped;\nshipped --\u003e delivered;\ncanceled --\u003e reinstated;\nreinstated --\u003e picked;\nreinstated --\u003e canceled;\n```\n\n![Mermaid.js diagram](order-rules-diagram.png)\n\nGenerate Mermaid.js transition history diagram:\n\n```go\ndiagram, _ :=order.State.GenerateMermaidTransitionHistoryDiagram()\n```\n\n```markdown\ngraph TD;\npacked;\nshipped;\ndelivered;\ncreated;\npicked;\ncanceled;\nreinstated;\ncreated --\u003e|1| picked;\npicked --\u003e|2| canceled;\ncanceled --\u003e|3| reinstated;\nreinstated --\u003e|4| picked;\npicked --\u003e|5| packed;\npacked --\u003e|6| shipped;\nshipped --\u003e|7| delivered;\n```\n\n![Mermaid.js diagram](order-th-diagram.png)\n\n## Benchmarks\n\n| Benchmark                    | Operations | Time per Operation | Memory Allocated per Operation |\n| ---------------------------- | ---------- | ------------------ | ------------------------------ |\n| Benchmark_singleTransition   | 5,166,985  | 273.8 ns/op        | 314 allocs/op                  |\n| Benchmark_twoTransitions     | 2,835,214  | 513.6 ns/op        | 577 allocs/op                  |\n| Benchmark_accessCurrentState | 75,695,847 | 14.36 ns/op        | 0 allocs/op                    |\n| Benchmark_accessCurrentStateWithMetadata | 3,881,552 | 312.2 ns/op        | 336 B/op                  |\n| Benchmark_accessCurrentStateWithEmptyMetadata | 55,361,556 | 22.37 ns/op        | 0 B/op                  |\n| Benchmark_accessTransitions  | 39,356,628 | 28.74 ns/op        | 48 allocs/op                   |\n| Benchmark_marshalJSON        | 1,000,000  | 1,174 ns/op        | 384 allocs/op                  |\n| Benchmark_unmarshalJSON      | 318,949    | 3,741 ns/op        | 1,240 allocs/op                |\n\n## Example\n\nHere's an example usage with a custom entity struct and state enum:\n\n```go\ntype OrderStatusEnum string\n\n// Enum values for the custom entity\nconst (\n\tStatusCreated    OrderStatusEnum = \"created\"\n\tStatusPicked     OrderStatusEnum = \"picked\"\n\tStatusPacked     OrderStatusEnum = \"packed\"\n\tStatusShipped    OrderStatusEnum = \"shipped\"\n\tStatusDelivered  OrderStatusEnum = \"delivered\"\n\tStatusCanceled   OrderStatusEnum = \"canceled\"\n\tStatusReinstated OrderStatusEnum = \"reinstated\"\n)\n\n// Order represents a custom entity with its current state\ntype Order struct {\n\tState *statetrooper.FSM[OrderStatusEnum]\n}\n\nfunc main() {\n\t// Create a new order with the initial state\n\torder := \u0026Order{State: statetrooper.NewFSM[OrderStatusEnum](StatusCreated, 10)}\n\n\t// Define the valid state transitions for the order\n\n\t// Created -\u003e Picked or Canceled\n\torder.State.AddRule(StatusCreated, StatusPicked, StatusCanceled)\n\t// Picked -\u003e Packed or Canceled\n\torder.State.AddRule(StatusPicked, StatusPacked, StatusCanceled)\n\t// Packed -\u003e Shipped\n\torder.State.AddRule(StatusPacked, StatusShipped)\n\t// Shipped -\u003e Delivered\n\torder.State.AddRule(StatusShipped, StatusDelivered)\n\t// Canceled -\u003e Reinstated\n\torder.State.AddRule(StatusCanceled, StatusReinstated)\n\t// Reinstated -\u003e Picked or Canceled\n\torder.State.AddRule(StatusReinstated, StatusPicked, StatusCanceled)\n\n\t// Check if a transition is valid\n\tcanTransition := order.State.CanTransition(StatusPicked)\n\tfmt.Printf(\"Can transition to %s: %t\\n\", StatusPicked, canTransition)\n\n\t// Transition to picked\n\t_, err := order.State.Transition(StatusPicked, nil)\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// Check if a transition to canceled is valid\n\tcanTransition = order.State.CanTransition(StatusCanceled)\n\tfmt.Printf(\"Can transition to %s: %t\\n\", StatusCanceled, canTransition)\n\n\t// Transition to canceled\n\t_, err = order.State.Transition(StatusCanceled, nil)\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// Check if we can resinstate the order\n\tcanTransition = order.State.CanTransition(StatusReinstated)\n\tfmt.Printf(\"Can transition to %s: %t\\n\", StatusReinstated, canTransition)\n\n\t// Transition to reinstated\n\t_, err = order.State.Transition(StatusReinstated, nil)\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// Transition to picked\n\t_, err = order.State.Transition(StatusPicked, nil)\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// Transition to packed\n\t_, err = order.State.Transition(StatusPacked, nil)\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// Transition to shipped\n\t_, err = order.State.Transition(\n\t\tStatusShipped,\n\t\tmap[string]string{\n\t\t\t\"carrier\":         \"Aramex\",\n\t\t\t\"tracking_number\": \"1234567890\",\n\t\t})\n\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// Transition to delivered\n\t_, err = order.State.Transition(StatusDelivered, nil)\n\tif err != nil {\n\t\tfmt.Println(\"Transition error:\", err)\n\t} else {\n\t\tfmt.Println(\"Transition successful. Current state:\", order.State.CurrentState())\n\t}\n\n\t// print the current FSM data\n\tfmt.Println(\"Current FSM data:\", order.State)\n}\n```\n\nNote that states can be defined using any comparable type, such as strings, int, etc e.g.:\n\n```go\n// CustomStateEnum represents the state enum for the custom entity\ntype CustomStateEnum int\n\n// Enum values for the custom entity\nconst (\n\tCustomStateEnumA CustomStateEnum = iota\n\tCustomStateEnumB\n\tCustomStateEnumC\n)\n\nfunc (e CustomStateEnum) String() string {\n        return fmt.Sprintf(\"%d\", e)\n}\n```\n\n## Accessing current state metadata\n\nUse `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.\n\n```go\nstate, metadata := order.State.CurrentStateWithMetadata()\nfmt.Printf(\"Current state: %s, metadata: %v\\n\", state, metadata)\n```\n\n## Serialization\n\nCurrent state, transition history and any metadata can be marshalled into JSON.\n\n```go\njson, err := json.Marshal(order.State)\nif err != nil {\n\tfmt.Println(\"JSON error:\", err)\n} else {\n\tfmt.Println(\"Current FSM data as JSON:\", string(json))\n}\n```\n\nOutput:\n\n```json\n{\n  \"current_state\": \"delivered\",\n  \"transitions\": [\n    {\n      \"from_state\": \"created\",\n      \"to_state\": \"picked\",\n      \"timestamp\": \"2023-06-18T11:44:42.776422+03:00\",\n      \"metadata\": null\n    },\n    {\n      \"from_state\": \"picked\",\n      \"to_state\": \"canceled\",\n      \"timestamp\": \"2023-06-18T11:44:42.77643+03:00\",\n      \"metadata\": null\n    },\n    {\n      \"from_state\": \"canceled\",\n      \"to_state\": \"reinstated\",\n      \"timestamp\": \"2023-06-18T11:44:42.776435+03:00\",\n      \"metadata\": null\n    },\n    {\n      \"from_state\": \"reinstated\",\n      \"to_state\": \"picked\",\n      \"timestamp\": \"2023-06-18T11:44:42.77644+03:00\",\n      \"metadata\": null\n    },\n    {\n      \"from_state\": \"picked\",\n      \"to_state\": \"packed\",\n      \"timestamp\": \"2023-06-18T11:44:42.776442+03:00\",\n      \"metadata\": null\n    },\n    {\n      \"from_state\": \"packed\",\n      \"to_state\": \"shipped\",\n      \"timestamp\": \"2023-06-18T11:44:42.776451+03:00\",\n      \"metadata\": {\n        \"carrier\": \"Aramex\",\n        \"tracking_number\": \"1234567890\"\n      }\n    },\n    {\n      \"from_state\": \"shipped\",\n      \"to_state\": \"delivered\",\n      \"timestamp\": \"2023-06-18T11:44:42.776454+03:00\",\n      \"metadata\": null\n    }\n  ]\n}\n```\n\n## License\n\nThis package is licensed under the MIT License. See the [LICENSE](LICENSE.md) file for details.\n\n## Contributing\n\nThank you for your interest in contributing! Feel free to PR bug fixes, optimisations and documentation improvements. For new features or functional alterations, please open an issue for discussion prior to submitting a PR.\n\n## Logo\n\nSynthwave title text generated courtesy of [Text Effect](https://www.textstudio.com/).\n\nTrooper Gopher generated via midjourney prompt: _a photorealistic rendering of the Go/Golang gopher mascot holding a sheriff's badge. Color palette similar to mascot's (baby blue or light beige). Buck teeth. Round glasses. Big smile. Transparent background. --v 5_\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhishamk%2Fstatetrooper","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhishamk%2Fstatetrooper","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhishamk%2Fstatetrooper/lists"}