# `Slap.SlateDB.ObjectStore`
[🔗](https://github.com/mjrusso/slap/blob/slap_slatedb-v0.2.0/slap_slatedb/lib/slap/slatedb/object_store.ex#L1)

Plain objects in the store a database lives in: small objects with
conditional writes (`get/3`, `put/4`), such as leases (see
`Slap.Cluster.Strategy.ObjectLease`), and large ones streamed in and out
(`upload/4`, `download/3`), such as file bodies. Every call takes the
`:timeout` option (see `Slap.SlateDB`); for a stream it bounds each step.

    {:ok, store} = Slap.SlateDB.ObjectStore.open("_cluster", store: {:url, "s3://bucket/prefix"})
    {:ok, version} = Slap.SlateDB.ObjectStore.put(store, "leases/0", "a", mode: :create)
    {:ok, {"a", ^version}} = Slap.SlateDB.ObjectStore.get(store, "leases/0")
    {:error, :conflict} = Slap.SlateDB.ObjectStore.put(store, "leases/0", "b", mode: :create)
    {:ok, _} = Slap.SlateDB.ObjectStore.put(store, "leases/0", "b", mode: {:update, version})

Conditional updates (`{:update, version}`, an `If-Match` PUT) need a store
that supports them: S3 and S3-compatible stores such as RustFS, Azure,
GCS, and `:memory`. The local file system only supports `:create`, and
returns `{:error, :unsupported}` for an update.

An atom error means the store refused a condition or does not support it;
`%Slap.SlateDB.Error{}` means the operation failed.

# `t`

```elixir
@opaque t()
```

# `version`

```elixir
@opaque version()
```

An object's version: opaque, compare it only for equality.

# `delete`

```elixir
@spec delete(t(), String.t(), keyword()) :: :ok | {:error, Slap.SlateDB.Error.t()}
```

Deletes an object. Deleting one that does not exist is not an error.

# `download`

```elixir
@spec download(t(), String.t(), keyword()) ::
  {:ok, {Enumerable.t(), non_neg_integer(), version()} | nil}
  | {:error, Slap.SlateDB.Error.t()}
```

Opens the object at `key` for reading. Returns `{:ok, {chunks, size,
version}}`, where `chunks` is a lazy stream of binaries (reading it
raises `Slap.SlateDB.Error` if the store fails) and `size` is the size of
the whole object, or `{:ok, nil}` if there is no object.

`range: {first, last}` reads only the bytes from `first` to `last`,
inclusive, as an HTTP range does. A `last` past the end of the object
reads to its end. A range that starts past the end returns an error whose
form depends on the store, so check the range against the object's size
first.

# `get`

```elixir
@spec get(t(), String.t(), keyword()) ::
  {:ok, {binary(), version()} | nil} | {:error, Slap.SlateDB.Error.t()}
```

The object's body and version, or `{:ok, nil}` if there is none.

# `list`

```elixir
@spec list(t(), String.t(), keyword()) ::
  {:ok, [String.t()]} | {:error, Slap.SlateDB.Error.t()}
```

The keys under `prefix`, in no particular order.

# `open`

```elixir
@spec open(String.t(), keyword()) :: {:ok, t()} | {:error, Slap.SlateDB.Error.t()}
```

Opens `store` (as for `Slap.SlateDB.open/2`). Keys are relative to `path`
within it (and to the prefix of a store URL). The `:store` option is required.

A `:memory` store is new and empty each time it is opened.

# `put`

```elixir
@spec put(t(), String.t(), binary(), keyword()) ::
  {:ok, version()} | {:error, :conflict | :unsupported | Slap.SlateDB.Error.t()}
```

Writes an object. `:mode` is:

  * `:overwrite` (default) - unconditionally.
  * `:create` - only if there is no object at `key`.
  * `{:update, version}` - only if the object is still at `version`.

Returns `{:ok, version}`, `{:error, :conflict}` when the condition does not
hold, or `{:error, :unsupported}` when the store cannot check it.

# `upload`

```elixir
@spec upload(t(), String.t(), Enumerable.t(), keyword()) ::
  {:ok, version()} | {:error, Slap.SlateDB.Error.t()}
```

Writes the object at `key` from `chunks`, an enumerable of binaries, and
returns `{:ok, version}`. Nothing is visible at `key` until the whole body
is written.

A body smaller than one part (5 MiB) is written with a single PUT; a
larger one as a multipart upload, a few parts at a time, which is aborted
if `chunks` raises or the upload fails. A process that dies during an
upload leaves its parts behind until the store expires them (S3 needs a
lifecycle rule for incomplete multipart uploads).

---

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