slate
Type-safe Gleam wrapper for Erlang DETS (Disk Erlang Term Storage).
DETS provides persistent key-value storage backed by files on disk. Tables survive process crashes and node restarts. DETS is built into OTP — no external database or dependency is needed.
Table Types
slate/set— Unique keys, one value per keyslate/bag— Multiple distinct values per keyslate/duplicate_bag— Multiple values per key (duplicates allowed)
Quick Start
import gleam/dynamic/decode
import slate/set
let assert Ok(table) = set.open("data/cache.dets",
key_decoder: decode.string, value_decoder: decode.string)
let assert Ok(Nil) = set.insert(table, "key", "value")
let assert Ok(value) = set.lookup(table, key: "key")
let assert Ok(Nil) = set.close(table)
Limitations
- 2 GB maximum file size
- No
ordered_settable type (unlike ETS) - Disk I/O on every operation (use ETS for high-frequency reads)
- Tables must be closed properly or data may be lost
- Bounded table name pool: slate uses a bounded set of internal DETS table names to avoid unbounded atom growth. Opening too many distinct tables at once may fail; close unused tables promptly
Types
Access mode for opening tables.
pub type AccessMode {
ReadWrite
ReadOnly
}
Constructors
-
ReadWriteRead and write access (default)
-
ReadOnlyRead-only access — writes will return
AccessDenied
Errors that can occur during DETS operations.
Match on the explicit variants for expected cases such as NotFound,
AccessDenied, or TypeMismatch.
Treat UnexpectedError(detail) as diagnostic output for logs and debugging
only. Its string detail is not part of slate’s stable API contract. Use
error_code or error_message when you want a stable classifier or a
user-facing message.
pub type DetsError {
NotFound
FileNotFound
AlreadyOpen
TableDoesNotExist
FileSizeLimitExceeded
KeyAlreadyPresent
AccessDenied
TypeMismatch
TableNamePoolExhausted
NotADetsFile
NeedsRepair
DecodeErrors(List(decode.DecodeError))
UnexpectedError(String)
}
Constructors
-
NotFoundNo value found for the given key
-
FileNotFoundTable file does not exist (when opening without create)
-
AlreadyOpenTable is already open with a different configuration
-
TableDoesNotExistThe table does not exist (not open)
-
FileSizeLimitExceededFile exceeds the 2 GB DETS limit
-
KeyAlreadyPresentKey already exists (for insert_new)
-
AccessDeniedWrite operation attempted on a read-only table
-
TypeMismatchTable type mismatch (e.g., opening a set file as a bag)
-
TableNamePoolExhaustedAll internal table name slots are in use; close unused tables to free slots
-
NotADetsFileFile exists but is not a valid DETS file
-
NeedsRepairFile was not closed cleanly and
NoRepairwas requested -
DecodeErrors(List(decode.DecodeError))Data read from disk did not match the expected Gleam types
-
UnexpectedError(String)Unexpected OTP or Erlang-level error for logging and diagnostics only.
Auto-repair policy for improperly closed tables.
pub type RepairPolicy {
AutoRepair
ForceRepair
NoRepair
}
Constructors
-
AutoRepairRepair automatically if needed (default)
-
ForceRepairForce repair even if file appears clean
-
NoRepairDon’t repair, return error instead
Values
pub fn error_code(of error: DetsError) -> String
Return a stable machine-readable code for a DetsError.
This is useful when you want to log, branch on, or serialize error
categories without relying on the detail string in UnexpectedError(_).
pub fn error_message(of error: DetsError) -> String
Return a concise user-facing description for a DetsError.
UnexpectedError(_) intentionally maps to a generic message so callers can
safely surface it without leaking raw Erlang/OTP diagnostic details.
pub fn is_dets_file(path: String) -> Result(Bool, DetsError)
Check whether the given file is a valid DETS file.
Returns Ok(True) if the file is a valid DETS file, Ok(False) if
it exists but is not a DETS file, or an error if the file cannot be read.
let assert Ok(True) = slate.is_dets_file("data/cache.dets")
let assert Ok(False) = slate.is_dets_file("README.md")