import { newId, logger } from '@rareindex/shared'; import { costs } from '@rareindex/database'; import { db } from './db.ts'; /** * Cost ledger (§169). Unit prices are approximations configurable by env: * FIRECRAWL_USD_PER_CREDIT (default 0.00083 ≈ $83 / 100k credits plan) * SCRAPFLY_USD_PER_CREDIT (default 0.00030 ≈ $30 / 100k credits plan) * AI prices are passed by the caller (per provider/model). */ export const UNIT_USD = { firecrawl: Number(process.env.FIRECRAWL_USD_PER_CREDIT ?? 0.00083), scrapfly: Number(process.env.SCRAPFLY_USD_PER_CREDIT ?? 0.0003), }; export interface CostEntry { kind: 'firecrawl' | 'scrapfly' | 'ai' | 'storage' | 'http'; provider?: string; connectorId?: string; categorySlug?: string; endpoint?: string; userId?: string; units?: number; credits?: number; usdEst?: number; metadata?: Record; } const buffer: CostEntry[] = []; let flushing: Promise | null = null; export function recordCost(entry: CostEntry): void { buffer.push(entry); if (buffer.length >= 200) void flushCosts(); } export async function flushCosts(): Promise { if (flushing) return flushing; if (buffer.length === 0) return; const batch = buffer.splice(0, buffer.length); flushing = (async () => { try { await db() .insert(costs) .values( batch.map((e) => ({ id: newId('event'), occurredAt: new Date(), kind: e.kind, provider: e.provider ?? null, connectorId: e.connectorId ?? null, categorySlug: e.categorySlug ?? null, endpoint: e.endpoint ?? null, userId: e.userId ?? null, units: e.units ?? 1, credits: e.credits ?? 0, usdEst: e.usdEst ?? (e.kind === 'firecrawl' ? (e.credits ?? 0) * UNIT_USD.firecrawl : e.kind === 'scrapfly' ? (e.credits ?? 0) * UNIT_USD.scrapfly : 0), metadata: e.metadata ?? {}, })), ); } catch (err) { logger.error({ err }, 'cost flush failed'); } finally { flushing = null; } })(); return flushing; }