cover/Elixir.WalletIntegrations.InboxStore.html

1 defmodule WalletIntegrations.InboxStore do
2 @moduledoc """
3 ETS-backed GenServer for InboxRecord deduplication (ADR 0002).
4
5 Table: `:wallet_integrations_inbox`
6 Keyed by message_id for O(1) dedup lookup.
7 """
8
9 use GenServer
10
11 alias WalletIntegrations.InboxRecord
12
13 @table :wallet_integrations_inbox
14
15
:-(
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
16
17 @doc """
18 Attempt to claim a message_id for processing.
19 Returns {:ok, record} when first seen, {:duplicate, existing_record} when already seen.
20 """
21 @spec claim(message_id :: String.t(), consumer_app :: atom(), opts :: keyword()) ::
22 {:ok, InboxRecord.t()} | {:duplicate, InboxRecord.t()}
23
:-(
def claim(message_id, consumer_app, opts \\ []) do
24 11 GenServer.call(__MODULE__, {:claim, message_id, consumer_app, opts})
25 end
26
27 @spec get(message_id :: String.t()) :: {:ok, InboxRecord.t()} | {:error, :not_found}
28 1 def get(message_id), do: GenServer.call(__MODULE__, {:get, message_id})
29
30 @spec update(InboxRecord.t()) :: :ok | {:error, :not_found}
31 2 def update(%InboxRecord{} = r), do: GenServer.call(__MODULE__, {:update, r})
32
33 @spec reset() :: :ok
34 23 def reset, do: GenServer.call(__MODULE__, :reset)
35
36 @impl true
37 def init(_opts) do
38
:-(
:ets.new(@table, [:set, :protected, :named_table])
39 {:ok, %{}}
40 end
41
42 @impl true
43 def handle_call({:claim, message_id, consumer_app, opts}, _from, state) do
44 11 result =
45 case :ets.lookup(@table, message_id) do
46 2 [{_, existing}] ->
47 {:duplicate, existing}
48 [] ->
49 9 record = InboxRecord.new(message_id, consumer_app, opts)
50 9 :ets.insert(@table, {message_id, record})
51 {:ok, record}
52 end
53 11 {:reply, result, state}
54 end
55
56 @impl true
57 def handle_call({:get, message_id}, _from, state) do
58 1 result =
59 case :ets.lookup(@table, message_id) do
60 1 [{_, r}] -> {:ok, r}
61
:-(
[] -> {:error, :not_found}
62 end
63 1 {:reply, result, state}
64 end
65
66 @impl true
67 def handle_call({:update, r}, _from, state) do
68 2 result =
69 2 case :ets.lookup(@table, r.message_id) do
70 [{_, _}] ->
71 2 :ets.insert(@table, {r.message_id, r})
72 :ok
73
:-(
[] ->
74 {:error, :not_found}
75 end
76 2 {:reply, result, state}
77 end
78
79 @impl true
80 def handle_call(:reset, _from, state) do
81 23 :ets.delete_all_objects(@table)
82 23 {:reply, :ok, state}
83 end
84 end
Line Hits Source