{"id":18002865,"url":"https://github.com/am-kantox/finitomata","last_synced_at":"2025-05-16T07:04:32.527Z","repository":{"id":41815343,"uuid":"475425222","full_name":"am-kantox/finitomata","owner":"am-kantox","description":"FSM implementation generated from Mermaid/PlantUML textual representation","archived":false,"fork":false,"pushed_at":"2025-04-06T03:55:40.000Z","size":998,"stargazers_count":116,"open_issues_count":5,"forks_count":9,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-07T03:35:25.588Z","etag":null,"topics":["elixir","finite-state-machine","fsm","fsm-library","state-machine","state-management"],"latest_commit_sha":null,"homepage":"","language":"Elixir","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/am-kantox.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}},"created_at":"2022-03-29T12:08:30.000Z","updated_at":"2025-04-06T03:55:43.000Z","dependencies_parsed_at":"2023-02-12T15:17:20.659Z","dependency_job_id":"ca0c1260-919e-4a40-b4b7-9bea1d2b1b4c","html_url":"https://github.com/am-kantox/finitomata","commit_stats":{"total_commits":109,"total_committers":5,"mean_commits":21.8,"dds":0.09174311926605505,"last_synced_commit":"81c438da40f69dc577dd257a00d59ceadb0cad1d"},"previous_names":[],"tags_count":98,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/am-kantox%2Ffinitomata","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/am-kantox%2Ffinitomata/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/am-kantox%2Ffinitomata/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/am-kantox%2Ffinitomata/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/am-kantox","download_url":"https://codeload.github.com/am-kantox/finitomata/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253351713,"owners_count":21895024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["elixir","finite-state-machine","fsm","fsm-library","state-machine","state-management"],"created_at":"2024-10-29T23:24:17.869Z","updated_at":"2025-05-16T07:04:32.492Z","avatar_url":"https://github.com/am-kantox.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ![Finitomata](https://raw.githubusercontent.com/am-kantox/finitomata/master/stuff/finitomata-48x48.png) Finitomata    [![Kantox ❤ OSS](https://img.shields.io/badge/❤-kantox_oss-informational.svg)](https://kantox.com/)  ![Test](https://github.com/am-kantox/finitomata/workflows/Test/badge.svg)  ![Dialyzer](https://github.com/am-kantox/finitomata/workflows/Dialyzer/badge.svg)\n\n**The FSM boilerplate based on callbacks**\n\n---\n\n## Bird View\n\n`Finitomata` provides a boilerplate for [FSM](https://en.wikipedia.org/wiki/Finite-state_machine) implementation, allowing to concentrate on the business logic rather than on the process management and transitions/events consistency tweaking.\n\nIt reads a description of the FSM from a string in [PlantUML](https://plantuml.com/en/state-diagram), [Mermaid](https://mermaid.live), or even custom format. \n\n\u003e ### Syntax Definition {: .tip}\n\u003e\n\u003e `Mermaid` **state diagram** format is literally the same as `PlantUML`, so if you want to use it, specify `syntax: :state_diagram` and\n\u003e if you want to use **mermaid graph**, specify `syntax: :flowchart`. The latter is the default.\n\nBasically, it looks more or less like this\n\n### `PlantUML` / `:state_diagram`\n\n    [*] --\u003e s1 : to_s1\n    s1 --\u003e s2 : to_s2\n    s1 --\u003e s3 : to_s3\n    s2 --\u003e [*] : ok\n    s3 --\u003e [*] : ok\n\n### `Mermaid` / `:flowchart`\n\n    s1 --\u003e |to_s2| s2\n    s1 --\u003e |to_s3| s3\n\n\u003e ### Using `syntax: :flowchart` {: .tip}\n\u003e\n\u003e `Mermaid` does not allow to explicitly specify transitions (and hence event names)\n\u003e from the starting state and to the end state(s), these states names are implicitly set to `:*`\n\u003e and events to `:__start__` and `:__end__` respectively.\n\n`Finitomata` validates the FSM is consistent, namely it has a single initial state, one or more final states, and no orphan states. If everything is OK, it generates a `GenServer` that could be used both alone, and with provided supervision tree. This `GenServer` requires to implement six callbacks\n\n- `on_transition/4` — **mandatory**\n- `on_failure/3` — optional\n- `on_enter/2` — optional\n- `on_exit/2` — optional\n- `on_terminate/1` — optional\n- `on_timer/2` — optional\n\nAll the callbacks do have a default implementation, that would perfectly handle transitions having a single _to_ state and not requiring any additional business logic attached.\n\nUpon start, it moves to the next to initial state and sits there awaiting for the _transition request_. Then it would call an `on_transition/4` callback and move to the next state, or remain in the current one, according to the response.\n\nUpon reaching a final state, it would terminate itself. The process keeps all the history of states it went through, and might have a payload in its state.\n\n## Special Events\n\nIf the event name is ended with a bang (e. g. `idle --\u003e |start!| started`) _and_\nthis event is the only one allowed from this state (there might be several transitions though,)\nit’d be considered as _determined_ and FSM will be transitioned into the new state instantly.\n\nIf the event name is ended with a question mark (e. g. `idle --\u003e |start?| started`,)\nthe transition is considered as expected to fail; no `on_failure/2` callback would\nbe called on failure and no log warning will be printed.\n\n## FSM Tuning and Configuration\n\n### Recurrent Callback\n\nIf `timer: non_neg_integer()` option is passed to `use Finitomata`,\nthen `c:Finitomata.on_timer/2` callback will be executed recurrently.\nThis might be helpful if _FSM_ needs to update its state from the outside\nworld on regular basis.\n\n### Automatic FSM Termination\n\nIf `auto_terminate: true() | state() | [state()]` option is passed to `use Finitomata`,\nthe special `__end__` event to transition to the end state will be called automatically\nunder the hood, if the current state is either listed explicitly, or if the value of\nthe parameter is `true`.\n\n### Ensuring State Entry\n\nIf `ensure_entry: true() | [state()]` option is passed to `use Finitomata`, the transition\nattempt will be retried with `{:continue, {:transition, {event(), event_payload()}}}` message\nuntil succeeded. Neither `on_failure/2` callback is called nor warning message is logged.\n\nThe payload would be updated to hold `__retries__: pos_integer()` key. If the payload was not a map,\nit will be converted to a map `%{payload: payload}`.\n\n### Examples\n\nSee [examples directory](https://github.com/am-kantox/finitomata/tree/main/examples) for\nreal-life examples of `Finitomata` usage.\n\n## Example\n\nLet’s define the FSM instance\n\n```elixir\ndefmodule MyFSM do\n  @fsm \"\"\"\n  s1 --\u003e |to_s2| s2\n  s1 --\u003e |to_s3| s3\n  \"\"\"\n  use Finitomata, fsm: @fsm, syntax: :flowchart\n\n  ## or uncomment lines below for `:state_diagram` syntax\n  # @fsm \"\"\"\n  # [*] --\u003e s1 : to_s1\n  # s1 --\u003e s2 : to_s2\n  # s1 --\u003e s3 : to_s3\n  # s2 --\u003e [*] : __end__\n  # s3 --\u003e [*] : __end__\n  # \"\"\"\n  # use Finitomata, fsm: @fsm, syntax: :state_diagram\n\n  @impl Finitomata\n  def on_transition(:s1, :to_s2, _event_payload, state_payload),\n    do: {:ok, :s2, state_payload}\nend\n```\n\nNow we can play with it a bit.\n\n```elixir\n# or embed into supervision tree using `Finitomata.child_spec()`\n{:ok, _pid} = Finitomata.start_link()\n\nFinitomata.start_fsm MyFSM, \"My first FSM\", %{foo: :bar}\nFinitomata.transition \"My first FSM\", {:to_s2, nil}\nFinitomata.state \"My first FSM\"                    \n#⇒ %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}\n\nFinitomata.allowed? \"My first FSM\", :* # state\n#⇒ true\nFinitomata.responds? \"My first FSM\", :to_s2 # event\n#⇒ false\n\nFinitomata.transition \"My first FSM\", {:__end__, nil} # to final state\n#⇒ [info]  [◉ ⇄] [state: %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}]\n\nFinitomata.alive? \"My first FSM\"\n#⇒ false\n```\n\nTypically, one would implement all the `on_transition/4` handlers, pattern matching on the state/event.\n\n---\n\n## Installation\n\n```elixir\ndef deps do\n  [\n    {:finitomata, \"~\u003e 0.30\"}\n  ]\nend\n```\n\n## Changelog\n- `0.30.0` — [UPD] `Finitomata.Flow`, backport `Infinitomata` to OTP25-, tons of tiny improvements\n- `0.28.0` — [UPD] initial `telemetria` integration \n- `0.27.0` — [UPD] options `hibernate: boolean()` and `cache_state: boolean()`\n- `0.26.0` — [UPD] a lot of tiny improvements, `Finitomata.Accessible`, `reset_timer` message + tests, experimental `Finitomata.Cache`\n- `0.25.0` — [UPD] allow assertions of entry states in `Finitomata.ExUnit`\n- `0.24.2` — [UPD/FIX] many fixes for better diagnostics in `Finitomata.ExUnit` \n- `0.23.7` — [UPD] allow both `:mox` and `{:mox, MyApp.Listener}` as well as just `MyApp.Listener` as a listener in FSM definition\n- `0.23.4` — [FIX] many fixes to a `Finitomata.ExUnit` test scaffold generation\n- `0.23.0` — [UPD] `mix finitomata.generate.test --module MyApp.FSM` to generate a `Finitomata.ExUnit` test scaffold\n- `0.22.0` — [FIX] `Infinitomata.start_fsm/4` is finally 102% sync\n- `0.21.4` — [FIX] `Finitomata.Pool` initialization in cluster\n- `0.21.3` — [FIX] proper return from `Infinitomata.start_fsm/4`\n- `0.21.1` — [UPD] `listener: :mox` and better `Finitomata.ExUnit` docs \n- `0.20.2` — [UPD] allow guard matches in the RHO of `~\u003e` operator in `assert_transition/3` \n- `0.20.0` — [FIX] starting pool on distribution, re-synch on `:badrpc` failure\n- `0.19.0` — [UPD] `Finitomata.ExUnit` lighten options check (compile-time module dependencies suck in \u003e=1.16) \n- `0.18.0` — [UPD] asynchronous `Finitomata.Pool` on top of `Infinitomata` \n- `0.17.0` — [UPD] careful naming and `Finitomata.Throttler`\n- `0.16.0` — [UPD] `Infinitomata` as a self-contained distributed implementation leveraging `:pg`\n- `0.15.0` — [UPD] support snippet formatting for modern Elixir\n- `0.14.6` — [FIX] persistency flaw when loading [credits @peaceful-james]\n- `0.14.5` — [FIX] `require Logger` in `Hook`\n- `0.14.4` — [FIX] Docs cleanup (credits: @TwistingTwists), `PlantUML` proper entry\n- `0.14.3` — [FIX] Draw diagram in docs\n- `0.14.2` — [FIX] Stop `Events` process\n- `0.14.1` — [FIX] Incorrect detection of superfluous determined transitions\n- `0.14.0` — `Finitomata.ExUnit` improvements\n- `0.13.0` — compile-time helpers for _FSM_, `Finitomata.ExUnit`\n- `0.12.1` — `c:Finitomata.on_start/1` callback\n- `0.11.3` — [FIX] better error message for options (credits @ray-sh)\n- `0.11.2` — [DEBT] exported `Finitomata.fqn/2`\n- `0.11.1` — `Inspect`, `:flowchart`/`:state_diagram` as default parsers, behaviour `Parser`\n- `0.11.0` — `{:ok, state_payload}` return from `on_timer/2`, `:persistent_term` to cache state\n- `0.10.0` — support for several supervision trees with `id`s, experimental support for persistence scaffold\n- `0.9.0` — [FIX] malformed callbacks had the FSM broken\n- `0.8.2` — last error is now kept in the state (credits to @egidijusz)\n- `0.8.1` — improvements to `:finitomata` compiler\n- `0.8.0` — `:finitomata` compiler to warn/hint about not implemented ambiguous transitions\n- `0.7.2` — [FIX] `banged!` transitions must not be determined\n- `0.6.3` — `soft?` events which do not call `on_failure/2` and do not log errors\n- `0.6.2` — `ensure_entry:` option to retry a transition\n- `0.6.1` — code cleanup + `auto_terminate:` option to make `:__end__` transition imminent\n- `0.6.0` — `on_timer/2` and banged imminent transitions\n- `0.5.2` — `state()` type on generated FSMs\n- `0.5.1` — fixed specs [credits @egidijusz]\n- `0.5.0` — all callbacks but `on_transition/4` are optional, accept `impl_for:` param to `use Finitomata`\n- `0.4.0` — allow anonymous FSM instances\n- `0.3.0` — `en_entry/2` and `on_exit/2` optional callbacks\n- `0.2.0` — [`Mermaid`](https://mermaid.live) support\n\n[Documentation](https://hexdocs.pm/finitomata).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fam-kantox%2Ffinitomata","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fam-kantox%2Ffinitomata","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fam-kantox%2Ffinitomata/lists"}