{"id":36440698,"url":"https://github.com/enetx/fsm","last_synced_at":"2026-01-11T21:56:48.488Z","repository":{"id":304395638,"uuid":"1018577175","full_name":"enetx/fsm","owner":"enetx","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-15T19:17:16.000Z","size":96,"stargazers_count":14,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-15T21:43:42.607Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"HTML","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/enetx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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,"zenodo":null}},"created_at":"2025-07-12T15:04:14.000Z","updated_at":"2025-08-15T19:17:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"5109cbce-a87b-48fb-93aa-391cbdb65c4c","html_url":"https://github.com/enetx/fsm","commit_stats":null,"previous_names":["enetx/fsm"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/enetx/fsm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enetx%2Ffsm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enetx%2Ffsm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enetx%2Ffsm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enetx%2Ffsm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/enetx","download_url":"https://codeload.github.com/enetx/fsm/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/enetx%2Ffsm/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28324554,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-11T18:42:50.174Z","status":"ssl_error","status_checked_at":"2026-01-11T18:39:13.842Z","response_time":60,"last_error":"SSL_read: 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":[],"created_at":"2026-01-11T21:56:47.788Z","updated_at":"2026-01-11T21:56:48.480Z","avatar_url":"https://github.com/enetx.png","language":"HTML","readme":"\u003cimg width=\"2898\" height=\"3401\" alt=\"image\" src=\"https://github.com/user-attachments/assets/90e4aba6-fff3-41b5-b0d6-0676c92339ab\" /\u003e\n\n# FSM for Go\n\nA generic, concurrent-safe, and easy-to-use finite state machine (FSM) library for Go.\n\nThis library provides a simple yet powerful API for defining states and transitions, handling callbacks, and managing stateful logic in your applications. It is built with types and utilities from the `github.com/enetx/g` library.\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/enetx/fsm.svg)](https://pkg.go.dev/github.com/enetx/fsm)\n[![Go Report Card](https://goreportcard.com/badge/github.com/enetx/fsm)](https://goreportcard.com/report/github.com/enetx/fsm)\n[![Coverage Status](https://coveralls.io/repos/github/enetx/fsm/badge.svg?branch=main\u0026service=github)](https://coveralls.io/github/enetx/fsm?branch=main)\n[![Go](https://github.com/enetx/fsm/actions/workflows/go.yml/badge.svg)](https://github.com/enetx/fsm/actions/workflows/go.yml)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/enetx/fsm)\n\n## Features\n\n-   **Simple \u0026 Fluent API**: Define your state machine with clear, chainable methods.\n-   **Fast by Default**: The base FSM is non-blocking for maximum performance in single-threaded use cases.\n-   **Drop-in Concurrency**: Get a fully thread-safe FSM by calling a single `Sync()` method.\n-   **State Callbacks**: Execute code on entering (`OnEnter`) or exiting (`OnExit`) a state.\n-   **Global Transition Hooks**: `OnTransition` allows you to monitor and log all state changes globally.\n-   **Guarded Transitions**: Control transitions with `TransitionWhen` based on custom logic.\n-   **JSON Serialization**: Easily save and restore the FSM's state with built-in `json.Marshaler` and `json.Unmarshaler` support.\n-   **Graphviz Visualization**: Generate DOT-format graphs to visualize your FSM.\n-   **Zero Dependencies** (besides `github.com/enetx/g`).\n\n## Installation\n\n```sh\ngo get github.com/enetx/fsm\n```\n\n## Quick Start\n\nHere's a simple example of a traffic light state machine:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/enetx/fsm\"\n)\n\nfunc main() {\n\t// 1. Define states and the event\n\tconst (\n\t\tStateGreen  = \"Green\"\n\t\tStateYellow = \"Yellow\"\n\t\tStateRed    = \"Red\"\n\t\tEventTimer  = \"timer_expires\"\n\t)\n\n\t// 2. Configure the FSM\n\tlightFSM := fsm.New(StateRed).\n\t\tTransition(StateGreen, EventTimer, StateYellow).\n\t\tTransition(StateYellow, EventTimer, StateRed).\n\t\tTransition(StateRed, EventTimer, StateGreen)\n\n\t// 3. Define callbacks for entering states\n\tlightFSM.OnEnter(StateGreen, func(ctx *fsm.Context) error {\n\t\tfmt.Println(\"LIGHT: Green -\u003e Go!\")\n\t\treturn nil\n\t})\n\tlightFSM.OnEnter(StateYellow, func(ctx *fsm.Context) error {\n\t\tfmt.Println(\"LIGHT: Yellow -\u003e Prepare to stop\")\n\t\treturn nil\n\t})\n\tlightFSM.OnEnter(StateRed, func(ctx *fsm.Context) error {\n\t\tfmt.Println(\"LIGHT: Red -\u003e Stop!\")\n\t\treturn nil\n\t})\n\n\t// 4. Run the FSM loop\n\tfmt.Printf(\"Initial state: %s\\n\", lightFSM.Current())\n\tlightFSM.CallEnter(StateRed) // Manually trigger the first prompt\n\n\tfor range 4  {\n\t\ttime.Sleep(1 * time.Second)\n\t\tfmt.Println(\"\\n...timer expires...\")\n\t\tlightFSM.Trigger(EventTimer)\n\t}\n}\n```\n\n### Output\n\n```text\nInitial state: Red\nLIGHT: Red -\u003e Stop!\n\n...timer expires...\nLIGHT: Green -\u003e Go!\n\n...timer expires...\nLIGHT: Yellow -\u003e Prepare to stop\n\n...timer expires...\nLIGHT: Red -\u003e Stop!\n\n...timer expires...\nLIGHT: Green -\u003e Go!\n```\n\n## API Overview\n\n### Creating an FSM\n\n```go\n// Create a new FSM instance (not thread-safe)\nfsmachine := fsm.New(\"initial_state\")\n\n// Get a thread-safe wrapper for concurrent use\nsafeFSM := fsmachine.Sync()\n```\n\n### Defining Transitions\n\n-   **`Transition(from, event, to)`**: A direct, unconditional transition.\n-   **`TransitionWhen(from, event, to, guard)`**: A transition that only occurs if the `guard` function returns `true`.\n\n```go\nfsmachine.Transition(\"idle\", \"start\", \"running\")\n\nfsmachine.TransitionWhen(\"running\", \"stop\", \"stopped\", func(ctx *fsm.Context) bool {\n    // Only allow stopping if a specific condition is met\n    return ctx.Data.Get(\"can_stop\").UnwrapOr(false).(bool)\n})\n```\n\n### Callbacks and Hooks\n\n-   **`OnEnter(state, callback)`**: Called when the FSM enters `state`.\n-   **`OnExit(state, callback)`**: Called before the FSM exits `state`.\n-   **`OnTransition(hook)`**: Called on *every* successful transition, after `OnExit` and before `OnEnter`.\n\n```go\nfsmachine.OnEnter(\"running\", func(ctx *fsm.Context) error {\n    fmt.Println(\"Job started!\")\n    return nil\n})\n\nfsmachine.OnExit(\"running\", func(ctx *fsm.Context) error {\n    fmt.Println(\"Cleaning up job...\")\n    return nil\n})\n\nfsmachine.OnTransition(func(from, to fsm.State, event fsm.Event, ctx *fsm.Context) error {\n    log.Printf(\"STATE CHANGE: %s -\u003e %s (on event %s)\", from, to, event)\n    return nil\n})\n```\n\n### Triggering Events\n\nThe `Trigger` method drives the state machine.\n\n```go\n// Simple trigger\nerr := fsmachine.Trigger(\"start\")\n\n// Trigger with data payload\n// The data will be available in the context as `ctx.Input`.\nerr := fsmachine.Trigger(\"process\", someDataObject)\n```\nAny error returned from a callback will halt the transition and be returned by `Trigger`.\n\n### Context\n\nThe `Context` is passed to every callback and guard. It's the primary way to manage data associated with an FSM instance.\n\n-   `ctx.Input`: Holds the data passed with the current `Trigger` call. It's ephemeral and lasts for one transition only.\n-   `ctx.Data`: A concurrent-safe map (`g.MapSafe`) for persistent data that is serialized with the FSM (e.g., user details).\n-   `ctx.Meta`: A concurrent-safe map (`g.MapSafe`) for ephemeral metadata that is also serialized (e.g., temporary counters).\n\n### Concurrency\n\nThe library is designed with performance and safety in mind, offering two distinct operating modes:\n\n1.  **`fsm.FSM` (Default)**: The base state machine is **not** thread-safe. It is optimized for performance in single-threaded scenarios by avoiding the overhead of mutexes.\n\n2.  **`fsm.SyncFSM` (Synchronized)**: This is a thread-safe wrapper around the base `FSM`. It protects all operations (like `Trigger`, `Current`, `Reset`) with a mutex, ensuring that all transitions are atomic and safe to use across multiple goroutines.\n\nYou should complete all configuration (`Transition`, `OnEnter`, etc.) on the base `FSM` before using it. The configuration process itself is **not** thread-safe.\n\n#### Activating Thread-Safety\n\nTo get a thread-safe instance, simply call the `Sync()` method after you have configured your FSM:\n\n```go\n// 1. Configure the non-thread-safe FSM template\nfsmTemplate := fsm.New(\"idle\").\n    Transition(\"idle\", \"start\", \"running\").\n    Transition(\"running\", \"stop\", \"stopped\")\n\n// 2. Get a thread-safe, synchronized instance\nsafeFSM := fsmTemplate.Sync()\n\n// 3. Now you can safely use safeFSM across multiple goroutines\ngo func() {\n    err := safeFSM.Trigger(\"start\")\n    // ...\n}()\n\ngo func() {\n    currentState := safeFSM.Current()\n    // ...\n}()\n```\n\n### Serialization\n\nYou can easily save and restore the FSM's state using `encoding/json`, as `FSM` implements the `json.Marshaler` and `json.Unmarshaler` interfaces.\n\n**Saving State:**\n```go\n// Assume `fsmachine` is in some state.\njsonData, err := json.Marshal(fsmachine)\nif err != nil {\n    // handle error\n}\n// Now you can save `jsonData` to a database, file, etc.\n```\n\n**Restoring State:**\n```go\n// 1. Create a new FSM with the same configuration as the original.\nrestoredFSM := fsm.New(\"initial_state\").\n    Transition(...) // ...add all transitions and callbacks\n\n// 2. Unmarshal the JSON data into the new instance.\nerr := json.Unmarshal(jsonData, restoredFSM)\nif err != nil {\n    // handle error\n}\n\n// `restoredFSM` is now in the same state as the original was.\nfmt.Println(restoredFSM.Current())\n```\n**Note**: Serialization only saves the FSM's state (`current`, `history`, `Data`, `Meta`). It does not save the transition rules or callbacks. You must configure the FSM template before unmarshaling. If you need a thread-safe FSM after restoring, call `.Sync()` *after* `json.Unmarshal`.\n\n### Visual Generator (Web UI)\n\nAn in-browser FSM editor and Go code generator for this library.\n\n[Open the Online Generator →](https://enetx.github.io/fsm/visual_generator/)\n\n- 100% client-side (no data sent anywhere).\n- Draw states and transitions, set callbacks and guards, then generate ready-to-use Go code for github.com/enetx/fsm.\n\n#### Controls\n- Double-click empty canvas — add a state.\n- Double-click state/transition — rename state / edit event name.\n- Shift + drag from one state to another — create a transition (self-loops supported).\n- Right-click state — context menu (Set as Initial / Delete).\n- Drag on empty canvas — rectangular multi-select; then use Align X, Align Y, Stack.\n- Esc — cancel linking / clear selection.\n\n#### Properties \u0026 Panels\n- State properties: name, color, OnEnter, OnExit, “Final state”, and “Set as Initial”.\n- Transition properties: event name and optional guard function.\n- Events panel: shows incoming/outgoing events for the selected state (guards are italicized).\n\n#### Generate Go Code\nClick “Generate Go Code” to get a self-contained example:\n- Declares const States and Events.\n- Builds an FSM via fsm.New(initial) with .Transition(...) / .TransitionWhen(..., guard).\n- Attaches callbacks with .OnEnter(...) / .OnExit(...).\n- Emits function stubs for every referenced callback/guard (once per unique name).\n\nNote: You must set an initial state before generating code. Callback/guard names you type in the UI become function names in the output.\n\n#### Import / Export\n- Export JSON — downloads fsm.json with positions, colors, callbacks, guards, transitions, and initial state.\n- Import JSON — loads a saved model. If positions are missing, the tool auto-lays out nodes.\n\n#### Validation \u0026 Hints\n- State names must be unique (enforced by the editor).\n- Warns about unreachable states.\n- Guarded transitions are rendered with dashed lines and a diamond arrowhead.\n\n### Visualization\n\nThe library includes a `ToDOT()` method to generate a graph of your state machine in the [DOT language](https://graphviz.org/doc/info/lang.html). This is extremely useful for debugging, documentation, and sharing your FSM's logic with your team.\n\nYou can render the output into an image using various tools:\n\n*   **Online Editors (Recommended for quick use):**\n    *   [**Graphviz Online**](https://dreampuf.github.io/GraphvizOnline/) - A simple and effective web-based viewer.\n    *   [**Edotor**](https://edotor.net/) - Another powerful online editor with different layout engines.\n    *   Simply paste the output of `ToDOT()` into one of these sites to see your diagram instantly.\n\n*   **Local Installation:**\n    *   For more advanced use or integration into build scripts, you can install [**Graphviz**](https://graphviz.org/download/) locally.\n\n**Example:**\n\n```go\nfunc main() {\n    fsmachine := fsm.New(\"Idle\").\n        Transition(\"Idle\", \"start\", \"Running\").\n        TransitionWhen(\"Running\", \"suspend\", \"Suspended\", func(ctx *fsm.Context) bool {\n            return true\n        }).\n        Transition(\"Suspended\", \"resume\", \"Running\").\n        Transition(\"Running\", \"finish\", \"Done\")\n\n    // Generate the DOT string\n    fsmachine.ToDOT().Println() // Copy this output\n}\n```\n\u003cimg width=\"941\" height=\"360\" alt=\"graphviz\" src=\"https://github.com/user-attachments/assets/2516c2c4-582f-4c08-81e6-4b8c36a1920c\" /\u003e\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a pull request or open an issue for bugs, feature requests, or questions.\n\n## License\n\nThis project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.\n","funding_links":[],"categories":["Data Integration Frameworks","Data Structures and Algorithms"],"sub_categories":["Miscellaneous Data Structures and Algorithms"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenetx%2Ffsm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fenetx%2Ffsm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fenetx%2Ffsm/lists"}