Replicant.Config (Replicant v0.1.0)

Copy Markdown View Source

Validates Replicant.start_link/1 options and enforces the go-forward-only start guard: a :state_mirror sink resuming from an empty checkpoint without go_forward_only: true would silently deliver partial data from the slot's creation point, so it is refused at start.

Pure — no processes, no connection. Replicant.start_link/1 (the facade) calls validate/1 then guard/1 before spawning a pipeline.

Summary

Functions

The go-forward-only start guard. Refuses ONLY the exact unsafe triple — a :state_mirror sink (default kind) with a definitively empty checkpoint ({:ok, nil}) and go_forward_only: false. An :append_log sink, a non-nil checkpoint, go_forward_only: true, OR a checkpoint READ fault (raise/exit/ {:error, _}) all pass — the read-fault path is deliberately fail-open (a re-dispatched already-persisted txn is deduped by the idempotent sink; only a definitive empty checkpoint proves partial-delivery risk). snapshot: true ALSO bypasses the empty-checkpoint refusal (alongside go_forward_only: true): the backfill IS the safe seed, so an empty checkpoint is the expected first-run state, not a partial-delivery risk.

Validate raw start_link options into a normalised config map, or return a plain-atom error: :config_invalid (missing/mis-shaped connection or opts), :invalid_identifier (slot/publication fails the Postgres-identifier allowlist), :invalid_sink (sink is not a module exporting the two mandatory callbacks), :conflicting_start_mode (go_forward_only: true AND snapshot: true — mutually exclusive start intents), :snapshot_unsupported (snapshot: true but the sink is missing one/both snapshot callbacks), :batch_unsupported (batch_delivery is set but the sink does not implement handle_batch/1).

Types

t()

@type t() :: %{
  optional(:batch) => keyword() | nil,
  optional(:batch_delivery) => keyword() | nil,
  optional(:streaming) => keyword() | nil,
  connection: keyword(),
  slot_name: String.t(),
  publication: String.t(),
  sink: module(),
  go_forward_only: boolean(),
  snapshot: boolean(),
  max_inflight_lag: pos_integer(),
  checkpoint_store: keyword() | nil
}

Functions

guard(map)

@spec guard(t()) :: :ok | {:error, :go_forward_required}

The go-forward-only start guard. Refuses ONLY the exact unsafe triple — a :state_mirror sink (default kind) with a definitively empty checkpoint ({:ok, nil}) and go_forward_only: false. An :append_log sink, a non-nil checkpoint, go_forward_only: true, OR a checkpoint READ fault (raise/exit/ {:error, _}) all pass — the read-fault path is deliberately fail-open (a re-dispatched already-persisted txn is deduped by the idempotent sink; only a definitive empty checkpoint proves partial-delivery risk). snapshot: true ALSO bypasses the empty-checkpoint refusal (alongside go_forward_only: true): the backfill IS the safe seed, so an empty checkpoint is the expected first-run state, not a partial-delivery risk.

validate(opts)

@spec validate(keyword()) ::
  {:ok, t()}
  | {:error,
     :config_invalid
     | :invalid_identifier
     | :invalid_sink
     | :conflicting_start_mode
     | :snapshot_unsupported
     | :batch_unsupported}

Validate raw start_link options into a normalised config map, or return a plain-atom error: :config_invalid (missing/mis-shaped connection or opts), :invalid_identifier (slot/publication fails the Postgres-identifier allowlist), :invalid_sink (sink is not a module exporting the two mandatory callbacks), :conflicting_start_mode (go_forward_only: true AND snapshot: true — mutually exclusive start intents), :snapshot_unsupported (snapshot: true but the sink is missing one/both snapshot callbacks), :batch_unsupported (batch_delivery is set but the sink does not implement handle_batch/1).