@async-kit/ratelimitx 0.1.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ ## 0.1.2 (2026-03-11)
2
+
3
+ ### 🩹 Fixes
4
+
5
+ - **release:** add publishConfig access public to all packages ([82c12ca](https://github.com/NexaLeaf/async-kit/commit/82c12ca))
6
+
7
+ ### ❤️ Thank You
8
+
9
+ - Palanisamy Muthusamy
package/README.md ADDED
@@ -0,0 +1,385 @@
1
+ <div align="center">
2
+
3
+ <img src="https://capsule-render.vercel.app/api?type=rect&color=gradient&customColorList=9&height=120&section=header&text=ratelimitx&fontSize=50&fontColor=fff&animation=fadeIn&desc=%40async-kit%2Fratelimitx&descAlignY=75&descAlign=50" width="100%"/>
4
+
5
+ <br/>
6
+
7
+ [![npm](https://img.shields.io/npm/v/@async-kit/ratelimitx?style=for-the-badge&logo=npm&color=96CEB4)](https://www.npmjs.com/package/@async-kit/ratelimitx)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178C6?style=for-the-badge&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](../../LICENSE)
10
+ [![Bundle size](https://img.shields.io/bundlephobia/minzip/@async-kit/ratelimitx?style=for-the-badge&color=96CEB4)](https://bundlephobia.com/package/@async-kit/ratelimitx)
11
+ [![Node](https://img.shields.io/badge/Node-%3E%3D18-339933?style=for-the-badge&logo=node.js&logoColor=white)](https://nodejs.org/)
12
+ [![Browser](https://img.shields.io/badge/Browser-Supported-4285F4?style=for-the-badge&logo=googlechrome&logoColor=white)](#compatibility)
13
+
14
+ **Multi-algorithm rate limiter — Token Bucket, Sliding Window, Fixed Window, and Composite enforcement with AbortSignal support.**
15
+
16
+ *Four algorithms, one interface. Zero dependencies.*
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ npm install @async-kit/ratelimitx
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```typescript
31
+ import { TokenBucket, SlidingWindow, FixedWindow, CompositeLimiter } from '@async-kit/ratelimitx';
32
+
33
+ // Token Bucket — burst-friendly
34
+ const bucket = new TokenBucket({ capacity: 10, refillRate: 2, refillInterval: 1000 });
35
+ await bucket.consume(); // waits until a token is available
36
+
37
+ // Sliding Window — strict per-window limit
38
+ const window = new SlidingWindow({ windowMs: 60_000, maxRequests: 100 });
39
+ await window.waitAndAcquire();
40
+
41
+ // Fixed Window — simplest, resets on schedule
42
+ const fw = new FixedWindow({ windowMs: 60_000, maxRequests: 100 });
43
+ fw.acquire(); // throws RateLimitError if over limit
44
+
45
+ // Composite — enforce multiple tiers simultaneously
46
+ const limiter = new CompositeLimiter([
47
+ new TokenBucket({ capacity: 10, refillRate: 10, refillInterval: 1000 }),
48
+ new SlidingWindow({ windowMs: 60_000, maxRequests: 500 }),
49
+ ]);
50
+ await limiter.waitAndAcquire();
51
+ ```
52
+
53
+ ## API
54
+
55
+ ### `TokenBucket`
56
+
57
+ Tokens accumulate at `refillRate` per `refillInterval` up to `capacity`. Burst-friendly.
58
+
59
+ ```typescript
60
+ const bucket = new TokenBucket({ capacity: 10, refillRate: 2, refillInterval: 1000 });
61
+ ```
62
+
63
+ | Method | Description |
64
+ |---|---|
65
+ | `.tryConsume(count?)` | Non-blocking; `true` if tokens available |
66
+ | `.consume(count?, signal?)` | **Async — waits** until tokens available; only throws if `count > capacity` |
67
+ | `.acquireOrThrow(count?)` | Throws `RateLimitError` immediately if tokens unavailable |
68
+ | `.tryAcquire()` | Alias for `tryConsume()` |
69
+ | `.acquire()` | Alias for `acquireOrThrow()` |
70
+ | `.waitAndAcquire(signal?)` | Alias for `consume(1, signal)` |
71
+ | `.reset()` | Refill to capacity, reset clock |
72
+ | `.setCapacity(n)` | Hot-resize; clamps current tokens if lower |
73
+ | `.available` | Current token count (after refill) |
74
+
75
+ ### `SlidingWindow`
76
+
77
+ Tracks exact timestamps in a **ring buffer** (`Float64Array`). O(k) prune, no burst.
78
+
79
+ ```typescript
80
+ const win = new SlidingWindow({ windowMs: 60_000, maxRequests: 100 });
81
+ ```
82
+
83
+ | Method | Description |
84
+ |---|---|
85
+ | `.tryAcquire()` | Non-blocking; `true` if a slot is available |
86
+ | `.acquire()` | Throws `RateLimitError` immediately if at limit |
87
+ | `.waitAndAcquire(signal?)` | Async — waits until the oldest request expires |
88
+ | `.currentCount` | Active request count in the current window |
89
+
90
+ ### `FixedWindow`
91
+
92
+ Counts requests in a fixed time bucket that resets every `windowMs`.
93
+
94
+ ```typescript
95
+ const fw = new FixedWindow({
96
+ windowMs: 60_000,
97
+ maxRequests: 100,
98
+ onWindowReset: (t) => console.log('Reset at', t),
99
+ });
100
+ ```
101
+
102
+ | Method | Description |
103
+ |---|---|
104
+ | `.tryAcquire()` | Non-blocking; `true` if under limit |
105
+ | `.acquire()` | Throws `RateLimitError` if at limit |
106
+ | `.waitAndAcquire(signal?)` | Async — waits until the window resets |
107
+ | `.reset()` | Manually reset count (useful in tests) |
108
+ | `.currentCount` | Requests in current window |
109
+ | `.windowResetMs` | Ms remaining until the window resets |
110
+
111
+ ### `CompositeLimiter`
112
+
113
+ Enforces **all** provided limiters simultaneously. Useful for multi-tier API limits.
114
+
115
+ ```typescript
116
+ const limiter = new CompositeLimiter([
117
+ new TokenBucket({ capacity: 10, refillRate: 10, refillInterval: 1000 }),
118
+ new SlidingWindow({ windowMs: 60_000, maxRequests: 500 }),
119
+ new FixedWindow({ windowMs: 3_600_000, maxRequests: 5000 }),
120
+ ]);
121
+ ```
122
+
123
+ | Method | Description |
124
+ |---|---|
125
+ | `.tryAcquire()` | `true` only if **all** limiters pass |
126
+ | `.acquire()` | Throws on the first limiter that rejects |
127
+ | `.waitAndAcquire(signal?)` | Async — waits until all limiters have capacity |
128
+
129
+ ### `RateLimitError`
130
+
131
+ ```typescript
132
+ try {
133
+ limiter.acquire();
134
+ } catch (err) {
135
+ if (err instanceof RateLimitError) {
136
+ console.log(err.algorithm); // 'token-bucket' | 'sliding-window' | 'fixed-window'
137
+ console.log(err.retryAfterMs); // ms to wait before retrying
138
+ console.log(err.limit); // configured limit
139
+ console.log(err.current); // current usage
140
+ }
141
+ }
142
+ ```
143
+
144
+ ## `Limiter` Interface
145
+
146
+ All three classes implement `Limiter`, making them interchangeable:
147
+
148
+ ```typescript
149
+ interface Limiter {
150
+ tryAcquire(): boolean;
151
+ acquire(): void;
152
+ waitAndAcquire(signal?: AbortSignal): Promise<void>;
153
+ }
154
+ ```
155
+
156
+ ## Algorithm Comparison
157
+
158
+ | Algorithm | Burst | Memory | Boundary spike | Best For |
159
+ |---|---|---|---|---|
160
+ | Token Bucket | ✅ Yes | O(1) | No | API quotas, outbound throttling |
161
+ | Sliding Window | ❌ No | O(maxRequests) | No | Strict per-window enforcement |
162
+ | Fixed Window | ✅ At boundary | O(1) | Yes | Simple quotas, easy reasoning |
163
+ | CompositeLimiter | Depends | Combined | Depends | Multi-tier API limits |
164
+
165
+ ## Examples
166
+
167
+ ### Express middleware — per-IP sliding window
168
+
169
+ ```typescript
170
+ import express from 'express';
171
+ import { SlidingWindow, RateLimitError } from '@async-kit/ratelimitx';
172
+
173
+ const app = express();
174
+ const limiters = new Map<string, SlidingWindow>();
175
+
176
+ app.use((req, res, next) => {
177
+ const ip = req.ip ?? 'unknown';
178
+ if (!limiters.has(ip)) {
179
+ limiters.set(ip, new SlidingWindow({ windowMs: 60_000, maxRequests: 100 }));
180
+ }
181
+ try {
182
+ limiters.get(ip)!.acquire();
183
+ next();
184
+ } catch (err) {
185
+ if (err instanceof RateLimitError) {
186
+ res.set('Retry-After', String(Math.ceil(err.retryAfterMs / 1000)));
187
+ res.set('X-RateLimit-Limit', String(err.limit));
188
+ res.set('X-RateLimit-Remaining', '0');
189
+ res.status(429).json({ error: 'Too Many Requests', retryAfterMs: err.retryAfterMs });
190
+ }
191
+ }
192
+ });
193
+ ```
194
+
195
+ ### Outbound API quota — token bucket for GitHub API
196
+
197
+ ```typescript
198
+ import { TokenBucket } from '@async-kit/ratelimitx';
199
+ import { Octokit } from 'octokit';
200
+
201
+ // GitHub: 5 000 authenticated requests / hour ≈ 1.38 / sec
202
+ const github = new TokenBucket({
203
+ capacity: 30, // burst: up to 30 back-to-back
204
+ refillRate: 1,
205
+ refillInterval: 720, // 720 ms ≈ 1.38 tokens/sec
206
+ });
207
+
208
+ const octokit = new Octokit({ auth: process.env.GH_TOKEN });
209
+
210
+ async function githubRequest<T>(fn: () => Promise<T>): Promise<T> {
211
+ // Block until a token is available (never drops requests)
212
+ await github.waitAndAcquire();
213
+ return fn();
214
+ }
215
+
216
+ // Safe to call in a tight loop — will naturally pace itself
217
+ const repos = await githubRequest(() =>
218
+ octokit.rest.repos.listForOrg({ org: 'my-org', per_page: 100 })
219
+ );
220
+ ```
221
+
222
+ ### Multi-tier composite limit (per-second + per-minute + per-hour)
223
+
224
+ ```typescript
225
+ import { TokenBucket, SlidingWindow, FixedWindow, CompositeLimiter } from '@async-kit/ratelimitx';
226
+
227
+ // Model a typical SaaS API tier:
228
+ // • burst 10 back-to-back
229
+ // • 60 / min steady-state
230
+ // • 1 000 / hr hard quota
231
+ const apiLimiter = new CompositeLimiter([
232
+ new TokenBucket({ capacity: 10, refillRate: 10, refillInterval: 1_000 }),
233
+ new SlidingWindow({ windowMs: 60_000, maxRequests: 60 }),
234
+ new FixedWindow ({ windowMs: 3_600_000, maxRequests: 1_000,
235
+ onWindowReset: (t) => console.log('Hourly quota reset at', new Date(t).toISOString()),
236
+ }),
237
+ ]);
238
+
239
+ async function callSaasApi(endpoint: string) {
240
+ await apiLimiter.waitAndAcquire();
241
+ return fetch(endpoint).then(r => r.json());
242
+ }
243
+ ```
244
+
245
+ ### Combine with retryx for automatic backoff
246
+
247
+ ```typescript
248
+ import { SlidingWindow, RateLimitError } from '@async-kit/ratelimitx';
249
+ import { retry } from '@async-kit/retryx';
250
+
251
+ const limiter = new SlidingWindow({ windowMs: 1_000, maxRequests: 10 });
252
+
253
+ const result = await retry(
254
+ () => { limiter.acquire(); return callApi(); },
255
+ {
256
+ maxAttempts: 60,
257
+ retryIf: (err) => err instanceof RateLimitError,
258
+ onRetry: (_n, err) => {
259
+ const wait = (err as RateLimitError).retryAfterMs;
260
+ console.log(`Rate limited — retrying in ${wait}ms`);
261
+ },
262
+ }
263
+ );
264
+ ```
265
+
266
+ ### Async queue consumer with `waitAndAcquire`
267
+
268
+ ```typescript
269
+ import { TokenBucket } from '@async-kit/ratelimitx';
270
+
271
+ const bucket = new TokenBucket({ capacity: 5, refillRate: 5, refillInterval: 1_000 });
272
+
273
+ // Consumer loop — processes at most 5 items/sec no matter how fast items arrive
274
+ async function processQueue(queue: AsyncIterable<Job>) {
275
+ for await (const job of queue) {
276
+ await bucket.waitAndAcquire(); // blocks here when the bucket is empty
277
+ void processJob(job); // fire without awaiting
278
+ }
279
+ }
280
+ ```
281
+
282
+ ### Fixed window with reset callback for quota UI
283
+
284
+ ```typescript
285
+ import { FixedWindow } from '@async-kit/ratelimitx';
286
+
287
+ let remaining = 100;
288
+
289
+ const fw = new FixedWindow({
290
+ windowMs: 60_000,
291
+ maxRequests: 100,
292
+ onWindowReset: () => { remaining = 100; },
293
+ });
294
+
295
+ function tryRequest(): { allowed: boolean; remaining: number; resetIn: number } {
296
+ const allowed = fw.tryAcquire();
297
+ if (allowed) remaining--;
298
+ return { allowed, remaining, resetIn: fw.windowResetMs };
299
+ }
300
+ ```
301
+
302
+ ### Dynamic capacity — hot-resize for plan upgrades
303
+
304
+ ```typescript
305
+ import { TokenBucket } from '@async-kit/ratelimitx';
306
+
307
+ const bucket = new TokenBucket({ capacity: 10, refillRate: 10, refillInterval: 1_000 });
308
+
309
+ // User upgrades from Free (10/s) to Pro (100/s) — no restart needed
310
+ async function onPlanUpgrade(userId: string, newPlan: 'free' | 'pro') {
311
+ const newCapacity = newPlan === 'pro' ? 100 : 10;
312
+ bucket.setCapacity(newCapacity);
313
+ console.log(`User ${userId} upgraded to ${newPlan} — new capacity: ${newCapacity}`);
314
+ }
315
+ ```
316
+
317
+ ### Custom `Limiter` implementation
318
+
319
+ ```typescript
320
+ import type { Limiter } from '@async-kit/ratelimitx';
321
+ import { CompositeLimiter } from '@async-kit/ratelimitx';
322
+
323
+ // Roll your own limiter and plug it into CompositeLimiter
324
+ class DailyQuotaLimiter implements Limiter {
325
+ private used = 0;
326
+ constructor(private readonly limit: number) {}
327
+
328
+ tryAcquire(): boolean {
329
+ if (this.used < this.limit) { this.used++; return true; }
330
+ return false;
331
+ }
332
+
333
+ acquire(): void {
334
+ if (!this.tryAcquire()) throw new Error(`Daily quota of ${this.limit} exhausted`);
335
+ }
336
+
337
+ async waitAndAcquire(): Promise<void> {
338
+ // Block until midnight UTC
339
+ const ms = msUntilMidnightUTC();
340
+ await new Promise(r => setTimeout(r, ms));
341
+ this.used = 0;
342
+ this.used++;
343
+ }
344
+ }
345
+
346
+ const composite = new CompositeLimiter([
347
+ new SlidingWindow({ windowMs: 1_000, maxRequests: 10 }),
348
+ new DailyQuotaLimiter(10_000),
349
+ ]);
350
+ ```
351
+
352
+ ## Types
353
+
354
+ ```typescript
355
+ import type {
356
+ TokenBucketOptions,
357
+ SlidingWindowOptions,
358
+ FixedWindowOptions,
359
+ Limiter,
360
+ RateLimitAlgorithm,
361
+ } from '@async-kit/ratelimitx';
362
+ ```
363
+
364
+ ## Compatibility
365
+
366
+ | Environment | Support | Notes |
367
+ |---|---|---|
368
+ | **Node.js** | ≥ 18 | Recommended ≥ 24 for best performance |
369
+ | **Deno** | ✅ | Via npm specifier (`npm:@async-kit/ratelimitx`) |
370
+ | **Bun** | ✅ | Full support |
371
+ | **Chrome** | ≥ 80 | ESM via bundler or native import |
372
+ | **Firefox** | ≥ 75 | ESM via bundler or native import |
373
+ | **Safari** | ≥ 13.1 | ESM via bundler or native import |
374
+ | **Edge** | ≥ 80 | ESM via bundler or native import |
375
+ | **React Native** | ✅ | Via Metro bundler |
376
+ | **Cloudflare Workers** | ✅ | ESM, `AbortSignal` natively supported |
377
+ | **Vercel Edge Runtime** | ✅ | ESM, no `process` / `fs` dependencies |
378
+
379
+ **No Node.js built-ins are used.** The package relies only on standard JavaScript (`Promise`, `setTimeout`, `clearTimeout`, `Float64Array`, `AbortSignal`, `DOMException`) — universally available in modern runtimes and browsers.
380
+
381
+ > **`Float64Array`** (used by `SlidingWindow`'s ring buffer) is part of the ECMAScript spec and available in every JavaScript environment including old browsers and edge workers.
382
+
383
+ ## License
384
+
385
+ MIT © async-kit contributors · Part of the [async-kit](../../README.md) ecosystem
@@ -0,0 +1,167 @@
1
+ /** Constructor options for `TokenBucket`. */
2
+ interface TokenBucketOptions {
3
+ /** Maximum token capacity (burst size). */
4
+ capacity: number;
5
+ /** Tokens added per `refillInterval`. */
6
+ refillRate: number;
7
+ /** Interval in ms at which tokens are added. Default: `1000`. */
8
+ refillInterval?: number;
9
+ }
10
+ /** Constructor options for `SlidingWindow`. */
11
+ interface SlidingWindowOptions {
12
+ /** Rolling window duration in ms. */
13
+ windowMs: number;
14
+ /** Maximum requests allowed per window. */
15
+ maxRequests: number;
16
+ }
17
+ /** Constructor options for `FixedWindow`. */
18
+ interface FixedWindowOptions {
19
+ /** Window duration in ms. */
20
+ windowMs: number;
21
+ /** Maximum requests per window. */
22
+ maxRequests: number;
23
+ /** Called each time the window resets. */
24
+ onWindowReset?: (windowStart: number) => void;
25
+ }
26
+ /**
27
+ * Common interface implemented by all three rate-limiter classes
28
+ * and accepted by `CompositeLimiter`.
29
+ */
30
+ interface Limiter {
31
+ tryAcquire(): boolean;
32
+ acquire(): void;
33
+ waitAndAcquire(signal?: AbortSignal): Promise<void>;
34
+ }
35
+ /** Algorithm tag used in `RateLimitError`. */
36
+ type RateLimitAlgorithm = 'token-bucket' | 'sliding-window' | 'fixed-window';
37
+
38
+ declare class RateLimitError extends Error {
39
+ readonly retryAfterMs: number;
40
+ readonly algorithm: 'token-bucket' | 'sliding-window' | 'fixed-window';
41
+ readonly limit: number;
42
+ readonly current: number;
43
+ constructor(message: string, retryAfterMs: number, algorithm: 'token-bucket' | 'sliding-window' | 'fixed-window', limit: number, current: number);
44
+ }
45
+ /**
46
+ * Token Bucket rate limiter.
47
+ *
48
+ * Tokens accumulate over time up to `capacity`. Burst-friendly — allows up
49
+ * to `capacity` back-to-back calls as long as the bucket is full.
50
+ *
51
+ * - `tryConsume()` — non-throwing, returns `true` if tokens available.
52
+ * - `consume()` — async, **blocks** until tokens are available.
53
+ * - `available` — current token count (after refill).
54
+ *
55
+ * @example
56
+ * const bucket = new TokenBucket({ capacity: 10, refillRate: 2, refillInterval: 1000 });
57
+ * await bucket.consume(); // waits until a token is available
58
+ */
59
+ declare class TokenBucket {
60
+ private tokens;
61
+ private lastRefillTime;
62
+ private capacity;
63
+ private readonly refillRate;
64
+ private readonly refillInterval;
65
+ constructor(options: TokenBucketOptions);
66
+ private refill;
67
+ get available(): number;
68
+ tryConsume(count?: number): boolean;
69
+ /**
70
+ * Waits until `count` tokens are available, then consumes them.
71
+ * Throws `RateLimitError` only if the request can never be satisfied
72
+ * (i.e. `count > capacity`). Cancellable via `AbortSignal`.
73
+ */
74
+ consume(count?: number, signal?: AbortSignal): Promise<void>;
75
+ /** Alias for `acquireOrThrow()` — satisfies the `Limiter` interface. */
76
+ acquire(count?: number): void;
77
+ /** Satisfies the `Limiter` interface — alias for `tryConsume()`. */
78
+ tryAcquire(): boolean;
79
+ /** Satisfies the `Limiter` interface — alias for `consume()`. */
80
+ waitAndAcquire(signal?: AbortSignal): Promise<void>;
81
+ /** Throws immediately if tokens are unavailable (non-blocking equivalent of `consume`). */
82
+ acquireOrThrow(count?: number): void;
83
+ /** Refill to full capacity and reset the refill clock. */
84
+ reset(): void;
85
+ /** Hot-resize capacity. Clamps current tokens if the new capacity is lower. */
86
+ setCapacity(capacity: number): void;
87
+ }
88
+ /**
89
+ * Sliding Window rate limiter.
90
+ *
91
+ * Tracks exact request timestamps in a ring buffer. Strict enforcement —
92
+ * no burst beyond `maxRequests` regardless of spacing.
93
+ *
94
+ * - `tryAcquire()` — non-throwing.
95
+ * - `acquire()` — throws `RateLimitError` immediately.
96
+ * - `waitAndAcquire()` — async, blocks until a slot is available.
97
+ *
98
+ * @example
99
+ * const window = new SlidingWindow({ windowMs: 60_000, maxRequests: 100 });
100
+ * await window.waitAndAcquire(); // queues caller until a slot opens
101
+ */
102
+ declare class SlidingWindow {
103
+ private readonly ring;
104
+ private head;
105
+ private count;
106
+ private readonly windowMs;
107
+ private readonly maxRequests;
108
+ constructor(options: SlidingWindowOptions);
109
+ private prune;
110
+ get currentCount(): number;
111
+ tryAcquire(): boolean;
112
+ acquire(): void;
113
+ /** Blocks until a slot is available, then acquires it. Cancellable via AbortSignal. */
114
+ waitAndAcquire(signal?: AbortSignal): Promise<void>;
115
+ }
116
+ /**
117
+ * Fixed Window rate limiter.
118
+ *
119
+ * Simplest algorithm — counts requests in a fixed time bucket that resets
120
+ * every `windowMs`. Easiest to reason about; susceptible to boundary spikes.
121
+ *
122
+ * @example
123
+ * const fw = new FixedWindow({ windowMs: 60_000, maxRequests: 100 });
124
+ * fw.acquire(); // throws if over limit in current window
125
+ */
126
+ declare class FixedWindow {
127
+ private count;
128
+ private windowStart;
129
+ private readonly windowMs;
130
+ private readonly maxRequests;
131
+ private readonly onWindowReset?;
132
+ constructor(options: FixedWindowOptions);
133
+ private tick;
134
+ get currentCount(): number;
135
+ /** Ms until the current window resets. */
136
+ get windowResetMs(): number;
137
+ tryAcquire(): boolean;
138
+ acquire(): void;
139
+ waitAndAcquire(signal?: AbortSignal): Promise<void>;
140
+ /** Manually reset the window (useful in tests). */
141
+ reset(): void;
142
+ }
143
+ /**
144
+ * Composite rate limiter — enforces multiple limits simultaneously.
145
+ *
146
+ * All limiters must pass for a call to be allowed. Useful when APIs enforce
147
+ * multiple tiers (e.g. 10/second AND 500/minute AND 5000/hour).
148
+ *
149
+ * @example
150
+ * const limiter = new CompositeLimiter([
151
+ * new TokenBucket({ capacity: 10, refillRate: 10, refillInterval: 1000 }),
152
+ * new SlidingWindow({ windowMs: 60_000, maxRequests: 500 }),
153
+ * ]);
154
+ * await limiter.waitAndAcquire();
155
+ */
156
+ declare class CompositeLimiter {
157
+ private readonly limiters;
158
+ constructor(limiters: Limiter[]);
159
+ /** Returns `true` only if ALL limiters can acquire. Rolls back on partial failure. */
160
+ tryAcquire(): boolean;
161
+ /** Throws `RateLimitError` with the most restrictive `retryAfterMs`. */
162
+ acquire(): void;
163
+ /** Waits for ALL limiters to have capacity, then acquires atomically. */
164
+ waitAndAcquire(signal?: AbortSignal): Promise<void>;
165
+ }
166
+
167
+ export { CompositeLimiter, FixedWindow, type FixedWindowOptions, type Limiter, type RateLimitAlgorithm, RateLimitError, SlidingWindow, type SlidingWindowOptions, TokenBucket, type TokenBucketOptions };