# `Mailglass.Repo`
[🔗](https://github.com/szTheory/mailglass/blob/v2.5.0/lib/mailglass/repo.ex#L1)

Thin facade over the host-configured `Ecto.Repo`.

Mailglass does not own a Repo — the host application does. Every context
module that needs Postgres routes through this facade, which resolves the
real Repo through the validated `Mailglass.Config` runtime façade.

Runtime resolution is deliberate: tests inject a test repo through
`config/test.exs`, host apps inject their Repo through
`config :mailglass, repo: MyApp.Repo`, and neither path requires
recompiling mailglass.

This module re-exports only what mailglass itself uses. Callers that need
lower-level operations call the host Repo directly.

## SQLSTATE 45A01 translation

Every write that touches `mailglass_events` can raise the immutability
trigger (`BEFORE UPDATE OR DELETE` raises SQLSTATE 45A01). The facade
rescues `%Postgrex.Error{}` at every call site and reraises as
`Mailglass.EventLedgerImmutableError` so callers pattern-match a
mailglass-owned error, never the raw Postgrex struct. The translation
is centralized in `translate_postgrex_error/2` — adding new write
functions means wiring the same rescue clause.

## Schema prefix injection (v2.0)

Every delegated read/write injects `prefix: Mailglass.Config.schema()` via
`Keyword.put_new`, routing all operations to the configured Postgres schema
(`"mailglass"` by default, `"public"` for pre-2.0 opt-out). An explicit
caller-supplied `:prefix` wins over the injected default. This is the
FACADE-01/FACADE-02 load-bearing mechanism for schema isolation.

`transact/2`, `multi/2`, and `query!/2` do NOT inject prefix — the prefix
must be threaded per-step in `Ecto.Multi` builders (see `multi_opts/1`).
A future raw caller passing a mailglass table to `query!/2` must qualify
the table inline; the facade cannot rewrite arbitrary SQL.

# `aggregate`
*since 1.4.5* 

```elixir
@spec aggregate(Ecto.Queryable.t(), atom(), atom(), keyword()) :: term() | nil
```

Delegates to the host Repo's `aggregate/4` (with optional opts for schema prefix).

# `all`
*since 0.1.0* 

```elixir
@spec all(
  Ecto.Queryable.t(),
  keyword()
) :: [struct()]
```

Delegates to the host Repo's `all/2`.

# `delete`
*since 0.1.0* 

```elixir
@spec delete(
  struct() | Ecto.Changeset.t(),
  keyword()
) :: {:ok, struct()} | {:error, Ecto.Changeset.t()}
```

Delegates to the host Repo's `delete/2`, translating event-ledger immutability errors.

# `delete_all`
*since 0.1.0* 

```elixir
@spec delete_all(
  Ecto.Queryable.t(),
  keyword()
) :: {non_neg_integer(), nil | [term()]}
```

Delegates to the host Repo's `delete_all/2`. Used by
`Mailglass.Webhook.Pruner` for retention-policy
DELETEs against `mailglass_webhook_events`.

Does NOT translate SQLSTATE 45A01 — that trigger fires only on
`mailglass_events` UPDATE/DELETE, not `mailglass_webhook_events`
(which is intentionally mutable + prunable per CONTEXT  split).

# `get`
*since 0.1.0* 

```elixir
@spec get(Ecto.Queryable.t(), term(), keyword()) :: struct() | nil
```

Delegates to the host Repo's `get/3`.

# `insert`
*since 0.1.0* 

```elixir
@spec insert(
  Ecto.Changeset.t() | struct(),
  keyword()
) :: {:ok, struct()} | {:error, Ecto.Changeset.t()}
```

Delegates to the host Repo's `insert/2`, translating event-ledger immutability errors.

# `multi`
*since 0.1.0* 

```elixir
@spec multi(
  Ecto.Multi.t(),
  keyword()
) :: {:ok, map()} | {:error, atom(), any(), map()}
```

Executes an `Ecto.Multi` against the host-configured repo and returns
the canonical `{:ok, changes}` / `{:error, step, reason, changes}` shape.

Added in   so `Mailglass.Outbound` can compose
two Multis via a public function — `repo/0` is deliberately
private to keep the facade narrow.

Raises `Mailglass.ConfigError{type: :missing}` when `:repo` is not
configured (same path as `transact/2`). Event-ledger-immutability
errors (SQLSTATE 45A01) are translated via the same rescue as the
other write helpers.

NOTE: `multi/2` does NOT inject prefix at the executor level — Ecto.Multi
does not propagate executor opts into inner step SQL. Use `multi_opts/1`
to thread prefix per-step in Multi builder chains.

# `multi_opts`
*since 2.0.0* 

```elixir
@spec multi_opts(keyword()) :: keyword()
```

Returns opts with `prefix: Mailglass.Config.schema()` injected via
`Keyword.put_new`, for use as the step-level opts in `Ecto.Multi` builders
in domain modules (Events, Outbound, Suppression.Escalation).

`Ecto.Multi` does NOT propagate the transaction/executor opts into inner
step SQL. Every builder step that writes a mailglass table must carry its
own `:prefix`; call `multi_opts/1` (or `multi_opts()`) to build that opts
list rather than constructing it by hand.

An explicit caller-supplied `:prefix` wins over the injected default
(`Keyword.put_new` semantics).

## Examples

    Ecto.Multi.new()
    |> Ecto.Multi.insert(:delivery, changeset, Repo.multi_opts())
    |> Ecto.Multi.insert_all(:events, Event, rows, Repo.multi_opts(on_conflict: :nothing))

# `one`
*since 0.1.0* 

```elixir
@spec one(
  Ecto.Queryable.t(),
  keyword()
) :: struct() | nil
```

Delegates to the host Repo's `one/2`.

# `query!`
*since 0.1.0* 

```elixir
@spec query!(String.t(), [term()]) :: term()
```

Delegates to the host Repo's `query!/2`. Raw passthrough — no SQLSTATE
translation.

Intentionally does NOT rescue `%Postgrex.Error{}`: the  webhook
ingest Multi calls `query!/2` from inside `Repo.transact/1`
to run `SET LOCAL statement_timeout = '2s'` + `SET LOCAL lock_timeout
= '500ms'`. Those `SET LOCAL` statements never produce SQLSTATE
45A01 — the immutability trigger fires only on UPDATE/DELETE against
`mailglass_events` rows — so translation would add latency for no gain
and muddle the semantics. Callers that need the trigger's translated
error use `insert/2`, `update/2`, `delete/2`, `transact/1`, or
`multi/1` instead.

NOTE: the facade cannot rewrite arbitrary SQL. A future raw caller
that touches a mailglass table must schema-qualify the table inline
(e.g. `"mailglass.mailglass_events"`) — the current two callers both
run schema-agnostic `SET LOCAL` statements.

# `transact`
*since 0.1.0* 

```elixir
@spec transact(
  (-&gt; {:ok, any()} | {:error, any()}),
  keyword()
) :: {:ok, any()} | {:error, any()}
```

Delegates to `c:Ecto.Repo.transact/2` on the host-configured Repo.

Preferred over `c:Ecto.Repo.transaction/2` because Ecto 3.13+ `transact/2`
accepts a zero-arity function that returns `{:ok, result}` or
`{:error, reason}` and rolls the transaction back on `:error` without
requiring `Ecto.Repo.rollback/1`.

Raises `Mailglass.ConfigError` of type `:missing` when `:repo` is not
configured. Raises `Mailglass.EventLedgerImmutableError` when the
mailglass_events immutability trigger fires (SQLSTATE 45A01).

## Examples

    Mailglass.Repo.transact(fn ->
      {:ok, inserted} = Mailglass.Events.append(multi)
      {:ok, inserted}
    end)

# `update`
*since 0.1.0* 

```elixir
@spec update(
  Ecto.Changeset.t(),
  keyword()
) :: {:ok, struct()} | {:error, Ecto.Changeset.t()}
```

Delegates to the host Repo's `update/2`, translating event-ledger immutability errors.

---

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