Current section
Files
Jump to
Current section
Files
src/trove.erl
-module(trove).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/trove.gleam").
-export([close/1, compact/2, size/1, is_empty/1, dirt_factor/1, file_sync/1, get/2, put/3, delete/2, has_key/2, put_multi/2, delete_multi/2, put_and_delete_multi/3, transaction/3, tx_get/2, tx_put/3, tx_delete/2, tx_has_key/2, with_snapshot/2, snapshot_get/2, snapshot_range/4, range/4, set_auto_compact/2, open/1]).
-export_type([open_error/0, auto_compact/0, file_sync/0, config/2, db/2, transaction_result/3]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" An embedded, crash-safe key-value store for Gleam.\n"
"\n"
" trove stores data in an append-only B+ tree on disk. Every write appends\n"
" new nodes and creates a new root — old data is never overwritten. This\n"
" gives you crash safety, zero-cost MVCC snapshots, and single-writer /\n"
" multiple-reader concurrency backed by an OTP actor.\n"
"\n"
" ## Quick Start\n"
"\n"
" ```gleam\n"
" import gleam/string\n"
" import trove\n"
" import trove/codec\n"
"\n"
" let config = trove.Config(\n"
" path: \"./my_db\",\n"
" key_codec: codec.string(),\n"
" value_codec: codec.string(),\n"
" key_compare: string.compare,\n"
" auto_compact: trove.NoAutoCompact,\n"
" auto_file_sync: trove.AutoSync,\n"
" call_timeout: 5000,\n"
" )\n"
"\n"
" let assert Ok(db) = trove.open(config)\n"
" trove.put(db, key: \"language\", value: \"gleam\")\n"
" let assert Ok(\"gleam\") = trove.get(db, key: \"language\")\n"
" trove.close(db)\n"
" ```\n"
).
-type open_error() :: {directory_error, binary()} |
{store_error, binary()} |
{lock_error, binary()} |
actor_start_error.
-type auto_compact() :: {auto_compact, integer(), float()} | no_auto_compact.
-type file_sync() :: auto_sync | manual_sync.
-type config(LNM, LNN) :: {config,
binary(),
trove@codec:codec(LNM),
trove@codec:codec(LNN),
fun((LNM, LNM) -> gleam@order:order()),
auto_compact(),
file_sync(),
integer()}.
-opaque db(LNO, LNP) :: {db,
gleam@erlang@process:subject(trove@internal@db:message(LNO, LNP)),
integer()}.
-type transaction_result(LNQ, LNR, LNS) :: {commit,
trove@internal@tx:tx(LNQ, LNR),
LNS} |
{cancel, LNS}.
-file("src/trove.gleam", 53).
-spec reraise(exception:exception()) -> any().
reraise(Ex) ->
{Class, Reason@3} = case Ex of
{errored, Reason} ->
{<<"error"/utf8>>, Reason};
{thrown, Reason@1} ->
{<<"throw"/utf8>>, Reason@1};
{exited, Reason@2} ->
{<<"exit"/utf8>>, Reason@2}
end,
erlang:raise(erlang:binary_to_atom(Class), Reason@3, []).
-file("src/trove.gleam", 170).
?DOC(
" Close the database and release the file handle and path lock. Does not\n"
" fsync — if using `ManualSync`, call `file_sync` before closing to ensure\n"
" durability. The `Db` handle must not be used after calling this.\n"
"\n"
" **Panics** if the store file handle cannot be closed.\n"
"\n"
" ```gleam\n"
" trove.close(db)\n"
" ```\n"
).
-spec close(db(any(), any())) -> nil.
close(Db) ->
trove@internal@db:close(erlang:element(2, Db), erlang:element(3, Db)).
-file("src/trove.gleam", 185).
?DOC(
" Trigger a manual compaction. Rebuilds the store file keeping only live\n"
" entries, resetting the dirt factor to zero. Returns `Ok(Nil)` on success\n"
" or `Error(reason)` if compaction failed. On failure the database remains\n"
" functional with the original store file.\n"
"\n"
" The timeout is separate from `call_timeout` because compaction can take\n"
" much longer than normal operations.\n"
"\n"
" ```gleam\n"
" let assert Ok(Nil) = trove.compact(db, timeout: 60_000)\n"
" ```\n"
).
-spec compact(db(any(), any()), integer()) -> {ok, nil} | {error, binary()}.
compact(Db, Timeout) ->
trove@internal@db:compact(erlang:element(2, Db), Timeout).
-file("src/trove.gleam", 194).
?DOC(
" Returns the number of live entries in the database.\n"
"\n"
" ```gleam\n"
" let count = trove.size(db)\n"
" ```\n"
).
-spec size(db(any(), any())) -> integer().
size(Db) ->
trove@internal@db:size(erlang:element(2, Db), erlang:element(3, Db)).
-file("src/trove.gleam", 203).
?DOC(
" Returns `True` if the database contains no entries.\n"
"\n"
" ```gleam\n"
" let empty = trove.is_empty(db)\n"
" ```\n"
).
-spec is_empty(db(any(), any())) -> boolean().
is_empty(Db) ->
size(Db) =:= 0.
-file("src/trove.gleam", 220).
?DOC(
" Returns the current dirt factor: a float between 0.0 and 1.0 that\n"
" approximates how much of the store file is occupied by superseded data.\n"
" Overwrites and deletes increment the dirt counter because they write new\n"
" nodes that make old ones unreachable. New inserts do not increment dirt\n"
" since they don't supersede existing data. The formula is\n"
" `dirt / (1 + size + dirt)` — the `+1` ensures the result is always\n"
" well-defined, even for an empty tree. The value approaches but never\n"
" reaches 1.0. Higher values mean more wasted space that compaction would\n"
" reclaim.\n"
"\n"
" ```gleam\n"
" let df = trove.dirt_factor(db)\n"
" ```\n"
).
-spec dirt_factor(db(any(), any())) -> float().
dirt_factor(Db) ->
trove@internal@db:dirt_factor(erlang:element(2, Db), erlang:element(3, Db)).
-file("src/trove.gleam", 235).
?DOC(
" Force an fsync of the store file to disk. Useful when `auto_file_sync`\n"
" is set to `ManualSync` and you want to control when data is flushed.\n"
"\n"
" **Panics** if the fsync system call fails.\n"
"\n"
" ```gleam\n"
" let config = trove.Config(..config, auto_file_sync: trove.ManualSync)\n"
" let assert Ok(db) = trove.open(config)\n"
" trove.put(db, key: \"hello\", value: \"world\")\n"
" trove.file_sync(db)\n"
" ```\n"
).
-spec file_sync(db(any(), any())) -> nil.
file_sync(Db) ->
trove@internal@db:file_sync(erlang:element(2, Db), erlang:element(3, Db)).
-file("src/trove.gleam", 260).
?DOC(
" Look up a key. Returns `Ok(value)` if found, `Error(Nil)` if the\n"
" key does not exist.\n"
"\n"
" **Panics** on store I/O or decode errors (e.g. file corruption).\n"
"\n"
" ```gleam\n"
" let assert Ok(\"world\") = trove.get(db, key: \"hello\")\n"
" ```\n"
).
-spec get(db(LPQ, LPR), LPQ) -> {ok, LPR} | {error, nil}.
get(Db, Key) ->
_pipe = trove@internal@db:get(
erlang:element(2, Db),
Key,
erlang:element(3, Db)
),
gleam@option:to_result(_pipe, nil).
-file("src/trove.gleam", 272).
?DOC(
" Insert or update a key-value pair.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.put(db, key: \"hello\", value: \"world\")\n"
" ```\n"
).
-spec put(db(LPW, LPX), LPW, LPX) -> nil.
put(Db, Key, Value) ->
trove@internal@db:put(
erlang:element(2, Db),
Key,
Value,
erlang:element(3, Db)
).
-file("src/trove.gleam", 283).
?DOC(
" Remove a key. No error if the key does not exist.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.delete(db, key: \"hello\")\n"
" ```\n"
).
-spec delete(db(LQA, any()), LQA) -> nil.
delete(Db, Key) ->
trove@internal@db:delete(erlang:element(2, Db), Key, erlang:element(3, Db)).
-file("src/trove.gleam", 294).
?DOC(
" Check whether a key exists in the database.\n"
"\n"
" **Panics** on store I/O or decode errors (e.g. file corruption).\n"
"\n"
" ```gleam\n"
" let assert True = trove.has_key(db, key: \"hello\")\n"
" ```\n"
).
-spec has_key(db(LQE, any()), LQE) -> boolean().
has_key(Db, Key) ->
trove@internal@db:has_key(erlang:element(2, Db), Key, erlang:element(3, Db)).
-file("src/trove.gleam", 306).
?DOC(
" Atomically insert multiple key-value pairs. A single header write covers\n"
" the entire batch.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.put_multi(db, entries: [#(\"a\", \"1\"), #(\"b\", \"2\")])\n"
" ```\n"
).
-spec put_multi(db(LQI, LQJ), list({LQI, LQJ})) -> nil.
put_multi(Db, Entries) ->
trove@internal@db:put_multi(
erlang:element(2, Db),
Entries,
erlang:element(3, Db)
).
-file("src/trove.gleam", 317).
?DOC(
" Atomically delete multiple keys.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.delete_multi(db, keys: [\"a\", \"b\"])\n"
" ```\n"
).
-spec delete_multi(db(LQN, any()), list(LQN)) -> nil.
delete_multi(Db, Keys) ->
trove@internal@db:delete_multi(
erlang:element(2, Db),
Keys,
erlang:element(3, Db)
).
-file("src/trove.gleam", 333).
?DOC(
" Atomically insert and delete entries in a single operation. Puts are\n"
" applied first, then deletes, all under a single header write.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.put_and_delete_multi(\n"
" db,\n"
" puts: [#(\"new_key\", \"value\")],\n"
" deletes: [\"old_key\"],\n"
" )\n"
" ```\n"
).
-spec put_and_delete_multi(db(LQS, LQT), list({LQS, LQT}), list(LQS)) -> nil.
put_and_delete_multi(Db, Puts, Deletes) ->
trove@internal@db:put_and_delete_multi(
erlang:element(2, Db),
Puts,
Deletes,
erlang:element(3, Db)
).
-file("src/trove.gleam", 465).
-spec drain_latest_loop(
gleam@erlang@process:subject(LRL),
gleam@option:option(LRL)
) -> gleam@option:option(LRL).
drain_latest_loop(Subject, Acc) ->
case gleam@erlang@process:'receive'(Subject, 0) of
{ok, Value} ->
drain_latest_loop(Subject, {some, Value});
{error, nil} ->
Acc
end.
-file("src/trove.gleam", 461).
-spec drain_latest(gleam@erlang@process:subject(LRI)) -> gleam@option:option(LRI).
drain_latest(Subject) ->
drain_latest_loop(Subject, none).
-file("src/trove.gleam", 401).
?DOC(
" Run an atomic transaction. The callback receives a `Tx` handle and must\n"
" return `Commit(tx:, result: value)` to apply writes or\n"
" `Cancel(result: value)` to discard. The transaction holds exclusive\n"
" write access for its duration.\n"
"\n"
" The `timeout` parameter (in milliseconds) controls how long the caller\n"
" waits for the transaction to complete, including queue wait time and\n"
" callback execution. Choose a value appropriate for your workload —\n"
" queued operations or auto-compaction may delay the start, and a\n"
" long-running callback consumes the remaining budget.\n"
"\n"
" **Important:** The callback runs inside the database actor. Do not call\n"
" any `trove` functions (such as `get`, `put`, `compact`, etc.) on the\n"
" same `Db` handle from within the callback — this will deadlock the actor\n"
" until the call timeout fires. Use the `Tx` handle (`tx_get`, `tx_put`,\n"
" `tx_delete`) for all reads and writes inside the transaction.\n"
"\n"
" **Panics** if the `Commit` variant contains a stale or replaced `Tx`\n"
" handle (e.g. the original handle instead of the latest one returned by\n"
" `tx_put`/`tx_delete`).\n"
"\n"
" **Non-escaping:** The `Tx` handle is only valid inside the callback.\n"
" Do not store it in a variable, send it to another process, or return it —\n"
" using a `Tx` after the callback returns will panic or produce undefined\n"
" behavior.\n"
"\n"
" **Timeout semantics:** If the timeout fires while the callback is still\n"
" executing, the caller panics but the actor continues running the callback\n"
" to completion. This means writes may be durably committed even though the\n"
" caller observes a timeout failure. Choose a timeout that accommodates your\n"
" expected callback duration and any queued operations ahead of it.\n"
"\n"
" ```gleam\n"
" let result = trove.transaction(db, timeout: 5000, callback: fn(tx) {\n"
" let tx = trove.tx_put(tx, key: \"key\", value: \"value\")\n"
" trove.Commit(tx:, result: \"done\")\n"
" })\n"
" ```\n"
).
-spec transaction(
db(LQY, LQZ),
integer(),
fun((trove@internal@tx:tx(LQY, LQZ)) -> transaction_result(LQY, LQZ, LRE))
) -> LRE.
transaction(Db, Timeout, Callback) ->
Result_subject = gleam@erlang@process:new_subject(),
Token = erlang:make_ref(),
Run = fun(Transaction) ->
Nonce_subject = gleam@erlang@process:new_subject(),
Transaction@1 = trove@internal@tx:set_token(Transaction, Token),
Transaction@2 = trove@internal@tx:set_nonce_tracker(
Transaction@1,
{some, Nonce_subject}
),
case exception_ffi:rescue(fun() -> Callback(Transaction@2) end) of
{ok, {commit, Tx_inner, Value}} ->
case exception_ffi:rescue(
fun() ->
case trove@internal@tx:token(Tx_inner) =:= Token of
true -> nil;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove"/utf8>>,
function => <<"transaction"/utf8>>,
line => 418,
value => _assert_fail,
start => 14517,
'end' => 14566,
pattern_start => 14528,
pattern_end => 14532})
end,
Latest_nonce = drain_latest(Nonce_subject),
case Latest_nonce of
none ->
nil;
{some, Expected} ->
case trove@internal@tx:nonce(Tx_inner) =:= Expected of
true -> nil;
_assert_fail@1 ->
erlang:error(
#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove"/utf8>>,
function => <<"transaction"/utf8>>,
line => 423,
value => _assert_fail@1,
start => 14748,
'end' => 14800,
pattern_start => 14759,
pattern_end => 14763}
)
end,
nil
end
end
) of
{ok, nil} ->
gleam@erlang@process:send(Result_subject, {ok, Value}),
{commit_outcome, trove@internal@tx:get_tree(Tx_inner)};
{error, Ex} ->
gleam@erlang@process:send(Result_subject, {error, Ex}),
cancel_outcome
end;
{ok, {cancel, Value@1}} ->
gleam@erlang@process:send(Result_subject, {ok, Value@1}),
cancel_outcome;
{error, Ex@1} ->
gleam@erlang@process:send(Result_subject, {error, Ex@1}),
cancel_outcome
end
end,
trove@internal@db:transaction(erlang:element(2, Db), Timeout, Run),
Result@1 = case gleam@erlang@process:'receive'(Result_subject, 0) of
{ok, Result} -> Result;
_assert_fail@2 ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove"/utf8>>,
function => <<"transaction"/utf8>>,
line => 454,
value => _assert_fail@2,
start => 15677,
'end' => 15735,
pattern_start => 15688,
pattern_end => 15698})
end,
case Result@1 of
{ok, Value@2} ->
Value@2;
{error, Ex@2} ->
reraise(Ex@2)
end.
-file("src/trove.gleam", 487).
?DOC(
" Read a key within a transaction. Sees writes made earlier in the same\n"
" transaction. Returns `Error(Nil)` if the key does not exist.\n"
"\n"
" **Panics** on store I/O or decode errors (e.g. file corruption).\n"
"\n"
" ```gleam\n"
" trove.transaction(db, timeout: 5000, callback: fn(tx) {\n"
" let assert Ok(current) = trove.tx_get(tx, key: \"counter\")\n"
" let tx = trove.tx_put(tx, key: \"counter\", value: current <> \"!\")\n"
" trove.Commit(tx:, result: Nil)\n"
" })\n"
" ```\n"
).
-spec tx_get(trove@internal@tx:tx(LRP, LRQ), LRP) -> {ok, LRQ} | {error, nil}.
tx_get(Tx, Key) ->
_pipe = trove@internal@tx:get(Tx, Key),
gleam@option:to_result(_pipe, nil).
-file("src/trove.gleam", 502).
?DOC(
" Write a key-value pair within a transaction. Returns the updated `Tx`.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.transaction(db, timeout: 5000, callback: fn(tx) {\n"
" let tx = trove.tx_put(tx, key: \"greeting\", value: \"hello\")\n"
" trove.Commit(tx:, result: Nil)\n"
" })\n"
" ```\n"
).
-spec tx_put(trove@internal@tx:tx(LRV, LRW), LRV, LRW) -> trove@internal@tx:tx(LRV, LRW).
tx_put(Tx, Key, Value) ->
trove@internal@tx:put(Tx, Key, Value).
-file("src/trove.gleam", 516).
?DOC(
" Delete a key within a transaction. Returns the updated `Tx`.\n"
"\n"
" **Panics** on store I/O errors (e.g. disk full, file corruption).\n"
"\n"
" ```gleam\n"
" trove.transaction(db, timeout: 5000, callback: fn(tx) {\n"
" let tx = trove.tx_delete(tx, key: \"old_key\")\n"
" trove.Commit(tx:, result: Nil)\n"
" })\n"
" ```\n"
).
-spec tx_delete(trove@internal@tx:tx(LSB, LSC), LSB) -> trove@internal@tx:tx(LSB, LSC).
tx_delete(Tx, Key) ->
trove@internal@tx:delete(Tx, Key).
-file("src/trove.gleam", 531).
?DOC(
" Check whether a key exists within a transaction. Sees writes made\n"
" earlier in the same transaction.\n"
"\n"
" **Panics** on store I/O or decode errors (e.g. file corruption).\n"
"\n"
" ```gleam\n"
" trove.transaction(db, timeout: 5000, callback: fn(tx) {\n"
" let exists = trove.tx_has_key(tx, key: \"counter\")\n"
" trove.Commit(tx:, result: exists)\n"
" })\n"
" ```\n"
).
-spec tx_has_key(trove@internal@tx:tx(LSH, any()), LSH) -> boolean().
tx_has_key(Tx, Key) ->
_pipe = tx_get(Tx, Key),
gleam@result:is_ok(_pipe).
-file("src/trove.gleam", 552).
?DOC(
" Run a callback with a point-in-time snapshot. The snapshot sees the state\n"
" of the database at the moment it was acquired — subsequent writes are\n"
" invisible to it.\n"
"\n"
" **Non-escaping:** The `Snapshot` handle is only valid inside the callback.\n"
" Do not store it in a variable, send it to another process, or return it —\n"
" using a `Snapshot` after the callback returns will panic or produce\n"
" undefined behavior because the underlying file handle is closed on exit.\n"
"\n"
" **Panics** if the snapshot file handle cannot be opened.\n"
"\n"
" ```gleam\n"
" let result = trove.with_snapshot(db, fn(snap) {\n"
" trove.snapshot_get(snapshot: snap, key: \"my_key\")\n"
" })\n"
" // result: Result(String, Nil)\n"
" ```\n"
).
-spec with_snapshot(
db(LSL, LSM),
fun((trove@internal@snapshot:snapshot(LSL, LSM)) -> LSR)
) -> LSR.
with_snapshot(Db, Callback) ->
Snap@1 = case trove@internal@db:acquire_snapshot(
erlang:element(2, Db),
erlang:element(3, Db)
) of
{ok, Snap} -> Snap;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove"/utf8>>,
function => <<"with_snapshot"/utf8>>,
line => 556,
value => _assert_fail,
start => 18916,
'end' => 19008,
pattern_start => 18927,
pattern_end => 18935})
end,
exception_ffi:defer(
fun() -> trove@internal@snapshot:close(Snap@1) end,
fun() -> Callback(Snap@1) end
).
-file("src/trove.gleam", 573).
?DOC(
" Look up a key in a snapshot. Returns `Error(Nil)` if the key does\n"
" not exist.\n"
"\n"
" **Panics** on store read or decode errors (e.g. file corruption).\n"
"\n"
" ```gleam\n"
" trove.with_snapshot(db, fn(snap) {\n"
" let assert Ok(value) = trove.snapshot_get(snapshot: snap, key: \"my_key\")\n"
" value\n"
" })\n"
" ```\n"
).
-spec snapshot_get(trove@internal@snapshot:snapshot(LSS, LST), LSS) -> {ok, LST} |
{error, nil}.
snapshot_get(Snapshot, Key) ->
_pipe = trove@internal@snapshot:get(Snapshot, Key),
gleam@option:to_result(_pipe, nil).
-file("src/trove.gleam", 611).
?DOC(
" Iterate over entries in a snapshot within optional key bounds.\n"
" Returns a lazy `Yielder` that streams entries from disk on demand,\n"
" reading only one leaf node at a time.\n"
"\n"
" The yielder holds a reference to the snapshot's file handle, so it\n"
" must be consumed before the snapshot is closed. For large ranges,\n"
" prefer this over `range` to avoid loading all entries into memory.\n"
"\n"
" **Panics** on store read or decode errors during iteration\n"
" (e.g. file corruption).\n"
"\n"
" Use `range.Inclusive(key)` or `range.Exclusive(key)` for bounds,\n"
" or `option.None` for unbounded. Use `range.Forward` or `range.Reverse`\n"
" for direction.\n"
"\n"
" ```gleam\n"
" import gleam/option.{None, Some}\n"
" import gleam/yielder\n"
" import trove/range\n"
"\n"
" let entries = trove.with_snapshot(db, fn(snap) {\n"
" let y = trove.snapshot_range(\n"
" snapshot: snap,\n"
" min: Some(range.Inclusive(\"a\")),\n"
" max: None,\n"
" direction: range.Forward,\n"
" )\n"
" yielder.to_list(y)\n"
" })\n"
" ```\n"
).
-spec snapshot_range(
trove@internal@snapshot:snapshot(LSY, LSZ),
gleam@option:option(trove@range:bound(LSY)),
gleam@option:option(trove@range:bound(LSY)),
trove@range:direction()
) -> gleam@yielder:yielder({LSY, LSZ}).
snapshot_range(Snapshot, Min, Max, Direction) ->
trove@internal@snapshot:range(Snapshot, Min, Max, Direction).
-file("src/trove.gleam", 645).
?DOC(
" Iterate over entries in the database within optional key bounds.\n"
" Returns a `List` of key-value pairs.\n"
"\n"
" For large result sets, use `with_snapshot` and `snapshot_range` instead\n"
" to stream entries lazily without loading them all at once.\n"
"\n"
" **Panics** if the snapshot file handle cannot be opened, or on store\n"
" read/decode errors during iteration.\n"
"\n"
" Use `range.Inclusive(key)` or `range.Exclusive(key)` for bounds,\n"
" or `option.None` for unbounded. Use `range.Forward` or `range.Reverse`\n"
" for direction.\n"
"\n"
" ```gleam\n"
" import gleam/option.{Some}\n"
" import trove/range\n"
"\n"
" let results =\n"
" trove.range(\n"
" db,\n"
" min: Some(range.Inclusive(\"a\")),\n"
" max: Some(range.Exclusive(\"z\")),\n"
" direction: range.Forward,\n"
" )\n"
" ```\n"
).
-spec range(
db(LTH, LTI),
gleam@option:option(trove@range:bound(LTH)),
gleam@option:option(trove@range:bound(LTH)),
trove@range:direction()
) -> list({LTH, LTI}).
range(Db, Min, Max, Direction) ->
Snap@1 = case trove@internal@db:acquire_snapshot(
erlang:element(2, Db),
erlang:element(3, Db)
) of
{ok, Snap} -> Snap;
_assert_fail ->
erlang:error(#{gleam_error => let_assert,
message => <<"Pattern match failed, no pattern matched the value."/utf8>>,
file => <<?FILEPATH/utf8>>,
module => <<"trove"/utf8>>,
function => <<"range"/utf8>>,
line => 651,
value => _assert_fail,
start => 21866,
'end' => 21958,
pattern_start => 21877,
pattern_end => 21885})
end,
exception_ffi:defer(
fun() -> trove@internal@snapshot:close(Snap@1) end,
fun() ->
_pipe = trove@internal@snapshot:range(Snap@1, Min, Max, Direction),
gleam@yielder:to_list(_pipe)
end
).
-file("src/trove.gleam", 658).
-spec map_open_error(trove@internal@db:internal_open_error()) -> open_error().
map_open_error(Error) ->
case Error of
{internal_directory_error, Reason} ->
{directory_error, Reason};
{internal_store_error, Reason@1} ->
{store_error, Reason@1};
{internal_lock_error, Reason@2} ->
{lock_error, Reason@2};
internal_actor_start_error ->
actor_start_error
end.
-file("src/trove.gleam", 667).
-spec to_internal_auto_compact(auto_compact()) -> trove@internal@db:auto_compact().
to_internal_auto_compact(Setting) ->
case Setting of
{auto_compact, Min_dirt, Min_dirt_factor} ->
{auto_compact, Min_dirt, Min_dirt_factor};
no_auto_compact ->
no_auto_compact
end.
-file("src/trove.gleam", 244).
?DOC(
" Change the auto-compaction setting at runtime.\n"
"\n"
" ```gleam\n"
" trove.set_auto_compact(db, trove.AutoCompact(min_dirt: 100, min_dirt_factor: 0.25))\n"
" ```\n"
).
-spec set_auto_compact(db(any(), any()), auto_compact()) -> nil.
set_auto_compact(Db, Setting) ->
trove@internal@db:set_auto_compact(
erlang:element(2, Db),
to_internal_auto_compact(Setting),
erlang:element(3, Db)
).
-file("src/trove.gleam", 675).
-spec to_internal_file_sync(file_sync()) -> trove@internal@db:file_sync().
to_internal_file_sync(Setting) ->
case Setting of
auto_sync ->
auto_sync;
manual_sync ->
manual_sync
end.
-file("src/trove.gleam", 147).
?DOC(
" Open a database at the configured path. Creates the directory if it does\n"
" not exist. If a store file already exists, recovers the tree from the\n"
" latest valid header.\n"
"\n"
" ```gleam\n"
" let config = trove.Config(\n"
" path: \"./my_db\",\n"
" key_codec: codec.string(),\n"
" value_codec: codec.string(),\n"
" key_compare: string.compare,\n"
" auto_compact: trove.NoAutoCompact,\n"
" auto_file_sync: trove.AutoSync,\n"
" call_timeout: 5000,\n"
" )\n"
" let assert Ok(db) = trove.open(config)\n"
" ```\n"
).
-spec open(config(LOE, LOF)) -> {ok, db(LOE, LOF)} | {error, open_error()}.
open(Config) ->
_pipe = trove@internal@db:open(
erlang:element(2, Config),
erlang:element(3, Config),
erlang:element(4, Config),
erlang:element(5, Config),
to_internal_file_sync(erlang:element(7, Config)),
to_internal_auto_compact(erlang:element(6, Config)),
erlang:element(8, Config)
),
_pipe@1 = gleam@result:map(
_pipe,
fun(_capture) -> {db, _capture, erlang:element(8, Config)} end
),
gleam@result:map_error(_pipe@1, fun map_open_error/1).