alepha@docs:~/docs/reference/primitives$
cat $rateLimit.md | pretty
2 min read

#$rateLimit

#Import

typescript
1import { $rateLimit } from "alepha/rate-limit";

#Overview

Declare a transport-agnostic, fail-closed rate limiter.

Counters are actor state (alepha/actor), so topology is the actor provider's: Memory is one instance, Redis (once REDIS_URL is set) is several, and a Durable Object on workerd. An app that runs several instances without REDIS_URL counts per instance.

consume(input) counts one hit in a fixed window anchored on its first hit. A counted result carries a ticket; refund(ticket) gives that hit back, within the same window only, and never throws. A refused hit is not counted: past the limit the counter saturates and a flood costs reads, not writes.

limit, window and key are numbers or synchronous functions of the consume input: load anything asynchronous (realm settings) before consuming and pass it in. A store error or ActorContentionError resolves a result with reason: "store" | "contention", refused under deny (the default for both) or allowed uncounted under allow. consume never rejects for them.

Every refusal emits rateLimit:consume:denied. Callers audit lockouts themselves from count: whether a hit "crossed" depends on whether they compare before or after counting.

middleware() puts a string-keyed limiter in front of an $action, $job or $pipeline: 429 on the limit, 503 on a store error.

#Examples

ts
 1import { $rateLimit } from "alepha/rate-limit"; 2  3interface Attempt { 4  userId: string; 5  max: number; 6} 7  8class Auth { 9  account = $rateLimit({10    name: "login:account",11    limit: (input: Attempt) => input.max,12    window: [15, "minutes"],13    key: (input: Attempt) => input.userId,14  });15 16  async attempt(userId: string, ok: () => Promise<boolean>) {17    const result = await this.account.consume({ userId, max: 5 });18    if (!result.allowed) return false;19    if (!(await ok())) return false;20    if (result.ticket) await this.account.refund(result.ticket);21    return true;22  }23}