Keep Phoenix LiveView state alive across reconnects, crashes, and redeploys.
A LiveView's assigns live in memory and vanish when the socket drops, the
process crashes, or you deploy. DurableStash saves the assigns you pick to
object storage and restores them the next time the LiveView mounts. The saved
copy belongs to the browser session, so every LiveView the user opens shares
it. Under the hood it plugs into
LiveStash and stores state through
DurableServer. State dies with the
browser session.
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
endCall LiveStash.recover_state/1 in mount/3 after assigning defaults
(recovered values overwrite them), and LiveStash.stash/1 whenever a
stored assign changes — or pass auto_stash: true.
Unlike the stock ETS adapter, DurableStash recovers on every mount —
fresh navigations included — and never deletes the stash on mount. State is
keyed by the browser session, not by the socket.
Setup
Register the adapter and configure the backend:
config :live_stash, adapters: [DurableStash] config :durable_stash, backend: {DurableServer.Backends.ObjectStore, bucket: "...", ...}, prefix: "durable_stash/", secret: "some-stable-secret"With that config,
LiveStash.Applicationstarts the DurableServer supervisor automatically throughchild_spec/1. Alternatively, run your ownDurableServer.Supervisorand point the adapter at it with the:supervisoroption.Put a session id into the cookie session, in the
:browserpipeline afterplug :fetch_session:plug :ensure_session_id defp ensure_session_id(conn, _opts) do if get_session(conn, "sid") do conn else sid = 16 |> :crypto.strong_rand_bytes() |> Base.url_encode64(padding: false) put_session(conn, "sid", sid) end end
Scopes
Not all state wants the same recovery policy. Each stored key declares a scope:
use LiveStash, adapter: DurableStash,
stored_keys: [
theme: :session, # recover on every mount (the default)
draft: :reconnect # recover only on reconnects; cleared on fresh mounts
]: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.
Options (via use LiveStash, adapter: DurableStash, ...)
:stored_keys(required) — assigns to persist. Bare atoms mean:sessionscope; see Scopes above for:reconnect.:permanentis reserved and raises for now.:vsn(default1) — version of this view's stored shape. On recovery, a stored slice with a different vsn is discarded to defaults unless:migrateis given.:migrate— 2-arity function(old_vsn, data) :: datareceiving the stored string-keyed data map and returning the migrated one. The migrated set is written back under the new vsn.:supervisor— DurableServer supervisor name (defaultconfig :durable_stash, :supervisor_name, falling back toDurableStash.Supervisor).:secret— mixed into the storage-key hash (defaultconfig :durable_stash, :secret).:session_id_key— cookie-session key holding the session id (default"sid").
What's storable
JSON-safe values only: no structs, tuples, pids, or functions; maps come
back with string keys. Offending values are skipped with a logged error, or
raise when config :durable_stash, on_invalid_value: :raise is set
(recommended for dev and test). Values are normalized through a JSON
round-trip at stash time, so what you recover in dev is byte-for-byte what
you'd recover after a redeploy in prod.
Summary
Functions
Starts the DurableServer supervisor from :durable_stash config when a
:backend is configured; otherwise starts an empty, harmless supervisor.
Invoked automatically by LiveStash.Application for registered adapters.