AuroraMeter.Entitlements (Aurora Meter v0.3.1)

View Source

Plan resolution and the entitlement gate.

Semantics (see plan.md D12): a :hard limit blocks at its cap; a :metered feature is always allowed (overage is billed); a {:feature, false} is denied; an undeclared feature is permissive. with_quota/4 reserves atomically so hard limits are correct under concurrency, releasing the reservation if the wrapped function raises.

Plans resolve through AuroraMeter.Subscriptions (cached), and only a subscription in an entitled status (AuroraMeter.Schema.Subscription.entitled_statuses/0) grants its plan; anything else gets the default plan.

Summary

Types

Result of an entitlement check.

A dashboard-ready view of one feature's quota. kind is :hard, :metered, :boolean or :undeclared; limit is set for hard caps, included for metered allowances; percent is used relative to whichever applies (nil when neither does).

Functions

Whether check/2 currently returns :ok.

Checks whether tenant may use feature right now.

Whether the tenant's plan grants access to feature at all (ignores quota).

Returns the plan for tenant: its subscription's plan when the subscription is in an entitled status, else the default plan.

A dashboard-ready snapshot of feature for tenant: kind, usage, cap or allowance, remaining, overage, percentage and the current period.

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

Atomically reserves qty of feature against the plan (increments the counter).

Assigns plan_id to tenant locally (no billing provider).

Gates, runs, and meters in one atomic step.

Types

check_result()

@type check_result() :: :ok | {:error, :limit_exceeded | :not_entitled}

Result of an entitlement check.

quota()

@type quota() :: %{
  feature: atom(),
  kind: :hard | :metered | :boolean | :undeclared,
  enabled: boolean(),
  used: integer(),
  limit: non_neg_integer() | nil,
  included: non_neg_integer() | nil,
  unit_price: number() | nil,
  remaining: non_neg_integer() | :unlimited,
  overage: non_neg_integer(),
  percent: non_neg_integer() | nil,
  period: AuroraMeter.Period.t()
}

A dashboard-ready view of one feature's quota. kind is :hard, :metered, :boolean or :undeclared; limit is set for hard caps, included for metered allowances; percent is used relative to whichever applies (nil when neither does).

Functions

allowed?(tenant, feature)

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

Whether check/2 currently returns :ok.

check(tenant, feature)

@spec check(term(), atom()) :: check_result()

Checks whether tenant may use feature right now.

entitled?(tenant, feature)

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

Whether the tenant's plan grants access to feature at all (ignores quota).

plan(tenant)

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

Returns the plan for tenant: its subscription's plan when the subscription is in an entitled status, else the default plan.

quota(tenant, feature)

@spec quota(term(), atom()) :: quota()

A dashboard-ready snapshot of feature for tenant: kind, usage, cap or allowance, remaining, overage, percentage and the current period.

remaining(tenant, feature)

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

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

reserve(tenant, feature, qty \\ 1)

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

Atomically reserves qty of feature against the plan (increments the counter).

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 (no billing provider).

with_quota(tenant, feature, fun)

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

Gates, runs, and meters in one atomic step.

Reserves qty of feature; if allowed, runs fun and returns {:ok, result} (the reservation is the usage). If the reservation is denied, returns {:error, reason} without running fun. If fun raises, the reservation is released and the error re-raised.

with_quota(tenant, feature, qty, fun)

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