Rate Limiting
End-to-end rate limiting: L1 global-IP + L2 auth + L3 handler opt-in.
What it shows
Section titled “What it shows”- Handler
rateLimit: { per, limit, windowSeconds } - How L1/L2 from
buildServerstack with L3
Per-payload limits
Section titled “Per-payload limits”A public write handler that mails an address typed by the caller is also limited per
address, so rotating IPs does not help against mail flooding. Declare it next to rateLimit:
r.writeHandler({ // ... access: { roles: ["anonymous"] }, rateLimit: { per: "ip+handler", limit: 5, windowSeconds: 600 }, additionalRateLimits: [{ per: { payloadField: "email" }, limit: 3, windowSeconds: 86400 }],});additionalRateLimits only complements rateLimit: an anonymous handler still needs a real,
ip-keyed rateLimit (boot fails otherwise), and payloadField must be a top-level string field
of the handler’s Zod object schema. The value is trimmed, lowercased and HMAC-hashed into the
bucket key, so Redis never holds the address in plaintext. The check runs after schema
validation and before the handler, identically for matching and non-matching values, and is
skipped for system callers. Exceeding it answers 429 rate_limited.
Source
Section titled “Source”Feature entry point: src/feature.ts.
Needs a running Postgres and TEST_DATABASE_URL set (e.g. postgres://postgres:[email protected]:5432/postgres, see demo/.env.example).
cd samples/recipes/rate-limitingbun testSource code
Section titled “Source code”The feature entry point — embedded straight from the source file, so the code here is exactly what runs. Multi-file samples keep their remaining files next to it on GitHub (link below):
// Rate-Limiting Showcase — minimal feature//// Declares one query handler with an L3 rateLimit option so the// dispatcher gates calls before the handler body runs. The integration// test pairs this with L1+L2 middleware wired via buildServer's// `rateLimit` option to prove all three layers stack.
import { defineFeature, type FeatureDefinition } from "@cosmicdrift/kumiko-framework/engine";import { z } from "zod";
export function createRateLimitShowcaseFeature(): FeatureDefinition { return defineFeature("rl-showcase", (r) => { // Per-user budget. Real apps tune `limit` to actual handler cost — a // search call against a sharded index might warrant 5/min, a full // export 1/min. The bucket is `user:<userId>` (see rate-limit/bucket.ts). r.queryHandler( "expensive-search", z.object({ q: z.string().min(1) }), async ({ payload }) => ({ q: payload.q, hits: 0 }), { access: { roles: ["Admin", "User"] }, rateLimit: { per: "user", limit: 3, windowSeconds: 60 }, }, ); });}📄 On GitHub: samples/recipes/rate-limiting/src/feature.ts