# `Slap.SlateDB.ObjectStore`
[🔗](https://github.com/mjrusso/slap/blob/slap_slatedb-v0.3.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, `:memory`, and local stores. Local mutations share one OS lock per
canonical store root, including across handles and processes. On a
filesystem without microsecond modification times, local updates return
`:unsupported`.

Local stores sync file contents before PUT or upload completion returns success.
On Unix, they also sync directory changes for object creation and deletion.
This can increase latency. Persistence requires the filesystem and device to
honor sync requests; other platforms do not sync directory entries.

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

On a local store, `get/3` can briefly return an earlier version of `key`
just after an upload of it completes; conditional updates are not affected.
Use `put/4` for objects whose versions you compare.

---

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