Scriba.Target.Ecto (Scriba v0.1.0)

Copy Markdown View Source

Postgres-backed read-model target.

Builds an Ecto.Multi from the batch's handler results, appends one per-stream scriba_positions upsert via Scriba.Position.multi/5 per stream represented in the batch, and runs everything in a single Repo.transaction/1. Read-model writes and per-stream cursor advances commit atomically together.

Handler return contract (§4.2)

  • :skip — no Multi op for this event (its stream's cursor still advances if the event was not dedup-skipped; see Pipeline stream_advances).
  • {:insert, schema_struct}Ecto.Multi.insert/3.
  • {:update, schema_module, filter_keyword, [set: keyword]}Ecto.Multi.update_all/4 filtered by filter_keyword.
  • {:delete, schema_module, filter_keyword}Ecto.Multi.delete_all/3.
  • {:multi, %Ecto.Multi{}} — merged into the batch's Multi.

Failure-shape results ({:error, reason} and the internal {:exception, exception, stacktrace} tag produced when a handler raises) do NOT reach apply_handler_result/3. Pipeline partitions them out and hands them to apply_batch/6 as the dead_letters list; this module appends a Scriba.DeadLetter.multi/4 step per dead-letter to the same Ecto.Multi that carries read-model writes and cursor advances. Result: one atomic transaction commits success rows, dead-letter rows, and advanced cursors together.

Usage

target: {Scriba.Target.Ecto, repo: MyApp.Repo}

Summary

Functions

Assembles the Ecto.Multi for a batch without running the transaction. Exposed so callers can inspect the structure of the assembled Multi.

Functions

build_multi(events, handler_results, projection, stream_advances, dead_letters)

@spec build_multi(
  [Scriba.Event.t()],
  [term()],
  %{name: String.t(), version: pos_integer()},
  %{required(String.t()) => non_neg_integer()},
  [{Scriba.Event.t(), term()}]
) :: Ecto.Multi.t()

Assembles the Ecto.Multi for a batch without running the transaction. Exposed so callers can inspect the structure of the assembled Multi.

stream_advances is %{stream_id => max_position} for each stream the batch touches. One position-update step is appended per stream, keyed {:scriba_position, stream_id}.

dead_letters is a list of {event, error} tuples for events whose handler returned {:error, _} or raised. One dead-letter step is appended per failed event, keyed {:scriba_dead_letter, event.id} (the same key shape Scriba.DeadLetter.multi/4 produces).

Order of steps in the assembled Multi:

  1. Read-model ops (one {:scriba_event, event.id} per success event).
  2. Per-stream cursor advances (one {:scriba_position, stream_id}).
  3. Dead-letter inserts (one {:scriba_dead_letter, event.id}).