The reference StatifierPersistence.Storage.Adapter: an Agent holding
three maps - charts keyed by content hash, positions keyed by session id,
and runs keyed by run id.
It ships in lib/, not the test-only support/ directory, for two
reasons: the conformance template this package ships in lib/ (this
package's own Testing namespace) needs a reference implementation to
check against from outside this repository's own test/, and a host
prototyping the stepper wants an adapter with no database to stand up.
Summary
Types
This adapter's state: the three record maps init/1 starts the Agent
with, plus the per-run lock table lock_run/3 acquires through.
Functions
Fetches the chart stored under content_hash, or :chart_not_found.
Fetches the position stored for session_id, or :position_not_found.
Fetches the run stored under run_id, or :run_not_found.
Starts the backing Agent and returns opts with :pid merged in - the
handle every other callback expects as its first argument.
Inserts run_record under its run_id, refusing a duplicate with
{:error, :run_exists}.
Runs fun under this adapter's per-run mutual exclusion for run_id
(the optional StatifierPersistence.Storage.Adapter.lock_run/3).
Stores chart_record under its content_hash, idempotent on repeat
writes of the same hash.
Stores position_record under its session_id, overwriting any position
already stored for that session.
Overwrites the run stored under run_record's run_id with the full
record, or refuses with :run_not_found when no run exists for the id.
Types
@type state() :: %{ charts: %{ required(StatifierPersistence.Storage.Adapter.content_hash()) => StatifierPersistence.Storage.Adapter.chart_record() }, positions: %{ required(StatifierPersistence.Storage.Adapter.session_id()) => StatifierPersistence.Storage.Adapter.position_record() }, runs: %{ required(StatifierPersistence.Storage.Adapter.run_id()) => StatifierPersistence.Storage.Adapter.run_record() }, locks: %{ required(StatifierPersistence.Storage.Adapter.run_id()) => reference() } }
This adapter's state: the three record maps init/1 starts the Agent
with, plus the per-run lock table lock_run/3 acquires through.
Functions
@spec fetch_chart( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.content_hash() ) :: {:ok, StatifierPersistence.Storage.Adapter.chart_record()} | {:error, StatifierPersistence.Storage.Adapter.error()}
Fetches the chart stored under content_hash, or :chart_not_found.
@spec fetch_position( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.session_id() ) :: {:ok, StatifierPersistence.Storage.Adapter.position_record()} | {:error, StatifierPersistence.Storage.Adapter.error()}
Fetches the position stored for session_id, or :position_not_found.
@spec fetch_run( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.run_id() ) :: {:ok, StatifierPersistence.Storage.Adapter.run_record()} | {:error, StatifierPersistence.Storage.Adapter.error()}
Fetches the run stored under run_id, or :run_not_found.
@spec init(StatifierPersistence.Storage.Adapter.opts()) :: {:ok, StatifierPersistence.Storage.Adapter.opts()} | {:error, StatifierPersistence.Storage.Adapter.error()}
Starts the backing Agent and returns opts with :pid merged in - the
handle every other callback expects as its first argument.
@spec insert_run( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.run_record() ) :: :ok | {:error, StatifierPersistence.Storage.Adapter.error()}
Inserts run_record under its run_id, refusing a duplicate with
{:error, :run_exists}.
The exists-check and the write run inside one Agent.get_and_update/2
call, so they are a single atomic state transition: two concurrent
inserts of the same run_id cannot both return :ok.
@spec lock_run( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.run_id(), (-> result) ) :: {:ok, result} | {:error, StatifierPersistence.Storage.Adapter.error()} when result: term()
Runs fun under this adapter's per-run mutual exclusion for run_id
(the optional StatifierPersistence.Storage.Adapter.lock_run/3).
Acquisition is an insert-if-absent on the Agent's lock table, one atomic
Agent.get_and_update/2 transition; contention spins with a small
bounded sleep (5ms) between attempts. The lock is
released in an after block, so any exit from fun - a raise included -
releases it; the raise itself propagates to the caller.
Simple and honest for a reference adapter. A production adapter should prefer its backend's native lock - the Ecto adapter implements this as a transaction-scoped advisory-plus-row lock (ADR-0004 decision 5 as amended 2026-08-22).
@spec save_chart( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.chart_record() ) :: :ok | {:error, StatifierPersistence.Storage.Adapter.error()}
Stores chart_record under its content_hash, idempotent on repeat
writes of the same hash.
@spec save_position( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.position_record() ) :: :ok | {:error, StatifierPersistence.Storage.Adapter.error()}
Stores position_record under its session_id, overwriting any position
already stored for that session.
@spec update_run( StatifierPersistence.Storage.Adapter.opts(), StatifierPersistence.Storage.Adapter.run_record() ) :: :ok | {:error, StatifierPersistence.Storage.Adapter.error()}
Overwrites the run stored under run_record's run_id with the full
record, or refuses with :run_not_found when no run exists for the id.