/** * @typedef {object} SlowDownConfig * @property {import('../rate-limit/index.js').RateLimitStore} store * @property {string | number} window Duration string ('1m', '30s') or ms. * @property {number} delayAfter First `delayAfter` requests are free; * subsequent ones get progressively delayed. * @property {number} delayMs Base per-request delay after the free window. * @property {number} [maxDelayMs=20000] Cap the per-request delay. * @property {'linear' | 'exponential'} [growth='linear'] * `linear`: delay = delayMs * excess. * `exponential`: delay = delayMs * 2^(excess - 1) (capped by maxDelayMs). */ /** * Progressive-delay throttle — a "soft" rate limiter that never rejects but * slows abusers down. Sits nicely IN FRONT of a hard limiter: `slowDown` * catches naive scrapers cheaply, the limiter catches the persistent ones. * * The returned object exposes `.check({ key })` matching the rate-limit * contract, so `multi({ limiters: [slowDown(...), rateLimit.fixed(...)] })` * composes them. * * **DoS caveat.** The delay is implemented by holding the request open on * `setTimeout` — each slowed request continues to occupy a socket + an * event-loop slot for the duration of its delay. An attacker who does not * care about latency can trivially exhaust concurrent connections. Always * pair with (a) a hard `maxConnections` on `http.Server` or a `limit_conn` * at your reverse proxy, and (b) a real rejecting limiter downstream (fed * by `multi({ limiters: [slowDown(...), rateLimit.sliding(...)] })`) so the * abuser eventually gets a `429` and the socket is released. * * @param {SlowDownConfig} config * @returns {{ check: (input: { key: string }) => Promise<{ * allowed: boolean, remaining: number, reset: Date | null, * retryAfter: number | null, delayMs: number, * }> }} */ export function slowDown(config: SlowDownConfig): { check: (input: { key: string; }) => Promise<{ allowed: boolean; remaining: number; reset: Date | null; retryAfter: number | null; delayMs: number; }>; }; export type SlowDownConfig = { store: import("../rate-limit/index.js").RateLimitStore; /** * Duration string ('1m', '30s') or ms. */ window: string | number; /** * First `delayAfter` requests are free; * subsequent ones get progressively delayed. */ delayAfter: number; /** * Base per-request delay after the free window. */ delayMs: number; /** * Cap the per-request delay. */ maxDelayMs?: number | undefined; /** * `linear`: delay = delayMs * excess. * `exponential`: delay = delayMs * 2^(excess - 1) (capped by maxDelayMs). */ growth?: "linear" | "exponential" | undefined; };