A simple ETS-based key-value store with the ability to watch changes.

Usage

You can start the bucket process under a supervisor:

children = [
  {Rkv, bucket: :my_bucket}
]

Supervisor.start_link(children, strategy: :one_for_one)

Or start it directly:

Rkv.start_link(bucket: :my_bucket)

Then you can use the bucket:

Rkv.put(:my_bucket, "foo", "bar")
Rkv.get(:my_bucket, "foo")
#=> "bar"

Buckets

A bucket's data lives only as long as the process that owns it. If that process restarts, the bucket comes back empty, so treat a bucket as a cache rather than a store.

Bucket names are global to the application. Pick one unlikely to clash if you use Rkv inside a library.

Watching changes

A notification tells you that a key changed, not what it changed to, so read the key when one arrives. A key that changes often may change again before you read it, so you see the latest value rather than every step along the way.

Watching a key and its bucket at the same time delivers two messages, one for each subscription.

Summary

Functions

Returns all key/value pairs.

Returns all active buckets.

Returns a specification to start this module under a supervisor.

Returns the :ets_options a bucket is created with by default.

Deletes the key from the bucket.

Returns the underlying ETS table.

Returns true if the key exists in the bucket, otherwise false.

Fetches the value for the key.

Returns the value by key.

Puts the key into the bucket.

Puts the key into the bucket only if the key does not exist.

Starts the Rkv bucket.

Unsubscribes the caller from all updates.

Unsubscribes the caller from key updates.

Subscribes the caller to all updates.

Subscribes the caller to key updates.

Types

bucket()

@type bucket() :: term()

key()

@type key() :: any()

option()

@type option() :: {:bucket, bucket()} | {:ets_options, list()}

value()

@type value() :: any()

Functions

all(bucket)

@spec all(bucket()) :: [{key(), value()}]

Returns all key/value pairs.

The order is not defined.

Examples

iex> Rkv.put(:my_bucket, "foo", "bar")
iex> Rkv.all(:my_bucket)
[{"foo", "bar"}]

buckets()

@spec buckets() :: [bucket()]

Returns all active buckets.

child_spec(init_arg)

@spec child_spec([option()]) :: Supervisor.child_spec()

Returns a specification to start this module under a supervisor.

See Supervisor.

default_ets_options()

@spec default_ets_options() :: list()

Returns the :ets_options a bucket is created with by default.

Add to it to keep the defaults:

{Rkv, bucket: :my_bucket, ets_options: Rkv.default_ets_options() ++ [:compressed]}

delete(bucket, key)

@spec delete(bucket(), key()) :: :ok

Deletes the key from the bucket.

Examples

iex> Rkv.put(:my_bucket, "foo", "bar")
iex> Rkv.delete(:my_bucket, "foo")
:ok
iex> Rkv.get(:my_bucket, "foo")
nil

ets(bucket)

@spec ets(bucket()) :: :ets.table()

Returns the underlying ETS table.

exists?(bucket, key)

@spec exists?(bucket(), key()) :: boolean()

Returns true if the key exists in the bucket, otherwise false.

Examples

iex> Rkv.put(:my_bucket, "foo", "bar")
iex> Rkv.exists?(:my_bucket, "foo")
true

iex> Rkv.exists?(:my_bucket, "missing")
false

fetch(bucket, key)

@spec fetch(bucket(), key()) :: {:ok, value()} | :error

Fetches the value for the key.

Returns {:ok, value} if the key exists, otherwise :error.

Examples

iex> Rkv.put(:my_bucket, "foo", "bar")
iex> Rkv.fetch(:my_bucket, "foo")
{:ok, "bar"}

iex> Rkv.fetch(:my_bucket, "missing")
:error

get(bucket, key, default \\ nil)

@spec get(bucket(), key(), any()) :: value() | nil

Returns the value by key.

Returns default if the key does not exist.

Examples

iex> Rkv.put(:my_bucket, "foo", "bar")
iex> Rkv.get(:my_bucket, "foo")
"bar"

iex> Rkv.get(:my_bucket, "missing")
nil

iex> Rkv.get(:my_bucket, "missing", :default)
:default

put(bucket, key, value)

@spec put(bucket(), key(), value()) :: :ok

Puts the key into the bucket.

If the key already exists, the value is updated.

Examples

iex> Rkv.put(:my_bucket, "foo", "bar")
:ok

put_new(bucket, key, value)

@spec put_new(bucket(), key(), value()) :: :ok | {:error, :already_exists}

Puts the key into the bucket only if the key does not exist.

Returns :ok if successful, or {:error, :already_exists} if the key already exists.

Examples

iex> Rkv.put_new(:my_bucket, "foo", "bar")
:ok
iex> Rkv.put_new(:my_bucket, "foo", "baz")
{:error, :already_exists}

start_link(opts)

@spec start_link([option()]) :: GenServer.on_start()

Starts the Rkv bucket.

The opts keyword list must contain the :bucket key, which is used to register the process.

Options

  • :bucket - the name of the bucket (required)
  • :ets_options - options for :ets.new/2 (optional). Replaces default_ets_options/0 instead of merging with it. The table must be :set or :ordered_set, and :public.

unwatch_all(bucket)

@spec unwatch_all(bucket()) :: :ok

Unsubscribes the caller from all updates.

Examples

iex> Rkv.unwatch_all(:my_bucket)
:ok

unwatch_key(bucket, key)

@spec unwatch_key(bucket(), key()) :: :ok

Unsubscribes the caller from key updates.

Examples

iex> Rkv.unwatch_key(:my_bucket, "foo")
:ok

watch_all(bucket)

@spec watch_all(bucket()) :: :ok | {:error, term()}

Subscribes the caller to all updates.

The caller will receive:

  • {:updated, bucket, key} when any key is updated
  • {:deleted, bucket, key} when any key is deleted

Examples

iex> Rkv.watch_all(:my_bucket)
:ok

watch_key(bucket, key)

@spec watch_key(bucket(), key()) :: :ok | {:error, term()}

Subscribes the caller to key updates.

The caller will receive:

  • {:updated, bucket, key} when the key is updated
  • {:deleted, bucket, key} when the key is deleted

Examples

iex> Rkv.watch_key(:my_bucket, "foo")
:ok