Mead.Deck.EventLog (mead v0.1.1)

Copy Markdown View Source

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.

Summary

Functions

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.

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

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.

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

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.

Types

t()

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

Functions

append(log, event, options \\ [])

@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(event_log)

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

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

compact(log, keep)

@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(event_log)

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

delete(directory, session_id)

@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(event_log)

@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(event_log)

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

open(directory, session_id)

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

read_after(event_log, sequence)

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