Persistent ETS tables backed by DETS — fast in-memory access with automatic disk persistence for the BEAM. Implements the classic Erlang ETS/DETS persistence pattern with a type-safe Gleam API.
gleam build # Compile project
gleam test # Run tests
gleam check # Type check without building
gleam format src test examples/src # Format code
gleam docs build # Generate documentationsrc/
├── shelf.gleam # Shared types (ShelfError, WriteMode, Config)
├── shelf_ffi.erl # Erlang FFI for ETS + DETS + transfers
├── shelf_rescue_ffi.erl # Erlang FFI for panic rescue in with_table
└── shelf/
├── internal.gleam # Internal types (EtsRef, DetsRef) + shared operations
├── set.gleam # Persistent set tables (unique keys)
├── bag.gleam # Persistent bag tables (multiple values per key)
└── duplicate_bag.gleam # Persistent duplicate bag tables
test/
├── shelf_test.gleam # Config builder tests + test entry point
├── set_test.gleam # Set table tests
├── bag_test.gleam # Bag table tests
├── duplicate_bag_test.gleam # Duplicate bag table tests
├── persistence_test.gleam # Save/reload/survive-restart tests
├── write_through_test.gleam # WriteThrough mode tests
├── writethrough_consistency_test.gleam # WriteThrough DETS-first ordering tests
├── ownership_test.gleam # Process ownership + NotOwner tests
├── concurrency_test.gleam # Cross-process read tests
├── guardian_test.gleam # Guardian process cleanup tests
├── atomic_save_test.gleam # Atomic save (temp file + rename) tests
├── reload_atomicity_test.gleam # Atomic reload (scratch table swap) tests
├── close_test.gleam # Close lifecycle + error recovery tests
├── update_counter_test.gleam # Atomic counter tests
├── path_validation_test.gleam # Path traversal / security tests
├── type_safety_test.gleam # Decoder validation tests
├── error_handling_test.gleam # ShelfError translation tests
├── error_path_test.gleam # Error path coverage tests
└── ffi_fixes_test.gleam # FFI edge case regression tests
- Open: Creates an ETS table + opens a DETS file, then validates all DETS entries through user-supplied decoders before inserting into ETS
- Reads: Always from ETS (microsecond latency)
- Writes: Always to ETS; DETS depends on WriteMode
- Save:
ets:to_dets/2atomically snapshots ETS contents to DETS - Close: Save + close DETS + delete ETS
- WriteBack (default): Writes go to ETS only. Call
save()to persist. - WriteThrough: Every write goes to DETS first (via
dets:insert), then ETS. Ensures DETS failure never leaves ETS in a divergent state.
The shelf_ffi.erl module wraps raw ets:* and dets:* calls with error translation. Key native functions used:
ets:to_dets(EtsTab, DetsTab)— replaces all DETS contents with ETS (atomic snapshot)dets:foldl/3viadets_fold_into_ets_strict/3— streams DETS entries through decoders into ETS with batched inserts
- Direct Erlang wrapping (not built on bravo/slate) to use efficient
ets:to_detsbulk transfers and decoder-validated loading - Opaque table handles:
PSet(k, v),PBag(k, v),PDuplicateBag(k, v)enforce type safety - No ordered set: DETS doesn't support
ordered_set - ETS table name: Always
shelf_ets(unnamed tables viaets:new/2); user-provided name is a diagnostic label only - DETS table name: Mapped through a bounded atom registry (
path_to_dets_name) to avoid unbounded atom creation
gleam_stdlib- Standard librarygleam_erlang- Erlang interop
startest- Testing framework
- Use Result types over exceptions
- Exhaustive pattern matching
- Follow
gleam formatoutput - Document public functions with
///comments - Test files create temporary
.detsfiles in/tmp/and clean up after each test