import type { BudgetReservationChange } from '../lifecycle/reservations.js'; import type { TenantUsageStore } from '../server/tenancy.js'; import type { BudgetReservationRecord } from '../types/lifecycle.js'; import type { MigrationResult, SchemaMigration } from '../utils/schema-migrations.js'; import { type PostgresLikeClient } from './client.js'; import type { PostgresMigrateOptions } from './migrations.js'; /** Options for `PostgresTenantUsage`. */ export interface PostgresTenantUsageOptions { /** Table name, optionally schema-qualified. Defaults to `nexus_tenant_usage`. */ table?: string; /** Clock for slot leases and spending periods, in epoch milliseconds. Defaults to `Date.now`. */ now?: () => number; } /** The schema, as statements: every migration's, in order. */ export declare function tenantUsageMigration(options?: Pick): string[]; /** * The versioned schema, which `migrate()` and `nexus db migrate` apply. Version 1 is the usage table; * version 2 adds the budget reservations table beside it, `_reservations`. */ export declare function tenantUsageMigrations(options?: Pick): SchemaMigration[]; /** * Tenant usage in Postgres, shared by every replica, for a deployment that already runs Postgres * and not Redis. Pass it to `tenantLimits({ usage })`. * * Each key is one row: a tenant's active slots as a JSON map from run to lease expiry, or its spending * in the current period. Every step is a single statement on that row, and Postgres applies * concurrent updates of one row one at a time, re-checking each against the newest version. Two * replicas admitting runs for one tenant at the same moment therefore cannot both take its last slot. */ export declare class PostgresTenantUsage implements TenantUsageStore { private readonly client; private readonly options; private readonly table; private readonly reservations; private readonly now; private purgedAt; constructor(client: PostgresLikeClient, options?: PostgresTenantUsageOptions); /** Creates the table, or brings it up to date. */ migrate(options?: PostgresMigrateOptions): Promise; /** Takes a slot for `member` unless `max` live slots are held. Holding one already counts as taken. */ acquire(key: string, member: string, max: number, ttlMs: number): Promise; /** Frees a slot. Freeing one that is not held does nothing. */ release(key: string, member: string): Promise; /** Live slots held under a key. */ count(key: string): Promise; /** Adds to a total that resets at `resetAt`, and resolves to the new total. */ add(key: string, amount: number, resetAt: number): Promise; /** A total, or 0 once its period has reset. */ total(key: string): Promise; /** Records a budget reservation and adds it to its total, in one statement. */ hold(record: BudgetReservationRecord): Promise; /** * Moves a budget reservation and its total together, in one statement, if it stands where * `change.from` allows. The row is locked as it is read, so of two processes settling one * reservation at once, the second finds it moved and changes nothing. */ settle(id: string, change: BudgetReservationChange): Promise; /** Extends a reserved budget reservation's lease. */ renew(id: string, leasedUntil: number): Promise; /** * Reserved budget reservations whose lease lapsed by `now`, soonest lapsed first. At most once a * minute it also deletes what no report can change any more: reservations a day past their period, * and those settled, released, or expired an hour ago. */ lapsed(now: number, limit: number, operation?: string): Promise; /** One budget reservation, while it is kept. */ reservation(id: string): Promise; }