A persisted per-account meter. Every method is safe to call from several processes at once against one store: two reservations cannot both take the last unit, and a reservation settles exactly once.

Implements

Constructors

Methods

  • Creates the tables and their indexes unless the tables answer a read already. On tables that answer it reads the store's catalogue for the meter's index names (pg_indexes on Postgres, sqlite_master on SQLite) and runs the CREATE INDEX IF NOT EXISTS statement of each index the tables lack, so a store an earlier release made gains the indexes it lacks, and a store that holds them all runs no index statement: on Postgres each one takes a SHARE lock on the reservations table, which holds back the table's writes, before it looks for the index's name. An index statement that fails is reported to SqlSpendMeterOptions.onWarning, and the meter uses the tables as they are. Called before the first statement; safe to call again. Does nothing with ensureSchema: false.

    Returns Promise<void>

  • Settles the expired reservations, at most limit a pass (default 100), the earliest expiry first. With resolveExpired the host decides each one: consumed, released, or deferred, which keeps it reserved and counted with its evidence and its next check on its row. Without it an ordinary reservation settles as resolveUnknown and unknownAfterLease say, and one made with requireFence, or deferred before, stays reserved and counts as pending; such a pass reads the ordinary reservations into its page first and the fenced holds only into the room they leave, each in expiry order, so the fenced holds it leaves never take the place of one it can settle, and no pass sorts every due row to find its page.

    Each decision is written to the reservation as the pass read it, in one conditional statement: the same attempt, still reserved, still past its lease at now and, for a settlement without resolveExpired, still unfenced. So no decision lands on a newer attempt of the operation, and none replaces a lease that a heartbeat renewed, or a next check after now that another pass deferred it to, while the resolver was asked; on a meter without resolveExpired a settlement also never takes a reservation an opted-in pass fenced meanwhile. The pass drops such a decision.

    Parameters

    • opts: {
          now?: number;
          limit?: number;
      } = {}
      • Optional now?: number
      • Optional limit?: number

    Returns Promise<SpendReconcileResult>

  • Writes the current period's allowance at once after a plan change; the units used stay. A store that reads the product's allowance at every reservation takes the product's answer again at the next one.

    Parameters

    • accountId: string
    • allowance: number
    • now: number = ...

    Returns Promise<void>

  • Deletes the reservations settled before before and, with a window, the account rows not updated since then that hold nothing reserved, at most limit of each (default 1,000): the retention a product's terms set. It deletes from this meter's own two tables, across every account in them, so a meter under a prefix of its own keeps its own retention. A reservation still reserved is never deleted, nor is a period meter's period row, which holds the period's count. With a window, a before at least one window back keeps every unit the window counts. An operation retried after its row was purged counts again, so before lies past the time a retry can come.

    Parameters

    • opts: {
          before: number;
          limit?: number;
      }
      • before: number
      • Optional limit?: number

    Returns Promise<SpendPurgeResult>

  • Runs reconcile every interval until stopReconciler. A failed pass is retried on the next tick.

    Parameters

    • intervalMs: number = 15_000
    • Optional onError: ((e) => void)
        • (e): void
        • Parameters

          • e: unknown

          Returns void

    Returns void