import { type LedgerCoverage, type PlanCatalog } from "./plan-model.js"; /** Which allowance paid for a call. */ export type FundingSource = /** The org-wide included window. */ "pool" /** The caller's seat pack. */ | "pack" /** Prepaid balance — the only one that moves money. */ | "wallet"; export interface UsageEvent { orgId: string; customerId: string; action: string; cost: number; funded: FundingSource; caller?: { kind: string; id?: string; }; /** Epoch ms. Defaults to now. */ at?: number; /** Makes a retry a no-op where the backing store supports it. */ idempotencyKey?: string; } export interface UsageQuery { orgId: string; customerId: string; /** Epoch ms, inclusive. */ start: number; /** Epoch ms, exclusive. Omit for "up to now". */ end?: number; filter?: { callerKind?: string; callerId?: string; }; /** * Which funding sources this window can actually contain — an optimisation * hint, never a filter on the answer. * * A ledger that reads two sources (the scope ledger reads INCLUDED usage from a * meter and WALLET-funded usage from the debits) can skip the leg that must * return 0. The caller knows this and the ledger cannot: whether a per-caller * window can hold wallet-funded usage is a property of the plan * (`exhaustedPolicy`), and whether it can hold included usage is * `capCovers` — an `api` caller on a `covers: "users"` plan draws no included * allowance at all, so reading its meter is a guaranteed 0. * * Omitted means BOTH, which is the safe reading: a ledger must never invent a * restriction the caller did not state, because skipping a source that could * contribute under-reports, and under-reporting reads as generosity and refuses * no one. `resolveAllowance` fills it in for every read it issues. */ sources?: { included?: boolean; wallet?: boolean; }; } export interface UsageLedger { record(event: UsageEvent): Promise; /** Summed cost in [start, end). */ total(query: UsageQuery): Promise; /** * Several windows at once, in as few requests as the backend allows. * * Optional, and purely an optimisation: the answer must equal * `Promise.all(queries.map(total))` element for element. It exists because a * plan declares several windows over the SAME caller — a monthly pack and a * weekly limit, say — and both a Stripe meter (via `value_grouping_window`) and * a balance-transaction walk can serve all of them from one pass. * * Callers should not reach for this directly; `stripeScopeUsageLedger` batches * per tick internally, so ordinary `total` calls issued together already * collapse. */ totals?(queries: readonly UsageQuery[]): Promise; /** * Which windows this ledger can count (see `LedgerCoverage`). * * Optional, and omitting it means "not stated" rather than "counts nothing": * the config checks skip a ledger that doesn't declare, exactly as they skip a * caller who passes no ledger at all. Every implementation shipped here declares * it, which is what lets one static check catch a plan whose included window the * wired ledger cannot see — the failure that otherwise reads 0% forever. */ readonly covers?: LedgerCoverage; } /** * The ledger this library has always had: the debits themselves. * * `record` is a no-op — `deductCredits` already wrote the row — and `total` is * `usageSince`. Correct and free for any plan where every call is wallet-funded, * which is every plan that existed before included windows, so it stays the * default. It cannot see pool- or pack-funded usage, because that usage moves no * money and therefore writes no transaction: a plan with an included window * needs the meter ledger below. */ export declare function stripeBalanceUsageLedger(): UsageLedger; /** Default name of the Stripe Billing Meter events are reported to. */ export declare const USAGE_METER_EVENT = "billing_tools_usage"; /** * Stripe Billing Meters: purpose-built usage counting, aggregated server-side. * * Chosen over reading balance transactions for two reasons beyond the money/usage * split. It is idempotent by construction (`identifier`), and a summary is ONE * call for any window — `usageSince` walks pages newest-first until it passes the * start, so an annual window would page through a year of transactions on the hot * path of every metered execution. * * The trade: summaries lag aggregation by a few seconds, so a hard cap can * overshoot slightly. Irrelevant against a pool of a million credits; it is why * seat packs, which are small, can keep using the exact balance path. * * The meter provisions itself on first use, like plan prices. `ensureMeters` is * the same call made eagerly, for a deploy step that wants it to exist before a * request does. */ export declare function stripeMeterUsageLedger(opts?: { eventName?: string; }): UsageLedger; /** * The meter id for `eventName`, creating the meter when it doesn't exist. * * NEVER THROWS — the same rule `defaultPaymentMethodConfig` follows, and for the * same reason: this is reached from `ledger.record` on the hot path of every * metered call, so a key without permission to create a meter must not take the * product down. It degrades to `null` (windows read 0) and says so once, loudly, * because that state is otherwise indistinguishable from no usage. */ export declare function meterIdFor(eventName: string, opts?: { create?: boolean; displayName?: string; }): Promise; /** * Ensure the usage meter exists, eagerly. Idempotent, like `ensurePlans`. * * No longer a prerequisite — the ledger provisions the meter on first use — but * still worth calling from a deploy step or a setup script when you'd rather the * meter existed before a customer's request pays for creating it, or want the * failure to surface in a deploy log instead of a warning. Unlike the lazy path * this one THROWS, because a setup script wants to know. * * Aggregates `sum` over the reported `value`, which is the credit cost. */ export declare function ensureMeters(opts?: { eventName?: string; displayName?: string; }): Promise<{ meterId: string; created: boolean; }>; /** Forget resolved meter ids — for a test, or after archiving one. Also clears the * once-per-process complaint above, so a key that gains permission (or a test that * wants to see it again) gets a fresh line rather than silence. */ export declare function invalidateMeters(): void; export declare function stripeUsageLedger(opts?: { eventName?: string; /** * Where a per-CALLER window is read from. Default: the balance ledger (exact, * per-member, wallet-funded only). `stripeScopeUsageLedger()` belongs here for * a plan whose per-member window is INCLUDED — it sees both halves. */ perCaller?: UsageLedger; /** * Where an ORG-wide window is read from. Default: the Stripe meter, which is * the right answer today. * * A seam rather than a constant because this is the leg most likely to change: * Stripe's Meter Usage Analytics API can answer the same question grouped by a * dimension, and when it leaves preview it belongs here — at which point the * per-caller leg can point at it too and the store disappears entirely. */ orgWide?: UsageLedger; /** * What to do when a window cannot be READ — a Stripe 429, an outage, a * permission that was revoked. * * This needs a policy because the two legs used to disagree by accident, and * both answers were wrong. A per-caller read caught its own error and returned * 0, so a member who had spent their whole pack was allowed through; an * org-wide read propagated, so the same rate limit 500'd the request instead. * Same cause, opposite outcomes, neither chosen. * * - `"last-known"` (default) serves the last value this ledger read * successfully for that window, and falls back to `"zero"` when there is * none. Stale but bounded, and it degrades gracefully exactly when the * account is busiest. * - `"zero"` is the old per-caller behaviour, made explicit: the window does * not apply and nothing is refused. Choose it only if over-serving is * cheaper for you than refusing. * - `"throw"` refuses the call rather than guessing. Correct where usage is * expensive and availability is not the priority; it makes a Stripe outage * an outage for metered calls. * * Every one of them reports a `UsageFault` — see `onUsageFault`. The point is * not which is picked, it is that the choice is visible. */ onReadFailure?: "last-known" | "zero" | "throw"; }): UsageLedger; export declare function defaultUsageLedger(): UsageLedger; /** * Warn at boot about the plans whose included windows this ledger cannot count. * * Worth a line in a deploy log because the failure is silent and looks like * generosity: the window reads 0, so nothing is ever refused. A ledger that * declares no `covers` (a consumer's own) says nothing here — see `UsageLedger`. * * Called from `createMeter`, i.e. once per composition rather than per metered * call, so it needs no de-duplication: a second line means a second meter was * built, which is itself worth seeing. */ export declare function warnLedgerGaps(plans: PlanCatalog, ledger: UsageLedger): void; //# sourceMappingURL=usage-ledger.d.ts.map