/** * Billing tier lookup (GH #473). * * The page limit used to be decided by two env vars and god-mode membership, so * the "Upgrade to Solo for 2,000 pages" message pointed at a tier that raised * nothing. This resolves a user's tier so `getPageLimit` can honour it. * * PostgREST-backed, mirroring PostgresPATStore. There is no SQLite backend on * purpose: `assertPageLimit` returns early unless the Postgres index is * configured, so a local run never reaches a tier lookup. * * Failure is non-fatal by design. A missing table, an unreachable database or a * malformed row all resolve to `null` — "no tier assigned" — and the caller * falls back to the env-var defaults. * * That matters because on production, code and schema move on different * triggers. Railway redeploys production code when `main` moves, but * `apply-migrations.mjs` runs there only inside `deploy-production.yml`, which * is `workflow_dispatch` and fires only when a human asks. So production can be * running code that reads this table days before anyone applies it. (Staging * does not have the gap — `deploy-staging.yml` runs migrations on every push to * `main`.) Refusing writes in that window would take production down for * everyone in exchange for a limit that is merely stale. */ /** Tiers the pricing page sells, plus the two legacy names seedUsers already writes. */ export type BillingTier = "free" | "solo" | "pro" | "unlimited" | "free_beta" | "launch_user"; export declare const BILLING_TIERS: readonly BillingTier[]; export declare function isBillingTier(value: string): value is BillingTier; export interface TierStore { /** Resolve a subject's tier, or null when none is assigned or lookup fails. */ get(subject: string): Promise; /** Assign a tier. Returns false when the write fails. */ set(subject: string, tier: BillingTier, updatedBy: string): Promise; /** Drop cached lookups. */ invalidate(subject?: string): void; } export declare class PostgresTierStore implements TierStore { private readonly supabaseUrl; private readonly serviceRoleKey; private readonly cache; constructor(supabaseUrl: string, serviceRoleKey: string); private get baseUrl(); private headers; private cacheTtlMs; private cacheSet; invalidate(subject?: string): void; get(subject: string): Promise; set(subject: string, tier: BillingTier, updatedBy: string): Promise; } /** No-op store for deployments without Postgres — every lookup is "no tier". */ export declare class NullTierStore implements TierStore { get(): Promise; set(): Promise; invalidate(): void; } export declare function getTierStore(): TierStore; /** Test seam. */ export declare function setTierStore(store: TierStore | null): void; //# sourceMappingURL=tierStore.d.ts.map