import { accountDb } from "../scoped-db"; import { randomId } from "../crypto"; import type { MeterId } from "~/lib/meters"; import { periodOf } from "./record"; /** Gauge holds outlive the write until the storage rollup (every 5 minutes) has counted the new bytes. */ export const GAUGE_HOLD_TTL_MS = 10 * 60 * 1000; /** * BILL-P9: storage gauges only move when the cron rolls them up, so a read-only check let concurrent writes * each see the same headroom. This holds `quantity` in one conditional INSERT (atomic in the account's object) * that counts every unexpired hold on the gauge, so two writes can never both fit into the same space. Holds * expire rather than settle; until then the stored bytes may count twice, which errs on the strict side. */ export async function holdGauge(accountId: string, meter: MeterId, quantity: number, cap: number): Promise { const now = Date.now(); const period = periodOf(); const result = await accountDb(accountId).run( `INSERT INTO usage_reservations (id, account_id, meter, period, quantity, expires_at, created_at, nonce) SELECT ?1, ?2, ?3, ?4, ?5, ?6, ?7, NULL WHERE COALESCE((SELECT current FROM usage_gauges WHERE account_id = ?2 AND meter = ?3 AND period = ?4), 0) + COALESCE((SELECT SUM(quantity) FROM usage_reservations WHERE account_id = ?2 AND meter = ?3 AND expires_at > ?7), 0) + ?5 <= ?8`, `gauge:${randomId()}`, accountId, meter, period, quantity, now + GAUGE_HOLD_TTL_MS, now, cap, ); return result.meta.changes > 0; }