StatifierPersistence

CI Hex.pm Version Hex Downloads Hex Docs License

Durable stepper and storage adapters for Statifier. An execution of a chart is a stored row rather than a live process: each event loads its position, steps it, hands the effects to your host and persists the result, so a loan that runs for weeks survives every restart and deploy in between.

Pre-1.0. Until statifier_persistence reaches v1.0, its public surface may change between minor releases, sometimes drastically: a release may rename modules, callbacks, table columns, telemetry events or error vocabulary with no compatibility shim. Every such change is recorded in CHANGELOG.md under a bold Breaking heading that says what to do about it. Pinning to an exact minor - ~> X.Y.0 - is the recommended way to consume the package until 1.0. What a host changes for each minor from 0.13 on is on one page: docs/upgrading.md.

Why a durable stepper

Statifier's pure interpreter contract (machine_state, event -> machine_state, effects) makes a persistence-first execution model possible: load a persisted position, step it, execute the effects, persist. Hosts running charts that span days or survive deploys should not need long-lived Session processes at all - but every host otherwise hand-rolls the loop, the storage guard, and the crash semantics. This package is that loop, packaged: a storage-adapter behaviour whose every load is guarded by the chart's identity, so a position is never resumed against the wrong chart; an execution lifecycle that orders concurrent deliveries to one execution; effects handed to your host through one seam; and an Ecto adapter with versioned migrations, where your host supplies the Repo and any tenancy columns. Domain actions, authoring UI and job scheduling stay out of it: statifier_oban owns timers and async work.

Installation

def deps do
  [
    {:statifier_persistence, "~> 0.25.0"},
    # Optional, for the Postgres adapter:
    {:ecto_sql, "~> 3.10"}
  ]
end

Basic usage

A copy of a book lent to a patron is one execution, keyed by the loan. Store the compiled chart, create the execution, and close the loan later from any process with nothing but its id:

alias Statifier.{Chart, Event}
alias StatifierPersistence.{Executions, Storage}

source = """
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="on_loan">
  <state id="on_loan">
    <transition event="loan.due" target="due"/>
    <transition event="loan.returned" target="returned"/>
  </state>
  <state id="due">
    <transition event="loan.returned" target="returned"/>
  </state>
  <final id="returned"/>
</scxml>
"""

# Store the compiled chart once; every later load is checked against it.
{:ok, machine} = Statifier.compile(source)
{:ok, chart_blob} = Chart.to_binary(machine)
{:ok, store} = Storage.new(StatifierPersistence.Storage.InMemory, [])
:ok = Storage.save_chart(store, machine, chart_blob)

# Effects reach your host through one function; this chart emits none.
opts = [executor: fn _effect, _ctx -> :ok end]

{:ok, _execution, _state} = Executions.create(store, "loan_01H8", machine, opts)

# Later, in any process, with only the execution id:
{:ok, record} = Storage.fetch_execution(store, "loan_01H8")
{:ok, %{chart_blob: blob}} = Storage.fetch_chart(store, record.content_hash)
{:ok, machine} = Chart.from_binary(blob)

{:ok, execution, _state} =
  Executions.step(store, "loan_01H8", machine, Event.external("loan.returned"), opts)

execution.status
#=> :completed

The in-memory store keeps the example short; the Ecto adapter stores the same rows in your database, and How to persist an execution and resume it in another process walks the whole loop with a chart that calls your host.

Documentation

Compatibility