@ayepi/rate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/index.cjs +337 -0
- package/dist/index.d.cts +189 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +331 -0
- package/dist/redis.cjs +103 -0
- package/dist/redis.d.cts +21 -0
- package/dist/redis.d.ts +21 -0
- package/dist/redis.js +102 -0
- package/dist/server.cjs +66 -0
- package/dist/server.d.cts +67 -0
- package/dist/server.d.ts +67 -0
- package/dist/server.js +65 -0
- package/package.json +94 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { AnyMiddleware, Json, MaybePromise, MiddlewareDef } from "@ayepi/core";
|
|
2
|
+
import { Doer, DoerTaskOptions } from "@ayepi/core/doer";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
|
|
6
|
+
/** The rate-limiting algorithm. */
|
|
7
|
+
type Algorithm = 'fixed-window' | 'sliding-window' | 'token-bucket';
|
|
8
|
+
/** Limiter state for one key, exposed to handlers (via `ctx.ratelimit`) and response headers. */
|
|
9
|
+
interface RateLimitInfo {
|
|
10
|
+
/** The configured request limit for the window. */
|
|
11
|
+
readonly limit: number;
|
|
12
|
+
/** Requests (or tokens) remaining before the limit is hit. */
|
|
13
|
+
readonly remaining: number;
|
|
14
|
+
/** Milliseconds until the window/bucket resets. */
|
|
15
|
+
readonly reset: number;
|
|
16
|
+
/** Milliseconds to wait before retrying (0 when allowed). */
|
|
17
|
+
readonly retryAfter: number;
|
|
18
|
+
}
|
|
19
|
+
/** A store's decision for one key. */
|
|
20
|
+
interface RateLimitResult extends RateLimitInfo {
|
|
21
|
+
/** Whether this request is within the limit. */
|
|
22
|
+
readonly allowed: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The rule a store evaluates a key against. */
|
|
25
|
+
interface RateLimitRule {
|
|
26
|
+
readonly limit: number;
|
|
27
|
+
readonly window: number;
|
|
28
|
+
readonly algorithm: Algorithm;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a request that is **itself rejected** (over the limit) still counts
|
|
31
|
+
* against the limit. Default `false` — rejected requests are not recorded, so a
|
|
32
|
+
* client cannot extend its own block by continuing to hammer the endpoint. Set
|
|
33
|
+
* `true` for the stricter behavior where every attempt consumes budget.
|
|
34
|
+
* (No effect on `token-bucket`, which never charges a request it can't admit.)
|
|
35
|
+
*/
|
|
36
|
+
readonly countRejected?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Pluggable backend that atomically records a hit and decides the outcome.
|
|
40
|
+
* Implementing the algorithm in the store keeps it correct across instances
|
|
41
|
+
* (the in-memory store is single-process; `@ayepi/rate/redis` is distributed).
|
|
42
|
+
*/
|
|
43
|
+
interface RateLimitStore {
|
|
44
|
+
/** Record a hit for `key` under `rule` at time `now` (ms) and return the decision. */
|
|
45
|
+
consume(key: string, rule: RateLimitRule, now: number): MaybePromise<RateLimitResult>;
|
|
46
|
+
/** Clear all state for `key` (optional). */
|
|
47
|
+
reset?(key: string): MaybePromise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** The argument passed to `key`/`skip`/`message` — the request plus accumulated context. */
|
|
50
|
+
interface RateKeyIO<Ctx extends object> {
|
|
51
|
+
readonly req: Request;
|
|
52
|
+
readonly ctx: Ctx;
|
|
53
|
+
}
|
|
54
|
+
/** Configuration for a standalone {@link limiter} — the base of {@link RateLimitOptions}. */
|
|
55
|
+
interface LimiterOptions {
|
|
56
|
+
/** Max requests (or token-bucket capacity) per window. */
|
|
57
|
+
readonly limit: number;
|
|
58
|
+
/** Window length in milliseconds (also the token refill period). */
|
|
59
|
+
readonly window: number;
|
|
60
|
+
/** Algorithm (default `'fixed-window'`). */
|
|
61
|
+
readonly algorithm?: Algorithm;
|
|
62
|
+
/** Backend store (default an in-process {@link memoryStore}). */
|
|
63
|
+
readonly store?: RateLimitStore;
|
|
64
|
+
/** Key prefix/namespace (default `'rl:'`). */
|
|
65
|
+
readonly prefix?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Count requests that are themselves rejected (over-limit) against the limit.
|
|
68
|
+
* Default `false` — see {@link RateLimitRule.countRejected}.
|
|
69
|
+
*/
|
|
70
|
+
readonly countRejected?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** Response customization shared by {@link rateLimitResponse} and {@link rateLimit}. */
|
|
73
|
+
interface RateLimitResponseOptions {
|
|
74
|
+
/** Over-limit status code (default `429`). */
|
|
75
|
+
readonly status?: number;
|
|
76
|
+
/** Over-limit body — a string, a JSON value, or a function of the limiter info. */
|
|
77
|
+
readonly message?: string | Json | ((info: RateLimitInfo) => string | Json);
|
|
78
|
+
/**
|
|
79
|
+
* Response headers. `true` (default) emits draft `RateLimit-Limit`/`-Remaining`/
|
|
80
|
+
* `-Reset` — plus `Retry-After` **only when the request was rejected**; `false`
|
|
81
|
+
* emits none; a function returns your own map.
|
|
82
|
+
*/
|
|
83
|
+
readonly headers?: boolean | ((info: RateLimitInfo) => Record<string, string>);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Options for the {@link rateLimit} **def** — frontend-safe only.
|
|
87
|
+
*
|
|
88
|
+
* @typeParam R - middleware this one depends on (their context is typed in the
|
|
89
|
+
* server-side `key`/`skip`).
|
|
90
|
+
*/
|
|
91
|
+
interface RateLimitDefOptions<R extends readonly AnyMiddleware[]> {
|
|
92
|
+
/** Middleware this one depends on — their context is available (and typed) in `key`/`skip`. */
|
|
93
|
+
readonly requires?: R;
|
|
94
|
+
/** Middleware name for docs/debugging (default `'rateLimit'`). */
|
|
95
|
+
readonly name?: string;
|
|
96
|
+
}
|
|
97
|
+
/** A reusable rate limiter bound to a rule + store — usable anywhere, not just middleware. */
|
|
98
|
+
interface Limiter {
|
|
99
|
+
/** Record a hit for `key` (at `now`, default `Date.now()`) and return the decision. */
|
|
100
|
+
check(key: string, now?: number): MaybePromise<RateLimitResult>;
|
|
101
|
+
/** Clear all state for `key`. */
|
|
102
|
+
reset(key: string): MaybePromise<void>;
|
|
103
|
+
/** The rule this limiter enforces. */
|
|
104
|
+
readonly rule: RateLimitRule;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a standalone {@link Limiter} — the rate-limit primitive the
|
|
108
|
+
* {@link rateLimit} middleware is built on. Use it anywhere you have a key: a
|
|
109
|
+
* plain handler, a queue/cron worker, a CLI, a different framework.
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* const lim = limiter({ limit: 100, window: 60_000, algorithm: 'token-bucket' })
|
|
113
|
+
* const { allowed, retryAfter } = await lim.check(userId)
|
|
114
|
+
* if (!allowed) throw reject(429, 'RATE_LIMITED', `retry in ${retryAfter}ms`)
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
declare function limiter(opts: LimiterOptions): Limiter;
|
|
118
|
+
/**
|
|
119
|
+
* Compute the rate-limit response headers for `info`. `true` (default) emits the
|
|
120
|
+
* draft `RateLimit-Limit`/`-Remaining`/`-Reset` headers — plus `Retry-After` **only
|
|
121
|
+
* when the request was rejected** (`retryAfter > 0`); `false` emits none; a function
|
|
122
|
+
* returns your own map (which **replaces** the defaults).
|
|
123
|
+
*
|
|
124
|
+
* Shared by {@link rateLimitResponse} (the 429) and the middleware's `alwaysHeaders`
|
|
125
|
+
* option (informational headers on allowed responses).
|
|
126
|
+
*/
|
|
127
|
+
declare function rateLimitHeaders(info: RateLimitInfo, headers?: boolean | ((info: RateLimitInfo) => Record<string, string>)): Record<string, string>;
|
|
128
|
+
/**
|
|
129
|
+
* Build a rate-limit (429) `Response` from limiter info — usable on its own,
|
|
130
|
+
* outside any middleware (e.g. from a handler that called {@link limiter} directly).
|
|
131
|
+
*/
|
|
132
|
+
declare function rateLimitResponse(info: RateLimitInfo, opts?: RateLimitResponseOptions): Response;
|
|
133
|
+
/**
|
|
134
|
+
* Create a rate-limiting middleware **def**. The def declares what the middleware
|
|
135
|
+
* contributes (`{ ratelimit: RateLimitInfo }`) and its dependencies — but **no**
|
|
136
|
+
* policy. Bind the key/limit/window/store with
|
|
137
|
+
* [`rateLimit.server(def, { key, limit, window })`](./server).
|
|
138
|
+
*
|
|
139
|
+
* @typeParam R - inferred from `requires`; their context types flow into the
|
|
140
|
+
* server-side `key`/`skip`/`message`.
|
|
141
|
+
*/
|
|
142
|
+
declare function rateLimit<const R extends readonly AnyMiddleware[] = readonly []>(opts?: RateLimitDefOptions<R>): RateLimitDef<R>;
|
|
143
|
+
/** The def type a {@link rateLimit} call produces — what `rateLimit.server` binds against. */
|
|
144
|
+
type RateLimitDef<R extends readonly AnyMiddleware[] = readonly []> = MiddlewareDef<{
|
|
145
|
+
ratelimit: RateLimitInfo;
|
|
146
|
+
}, R>;
|
|
147
|
+
/** Options for {@link rateLimitedDoer} — a {@link LimiterOptions} plus doer-specific knobs. */
|
|
148
|
+
interface RateLimitedDoerOptions extends LimiterOptions {
|
|
149
|
+
/** Limit key — a single shared bucket by default (`'doer'`), or derived per task. */
|
|
150
|
+
readonly key?: string | ((opts: DoerTaskOptions) => string);
|
|
151
|
+
/** Floor on the re-check delay for deferred tasks (ms, default 50). */
|
|
152
|
+
readonly retryFloor?: number;
|
|
153
|
+
/** Clock injection (default `Date.now`). */
|
|
154
|
+
readonly now?: () => number;
|
|
155
|
+
/** The doer that actually runs admitted tasks (default {@link unlimitedDoer}). Compose policies. */
|
|
156
|
+
readonly doer?: Doer;
|
|
157
|
+
/**
|
|
158
|
+
* Observe a store error during admission (e.g. a distributed store hiccup). The drain loop
|
|
159
|
+
* never crashes on it — the task stays pending and admission is retried shortly. Off by
|
|
160
|
+
* default; it must not throw — if it does, the throw is ignored.
|
|
161
|
+
*/
|
|
162
|
+
readonly onError?: (err: unknown) => void;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* A {@link Doer} (see `@ayepi/core/doer`) that caps the **start rate** of tasks using a
|
|
166
|
+
* standalone {@link limiter} — the same primitive (and pluggable {@link RateLimitStore}/
|
|
167
|
+
* algorithm) the {@link rateLimit} middleware uses. When the limiter admits a task it is
|
|
168
|
+
* handed to an **inner doer** (default {@link unlimitedDoer}), so you can compose a rate
|
|
169
|
+
* cap with a concurrency/ordering policy (e.g. `rateLimitedDoer({ …, doer: priorityDoer({ max: 4 }) })`).
|
|
170
|
+
* Excess tasks wait, oldest-first; a distributed store rate-limits **across a fleet**.
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { rateLimitedDoer } from '@ayepi/rate'
|
|
174
|
+
* import { createWork } from '@ayepi/work'
|
|
175
|
+
*
|
|
176
|
+
* const doer = rateLimitedDoer({ limit: 100, window: 60_000, algorithm: 'token-bucket' })
|
|
177
|
+
* const w = createWork({ work: [sendEmail] as const, doer })
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
declare function rateLimitedDoer(opts: RateLimitedDoerOptions): Doer;
|
|
181
|
+
/**
|
|
182
|
+
* An in-process {@link RateLimitStore} implementing all three algorithms. The
|
|
183
|
+
* default store — fine for a single instance; use a distributed store (e.g.
|
|
184
|
+
* `@ayepi/rate/redis`) to share limits across pods. Expired entries are swept
|
|
185
|
+
* lazily.
|
|
186
|
+
*/
|
|
187
|
+
declare function memoryStore(): RateLimitStore;
|
|
188
|
+
//#endregion
|
|
189
|
+
export { Algorithm, Limiter, LimiterOptions, RateKeyIO, RateLimitDef, RateLimitDefOptions, RateLimitInfo, RateLimitResponseOptions, RateLimitResult, RateLimitRule, RateLimitStore, RateLimitedDoerOptions, limiter, memoryStore, rateLimit, rateLimitHeaders, rateLimitResponse, rateLimitedDoer };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { AnyMiddleware, Json, MaybePromise, MiddlewareDef } from "@ayepi/core";
|
|
2
|
+
import { Doer, DoerTaskOptions } from "@ayepi/core/doer";
|
|
3
|
+
|
|
4
|
+
//#region src/index.d.ts
|
|
5
|
+
|
|
6
|
+
/** The rate-limiting algorithm. */
|
|
7
|
+
type Algorithm = 'fixed-window' | 'sliding-window' | 'token-bucket';
|
|
8
|
+
/** Limiter state for one key, exposed to handlers (via `ctx.ratelimit`) and response headers. */
|
|
9
|
+
interface RateLimitInfo {
|
|
10
|
+
/** The configured request limit for the window. */
|
|
11
|
+
readonly limit: number;
|
|
12
|
+
/** Requests (or tokens) remaining before the limit is hit. */
|
|
13
|
+
readonly remaining: number;
|
|
14
|
+
/** Milliseconds until the window/bucket resets. */
|
|
15
|
+
readonly reset: number;
|
|
16
|
+
/** Milliseconds to wait before retrying (0 when allowed). */
|
|
17
|
+
readonly retryAfter: number;
|
|
18
|
+
}
|
|
19
|
+
/** A store's decision for one key. */
|
|
20
|
+
interface RateLimitResult extends RateLimitInfo {
|
|
21
|
+
/** Whether this request is within the limit. */
|
|
22
|
+
readonly allowed: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The rule a store evaluates a key against. */
|
|
25
|
+
interface RateLimitRule {
|
|
26
|
+
readonly limit: number;
|
|
27
|
+
readonly window: number;
|
|
28
|
+
readonly algorithm: Algorithm;
|
|
29
|
+
/**
|
|
30
|
+
* Whether a request that is **itself rejected** (over the limit) still counts
|
|
31
|
+
* against the limit. Default `false` — rejected requests are not recorded, so a
|
|
32
|
+
* client cannot extend its own block by continuing to hammer the endpoint. Set
|
|
33
|
+
* `true` for the stricter behavior where every attempt consumes budget.
|
|
34
|
+
* (No effect on `token-bucket`, which never charges a request it can't admit.)
|
|
35
|
+
*/
|
|
36
|
+
readonly countRejected?: boolean;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Pluggable backend that atomically records a hit and decides the outcome.
|
|
40
|
+
* Implementing the algorithm in the store keeps it correct across instances
|
|
41
|
+
* (the in-memory store is single-process; `@ayepi/rate/redis` is distributed).
|
|
42
|
+
*/
|
|
43
|
+
interface RateLimitStore {
|
|
44
|
+
/** Record a hit for `key` under `rule` at time `now` (ms) and return the decision. */
|
|
45
|
+
consume(key: string, rule: RateLimitRule, now: number): MaybePromise<RateLimitResult>;
|
|
46
|
+
/** Clear all state for `key` (optional). */
|
|
47
|
+
reset?(key: string): MaybePromise<void>;
|
|
48
|
+
}
|
|
49
|
+
/** The argument passed to `key`/`skip`/`message` — the request plus accumulated context. */
|
|
50
|
+
interface RateKeyIO<Ctx extends object> {
|
|
51
|
+
readonly req: Request;
|
|
52
|
+
readonly ctx: Ctx;
|
|
53
|
+
}
|
|
54
|
+
/** Configuration for a standalone {@link limiter} — the base of {@link RateLimitOptions}. */
|
|
55
|
+
interface LimiterOptions {
|
|
56
|
+
/** Max requests (or token-bucket capacity) per window. */
|
|
57
|
+
readonly limit: number;
|
|
58
|
+
/** Window length in milliseconds (also the token refill period). */
|
|
59
|
+
readonly window: number;
|
|
60
|
+
/** Algorithm (default `'fixed-window'`). */
|
|
61
|
+
readonly algorithm?: Algorithm;
|
|
62
|
+
/** Backend store (default an in-process {@link memoryStore}). */
|
|
63
|
+
readonly store?: RateLimitStore;
|
|
64
|
+
/** Key prefix/namespace (default `'rl:'`). */
|
|
65
|
+
readonly prefix?: string;
|
|
66
|
+
/**
|
|
67
|
+
* Count requests that are themselves rejected (over-limit) against the limit.
|
|
68
|
+
* Default `false` — see {@link RateLimitRule.countRejected}.
|
|
69
|
+
*/
|
|
70
|
+
readonly countRejected?: boolean;
|
|
71
|
+
}
|
|
72
|
+
/** Response customization shared by {@link rateLimitResponse} and {@link rateLimit}. */
|
|
73
|
+
interface RateLimitResponseOptions {
|
|
74
|
+
/** Over-limit status code (default `429`). */
|
|
75
|
+
readonly status?: number;
|
|
76
|
+
/** Over-limit body — a string, a JSON value, or a function of the limiter info. */
|
|
77
|
+
readonly message?: string | Json | ((info: RateLimitInfo) => string | Json);
|
|
78
|
+
/**
|
|
79
|
+
* Response headers. `true` (default) emits draft `RateLimit-Limit`/`-Remaining`/
|
|
80
|
+
* `-Reset` — plus `Retry-After` **only when the request was rejected**; `false`
|
|
81
|
+
* emits none; a function returns your own map.
|
|
82
|
+
*/
|
|
83
|
+
readonly headers?: boolean | ((info: RateLimitInfo) => Record<string, string>);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Options for the {@link rateLimit} **def** — frontend-safe only.
|
|
87
|
+
*
|
|
88
|
+
* @typeParam R - middleware this one depends on (their context is typed in the
|
|
89
|
+
* server-side `key`/`skip`).
|
|
90
|
+
*/
|
|
91
|
+
interface RateLimitDefOptions<R extends readonly AnyMiddleware[]> {
|
|
92
|
+
/** Middleware this one depends on — their context is available (and typed) in `key`/`skip`. */
|
|
93
|
+
readonly requires?: R;
|
|
94
|
+
/** Middleware name for docs/debugging (default `'rateLimit'`). */
|
|
95
|
+
readonly name?: string;
|
|
96
|
+
}
|
|
97
|
+
/** A reusable rate limiter bound to a rule + store — usable anywhere, not just middleware. */
|
|
98
|
+
interface Limiter {
|
|
99
|
+
/** Record a hit for `key` (at `now`, default `Date.now()`) and return the decision. */
|
|
100
|
+
check(key: string, now?: number): MaybePromise<RateLimitResult>;
|
|
101
|
+
/** Clear all state for `key`. */
|
|
102
|
+
reset(key: string): MaybePromise<void>;
|
|
103
|
+
/** The rule this limiter enforces. */
|
|
104
|
+
readonly rule: RateLimitRule;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Create a standalone {@link Limiter} — the rate-limit primitive the
|
|
108
|
+
* {@link rateLimit} middleware is built on. Use it anywhere you have a key: a
|
|
109
|
+
* plain handler, a queue/cron worker, a CLI, a different framework.
|
|
110
|
+
*
|
|
111
|
+
* ```ts
|
|
112
|
+
* const lim = limiter({ limit: 100, window: 60_000, algorithm: 'token-bucket' })
|
|
113
|
+
* const { allowed, retryAfter } = await lim.check(userId)
|
|
114
|
+
* if (!allowed) throw reject(429, 'RATE_LIMITED', `retry in ${retryAfter}ms`)
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
declare function limiter(opts: LimiterOptions): Limiter;
|
|
118
|
+
/**
|
|
119
|
+
* Compute the rate-limit response headers for `info`. `true` (default) emits the
|
|
120
|
+
* draft `RateLimit-Limit`/`-Remaining`/`-Reset` headers — plus `Retry-After` **only
|
|
121
|
+
* when the request was rejected** (`retryAfter > 0`); `false` emits none; a function
|
|
122
|
+
* returns your own map (which **replaces** the defaults).
|
|
123
|
+
*
|
|
124
|
+
* Shared by {@link rateLimitResponse} (the 429) and the middleware's `alwaysHeaders`
|
|
125
|
+
* option (informational headers on allowed responses).
|
|
126
|
+
*/
|
|
127
|
+
declare function rateLimitHeaders(info: RateLimitInfo, headers?: boolean | ((info: RateLimitInfo) => Record<string, string>)): Record<string, string>;
|
|
128
|
+
/**
|
|
129
|
+
* Build a rate-limit (429) `Response` from limiter info — usable on its own,
|
|
130
|
+
* outside any middleware (e.g. from a handler that called {@link limiter} directly).
|
|
131
|
+
*/
|
|
132
|
+
declare function rateLimitResponse(info: RateLimitInfo, opts?: RateLimitResponseOptions): Response;
|
|
133
|
+
/**
|
|
134
|
+
* Create a rate-limiting middleware **def**. The def declares what the middleware
|
|
135
|
+
* contributes (`{ ratelimit: RateLimitInfo }`) and its dependencies — but **no**
|
|
136
|
+
* policy. Bind the key/limit/window/store with
|
|
137
|
+
* [`rateLimit.server(def, { key, limit, window })`](./server).
|
|
138
|
+
*
|
|
139
|
+
* @typeParam R - inferred from `requires`; their context types flow into the
|
|
140
|
+
* server-side `key`/`skip`/`message`.
|
|
141
|
+
*/
|
|
142
|
+
declare function rateLimit<const R extends readonly AnyMiddleware[] = readonly []>(opts?: RateLimitDefOptions<R>): RateLimitDef<R>;
|
|
143
|
+
/** The def type a {@link rateLimit} call produces — what `rateLimit.server` binds against. */
|
|
144
|
+
type RateLimitDef<R extends readonly AnyMiddleware[] = readonly []> = MiddlewareDef<{
|
|
145
|
+
ratelimit: RateLimitInfo;
|
|
146
|
+
}, R>;
|
|
147
|
+
/** Options for {@link rateLimitedDoer} — a {@link LimiterOptions} plus doer-specific knobs. */
|
|
148
|
+
interface RateLimitedDoerOptions extends LimiterOptions {
|
|
149
|
+
/** Limit key — a single shared bucket by default (`'doer'`), or derived per task. */
|
|
150
|
+
readonly key?: string | ((opts: DoerTaskOptions) => string);
|
|
151
|
+
/** Floor on the re-check delay for deferred tasks (ms, default 50). */
|
|
152
|
+
readonly retryFloor?: number;
|
|
153
|
+
/** Clock injection (default `Date.now`). */
|
|
154
|
+
readonly now?: () => number;
|
|
155
|
+
/** The doer that actually runs admitted tasks (default {@link unlimitedDoer}). Compose policies. */
|
|
156
|
+
readonly doer?: Doer;
|
|
157
|
+
/**
|
|
158
|
+
* Observe a store error during admission (e.g. a distributed store hiccup). The drain loop
|
|
159
|
+
* never crashes on it — the task stays pending and admission is retried shortly. Off by
|
|
160
|
+
* default; it must not throw — if it does, the throw is ignored.
|
|
161
|
+
*/
|
|
162
|
+
readonly onError?: (err: unknown) => void;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* A {@link Doer} (see `@ayepi/core/doer`) that caps the **start rate** of tasks using a
|
|
166
|
+
* standalone {@link limiter} — the same primitive (and pluggable {@link RateLimitStore}/
|
|
167
|
+
* algorithm) the {@link rateLimit} middleware uses. When the limiter admits a task it is
|
|
168
|
+
* handed to an **inner doer** (default {@link unlimitedDoer}), so you can compose a rate
|
|
169
|
+
* cap with a concurrency/ordering policy (e.g. `rateLimitedDoer({ …, doer: priorityDoer({ max: 4 }) })`).
|
|
170
|
+
* Excess tasks wait, oldest-first; a distributed store rate-limits **across a fleet**.
|
|
171
|
+
*
|
|
172
|
+
* ```ts
|
|
173
|
+
* import { rateLimitedDoer } from '@ayepi/rate'
|
|
174
|
+
* import { createWork } from '@ayepi/work'
|
|
175
|
+
*
|
|
176
|
+
* const doer = rateLimitedDoer({ limit: 100, window: 60_000, algorithm: 'token-bucket' })
|
|
177
|
+
* const w = createWork({ work: [sendEmail] as const, doer })
|
|
178
|
+
* ```
|
|
179
|
+
*/
|
|
180
|
+
declare function rateLimitedDoer(opts: RateLimitedDoerOptions): Doer;
|
|
181
|
+
/**
|
|
182
|
+
* An in-process {@link RateLimitStore} implementing all three algorithms. The
|
|
183
|
+
* default store — fine for a single instance; use a distributed store (e.g.
|
|
184
|
+
* `@ayepi/rate/redis`) to share limits across pods. Expired entries are swept
|
|
185
|
+
* lazily.
|
|
186
|
+
*/
|
|
187
|
+
declare function memoryStore(): RateLimitStore;
|
|
188
|
+
//#endregion
|
|
189
|
+
export { Algorithm, Limiter, LimiterOptions, RateKeyIO, RateLimitDef, RateLimitDefOptions, RateLimitInfo, RateLimitResponseOptions, RateLimitResult, RateLimitRule, RateLimitStore, RateLimitedDoerOptions, limiter, memoryStore, rateLimit, rateLimitHeaders, rateLimitResponse, rateLimitedDoer };
|