Amarula.RetryCache behaviour (amarula v0.1.0)

View Source

Pluggable 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

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.DETS

Scoping

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

adapter_state()

@type adapter_state() :: term()

Adapter-specific state, returned by the adapter's new/1.

entry()

@type entry() :: %{recipient_jid: String.t(), message: struct(), ts: integer()}

A cached entry: the recipient + the sent message + a ms timestamp.

profile()

@type profile() :: atom() | String.t()

The connection identity (its :profile).

Callbacks

count(adapter_state, profile)

@callback count(adapter_state(), profile()) :: non_neg_integer()

Number of cached entries for profile (for tests/introspection).

get(adapter_state, profile, msg_id)

@callback get(adapter_state(), profile(), msg_id :: String.t()) :: {:ok, entry()} | :error

Fetch a cached entry by msg_id, or :error on a miss.

new(opts)

@callback new(opts :: keyword()) :: adapter_state()

Initialise adapter state from opts. Called once per connection.

put(adapter_state, profile, msg_id, entry)

@callback put(adapter_state(), profile(), msg_id :: String.t(), entry()) :: :ok

Store entry under msg_id for profile, evicting to stay within bound.

Functions

count(scope, profile)

Number of cached entries for profile.

default_adapter()

@spec default_adapter() :: module()

The adapter used when config gives bare opts / no :retry_cache.

get(scope, profile, msg_id)

@spec get(Amarula.RetryCache.Scope.t(), profile(), String.t()) ::
  {:ok, entry()} | :error

Fetch a cached entry by id, or :error.

put(scope, profile, msg_id, entry)

@spec put(Amarula.RetryCache.Scope.t(), profile(), String.t(), entry()) :: :ok

Store a sent message for possible retry-resend.

scope(config)

@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.