import type { Plan } from '@bmm/llm'; import { getRedis } from './redis.js'; const DAY_SEC = 24 * 60 * 60; function todayKey(): string { return new Date().toISOString().slice(0, 10); } export interface RateLimitResult { ok: boolean; remaining: number; resetIn: number; } /** * Daily counter via Redis INCR. Atomic — no race window between read & write. * First INCR (count === 1) sets the TTL so the key auto-rolls at midnight UTC. */ export async function checkDailyLimit( scope: string, userId: string, max: number, ): Promise { const key = `ratelimit:${scope}:${userId}:${todayKey()}`; const redis = getRedis(); const count = await redis.incr(key); if (count === 1) await redis.expire(key, DAY_SEC); const ttl = await redis.ttl(key); return { ok: count <= max, remaining: Math.max(0, max - count), resetIn: ttl > 0 ? ttl : DAY_SEC, }; } // Per-tier daily limits on the two LLM-priced actions. // Preview = ~€0.002-0.015/call · Build = ~€0.005-0.22/call. export const PREVIEW_DAILY_LIMIT: Record = { hobby: 5, pro: 40, team: 150, enterprise: 1000, }; export const BUILD_DAILY_LIMIT: Record = { hobby: 3, pro: 20, team: 100, enterprise: 500, };