Packages
finitomata
0.41.0
0.41.0
0.40.0
0.35.0
0.34.0
0.33.0
0.32.0
0.31.1
0.30.3
0.30.2
0.30.1
0.30.0
0.29.10
0.29.9
0.29.8
0.29.7
0.29.6
0.29.5
0.29.4
0.29.3
0.29.2
0.29.1
0.29.0
0.28.1
0.28.0
0.27.1
0.27.0
0.26.4
0.26.3
0.26.2
0.26.1
0.26.0
0.25.0
0.24.4
0.24.3
0.24.2
0.24.1
0.24.0
0.23.7
0.23.6
0.23.5
0.23.4
0.23.3
0.23.2
0.23.1
0.23.0
0.22.1
0.22.0
0.21.4
0.21.3
0.21.2
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.6
0.19.5
0.19.4
0.19.3
0.19.2
0.19.1
0.19.0
0.18.4
0.18.3
0.18.2
0.18.1
0.18.0
0.17.1
0.17.0
0.16.0
0.15.1
0.15.0
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.1
0.12.0
0.11.3
0.11.2
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.1
0.1.0
The FSM implementation generated from PlantUML textual representation.
Current section
Files
Jump to
Current section
Files
finitomata
README.md
README.md
#  Finitomata [](https://kantox.com/) [](https://github.com/am-kantox/finitomata/actions/workflows/ci.yml)
**The FSM boilerplate based on callbacks**
---
## Bird View
`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.
It 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.
> ### Syntax Definition {: .tip}
>
> `Mermaid` **state diagram** format is literally the same as `PlantUML`, so if you want to use it, specify `syntax: :state_diagram` and
> if you want to use **mermaid graph**, specify `syntax: :flowchart`. The latter is the default.
Basically, it looks more or less like this
### `PlantUML` / `:state_diagram`
[*] --> s1 : to_s1
s1 --> s2 : to_s2
s1 --> s3 : to_s3
s2 --> [*] : ok
s3 --> [*] : ok
### `Mermaid` / `:flowchart`
s1 --> |to_s2| s2
s1 --> |to_s3| s3
> ### Using `syntax: :flowchart` {: .tip}
>
> `Mermaid` does not allow to explicitly specify transitions (and hence event names)
> from the starting state and to the end state(s), these states names are implicitly set to `:*`
> and events to `:__start__` and `:__end__` respectively.
`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
- `on_transition/4` — **mandatory**
- `on_failure/3` — optional
- `on_enter/2` — optional
- `on_exit/2` — optional
- `on_terminate/1` — optional
- `on_timer/2` — optional
All 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.
Upon 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.
Upon 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.
## Special Events
If the event name is ended with a bang (e. g. `idle --> |start!| started`) _and_
this event is the only one allowed from this state (there might be several transitions though,)
it’d be considered as _determined_ and FSM will be transitioned into the new state instantly.
If the event name is ended with a question mark (e. g. `idle --> |start?| started`,)
the transition is considered as expected to fail; no `on_failure/2` callback would
be called on failure and no log warning will be printed.
## FSM Tuning and Configuration
### Recurrent Callback
If `timer: non_neg_integer()` option is passed to `use Finitomata`,
then `c:Finitomata.on_timer/2` callback will be executed recurrently.
This might be helpful if _FSM_ needs to update its state from the outside
world on regular basis.
### Automatic FSM Termination
If `auto_terminate: true() | state() | [state()]` option is passed to `use Finitomata`,
the special `__end__` event to transition to the end state will be called automatically
under the hood, if the current state is either listed explicitly, or if the value of
the parameter is `true`.
### Ensuring State Entry
If `ensure_entry: true() | [state()]` option is passed to `use Finitomata`, the transition
attempt will be retried with `{:continue, {:transition, {event(), event_payload()}}}` message
until succeeded. Neither `on_failure/2` callback is called nor warning message is logged.
The payload would be updated to hold `__retries__: pos_integer()` key. If the payload was not a map,
it will be converted to a map `%{payload: payload}`.
### Examples
See [examples directory](https://github.com/am-kantox/finitomata/tree/main/examples) for
real-life examples of `Finitomata` usage.
## Example
Let’s define the FSM instance
```elixir
defmodule MyFSM do
@fsm """
s1 --> |to_s2| s2
s1 --> |to_s3| s3
"""
use Finitomata, fsm: @fsm, syntax: :flowchart
## or uncomment lines below for `:state_diagram` syntax
# @fsm """
# [*] --> s1 : to_s1
# s1 --> s2 : to_s2
# s1 --> s3 : to_s3
# s2 --> [*] : __end__
# s3 --> [*] : __end__
# """
# use Finitomata, fsm: @fsm, syntax: :state_diagram
@impl Finitomata
def on_transition(:s1, :to_s2, _event_payload, state_payload),
do: {:ok, :s2, state_payload}
end
```
Now we can play with it a bit.
```elixir
# or embed into supervision tree using `Finitomata.child_spec()`
{:ok, _pid} = Finitomata.start_link()
Finitomata.start_fsm MyFSM, "My first FSM", %{foo: :bar}
Finitomata.transition "My first FSM", {:to_s2, nil}
Finitomata.state "My first FSM"
#⇒ %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}
Finitomata.allowed? "My first FSM", :* # state
#⇒ true
Finitomata.responds? "My first FSM", :to_s2 # event
#⇒ false
Finitomata.transition "My first FSM", {:__end__, nil} # to final state
#⇒ [info] [◉ ⇄] [state: %Finitomata.State{current: :s2, history: [:s1], payload: %{foo: :bar}}]
Finitomata.alive? "My first FSM"
#⇒ false
```
Typically, one would implement all the `on_transition/4` handlers, pattern matching on the state/event.
---
## Installation
```elixir
def deps do
[
{:finitomata, "~> 0.30"}
]
end
```
## Changelog
- `0.41.0` — [UPD] `hibernate:` accepts a `state()`/`[state()]` to hibernate only on selected states (#116); a transition resolving to a state not allowed from the current one is now rolled back instead of committed inevitably — persistency/listener are no longer touched and a new optional `c:Finitomata.on_rollback/3` callback lets consumers compensate (#121); the `listener` is notified about failed `on_fork/2` resolutions via the new optional `c:Finitomata.Listener.after_fork_failure/3` (#118)
- `0.40.0` — [UPD] `Finitomata.ExUnit` testing improvements: a dependency-free `Finitomata.ExUnit.Listener` (test without `Mox`), `assert_no_transition/3` for failure assertions, `event_generator/1` for property-based fuzzing, configurable `assert_receive`/`refute_receive`/flush timeouts, and a `Mox.stub/3`-by-default listener (the exact `transition_count` is opt-in); [DOC] documented the `Access` requirement for `~>`, the `:_` timer sugar, and fixed the moduledoc examples
- `0.39.0` — [UPD] extracted the `use Finitomata` compile-time option parsing into `Finitomata.ConfigBuilder` and moved the `safe_on_*` callback wrappers' bodies into `Finitomata.Engine`, shrinking the generated macro (cyclomatic complexity 97 → 37); the file-level Credo suppressions on `lib/finitomata.ex` are gone and the thresholds were lowered (no public API change)
- `0.38.0` — [UPD] built-in dependency-free persistence adapters `Finitomata.Persistency.ETS` (in-memory, survives FSM restart) and `Finitomata.Persistency.DETS` (disk-durable across node restart); [DOC] corrected the `Finitomata.Persistency` `load/1` contract (it receives a `{type, fields}` descriptor and returns `{lifecycle, {state, payload}}`)
- `0.37.1` — [UPD] graph search-depth caps in `Finitomata.Transition` are now configurable (defaults unchanged), `mix credo --strict` runs on push CI; [DOC] documented why `Infinitomata` keeps `:rpc.block_call` rather than `:erpc`
- `0.37.0` — [UPD] completed `Finitomata.Engine` extraction: the transition lifecycle, `init/1`, and all `GenServer` callbacks now live in a shared, unit-testable module, leaving each generated FSM as thin delegations plus per-module telemetry wrappers (no public API change); [FIX] credo cleanups, `Credo.Check.Refactor.Nesting` no longer suppressed
- `0.36.0` — [UPD] ETS-backed state cache (configurable via `:cache_backend`), `Finitomata.Error` struct in `last_error`, `Finitomata.Engine` seam, `Infinitomata` RPC timeouts + backoff; [FIX] `mix test` alias, `format_status/1` for OTP25+, OTP detection via `:pg.monitor/1`; deprecation warning for ambiguous `start_fsm/4`
- `0.30.0` — [UPD] `Finitomata.Flow`, backport `Infinitomata` to OTP25-, tons of tiny improvements
- `0.28.0` — [UPD] initial `telemetria` integration
- `0.27.0` — [UPD] options `hibernate: boolean()` and `cache_state: boolean()`
- `0.26.0` — [UPD] a lot of tiny improvements, `Finitomata.Accessible`, `reset_timer` message + tests, experimental `Finitomata.Cache`
- `0.25.0` — [UPD] allow assertions of entry states in `Finitomata.ExUnit`
- `0.24.2` — [UPD/FIX] many fixes for better diagnostics in `Finitomata.ExUnit`
- `0.23.7` — [UPD] allow both `:mox` and `{:mox, MyApp.Listener}` as well as just `MyApp.Listener` as a listener in FSM definition
- `0.23.4` — [FIX] many fixes to a `Finitomata.ExUnit` test scaffold generation
- `0.23.0` — [UPD] `mix finitomata.generate.test --module MyApp.FSM` to generate a `Finitomata.ExUnit` test scaffold
- `0.22.0` — [FIX] `Infinitomata.start_fsm/4` is finally 102% sync
- `0.21.4` — [FIX] `Finitomata.Pool` initialization in cluster
- `0.21.3` — [FIX] proper return from `Infinitomata.start_fsm/4`
- `0.21.1` — [UPD] `listener: :mox` and better `Finitomata.ExUnit` docs
- `0.20.2` — [UPD] allow guard matches in the RHO of `~>` operator in `assert_transition/3`
- `0.20.0` — [FIX] starting pool on distribution, re-synch on `:badrpc` failure
- `0.19.0` — [UPD] `Finitomata.ExUnit` lighten options check (compile-time module dependencies suck in >=1.16)
- `0.18.0` — [UPD] asynchronous `Finitomata.Pool` on top of `Infinitomata`
- `0.17.0` — [UPD] careful naming and `Finitomata.Throttler`
- `0.16.0` — [UPD] `Infinitomata` as a self-contained distributed implementation leveraging `:pg`
- `0.15.0` — [UPD] support snippet formatting for modern Elixir
- `0.14.6` — [FIX] persistency flaw when loading [credits @peaceful-james]
- `0.14.5` — [FIX] `require Logger` in `Hook`
- `0.14.4` — [FIX] Docs cleanup (credits: @TwistingTwists), `PlantUML` proper entry
- `0.14.3` — [FIX] Draw diagram in docs
- `0.14.2` — [FIX] Stop `Events` process
- `0.14.1` — [FIX] Incorrect detection of superfluous determined transitions
- `0.14.0` — `Finitomata.ExUnit` improvements
- `0.13.0` — compile-time helpers for _FSM_, `Finitomata.ExUnit`
- `0.12.1` — `c:Finitomata.on_start/1` callback
- `0.11.3` — [FIX] better error message for options (credits @ray-sh)
- `0.11.2` — [DEBT] exported `Finitomata.fqn/2`
- `0.11.1` — `Inspect`, `:flowchart`/`:state_diagram` as default parsers, behaviour `Parser`
- `0.11.0` — `{:ok, state_payload}` return from `on_timer/2`, `:persistent_term` to cache state
- `0.10.0` — support for several supervision trees with `id`s, experimental support for persistence scaffold
- `0.9.0` — [FIX] malformed callbacks had the FSM broken
- `0.8.2` — last error is now kept in the state (credits to @egidijusz)
- `0.8.1` — improvements to `:finitomata` compiler
- `0.8.0` — `:finitomata` compiler to warn/hint about not implemented ambiguous transitions
- `0.7.2` — [FIX] `banged!` transitions must not be determined
- `0.6.3` — `soft?` events which do not call `on_failure/2` and do not log errors
- `0.6.2` — `ensure_entry:` option to retry a transition
- `0.6.1` — code cleanup + `auto_terminate:` option to make `:__end__` transition imminent
- `0.6.0` — `on_timer/2` and banged imminent transitions
- `0.5.2` — `state()` type on generated FSMs
- `0.5.1` — fixed specs [credits @egidijusz]
- `0.5.0` — all callbacks but `on_transition/4` are optional, accept `impl_for:` param to `use Finitomata`
- `0.4.0` — allow anonymous FSM instances
- `0.3.0` — `en_entry/2` and `on_exit/2` optional callbacks
- `0.2.0` — [`Mermaid`](https://mermaid.live) support
[Documentation](https://hexdocs.pm/finitomata).