# `Slap.KV`
[🔗](https://github.com/mjrusso/slap/blob/slap_kv-v0.3.0/slap_kv/lib/slap/kv.ex#L1)

A durable key-value store on `Slap.KV.Cluster`. Rows are addressed by a
partition and a key, both non-empty binaries: the partition places the
row on a shard (`Slap.KV.Cluster.shard_for(partition)`), and within a
partition rows are in key order, so a partition can be scanned. There
are no scans across partitions.

Each row has a version, an integer that changes with every write to it.
`put/4` and `delete/3` take `if_version:` to write only if the row is at
that version (`:absent` for a row that must not exist), so a
read-modify-write can detect a concurrent change.

A write returns once it is durable, and reads return only durable rows:
a read never sees a write that could still be lost.
Pass `cluster:` to use a module defined with `use Slap.KV.Cluster,
otp_app: :my_app`. Run one KV cluster per VM.

A write may be applied after its caller has stopped waiting for it (see
`:timeout` below). A write with a `:deadline` is not applied after it,
and a read with `consistency: :linearizable` sees every write applied
before it: together, they let a caller find out when a write it gave up
on can no longer happen.

## Errors shared by all calls

Invalid row data and scan bounds return `{:error, {:bad_request, reason}}`.
Invalid control options (`:consistency`, `:with_versions`, `:timeout`,
`:cluster`) and unknown option names raise `ArgumentError`.

  * `{:error, :unavailable}` - the shard is stopping, moving or has no
    owner, or a write failed. A write that fails this way may still have
    been applied: read the row to find out rather than retry blindly.
  * `{:error, :timeout}` - no reply within `:timeout` (default 30 s). A
    write may still be applied.
  * `{:error, :deadline_exceeded}` - a write's `:deadline` passed before
    it was applied. It was not, and will not be, applied.
  * `{:error, {:bad_request, reason}}` - invalid row data or scan bounds.

# `entry`

```elixir
@type entry() :: %{value: binary(), version: version()}
```

The value and version returned by `get/3`.

# `error`

```elixir
@type error() ::
  :deadline_exceeded
  | :timeout
  | :unavailable
  | {:bad_request, term()}
  | {:conflict, version() | nil}
```

# `key`

```elixir
@type key() :: binary()
```

# `page`

```elixir
@type page() :: %{rows: [scan_entry()], cursor: binary() | nil}
```

# `partition`

```elixir
@type partition() :: binary()
```

# `scan_entry`

```elixir
@type scan_entry() :: {key(), binary()} | {key(), binary(), version()}
```

A row returned by `scan/2`, with its key and optional version.

# `version`

```elixir
@type version() :: non_neg_integer()
```

# `delete`

```elixir
@spec delete(partition(), key(), keyword()) :: :ok | {:error, error()}
```

Deletes a row. Returns `:ok` once the deletion is durable, whether or not
the row existed. With `if_version: version` the row must be at `version`,
and with `deadline:` it must be applied by then (as for `put/4`).

# `get`

```elixir
@spec get(partition(), key(), keyword()) :: {:ok, entry() | nil} | {:error, error()}
```

Reads a row. Returns `{:ok, %{value: value, version: version}}`, or
`{:ok, nil}` when there is none.

With `consistency: :linearizable`, the read goes through the row's
partition writer, behind the writes it has received, and returns once
every write it has applied is durable and a write of its own confirms
that its node still owns the shard. Such a read sees every write that
was applied before it, acknowledged or not; it costs a write. The
default, `:durable`, reads what is durable without waiting, and may miss
a write that is applied but not yet acknowledged.

# `put`

```elixir
@spec put(partition(), key(), binary(), keyword()) ::
  {:ok, version()} | {:error, error()}
```

Writes a row. Returns `{:ok, version}` once it is durable.

With `if_version: version` the row must be at `version`, and with
`if_version: :absent` there must be no row; otherwise the result is
`{:error, {:conflict, current}}`, where `current` is the row's version,
or `nil` if there is none.

With `deadline: ms` (a system time, `System.os_time(:millisecond)`) the
write is applied only if the row's partition writer gets to it before
then, by its node's clock; otherwise the result is `{:error,
:deadline_exceeded}`. So once a clock reads `ms` plus the largest
difference between nodes' clocks, the write can no longer be applied.

# `scan`

```elixir
@spec scan(partition(), keyword()) :: {:ok, page()} | {:error, error()}
```

Lists a partition's rows in key order. Returns `{:ok, %{rows: [{key,
value}], cursor: cursor}}`, where `cursor` is `nil` after the last page.
With `with_versions: true`, rows are `{key, value, version}`.

Options:

  * `:prefix` - only keys that start with it.
  * `:gte`, `:lt` - only keys in this range.
  * `:limit` - rows per page, 1 to 1,000 (default 100).
  * `:cursor` - the `cursor` of the previous page, to continue after it.
  * `:with_versions` - include each row's version (default `false`).

Each page is a separate read of the durable rows: rows written between
pages appear in later pages if their keys come later.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
