-module(trove). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/trove.gleam"). -export([list_keyspaces/1, put_in/4, get_in/3, delete_in/3, has_key_in/3, size_in/2, put_and_delete_multi_in/4, put_multi_in/3, delete_multi_in/3, 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, tx_get_in/3, tx_put_in/4, tx_delete_in/3, tx_has_key_in/3, with_snapshot/2, snapshot_get/2, snapshot_range/4, range/4, snapshot_get_in/3, snapshot_range_in/5, range_in/5, set_auto_compact/2, open/1, keyspace/5]). -export_type([open_error/0, auto_compact/0, file_sync/0, config/2, db/2, keyspace/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, which\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.AutoCompact(min_dirt: 1000, min_dirt_factor: 0.25),\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(MWM, MWN) :: {config, binary(), trove@codec:codec(MWM), trove@codec:codec(MWN), fun((MWM, MWM) -> gleam@order:order()), auto_compact(), file_sync(), integer()}. -opaque db(MWO, MWP) :: {db, gleam@erlang@process:subject(trove@internal@db:message(MWO, MWP)), integer()}. -opaque keyspace(MWQ, MWR) :: {keyspace, binary(), trove@codec:codec(MWQ), trove@codec:codec(MWR), fun((MWQ, MWQ) -> gleam@order:order())}. -type transaction_result(MWS, MWT, MWU) :: {commit, trove@internal@tx:tx(MWS, MWT), MWU} | {cancel, MWU}. -file("src/trove.gleam", 56). -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", 198). -spec adapt_compare( trove@codec:codec(MXQ), fun((MXQ, MXQ) -> gleam@order:order()) ) -> fun((bitstring(), bitstring()) -> gleam@order:order()). adapt_compare(Key_codec, Key_compare) -> fun(A, B) -> Decoded_a@1 = case (erlang:element(3, Key_codec))(A) of {ok, Decoded_a} -> Decoded_a; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"trove"/utf8>>, function => <<"adapt_compare"/utf8>>, line => 203, value => _assert_fail, start => 6796, 'end' => 6842, pattern_start => 6807, pattern_end => 6820}) end, Decoded_b@1 = case (erlang:element(3, Key_codec))(B) of {ok, Decoded_b} -> Decoded_b; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"trove"/utf8>>, function => <<"adapt_compare"/utf8>>, line => 204, value => _assert_fail@1, start => 6847, 'end' => 6893, pattern_start => 6858, pattern_end => 6871}) end, Key_compare(Decoded_a@1, Decoded_b@1) end. -file("src/trove.gleam", 220). ?DOC( " List the names of every keyspace currently registered on this database.\n" "\n" " Returns names in sorted order. Includes every keyspace that has been\n" " opened with `trove.keyspace(...)` in this session, plus every keyspace\n" " that was persisted in the store file (even if not yet registered in this\n" " session). Reading or writing a persisted-but-unregistered keyspace\n" " without first calling `trove.keyspace(...)` panics.\n" "\n" " ```gleam\n" " let names = trove.list_keyspaces(db)\n" " ```\n" ). -spec list_keyspaces(db(any(), any())) -> list(binary()). list_keyspaces(Db) -> trove@internal@db:list_keyspaces( erlang:element(2, Db), erlang:element(3, Db) ). -file("src/trove.gleam", 231). ?DOC( " Insert or update a key-value pair in a named keyspace.\n" "\n" " **Panics** on store I/O errors (e.g. disk full, file corruption).\n" "\n" " ```gleam\n" " trove.put_in(db, keyspace: users, key: \"alice\", value: \"admin\")\n" " ```\n" ). -spec put_in(db(any(), any()), keyspace(MYB, MYC), MYB, MYC) -> nil. put_in(Db, Keyspace, Key, Value) -> trove@internal@db:put_in( erlang:element(2, Db), erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key), (erlang:element(2, erlang:element(4, Keyspace)))(Value), erlang:element(3, Db) ). -file("src/trove.gleam", 255). ?DOC( " Look up a key in a named keyspace. Returns `Ok(value)` if found,\n" " `Error(Nil)` if the key does not exist.\n" "\n" " **Panics** on store I/O or decode errors (e.g. file corruption, codec\n" " mismatch against on-disk bytes).\n" "\n" " ```gleam\n" " let assert Ok(\"admin\") = trove.get_in(db, keyspace: users, key: \"alice\")\n" " ```\n" ). -spec get_in(db(any(), any()), keyspace(MYJ, MYK), MYJ) -> {ok, MYK} | {error, nil}. get_in(Db, Keyspace, Key) -> gleam@result:'try'( trove@internal@db:get_in( erlang:element(2, Db), erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key), erlang:element(3, Db) ), fun(Value_bytes) -> (erlang:element(3, erlang:element(4, Keyspace)))(Value_bytes) end ). -file("src/trove.gleam", 276). ?DOC( " Remove a key from a named keyspace. No error if the key does not exist.\n" "\n" " **Panics** on store I/O errors.\n" "\n" " ```gleam\n" " trove.delete_in(db, keyspace: users, key: \"alice\")\n" " ```\n" ). -spec delete_in(db(any(), any()), keyspace(MYT, any()), MYT) -> nil. delete_in(Db, Keyspace, Key) -> trove@internal@db:delete_in( erlang:element(2, Db), erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key), erlang:element(3, Db) ). -file("src/trove.gleam", 296). ?DOC( " Check whether a key exists in a named keyspace.\n" "\n" " **Panics** on store I/O errors.\n" "\n" " ```gleam\n" " let assert True = trove.has_key_in(db, keyspace: users, key: \"alice\")\n" " ```\n" ). -spec has_key_in(db(any(), any()), keyspace(MZB, any()), MZB) -> boolean(). has_key_in(Db, Keyspace, Key) -> trove@internal@db:has_key_in( erlang:element(2, Db), erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key), erlang:element(3, Db) ). -file("src/trove.gleam", 314). ?DOC( " Returns the number of live entries in a named keyspace.\n" "\n" " ```gleam\n" " let n = trove.size_in(db, keyspace: users)\n" " ```\n" ). -spec size_in(db(any(), any()), keyspace(any(), any())) -> integer(). size_in(Db, Keyspace) -> trove@internal@db:size_in( erlang:element(2, Db), erlang:element(2, Keyspace), erlang:element(3, Db) ). -file("src/trove.gleam", 366). ?DOC( " Atomically insert and delete entries in a named keyspace under a single\n" " header write. Puts are applied first, then deletes.\n" "\n" " **Panics** on store I/O errors.\n" "\n" " ```gleam\n" " trove.put_and_delete_multi_in(\n" " db,\n" " keyspace: users,\n" " puts: [#(\"bob\", \"admin\")],\n" " deletes: [\"alice\"],\n" " )\n" " ```\n" ). -spec put_and_delete_multi_in( db(any(), any()), keyspace(NAJ, NAK), list({NAJ, NAK}), list(NAJ) ) -> nil. put_and_delete_multi_in(Db, Keyspace, Puts, Deletes) -> Encoded_puts = gleam@list:map( Puts, fun(Pair) -> {(erlang:element(2, erlang:element(3, Keyspace)))( erlang:element(1, Pair) ), (erlang:element(2, erlang:element(4, Keyspace)))( erlang:element(2, Pair) )} end ), Encoded_deletes = gleam@list:map( Deletes, erlang:element(2, erlang:element(3, Keyspace)) ), trove@internal@db:put_and_delete_multi_in( erlang:element(2, Db), erlang:element(2, Keyspace), Encoded_puts, Encoded_deletes, erlang:element(3, Db) ). -file("src/trove.gleam", 330). ?DOC( " Atomically insert multiple key-value pairs into a named keyspace. A\n" " single header write covers the entire batch.\n" "\n" " **Panics** on store I/O errors.\n" "\n" " ```gleam\n" " trove.put_multi_in(\n" " db,\n" " keyspace: users,\n" " entries: [#(\"alice\", \"admin\"), #(\"bob\", \"member\")],\n" " )\n" " ```\n" ). -spec put_multi_in(db(any(), any()), keyspace(MZR, MZS), list({MZR, MZS})) -> nil. put_multi_in(Db, Keyspace, Entries) -> put_and_delete_multi_in(Db, Keyspace, Entries, []). -file("src/trove.gleam", 345). ?DOC( " Atomically delete multiple keys from a named keyspace.\n" "\n" " **Panics** on store I/O errors.\n" "\n" " ```gleam\n" " trove.delete_multi_in(db, keyspace: users, keys: [\"alice\", \"bob\"])\n" " ```\n" ). -spec delete_multi_in(db(any(), any()), keyspace(NAA, any()), list(NAA)) -> nil. delete_multi_in(Db, Keyspace, Keys) -> put_and_delete_multi_in(Db, Keyspace, [], Keys). -file("src/trove.gleam", 425). ?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", 440). ?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", 449). ?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", 458). ?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", 475). ?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", 490). ?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", 515). ?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(NCB, NCC), NCB) -> {ok, NCC} | {error, nil}. get(Db, Key) -> trove@internal@db:get(erlang:element(2, Db), Key, erlang:element(3, Db)). -file("src/trove.gleam", 526). ?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(NCH, NCI), NCH, NCI) -> nil. put(Db, Key, Value) -> trove@internal@db:put( erlang:element(2, Db), Key, Value, erlang:element(3, Db) ). -file("src/trove.gleam", 537). ?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(NCL, any()), NCL) -> nil. delete(Db, Key) -> trove@internal@db:delete(erlang:element(2, Db), Key, erlang:element(3, Db)). -file("src/trove.gleam", 548). ?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(NCP, any()), NCP) -> boolean(). has_key(Db, Key) -> trove@internal@db:has_key(erlang:element(2, Db), Key, erlang:element(3, Db)). -file("src/trove.gleam", 560). ?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(NCT, NCU), list({NCT, NCU})) -> nil. put_multi(Db, Entries) -> trove@internal@db:put_multi( erlang:element(2, Db), Entries, erlang:element(3, Db) ). -file("src/trove.gleam", 571). ?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(NCY, any()), list(NCY)) -> nil. delete_multi(Db, Keys) -> trove@internal@db:delete_multi( erlang:element(2, Db), Keys, erlang:element(3, Db) ). -file("src/trove.gleam", 587). ?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(NDD, NDE), list({NDD, NDE}), list(NDD)) -> 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", 722). -spec drain_latest_loop( gleam@erlang@process:subject(NDW), gleam@option:option(NDW) ) -> gleam@option:option(NDW). 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", 718). -spec drain_latest(gleam@erlang@process:subject(NDT)) -> gleam@option:option(NDT). drain_latest(Subject) -> drain_latest_loop(Subject, none). -file("src/trove.gleam", 655). ?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(NDJ, NDK), integer(), fun((trove@internal@tx:tx(NDJ, NDK)) -> transaction_result(NDJ, NDK, NDP)) ) -> NDP. 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 => <>, module => <<"trove"/utf8>>, function => <<"transaction"/utf8>>, line => 672, value => _assert_fail, start => 21596, 'end' => 21645, pattern_start => 21607, pattern_end => 21611}) 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 => <>, module => <<"trove"/utf8>>, function => <<"transaction"/utf8>>, line => 677, value => _assert_fail@1, start => 21827, 'end' => 21879, pattern_start => 21838, pattern_end => 21842} ) end, nil end end ) of {ok, nil} -> gleam@erlang@process:send(Result_subject, {ok, Value}), {commit_outcome, trove@internal@tx:get_tree(Tx_inner), trove@internal@tx:get_other_trees(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 => <>, module => <<"trove"/utf8>>, function => <<"transaction"/utf8>>, line => 711, value => _assert_fail@2, start => 22833, 'end' => 22891, pattern_start => 22844, pattern_end => 22854}) end, case Result@1 of {ok, Value@2} -> Value@2; {error, Ex@2} -> reraise(Ex@2) end. -file("src/trove.gleam", 744). ?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(NEA, NEB), NEA) -> {ok, NEB} | {error, nil}. tx_get(Tx, Key) -> trove@internal@tx:get(Tx, Key). -file("src/trove.gleam", 758). ?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(NEG, NEH), NEG, NEH) -> trove@internal@tx:tx(NEG, NEH). tx_put(Tx, Key, Value) -> trove@internal@tx:put(Tx, Key, Value). -file("src/trove.gleam", 772). ?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(NEM, NEN), NEM) -> trove@internal@tx:tx(NEM, NEN). tx_delete(Tx, Key) -> trove@internal@tx:delete(Tx, Key). -file("src/trove.gleam", 787). ?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(NES, any()), NES) -> boolean(). tx_has_key(Tx, Key) -> _pipe = tx_get(Tx, Key), gleam@result:is_ok(_pipe). -file("src/trove.gleam", 796). ?DOC( " Look up a key in a named keyspace within a transaction. Sees writes made\n" " earlier in the same transaction.\n" "\n" " **Panics** if the keyspace has not been registered in this session via\n" " `trove.keyspace(...)`, or on store I/O or decode errors.\n" ). -spec tx_get_in(trove@internal@tx:tx(any(), any()), keyspace(NFA, NFB), NFA) -> {ok, NFB} | {error, nil}. tx_get_in(Tx, Keyspace, Key) -> gleam@result:'try'( trove@internal@tx:get_in( Tx, erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key) ), fun(Value_bytes) -> (erlang:element(3, erlang:element(4, Keyspace)))(Value_bytes) end ). -file("src/trove.gleam", 814). ?DOC( " Insert or update a key-value pair in a named keyspace within a\n" " transaction. Returns the updated `Tx`.\n" "\n" " **Panics** if the keyspace has not been registered in this session via\n" " `trove.keyspace(...)`, or on store I/O errors.\n" ). -spec tx_put_in(trove@internal@tx:tx(NFG, NFH), keyspace(NFK, NFL), NFK, NFL) -> trove@internal@tx:tx(NFG, NFH). tx_put_in(Tx, Keyspace, Key, Value) -> trove@internal@tx:put_in( Tx, erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key), (erlang:element(2, erlang:element(4, Keyspace)))(Value) ). -file("src/trove.gleam", 832). ?DOC( " Delete a key from a named keyspace within a transaction.\n" "\n" " **Panics** if the keyspace has not been registered in this session via\n" " `trove.keyspace(...)`, or on store I/O errors.\n" ). -spec tx_delete_in(trove@internal@tx:tx(NFQ, NFR), keyspace(NFU, any()), NFU) -> trove@internal@tx:tx(NFQ, NFR). tx_delete_in(Tx, Keyspace, Key) -> trove@internal@tx:delete_in( Tx, erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key) ). -file("src/trove.gleam", 848). ?DOC( " Check whether a key exists in a named keyspace within a transaction.\n" "\n" " **Panics** if the keyspace has not been registered in this session via\n" " `trove.keyspace(...)`, or on store I/O or decode errors.\n" ). -spec tx_has_key_in( trove@internal@tx:tx(any(), any()), keyspace(NGE, any()), NGE ) -> boolean(). tx_has_key_in(Tx, Keyspace, Key) -> _pipe = tx_get_in(Tx, Keyspace, Key), gleam@result:is_ok(_pipe). -file("src/trove.gleam", 873). ?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(NGI, NGJ), fun((trove@internal@snapshot:snapshot(NGI, NGJ)) -> NGO) ) -> NGO. 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 => <>, module => <<"trove"/utf8>>, function => <<"with_snapshot"/utf8>>, line => 877, value => _assert_fail, start => 27987, 'end' => 28079, pattern_start => 27998, pattern_end => 28006}) end, exception_ffi:defer( fun() -> trove@internal@snapshot:close(Snap@1) end, fun() -> Callback(Snap@1) end ). -file("src/trove.gleam", 894). ?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(NGP, NGQ), NGP) -> {ok, NGQ} | {error, nil}. snapshot_get(Snapshot, Key) -> trove@internal@snapshot:get(Snapshot, Key). -file("src/trove.gleam", 931). ?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(NGV, NGW), gleam@option:option(trove@range:bound(NGV)), gleam@option:option(trove@range:bound(NGV)), trove@range:direction() ) -> gleam@yielder:yielder({NGV, NGW}). snapshot_range(Snapshot, Min, Max, Direction) -> trove@internal@snapshot:range(Snapshot, Min, Max, Direction). -file("src/trove.gleam", 965). ?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(NHE, NHF), gleam@option:option(trove@range:bound(NHE)), gleam@option:option(trove@range:bound(NHE)), trove@range:direction() ) -> list({NHE, NHF}). 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 => <>, module => <<"trove"/utf8>>, function => <<"range"/utf8>>, line => 971, value => _assert_fail, start => 30910, 'end' => 31002, pattern_start => 30921, pattern_end => 30929}) 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", 988). ?DOC( " Look up a key in a named keyspace within a snapshot.\n" "\n" " **Panics** if the keyspace was not registered before the snapshot was\n" " acquired, or on store read or decode errors.\n" "\n" " ```gleam\n" " trove.with_snapshot(db, fn(snap) {\n" " trove.snapshot_get_in(snap, keyspace: users, key: \"alice\")\n" " })\n" " ```\n" ). -spec snapshot_get_in( trove@internal@snapshot:snapshot(any(), any()), keyspace(NHR, NHS), NHR ) -> {ok, NHS} | {error, nil}. snapshot_get_in(Snapshot, Keyspace, Key) -> gleam@result:'try'( trove@internal@snapshot:get_in( Snapshot, erlang:element(2, Keyspace), (erlang:element(2, erlang:element(3, Keyspace)))(Key) ), fun(Value_bytes) -> (erlang:element(3, erlang:element(4, Keyspace)))(Value_bytes) end ). -file("src/trove.gleam", 1043). -spec encode_bound( gleam@option:option(trove@range:bound(NIK)), trove@codec:codec(NIK) ) -> gleam@option:option(trove@range:bound(bitstring())). encode_bound(Bound, Key_codec) -> gleam@option:map(Bound, fun(B) -> case B of {inclusive, V} -> {inclusive, (erlang:element(2, Key_codec))(V)}; {exclusive, V@1} -> {exclusive, (erlang:element(2, Key_codec))(V@1)} end end). -file("src/trove.gleam", 1022). ?DOC( " Iterate over entries in a named keyspace within a snapshot. Returns a\n" " lazy `Yielder` streaming entries from disk.\n" "\n" " The yielder holds a reference to the snapshot's file handle; consume it\n" " before the snapshot closes.\n" "\n" " **Panics** if the keyspace was not registered before the snapshot was\n" " acquired, or on store read or decode errors during iteration.\n" "\n" " ```gleam\n" " trove.with_snapshot(db, fn(snap) {\n" " trove.snapshot_range_in(\n" " snap,\n" " keyspace: users,\n" " min: Some(range.Inclusive(\"a\")),\n" " max: None,\n" " direction: range.Forward,\n" " )\n" " |> yielder.to_list\n" " })\n" " ```\n" ). -spec snapshot_range_in( trove@internal@snapshot:snapshot(any(), any()), keyspace(NIB, NIC), gleam@option:option(trove@range:bound(NIB)), gleam@option:option(trove@range:bound(NIB)), trove@range:direction() ) -> gleam@yielder:yielder({NIB, NIC}). snapshot_range_in(Snapshot, Keyspace, Min, Max, Direction) -> _pipe = trove@internal@snapshot:range_in( Snapshot, erlang:element(2, Keyspace), encode_bound(Min, erlang:element(3, Keyspace)), encode_bound(Max, erlang:element(3, Keyspace)), Direction ), gleam@yielder:map( _pipe, fun(Pair) -> K@1 = case (erlang:element(3, erlang:element(3, Keyspace)))( erlang:element(1, Pair) ) of {ok, K} -> K; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"trove"/utf8>>, function => <<"snapshot_range_in"/utf8>>, line => 1037, value => _assert_fail, start => 32940, 'end' => 32992, pattern_start => 32951, pattern_end => 32956}) end, V@1 = case (erlang:element(3, erlang:element(4, Keyspace)))( erlang:element(2, Pair) ) of {ok, V} -> V; _assert_fail@1 -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"trove"/utf8>>, function => <<"snapshot_range_in"/utf8>>, line => 1038, value => _assert_fail@1, start => 32997, 'end' => 33051, pattern_start => 33008, pattern_end => 33013}) end, {K@1, V@1} end ). -file("src/trove.gleam", 1072). ?DOC( " Iterate over entries in a named keyspace within optional key bounds.\n" " Returns a `List` of key-value pairs. For large result sets, use\n" " `with_snapshot` and `snapshot_range_in` instead.\n" "\n" " **Panics** if the keyspace has not been registered in this session via\n" " `trove.keyspace(...)`, if the snapshot file handle cannot be opened,\n" " or on store read or decode errors during iteration.\n" "\n" " ```gleam\n" " let results = trove.range_in(\n" " db,\n" " keyspace: users,\n" " min: Some(range.Inclusive(\"a\")),\n" " max: Some(range.Exclusive(\"z\")),\n" " direction: range.Forward,\n" " )\n" " ```\n" ). -spec range_in( db(any(), any()), keyspace(NIU, NIV), gleam@option:option(trove@range:bound(NIU)), gleam@option:option(trove@range:bound(NIU)), trove@range:direction() ) -> list({NIU, NIV}). range_in(Db, Keyspace, 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 => <>, module => <<"trove"/utf8>>, function => <<"range_in"/utf8>>, line => 1079, value => _assert_fail, start => 34217, 'end' => 34309, pattern_start => 34228, pattern_end => 34236}) end, exception_ffi:defer( fun() -> trove@internal@snapshot:close(Snap@1) end, fun() -> _pipe = snapshot_range_in(Snap@1, Keyspace, Min, Max, Direction), gleam@yielder:to_list(_pipe) end ). -file("src/trove.gleam", 1092). -spec map_open_error(trove@internal@db:open_error()) -> open_error(). map_open_error(Error) -> case Error of {directory_error, Reason} -> {directory_error, Reason}; {store_error, Reason@1} -> {store_error, Reason@1}; {lock_error, Reason@2} -> {lock_error, Reason@2}; actor_start_error -> actor_start_error end. -file("src/trove.gleam", 1101). -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", 499). ?DOC( " Change the auto-compaction setting at runtime.\n" "\n" " ```gleam\n" " trove.set_auto_compact(db, trove.AutoCompact(min_dirt: 1000, 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", 1109). -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", 402). ?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.AutoCompact(min_dirt: 1000, min_dirt_factor: 0.25),\n" " auto_file_sync: trove.AutoSync,\n" " call_timeout: 5000,\n" " )\n" " let assert Ok(db) = trove.open(config)\n" " ```\n" ). -spec open(config(NAP, NAQ)) -> {ok, db(NAP, NAQ)} | {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). -file("src/trove.gleam", 176). ?DOC( " Obtain a typed handle to a named keyspace. First use of a name registers it\n" " in this session; later uses update the codecs.\n" "\n" " **Panics** if `name` collides with the reserved default-keyspace sentinel.\n" "\n" " **Codec trust model.** Passing codecs that don't match those previously\n" " used for the same keyspace is undefined behavior: reads will likely\n" " produce garbage values or panics. Keep the\n" " `(key_codec, value_codec, key_compare)` tuple stable across opens for a\n" " given keyspace name. Matches the trust model of `Config.key_codec` and\n" " `Config.value_codec`.\n" "\n" " ```gleam\n" " let users =\n" " trove.keyspace(\n" " db,\n" " name: \"users\",\n" " key_codec: codec.string(),\n" " value_codec: codec.string(),\n" " key_compare: string.compare,\n" " )\n" " ```\n" ). -spec keyspace( db(any(), any()), binary(), trove@codec:codec(MXK), trove@codec:codec(MXM), fun((MXK, MXK) -> gleam@order:order()) ) -> keyspace(MXK, MXM). keyspace(Db, Name, Key_codec, Value_codec, Key_compare) -> case Name /= <<"__trove_default__"/utf8>> of true -> nil; _assert_fail -> erlang:error(#{gleam_error => let_assert, message => <<"Pattern match failed, no pattern matched the value."/utf8>>, file => <>, module => <<"trove"/utf8>>, function => <<"keyspace"/utf8>>, line => 183, value => _assert_fail, start => 6293, 'end' => 6349, pattern_start => 6304, pattern_end => 6308}) end, trove@internal@db:register_keyspace( erlang:element(2, Db), Name, adapt_compare(Key_codec, Key_compare), erlang:element(3, Db) ), {keyspace, Name, Key_codec, Value_codec, Key_compare}.