AuroraMeter (Aurora Meter v0.3.1)

View Source

Aurora Meter — real-time usage metering, plan entitlements, and Stripe-ready billing primitives for Phoenix.

Count, gate, and bill on the BEAM: increments hit an in-memory ETS counter (microseconds, no database on the hot path), a flusher persists snapshots to Postgres on an interval, and a broadcaster fans live values out over Phoenix.PubSub.

Add it to your host application's supervision tree — it validates configuration at boot and starts the metering runtime:

children = [
  MyApp.Repo,
  {Phoenix.PubSub, name: MyApp.PubSub},
  AuroraMeter,
  MyAppWeb.Endpoint
]

Tenants: the first argument everywhere

Every function takes a tenant first (org in the examples). It is whatever identifies the customer being metered: the organisation or account that owns the subscription, not the individual user. Strings, integers and atoms work as they are ("org_42", 42, :acme); to pass your own struct, configure a module that implements AuroraMeter.Tenant:

defmodule MyApp.Tenant do
  @behaviour AuroraMeter.Tenant
  def to_key(%MyApp.Accounts.Org{id: id}), do: "org_#{id}"
  def to_key(key) when is_binary(key), do: key
end

config :aurora_meter, tenant: MyApp.Tenant

The resolved key must be stable and unique per customer: it is the key for the ETS counters, the persisted counter rows and the PubSub topics. Subscribe a plan (subscribe/2) with the same term you meter with.

The public API: track/4, usage/2, usage_all/1, history/3 (metering); check/2, allowed?/2, entitled?/2, remaining/2, quota/2, reserve/3, with_quota/4 (entitlements); subscribe/2, plan/1, period/1 (plans).

Summary

Functions

Whether check/2 currently returns :ok.

Checks whether tenant may use feature. See AuroraMeter.Entitlements.check/2.

Whether the plan grants access to feature (ignores quota).

Returns tenant's daily usage of feature as a list of %{date: Date.t(), value: integer()} points, one per UTC day, oldest first.

Returns the current billing period for tenant (%{start:, end:, source:}).

Returns tenant's current plan. See AuroraMeter.Entitlements.plan/1.

A dashboard-ready quota snapshot. See AuroraMeter.Entitlements.quota/2.

Remaining quota for a hard-limited feature, or :unlimited.

Atomically reserves usage against the plan. See AuroraMeter.Entitlements.reserve/3.

Atomically reserves qty usage against the plan.

Validates configuration and starts the Aurora Meter runtime supervisor.

Assigns plan_id to tenant locally. See AuroraMeter.Entitlements.subscribe/2.

Records qty usage of feature for tenant in the current period.

Returns tenant's usage of feature in the current period.

Returns a map of feature => value for tenant's warm counters this period.

Returns the Aurora Meter version.

Gates, runs, and meters qty in one step.

Functions

allowed?(tenant, feature)

@spec allowed?(term(), atom()) :: boolean()

Whether check/2 currently returns :ok.

check(tenant, feature)

@spec check(term(), atom()) :: :ok | {:error, :limit_exceeded | :not_entitled}

Checks whether tenant may use feature. See AuroraMeter.Entitlements.check/2.

Advisory: this reads the counter and compares, so two concurrent callers can both see :ok at the cap. To enforce a hard limit atomically use reserve/3 or with_quota/4.

entitled?(tenant, feature)

@spec entitled?(term(), atom()) :: boolean()

Whether the plan grants access to feature (ignores quota).

history(tenant, feature, opts \\ [])

@spec history(term(), atom(), keyword()) :: [AuroraMeter.Storage.history_point()]

Returns tenant's daily usage of feature as a list of %{date: Date.t(), value: integer()} points, one per UTC day, oldest first.

Options: :days (default 30, ending today), or explicit :from / :to dates. Days with no usage are present with a value of 0. Requires :history (on by default) and schema version 2.

AuroraMeter.history(org, :ai_generations, days: 7)
#=> [%{date: ~D[2026-09-01], value: 12}, ..., %{date: ~D[2026-09-07], value: 3}]

period(tenant)

@spec period(term()) :: AuroraMeter.Period.t()

Returns the current billing period for tenant (%{start:, end:, source:}).

plan(tenant)

@spec plan(term()) :: AuroraMeter.Plan.t() | nil

Returns tenant's current plan. See AuroraMeter.Entitlements.plan/1.

quota(tenant, feature)

@spec quota(term(), atom()) :: AuroraMeter.Entitlements.quota()

A dashboard-ready quota snapshot. See AuroraMeter.Entitlements.quota/2.

remaining(tenant, feature)

@spec remaining(term(), atom()) :: non_neg_integer() | :unlimited

Remaining quota for a hard-limited feature, or :unlimited.

reserve(tenant, feature)

@spec reserve(term(), atom()) :: :ok | {:error, :limit_exceeded | :not_entitled}

Atomically reserves usage against the plan. See AuroraMeter.Entitlements.reserve/3.

reserve(tenant, feature, qty)

@spec reserve(term(), atom(), pos_integer()) ::
  :ok | {:error, :limit_exceeded | :not_entitled}

Atomically reserves qty usage against the plan.

start_link(opts \\ [])

@spec start_link(keyword()) :: Supervisor.on_start()

Validates configuration and starts the Aurora Meter runtime supervisor.

Raises NimbleOptions.ValidationError if the :aurora_meter configuration is missing a required key or has a value of the wrong type.

subscribe(tenant, plan_id)

@spec subscribe(term(), atom() | String.t()) ::
  {:ok, AuroraMeter.Schema.Subscription.t()} | {:error, Ecto.Changeset.t()}

Assigns plan_id to tenant locally. See AuroraMeter.Entitlements.subscribe/2.

track(tenant, feature, qty \\ 1, opts \\ [])

@spec track(term(), atom(), integer(), keyword()) :: :ok

Records qty usage of feature for tenant in the current period.

tenant is any term that identifies the customer (a string, an id, or your own struct via a configured AuroraMeter.Tenant); see the module docs.

Runs on the ETS hot path (no database round-trip) unless the feature is durable (opts[:durable] or configured in :durable_features), in which case a raw event row is also written. Options: :durable (boolean), :metadata (map).

usage(tenant, feature)

@spec usage(term(), atom()) :: integer()

Returns tenant's usage of feature in the current period.

usage_all(tenant)

@spec usage_all(term()) :: %{required(atom()) => integer()}

Returns a map of feature => value for tenant's warm counters this period.

version()

@spec version() :: String.t()

Returns the Aurora Meter version.

Examples

iex> is_binary(AuroraMeter.version())
true

with_quota(tenant, feature, fun)

@spec with_quota(term(), atom(), (-> result)) :: {:ok, result} | {:error, term()}
when result: term()

Gates, runs, and meters in one step. See AuroraMeter.Entitlements.with_quota/4.

with_quota(tenant, feature, qty, fun)

@spec with_quota(term(), atom(), pos_integer(), (-> result)) ::
  {:ok, result} | {:error, term()}
when result: term()

Gates, runs, and meters qty in one step.