TypeScript 50.6%
JavaScript 46.1%
SQL 2.1%
Shell 0.6%
CSS 0.5%
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