slate/bag

Types

An open DETS bag table with typed keys and values.

pub opaque type Bag(k, v)

Values

pub fn close(table: Bag(k, v)) -> Result(Nil, slate.DetsError)

Close the table, flushing all pending writes to disk.

pub fn delete_all(
  from table: Bag(k, v),
) -> Result(Nil, slate.DetsError)

Delete all objects in the table (keeps the table open).

pub fn delete_key(
  from table: Bag(k, v),
  key key: k,
) -> Result(Nil, slate.DetsError)

Delete all values for the given key.

This operation is idempotent — deleting a key that does not exist succeeds with Ok(Nil).

pub fn delete_object(
  from table: Bag(k, v),
  key key: k,
  value value: v,
) -> Result(Nil, slate.DetsError)

Delete a specific key-value pair from the table.

Only the exact matching pair is removed. Other values for the same key are preserved. This is the primary way to remove individual values from a bag without affecting other entries for the same key.

import gleam/dynamic/decode
let assert Ok(table) = bag.open("tags.dets",
  key_decoder: decode.string, value_decoder: decode.string)
let assert Ok(Nil) = bag.insert(table, "color", "red")
let assert Ok(Nil) = bag.insert(table, "color", "blue")
let assert Ok(Nil) = bag.delete_object(table, "color", "red")
let assert Ok(["blue"]) = bag.lookup(table, "color")
pub fn fold(
  over table: Bag(k, v),
  from initial: acc,
  with fun: fn(acc, k, v) -> acc,
) -> Result(acc, slate.DetsError)

Fold over all entries. Order is unspecified.

Returns Error(DecodeErrors(_)) if any entry doesn’t match the expected types. The fold stops at the first decode error. If the callback raises, the exception is re-raised.

pub fn fold_results(
  over table: Bag(k, v),
  from initial: acc,
  with fun: fn(acc, Result(#(k, v), List(decode.DecodeError))) -> acc,
) -> Result(acc, slate.DetsError)

Fold over all entries, passing decode results to the callback.

Unlike fold, decode failures do not abort the traversal. Each entry is presented to the callback as Ok(#(key, value)) on success or Error(decode_errors) on failure, letting the caller decide how to handle bad records.

DETS-level errors (e.g., the table does not exist) still fail the entire operation via the outer Result. If the callback raises, the exception is re-raised.

Examples

Skip entries that fail to decode:

bag.fold_results(table, [], fn(acc, entry) {
  case entry {
    Ok(#(k, v)) -> [#(k, v), ..acc]
    Error(_) -> acc
  }
})

Partition into successes and failures:

bag.fold_results(table, #([], []), fn(acc, entry) {
  case entry {
    Ok(#(k, v)) -> #([#(k, v), ..acc.0], acc.1)
    Error(errs) -> #(acc.0, [errs, ..acc.1])
  }
})
pub fn info(
  table: Bag(k, v),
) -> Result(slate.TableInfo, slate.DetsError)

Get information about an open table.

pub fn insert(
  into table: Bag(k, v),
  key key: k,
  value value: v,
) -> Result(Nil, slate.DetsError)

Insert a key-value pair. If the exact pair already exists, this is a no-op (the duplicate is silently ignored).

Multiple distinct values for the same key are stored separately.

pub fn insert_list(
  into table: Bag(k, v),
  entries entries: List(#(k, v)),
) -> Result(Nil, slate.DetsError)

Insert multiple key-value pairs. Duplicate pairs already in the table are silently ignored.

pub fn insert_new(
  into table: Bag(k, v),
  key key: k,
  value value: v,
) -> Result(Nil, slate.DetsError)

Insert a key-value pair only if the exact pair does not already exist.

Returns Error(KeyAlreadyPresent) if the exact key-value pair is already in the table. Use insert when you don’t need duplicate detection.

Under shared concurrent access this check is best-effort rather than atomic, because DETS does not provide an exact-object insert_new operation for bag tables. If you need strict duplicate exclusion across writers, serialize writes through an owner process.

pub fn lookup(
  from table: Bag(k, v),
  key key: k,
) -> Result(List(v), slate.DetsError)

Look up all values for a key.

Returns Ok([]) if the key does not exist. This differs from set.lookup, which returns Error(NotFound) for missing keys. Returns Error(DecodeErrors(_)) if any stored value doesn’t match the expected type.

pub fn member(
  of table: Bag(k, v),
  key key: k,
) -> Result(Bool, slate.DetsError)

Check if a key exists without returning the values.

pub fn open(
  path: String,
  key_decoder key_decoder: decode.Decoder(k),
  value_decoder value_decoder: decode.Decoder(v),
) -> Result(Bag(k, v), slate.DetsError)

Open or create a DETS bag table at the given file path.

Decoders are used to validate data read from disk, ensuring type safety even when opening files created by other code or previous runs.

import gleam/dynamic/decode
let assert Ok(table) = bag.open("data/tags.dets",
  key_decoder: decode.string, value_decoder: decode.string)
pub fn open_with(
  path path: String,
  repair repair: slate.RepairPolicy,
  key_decoder key_decoder: decode.Decoder(k),
  value_decoder value_decoder: decode.Decoder(v),
) -> Result(Bag(k, v), slate.DetsError)

Open or create a DETS bag table with a specific repair policy.

The repair policy controls what happens when the table file was not closed cleanly (e.g., after a crash):

  • AutoRepair — silently repair the file if needed (default for open)
  • ForceRepair — repair even if the file appears clean
  • NoRepair — return an error instead of repairing
import gleam/dynamic/decode
import slate.{ForceRepair}
let assert Ok(table) = bag.open_with(path: "data/tags.dets",
  repair: ForceRepair,
  key_decoder: decode.string, value_decoder: decode.string)
pub fn open_with_access(
  path path: String,
  repair repair: slate.RepairPolicy,
  access access: slate.AccessMode,
  key_decoder key_decoder: decode.Decoder(k),
  value_decoder value_decoder: decode.Decoder(v),
) -> Result(Bag(k, v), slate.DetsError)

Open a DETS bag table with repair and access mode options.

Use ReadOnly to open a table for reading only. Write operations on a read-only table will return Error(AccessDenied).

import gleam/dynamic/decode
import slate.{AutoRepair, ReadOnly}
let assert Ok(table) = bag.open_with_access(path: "data/tags.dets",
  repair: AutoRepair, access: ReadOnly,
  key_decoder: decode.string, value_decoder: decode.string)
let assert Ok(vals) = bag.lookup(table, key: "key")
// bag.insert(table, "key", "val") would return Error(AccessDenied)
pub fn size(of table: Bag(k, v)) -> Result(Int, slate.DetsError)

Return the number of objects stored.

pub fn sync(table: Bag(k, v)) -> Result(Nil, slate.DetsError)

Flush pending writes to disk without closing the table.

DETS auto-syncs periodically, so this is only needed when you require a durability guarantee at a specific point (e.g., after a critical write).

pub fn to_list(
  from table: Bag(k, v),
) -> Result(List(#(k, v)), slate.DetsError)

Return all key-value pairs as a list.

Warning: loads entire table into memory. Returns Error(DecodeErrors(_)) if any entry doesn’t match the expected types.

pub fn with_table(
  path: String,
  key_decoder key_decoder: decode.Decoder(k),
  value_decoder value_decoder: decode.Decoder(v),
  fun fun: fn(Bag(k, v)) -> Result(a, slate.DetsError),
) -> Result(a, slate.DetsError)

Use a table within a callback, ensuring it is closed afterward.

This is the recommended way to use DETS tables for short-lived operations. Opens with AutoRepair and ReadWrite access; use open_with_access and manual close if you need different settings.

If both the callback and close fail, the callback error is returned. If the callback raises an exception, close is attempted before re-raising.

import gleam/dynamic/decode
use table <- bag.with_table("data/tags.dets",
  key_decoder: decode.string, value_decoder: decode.string)
bag.insert(table, "color", "red")
Search Document