Repository Public
1import { sql } from "./db";2import { ApiError } from "./http";34/** Fixed-window counter in PostgreSQL (shared by the web processes, survives restarts). */5export async function rateLimit(key: string, limit: number, windowSec: number): Promise<void> {6 const [row] = await sql<{ count: number }[]>`7 INSERT INTO rate_limits (key, window_start, count)8 VALUES (${key}, to_timestamp(floor(extract(epoch FROM now()) / ${windowSec}) * ${windowSec}), 1)9 ON CONFLICT (key, window_start) DO UPDATE SET count = rate_limits.count + 110 RETURNING count`;11 if (row.count > limit) throw new ApiError(429, "rate_limited", "Too many requests, try again later");12}13