Generic key/value storage.
This is intentionally minimal and un-opinionated.
If you want namespacing, encode it in key (e.g. "my_game:key1").
If you want per-user values, pass user_id: ... to get/2, put/4, and delete/2.
If you want per-lobby values, pass lobby_id: ... to the same functions.
You can also pass both to scope a key to a user within a lobby.
This module uses the app cache (Gamend.Cache) as a best-effort read cache.
Writes update the cache and deletes evict it.
Summary
Types
Attributes used when creating or updating entries.
Options accepted by list_entries/1 and count_entries/1.
Metadata stored alongside a value. Typically a small map with auxiliary fields.
Value stored for a key. This is an arbitrary map and should contain JSON-serializable data.
Functions
Count the number of entries that match the optional filter.
Create a new Entry from attrs (expecting key, optional user_id/lobby_id,
value, metadata).
Returns {:ok, entry} or {:error, changeset}.
Delete the entry at key.
Delete an entry by its id.
Delete every entry scoped to a lobby, in one statement: for deleting the lobby.
Delete every entry a user holds inside one lobby.
Retrieve the value and metadata stored for key.
Fetch an Entry by its id.
Returns the Entry struct or nil if not found.
List key/value entries with optional pagination and filtering.
Delete every entry whose key starts with prefix and that has not been
written for days days, in batches of batch (default 500), and answer
how many went. For a key family that is history — one row per day, say —
which nothing else ever trims. Hand it to the retention sweep with
Gamend.Retention.register_kv_prefix/3 rather than calling it directly.
Store value with optional metadata at key.
Subscribe the current process to changes for a specific key/scope.
Unsubscribe the current process from changes for a specific key/scope.
Update an existing entry by id with attrs.
Returns {:ok, entry}, {:error, :not_found} if missing, or {:error, changeset} on validation error.
Types
@type attrs() :: %{ :key => String.t(), optional(:user_id) => String.t(), optional(:lobby_id) => String.t(), :value => value(), optional(:metadata) => metadata() }
Attributes used when creating or updating entries.
Expected keys (atom keys recommended):
:key— the entry key (String.t()):user_id— optional user id (String.t()):lobby_id— optional lobby id (String.t())
:value— the stored value (value()):metadata— optional metadata (metadata())
@type list_opts() :: [ page: pos_integer(), page_size: pos_integer(), user_id: Ecto.UUID.t(), lobby_id: Ecto.UUID.t(), global_only: boolean(), key: String.t() ]
Options accepted by list_entries/1 and count_entries/1.
Keys (all optional):
:page— page number (pos_integer(), defaults to1):page_size— page size (pos_integer(), defaults to50):user_id— filter by user id (Ecto.UUID.t()):lobby_id— filter by lobby id (Ecto.UUID.t()):global_only— when true, only return global entries (whereuser_idandlobby_idarenil) (boolean()):key— substring filter (String.t())
@type metadata() :: map()
Metadata stored alongside a value. Typically a small map with auxiliary fields.
@type value() :: map()
Value stored for a key. This is an arbitrary map and should contain JSON-serializable data.
Functions
@spec count_entries(list_opts()) :: non_neg_integer()
Count the number of entries that match the optional filter.
Accepts the same options as list_entries/1 (see list_opts/0). Returns a non-negative integer.
@spec create_entry(attrs()) :: {:ok, Gamend.KV.Entry.t()} | {:error, Ecto.Changeset.t()}
Create a new Entry from attrs (expecting key, optional user_id/lobby_id,
value, metadata).
Returns {:ok, entry} or {:error, changeset}.
Delete the entry at key.
Pass user_id: id or lobby_id: id in opts to delete a scoped key. Returns :ok.
@spec delete_entry(Ecto.UUID.t()) :: :ok
Delete an entry by its id.
Returns :ok whether or not the entry existed.
@spec delete_lobby_entries(Ecto.UUID.t()) :: non_neg_integer()
Delete every entry scoped to a lobby, in one statement: for deleting the lobby.
The per-entry cache invalidations and kv_deleted broadcasts wait for the
enclosing transaction to commit (Gamend.AfterCommit). One delete/2 per
entry cost a statement and two cache round-trips each while the caller held
the lobby's lock. Returns the number of entries deleted.
@spec delete_user_lobby_entries(Ecto.UUID.t(), Ecto.UUID.t()) :: non_neg_integer()
Delete every entry a user holds inside one lobby.
Called when a user stops being a member of a lobby, so per-member lobby state (ready flags, loadouts, character picks) does not survive a leave and rejoin. Entries scoped to the lobby alone, or to the user alone, are left untouched.
Returns the number of entries deleted.
Retrieve the value and metadata stored for key.
Pass user_id: id or lobby_id: id in opts to scope the lookup.
Returns {:ok, %{value: map(), metadata: map()}} when found, or :error when not present.
@spec get_entry(Ecto.UUID.t()) :: Gamend.KV.Entry.t() | nil
Fetch an Entry by its id.
Returns the Entry struct or nil if not found.
@spec list_entries(list_opts()) :: [Gamend.KV.Entry.t()]
List key/value entries with optional pagination and filtering.
Supported options: :page, :page_size, :user_id, :lobby_id, :global_only,
and :key (substring filter).
See list_opts/0 for the expected option types.
Returns a list of Entry structs ordered by most recently updated.
@spec prune_prefix(String.t(), pos_integer(), keyword()) :: non_neg_integer()
Delete every entry whose key starts with prefix and that has not been
written for days days, in batches of batch (default 500), and answer
how many went. For a key family that is history — one row per day, say —
which nothing else ever trims. Hand it to the retention sweep with
Gamend.Retention.register_kv_prefix/3 rather than calling it directly.
The prefix is matched literally (% and _ in it are not wildcards). An
empty prefix or a window of 0 deletes nothing. Each deleted row's
cache and its scope's listing are invalidated, as delete/2 does.
@spec put(String.t(), value(), metadata()) :: {:ok, Gamend.KV.Entry.t()} | {:error, Ecto.Changeset.t()}
@spec put(String.t(), value(), metadata(), list_opts()) :: {:ok, Gamend.KV.Entry.t()} | {:error, Ecto.Changeset.t()}
Store value with optional metadata at key.
When using the 4-arity, supported options include user_id: id or lobby_id: id to scope
the entry.
Returns {:ok, entry} on success or {:error, changeset} on validation failure.
Subscribe the current process to changes for a specific key/scope.
Unsubscribe the current process from changes for a specific key/scope.
@spec update_entry(Ecto.UUID.t(), attrs()) :: {:ok, Gamend.KV.Entry.t()} | {:error, :not_found} | {:error, Ecto.Changeset.t()}
Update an existing entry by id with attrs.
Returns {:ok, entry}, {:error, :not_found} if missing, or {:error, changeset} on validation error.