ratelimit-flex 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/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +274 -0
- package/dist/cjs/index.d.ts +32 -0
- package/dist/cjs/index.d.ts.map +1 -0
- package/dist/cjs/index.js +73 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/middleware/express.d.ts +18 -0
- package/dist/cjs/middleware/express.d.ts.map +1 -0
- package/dist/cjs/middleware/express.js +61 -0
- package/dist/cjs/middleware/express.js.map +1 -0
- package/dist/cjs/middleware/fastify.d.ts +21 -0
- package/dist/cjs/middleware/fastify.d.ts.map +1 -0
- package/dist/cjs/middleware/fastify.js +66 -0
- package/dist/cjs/middleware/fastify.js.map +1 -0
- package/dist/cjs/middleware/index.d.ts +4 -0
- package/dist/cjs/middleware/index.d.ts.map +1 -0
- package/dist/cjs/middleware/index.js +9 -0
- package/dist/cjs/middleware/index.js.map +1 -0
- package/dist/cjs/middleware/merge-options.d.ts +16 -0
- package/dist/cjs/middleware/merge-options.d.ts.map +1 -0
- package/dist/cjs/middleware/merge-options.js +71 -0
- package/dist/cjs/middleware/merge-options.js.map +1 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/stores/index.d.ts +4 -0
- package/dist/cjs/stores/index.d.ts.map +1 -0
- package/dist/cjs/stores/index.js +11 -0
- package/dist/cjs/stores/index.js.map +1 -0
- package/dist/cjs/stores/memory-store.d.ts +74 -0
- package/dist/cjs/stores/memory-store.d.ts.map +1 -0
- package/dist/cjs/stores/memory-store.js +259 -0
- package/dist/cjs/stores/memory-store.js.map +1 -0
- package/dist/cjs/stores/redis-store.d.ts +113 -0
- package/dist/cjs/stores/redis-store.d.ts.map +1 -0
- package/dist/cjs/stores/redis-store.js +452 -0
- package/dist/cjs/stores/redis-store.js.map +1 -0
- package/dist/cjs/strategies/defaults.d.ts +28 -0
- package/dist/cjs/strategies/defaults.d.ts.map +1 -0
- package/dist/cjs/strategies/defaults.js +31 -0
- package/dist/cjs/strategies/defaults.js.map +1 -0
- package/dist/cjs/strategies/index.d.ts +4 -0
- package/dist/cjs/strategies/index.d.ts.map +1 -0
- package/dist/cjs/strategies/index.js +13 -0
- package/dist/cjs/strategies/index.js.map +1 -0
- package/dist/cjs/strategies/rate-limit-engine.d.ts +42 -0
- package/dist/cjs/strategies/rate-limit-engine.d.ts.map +1 -0
- package/dist/cjs/strategies/rate-limit-engine.js +128 -0
- package/dist/cjs/strategies/rate-limit-engine.js.map +1 -0
- package/dist/cjs/types/index.d.ts +93 -0
- package/dist/cjs/types/index.d.ts.map +1 -0
- package/dist/cjs/types/index.js +14 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/utils/index.d.ts +3 -0
- package/dist/cjs/utils/index.d.ts.map +1 -0
- package/dist/cjs/utils/index.js +4 -0
- package/dist/cjs/utils/index.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +43 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.d.ts +18 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +58 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/fastify.d.ts +21 -0
- package/dist/middleware/fastify.d.ts.map +1 -0
- package/dist/middleware/fastify.js +60 -0
- package/dist/middleware/fastify.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/merge-options.d.ts +16 -0
- package/dist/middleware/merge-options.d.ts.map +1 -0
- package/dist/middleware/merge-options.js +64 -0
- package/dist/middleware/merge-options.js.map +1 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.d.ts.map +1 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/index.js.map +1 -0
- package/dist/stores/memory-store.d.ts +74 -0
- package/dist/stores/memory-store.d.ts.map +1 -0
- package/dist/stores/memory-store.js +255 -0
- package/dist/stores/memory-store.js.map +1 -0
- package/dist/stores/redis-store.d.ts +113 -0
- package/dist/stores/redis-store.d.ts.map +1 -0
- package/dist/stores/redis-store.js +413 -0
- package/dist/stores/redis-store.js.map +1 -0
- package/dist/strategies/defaults.d.ts +28 -0
- package/dist/strategies/defaults.d.ts.map +1 -0
- package/dist/strategies/defaults.js +28 -0
- package/dist/strategies/defaults.js.map +1 -0
- package/dist/strategies/index.d.ts +4 -0
- package/dist/strategies/index.d.ts.map +1 -0
- package/dist/strategies/index.js +4 -0
- package/dist/strategies/index.js.map +1 -0
- package/dist/strategies/rate-limit-engine.d.ts +42 -0
- package/dist/strategies/rate-limit-engine.d.ts.map +1 -0
- package/dist/strategies/rate-limit-engine.js +122 -0
- package/dist/strategies/rate-limit-engine.js.map +1 -0
- package/dist/types/index.d.ts +93 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +11 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +90 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
## [1.0.0] - 2026-03-27
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial public release of `ratelimit-flex`.
|
|
9
|
+
- Rate limiting strategies: sliding window, fixed window, and token bucket.
|
|
10
|
+
- Express middleware and Fastify plugin integrations.
|
|
11
|
+
- `MemoryStore` and Redis-backed `RedisStore`.
|
|
12
|
+
- TypeScript-first API with documented defaults and strategy options.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ratelimit-flex contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# ratelimit-flex
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/ratelimit-flex)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+
Flexible, TypeScript-first rate limiting for Node.js APIs with first-class Express and Fastify support.
|
|
9
|
+
|
|
10
|
+
**Key features**
|
|
11
|
+
- Multiple strategies: sliding window, token bucket, fixed window
|
|
12
|
+
- Works with Express and Fastify
|
|
13
|
+
- Pluggable stores: in-memory and Redis
|
|
14
|
+
- Strong TypeScript types and clean public API
|
|
15
|
+
- Supports custom keys, skip logic, callbacks, and custom responses
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install ratelimit-flex
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add ratelimit-flex
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add ratelimit-flex
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Note:** The published package targets **Node.js 18+**. Continuous integration and local development use **Node.js 20.12+** because Vitest 4 depends on APIs that are not available on Node 18.
|
|
32
|
+
|
|
33
|
+
## Quick Start (Express)
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import express from 'express';
|
|
37
|
+
import rateLimit from 'ratelimit-flex';
|
|
38
|
+
const app = express();
|
|
39
|
+
app.use(rateLimit({ maxRequests: 100, windowMs: 60_000 }));
|
|
40
|
+
app.get('/health', (_req, res) => res.json({ ok: true }));
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Strategies
|
|
44
|
+
|
|
45
|
+
### Sliding Window
|
|
46
|
+
Most accurate for per-window fairness because each request is counted against a moving window, not a fixed boundary. Use this as your default for user-facing APIs where consistency matters more than minimal memory usage.
|
|
47
|
+
|
|
48
|
+
### Token Bucket
|
|
49
|
+
Great when you want to allow short bursts while still enforcing a long-term average rate. Useful for APIs where occasional spikes are expected (mobile reconnects, batchy clients, webhook retries).
|
|
50
|
+
|
|
51
|
+
### Fixed Window
|
|
52
|
+
Simplest and most memory-efficient approach. Best for straightforward rate limiting where slight boundary effects are acceptable (for example, internal tools or low-risk endpoints).
|
|
53
|
+
|
|
54
|
+
## API Reference
|
|
55
|
+
|
|
56
|
+
### Exports
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
import rateLimit, {
|
|
60
|
+
expressRateLimiter,
|
|
61
|
+
fastifyRateLimiter,
|
|
62
|
+
createRateLimiter,
|
|
63
|
+
createRateLimitEngine,
|
|
64
|
+
MemoryStore,
|
|
65
|
+
RedisStore,
|
|
66
|
+
RateLimitEngine,
|
|
67
|
+
RateLimitStrategy,
|
|
68
|
+
slidingWindowDefaults,
|
|
69
|
+
fixedWindowDefaults,
|
|
70
|
+
tokenBucketDefaults,
|
|
71
|
+
} from 'ratelimit-flex';
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
- Default export: `expressRateLimiter`
|
|
75
|
+
- Named exports include all of the above plus all types from `types/index.ts`
|
|
76
|
+
- `createRateLimitEngine(options)`: factory that returns a `RateLimitEngine` instance (for advanced use cases)
|
|
77
|
+
|
|
78
|
+
### `RateLimitOptions`
|
|
79
|
+
|
|
80
|
+
| Option | Type | Default | Description |
|
|
81
|
+
|---|---|---|---|
|
|
82
|
+
| `strategy` | `RateLimitStrategy` | `SLIDING_WINDOW` | Strategy to use: `SLIDING_WINDOW`, `TOKEN_BUCKET`, or `FIXED_WINDOW` |
|
|
83
|
+
| `store` | `RateLimitStore` | `MemoryStore` (when omitted in middleware helpers) | Backing store for counters/token state |
|
|
84
|
+
| `keyGenerator` | `(req: unknown) => string` | framework IP-based fallback | Builds the rate-limit key (user, API key, tenant, etc.) |
|
|
85
|
+
| `onLimitReached` | `(req, result) => void \| Promise<void>` | `undefined` | Called when a request is blocked |
|
|
86
|
+
| `skip` | `(req: unknown) => boolean` | `undefined` | Skip rate limiting entirely for matching requests |
|
|
87
|
+
| `headers` | `boolean` | `true` | Adds `X-RateLimit-*` and `Retry-After` headers |
|
|
88
|
+
| `statusCode` | `number` | `429` | HTTP status code used when blocked |
|
|
89
|
+
| `message` | `string \| object` | `"Too many requests"` | Error payload wrapped as `{ error: message }` |
|
|
90
|
+
| `skipFailedRequests` | `boolean` | `false` | Decrement usage for failed responses (`>= 400`) |
|
|
91
|
+
| `skipSuccessfulRequests` | `boolean` | `false` | Decrement usage for successful responses (`< 400`) |
|
|
92
|
+
| `windowMs` | `number` | `60000` | Window length for sliding/fixed window strategies |
|
|
93
|
+
| `maxRequests` | `number` | `100` | Max requests per window for sliding/fixed window |
|
|
94
|
+
| `tokensPerInterval` | `number` | `10` | Tokens refilled per interval (token bucket) |
|
|
95
|
+
| `interval` | `number` | `60000` | Refill interval in ms (token bucket) |
|
|
96
|
+
| `bucketSize` | `number` | `100` | Max bucket capacity (token bucket burst size) |
|
|
97
|
+
|
|
98
|
+
### Defaults
|
|
99
|
+
|
|
100
|
+
- `slidingWindowDefaults`: `{ strategy: SLIDING_WINDOW, windowMs: 60000, maxRequests: 100 }`
|
|
101
|
+
- `fixedWindowDefaults`: `{ strategy: FIXED_WINDOW, windowMs: 60000, maxRequests: 100 }`
|
|
102
|
+
- `tokenBucketDefaults`: `{ strategy: TOKEN_BUCKET, tokensPerInterval: 10, interval: 60000, bucketSize: 100 }`
|
|
103
|
+
|
|
104
|
+
### Convenience Factory: `createRateLimiter(options)`
|
|
105
|
+
|
|
106
|
+
`createRateLimiter` returns an object with both `.express` and `.fastify` properties:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
const limiter = createRateLimiter({ maxRequests: 100 });
|
|
110
|
+
app.use(limiter.express); // Express
|
|
111
|
+
await app.register(limiter.fastify); // Fastify
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Note**: This function requires `fastify-plugin` as a peer dependency (already listed). For best type-safety and clarity, prefer direct imports (`expressRateLimiter` or `fastifyRateLimiter`) when you already know your framework.
|
|
115
|
+
|
|
116
|
+
## Examples
|
|
117
|
+
|
|
118
|
+
### Basic Express usage
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
import express from 'express';
|
|
122
|
+
import { expressRateLimiter } from 'ratelimit-flex';
|
|
123
|
+
|
|
124
|
+
const app = express();
|
|
125
|
+
app.use(
|
|
126
|
+
expressRateLimiter({
|
|
127
|
+
strategy: 'SLIDING_WINDOW',
|
|
128
|
+
windowMs: 60_000,
|
|
129
|
+
maxRequests: 100,
|
|
130
|
+
}),
|
|
131
|
+
);
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Basic Fastify usage
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
import Fastify from 'fastify';
|
|
138
|
+
import { fastifyRateLimiter, RateLimitStrategy } from 'ratelimit-flex';
|
|
139
|
+
|
|
140
|
+
const app = Fastify();
|
|
141
|
+
await app.register(fastifyRateLimiter, {
|
|
142
|
+
strategy: RateLimitStrategy.SLIDING_WINDOW,
|
|
143
|
+
windowMs: 60_000,
|
|
144
|
+
maxRequests: 100,
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Per-user rate limiting by API key
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { expressRateLimiter } from 'ratelimit-flex';
|
|
152
|
+
|
|
153
|
+
app.use(
|
|
154
|
+
expressRateLimiter({
|
|
155
|
+
maxRequests: 60,
|
|
156
|
+
windowMs: 60_000,
|
|
157
|
+
keyGenerator: (req) =>
|
|
158
|
+
String((req as import('express').Request).header('x-api-key') ?? 'anonymous'),
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Token bucket for API endpoints
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
import { expressRateLimiter, RateLimitStrategy } from 'ratelimit-flex';
|
|
167
|
+
|
|
168
|
+
app.use(
|
|
169
|
+
'/api',
|
|
170
|
+
expressRateLimiter({
|
|
171
|
+
strategy: RateLimitStrategy.TOKEN_BUCKET,
|
|
172
|
+
tokensPerInterval: 20,
|
|
173
|
+
interval: 60_000,
|
|
174
|
+
bucketSize: 60,
|
|
175
|
+
}),
|
|
176
|
+
);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Using RedisStore for distributed systems
|
|
180
|
+
|
|
181
|
+
```ts
|
|
182
|
+
import { expressRateLimiter, RedisStore, RateLimitStrategy } from 'ratelimit-flex';
|
|
183
|
+
|
|
184
|
+
const store = new RedisStore({
|
|
185
|
+
strategy: RateLimitStrategy.SLIDING_WINDOW,
|
|
186
|
+
windowMs: 60_000,
|
|
187
|
+
maxRequests: 100,
|
|
188
|
+
url: process.env.REDIS_URL!,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
app.use(expressRateLimiter({ strategy: RateLimitStrategy.SLIDING_WINDOW, store }));
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Custom error responses
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
app.use(
|
|
198
|
+
expressRateLimiter({
|
|
199
|
+
maxRequests: 20,
|
|
200
|
+
windowMs: 60_000,
|
|
201
|
+
statusCode: 429,
|
|
202
|
+
message: { code: 'RATE_LIMITED', detail: 'Please retry shortly.' },
|
|
203
|
+
}),
|
|
204
|
+
);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Skipping certain routes
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
app.use(
|
|
211
|
+
expressRateLimiter({
|
|
212
|
+
maxRequests: 100,
|
|
213
|
+
windowMs: 60_000,
|
|
214
|
+
skip: (req) => String((req as { path?: string }).path ?? '').startsWith('/health'),
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Multiple rate limiters (global + per-endpoint)
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
import { expressRateLimiter } from 'ratelimit-flex';
|
|
223
|
+
|
|
224
|
+
app.use(expressRateLimiter({ maxRequests: 100, windowMs: 60_000 })); // global
|
|
225
|
+
app.use('/login', expressRateLimiter({ maxRequests: 10, windowMs: 60_000 })); // strict endpoint
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Stores
|
|
229
|
+
|
|
230
|
+
| Store | Best for | Pros | Trade-offs |
|
|
231
|
+
|---|---|---|---|
|
|
232
|
+
| `MemoryStore` | Single-instance apps, local development | Zero setup, fastest, no external dependencies | Not shared across multiple app instances/processes |
|
|
233
|
+
| `RedisStore` | Multi-instance/distributed deployments | Shared counters across nodes, atomic operations via Lua scripts | Requires Redis and network round-trips |
|
|
234
|
+
|
|
235
|
+
## Writing Custom Stores
|
|
236
|
+
|
|
237
|
+
Implement the `RateLimitStore` interface:
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
export interface RateLimitStore {
|
|
241
|
+
increment(key: string): Promise<{
|
|
242
|
+
totalHits: number;
|
|
243
|
+
remaining: number;
|
|
244
|
+
resetTime: Date;
|
|
245
|
+
isBlocked: boolean;
|
|
246
|
+
}>;
|
|
247
|
+
decrement(key: string): Promise<void>;
|
|
248
|
+
reset(key: string): Promise<void>;
|
|
249
|
+
shutdown(): Promise<void>;
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Use your custom store by passing `store` in options:
|
|
254
|
+
|
|
255
|
+
```ts
|
|
256
|
+
app.use(expressRateLimiter({ store: myCustomStore, maxRequests: 100, windowMs: 60_000 }));
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Contributing
|
|
260
|
+
|
|
261
|
+
Contributions are welcome.
|
|
262
|
+
|
|
263
|
+
1. Fork the repository
|
|
264
|
+
2. Create a feature branch
|
|
265
|
+
3. Add or update tests
|
|
266
|
+
4. Run checks locally:
|
|
267
|
+
- `npm test`
|
|
268
|
+
- `npm run build`
|
|
269
|
+
- `npm run lint`
|
|
270
|
+
5. Open a pull request with context and rationale
|
|
271
|
+
|
|
272
|
+
## License
|
|
273
|
+
|
|
274
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ratelimit-flex — flexible rate limiting for Node.js
|
|
3
|
+
*/
|
|
4
|
+
import { expressRateLimiter } from './middleware/express.js';
|
|
5
|
+
import { fastifyRateLimiter } from './middleware/fastify.js';
|
|
6
|
+
import type { RateLimitOptions } from './types/index.js';
|
|
7
|
+
export declare const VERSION = "0.1.0";
|
|
8
|
+
export { expressRateLimiter, fastifyRateLimiter };
|
|
9
|
+
export { RateLimitEngine, createRateLimiter as createRateLimitEngine, defaultKeyGenerator, type RateLimitConsumeResult, type RateLimiterConfigInput, } from './strategies/rate-limit-engine.js';
|
|
10
|
+
export { MemoryStore } from './stores/memory-store.js';
|
|
11
|
+
export { RedisStore } from './stores/redis-store.js';
|
|
12
|
+
export { fixedWindowDefaults, slidingWindowDefaults, tokenBucketDefaults } from './strategies/defaults.js';
|
|
13
|
+
export * from './types/index.js';
|
|
14
|
+
export { RateLimitStrategy } from './types/index.js';
|
|
15
|
+
/**
|
|
16
|
+
* Convenience factory that returns both Express middleware and Fastify plugin.
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
* ```ts
|
|
20
|
+
* const limiter = createRateLimiter({ maxRequests: 100 });
|
|
21
|
+
* app.use(limiter.express); // Express
|
|
22
|
+
* await app.register(limiter.fastify); // Fastify
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Prefer importing `expressRateLimiter` or `fastifyRateLimiter` directly when framework is known.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createRateLimiter(options: Partial<RateLimitOptions>): {
|
|
28
|
+
express: import("express").RequestHandler<import("express-serve-static-core").ParamsDictionary, any, any, import("qs").ParsedQs, Record<string, any>>;
|
|
29
|
+
fastify: typeof fastifyRateLimiter;
|
|
30
|
+
};
|
|
31
|
+
export default expressRateLimiter;
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGzD,eAAO,MAAM,OAAO,UAAU,CAAC;AAG/B,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAGlD,OAAO,EACL,eAAe,EACf,iBAAiB,IAAI,qBAAqB,EAC1C,mBAAmB,EACnB,KAAK,sBAAsB,EAC3B,KAAK,sBAAsB,GAC5B,MAAM,mCAAmC,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAGrD,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG3G,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC;;aActC,OAAO,kBAAkB;EAEtD;AAGD,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ratelimit-flex — flexible rate limiting for Node.js
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
17
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
18
|
+
};
|
|
19
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
20
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
21
|
+
};
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.RateLimitStrategy = exports.tokenBucketDefaults = exports.slidingWindowDefaults = exports.fixedWindowDefaults = exports.RedisStore = exports.MemoryStore = exports.defaultKeyGenerator = exports.createRateLimitEngine = exports.RateLimitEngine = exports.fastifyRateLimiter = exports.expressRateLimiter = exports.VERSION = void 0;
|
|
24
|
+
exports.createRateLimiter = createRateLimiter;
|
|
25
|
+
const express_js_1 = require("./middleware/express.js");
|
|
26
|
+
Object.defineProperty(exports, "expressRateLimiter", { enumerable: true, get: function () { return express_js_1.expressRateLimiter; } });
|
|
27
|
+
const fastify_js_1 = require("./middleware/fastify.js");
|
|
28
|
+
Object.defineProperty(exports, "fastifyRateLimiter", { enumerable: true, get: function () { return fastify_js_1.fastifyRateLimiter; } });
|
|
29
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
30
|
+
exports.VERSION = '0.1.0';
|
|
31
|
+
// Core engine and stores
|
|
32
|
+
var rate_limit_engine_js_1 = require("./strategies/rate-limit-engine.js");
|
|
33
|
+
Object.defineProperty(exports, "RateLimitEngine", { enumerable: true, get: function () { return rate_limit_engine_js_1.RateLimitEngine; } });
|
|
34
|
+
Object.defineProperty(exports, "createRateLimitEngine", { enumerable: true, get: function () { return rate_limit_engine_js_1.createRateLimiter; } });
|
|
35
|
+
Object.defineProperty(exports, "defaultKeyGenerator", { enumerable: true, get: function () { return rate_limit_engine_js_1.defaultKeyGenerator; } });
|
|
36
|
+
var memory_store_js_1 = require("./stores/memory-store.js");
|
|
37
|
+
Object.defineProperty(exports, "MemoryStore", { enumerable: true, get: function () { return memory_store_js_1.MemoryStore; } });
|
|
38
|
+
var redis_store_js_1 = require("./stores/redis-store.js");
|
|
39
|
+
Object.defineProperty(exports, "RedisStore", { enumerable: true, get: function () { return redis_store_js_1.RedisStore; } });
|
|
40
|
+
// Built-in defaults
|
|
41
|
+
var defaults_js_1 = require("./strategies/defaults.js");
|
|
42
|
+
Object.defineProperty(exports, "fixedWindowDefaults", { enumerable: true, get: function () { return defaults_js_1.fixedWindowDefaults; } });
|
|
43
|
+
Object.defineProperty(exports, "slidingWindowDefaults", { enumerable: true, get: function () { return defaults_js_1.slidingWindowDefaults; } });
|
|
44
|
+
Object.defineProperty(exports, "tokenBucketDefaults", { enumerable: true, get: function () { return defaults_js_1.tokenBucketDefaults; } });
|
|
45
|
+
// Shared types and enums
|
|
46
|
+
__exportStar(require("./types/index.js"), exports);
|
|
47
|
+
var index_js_1 = require("./types/index.js");
|
|
48
|
+
Object.defineProperty(exports, "RateLimitStrategy", { enumerable: true, get: function () { return index_js_1.RateLimitStrategy; } });
|
|
49
|
+
/**
|
|
50
|
+
* Convenience factory that returns both Express middleware and Fastify plugin.
|
|
51
|
+
*
|
|
52
|
+
* Usage:
|
|
53
|
+
* ```ts
|
|
54
|
+
* const limiter = createRateLimiter({ maxRequests: 100 });
|
|
55
|
+
* app.use(limiter.express); // Express
|
|
56
|
+
* await app.register(limiter.fastify); // Fastify
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* Prefer importing `expressRateLimiter` or `fastifyRateLimiter` directly when framework is known.
|
|
60
|
+
*/
|
|
61
|
+
function createRateLimiter(options) {
|
|
62
|
+
const wrappedPlugin = (0, fastify_plugin_1.default)(async (fastify, pluginOpts = {}) => {
|
|
63
|
+
const merged = { ...options, ...pluginOpts };
|
|
64
|
+
return fastify_js_1.fastifyRateLimiter(fastify, merged);
|
|
65
|
+
}, { name: 'ratelimit-flex-wrapper' });
|
|
66
|
+
return {
|
|
67
|
+
express: (0, express_js_1.expressRateLimiter)(options),
|
|
68
|
+
fastify: wrappedPlugin,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Express is the most common default integration path.
|
|
72
|
+
exports.default = express_js_1.expressRateLimiter;
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AAAA;;GAEG;;;;;;;;;;;;;;;;;;;;AA0CH,8CAgBC;AAxDD,wDAA6D;AAQpD,mGARA,+BAAkB,OAQA;AAP3B,wDAA6D;AAOhC,mGAPpB,+BAAkB,OAOoB;AAL/C,oEAAgC;AAEnB,QAAA,OAAO,GAAG,OAAO,CAAC;AAK/B,yBAAyB;AACzB,0EAM2C;AALzC,uHAAA,eAAe,OAAA;AACf,6HAAA,iBAAiB,OAAyB;AAC1C,2HAAA,mBAAmB,OAAA;AAIrB,4DAAuD;AAA9C,8GAAA,WAAW,OAAA;AACpB,0DAAqD;AAA5C,4GAAA,UAAU,OAAA;AAEnB,oBAAoB;AACpB,wDAA2G;AAAlG,kHAAA,mBAAmB,OAAA;AAAE,oHAAA,qBAAqB,OAAA;AAAE,kHAAA,mBAAmB,OAAA;AAExE,yBAAyB;AACzB,mDAAiC;AACjC,6CAAqD;AAA5C,6GAAA,iBAAiB,OAAA;AAE1B;;;;;;;;;;;GAWG;AACH,SAAgB,iBAAiB,CAAC,OAAkC;IAClE,MAAM,aAAa,GAAG,IAAA,wBAAE,EACtB,KAAK,EAAE,OAAgB,EAAE,aAAwC,EAAE,EAAE,EAAE;QACrE,MAAM,MAAM,GAAG,EAAE,GAAG,OAAO,EAAE,GAAG,UAAU,EAAE,CAAC;QAC7C,OAAQ,+BAA6F,CACnG,OAAO,EACP,MAAM,CACP,CAAC;IACJ,CAAC,EACD,EAAE,IAAI,EAAE,wBAAwB,EAAE,CACnC,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,IAAA,+BAAkB,EAAC,OAAO,CAAC;QACpC,OAAO,EAAE,aAA0C;KACpD,CAAC;AACJ,CAAC;AAED,uDAAuD;AACvD,kBAAe,+BAAkB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { RequestHandler } from 'express';
|
|
2
|
+
import type { RateLimitInfo, RateLimitOptions } from '../types/index.js';
|
|
3
|
+
declare module 'express-serve-static-core' {
|
|
4
|
+
interface Request {
|
|
5
|
+
/** Populated by {@link expressRateLimiter} after a successful consume (not blocked). */
|
|
6
|
+
rateLimit?: RateLimitInfo;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Express middleware factory. One {@link RateLimitEngine} and store are created per call (singleton for that middleware instance).
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* app.use(expressRateLimiter({ maxRequests: 50 }));
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function expressRateLimiter(options: Partial<RateLimitOptions>): RequestHandler;
|
|
18
|
+
//# sourceMappingURL=express.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.d.ts","sourceRoot":"","sources":["../../../src/middleware/express.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAyB,cAAc,EAAY,MAAM,SAAS,CAAC;AAE/E,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,QAAQ,2BAA2B,CAAC;IACzC,UAAU,OAAO;QACf,wFAAwF;QACxF,SAAS,CAAC,EAAE,aAAa,CAAC;KAC3B;CACF;AAQD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,cAAc,CAkDrF"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.expressRateLimiter = expressRateLimiter;
|
|
4
|
+
const rate_limit_engine_js_1 = require("../strategies/rate-limit-engine.js");
|
|
5
|
+
const merge_options_js_1 = require("./merge-options.js");
|
|
6
|
+
function applyHeaders(res, headers) {
|
|
7
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
8
|
+
res.setHeader(name, value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Express middleware factory. One {@link RateLimitEngine} and store are created per call (singleton for that middleware instance).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* app.use(expressRateLimiter({ maxRequests: 50 }));
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
function expressRateLimiter(options) {
|
|
20
|
+
const resolved = (0, merge_options_js_1.mergeRateLimiterOptions)(options);
|
|
21
|
+
const { onLimitReached, ...engineOptions } = resolved;
|
|
22
|
+
const engine = new rate_limit_engine_js_1.RateLimitEngine(engineOptions);
|
|
23
|
+
const keyGen = resolved.keyGenerator ?? rate_limit_engine_js_1.defaultKeyGenerator;
|
|
24
|
+
return async function rateLimitMiddleware(req, res, next) {
|
|
25
|
+
if (resolved.skip?.(req) === true) {
|
|
26
|
+
next();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const key = keyGen(req);
|
|
30
|
+
const result = await engine.consumeWithKey(key, req);
|
|
31
|
+
if (resolved.headers !== false) {
|
|
32
|
+
applyHeaders(res, result.headers);
|
|
33
|
+
}
|
|
34
|
+
if (result.isBlocked) {
|
|
35
|
+
if (onLimitReached) {
|
|
36
|
+
await Promise.resolve(onLimitReached(req, result));
|
|
37
|
+
}
|
|
38
|
+
res
|
|
39
|
+
.status(resolved.statusCode ?? 429)
|
|
40
|
+
.json((0, merge_options_js_1.jsonErrorBody)(resolved.message ?? 'Too many requests'));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
req.rateLimit = (0, merge_options_js_1.toRateLimitInfo)(resolved, result);
|
|
44
|
+
const shouldDecrementFailed = resolved.skipFailedRequests === true;
|
|
45
|
+
const shouldDecrementSuccess = resolved.skipSuccessfulRequests === true;
|
|
46
|
+
if (shouldDecrementFailed || shouldDecrementSuccess) {
|
|
47
|
+
res.once('finish', () => {
|
|
48
|
+
const status = res.statusCode;
|
|
49
|
+
const failed = status >= 400;
|
|
50
|
+
const success = status < 400;
|
|
51
|
+
if ((shouldDecrementFailed && failed) || (shouldDecrementSuccess && success)) {
|
|
52
|
+
void resolved.store.decrement(key).catch(() => {
|
|
53
|
+
/* ignore */
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
next();
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=express.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"express.js","sourceRoot":"","sources":["../../../src/middleware/express.ts"],"names":[],"mappings":";;AA0BA,gDAkDC;AA3ED,6EAA0F;AAE1F,yDAA6F;AAS7F,SAAS,YAAY,CAAC,GAAa,EAAE,OAA+B;IAClE,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACpD,GAAG,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,SAAgB,kBAAkB,CAAC,OAAkC;IACnE,MAAM,QAAQ,GAAG,IAAA,0CAAuB,EAAC,OAAO,CAAC,CAAC;IAClD,MAAM,EAAE,cAAc,EAAE,GAAG,aAAa,EAAE,GAAG,QAAQ,CAAC;IACtD,MAAM,MAAM,GAAG,IAAI,sCAAe,CAAC,aAAa,CAAC,CAAC;IAClD,MAAM,MAAM,GAAG,QAAQ,CAAC,YAAY,IAAI,0CAAmB,CAAC;IAE5D,OAAO,KAAK,UAAU,mBAAmB,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB;QACvF,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;YAClC,IAAI,EAAE,CAAC;YACP,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;QAExB,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QAErD,IAAI,QAAQ,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YAC/B,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QACpC,CAAC;QAED,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,IAAI,cAAc,EAAE,CAAC;gBACnB,MAAM,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;YACrD,CAAC;YACD,GAAG;iBACA,MAAM,CAAC,QAAQ,CAAC,UAAU,IAAI,GAAG,CAAC;iBAClC,IAAI,CAAC,IAAA,gCAAa,EAAC,QAAQ,CAAC,OAAO,IAAI,mBAAmB,CAAC,CAAC,CAAC;YAChE,OAAO;QACT,CAAC;QAED,GAAG,CAAC,SAAS,GAAG,IAAA,kCAAe,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAElD,MAAM,qBAAqB,GAAG,QAAQ,CAAC,kBAAkB,KAAK,IAAI,CAAC;QACnE,MAAM,sBAAsB,GAAG,QAAQ,CAAC,sBAAsB,KAAK,IAAI,CAAC;QAExE,IAAI,qBAAqB,IAAI,sBAAsB,EAAE,CAAC;YACpD,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,EAAE;gBACtB,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC;gBAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,GAAG,CAAC;gBAC7B,MAAM,OAAO,GAAG,MAAM,GAAG,GAAG,CAAC;gBAC7B,IAAI,CAAC,qBAAqB,IAAI,MAAM,CAAC,IAAI,CAAC,sBAAsB,IAAI,OAAO,CAAC,EAAE,CAAC;oBAC7E,KAAK,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;wBAC5C,YAAY;oBACd,CAAC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC,CAAC,CAAC;QACL,CAAC;QAED,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { FastifyPluginAsync } from 'fastify';
|
|
2
|
+
import type { RateLimitInfo, RateLimitOptions } from '../types/index.js';
|
|
3
|
+
declare module 'fastify' {
|
|
4
|
+
interface FastifyRequest {
|
|
5
|
+
/** Populated by {@link fastifyRateLimiter} after a successful consume (not blocked). */
|
|
6
|
+
rateLimit?: RateLimitInfo;
|
|
7
|
+
/** Internal: storage key for optional response-based decrement. */
|
|
8
|
+
rateLimitKey?: string;
|
|
9
|
+
/** Internal: when to consider decrement on `onResponse`. */
|
|
10
|
+
rateLimitDecrementFlags?: {
|
|
11
|
+
onFailed: boolean;
|
|
12
|
+
onSuccess: boolean;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Fastify plugin (wrapped with `fastify-plugin` for correct encapsulation).
|
|
18
|
+
* Registers `onRequest` / `onResponse` hooks; one {@link RateLimitEngine} and store per registration.
|
|
19
|
+
*/
|
|
20
|
+
export declare const fastifyRateLimiter: FastifyPluginAsync<Partial<RateLimitOptions>>;
|
|
21
|
+
//# sourceMappingURL=fastify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../../../src/middleware/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAGlD,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGzE,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,wFAAwF;QACxF,SAAS,CAAC,EAAE,aAAa,CAAC;QAC1B,mEAAmE;QACnE,YAAY,CAAC,EAAE,MAAM,CAAC;QACtB,4DAA4D;QAC5D,uBAAuB,CAAC,EAAE;YACxB,QAAQ,EAAE,OAAO,CAAC;YAClB,SAAS,EAAE,OAAO,CAAC;SACpB,CAAC;KACH;CACF;AA6DD;;;GAGG;AACH,eAAO,MAAM,kBAAkB,+CAE7B,CAAC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.fastifyRateLimiter = void 0;
|
|
7
|
+
const fastify_plugin_1 = __importDefault(require("fastify-plugin"));
|
|
8
|
+
const rate_limit_engine_js_1 = require("../strategies/rate-limit-engine.js");
|
|
9
|
+
const merge_options_js_1 = require("./merge-options.js");
|
|
10
|
+
const plugin = async (fastify, options) => {
|
|
11
|
+
const resolved = (0, merge_options_js_1.mergeRateLimiterOptions)(options);
|
|
12
|
+
const { onLimitReached, ...engineOptions } = resolved;
|
|
13
|
+
const engine = new rate_limit_engine_js_1.RateLimitEngine(engineOptions);
|
|
14
|
+
const keyGen = resolved.keyGenerator ?? rate_limit_engine_js_1.defaultKeyGenerator;
|
|
15
|
+
fastify.addHook('onRequest', async (request, reply) => {
|
|
16
|
+
if (resolved.skip?.(request) === true) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const key = keyGen(request);
|
|
20
|
+
const result = await engine.consumeWithKey(key, request);
|
|
21
|
+
if (resolved.headers !== false) {
|
|
22
|
+
for (const [name, value] of Object.entries(result.headers)) {
|
|
23
|
+
reply.header(name, value);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (result.isBlocked) {
|
|
27
|
+
if (onLimitReached) {
|
|
28
|
+
await Promise.resolve(onLimitReached(request, result));
|
|
29
|
+
}
|
|
30
|
+
await reply
|
|
31
|
+
.status(resolved.statusCode ?? 429)
|
|
32
|
+
.send((0, merge_options_js_1.jsonErrorBody)(resolved.message ?? 'Too many requests'));
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
request.rateLimit = (0, merge_options_js_1.toRateLimitInfo)(resolved, result);
|
|
36
|
+
const onFailed = resolved.skipFailedRequests === true;
|
|
37
|
+
const onSuccess = resolved.skipSuccessfulRequests === true;
|
|
38
|
+
if (onFailed || onSuccess) {
|
|
39
|
+
request.rateLimitKey = key;
|
|
40
|
+
request.rateLimitDecrementFlags = { onFailed, onSuccess };
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
fastify.addHook('onResponse', async (request, reply) => {
|
|
44
|
+
const key = request.rateLimitKey;
|
|
45
|
+
const flags = request.rateLimitDecrementFlags;
|
|
46
|
+
if (!key || !flags) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const status = reply.statusCode;
|
|
50
|
+
const failed = status >= 400;
|
|
51
|
+
const success = status < 400;
|
|
52
|
+
if ((flags.onFailed && failed) || (flags.onSuccess && success)) {
|
|
53
|
+
void resolved.store.decrement(key).catch(() => {
|
|
54
|
+
/* ignore */
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Fastify plugin (wrapped with `fastify-plugin` for correct encapsulation).
|
|
61
|
+
* Registers `onRequest` / `onResponse` hooks; one {@link RateLimitEngine} and store per registration.
|
|
62
|
+
*/
|
|
63
|
+
exports.fastifyRateLimiter = (0, fastify_plugin_1.default)(plugin, {
|
|
64
|
+
name: 'ratelimit-flex',
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=fastify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fastify.js","sourceRoot":"","sources":["../../../src/middleware/fastify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAE7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/** Framework middleware adapters (Express, Fastify, etc.) */
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.fastifyRateLimiter = exports.expressRateLimiter = void 0;
|
|
5
|
+
var express_js_1 = require("./express.js");
|
|
6
|
+
Object.defineProperty(exports, "expressRateLimiter", { enumerable: true, get: function () { return express_js_1.expressRateLimiter; } });
|
|
7
|
+
var fastify_js_1 = require("./fastify.js");
|
|
8
|
+
Object.defineProperty(exports, "fastifyRateLimiter", { enumerable: true, get: function () { return fastify_js_1.fastifyRateLimiter; } });
|
|
9
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/middleware/index.ts"],"names":[],"mappings":";AAAA,6DAA6D;;;AAE7D,2CAAkD;AAAzC,gHAAA,kBAAkB,OAAA;AAC3B,2CAAkD;AAAzC,gHAAA,kBAAkB,OAAA"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { RateLimitInfo, RateLimitOptions, RateLimitResult } from '../types/index.js';
|
|
2
|
+
export declare const baseDefaults: {
|
|
3
|
+
readonly headers: true;
|
|
4
|
+
readonly statusCode: 429;
|
|
5
|
+
readonly message: "Too many requests";
|
|
6
|
+
readonly skipFailedRequests: false;
|
|
7
|
+
readonly skipSuccessfulRequests: false;
|
|
8
|
+
};
|
|
9
|
+
export declare function getLimit(opts: RateLimitOptions): number;
|
|
10
|
+
/**
|
|
11
|
+
* Merge partial options with strategy defaults and ensure a {@link MemoryStore} when `store` is omitted.
|
|
12
|
+
*/
|
|
13
|
+
export declare function mergeRateLimiterOptions(options: Partial<RateLimitOptions>): RateLimitOptions;
|
|
14
|
+
export declare function toRateLimitInfo(opts: RateLimitOptions, result: RateLimitResult): RateLimitInfo;
|
|
15
|
+
export declare function jsonErrorBody(message: string | object): object;
|
|
16
|
+
//# sourceMappingURL=merge-options.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-options.d.ts","sourceRoot":"","sources":["../../../src/middleware/merge-options.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAG1F,eAAO,MAAM,YAAY;;;;;;CAMf,CAAC;AAEX,wBAAgB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,MAAM,CAKvD;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAwC5F;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,gBAAgB,EAAE,MAAM,EAAE,eAAe,GAAG,aAAa,CAO9F;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAE9D"}
|