# `Slap.Yjs.Store`
[🔗](https://github.com/mjrusso/slap/blob/slap_yjs-v0.4.0/slap_yjs/lib/slap/yjs/store.ex#L1)

Yjs documents as `Slap.SnapshotLog`s, in the reference `y-durable-streams`
server's layout, so its HTTP clients can later read the same documents:

    <prefix>/yjs/<service>/docs/<doc>/.updates                     application/octet-stream
    <prefix>/yjs/<service>/docs/<doc>/.index                       application/json
    <prefix>/yjs/<service>/docs/<doc>/.snapshots/<offset>_snapshot application/octet-stream

Each entry in `.updates` is a batch of lib0-framed updates
(`Slap.Yjs.Frame`), and a snapshot is an update holding the document's
state. `<prefix>` defaults to `/v1/stream` and can be set per call.
Pass `prefix:` and `cluster:` to each operation to select another store.
For `load/2`, `:timeout` bounds each `Slap.SnapshotLog.next/3` call,
including its waits and retries. For other operations it bounds each
underlying Streams call. Define a cluster module with
`use Slap.Streams.Cluster, otp_app: :my_app` and run one Streams cluster
per VM.

Store operations return `{:error, {:bad_request, reason}}` for invalid
document ids, frames and offsets. Invalid `:prefix`, `:cluster` or
`:timeout` settings and unknown options raise `ArgumentError`. The
`base/2` and `path/3` path builders raise for invalid document ids.

# `doc`

```elixir
@type doc() :: {String.t(), String.t()}
```

A document: `{service, name}`, each a non-empty path segment.

# `error`

```elixir
@type error() :: Slap.SnapshotLog.error() | {:bad_frames, term()}
```

# `loaded`

```elixir
@type loaded() :: %{
  snapshot: binary() | nil,
  snapshot_offset: non_neg_integer() | nil,
  updates: [binary()],
  offset: non_neg_integer()
}
```

# `append`

```elixir
@spec append(doc(), binary(), keyword()) ::
  {:ok, non_neg_integer()} | {:error, error()}
```

Appends a batch of frames (`Slap.Yjs.Frame`); returns the offset after it.

# `base`

```elixir
@spec base(doc(), keyword()) :: String.t()
```

The document's base path: its `Slap.SnapshotLog`.

# `delete_doc`

```elixir
@spec delete_doc(doc(), keyword()) :: :ok | {:error, error()}
```

Deletes the document for good: later calls on it return `{:error,
:deleted}`, and its id cannot be used again (`Slap.SnapshotLog.delete/1`).

# `load`

```elixir
@spec load(doc(), keyword()) :: {:ok, loaded()} | {:error, error()}
```

Opens the document (creating it if needed) and reads it: the current
snapshot, and every update after it, up to the durable tail (`:offset`,
where the next append goes).

# `path`

```elixir
@spec path(doc(), :updates | :index | {:snapshot, non_neg_integer()}, keyword()) ::
  String.t()
```

The path of the document's `.updates`, `.index` or a snapshot stream.

# `read_snapshot`

```elixir
@spec read_snapshot(doc(), non_neg_integer(), keyword()) ::
  {:ok, binary()} | {:error, error()}
```

The snapshot at `offset`.

# `snapshot`

```elixir
@spec snapshot(doc(), non_neg_integer(), binary(), keyword()) ::
  :ok | {:error, error()}
```

Publishes the document's state as a snapshot at `offset`
(`Slap.SnapshotLog.snapshot/4`). The state may hold more than the updates
before `offset` (pending and local updates): applying an update twice
changes nothing. `:timeout` bounds each underlying Streams call.

# `snapshots`

```elixir
@spec snapshots(doc(), keyword()) ::
  {:ok, [Slap.SnapshotLog.snapshot_info()]} | {:error, error()}
```

The document's stored snapshots, oldest first (`Slap.SnapshotLog.snapshots/1`).

# `tail`

```elixir
@spec tail(doc(), keyword()) :: {:ok, non_neg_integer()} | {:error, error()}
```

The durable tail of the document's updates.

---

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