ream88

ream88

Durable_stash - LiveView state that survives navigation, crashes, and redeploys

What happens when two of the most interesting recent Elixir packages join together? Introducing durable_stash, our vibecoded approach to fixing one of the most annoying Phoenix LiveView problems: your assigns keep dying. Navigate away and they’re gone. And after every deploy, that setting your user carefully configured five minutes ago is back to its default.

The two packages in question:

  • LiveStash (Software Mansion) has a lovely API for stashing and recovering assigns. But the stock ETS adapter only recovers on reconnects, deletes the stash on every fresh mount (by design, their README says to use URL params for navigation), and like everything in BEAM memory it dies on deploy.
  • DurableServer (Chris McCord) gives you durable processes persisted to S3-compatible object storage. Durable Objects, basically, but on the BEAM.

durable_stash is a LiveStash adapter backed by DurableServer. One durable process per browser session, shared by every LiveView of that session:

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
end

A plug drops a random sid into the cookie session, the adapter hashes it into a storage key, and from then on your stored assigns survive live navigation, Wi-Fi hiccups, LiveView crashes, and full redeploys. Cleared cookies or another browser means defaults again. Which is the point. It’s session state, not a database.

Some details I’m happy with:

  • Each stored key declares a recovery scope, because settings and form drafts want different policies:
stored_keys: [
  theme: :session,    # recovers on every mount: navigation, crashes, deploys
  draft: :reconnect   # recovers only on rejoins; clears on fresh navigation
]

A :reconnect draft survives a deploy mid-edit (the browser stays on the page and rejoins), but navigating to the form fresh starts blank. That’s stock LiveStash semantics, per key, with deploy survival on top.

  • Writes are per-key diffs merged by a single actor, so two tabs writing different keys can’t clobber each other. We looked at CRDTs for this and rejected them: one process per session means there’s nothing to merge.
  • Values are JSON-normalized at stash time. What you recover in dev is byte-for-byte what you’d recover after a redeploy in prod, so you don’t get atom keys in dev and string keys in prod.
  • There’s a vsn/migrate option for when your stored shape changes.
  • It ships an in-memory DurableServer.StorageBackend with proper etag CAS, so your tests need neither S3 nor LocalStack.

About “vibecoded”: yes, Claude wrote most of it. The test suite is real though: 40 tests including a deploy simulation (two DurableServer supervisors sharing one backend, stop the first, recover on the second) and actual Phoenix.LiveViewTest lifecycle coverage. We also ran it against a local S3 container and killed the VM between write and read.

It’s a 0.1 and young. On the roadmap: atomic update/3 inside the session actor, PubSub broadcasts for live multi-tab convergence, TTL/idle-stop, and a :permanent scope for user-scoped (not session-scoped) keys.

Hex: durable_stash | Hex
Docs: durable_stash v0.1.1 — Documentation
GitHub:

Feedback very welcome, especially from anyone using LiveStash or DurableServer in anger.

Where Next?

Popular in Announcing Top

OvermindDL1
I created a new library (rather I pulled out a couple files from my big project), it manages an operating system PID file for the BEAM. ...
New
asiniy
Hey there! I wrote a download elixir package which does exactly what its name about - an easy way to download files. I saw solutions ab...
New
mischov
import Meeseeks.CSS html = HTTPoison.get!("https://news.ycombinator.com/").body for story <- Meeseeks.all(html, css("tr.athing")) do...
New
josevalim
Yes, yet another parser combinator library! Most of the parser combinators in the ecosystem are either compile-time, often using AST tra...
159 19417 141
New
nikokozak
Hello all, I’ve been working on Svonix - a library for quickly integrating Svelte components into Phoenix views. It’s a much-needed succ...
New
Crowdhailer
Raxx is an alternative to Plug and is inspired by projects such as Rack(Ruby) and Ring(Clojure). 1.0-rc.1 is now available. To use it re...
New
cjen07
parameterized pipe in elixir: |n> edit: negative index in |n> and mixed usage with |> are supported example: use ParamPipe ...
New
KronicDeth
Elixir plugin for JetBrain’s IntelliJ Platform (including Rubymine) This is a plugin that adds support for Elixir to JetBrains IntelliJ...
289 36352 110
New
Flo0807
Hello everyone! I am excited to share our heart project Backpex with you. After building several Phoenix applications, we realized that...
New
josevalim
Hello everyone, We have just released NimbleCSV which is a small and fast CSV parsing library for Elixir. It allows developers to define...
New

Other popular topics Top

aadeshere1
I have a another noob question about loop. Since elixir is immutable, while loop is not directly possible. total = 10 while total != 0 ...
New
Harrisonl
We have an ECS cluster with 4 services, where each task joins a single cluster, via discovery ECS discovery service. Currently when I de...
New
AstonJ
Posting this to see if we can make things easier for people to get into Neovim. If you use Neovim and have a favourite distro please let ...
New
lastday4you
I wanted to check elixir version in phoenix because i found that my elixir is 1.5 but when i use Enum.chunk_by it said the function is un...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
stefanchrobot
What’s the safe way to decode a JSON string into a struct? I want to avoid calling String.to_atom. Jason.decode can give me a map with st...
New
aesmail
Hello guys, I have finally made it. I created an admin interface for a framework. It’s been on my todo list for years and with the curre...
New
ashish173
I am using Ecto timestamps with postgres, I can see the timestamps() use the :naive_dateime but for my use case I wanted to store the ti...
New
openscript
Hello! Sorry for this astonishing simple question, but I’m really stuck. I try to set up the intellij-elixir plugin, but I don’t know ho...
New

Latest on Elixir Forum

We're in Beta

About us Mission Statement