shelf
Fast in-memory access with automatic disk persistence for the BEAM.
The pattern
Section titled “The pattern”Reads come from ETS at memory speed; writes persist to DETS on disk. shelf wraps the classic Erlang persistence pattern in a type-safe Gleam API, with decoders validating every entry loaded from disk before it reaches your code.
import gleam/dynamic/decodeimport shelf/set
// Open a persistent set — existing data loads from disk, through your decoderslet assert Ok(table) = set.open( name: "users", path: "data/users.dets", base_directory: "/app/data", key: decode.string, value: decode.int, )
// Writes and reads hit ETS at memory speedlet assert Ok(Nil) = set.insert(into: table, key: "alice", value: 42)let assert Ok(42) = set.lookup(from: table, key: "alice")
// Persist to disk when ready; close auto-saveslet assert Ok(Nil) = set.save(table)Compared to the alternatives
Section titled “Compared to the alternatives”Gleam's ETS ecosystem already covers the pieces individually — shelf exists because most projects need both at once.
- bravo wraps ETS. Fast, in-memory only; nothing survives a restart.
- slate wraps DETS. Persistent, but every read and write touches disk.
- Mnesia ships with OTP and gives you both, plus distribution, transactions, and a schema — more than most single-node apps need.
- shelf combines ETS-speed reads with DETS-backed persistence, without a distributed database to run.
Built in
Section titled “Built in”- ETS speed, DETS persistence
- Microsecond reads from memory, durable storage on disk — no database process to run.
- Runtime type safety
- Decoder-gated loading catches corrupted or mistyped data at the storage boundary, not in production.
- Two write modes
- WriteBack for high-throughput batching, or WriteThrough for immediate durability on every write.
- Set, bag, and duplicate bag
- All three ETS table types, each with persistent backing — pick the data model your use case needs.
- Safe resource management
- The
with_tablecallback closes tables even when the body panics or returns an error. - Atomic counters
- Lock-free increments via
update_counter— no serialising through an actor. - Cross-process reads
- Tables are
protected: one owner process writes while any process reads concurrently.