@zeitar/throttle 1.0.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.md +24 -0
- package/README.md +204 -0
- package/dist/CompoundLimiter.d.ts +33 -0
- package/dist/CompoundLimiter.d.ts.map +1 -0
- package/dist/CompoundLimiter.js +62 -0
- package/dist/CompoundLimiter.js.map +1 -0
- package/dist/CompoundRateLimiterFactory.d.ts +19 -0
- package/dist/CompoundRateLimiterFactory.d.ts.map +1 -0
- package/dist/CompoundRateLimiterFactory.js +29 -0
- package/dist/CompoundRateLimiterFactory.js.map +1 -0
- package/dist/LimiterInterface.d.ts +32 -0
- package/dist/LimiterInterface.d.ts.map +1 -0
- package/dist/LimiterInterface.js +3 -0
- package/dist/LimiterInterface.js.map +1 -0
- package/dist/LimiterStateInterface.d.ts +16 -0
- package/dist/LimiterStateInterface.d.ts.map +1 -0
- package/dist/LimiterStateInterface.js +3 -0
- package/dist/LimiterStateInterface.js.map +1 -0
- package/dist/RateLimit.d.ts +43 -0
- package/dist/RateLimit.d.ts.map +1 -0
- package/dist/RateLimit.js +68 -0
- package/dist/RateLimit.js.map +1 -0
- package/dist/RateLimiterFactory.d.ts +83 -0
- package/dist/RateLimiterFactory.d.ts.map +1 -0
- package/dist/RateLimiterFactory.js +115 -0
- package/dist/RateLimiterFactory.js.map +1 -0
- package/dist/RateLimiterFactoryInterface.d.ts +17 -0
- package/dist/RateLimiterFactoryInterface.d.ts.map +1 -0
- package/dist/RateLimiterFactoryInterface.js +3 -0
- package/dist/RateLimiterFactoryInterface.js.map +1 -0
- package/dist/Reservation.d.ts +29 -0
- package/dist/Reservation.d.ts.map +1 -0
- package/dist/Reservation.js +44 -0
- package/dist/Reservation.js.map +1 -0
- package/dist/__tests__/CompoundLimiter.test.d.ts +2 -0
- package/dist/__tests__/CompoundLimiter.test.d.ts.map +1 -0
- package/dist/__tests__/CompoundLimiter.test.js +231 -0
- package/dist/__tests__/CompoundLimiter.test.js.map +1 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.d.ts +2 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.d.ts.map +1 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.js +213 -0
- package/dist/__tests__/CompoundRateLimiterFactory.test.js.map +1 -0
- package/dist/__tests__/RateLimit.test.d.ts +2 -0
- package/dist/__tests__/RateLimit.test.d.ts.map +1 -0
- package/dist/__tests__/RateLimit.test.js +108 -0
- package/dist/__tests__/RateLimit.test.js.map +1 -0
- package/dist/__tests__/RateLimiterFactory.test.d.ts +2 -0
- package/dist/__tests__/RateLimiterFactory.test.d.ts.map +1 -0
- package/dist/__tests__/RateLimiterFactory.test.js +323 -0
- package/dist/__tests__/RateLimiterFactory.test.js.map +1 -0
- package/dist/__tests__/Reservation.test.d.ts +2 -0
- package/dist/__tests__/Reservation.test.d.ts.map +1 -0
- package/dist/__tests__/Reservation.test.js +110 -0
- package/dist/__tests__/Reservation.test.js.map +1 -0
- package/dist/errors/InvalidIntervalError.d.ts +10 -0
- package/dist/errors/InvalidIntervalError.d.ts.map +1 -0
- package/dist/errors/InvalidIntervalError.js +18 -0
- package/dist/errors/InvalidIntervalError.js.map +1 -0
- package/dist/errors/MaxWaitDurationExceededError.d.ts +15 -0
- package/dist/errors/MaxWaitDurationExceededError.d.ts.map +1 -0
- package/dist/errors/MaxWaitDurationExceededError.js +24 -0
- package/dist/errors/MaxWaitDurationExceededError.js.map +1 -0
- package/dist/errors/RateLimitExceededError.d.ts +27 -0
- package/dist/errors/RateLimitExceededError.d.ts.map +1 -0
- package/dist/errors/RateLimitExceededError.js +42 -0
- package/dist/errors/RateLimitExceededError.js.map +1 -0
- package/dist/errors/ReserveNotSupportedError.d.ts +10 -0
- package/dist/errors/ReserveNotSupportedError.d.ts.map +1 -0
- package/dist/errors/ReserveNotSupportedError.js +18 -0
- package/dist/errors/ReserveNotSupportedError.js.map +1 -0
- package/dist/index.d.ts +35 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/policy/FixedWindowLimiter.d.ts +36 -0
- package/dist/policy/FixedWindowLimiter.d.ts.map +1 -0
- package/dist/policy/FixedWindowLimiter.js +105 -0
- package/dist/policy/FixedWindowLimiter.js.map +1 -0
- package/dist/policy/NoLimiter.d.ts +23 -0
- package/dist/policy/NoLimiter.d.ts.map +1 -0
- package/dist/policy/NoLimiter.js +34 -0
- package/dist/policy/NoLimiter.js.map +1 -0
- package/dist/policy/Rate.d.ts +69 -0
- package/dist/policy/Rate.d.ts.map +1 -0
- package/dist/policy/Rate.js +121 -0
- package/dist/policy/Rate.js.map +1 -0
- package/dist/policy/SlidingWindow.d.ts +74 -0
- package/dist/policy/SlidingWindow.d.ts.map +1 -0
- package/dist/policy/SlidingWindow.js +130 -0
- package/dist/policy/SlidingWindow.js.map +1 -0
- package/dist/policy/SlidingWindowLimiter.d.ts +41 -0
- package/dist/policy/SlidingWindowLimiter.d.ts.map +1 -0
- package/dist/policy/SlidingWindowLimiter.js +127 -0
- package/dist/policy/SlidingWindowLimiter.js.map +1 -0
- package/dist/policy/TokenBucket.d.ts +63 -0
- package/dist/policy/TokenBucket.d.ts.map +1 -0
- package/dist/policy/TokenBucket.js +92 -0
- package/dist/policy/TokenBucket.js.map +1 -0
- package/dist/policy/TokenBucketLimiter.d.ts +38 -0
- package/dist/policy/TokenBucketLimiter.d.ts.map +1 -0
- package/dist/policy/TokenBucketLimiter.js +114 -0
- package/dist/policy/TokenBucketLimiter.js.map +1 -0
- package/dist/policy/Window.d.ts +58 -0
- package/dist/policy/Window.d.ts.map +1 -0
- package/dist/policy/Window.js +105 -0
- package/dist/policy/Window.js.map +1 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.js +180 -0
- package/dist/policy/__tests__/FixedWindowLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/NoLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/NoLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/NoLimiter.test.js +40 -0
- package/dist/policy/__tests__/NoLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/Rate.test.d.ts +2 -0
- package/dist/policy/__tests__/Rate.test.d.ts.map +1 -0
- package/dist/policy/__tests__/Rate.test.js +162 -0
- package/dist/policy/__tests__/Rate.test.js.map +1 -0
- package/dist/policy/__tests__/SlidingWindow.test.d.ts +2 -0
- package/dist/policy/__tests__/SlidingWindow.test.d.ts.map +1 -0
- package/dist/policy/__tests__/SlidingWindow.test.js +257 -0
- package/dist/policy/__tests__/SlidingWindow.test.js.map +1 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.js +201 -0
- package/dist/policy/__tests__/SlidingWindowLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/TokenBucket.test.d.ts +2 -0
- package/dist/policy/__tests__/TokenBucket.test.d.ts.map +1 -0
- package/dist/policy/__tests__/TokenBucket.test.js +171 -0
- package/dist/policy/__tests__/TokenBucket.test.js.map +1 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.d.ts +2 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.d.ts.map +1 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.js +175 -0
- package/dist/policy/__tests__/TokenBucketLimiter.test.js.map +1 -0
- package/dist/policy/__tests__/Window.test.d.ts +2 -0
- package/dist/policy/__tests__/Window.test.d.ts.map +1 -0
- package/dist/policy/__tests__/Window.test.js +193 -0
- package/dist/policy/__tests__/Window.test.js.map +1 -0
- package/dist/storage/InMemoryStorage.d.ts +34 -0
- package/dist/storage/InMemoryStorage.d.ts.map +1 -0
- package/dist/storage/InMemoryStorage.js +62 -0
- package/dist/storage/InMemoryStorage.js.map +1 -0
- package/dist/storage/LockInterface.d.ts +41 -0
- package/dist/storage/LockInterface.d.ts.map +1 -0
- package/dist/storage/LockInterface.js +19 -0
- package/dist/storage/LockInterface.js.map +1 -0
- package/dist/storage/StorageInterface.d.ts +29 -0
- package/dist/storage/StorageInterface.d.ts.map +1 -0
- package/dist/storage/StorageInterface.js +3 -0
- package/dist/storage/StorageInterface.js.map +1 -0
- package/dist/storage/__tests__/InMemoryStorage.test.d.ts +2 -0
- package/dist/storage/__tests__/InMemoryStorage.test.d.ts.map +1 -0
- package/dist/storage/__tests__/InMemoryStorage.test.js +154 -0
- package/dist/storage/__tests__/InMemoryStorage.test.js.map +1 -0
- package/dist/util/TimeUtil.d.ts +35 -0
- package/dist/util/TimeUtil.d.ts.map +1 -0
- package/dist/util/TimeUtil.js +87 -0
- package/dist/util/TimeUtil.js.map +1 -0
- package/dist/util/__tests__/TimeUtil.test.d.ts +2 -0
- package/dist/util/__tests__/TimeUtil.test.d.ts.map +1 -0
- package/dist/util/__tests__/TimeUtil.test.js +132 -0
- package/dist/util/__tests__/TimeUtil.test.js.map +1 -0
- package/package.json +59 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Khaled Zeitar
|
|
4
|
+
|
|
5
|
+
This library implements rate limiting algorithms with architecture inspired by
|
|
6
|
+
Symfony's Rate Limiter component.
|
|
7
|
+
|
|
8
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
9
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
10
|
+
in the Software without restriction, including without limitation the rights
|
|
11
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
12
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
13
|
+
furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all
|
|
16
|
+
copies or substantial portions of the Software.
|
|
17
|
+
|
|
18
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
21
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
22
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
23
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
24
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# @zeitar/throttle
|
|
2
|
+
|
|
3
|
+
A production-ready TypeScript rate limiting library with support for multiple algorithms.
|
|
4
|
+
|
|
5
|
+
> Architecture inspired by [Symfony's Rate Limiter component](https://symfony.com/doc/current/rate_limiter.html), implemented natively in TypeScript with async/await patterns for Node.js.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@zeitar/throttle)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- 🚀 **Multiple Algorithms**: Token Bucket, Fixed Window, Sliding Window, and No-Limit policies
|
|
13
|
+
- 📦 **TypeScript Native**: Full type safety with strict typing
|
|
14
|
+
- 🔒 **Production Ready**: Thread-safe with optional distributed locking support
|
|
15
|
+
- 🧩 **Composable**: Combine multiple limiters with CompoundLimiter
|
|
16
|
+
- 💾 **Pluggable Storage**: In-memory storage included, easily extend for Redis, etc.
|
|
17
|
+
- ⚡ **High Performance**: Efficient algorithms with minimal overhead (O(1) time complexity)
|
|
18
|
+
- 🎯 **Zero Dependencies**: Core library has no external dependencies
|
|
19
|
+
- 🔌 **Framework Agnostic**: Works with Express, Fastify, or any Node.js framework
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @zeitar/throttle
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Quick Start
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
import { RateLimiterFactory, InMemoryStorage } from '@zeitar/throttle';
|
|
31
|
+
|
|
32
|
+
// Create a rate limiter factory
|
|
33
|
+
const factory = new RateLimiterFactory(
|
|
34
|
+
{
|
|
35
|
+
policy: 'token_bucket',
|
|
36
|
+
id: 'api',
|
|
37
|
+
limit: 100, // Burst size
|
|
38
|
+
rate: {
|
|
39
|
+
interval: '1 hour', // Refill interval
|
|
40
|
+
amount: 100 // Tokens per interval
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
new InMemoryStorage()
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Create a limiter for a specific user
|
|
47
|
+
const limiter = factory.create('user-123');
|
|
48
|
+
|
|
49
|
+
// Try to consume tokens
|
|
50
|
+
const result = await limiter.consume(5);
|
|
51
|
+
|
|
52
|
+
if (result.isAccepted()) {
|
|
53
|
+
console.log(`✓ Request accepted! ${result.getRemainingTokens()} tokens remaining`);
|
|
54
|
+
} else {
|
|
55
|
+
console.log(`✗ Rate limited. Retry after ${result.getRetryAfter()} seconds`);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Express Middleware Example
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
import { RateLimiterFactory, InMemoryStorage } from '@zeitar/throttle';
|
|
63
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
64
|
+
|
|
65
|
+
const factory = new RateLimiterFactory(
|
|
66
|
+
{
|
|
67
|
+
policy: 'token_bucket',
|
|
68
|
+
id: 'api',
|
|
69
|
+
limit: 100,
|
|
70
|
+
rate: { interval: '1 minute', amount: 100 }
|
|
71
|
+
},
|
|
72
|
+
new InMemoryStorage()
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
app.use(async (req: Request, res: Response, next: NextFunction) => {
|
|
76
|
+
const limiter = factory.create(req.ip);
|
|
77
|
+
const result = await limiter.consume();
|
|
78
|
+
|
|
79
|
+
res.setHeader('X-RateLimit-Remaining', result.getRemainingTokens().toString());
|
|
80
|
+
|
|
81
|
+
if (!result.isAccepted()) {
|
|
82
|
+
res.setHeader('Retry-After', result.getRetryAfter().toString());
|
|
83
|
+
return res.status(429).json({
|
|
84
|
+
error: 'Too many requests',
|
|
85
|
+
retryAfter: result.getRetryAfter()
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
next();
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Documentation
|
|
94
|
+
|
|
95
|
+
### Getting Started
|
|
96
|
+
- **[Getting Started Guide](./docs/getting-started.md)** - Installation, basic usage, and testing
|
|
97
|
+
- **[Choosing an Algorithm](./docs/algorithms.md)** - Algorithm comparison and selection guide
|
|
98
|
+
- **[Framework Integration](./docs/framework-integration.md)** - Express, Fastify, NestJS, Koa, Hono examples
|
|
99
|
+
|
|
100
|
+
### Core Concepts
|
|
101
|
+
- **[Common Patterns](./docs/common-patterns.md)** - User tiers, endpoint limits, global+per-user patterns
|
|
102
|
+
- **[Advanced Usage](./docs/advanced-usage.md)** - Reservation pattern, compound limiters, dynamic creation
|
|
103
|
+
- **[Custom Storage](./docs/custom-storage.md)** - Redis, PostgreSQL, DynamoDB implementations
|
|
104
|
+
- **[API Reference](./docs/api-reference.md)** - Complete API documentation
|
|
105
|
+
|
|
106
|
+
### Help & Troubleshooting
|
|
107
|
+
- **[Troubleshooting](./docs/troubleshooting.md)** - Common issues and solutions
|
|
108
|
+
|
|
109
|
+
## Algorithm Comparison
|
|
110
|
+
|
|
111
|
+
| Algorithm | Best For | Allows Bursts? | Precision |
|
|
112
|
+
|-----------|----------|----------------|-----------|
|
|
113
|
+
| **Token Bucket** ⭐ | Most APIs, microservices | ✅ Yes | Medium |
|
|
114
|
+
| **Fixed Window** | Daily quotas, analytics | ⚠️ At boundaries | Low |
|
|
115
|
+
| **Sliding Window** | High-security APIs, payments | ❌ No | High |
|
|
116
|
+
| **No Limit** | Testing, feature flags | ✅ Always | N/A |
|
|
117
|
+
|
|
118
|
+
**Not sure which to choose?** See the [Algorithm Guide](./docs/algorithms.md).
|
|
119
|
+
|
|
120
|
+
## Key Features
|
|
121
|
+
|
|
122
|
+
### Multiple Rate Limits (Compound Limiter)
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
import { CompoundRateLimiterFactory } from '@zeitar/throttle';
|
|
126
|
+
|
|
127
|
+
// Enforce BOTH limits simultaneously
|
|
128
|
+
const compound = new CompoundRateLimiterFactory([
|
|
129
|
+
perSecondFactory, // 10/second
|
|
130
|
+
perMinuteFactory // 100/minute
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
const limiter = compound.create('user-123');
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Reservation Pattern
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// Reserve tokens and wait for availability
|
|
140
|
+
const reservation = await limiter.reserve(10, 5); // Wait max 5 seconds
|
|
141
|
+
await reservation.wait();
|
|
142
|
+
// Tokens are now reserved, proceed with operation
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Distributed Systems
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Use Redis for multi-server deployments
|
|
149
|
+
import { RedisStorage, RedisLock } from './your-impl';
|
|
150
|
+
|
|
151
|
+
const factory = new RateLimiterFactory(
|
|
152
|
+
config,
|
|
153
|
+
new RedisStorage(redisClient),
|
|
154
|
+
new RedisLock(redisClient)
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
See [Custom Storage](./docs/custom-storage.md) for implementation.
|
|
159
|
+
|
|
160
|
+
## Performance
|
|
161
|
+
|
|
162
|
+
- **All algorithms**: O(1) time complexity
|
|
163
|
+
- **Memory usage**: ~100 bytes per active limiter
|
|
164
|
+
- **Throughput**: Designed for high-concurrency scenarios
|
|
165
|
+
- **Storage**: Pluggable backend (in-memory, Redis, database)
|
|
166
|
+
|
|
167
|
+
## Architecture
|
|
168
|
+
|
|
169
|
+
This library uses several design patterns for flexibility and maintainability:
|
|
170
|
+
|
|
171
|
+
- **Strategy Pattern**: Different algorithms implement `LimiterInterface`
|
|
172
|
+
- **Factory Pattern**: `RateLimiterFactory` creates configured limiters
|
|
173
|
+
- **Composite Pattern**: `CompoundLimiter` combines multiple limiters
|
|
174
|
+
- **Dependency Injection**: Storage and locking are pluggable
|
|
175
|
+
|
|
176
|
+
Inspired by [Symfony's Rate Limiter](https://github.com/symfony/rate-limiter) with full TypeScript support and async/await patterns.
|
|
177
|
+
|
|
178
|
+
## Testing
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { NoLimiter, InMemoryStorage } from '@zeitar/throttle';
|
|
182
|
+
|
|
183
|
+
// Use NoLimiter for tests that shouldn't be rate limited
|
|
184
|
+
const limiter = new NoLimiter();
|
|
185
|
+
|
|
186
|
+
// Or clear InMemoryStorage between tests
|
|
187
|
+
const storage = new InMemoryStorage();
|
|
188
|
+
storage.clear();
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Contributing
|
|
192
|
+
|
|
193
|
+
Contributions welcome! Please open an issue or PR.
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT © 2025 Khaled Zeitar
|
|
198
|
+
|
|
199
|
+
## Credits & Acknowledgments
|
|
200
|
+
|
|
201
|
+
- **Implementation**: © 2025 Khaled Zeitar - Original TypeScript implementation
|
|
202
|
+
- **Architectural inspiration**: [Symfony Rate Limiter](https://github.com/symfony/rate-limiter) by Fabien Potencier and contributors
|
|
203
|
+
|
|
204
|
+
While the code is written from scratch in TypeScript, the design patterns, API structure, and architectural decisions are influenced by Symfony's proven approach to rate limiting.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { LimiterInterface } from './LimiterInterface';
|
|
2
|
+
import type { RateLimit } from './RateLimit';
|
|
3
|
+
import { Reservation } from './Reservation';
|
|
4
|
+
/**
|
|
5
|
+
* Compound rate limiter that combines multiple limiters.
|
|
6
|
+
*
|
|
7
|
+
* Uses AND logic: all limiters must accept for the request to be accepted.
|
|
8
|
+
* Returns the most restrictive result (lowest available tokens, longest retry time).
|
|
9
|
+
*
|
|
10
|
+
* Note: Does NOT support the reserve() pattern due to complexity of coordinating
|
|
11
|
+
* multiple reservations.
|
|
12
|
+
*/
|
|
13
|
+
export declare class CompoundLimiter implements LimiterInterface {
|
|
14
|
+
private readonly limiters;
|
|
15
|
+
constructor(limiters: LimiterInterface[]);
|
|
16
|
+
/**
|
|
17
|
+
* Reserve is not supported by compound limiters.
|
|
18
|
+
*
|
|
19
|
+
* @throws {ReserveNotSupportedError} Always throws
|
|
20
|
+
*/
|
|
21
|
+
reserve(_tokens?: number, _maxTime?: number | null): Promise<Reservation>;
|
|
22
|
+
/**
|
|
23
|
+
* Try to consume tokens from all limiters.
|
|
24
|
+
*
|
|
25
|
+
* Returns the most restrictive result if any limiter rejects.
|
|
26
|
+
*/
|
|
27
|
+
consume(tokens?: number): Promise<RateLimit>;
|
|
28
|
+
/**
|
|
29
|
+
* Reset all limiters.
|
|
30
|
+
*/
|
|
31
|
+
reset(): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=CompoundLimiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompoundLimiter.d.ts","sourceRoot":"","sources":["../src/CompoundLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C;;;;;;;;GAQG;AACH,qBAAa,eAAgB,YAAW,gBAAgB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;gBAElC,QAAQ,EAAE,gBAAgB,EAAE;IAOxC;;;;OAIG;IACG,OAAO,CAAC,OAAO,GAAE,MAAU,EAAE,QAAQ,GAAE,MAAM,GAAG,IAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IAMxF;;;;OAIG;IACG,OAAO,CAAC,MAAM,GAAE,MAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IA4BrD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAG7B"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompoundLimiter = void 0;
|
|
4
|
+
const ReserveNotSupportedError_1 = require("./errors/ReserveNotSupportedError");
|
|
5
|
+
/**
|
|
6
|
+
* Compound rate limiter that combines multiple limiters.
|
|
7
|
+
*
|
|
8
|
+
* Uses AND logic: all limiters must accept for the request to be accepted.
|
|
9
|
+
* Returns the most restrictive result (lowest available tokens, longest retry time).
|
|
10
|
+
*
|
|
11
|
+
* Note: Does NOT support the reserve() pattern due to complexity of coordinating
|
|
12
|
+
* multiple reservations.
|
|
13
|
+
*/
|
|
14
|
+
class CompoundLimiter {
|
|
15
|
+
constructor(limiters) {
|
|
16
|
+
if (limiters.length === 0) {
|
|
17
|
+
throw new Error('CompoundLimiter requires at least one limiter');
|
|
18
|
+
}
|
|
19
|
+
this.limiters = limiters;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reserve is not supported by compound limiters.
|
|
23
|
+
*
|
|
24
|
+
* @throws {ReserveNotSupportedError} Always throws
|
|
25
|
+
*/
|
|
26
|
+
async reserve(_tokens = 1, _maxTime = null) {
|
|
27
|
+
throw new ReserveNotSupportedError_1.ReserveNotSupportedError('CompoundLimiter does not support the reserve() method. Use consume() instead.');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Try to consume tokens from all limiters.
|
|
31
|
+
*
|
|
32
|
+
* Returns the most restrictive result if any limiter rejects.
|
|
33
|
+
*/
|
|
34
|
+
async consume(tokens = 1) {
|
|
35
|
+
const results = await Promise.all(this.limiters.map(limiter => limiter.consume(tokens)));
|
|
36
|
+
// Find the most restrictive result
|
|
37
|
+
let mostRestrictive = results[0];
|
|
38
|
+
for (const result of results.slice(1)) {
|
|
39
|
+
// If current result is rejected and previous was accepted, or
|
|
40
|
+
// if both rejected but current has longer wait time, or
|
|
41
|
+
// if both accepted but current has fewer tokens
|
|
42
|
+
if ((!result.isAccepted() && mostRestrictive.isAccepted()) ||
|
|
43
|
+
(!result.isAccepted() &&
|
|
44
|
+
!mostRestrictive.isAccepted() &&
|
|
45
|
+
result.getRetryAfter() > mostRestrictive.getRetryAfter()) ||
|
|
46
|
+
(result.isAccepted() &&
|
|
47
|
+
mostRestrictive.isAccepted() &&
|
|
48
|
+
result.getRemainingTokens() < mostRestrictive.getRemainingTokens())) {
|
|
49
|
+
mostRestrictive = result;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return mostRestrictive;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reset all limiters.
|
|
56
|
+
*/
|
|
57
|
+
async reset() {
|
|
58
|
+
await Promise.all(this.limiters.map(limiter => limiter.reset()));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
exports.CompoundLimiter = CompoundLimiter;
|
|
62
|
+
//# sourceMappingURL=CompoundLimiter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompoundLimiter.js","sourceRoot":"","sources":["../src/CompoundLimiter.ts"],"names":[],"mappings":";;;AAGA,gFAA6E;AAE7E;;;;;;;;GAQG;AACH,MAAa,eAAe;IAG1B,YAAY,QAA4B;QACtC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAC3B,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,UAAkB,CAAC,EAAE,WAA0B,IAAI;QAC/D,MAAM,IAAI,mDAAwB,CAChC,+EAA+E,CAChF,CAAC;IACJ,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,OAAO,CAAC,SAAiB,CAAC;QAC9B,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CACtD,CAAC;QAEF,mCAAmC;QACnC,IAAI,eAAe,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAEjC,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;YACtC,8DAA8D;YAC9D,wDAAwD;YACxD,gDAAgD;YAChD,IACE,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE,IAAI,eAAe,CAAC,UAAU,EAAE,CAAC;gBACtD,CAAC,CAAC,MAAM,CAAC,UAAU,EAAE;oBACnB,CAAC,eAAe,CAAC,UAAU,EAAE;oBAC7B,MAAM,CAAC,aAAa,EAAE,GAAG,eAAe,CAAC,aAAa,EAAE,CAAC;gBAC3D,CAAC,MAAM,CAAC,UAAU,EAAE;oBAClB,eAAe,CAAC,UAAU,EAAE;oBAC5B,MAAM,CAAC,kBAAkB,EAAE,GAAG,eAAe,CAAC,kBAAkB,EAAE,CAAC,EACrE,CAAC;gBACD,eAAe,GAAG,MAAM,CAAC;YAC3B,CAAC;QACH,CAAC;QAED,OAAO,eAAe,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,KAAK;QACT,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;CACF;AA5DD,0CA4DC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RateLimiterFactoryInterface } from './RateLimiterFactoryInterface';
|
|
2
|
+
import type { LimiterInterface } from './LimiterInterface';
|
|
3
|
+
/**
|
|
4
|
+
* Factory for creating compound rate limiters.
|
|
5
|
+
*
|
|
6
|
+
* Combines multiple factories to create a single limiter that enforces
|
|
7
|
+
* all policies simultaneously.
|
|
8
|
+
*/
|
|
9
|
+
export declare class CompoundRateLimiterFactory implements RateLimiterFactoryInterface {
|
|
10
|
+
private readonly factories;
|
|
11
|
+
constructor(factories: RateLimiterFactoryInterface[]);
|
|
12
|
+
/**
|
|
13
|
+
* Create a compound rate limiter.
|
|
14
|
+
*
|
|
15
|
+
* @param key - Optional key passed to all underlying factories
|
|
16
|
+
*/
|
|
17
|
+
create(key?: string | null): LimiterInterface;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=CompoundRateLimiterFactory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompoundRateLimiterFactory.d.ts","sourceRoot":"","sources":["../src/CompoundRateLimiterFactory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AACjF,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D;;;;;GAKG;AACH,qBAAa,0BAA2B,YAAW,2BAA2B;IAC5E,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE9C,SAAS,EAAE,2BAA2B,EAAE;IAOpD;;;;OAIG;IACH,MAAM,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,gBAAgB;CAI9C"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompoundRateLimiterFactory = void 0;
|
|
4
|
+
const CompoundLimiter_1 = require("./CompoundLimiter");
|
|
5
|
+
/**
|
|
6
|
+
* Factory for creating compound rate limiters.
|
|
7
|
+
*
|
|
8
|
+
* Combines multiple factories to create a single limiter that enforces
|
|
9
|
+
* all policies simultaneously.
|
|
10
|
+
*/
|
|
11
|
+
class CompoundRateLimiterFactory {
|
|
12
|
+
constructor(factories) {
|
|
13
|
+
if (factories.length === 0) {
|
|
14
|
+
throw new Error('CompoundRateLimiterFactory requires at least one factory');
|
|
15
|
+
}
|
|
16
|
+
this.factories = factories;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Create a compound rate limiter.
|
|
20
|
+
*
|
|
21
|
+
* @param key - Optional key passed to all underlying factories
|
|
22
|
+
*/
|
|
23
|
+
create(key) {
|
|
24
|
+
const limiters = this.factories.map(factory => factory.create(key));
|
|
25
|
+
return new CompoundLimiter_1.CompoundLimiter(limiters);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.CompoundRateLimiterFactory = CompoundRateLimiterFactory;
|
|
29
|
+
//# sourceMappingURL=CompoundRateLimiterFactory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CompoundRateLimiterFactory.js","sourceRoot":"","sources":["../src/CompoundRateLimiterFactory.ts"],"names":[],"mappings":";;;AAEA,uDAAoD;AAEpD;;;;;GAKG;AACH,MAAa,0BAA0B;IAGrC,YAAY,SAAwC;QAClD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;QAC9E,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,GAAmB;QACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACpE,OAAO,IAAI,iCAAe,CAAC,QAAQ,CAAC,CAAC;IACvC,CAAC;CACF;AAnBD,gEAmBC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { RateLimit } from './RateLimit';
|
|
2
|
+
import type { Reservation } from './Reservation';
|
|
3
|
+
/**
|
|
4
|
+
* Main contract for all rate limiters.
|
|
5
|
+
*
|
|
6
|
+
* Provides two patterns for rate limiting:
|
|
7
|
+
* - reserve(): Reserve tokens and wait if necessary
|
|
8
|
+
* - consume(): Immediately try to consume tokens
|
|
9
|
+
*/
|
|
10
|
+
export interface LimiterInterface {
|
|
11
|
+
/**
|
|
12
|
+
* Reserve tokens with optional maximum wait time.
|
|
13
|
+
*
|
|
14
|
+
* @param tokens - Number of tokens to reserve
|
|
15
|
+
* @param maxTime - Maximum time to wait (in seconds), or null for no limit
|
|
16
|
+
* @returns A Reservation object
|
|
17
|
+
* @throws {MaxWaitDurationExceededError} If wait time exceeds maxTime
|
|
18
|
+
*/
|
|
19
|
+
reserve(tokens: number, maxTime?: number | null): Promise<Reservation>;
|
|
20
|
+
/**
|
|
21
|
+
* Try to consume tokens immediately.
|
|
22
|
+
*
|
|
23
|
+
* @param tokens - Number of tokens to consume
|
|
24
|
+
* @returns A RateLimit object indicating success or failure
|
|
25
|
+
*/
|
|
26
|
+
consume(tokens: number): Promise<RateLimit>;
|
|
27
|
+
/**
|
|
28
|
+
* Reset the rate limiter state.
|
|
29
|
+
*/
|
|
30
|
+
reset(): Promise<void>;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=LimiterInterface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimiterInterface.d.ts","sourceRoot":"","sources":["../src/LimiterInterface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;IAEvE;;;;;OAKG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;IAE5C;;OAEG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimiterInterface.js","sourceRoot":"","sources":["../src/LimiterInterface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interface for serializable rate limiter state objects.
|
|
3
|
+
*
|
|
4
|
+
* State objects must be JSON-serializable and provide their own expiration time.
|
|
5
|
+
*/
|
|
6
|
+
export interface LimiterStateInterface {
|
|
7
|
+
/**
|
|
8
|
+
* Get the unique identifier for this state object.
|
|
9
|
+
*/
|
|
10
|
+
getId(): string;
|
|
11
|
+
/**
|
|
12
|
+
* Get the expiration time (in seconds since epoch) or null if no expiration.
|
|
13
|
+
*/
|
|
14
|
+
getExpirationTime(): number | null;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=LimiterStateInterface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimiterStateInterface.d.ts","sourceRoot":"","sources":["../src/LimiterStateInterface.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,KAAK,IAAI,MAAM,CAAC;IAEhB;;OAEG;IACH,iBAAiB,IAAI,MAAM,GAAG,IAAI,CAAC;CACpC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimiterStateInterface.js","sourceRoot":"","sources":["../src/LimiterStateInterface.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Result object from a rate limiting operation.
|
|
3
|
+
*
|
|
4
|
+
* Contains information about whether the request was accepted, how many tokens
|
|
5
|
+
* are remaining, and when the client should retry if rejected.
|
|
6
|
+
*/
|
|
7
|
+
export declare class RateLimit {
|
|
8
|
+
private readonly availableTokens;
|
|
9
|
+
private readonly retryAfter;
|
|
10
|
+
private readonly accepted;
|
|
11
|
+
private readonly limit;
|
|
12
|
+
constructor(availableTokens: number, retryAfter: Date, accepted: boolean, limit: number);
|
|
13
|
+
/**
|
|
14
|
+
* Check if the request was accepted.
|
|
15
|
+
*/
|
|
16
|
+
isAccepted(): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Ensure the request was accepted, throw an exception otherwise.
|
|
19
|
+
*
|
|
20
|
+
* @throws {RateLimitExceededError} If the request was not accepted
|
|
21
|
+
*/
|
|
22
|
+
ensureAccepted(): this;
|
|
23
|
+
/**
|
|
24
|
+
* Get the date/time when the client should retry.
|
|
25
|
+
*/
|
|
26
|
+
getRetryAfter(): Date;
|
|
27
|
+
/**
|
|
28
|
+
* Get the number of remaining tokens.
|
|
29
|
+
*/
|
|
30
|
+
getRemainingTokens(): number;
|
|
31
|
+
/**
|
|
32
|
+
* Get the rate limit maximum.
|
|
33
|
+
*/
|
|
34
|
+
getLimit(): number;
|
|
35
|
+
/**
|
|
36
|
+
* Wait until the retry time (blocking operation).
|
|
37
|
+
*
|
|
38
|
+
* Note: This uses a blocking setTimeout. Consider using async/await patterns
|
|
39
|
+
* in production code instead.
|
|
40
|
+
*/
|
|
41
|
+
wait(): Promise<void>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=RateLimit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimit.d.ts","sourceRoot":"","sources":["../src/RateLimit.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAO;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAG7B,eAAe,EAAE,MAAM,EACvB,UAAU,EAAE,IAAI,EAChB,QAAQ,EAAE,OAAO,EACjB,KAAK,EAAE,MAAM;IAQf;;OAEG;IACH,UAAU,IAAI,OAAO;IAIrB;;;;OAIG;IACH,cAAc,IAAI,IAAI;IAOtB;;OAEG;IACH,aAAa,IAAI,IAAI;IAIrB;;OAEG;IACH,kBAAkB,IAAI,MAAM;IAI5B;;OAEG;IACH,QAAQ,IAAI,MAAM;IAIlB;;;;;OAKG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ5B"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimit = void 0;
|
|
4
|
+
const RateLimitExceededError_1 = require("./errors/RateLimitExceededError");
|
|
5
|
+
/**
|
|
6
|
+
* Result object from a rate limiting operation.
|
|
7
|
+
*
|
|
8
|
+
* Contains information about whether the request was accepted, how many tokens
|
|
9
|
+
* are remaining, and when the client should retry if rejected.
|
|
10
|
+
*/
|
|
11
|
+
class RateLimit {
|
|
12
|
+
constructor(availableTokens, retryAfter, accepted, limit) {
|
|
13
|
+
this.availableTokens = availableTokens;
|
|
14
|
+
this.retryAfter = retryAfter;
|
|
15
|
+
this.accepted = accepted;
|
|
16
|
+
this.limit = limit;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if the request was accepted.
|
|
20
|
+
*/
|
|
21
|
+
isAccepted() {
|
|
22
|
+
return this.accepted;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Ensure the request was accepted, throw an exception otherwise.
|
|
26
|
+
*
|
|
27
|
+
* @throws {RateLimitExceededError} If the request was not accepted
|
|
28
|
+
*/
|
|
29
|
+
ensureAccepted() {
|
|
30
|
+
if (!this.accepted) {
|
|
31
|
+
throw new RateLimitExceededError_1.RateLimitExceededError('Rate limit exceeded', this);
|
|
32
|
+
}
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the date/time when the client should retry.
|
|
37
|
+
*/
|
|
38
|
+
getRetryAfter() {
|
|
39
|
+
return this.retryAfter;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get the number of remaining tokens.
|
|
43
|
+
*/
|
|
44
|
+
getRemainingTokens() {
|
|
45
|
+
return this.availableTokens;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the rate limit maximum.
|
|
49
|
+
*/
|
|
50
|
+
getLimit() {
|
|
51
|
+
return this.limit;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Wait until the retry time (blocking operation).
|
|
55
|
+
*
|
|
56
|
+
* Note: This uses a blocking setTimeout. Consider using async/await patterns
|
|
57
|
+
* in production code instead.
|
|
58
|
+
*/
|
|
59
|
+
async wait() {
|
|
60
|
+
const now = Date.now();
|
|
61
|
+
const waitMs = this.retryAfter.getTime() - now;
|
|
62
|
+
if (waitMs > 0) {
|
|
63
|
+
await new Promise(resolve => setTimeout(resolve, waitMs));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.RateLimit = RateLimit;
|
|
68
|
+
//# sourceMappingURL=RateLimit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RateLimit.js","sourceRoot":"","sources":["../src/RateLimit.ts"],"names":[],"mappings":";;;AAAA,4EAAyE;AAEzE;;;;;GAKG;AACH,MAAa,SAAS;IAMpB,YACE,eAAuB,EACvB,UAAgB,EAChB,QAAiB,EACjB,KAAa;QAEb,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QACvC,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;IAED;;OAEG;IACH,UAAU;QACR,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,cAAc;QACZ,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnB,MAAM,IAAI,+CAAsB,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;QAChE,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;OAEG;IACH,aAAa;QACX,OAAO,IAAI,CAAC,UAAU,CAAC;IACzB,CAAC;IAED;;OAEG;IACH,kBAAkB;QAChB,OAAO,IAAI,CAAC,eAAe,CAAC;IAC9B,CAAC;IAED;;OAEG;IACH,QAAQ;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,IAAI;QACR,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,GAAG,CAAC;QAE/C,IAAI,MAAM,GAAG,CAAC,EAAE,CAAC;YACf,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;CACF;AAxED,8BAwEC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { RateLimiterFactoryInterface } from './RateLimiterFactoryInterface';
|
|
2
|
+
import type { LimiterInterface } from './LimiterInterface';
|
|
3
|
+
import type { StorageInterface } from './storage/StorageInterface';
|
|
4
|
+
import type { LockInterface } from './storage/LockInterface';
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for token bucket policy.
|
|
7
|
+
*/
|
|
8
|
+
export interface TokenBucketConfig {
|
|
9
|
+
policy: 'token_bucket';
|
|
10
|
+
id: string;
|
|
11
|
+
limit: number;
|
|
12
|
+
rate: {
|
|
13
|
+
interval: string;
|
|
14
|
+
amount: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Configuration for fixed window policy.
|
|
19
|
+
*/
|
|
20
|
+
export interface FixedWindowConfig {
|
|
21
|
+
policy: 'fixed_window';
|
|
22
|
+
id: string;
|
|
23
|
+
limit: number;
|
|
24
|
+
interval: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Configuration for sliding window policy.
|
|
28
|
+
*/
|
|
29
|
+
export interface SlidingWindowConfig {
|
|
30
|
+
policy: 'sliding_window';
|
|
31
|
+
id: string;
|
|
32
|
+
limit: number;
|
|
33
|
+
interval: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Configuration for no-limit policy.
|
|
37
|
+
*/
|
|
38
|
+
export interface NoLimitConfig {
|
|
39
|
+
policy: 'no_limit';
|
|
40
|
+
id: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Union type of all possible rate limiter configurations.
|
|
44
|
+
*/
|
|
45
|
+
export type RateLimiterConfig = TokenBucketConfig | FixedWindowConfig | SlidingWindowConfig | NoLimitConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Factory for creating rate limiter instances based on configuration.
|
|
48
|
+
*
|
|
49
|
+
* Supports multiple rate limiting algorithms:
|
|
50
|
+
* - token_bucket: Token bucket with configurable burst and refill rate
|
|
51
|
+
* - fixed_window: Fixed time windows with hit counting
|
|
52
|
+
* - sliding_window: Sliding window with weighted previous window
|
|
53
|
+
* - no_limit: No rate limiting (always accepts)
|
|
54
|
+
*/
|
|
55
|
+
export declare class RateLimiterFactory implements RateLimiterFactoryInterface {
|
|
56
|
+
private readonly config;
|
|
57
|
+
private readonly storage;
|
|
58
|
+
private readonly lock?;
|
|
59
|
+
constructor(config: RateLimiterConfig, storage: StorageInterface, lock?: LockInterface);
|
|
60
|
+
/**
|
|
61
|
+
* Create a rate limiter instance.
|
|
62
|
+
*
|
|
63
|
+
* @param key - Optional key to distinguish between different limiters (e.g., user ID)
|
|
64
|
+
*/
|
|
65
|
+
create(key?: string | null): LimiterInterface;
|
|
66
|
+
/**
|
|
67
|
+
* Validate and normalize configuration.
|
|
68
|
+
*/
|
|
69
|
+
private validateConfig;
|
|
70
|
+
/**
|
|
71
|
+
* Create a token bucket limiter.
|
|
72
|
+
*/
|
|
73
|
+
private createTokenBucketLimiter;
|
|
74
|
+
/**
|
|
75
|
+
* Create a fixed window limiter.
|
|
76
|
+
*/
|
|
77
|
+
private createFixedWindowLimiter;
|
|
78
|
+
/**
|
|
79
|
+
* Create a sliding window limiter.
|
|
80
|
+
*/
|
|
81
|
+
private createSlidingWindowLimiter;
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=RateLimiterFactory.d.ts.map
|