Rate Limiting (primitive)
ratelimit is a sliding-window rate limiter. Each check(id) is a single atomic Lua round trip that drops expired entries, counts the window, and admits the request if it is under the limit.
import { ratelimit } from "benni/schema";
export const apiLimit = ratelimit("api", { limit: 10, windowMs: 60_000 });const { success, remaining, resetMs } = await redis.query.apiLimit.check(userId);if (!success) { throw new Response("Too Many Requests", { status: 429, headers: { "Retry-After": String(Math.ceil((resetMs - Date.now()) / 1000)) } });}Declared as a schema value it lands in redis.query and needs no client of its own. Where you hold a client but no handle, such as inside a middleware factory, benni/primitives exports the same limiter in its client-taking form, over the same keys:
import { ratelimit } from "benni/primitives";
const limiter = ratelimit({ client, limit: 10, windowMs: 60_000 });const { success } = await limiter.check(userId);client accepts a RedisClient, a promise of one, a factory, or a Benni handle, so it runs over every adapter, including benni/upstash on the edge, which is where rate limiting is most often needed.
The result
Section titled “The result”type RatelimitResult = { success: boolean; // is this request allowed? limit: number; // the configured limit remaining: number; // requests left in the window (0 when denied) resetMs: number; // epoch-ms when the window next frees a slot};How it works
Section titled “How it works”The window is a log of request timestamps in one sorted set (a single key, so it is Redis Cluster safe). That makes the limit exact (no fixed-window boundary bursts) at the cost of storing up to limit entries per key. For typical API limits (tens to hundreds per window) that is ideal; for very high per-key rates, prefer a counter-based limiter.
Options
Section titled “Options”| Option | Default | Meaning |
|---|---|---|
limit | - | Maximum requests allowed within the window. |
windowMs | - | Window length in milliseconds. |
prefix | "ratelimit" | Key namespace; keys are <prefix>:<id>. |
Use a stable id per subject: a user id, API key, or IP. Each id is limited independently.
See Rate Limiting patterns for the underlying Redis approach if you want to roll your own.