Amarula.RetryCache behaviour (amarula v0.1.0)
View SourcePluggable cache of recently-sent messages, so the library can re-encrypt and
resend when a recipient asks for a retry (<receipt type="retry">).
This is a separate concern from Amarula.Storage: it holds ephemeral,
bounded, latency-sensitive state (a small LRU of recent sends), not durable
account state. It therefore has its own behaviour, its own adapters, and its
own config key — independent of where durable storage lives.
Adapters
Amarula.RetryCache.ETS— in-memory, per-connection (the default). Lost on restart, which is fine: a retry receipt arrives within seconds.Amarula.RetryCache.DETS— on-disk, survives restart.
Select one via the connection config :retry_cache (a {adapter, opts} spec,
a bare opts list → the default adapter, or a prebuilt Scope), or set the
default for all connections:
config :amarula, retry_cache_adapter: Amarula.RetryCache.DETSScoping
Like Amarula.Storage, every call carries the connection profile, so one
adapter instance serves many connections. The dispatch handle is a
RetryCache.Scope (adapter + state).
Eviction
The cache is bounded; eviction is the adapter's job (each backend does it
differently — an ETS size check, a DETS fold, a Redis TTL). put/4 is expected
to keep the cache within its bound.
Summary
Types
Adapter-specific state, returned by the adapter's new/1.
A cached entry: the recipient + the sent message + a ms timestamp.
The connection identity (its :profile).
Callbacks
Number of cached entries for profile (for tests/introspection).
Fetch a cached entry by msg_id, or :error on a miss.
Initialise adapter state from opts. Called once per connection.
Store entry under msg_id for profile, evicting to stay within bound.
Functions
Number of cached entries for profile.
The adapter used when config gives bare opts / no :retry_cache.
Fetch a cached entry by id, or :error.
Store a sent message for possible retry-resend.
Build a RetryCache.Scope from a connection config. Reads :retry_cache
(a {adapter, opts} spec, a bare adapter module, a bare opts list, or a
prebuilt Scope); when absent, uses default_adapter/0 with no opts.
Types
@type adapter_state() :: term()
Adapter-specific state, returned by the adapter's new/1.
A cached entry: the recipient + the sent message + a ms timestamp.
The connection identity (its :profile).
Callbacks
@callback count(adapter_state(), profile()) :: non_neg_integer()
Number of cached entries for profile (for tests/introspection).
@callback get(adapter_state(), profile(), msg_id :: String.t()) :: {:ok, entry()} | :error
Fetch a cached entry by msg_id, or :error on a miss.
@callback new(opts :: keyword()) :: adapter_state()
Initialise adapter state from opts. Called once per connection.
@callback put(adapter_state(), profile(), msg_id :: String.t(), entry()) :: :ok
Store entry under msg_id for profile, evicting to stay within bound.
Functions
@spec count(Amarula.RetryCache.Scope.t(), profile()) :: non_neg_integer()
Number of cached entries for profile.
@spec default_adapter() :: module()
The adapter used when config gives bare opts / no :retry_cache.
@spec get(Amarula.RetryCache.Scope.t(), profile(), String.t()) :: {:ok, entry()} | :error
Fetch a cached entry by id, or :error.
@spec put(Amarula.RetryCache.Scope.t(), profile(), String.t(), entry()) :: :ok
Store a sent message for possible retry-resend.
@spec scope(map()) :: Amarula.RetryCache.Scope.t()
Build a RetryCache.Scope from a connection config. Reads :retry_cache
(a {adapter, opts} spec, a bare adapter module, a bare opts list, or a
prebuilt Scope); when absent, uses default_adapter/0 with no opts.