TypeScript 61.9%
HTML 37.2%
SQL 0.7%
1import { newId, logger } from '@rareindex/shared';2import { costs } from '@rareindex/database';3import { db } from './db.ts';45/**6 * Cost ledger (§169). Unit prices are approximations configurable by env:7 * FIRECRAWL_USD_PER_CREDIT (default 0.00083 ≈ $83 / 100k credits plan)8 * SCRAPFLY_USD_PER_CREDIT (default 0.00030 ≈ $30 / 100k credits plan)9 * AI prices are passed by the caller (per provider/model).10 */11export const UNIT_USD = {12 firecrawl: Number(process.env.FIRECRAWL_USD_PER_CREDIT ?? 0.00083),13 scrapfly: Number(process.env.SCRAPFLY_USD_PER_CREDIT ?? 0.0003),14};1516export interface CostEntry {17 kind: 'firecrawl' | 'scrapfly' | 'ai' | 'storage' | 'http';18 provider?: string;19 connectorId?: string;20 categorySlug?: string;21 endpoint?: string;22 userId?: string;23 units?: number;24 credits?: number;25 usdEst?: number;26 metadata?: Record<string, unknown>;27}2829const buffer: CostEntry[] = [];30let flushing: Promise<void> | null = null;3132export function recordCost(entry: CostEntry): void {33 buffer.push(entry);34 if (buffer.length >= 200) void flushCosts();35}3637export async function flushCosts(): Promise<void> {38 if (flushing) return flushing;39 if (buffer.length === 0) return;40 const batch = buffer.splice(0, buffer.length);41 flushing = (async () => {42 try {43 await db()44 .insert(costs)45 .values(46 batch.map((e) => ({47 id: newId('event'),48 occurredAt: new Date(),49 kind: e.kind,50 provider: e.provider ?? null,51 connectorId: e.connectorId ?? null,52 categorySlug: e.categorySlug ?? null,53 endpoint: e.endpoint ?? null,54 userId: e.userId ?? null,55 units: e.units ?? 1,56 credits: e.credits ?? 0,57 usdEst: e.usdEst ?? (e.kind === 'firecrawl' ? (e.credits ?? 0) * UNIT_USD.firecrawl : e.kind === 'scrapfly' ? (e.credits ?? 0) * UNIT_USD.scrapfly : 0),58 metadata: e.metadata ?? {},59 })),60 );61 } catch (err) {62 logger.error({ err }, 'cost flush failed');63 } finally {64 flushing = null;65 }66 })();67 return flushing;68}69