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
endForgetting 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
@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.
@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
@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
@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}
@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}
@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)
[]
@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