Docket.Backend.EventStore behaviour (docket v0.1.2)

Copy Markdown View Source

Persistence contract for append-only run events.

Event retention is a backend policy. Since 0.1.2 lifecycle orchestration appends events through Docket.Backend.TransitionStore; this focused write remains temporarily for the 0.1.x compatibility adapter.

Summary

Callbacks

Appends retained events for run_id in sequence order.

Reads one retained event by its assigned positive sequence.

Reads the highest-sequence retained event for a run.

Reads a page of committed retained events for run_id.

Types

ctx()

@type ctx() :: Docket.Backend.ctx()

scope()

@type scope() :: Docket.Backend.scope()

Callbacks

append_events(ctx, scope, run_id, events)

This callback is deprecated. lifecycle event composition is deprecated; append through TransitionStore.
@callback append_events(
  ctx(),
  scope(),
  run_id :: String.t(),
  events :: [Docket.Event.t()]
) :: :ok | {:error, term()}

Appends retained events for run_id in sequence order.

The operation must be idempotent for an already-persisted {run_id, seq} containing the same event. Conflicting content for that key is an error. Every event must belong to run_id; a backend must reject a mismatched event rather than storing it under the supplied run.

Event identities are assigned before this callback. The store never derives a sequence from the maximum already-stored sequence, never substitutes the run's checkpoint sequence, and accepts gaps left by persistence filtering. In particular, :checkpoint_committed is an ordinary assigned event at this boundary.

For a non-empty append, scope is checked through the owning run and a tenant mismatch is reported as {:error, :not_found}. An empty event list validates the scope value but succeeds without a run lookup or storage change.

fetch_event(ctx, scope, run_id, seq)

@callback fetch_event(
  ctx(),
  scope(),
  run_id :: String.t(),
  seq :: pos_integer()
) :: {:ok, Docket.Event.t()} | {:error, :not_found} | {:error, term()}

Reads one retained event by its assigned positive sequence.

Ownership is enforced through the owning run. An unknown or out-of-scope run and an absent or pruned sequence all return {:error, :not_found}. A present corrupt row returns a typed {:error, %Docket.Error{type: :corrupt_event_row}} and is never reported as absent.

seq is trusted to be pre-validated by the caller as a positive integer.

fetch_latest_event(ctx, scope, run_id)

@callback fetch_latest_event(
  ctx(),
  scope(),
  run_id :: String.t()
) :: {:ok, Docket.Event.t() | nil} | {:error, :not_found} | {:error, term()}

Reads the highest-sequence retained event for a run.

Ownership is enforced through the owning run. An unknown or out-of-scope run returns {:error, :not_found}. A visible run with no retained events returns {:ok, nil}, including when its complete history has been pruned. A present corrupt latest row returns a typed {:error, %Docket.Error{type: :corrupt_event_row}} and is never skipped in favor of an older event.

list_events(ctx, scope, run_id, opts)

@callback list_events(
  ctx(),
  scope(),
  run_id :: String.t(),
  opts :: %{after_seq: non_neg_integer(), limit: pos_integer()}
) :: {:ok, Docket.EventPage.t()} | {:error, :not_found} | {:error, term()}

Reads a page of committed retained events for run_id.

Events are returned in ascending sequence order, restricted to sequences greater than opts.after_seq and limited to at most opts.limit rows. This keyset scan skips pruned and persistence-filtered sequences, so a page and the retention bounds are not promised contiguous.

Ownership is enforced through the owning run: a wrong tenant and an unknown run both report {:error, :not_found}. The page rows and the returned retention bounds are observed from one consistent snapshot. A corrupt or undecodable stored row is a typed error and is never silently skipped: a corrupt event row returns {:error, %Docket.Error{type: :corrupt_event_row}}, while a corrupt owning-run row propagates the same way as run reads, by raising the typed error.

Options are trusted to be pre-validated by the caller: after_seq is a non-negative integer and limit is a positive integer. A backend may assert these rather than validate them.