@portable/rate-limit
Functions
RateLimit.create
Creates a rate limiter that allows limit requests per period for each key, with the generic
cell rate algorithm (GCRA). Allowance comes back continuously, one request every
period / limit milliseconds, up to burst.
function create(options: create.Options): Limiter;| Option | Type | Default | Description |
|---|---|---|---|
limit | number | Requests allowed per period, on average. | |
period | number | Length of the period in milliseconds. | |
burst | number | limit | Requests allowed at once after an idle period. |
store | Store | RateLimit.memoryStore() | Where the limiter keeps one number per key. |
import * as RateLimit from '@portable/rate-limit';
const limiter = RateLimit.create({ limit: 100, period: 60_000 });
export default {
async fetch(request: Request) {
const ip = request.headers.get('cf-connecting-ip') ?? 'unknown';
const result = await limiter.take(ip, { signal: request.signal });
if (!result.allowed) {
return new Response(null, {
status: 429,
headers: RateLimit.headers(result),
});
}
return new Response('ok', { headers: RateLimit.headers(result) });
},
};RateLimit.headers
Returns the RateLimit and RateLimit-Policy fields of draft-ietf-httpapi-ratelimit-headers-11
for a result, and Retry-After when the request was denied. Times are whole seconds, rounded up.
function headers(
result: Result,
options: headers.Options = {},
): Record<string, string>;| Option | Type | Default | Description |
|---|---|---|---|
name | string | 'default' | The quota policy name in both fields. |
now | any | new Date() | The time resetAt is counted from, as a Date or a Temporal.Instant. |
import * as RateLimit from '@portable/rate-limit';
const result = await limiter.take(ip);
if (!result.allowed) {
return new Response(null, {
status: 429,
headers: RateLimit.headers(result),
});
}
// RateLimit-Policy: "default";q=100;w=60
// RateLimit: "default";r=0;t=1
// Retry-After: 1RateLimit.memoryStore
Creates a store that keeps keys in a Map in this isolate or process. An expired key is
deleted when it is read, and all expired keys are deleted whenever the map has doubled in size
since the last sweep.
function memoryStore(): {
get(key: string, options?: Store.GetOptions): number | undefined;
set(key: string, value: number, options: Store.SetOptions): boolean;
};import * as RateLimit from '@portable/rate-limit';
const store = RateLimit.memoryStore();
const perMinute = RateLimit.create({
limit: 100,
period: 60_000,
store,
});Types
RateLimit.Limiter
The limiter that create returns.
type Limiter = {
take(key: string, options?: Limiter.TakeOptions): Promise<Result>;
};RateLimit.Result
The outcome of take. Durations are milliseconds.
type Result = {
allowed: boolean;
limit: number;
period: number;
remaining: number;
resetAt: Date;
retryAfter: number;
};RateLimit.Store
Where a limiter keeps one number per key. get returns the stored number, or undefined for a
missing or expired key. set writes value only if the key still holds expected, and returns
whether it wrote. Either method may return a promise.
type Store = {
get(
key: string,
options: Store.GetOptions,
): Store.MaybePromise<number | undefined>;
set(
key: string,
value: number,
options: Store.SetOptions,
): Store.MaybePromise<boolean>;
};