StatifierPersistence.Storage.InMemory (StatifierPersistence v0.1.0)

Copy Markdown View Source

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

state()

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

fetch_chart(opts, content_hash)

Fetches the chart stored under content_hash, or :chart_not_found.

fetch_position(opts, session_id)

Fetches the position stored for session_id, or :position_not_found.

fetch_run(opts, run_id)

Fetches the run stored under run_id, or :run_not_found.

init(opts)

Starts the backing Agent and returns opts with :pid merged in - the handle every other callback expects as its first argument.

insert_run(opts, run_record)

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.

lock_run(opts, run_id, fun)

@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).

save_chart(opts, chart_record)

Stores chart_record under its content_hash, idempotent on repeat writes of the same hash.

save_position(opts, position_record)

Stores position_record under its session_id, overwriting any position already stored for that session.

update_run(opts, run_record)

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.