# `Mead.Deck.EventLog`
[🔗](https://github.com/albertosca/mead/blob/main/lib/mead/deck/event_log.ex#L1)

The append-only log of the sequenced events of one deck session, one file `<id>.events` per
session beside the session history, on the same `Mead.Session.Record` framing (length and
checksum per record, a torn last record cut on open).

The value is plain data: events are held in memory, newest first, so `read_after/2` never
touches the disk, and every `append/2` opens the file, writes, syncs and closes it, the way
`Mead.Session.Store.append/3` does. The whole log is read on `open/2`. `compact/2` keeps it
bounded: the oldest events are dropped, except the ones the channel rebuilds its permission
state from, the latest `policy.updated` and every remembered tool rule still in force. `first_sequence/1` is where a replay can start.

With a `nil` directory the log lives in memory only, for sessions that have no storage.
Sequences must strictly increase; an event that does not carry a larger sequence than the last
one is refused before anything is written.

# `t`

```elixir
@type t() :: %Mead.Deck.EventLog{
  count: non_neg_integer(),
  events: [map()],
  last_sequence: non_neg_integer(),
  path: Path.t() | nil
}
```

# `append`

```elixir
@spec append(t(), map(), keyword()) :: {:ok, t()} | {:error, :out_of_order | term()}
```

Appends `event`. With `sync: false` the file is written and closed but not forced to disk, for
the many small events (streamed text) whose loss in a power cut costs nothing the next synced
event does not make up for: that sync flushes everything written before it.

# `close`

```elixir
@spec close(t()) :: :ok
```

Present for symmetry with the file lifecycle; the log holds no open handle.

# `compact`

```elixir
@spec compact(t(), pos_integer()) :: {:ok, t()} | {:error, term()}
```

Drops the oldest events until `keep` remain, plus the latest `policy.updated` and the live
remembered-tool rules (`permission.resolved` with `remember`, not undone by a later
`permission.forgotten`) when they would otherwise go. The new file is written beside the old one and renamed over it, so a crash leaves
either the whole old log or the whole new one. A log within `keep` is returned unchanged.

# `count`

```elixir
@spec count(t()) :: non_neg_integer()
```

# `delete`

```elixir
@spec delete(String.t(), term()) :: :ok | {:error, term()}
```

Removes the file of a session; a missing file or an invalid id is not an error.

# `first_sequence`

```elixir
@spec first_sequence(t()) :: non_neg_integer()
```

The lowest sequence a replay can start from: 1 for a log that was never compacted, and the first
sequence of the retained tail after a compaction. The kept `policy.updated` older than the tail
is not part of it.

# `last_sequence`

```elixir
@spec last_sequence(t()) :: non_neg_integer()
```

# `open`

```elixir
@spec open(String.t() | nil, term()) ::
  {:ok, t()} | {:error, :invalid_session_id | :corrupt | term()}
```

# `read_after`

```elixir
@spec read_after(t(), non_neg_integer()) :: [map()]
```

---

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