AuroraMeter.Events (Aurora Meter v1.0.0)

View Source

Reading, streaming and totalling the durable usage facts that AuroraMeter.record/4 writes.

AuroraMeter.track/4 counts; AuroraMeter.record/4 records. A recorded event has a caller-supplied identity, a payload hash, a transaction of its own and a projected total, so a retry after an uncertain write is a duplicate rather than a second charge. This module is the read side of that, plus the one function a host needs when it wraps record/4 in a transaction of its own.

When the host owns the transaction

Inside a host transaction Aurora Meter cannot know when, or whether, the commit happens. So it does the durable work on a savepoint, returns the event with durability: :conditional, and performs no ETS hydration and no PubSub broadcast. The host calls after_commit/1 once, after its own commit:

MyApp.Repo.transaction(fn ->
  {:ok, event, :inserted} = AuroraMeter.record(org, :api_calls, 1, id: id, occurred_at: at)
  ...
  event
end)
|> case do
  {:ok, event} -> AuroraMeter.Events.after_commit([event])
  {:error, _reason} -> :ok
end

Forgetting the call costs nothing durable: the row, its total and the export intent all committed with the host's transaction. Only the in-memory view stays low until the next cold seed, and total/3 is authoritative either way.

Summary

Types

Everything AuroraMeter.correct/4 and replace/4 can go wrong with.

Everything AuroraMeter.record/4 and record_batch/2 can go wrong with.

Functions

Applies the in-memory projection and publishes, for events recorded inside a transaction the host owned.

The durable total and the event count for one feature and period.

Reads one recorded event by the identity its caller gave it.

A lazy stream of recorded events in insertion order.

The durable total for one feature and period, from the active projection generation.

Types

correction_error()

@type correction_error() ::
  {:invalid, [{atom(), atom()}]}
  | {:conflict, AuroraMeter.Event.t()}
  | {:not_found, :original}
  | {:unavailable, term()}
  | {:unsupported, :corrections}

Everything AuroraMeter.correct/4 and replace/4 can go wrong with.

error()

@type error() ::
  {:invalid, [{atom(), atom()}]}
  | {:conflict, AuroraMeter.Event.t()}
  | {:unavailable, term()}
  | {:unsupported, :durable_events}

Everything AuroraMeter.record/4 and record_batch/2 can go wrong with.

Functions

after_commit(event)

@spec after_commit([AuroraMeter.Event.t()] | AuroraMeter.Event.t()) :: :ok

Applies the in-memory projection and publishes, for events recorded inside a transaction the host owned.

Call it once, after the host's own commit, with the events record/4 returned. It holds no state: calling it twice applies the in-memory delta twice, which double counts the advisory value AuroraMeter.usage/2 reads until the next cold seed. It is a no-op for an event that is already durability: :durable, because that one's effects were applied when it committed.

Examples

iex> AuroraMeter.Events.after_commit([])
:ok

count(tenant, feature, period_start)

@spec count(term(), atom(), DateTime.t()) :: %{
  quantity: non_neg_integer(),
  events: non_neg_integer()
}

The durable total and the event count for one feature and period.

Examples

iex> AuroraMeter.Events.count("org_no_such_tenant", :api_calls, ~U[2026-01-01 00:00:00Z])
%{quantity: 0, events: 0}

get(tenant, event_id)

@spec get(term(), String.t()) ::
  {:ok, AuroraMeter.Event.t()} | {:error, :not_found | error()}

Reads one recorded event by the identity its caller gave it.

Examples

iex> AuroraMeter.Events.get("org_no_such_tenant", "no-such-event")
{:error, :not_found}

stream(opts \\ [])

@spec stream(keyword()) :: Enumerable.t()

A lazy stream of recorded events in insertion order.

Ordered by seq, the database-assigned identity column, and not by id: event ids are random v4 UUIDs, so a keyset scan ordered by one can miss a row committed by a transaction that started earlier. One bounded query per chunk, never a Repo.stream inside an implicit transaction, so a consumer may stop at any point without holding a connection open.

Options: :after_seq (default 0), :limit (rows per query, default 1000), :tenant, :feature, :from and :to (on occurred_at, half-open).

Examples

iex> AuroraMeter.Events.stream(tenant: "org_no_such_tenant") |> Enum.take(1)
[]

total(tenant, feature, period_start)

@spec total(term(), atom(), DateTime.t()) :: non_neg_integer()

The durable total for one feature and period, from the active projection generation.

This is the authoritative number. AuroraMeter.usage/2 reads the in-memory counter, which is a view and may lag; this reads what was committed.

Examples

iex> AuroraMeter.Events.total("org_no_such_tenant", :api_calls, ~U[2026-01-01 00:00:00Z])
0