### Start and Use Finitomata Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Demonstrates how to start the Finitomata supervision tree, start an FSM instance, trigger a transition, and query the FSM's state. ```elixir # Start the supervision tree (or embed Finitomata.child_spec() into yours) {:ok, _pid} = Finitomata.start_link() # Start an FSM instance Finitomata.start_fsm(MyFSM, "my_fsm_1", %{foo: :bar}) # Trigger a transition Finitomata.transition("my_fsm_1", {:process, %{some: :data}}) # Query the state Finitomata.state("my_fsm_1") ``` -------------------------------- ### Test Specific Examples Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Navigates to example directories and runs tests within them. ```bash cd examples/ecto_integration && mix test cd examples/caches && mix test cd examples/ex_unit_testing && mix test cd examples/telemetria && mix test ``` -------------------------------- ### Install Dependencies Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Use this command to install project dependencies. ```bash mix deps.get ``` -------------------------------- ### Interact with Finitomata FSM Source: https://github.com/am-kantox/finitomata/blob/main/README.md Demonstrates starting, transitioning, and querying the state of an FSM instance. Includes examples of checking allowed transitions and FSM liveness. ```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 ``` -------------------------------- ### FSM Syntax: PlantUML / :state_diagram Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Example of FSM definition using the `:state_diagram` (PlantUML) syntax. Explicit start and end transitions are required. ```text [*] --> idle : wake idle --> ready : start ready --> done : process done --> [*] : finish ``` -------------------------------- ### FSM Syntax: Mermaid / :flowchart Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Example of FSM definition using the default `:flowchart` (Mermaid) syntax. Starting and ending transitions are implicit. ```text idle --> |wake| ready ready --> |process| done ready --> |fail| broken ``` -------------------------------- ### Starting an FSM Instance with `start_fsm/4` Source: https://context7.com/am-kantox/finitomata/llms.txt Create a new FSM instance with a unique name and initial payload. ```APIDOC ## Starting an FSM Instance with `start_fsm/4` ### Description Create a new FSM instance with a unique name and initial payload. ### Method `Finitomata.start_fsm/4` ### Endpoint N/A ### Parameters - **finitomata_id** (atom) - Optional - The ID of the Finitomata supervision tree to use. Defaults to nil. - **fsm_module** (module) - Required - The module defining the FSM. - **fsm_name** (string) - Required - A unique name for the FSM instance. - **initial_payload** (map) - Required - The initial payload for the FSM. ### Request Example ```elixir # Start an FSM instance {:ok, pid} = Finitomata.start_fsm(MyApp.OrderFSM, "order_123", %{ order_id: "123", items: ["item_a", "item_b"], total: 99.99 }) # With custom Finitomata ID {:ok, pid} = Finitomata.start_fsm(:orders, MyApp.OrderFSM, "order_456", %{ order_id: "456", items: ["item_c"], total: 49.99 }) # Check if FSM was started true = Finitomata.alive?("order_123") ``` ### Response - **{:ok, pid}** (tuple) - The PID of the started FSM process. ### Response Example ```elixir {:ok, #PID<0.123.456>} ``` ``` -------------------------------- ### Finitomata Startup and Initial State Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Explains how a Finitomata state machine starts, including the `on_start/1` callback and parent PID configuration. ```APIDOC ## Initial State and Startup ### How the FSM starts 1. `Finitomata.start_fsm/4` is called with a module, a name, and an initial payload. 2. The `GenServer` process is started, and its `init/1` callback is invoked. 3. The `on_start/1` callback (if implemented in the FSM module) is called with the initial payload. 4. The FSM transitions from the internal starting pseudo-state (`*`) to the defined entry state using the entry event (e.g., `:__start__` for flowcharts). ### `on_start/1` Callback The `on_start/1` callback is optional and allows modification of the initial payload or control over the startup behavior. ```elixir @impl Finitomata def on_start(payload) do # Modify payload, e.g., add a timestamp {:continue, Map.put(payload, :started_at, DateTime.utc_now())} end ``` **Return Values for `on_start/1`:** - `:ok` or `:ignore`: Proceed normally with the payload unchanged. - `{:continue, new_payload}`: Proceed with the modified payload, automatically triggering the entry transition. - `{:ok, new_payload}`: Set the new payload but do NOT auto-transition. The entry transition must be triggered manually. If `on_start/1` raises an exception, the FSM process will terminate. ### Passing the Parent PID By default, the calling process (`self()`) is stored as the parent PID. This can be overridden by including a `:parent` key in the initial payload. ```elixir Finitomata.start_fsm(MyFSM, "my_fsm", %{parent: some_pid, foo: :bar}) ``` The `:parent` key is extracted and stored in `State.parent` and is not part of the actual state payload. ``` -------------------------------- ### Install EctoIntegration Package Source: https://github.com/am-kantox/finitomata/blob/main/examples/ecto_integration/README.md Add this to your `mix.exs` dependencies to install the EctoIntegration package. Ensure you are using a compatible version. ```elixir def deps do [ {:ecto_integration, "~> 0.1.0"} ] end ``` -------------------------------- ### Finitomata transition examples Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Demonstrates how to trigger transitions with and without event payloads, and how to delay transitions. ```elixir Finitomata.transition("my_fsm", :process) ``` ```elixir # equivalent to Finitomata.transition("my_fsm", {:process, nil}) ``` ```elixir # Fire :process after 5 seconds Finitomata.transition("my_fsm", {:process, %{data: 42}}, 5_000) ``` -------------------------------- ### Starting Finitomata Supervision Tree Source: https://context7.com/am-kantox/finitomata/llms.txt Start the supervision tree to manage FSM instances. The optional `id` parameter allows running multiple independent Finitomata trees. ```APIDOC ## Starting Finitomata Supervision Tree ### Description Start the supervision tree to manage FSM instances. The optional `id` parameter allows running multiple independent Finitomata trees. ### Method `Finitomata.start_link/0`, `Finitomata.start_link/1`, `Finitomata.child_spec/0`, `Finitomata.child_spec/1` ### Endpoint N/A ### Parameters #### Start Link - **id** (atom) - Optional - A custom ID for the Finitomata supervision tree. #### Child Spec - **id** (atom) - Optional - A custom ID for the Finitomata supervision tree. ### Request Example ```elixir # Start with default ID (nil) {:ok, _pid} = Finitomata.start_link() # Or start with a custom ID {:ok, _pid} = Finitomata.start_link(:my_app) # Or embed in your supervision tree defmodule MyApp.Application do use Application def start(_type, _args) do children = [ Finitomata.child_spec(), # With custom ID Finitomata.child_spec(:orders) ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) end end ``` ### Response - **{:ok, pid}** (tuple) - The PID of the started Finitomata supervisor. ### Response Example ```elixir {:ok, #PID<0.123.0>} ``` ``` -------------------------------- ### Get Dependencies and Compile Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md A combined command to fetch dependencies, compile them, and then compile the main project. ```bash mix do deps.get, deps.compile, compile ``` -------------------------------- ### Start Finitomata Supervision Tree Source: https://context7.com/am-kantox/finitomata/llms.txt Start the Finitomata supervision tree to manage FSM instances. You can use the default ID or provide a custom one. This can also be embedded within your application's main supervision tree. ```elixir # Start with default ID (nil) {:ok, _pid} = Finitomata.start_link() # Or start with a custom ID {:ok, _pid} = Finitomata.start_link(:my_app) # Or embed in your supervision tree defmodule MyApp.Application do use Application def start(_type, _args) do children = [ Finitomata.child_spec(), # With custom ID Finitomata.child_spec(:orders) ] opts = [strategy: :one_for_one, name: MyApp.Supervisor] Supervisor.start_link(children, opts) end end ``` -------------------------------- ### Get FSM Configuration - Entry Event Source: https://context7.com/am-kantox/finitomata/llms.txt Retrieves the configured entry event for a Finitomata state machine. This event is typically the starting point of the FSM's lifecycle. ```elixir entry_event = MyApp.QueryableFSM.__config__(:entry) # => :__start__ ``` -------------------------------- ### Install Telemetria Package Source: https://github.com/am-kantox/finitomata/blob/main/examples/telemetria/README.md Add this dependency to your mix.exs file to include the Telemetria package in your project. ```elixir def deps do [ {:telemetria, "~> 0.1.0"} ] end ``` -------------------------------- ### Finitomata start_fsm with parent PID Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Demonstrates how to override the default parent PID by including a :parent key in the initial payload when starting the FSM. ```elixir Finitomata.start_fsm(MyFSM, "my_fsm", %{parent: some_pid, foo: :bar}) ``` -------------------------------- ### Start and Manage Distributed FSMs with Infinitomata Source: https://context7.com/am-kantox/finitomata/llms.txt Integrate Infinitomata into your supervision tree to run FSMs across a cluster using :pg. Start distributed FSMs, perform transitions transparently across nodes, query state, and synchronize the cluster. ```elixir # Start Infinitomata in your supervision tree defmodule MyApp.Application do use Application def start(_type, _args) do children = [ # Start distributed FSM support {Infinitomata, [nil, Node.list()]} ] Supervisor.start_link(children, strategy: :one_for_one) end end # Start a distributed FSM {:ok, pid} = Infinitomata.start_fsm(MyApp.OrderFSM, "distributed_order_1", %{ order_id: "dist_001", items: ["item_x"] }) # Transitions work transparently across nodes :ok = Infinitomata.transition("distributed_order_1", :confirm) # Query state from any node %Finitomata.State{} = Infinitomata.state("distributed_order_1") # Get all FSMs across the cluster %{"distributed_order_1" => %{pid: pid, node: node, reference: ref}} = Infinitomata.all() # Check if alive true = Infinitomata.alive?("distributed_order_1") # Synchronize with cluster :ok = Infinitomata.synch() ``` -------------------------------- ### Start FSM Instance with `start_fsm/4` Source: https://context7.com/am-kantox/finitomata/llms.txt Create a new FSM instance with a unique name and initial payload. You can specify a custom Finitomata ID if you are managing multiple FSM supervisors. Use `Finitomata.alive?/1` to check if an FSM is running. ```elixir # Start an FSM instance {:ok, pid} = Finitomata.start_fsm(MyApp.OrderFSM, "order_123", %{ order_id: "123", items: ["item_a", "item_b"], total: 99.99 }) # With custom Finitomata ID {:ok, pid} = Finitomata.start_fsm(:orders, MyApp.OrderFSM, "order_456", %{ order_id: "456", items: ["item_c"], total: 49.99 }) # Check if FSM was started true = Finitomata.alive?("order_123") ``` -------------------------------- ### Mermaid FSM Definition Example Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Example of defining a Finite State Machine using Mermaid flowchart syntax. ```mermaid s1 --> |to_s2| s2 s1 --> |to_s3| s3 ``` -------------------------------- ### Normal Event Example Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Illustrates a normal event transition. The transition must be triggered explicitly using `Finitomata.transition/3`. If the transition fails, `on_failure/3` is called. ```text ready --> |process| done ``` -------------------------------- ### Get FSM Configuration - Events Source: https://context7.com/am-kantox/finitomata/llms.txt Retrieves the list of defined events for a Finitomata state machine. No specific setup is required beyond defining the FSM module. ```elixir events = MyApp.QueryableFSM.__config__(:events) # => [:__start__, :start, :pause, :resume, :stop, :__end__] ``` -------------------------------- ### Event Payload Example Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Shows how to attach event-specific data to a transition call. This data is passed as the third argument to `on_transition/4`. ```elixir # Attach event payload to a transition Finitomata.transition("my_fsm", {:process, %{user_id: 42, action: :approve}}) ``` -------------------------------- ### PlantUML FSM Definition Example Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Example of defining a Finite State Machine using PlantUML state diagram syntax. ```plantuml [*] --> s1 : to_s1 s1 --> s2 : to_s2 s1 --> s3 : to_s3 s2 --> [*] : ok s3 --> [*] : ok ``` -------------------------------- ### Soft Event Example Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Shows soft events (ending with '?'). These events do not trigger `on_failure/3` or log warnings if the transition fails, making them suitable for optimistic transitions. ```text ready --> |try_call?| done ``` -------------------------------- ### Finitomata Initialization Chain Pattern Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md An FSM can auto-transition through initial states like `idle -> configuring -> ready` without external triggers, then wait for a specific event like `:process`. This is useful for setup sequences. ```elixir idle --> |init!| configuring configuring --> |configure!| ready ready --> |process| done ``` -------------------------------- ### Test FSMs with Finitomata.ExUnit Source: https://context7.com/am-kantox/finitomata/llms.txt Utilize Finitomata.ExUnit for comprehensive FSM testing, including setup helpers and assertion macros. Define FSMs with a :mox listener for testing purposes. ```elixir defmodule MyApp.OrderFSMTest do use ExUnit.Case, async: true import Mox import Finitomata.ExUnit # Define the FSM with :mox listener for testing defmodule TestOrderFSM do @fsm """ pending --> |confirm| confirmed confirmed --> |ship| shipped """ use Finitomata, fsm: @fsm, syntax: :flowchart, listener: :mox @impl Finitomata def on_transition(:pending, :confirm, _payload, state) do {:ok, :confirmed, state} end @impl Finitomata def on_transition(:confirmed, :ship, %{carrier: carrier}, state) do {:ok, :shipped, Map.put(state, :carrier, carrier)} end end describe "OrderFSM" do setup_finitomata do [ fsm: [ implementation: TestOrderFSM, payload: %{order_id: "test_123", items: []} ], context: [order_id: "test_123"] ] end test_path "happy path through order lifecycle", %{order_id: order_id} do :confirm -> assert_state :confirmed do assert_payload do order_id ~> ^order_id end end {:ship, %{carrier: "UPS"}} -> assert_state :shipped do assert_payload do carrier ~> "UPS" order_id ~> ^order_id end end end test "individual transition assertion", ctx do assert_transition ctx, :confirm do :confirmed -> assert_payload do order_id ~> "test_123" end end end end end ``` -------------------------------- ### Get FSM Configuration - Paths Source: https://context7.com/am-kantox/finitomata/llms.txt Retrieves the defined transition paths for a Finitomata state machine. This configuration is essential for understanding the possible state transitions. ```elixir paths = MyApp.QueryableFSM.__config__(:paths) # => [list of Finitomata.Transition.Path structs] ``` -------------------------------- ### Elixir FSM Runtime Error Example Source: https://github.com/am-kantox/finitomata/blob/main/stuff/compiler.md This example demonstrates the shape of a runtime error that occurs when an ambiguous transition is called without a defined handler. It highlights the need for compile-time checks. ```elixir {:error, {:ambiguous_transition, {:s2, :ambiguous}, [:s3. :s4]}} ``` -------------------------------- ### Hard Event Example Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Demonstrates hard events (ending with '!'). These events fire automatically if they are the sole outgoing event from a state. `on_failure/3` is still called if the transition fails. ```text idle --> |init!| ready ready --> |do!| done done --> |finish!| ended ``` -------------------------------- ### Define a Finite State Machine Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Define a finite state machine using Finitomata's `@fsm` attribute and `use Finitomata` macro. This example defines a simple FSM with 'idle', 'ready', and 'done' states. ```elixir defmodule MyFSM do @fsm """ idle --> |wake!| ready ready --> |process| done """ use Finitomata, fsm: @fsm, auto_terminate: true @impl Finitomata def on_transition(:ready, :process, _event_payload, state_payload) do {:ok, :done, state_payload} end end ``` -------------------------------- ### Define FSM with PlantUML State Diagram Syntax Source: https://context7.com/am-kantox/finitomata/llms.txt Defines a document FSM using Finitomata with PlantUML state diagram syntax. Explicit start and end states are used. The `on_transition/4` callback is mandatory. ```elixir defmodule MyApp.DocumentFSM do @fsm """ [*] --> draft : create draft --> review : submit draft --> draft : edit review --> approved : approve review --> draft : reject approved --> published : publish published --> [*] : archive """ use Finitomata, fsm: @fsm, syntax: :state_diagram @impl Finitomata def on_transition(:draft, :submit, _payload, state) do {:ok, :review, Map.put(state, :submitted_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:draft, :edit, changes, state) do {:ok, :draft, Map.merge(state, changes)} end @impl Finitomata def on_transition(:review, :approve, %{approver: approver}, state) do {:ok, :approved, Map.put(state, :approved_by, approver)} end @impl Finitomata def on_transition(:review, :reject, %{reason: reason}, state) do {:ok, :draft, Map.put(state, :rejection_reason, reason)} end @impl Finitomata def on_transition(:approved, :publish, _payload, state) do {:ok, :published, Map.put(state, :published_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:published, :archive, _payload, state) do {:ok, :*, state} # :* is the final state end end ``` -------------------------------- ### Distributed FSM with Infinitomata Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Use Infinitomata as a drop-in replacement for Finitomata to distribute FSMs across cluster nodes using `:pg` process groups. The API remains the same for starting, transitioning, and querying. ```elixir # In your supervision tree {Infinitomata, nil} # Start, transition, query -- same API Infinitomata.start_fsm(MyFSM, "my_fsm", %{foo: :bar}) Infinitomata.transition("my_fsm", {:process, nil}) Infinitomata.state("my_fsm") ``` -------------------------------- ### Install Caches Package with Mix Source: https://github.com/am-kantox/finitomata/blob/main/examples/caches/README.md Add the Caches package to your project's dependencies in mix.exs. This is the standard way to include Elixir libraries. ```elixir def deps do [ {:caches, "~> 0.1.0"} ] end ``` -------------------------------- ### Get FSM Configuration - Loops Source: https://context7.com/am-kantox/finitomata/llms.txt Retrieves any defined loop paths within the Finitomata state machine configuration. This is useful for identifying cyclical transitions. ```elixir loops = MyApp.QueryableFSM.__config__(:loops) # => [loop paths if any exist] ``` -------------------------------- ### Finitomata on_start callback Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md The optional on_start callback is called once during initialization. It can modify the initial payload or control startup behavior by returning {:continue, new_payload} or {:ok, new_payload}. ```elixir @impl Finitomata def on_start(payload) do # Fetch some data, validate, enrich the payload, etc. {:continue, Map.put(payload, :started_at, DateTime.utc_now())} end ``` -------------------------------- ### Implement FSM Callbacks with Flowchart Syntax Source: https://context7.com/am-kantox/finitomata/llms.txt Defines a payment FSM using Finitomata with various callbacks for lifecycle events. The `on_transition/4` callback is mandatory. Requires `syntax: :flowchart` and `timer` option. ```elixir defmodule MyApp.PaymentFSM do @fsm """ pending --> |process| processing processing --> |complete| completed processing --> |fail| failed failed --> |retry| processing """ use Finitomata, fsm: @fsm, syntax: :flowchart, timer: 5_000, # Call on_timer every 5 seconds auto_terminate: [:completed, :failed] # Auto-terminate in these states # Called when FSM starts (optional) @impl Finitomata def on_start(payload) do {:continue, Map.put(payload, :started_at, DateTime.utc_now())} end # Main transition handler (mandatory) @impl Finitomata def on_transition(:pending, :process, %{amount: amount}, payload) do case process_payment(amount) do {:ok, txn_id} -> {:ok, :processing, Map.put(payload, :txn_id, txn_id)} {:error, _} -> {:error, :payment_failed} end end @impl Finitomata def on_transition(:processing, :complete, _event_payload, payload) do {:ok, :completed, payload} end @impl Finitomata def on_transition(:processing, :fail, reason, payload) do {:ok, :failed, Map.put(payload, :failure_reason, reason)} end @impl Finitomata def on_transition(:failed, :retry, _event_payload, payload) do {:ok, :processing, Map.update(payload, :retry_count, 1, &(&1 + 1))} end # Called on entering a state (optional) @impl Finitomata def on_enter(:completed, state) do Logger.info("Payment #{state.payload.txn_id} completed") :ok end # Called on exiting a state (optional) @impl Finitomata def on_exit(:processing, _state), do: :ok # Called when transition fails (optional) @impl Finitomata def on_failure(:process, _event_payload, state) do Logger.warning("Payment processing failed for #{inspect(state.payload)}") :ok end # Called on timer tick (optional, requires timer: option) @impl Finitomata def on_timer(:processing, state) do # Check payment status case check_payment_status(state.payload.txn_id) do :completed -> {:transition, :complete, state.payload} :failed -> {:transition, {:fail, :timeout}, state.payload} :pending -> :ok # Keep waiting end end # Called before termination (optional) @impl Finitomata def on_terminate(state) do Logger.info("FSM terminated in state #{state.current}") :ok end defp process_payment(_amount), do: {:ok, "txn_#{:rand.uniform(1000)}"} defp check_payment_status(_txn_id), do: :completed end ``` -------------------------------- ### Finitomata Transitions Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Details on how to trigger transitions, including event payload wrapping and delayed transitions. ```APIDOC ## Finitomata Transitions ### Event Payload Wrapping Finitomata automatically wraps event payloads for internal processing: - If the event payload is a map, the `__retries__` key is injected or incremented. - If the event payload is not a map, it is wrapped as `%{payload: original_value, __retries__: 1}`. - If the event payload is `nil`, it becomes `%{__retries__: 1}`. ### Triggering Transitions Transitions can be triggered using the `Finitomata.transition/3` function. **Basic Transition:** ```elixir Finitomata.transition("my_fsm", :process) # Equivalent to: Finitomata.transition("my_fsm", {:process, nil}) ``` **Delayed Transitions:** Transitions can be delayed by providing a third argument representing the delay in milliseconds. ```elixir # Fire :process after 5 seconds with event payload Finitomata.transition("my_fsm", {:process, %{data: 42}}, 5_000) ``` ``` -------------------------------- ### Format Code Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Formats the project's code according to defined style guidelines. ```bash mix format ``` -------------------------------- ### Defining an FSM with `use Finitomata` Source: https://context7.com/am-kantox/finitomata/llms.txt The `use Finitomata` macro transforms a module into a FSM by parsing the diagram syntax and generating callbacks. Callbacks like `on_transition` can be defined to handle state transitions. ```APIDOC ## Defining an FSM with `use Finitomata` ### Description The `use Finitomata` macro transforms a module into a fully functional FSM by parsing the diagram syntax and generating callbacks. ### Method Macro ### Endpoint N/A ### Parameters #### Request Body - **fsm** (string) - Required - The FSM description string. - **syntax** (atom) - Optional - The syntax of the FSM description (:flowchart or :plantuml). Defaults to :flowchart. ### Request Example ```elixir defmodule MyApp.OrderFSM do @fsm """ pending --> |confirm| confirmed confirmed --> |ship| shipped shipped --> |deliver| delivered delivered --> |archive| archived """ use Finitomata, fsm: @fsm, syntax: :flowchart @impl Finitomata def on_transition(:pending, :confirm, event_payload, state_payload) do # Validate order and update payload {:ok, :confirmed, Map.put(state_payload, :confirmed_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:confirmed, :ship, _event_payload, state_payload) do {:ok, :shipped, Map.put(state_payload, :shipped_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:shipped, :deliver, _event_payload, state_payload) do {:ok, :delivered, Map.put(state_payload, :delivered_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:delivered, :archive, _event_payload, state_payload) do {:ok, :archived, state_payload} end end ``` ### Response N/A (This is a macro) ### Response Example N/A ``` -------------------------------- ### Compile Project with Finitomata Compiler Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Compiles the project, including the custom :finitomata compiler for FSM definitions. ```bash mix compile ``` -------------------------------- ### Finitomata Callbacks Reference Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md A comprehensive reference for all available Finitomata callbacks, including their purpose and expected return values. ```APIDOC ## Callbacks Reference ### `on_transition/4` -- mandatory This is the core callback, invoked for every transition attempt. ```elixir @impl Finitomata def on_transition(current_state, event, event_payload, state_payload) do {:ok, next_state, new_state_payload} # or {:error, reason} end ``` - For ambiguous transitions (same event leading to multiple possible states), you must explicitly return the `next_state`. - For determined transitions (only one possible target state), Finitomata can handle the state progression automatically if this callback returns `{:ok, new_state_payload}`. ### `on_start/1` -- optional Called once during FSM initialization. See the "Initial State and Startup" section for details. ### `on_enter/2` -- optional, pure Called immediately after entering a new state. ```elixir @impl Finitomata def on_enter(:ready, state) do Logger.info("Entered ready state") :ok end ``` ### `on_exit/2` -- optional, pure Called immediately before exiting a state. ```elixir @impl Finitomata def on_exit(:ready, state) do Logger.info("Leaving ready state") :ok end ``` ### `on_failure/3` -- optional, pure Called when a transition fails. This callback is not invoked for soft (`?`) events. ```elixir @impl Finitomata def on_failure(event, event_payload, state) do Logger.warning("Transition #{event} failed: #{inspect(state.last_error)}") :ok end ``` ### `on_terminate/1` -- optional, pure Called when the FSM reaches its final state and is about to terminate. ```elixir @impl Finitomata def on_terminate(state) do Logger.info("FSM terminated: #{inspect(state.payload)}") :ok end ``` ### `on_timer/2` -- optional, mutating Called periodically if a `timer: milliseconds` option is set for the FSM. Allows for timed actions. ```elixir use Finitomata, fsm: @fsm, timer: 5_000 @impl Finitomata def on_timer(:ready, state) do # Option 1: Do nothing :ok # Option 2: Update the state payload # {:ok, new_payload} # Option 3: Trigger a transition # {:transition, :some_event, new_payload} # Option 4: Trigger a transition with an event payload # {:transition, {:event, ev_payload}, new_payload} # Option 5: Reschedule the timer with a new interval # {:reschedule, 10_000} end ``` ``` -------------------------------- ### Finitomata Use Options Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Configure Finitomata's behavior using various options when using the macro. Options control FSM definition, syntax, callback implementations, termination, timers, state persistence, listeners, and GenServer shutdown. ```elixir use Finitomata, fsm: @fsm, # required -- the FSM definition string syntax: :flowchart, # :flowchart (default) | :state_diagram impl_for: :all, # :all | :none | list of callback names auto_terminate: false, # true | state_atom | [state_atoms] timer: false, # false | pos_integer (ms) ensure_entry: [], # true | [state_atoms] hibernate: false, # true | false | [state_atoms] cache_state: true, # cache payload in :persistent_term persistency: nil, # module implementing Finitomata.Persistency listener: nil, # module | :mox | {:mox, FallbackModule} forks: [], # [{state, {event, fork_module}}, ...] shutdown: 5_000 # GenServer shutdown timeout ``` -------------------------------- ### Run Finitomata Environment Tests Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Sets the MIX_ENV to 'finitomata' and runs tests specifically for the library's internal environment, excluding general tests. ```bash MIX_ENV=finitomata mix do deps.get, deps.compile, compile MIX_ENV=finitomata mix test --exclude test --include finitomata ``` -------------------------------- ### Generate Test Scaffolding Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Generates basic test file scaffolding for a given FSM module. ```bash mix finitomata.generate.test --module MyApp.FSM ``` -------------------------------- ### Define FSM with `use Finitomata` Source: https://context7.com/am-kantox/finitomata/llms.txt Define a Finite State Machine using the `use Finitomata` macro. The FSM diagram is provided as a string, and callback functions handle state transitions and payload updates. Ensure all required callbacks are implemented for each transition. ```elixir defmodule MyApp.OrderFSM do @fsm """ pending --> |confirm| confirmed confirmed --> |ship| shipped shipped --> |deliver| delivered delivered --> |archive| archived """ use Finitomata, fsm: @fsm, syntax: :flowchart @impl Finitomata def on_transition(:pending, :confirm, event_payload, state_payload) do # Validate order and update payload {:ok, :confirmed, Map.put(state_payload, :confirmed_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:confirmed, :ship, _event_payload, state_payload) do {:ok, :shipped, Map.put(state_payload, :shipped_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:shipped, :deliver, _event_payload, state_payload) do {:ok, :delivered, Map.put(state_payload, :delivered_at, DateTime.utc_now())} end @impl Finitomata def on_transition(:delivered, :archive, _event_payload, state_payload) do {:ok, :archived, state_payload} end end ``` -------------------------------- ### Check FSM Transition and Response Capabilities Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Determine if a specific transition to a target state is allowed or if the FSM can currently handle a given event. Also, check if the FSM process is currently alive. ```elixir # Can we transition to state :done? Finitomata.allowed?("my_fsm", :done) # Can we handle event :process right now? Finitomata.responds?("my_fsm", :process) # Is this FSM alive? Finitomata.alive?("my_fsm") ``` -------------------------------- ### Run All Tests Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Executes all tests in the project, excluding tests related to distributed functionality and the finitomata environment. ```bash mix test ``` -------------------------------- ### Implement FSM Persistence with Finitomata.Persistency Source: https://context7.com/am-kantox/finitomata/llms.txt Implement the `Finitomata.Persistency` behaviour to save and load FSM states to a database. Requires defining `load/1` and `store/3` functions. ```elixir defmodule MyApp.FSMPersistency do @behaviour Finitomata.Persistency @impl Finitomata.Persistency def load({type, %{id: id}}) do case MyApp.Repo.get_by(type, id: id) do nil -> {:created, {nil, %{id: id}}} record -> {:loaded, {record.state, Map.from_struct(record)}} end end @impl Finitomata.Persistency def store(_name, payload, info) do changeset = MyApp.Order.changeset(%MyApp.Order{}, %{ state: info.to, payload: payload }) case MyApp.Repo.insert_or_update(changeset) do {:ok, _record} -> :ok {:error, changeset} -> {:error, changeset} end end @impl Finitomata.Persistency def store_error(name, payload, error, info) do Logger.error("FSM #{name} error: #{inspect(error)}") {:ok, payload} end end # Use in FSM definition defmodule MyApp.PersistentOrderFSM do @fsm """ pending --> |confirm| confirmed confirmed --> |complete| completed """ use Finitomata, fsm: @fsm, syntax: :flowchart, persistency: MyApp.FSMPersistency @impl Finitomata def on_transition(:pending, :confirm, _payload, state) do {:ok, :confirmed, state} end @impl Finitomata def on_transition(:confirmed, :complete, _payload, state) do {:ok, :completed, state} end end ``` -------------------------------- ### Finitomata on_transition callback Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md The mandatory on_transition callback is invoked for every transition attempt. It should return {:ok, next_state, new_state_payload} or {:error, reason}. ```elixir def on_transition(:ready, :process, event_payload, state_payload) do {:ok, :done, Map.put(state_payload, :last_user, event_payload.user_id)} end ``` ```elixir @impl Finitomata def on_transition(current_state, event, event_payload, state_payload) do {:ok, next_state, new_state_payload} # or {:error, reason} end ``` -------------------------------- ### Run Quality Assurance Tasks Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Executes formatting, Credo static analysis, and Dialyzer type checking. ```bash mix quality ``` -------------------------------- ### Add Finitomata Dependency Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md Add the Finitomata dependency to your project's `mix.exs` file. ```elixir def deps do [{:finitomata, "~> 0.30"}] end ``` -------------------------------- ### Finitomata on_enter callback Source: https://github.com/am-kantox/finitomata/blob/main/stuff/cheatsheet.md The optional on_enter callback is called after entering a new state. It receives the state name and the current state data. ```elixir @impl Finitomata def on_enter(:ready, state) do Logger.info("Entered ready state") :ok end ``` -------------------------------- ### Define Turnstile FSM with Finitomata Source: https://github.com/am-kantox/finitomata/blob/main/stuff/fsm.md Defines the states and transitions for a turnstile FSM using Finitomata's DSL. Includes custom `on_transition` callbacks for specific events. ```elixir defmodule Turnstile do @fsm """ built --> |on!| locked locked --> |push| locked locked --> |coin?| unlocked unlocked --> |push| locked unlocked --> |coin?| unlocked unlocked --> |off| destroyed """ use Finitomata, @fsm, auto_terminate: true def on_transition(state, :push, _event_payload, state_payload) do if state == :locked, do: electrocute!() {:ok, :locked, state_payload} end def on_transition(:locked, :coin?, _event_payload, state_payload) do {:ok, :unlocked, state_payload} end def on_transition(:unlocked, :coin, _event_payload, state_payload) do Logger.info("Thanks, this coin will be donated to the animal shelter!") {:error, :unexpected_coin} end def on_transition(_, :off, _, state_payload), do: {:ok, :destroyed, state_payload} # def on_failure(…), do: … # def on_terminate(…), do: … end ``` -------------------------------- ### Run CI Quality Assurance Source: https://github.com/am-kantox/finitomata/blob/main/WARP.md Runs quality assurance tasks in a CI environment, including format checking. ```bash mix quality.ci ``` -------------------------------- ### Define FSM with Flowchart Syntax Source: https://github.com/am-kantox/finitomata/blob/main/README.md Defines a Finite State Machine (FSM) using Finitomata with the :flowchart syntax. Requires implementing `on_transition/4` callbacks for state changes. ```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 ``` -------------------------------- ### Checking Transition Availability Source: https://context7.com/am-kantox/finitomata/llms.txt Query whether specific states or events are available from the current state. ```APIDOC ## Checking Transition Availability ### Description Query whether specific states or events are available from the current state. ### Method `Finitomata.allowed?/3`, `Finitomata.responds?/3` ### Endpoint N/A ### Parameters #### `allowed?/3` - **finitomata_id** (atom) - Optional - The ID of the Finitomata supervision tree to use. Defaults to nil. - **fsm_name** (string) - Required - The name of the FSM instance. - **state** (atom) - Required - The target state to check for availability. #### `responds?/3` - **finitomata_id** (atom) - Optional - The ID of the Finitomata supervision tree to use. Defaults to nil. - **fsm_name** (string) - Required - The name of the FSM instance. - **event** (atom) - Required - The event to check for availability. ### Request Example ```elixir # Check if transition to a state is allowed true = Finitomata.allowed?("order_123", :shipped) false = Finitomata.allowed?("order_123", :archived) # Check if FSM responds to an event true = Finitomata.responds?("order_123", :ship) false = Finitomata.responds?("order_123", :archive) ``` ### Response - **true** (boolean) - Indicates the transition or event is allowed/available. - **false** (boolean) - Indicates the transition or event is not allowed/available. ### Response Example ```elixir true ``` ``` -------------------------------- ### Transitioning State with `transition/4` Source: https://context7.com/am-kantox/finitomata/llms.txt Trigger state transitions by sending events with optional payloads. ```APIDOC ## Transitioning State with `transition/4` ### Description Trigger state transitions by sending events with optional payloads. ### Method `Finitomata.transition/4` ### Endpoint N/A ### Parameters - **finitomata_id** (atom) - Optional - The ID of the Finitomata supervision tree to use. Defaults to nil. - **fsm_name** (string) - Required - The name of the FSM instance. - **event** (atom or tuple) - Required - The event to trigger. Can be an atom or a tuple `{event_atom, event_payload}`. - **delay_ms** (integer) - Optional - A delay in milliseconds before triggering the transition. ### Request Example ```elixir # Simple transition :ok = Finitomata.transition("order_123", :confirm) # Transition with event payload :ok = Finitomata.transition("order_123", {:ship, %{carrier: "FedEx", tracking: "ABC123"}}) # Delayed transition (500ms delay) :ok = Finitomata.transition("order_123", {:deliver, %{signature: "John Doe"}}, 500) # With custom Finitomata ID :ok = Finitomata.transition(:orders, "order_456", :confirm) ``` ### Response - **:ok** (atom) - Indicates the transition was successfully triggered. ### Response Example ```elixir :ok ``` ```