Durable session store backed by EKV (SQLite in WAL mode, via a vendored NIF).
Requires the optional :ekv dependency:
{:ekv, "~> 0.4"}Start the store in a supervision tree and point sessions at it:
children = [
{FIX.Session.Store.EKV, data_dir: "/var/lib/my_app/fix_session"},
{FIX.Session, FIX.Session.Config.new!(store: FIX.Session.Store.EKV, ...)}
]Store operations take the EKV instance name as the store_ref.
Options
:name— the EKV instance name,FIX.Session.Store.EKVby default, which also serves as thestore_ref. Pass a distinct atom per instance to run several stores side by side.:data_dir— required. Directory holding the SQLite files; created if absent. Reusing adata_dirresumes its persisted state, which is what makes this store durable.
All other options are passed through to EKV.start_link/1.
Durability
Writes are committed to SQLite before the store call returns, so state
survives session, store, and VM crashes. SQLite runs in WAL mode with
synchronous=NORMAL: the most recent commits can still be lost to an
OS crash or power failure.
commit_outbound/5 writes the wire record and then the advanced
next_out pointer as two single-key EKV writes (EKV has no multi-key
transactions). A crash between the two leaves a wire record that was
never sent — commits happen before the socket write — so on reload the
stale pointer hands out that sequence number again and the next commit
overwrites the orphan. No sequence number is reused on the wire or
skipped.
Session ids become part of durable keys via
:erlang.term_to_binary(session_id, [:deterministic]), so a session
id must have a stable external term format (atoms, binaries, numbers,
and tuples/lists/maps thereof).
Error semantics
load/2, save_inbound/3, and commit_outbound/5 report an
unreachable EKV instance as {:error, :store_unavailable}.
get_outbound/3 instead raises when the instance is gone: its
:error return must mean "never stored" and nothing else, because the
resend path answers it with a SequenceReset-GapFill. This store never
deletes entries or sets TTLs, so a nil read strictly means the
sequence number was never committed.
Summary
Types
@type store_ref() :: atom()
Functions
@spec child_spec(keyword()) :: Supervisor.child_spec()
@spec start_link(keyword()) :: Supervisor.on_start()