DurableStash
Durable, browser-session-scoped server state for Phoenix LiveView.
DurableStash is a LiveStash adapter backed by DurableServer: one durable process per browser session, persisted to S3-compatible object storage, shared by every LiveView of that session.
State survives:
- live navigation (unlike the stock ETS adapter, recovery happens on every mount, not just reconnects)
- WebSocket reconnects (Wi-Fi hiccups)
- LiveView crashes
- redeploys — state lives in object storage, not BEAM memory
State dies with the browser session: cleared cookies, another browser, or TTL expiry mean defaults.
Usage
defmodule MyAppWeb.SomeLive do
use MyAppWeb, :live_view
use LiveStash, adapter: DurableStash, stored_keys: [:count, :username]
def mount(_params, _session, socket) do
socket = assign(socket, count: 0, username: nil)
{_status, socket} = LiveStash.recover_state(socket)
{:ok, socket}
end
def handle_event("increment", _params, socket) do
socket = update(socket, :count, &(&1 + 1))
{:noreply, LiveStash.stash(socket)}
end
end
Scopes
Not all state wants the same recovery policy, so each stored key declares one:
use LiveStash, adapter: DurableStash,
stored_keys: [
theme: :session, # recover on every mount (the default for bare atoms)
draft: :reconnect # recover only on rejoins; cleared on fresh navigation
]
:session— recovered on every mount: live navigation, reconnects, crashes, redeploys. Right for settings the user expects to stick.:reconnect— recovered only when the client rejoins an existing view (_mounts > 0): Wi-Fi drops, LiveView crashes, and redeploys — the browser stays on the page through all of these. A fresh navigation to the view clears the stored values, so starting a new thing starts blank. Right for in-progress form drafts.
See the DurableStash moduledoc for setup (adapter registration, backend
config, the ensure_session_id plug) and all options (:vsn, :migrate,
:secret).
How it works
- A plug puts a random
"sid"into the cookie session; the adapter hashes it (with a secret) into the storage key of oneDurableStash.Sessionprocess. - All LiveViews of a session write through that single actor: per-key diffs, key-wise merge, per-key last-write-wins. Two tabs writing different keys cannot clobber each other; same-key writes are totally ordered.
- Every accepted write syncs to object storage immediately, so a deploy can never lose acknowledged state.
- Values are JSON-normalized at stash time — what you recover in dev is exactly what you would recover after a redeploy in prod.
DurableStash.TestBackend ships with the package: a faithful in-memory
DurableServer.StorageBackend (including etag CAS) for tests and make run
style development without S3 credentials.
Installation
def deps do
[
{:durable_stash, "~> 0.1.0"}
]
end
Credits
DurableStash stands on two lineages:
- the adapter API of LiveStash by Software Mansion
- the durable-process runtime of DurableServer by Chris McCord