SPB Git forge

spb/toveli

Public
14commits 1branches 0releases
1.1 MBsize
maindefault branch
3 h agolast push
TypeScript 50.6% JavaScript 46.1% SQL 2.1% Shell 0.6% CSS 0.5%
1.3 KB · 16 lines typescript
Raw Blame History
1import { sql } from "drizzle-orm";2import { db } from "@/db/client";3import { rateLimits } from "@/db/schema";45/** Fenêtre glissante approximée par tranches : compte les tranches actives, incrémente la courante. Jamais de blocage irréversible. */6export async function hit(key: string, limit: number, windowSec: number, slices = 6): Promise<{ ok: boolean; remaining: number; retryAfterSec: number }> {7  const sliceMs = Math.max(1000, Math.floor((windowSec * 1000) / slices));8  const now = Date.now(); const cur = new Date(Math.floor(now / sliceMs) * sliceMs); const since = new Date(now - windowSec * 1000);9  const d = db();10  await d.insert(rateLimits).values({ key, windowStart: cur, count: 1 }).onConflictDoUpdate({ target: [rateLimits.key, rateLimits.windowStart], set: { count: sql`${rateLimits.count} + 1` } });11  const [row] = await d.select({ total: sql<number>`coalesce(sum(${rateLimits.count}), 0)::int` }).from(rateLimits).where(sql`${rateLimits.key} = ${key} and ${rateLimits.windowStart} >= ${since.toISOString()}::timestamptz`);12  const total = Number(row?.total ?? 0);13  if (Math.random() < 0.02) await d.execute(sql`delete from ${rateLimits} where ${rateLimits.windowStart} < now() - interval '2 days'`);14  return { ok: total <= limit, remaining: Math.max(0, limit - total), retryAfterSec: total <= limit ? 0 : Math.ceil(sliceMs / 1000) };15}16