Set Tables
Set tables store one value per key — inserting with an existing key overwrites the previous value. This is the most common table type, equivalent to a persistent key-value store.
Create /app/data before opening the table. The examples below assume a new
users.dets file and follow one table from insertion through close.
Opening a Set Table
Section titled “Opening a Set Table”import gleam/dynamic/decodeimport shelf/set
let assert Ok(table) = set.open(name: "users", path: "users.dets", base_directory: "/app/data", key: decode.string, value: decode.int)If the DETS file exists, its contents are loaded into ETS automatically. If the file doesn't exist, both tables start empty.
For WriteThrough, open the table with open_config instead:
import gleam/dynamic/decodeimport shelfimport shelf/set
let config = shelf.config(name: "users", path: "users.dets", base_directory: "/app/data") |> shelf.write_mode(shelf.WriteThrough)
let assert Ok(table) = set.open_config(config: config, key: decode.string, value: decode.int)Writing Data
Section titled “Writing Data”// Insert or overwritelet assert Ok(Nil) = set.insert(into: table, key: "alice", value: 42)
// Insert only if key doesn't exist — returns Error(KeyAlreadyPresent) otherwiselet assert Ok(Nil) = set.insert_new(into: table, key: "bob", value: 99)
// Insert multiple entries at oncelet assert Ok(Nil) = set.insert_list(into: table, entries: [ #("charlie", 10), #("diana", 20),])Reading Data
Section titled “Reading Data”With those entries inserted, you can read them from ETS:
let assert Ok(42) = set.lookup(from: table, key: "alice")let assert Ok(True) = set.member(of: table, key: "alice")let assert Ok(count) = set.size(of: table)let assert Ok(entries) = set.to_list(from: table)to_list allocates a list of all entries. Use fold when you need an
aggregate instead. Iteration order is unspecified.
let assert Ok(total) = set.fold(over: table, from: 0, with: fn(acc, _key, value) { acc + value })Deleting Data
Section titled “Deleting Data”// Delete by keylet assert Ok(Nil) = set.delete_key(from: table, key: "alice")
// Compare-and-delete: only deletes if the stored value matches.// If "bob" is not currently 99, nothing is removed — useful for// optimistic concurrency where you want to avoid clobbering a// concurrent update.let assert Ok(Nil) = set.delete_object(from: table, key: "bob", value: 99)
// Delete all entries (keeps the table open)let assert Ok(Nil) = set.delete_all(from: table)Atomic Counters
Section titled “Atomic Counters”The owner can increment integer values with update_counter:
let assert Ok(Nil) = set.insert(into: table, key: "page_views", value: 0)let assert Ok(1) = set.update_counter(in: table, key: "page_views", increment: 1)let assert Ok(11) = set.update_counter(in: table, key: "page_views", increment: 10)let assert Ok(9) = set.update_counter(in: table, key: "page_views", increment: -2)Resource Management
Section titled “Resource Management”Close the table to save the changes and release its resources:
let assert Ok(Nil) = set.close(table)Use with_table to ensure a table is always closed, even if an error occurs:
let assert Ok(Nil) = { use table <- set.with_table("cache", "cache.dets", base_directory: "/app/data", key: decode.string, value: decode.string) set.insert(into: table, key: "key", value: "value")}The callback must return Result(a, ShelfError). The table is closed after the callback returns.