import { all, run } from "../db"; import { domainSites } from "../sites/store"; import { locate, registerLocator } from "../scoped-db/locators"; /** * Directory lookups for metering fan-outs. Usage lives in each account's AccountState; these list which * objects to visit. Anonymous `scratch:` ids have no accounts row, so they register a `scratch_account` * locator the first time they hold or record usage. */ export async function directoryAccountIds(): Promise { return (await all<{ id: string }>("SELECT id FROM accounts ORDER BY id")).map((row) => row.id); } export async function scratchAccountIds(): Promise { return (await all<{ key: string }>("SELECT key FROM scope_locators WHERE kind = 'scratch_account' ORDER BY key")).map( (row) => row.key, ); } /** Every object that may hold usage: directory accounts plus registered scratch ids. */ export async function meteredAccountIds(): Promise { return [...(await directoryAccountIds()), ...(await scratchAccountIds())]; } /** Makes a scratch id reachable by sweeps (anonymous usage is low volume, so this is one small upsert). */ export async function noteScratchAccount(accountId: string): Promise { if (!accountId.startsWith("scratch:")) return; await registerLocator("scratch_account", accountId, accountId); } /** Registers `key` only when it is unclaimed; returns the scope that owns it afterwards (first claim wins). */ export async function claimLocator( kind: "stripe_customer" | "scratch_claim", key: string, scopeId: string, ): Promise { await run( "INSERT INTO scope_locators (kind, key, scope_id) VALUES (?, ?, ?) ON CONFLICT (kind, key) DO NOTHING", kind, key, scopeId, ); return (await locate(kind, key)) ?? scopeId; } /** Custom domains held per owning account, across the sites indexed as holding one. */ export async function customDomainCounts(): Promise> { const out = new Map(); for (const site of await domainSites("owner_id IS NOT NULL")) { if (site.owner_id) out.set(site.owner_id, (out.get(site.owner_id) ?? 0) + 1); } return out; }