Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/AmeyaBorkar/throttlekit/llms.txt

Use this file to discover all available pages before exploring further.

This page documents the complete set of types that form ThrottleKit’s stable API surface. All types are exported from the top-level throttlekit package. They are designed for extensibility: producer types (values the library returns) grow only by appending optional readonly fields; consumer interfaces (contracts you implement) grow only by adding optional members.

Decision

The immutable result of one rate-limit check. Every check method returns a Decision. All numeric fields are integers so the JavaScript and Redis-Lua execution paths produce bit-identical values.
import type { Decision } from "throttlekit";
allowed
boolean
Whether the request is permitted.
limit
number
The effective ceiling: burst capacity (GCRA/token bucket) or window quota.
remaining
number
Whole units remaining before the next rejection. Never negative.
resetAt
number
Epoch-ms at which the limiter is fully replenished.
retryAfterMs
number
Milliseconds to wait before retrying. 0 when allowed is true.

Forecast

A non-consuming projection of a key’s near-future capacity. Returned by Limiter.forecast().
import type { Forecast } from "throttlekit";
spendableNow
number
Whole units of the given cost admissible right now before the next denial.
nextReplenishAt
number
Epoch-ms when capacity next increases by at least one unit.
fullAt
number
Epoch-ms when the limiter is fully replenished to its ceiling from the current state.

Strategy<S>

A pure rate-limiting algorithm over serializable state S. The check function is a pure function of (state, now, cost) — no I/O, no clock reads — which makes it deterministic, trivially testable, and portable to an atomic Redis Lua form.
import type { Strategy } from "throttlekit";
name
string
required
Stable identifier surfaced in RateLimit-Policy and metrics (e.g. "gcra").
limit
number
required
Effective ceiling reported to clients (burst capacity or window quota).
windowMs
number
Effective window length in ms, surfaced as the w of RateLimit-Policy. Optional.
ttlMs
number
required
Upper bound on how long state stays relevant; used as the store TTL hint.
check
(state: S | undefined, now: number, cost: number) => StrategyOutcome<S>
required
The pure transition. Given the current state, epoch-ms now, and request cost, returns the next state and the resulting Decision.
lua
LuaProgram
Optional atomic Redis form. When present, a Lua-capable store runs it in one round trip.
peek
(state: S | undefined, now: number) => Decision
Pure, non-mutating introspection: the Decision for the current state without consuming. Optional. The basis of Limiter.peek.
forecast
(state: S | undefined, now: number, cost: number) => Forecast
Pure capacity forecast for the current state. Optional. The basis of Limiter.forecast.
readState
ReadState<S>
Read-only access to stored state for a Lua-capable store. Required for peek/forecast to work over a Lua store.

Store

Storage exposes exactly one mutating primitive: apply. Adding a backend means implementing one method; adding an algorithm never touches a store.
import type { Store } from "throttlekit";
apply
(key: string, transform: Transform<S, R>) => Promise<R>
required
Run transform atomically with respect to other applies on the same key.
applySync
(key: string, transform: Transform<S, R>, now?: number) => R
Synchronous, allocation-light variant. Absent on async-only stores (e.g. RedisStore). When present, enables checkSync, checkManySync, peekSync, and forecastSync on the limiter.
reset
(key: string) => Promise<void>
required
Forget a key’s stored state.
resetSync
(key: string) => void
Synchronous reset. Optional; only on stores that can guarantee synchronous deletion.
close
() => Promise<void>
Release resources (timers, connections). Optional.

Limiter

A constructed limiter: a strategy + store + key namespace + clock. Returned by rateLimit(), twoTier(), and every higher-level factory.
import type { Limiter } from "throttlekit";
strategy
Strategy
The active strategy. Use limiter.strategy.name for headers/policy labelling.
check
(key: string, cost?: number) => Promise<Decision>
required
Check key consuming cost units (default 1).
checkSync
(key: string, cost?: number) => Decision
required
Zero-await synchronous check. Throws ThrottleKitError on an async-only store.
checkMany
(keys: readonly string[], cost?: number) => Promise<Decision[]>
required
Check many independent keys in one call at a single consistent timestamp.
checkManySync
(keys: readonly string[], cost?: number) => Decision[]
required
Synchronous batch check. Throws on an async-only store.
peek
(key: string) => Promise<Decision>
Non-consuming introspection without spending capacity. Optional.
peekSync
(key: string) => Decision
Synchronous peek. Requires a synchronous store and a strategy that implements peek. Optional.
forecast
(key: string, cost?: number) => Promise<Forecast>
Non-consuming capacity forecast. Optional.
forecastSync
(key: string, cost?: number) => Forecast
Synchronous forecast. Optional.
reset
(key: string) => Promise<void>
required
Forget a key’s state.
close
() => Promise<void>
Release resources owned by this limiter. Optional.

Transform<S, R>

A pure read-modify-write run atomically by a store. Closes over now and cost.
type Transform<S, R> = ((state: S | undefined) => ApplyOutcome<S, R>) & {
  readonly lua?: LuaInvocation<R>;
};
The optional lua property carries an atomic acceleration for Lua-capable stores. Stores that ignore it remain correct via the function body.

ApplyOutcome<S, R>

The outcome of a store apply: new state + caller’s result + persistence info.
state
S | undefined
Next state. Written when persist is true.
result
R
Value returned to the caller (a Decision for rate-limit checks).
ttlMs
number
TTL for the persisted state, in ms.
persist
boolean
Whether state must be written to the store.

StrategyOutcome<S>

What a Strategy returns from a single transition. An alias for ApplyOutcome<S, Decision>.
type StrategyOutcome<S> = ApplyOutcome<S, Decision>;

FailMode

Behavior when the backing store is unreachable.
type FailMode = "open" | "closed";
  • "open" — admit the request when the store is unavailable (fail permissively).
  • "closed" — deny the request when the store is unavailable (fail safely).

Clock

Injected time source. Epoch-ms. Nothing in the core ever reads the clock directly.
import type { Clock } from "throttlekit";

interface Clock {
  now(): number; // current time in epoch-ms
}

ManualClock

A controllable Clock for deterministic testing. Advance its time with advance(ms).
import { ManualClock } from "throttlekit";

const clock = new ManualClock(0);
clock.advance(5_000); // advance 5 seconds
console.log(clock.now()); // 5000

Error Classes

All errors extend ThrottleKitError and carry a code field. Prefer code over instanceof for robustness across realms and duplicate bundle copies.

ThrottleKitError

Base class for all errors thrown by ThrottleKit.
import { ThrottleKitError } from "throttlekit";
code
ThrottleKitErrorCode
Machine-readable discriminant. Robust to cross-realm instanceof checks. One of: "throttlekit_error" | "store_unavailable" | "not_implemented" | "rate_limit_exceeded" | "queue_full" | "config_invalid".

StoreUnavailableError

Thrown when the backing store cannot be reached. Code: "store_unavailable".
import { StoreUnavailableError } from "throttlekit";

if (err.code === "store_unavailable") { /* fail open/closed */ }

RateLimitExceededError

Convenience error for callers who prefer throwing over inspecting a Decision. Carries the denying decision and its retryAfterMs. Code: "rate_limit_exceeded".
import { RateLimitExceededError } from "throttlekit";

const d = await limiter.check("user:42");
if (!d.allowed) throw new RateLimitExceededError(d);
// err.retryAfterMs, err.decision are available on the caught error

NotImplementedError

Thrown by placeholder code paths that are declared but not yet implemented. Code: "not_implemented".

QueueFullError

Thrown by leakyBucket when the shaper’s internal queue is at capacity and cannot accept another reservation. Code: "queue_full". Carries retryAfterMs.
import { QueueFullError } from "throttlekit";

try {
  await shaper.reserve(1);
} catch (err) {
  if (err instanceof QueueFullError) {
    // The leaky-bucket queue is full — shed or retry after err.retryAfterMs
  }
}

combineDecisions

Combine two Decision objects into one — the pure algebra at the heart of unifiedAdmission.
import { combineDecisions } from "throttlekit";

function combineDecisions(a: Decision, b: Decision): Decision
Field-by-field aggregation:
FieldRuleWhy
alloweda.allowed && b.allowedAND — both must allow
limitmin(a.limit, b.limit)binding (smaller) ceiling
remainingmin(a.remaining, b.remaining)binding remainder
resetAtmax(a.resetAt, b.resetAt)latest-resolution wait
retryAfterMsmax(a.retryAfterMs, b.retryAfterMs)dominant wait
The operation is total, pure, and satisfies four algebraic laws: identity (with ALLOW_FULL), associativity, commutativity, and idempotency.

ALLOW_FULL

The neutral element (identity) for combineDecisions. An always-allowing Decision with limit = remaining = Number.MAX_SAFE_INTEGER.
import { ALLOW_FULL } from "throttlekit";

combineDecisions(myDecision, ALLOW_FULL) // === myDecision

buildRateLimitHeaders

Build standards-compliant rate-limit response headers for a Decision.
import { buildRateLimitHeaders } from "throttlekit";

function buildRateLimitHeaders(
  decision: Decision,
  opts?: BuildRateLimitHeadersOptions
): Record<string, string>
decision
Decision
required
The decision to build headers from.
opts.now
number
Current epoch-ms for delta-seconds math. Defaults to the system clock. Pass clock.now() for deterministic output in tests.
opts.policyName
string
Policy name surfaced in structured fields. Defaults to "default".
opts.windowSeconds
number
Window length in seconds, surfaced as ;w= in RateLimit-Policy.
opts.emit
HeaderEmit
Which header families to emit. Defaults to { draft: true }.
const headers = buildRateLimitHeaders(decision, {
  emit: { draft: true, legacy: true },
  policyName: "api",
});
// { "RateLimit-Limit": "100", "RateLimit-Remaining": "42", ... }

createEnforcer

Build a transport-agnostic Enforcer from a limiter plus the shared header/fail policy.
import { createEnforcer } from "throttlekit";

function createEnforcer(options: EnforceOptions): Enforcer
options.strategy
Strategy
Build a fresh in-memory limiter from this strategy. Mutually exclusive with limiter.
options.limiter
Limiter
A pre-built limiter to use directly. Mutually exclusive with strategy.
options.fail
FailMode
Store-outage policy. Default "open".
options.emit
HeaderEmit
Which header families to emit on each decision.
options.policyName
string
Policy name surfaced in structured headers.
options.onLimited
(key: string, decision: Decision) => void
Callback fired on every denial (outcome: "limited").
options.onError
(key: string, err: unknown) => void
Callback fired when the store throws, before the fail policy is applied.
limiter
Limiter
The resolved limiter, for direct check use or header/policy introspection.
fail
FailMode
The store-outage policy applied inside enforce.
enforce
(key: string, cost?: number) => Promise<EnforceResult>
Run the limit for key. Never throws on a store outage — the fail policy converts it to an EnforceResult with outcome: "error".
const { enforce } = createEnforcer({
  strategy: gcra({ limit: 100, periodMs: 60_000 }),
  fail: "open",
});

const result = await enforce(clientIp({ remoteAddr: req.socket.remoteAddress }));
if (!result.allowed) {
  res.set(result.headers).status(result.outcome === "limited" ? 429 : 503).end();
}

clientIp

Derive the proxy-correct, IPv6-aggregated client IP key from a request’s socket peer and X-Forwarded-For header.
import { clientIp } from "throttlekit";

function clientIp(input: ClientIpInput, config?: TrustProxyConfig): string
input.remoteAddr
string
required
The socket peer address (e.g. req.socket.remoteAddress).
input.xForwardedFor
string | string[]
The X-Forwarded-For header value.
config.trustProxy
false | number | string[]
Trust policy for X-Forwarded-For. Default false (ignore XFF, use socket peer only). A number trusts that many hops; a string array is an IP/CIDR allowlist of trusted proxies.
config.ipv6Prefix
number
IPv6 aggregation prefix length in bits. Default 64. IPv4-mapped IPv6 is collapsed to its embedded IPv4 address.
const key = clientIp(
  { remoteAddr: req.socket.remoteAddress, xForwardedFor: req.headers["x-forwarded-for"] },
  { trustProxy: 1, ipv6Prefix: 48 }
);

hashKey / hmacKeyer

PII-safe HMAC-SHA-256 key hashing. Prevents raw identifiers (IPs, user IDs) from reaching the store.
import { hashKey, hmacKeyer } from "throttlekit";

function hashKey(raw: string, secret: string): string
function hmacKeyer(secret: string): (raw: string) => string
const keyer = hmacKeyer(process.env.RATE_LIMIT_SECRET!);
const key = keyer(userEmail); // 64-char lowercase hex digest
const d = await limiter.check(key);

Build docs developers (and LLMs) love