Novus · rate limiter
src/lib/rate-limit.ts ● ALLOW
Interactive systems walkthrough

Thirty requests a minute. Then the window says no.

Every paid AI call on Novus first passes one gate: a per-user counter that allows 30 requests every 60 seconds, resets on the clock, and — surprisingly — lets you through when it breaks. Below is a live model of that exact gate. Drive it.

limit = 30 window = 60s store = atomic Postgres counter on error = fail open

The gate, live

Fire requests and watch the current window fill. At 30 it flips to 429. When the 60-second window rolls over, the count resets to zero and you're clear again. The clock runs fast so you can watch it — speed is yours to set.

check_ai_rate_limit(user, 30, 60) t = 0.0s · window #0
0of 30 used
60.0s left in window

Requests per 60s window

current: #0
limit 30

Request log

No requests yet — hit “Send request”.
rate 6/s
Simulate infra down
allowed 0 blocked 0 windows elapsed 0

Two design decisions worth understanding

🪟

Fixed-window counting

Time is chopped into fixed 60-second windows. Each request bumps a counter for the current window; cross 30 and you're blocked until the clock ticks into the next window, where the counter starts fresh at zero. Simple, cheap, and atomic in Postgres.

watch the counter reset on rollover
🚪

Fail open, on purpose

If the counter’s database call errors — say the migration hasn’t run yet — the limiter returns true and lets the request through. It’s best-effort hardening that must never take the app down. Errors aren’t attacker-controllable, so fail-open isn’t a bypass. Flip “infra down” to see it.

availability > strictness

The whole limiter is this small

src/lib/rate-limit.ts
export async function checkRateLimit(
  userId: string, limit = 30, windowSeconds = 60,
): Promise<boolean> {
  try {
    const { data, error } = await supabaseAdmin.rpc('check_ai_rate_limit', {
      p_user: userId, p_limit: limit, p_window_seconds: windowSeconds,
    })
    if (error) return true   // ← fail OPEN: infra error must not take the app down
    return data === true
  } catch {
    return true          // ← same story: allow rather than crash
  }
}
The insight
A fixed window is the simplest limiter that still holds across serverless instances — one atomic counter, no memory. Its known cost is the window-edge burst: 30 near the end of one window plus 30 at the start of the next means ~60 land in seconds. Novus accepts that, and chooses to fail open, because the limiter is a guardrail on cost — not a security boundary.

Recap