SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
2.1 KB · 69 lines typescript
Raw Blame History
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