-module(shelf@duplicate_bag). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]). -define(FILEPATH, "src/shelf/duplicate_bag.gleam"). -export([open_config/3, open/5, close/1, with_table/6, member/2, to_list/1, fold/3, size/1, insert/3, insert_list/2, delete_key/2, delete_object/3, delete_all/1, save/1, reload/1, sync/1, lookup/2]). -export_type([p_duplicate_bag/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -opaque p_duplicate_bag(MFL, MFM) :: {p_duplicate_bag, shelf@internal:ets_ref(), shelf@internal:dets_ref(), shelf@internal:guardian_ref(), shelf:write_mode(), gleam@dynamic@decode:decoder({MFL, MFM})}. -file("src/shelf/duplicate_bag.gleam", 65). ?DOC( " Open a persistent duplicate bag table with full configuration.\n" "\n" " If the DETS file exists, its contents are loaded into a fresh ETS\n" " table after validating each entry through the provided decoders.\n" " If no file exists, both tables start empty.\n" "\n" " The DETS file path is validated against the configured base directory.\n" "\n" " ```gleam\n" " let config =\n" " shelf.config(name: \"events\", path: \"events.dets\",\n" " base_directory: \"/app/data\")\n" " |> shelf.write_mode(shelf.WriteThrough)\n" " let assert Ok(table) =\n" " duplicate_bag.open_config(config,\n" " key: decode.string, value: decode.string)\n" " ```\n" ). -spec open_config( shelf:config(), gleam@dynamic@decode:decoder(MFN), gleam@dynamic@decode:decoder(MFP) ) -> {ok, p_duplicate_bag(MFN, MFP)} | {error, shelf:shelf_error()}. open_config(Config, Key_decoder, Value_decoder) -> gleam@result:'try'( shelf@internal:generic_open( Config, <<"duplicate_bag"/utf8>>, Key_decoder, Value_decoder ), fun(Result) -> {ok, {p_duplicate_bag, erlang:element(1, Result), erlang:element(2, Result), erlang:element(3, Result), erlang:element(4, Result), erlang:element(5, Result)}} end ). -file("src/shelf/duplicate_bag.gleam", 94). ?DOC( " Open a persistent duplicate bag table with defaults (WriteBack mode).\n" "\n" " ```gleam\n" " let assert Ok(table) =\n" " duplicate_bag.open(name: \"events\", path: \"events.dets\",\n" " base_directory: \"/app/data\",\n" " key: decode.string, value: decode.string)\n" " ```\n" ). -spec open( binary(), binary(), binary(), gleam@dynamic@decode:decoder(MFV), gleam@dynamic@decode:decoder(MFX) ) -> {ok, p_duplicate_bag(MFV, MFX)} | {error, shelf:shelf_error()}. open(Name, Path, Base_directory, Key_decoder, Value_decoder) -> open_config( shelf:config(Name, Path, Base_directory), Key_decoder, Value_decoder ). -file("src/shelf/duplicate_bag.gleam", 116). ?DOC( " Close the table, saving all data to disk.\n" "\n" " On `Ok(Nil)`, the handle must not be used again. If the final save\n" " fails with a retryable persistence error, `close()` returns\n" " `Error(...)` and leaves the table open so the caller can retry.\n" " If close fails terminally, Shelf still releases resources and future\n" " operations on the handle return `Error(TableClosed)`.\n" ). -spec close(p_duplicate_bag(any(), any())) -> {ok, nil} | {error, shelf:shelf_error()}. close(Table) -> shelf_ffi:close( erlang:element(2, Table), erlang:element(3, Table), erlang:element(4, Table) ). -file("src/shelf/duplicate_bag.gleam", 134). ?DOC( " Use a table within a callback, ensuring it is closed afterward.\n" "\n" " If the final save fails during close, `with_table` force-cleans the\n" " table to release resources. If the callback succeeded, the close\n" " error is returned; if both the callback and close fail, the callback\n" " error is preserved.\n" "\n" " ```gleam\n" " use table <- duplicate_bag.with_table(\"events\", \"events.dets\",\n" " base_directory: \"/app/data\",\n" " key: decode.string, value: decode.string)\n" " duplicate_bag.insert(table, \"click\", \"btn_1\")\n" " ```\n" ). -spec with_table( binary(), binary(), binary(), gleam@dynamic@decode:decoder(MGJ), gleam@dynamic@decode:decoder(MGL), fun((p_duplicate_bag(MGJ, MGL)) -> {ok, MGP} | {error, shelf:shelf_error()}) ) -> {ok, MGP} | {error, shelf:shelf_error()}. with_table(Name, Path, Base_directory, Key_decoder, Value_decoder, Fun) -> shelf@internal:generic_with_table( fun() -> open(Name, Path, Base_directory, Key_decoder, Value_decoder) end, fun close/1, Fun ). -file("src/shelf/duplicate_bag.gleam", 172). ?DOC(" Check if a key exists without returning the values.\n"). -spec member(p_duplicate_bag(MHB, any()), MHB) -> {ok, boolean()} | {error, shelf:shelf_error()}. member(Table, Key) -> shelf_ffi:member(erlang:element(2, Table), Key). -file("src/shelf/duplicate_bag.gleam", 183). ?DOC( " Return all key-value pairs as a list.\n" "\n" " **Warning**: loads entire table into memory.\n" ). -spec to_list(p_duplicate_bag(MHH, MHI)) -> {ok, list({MHH, MHI})} | {error, shelf:shelf_error()}. to_list(Table) -> shelf_ffi:to_list(erlang:element(2, Table)). -file("src/shelf/duplicate_bag.gleam", 191). ?DOC(" Fold over all entries. Order is unspecified.\n"). -spec fold(p_duplicate_bag(MHO, MHP), MHS, fun((MHS, MHO, MHP) -> MHS)) -> {ok, MHS} | {error, shelf:shelf_error()}. fold(Table, Initial, Fun) -> shelf@internal:generic_fold(erlang:element(2, Table), Initial, Fun). -file("src/shelf/duplicate_bag.gleam", 201). ?DOC(" Return the number of entries in the table.\n"). -spec size(p_duplicate_bag(any(), any())) -> {ok, integer()} | {error, shelf:shelf_error()}. size(Table) -> shelf_ffi:size(erlang:element(2, Table)). -file("src/shelf/duplicate_bag.gleam", 209). ?DOC(" Insert a key-value pair. Duplicates are preserved.\n"). -spec insert(p_duplicate_bag(MIB, MIC), MIB, MIC) -> {ok, nil} | {error, shelf:shelf_error()}. insert(Table, Key, Value) -> shelf@internal:generic_insert( erlang:element(2, Table), erlang:element(3, Table), erlang:element(5, Table), Key, Value ). -file("src/shelf/duplicate_bag.gleam", 219). ?DOC(" Insert multiple key-value pairs.\n"). -spec insert_list(p_duplicate_bag(MIH, MII), list({MIH, MII})) -> {ok, nil} | {error, shelf:shelf_error()}. insert_list(Table, Entries) -> shelf@internal:generic_insert_list( erlang:element(2, Table), erlang:element(3, Table), erlang:element(5, Table), Entries ). -file("src/shelf/duplicate_bag.gleam", 230). ?DOC(" Delete all values for the given key.\n"). -spec delete_key(p_duplicate_bag(MIO, any()), MIO) -> {ok, nil} | {error, shelf:shelf_error()}. delete_key(Table, Key) -> shelf@internal:generic_delete_key( erlang:element(2, Table), erlang:element(3, Table), erlang:element(5, Table), Key ). -file("src/shelf/duplicate_bag.gleam", 242). ?DOC( " Delete a specific key-value pair.\n" "\n" " Only the exact matching pair is removed. Other values for the same\n" " key are preserved.\n" ). -spec delete_object(p_duplicate_bag(MIU, MIV), MIU, MIV) -> {ok, nil} | {error, shelf:shelf_error()}. delete_object(Table, Key, Value) -> shelf@internal:generic_delete_object( erlang:element(2, Table), erlang:element(3, Table), erlang:element(5, Table), Key, Value ). -file("src/shelf/duplicate_bag.gleam", 261). ?DOC( " Delete all entries from the table.\n" "\n" " The table remains open and usable after this call — only the data\n" " is removed. To release the table entirely, use `close`.\n" ). -spec delete_all(p_duplicate_bag(any(), any())) -> {ok, nil} | {error, shelf:shelf_error()}. delete_all(Table) -> shelf@internal:generic_delete_all( erlang:element(2, Table), erlang:element(3, Table), erlang:element(5, Table) ). -file("src/shelf/duplicate_bag.gleam", 273). ?DOC( " Snapshot the current ETS contents to DETS.\n" "\n" " Uses an atomic save strategy: data is written to a temporary file\n" " first, then atomically renamed over the original DETS file. This\n" " prevents data loss if the process is killed mid-save.\n" ). -spec save(p_duplicate_bag(any(), any())) -> {ok, nil} | {error, shelf:shelf_error()}. save(Table) -> shelf_ffi:save(erlang:element(2, Table), erlang:element(3, Table)). -file("src/shelf/duplicate_bag.gleam", 284). ?DOC( " Discard unsaved ETS changes and reload from DETS.\n" "\n" " Clears the ETS table, re-reads all DETS entries, validates them\n" " through the stored decoders, and loads valid entries into ETS.\n" " Only useful in WriteBack mode — in WriteThrough mode, ETS and\n" " DETS are always in sync.\n" ). -spec reload(p_duplicate_bag(any(), any())) -> {ok, nil} | {error, shelf:shelf_error()}. reload(Table) -> shelf@internal:generic_reload( erlang:element(2, Table), erlang:element(3, Table), erlang:element(6, Table) ). -file("src/shelf/duplicate_bag.gleam", 294). ?DOC( " Flush the DETS write buffer to the OS.\n" "\n" " DETS buffers writes internally. This forces them to be written\n" " to the underlying filesystem. Most useful in WriteThrough mode\n" " when you want to guarantee durability.\n" ). -spec sync(p_duplicate_bag(any(), any())) -> {ok, nil} | {error, shelf:shelf_error()}. sync(Table) -> shelf_ffi:sync_dets(erlang:element(2, Table), erlang:element(3, Table)). -file("src/shelf/duplicate_bag.gleam", 163). ?DOC( " Look up all values for a key.\n" "\n" " Returns `Error(NotFound)` if the key does not exist.\n" ). -spec lookup(p_duplicate_bag(MGU, MGV), MGU) -> {ok, list(MGV)} | {error, shelf:shelf_error()}. lookup(Table, Key) -> shelf_ffi:lookup_bag(erlang:element(2, Table), Key).