ratelimit-engine 0.0.0-stage → 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 +21 -0
- package/README.md +437 -2
- package/bin/cli.js +263 -0
- package/package.json +56 -4
- package/src/adapters/BaseAdapter.js +38 -0
- package/src/adapters/ExpressAdapter.js +64 -0
- package/src/adapters/FastifyAdapter.js +67 -0
- package/src/algorithms/BaseAlgorithm.js +40 -0
- package/src/algorithms/Composite.js +97 -0
- package/src/algorithms/FixedWindow.js +70 -0
- package/src/algorithms/LeakyBucket.js +88 -0
- package/src/algorithms/SlidingWindow.js +99 -0
- package/src/algorithms/TokenBucket.js +85 -0
- package/src/constants/algorithms.js +18 -0
- package/src/constants/defaults.js +15 -0
- package/src/constants/errors.js +42 -0
- package/src/constants/headers.js +18 -0
- package/src/constants/store.js +20 -0
- package/src/engine/HeaderWriter.js +56 -0
- package/src/engine/KeyResolver.js +41 -0
- package/src/engine/RateLimiter.js +145 -0
- package/src/engine/createLimiter.js +37 -0
- package/src/errors/ConfigurationError.js +20 -0
- package/src/errors/ErrorFactory.js +117 -0
- package/src/errors/RateLimitError.js +25 -0
- package/src/errors/StoreError.js +20 -0
- package/src/index.js +52 -0
- package/src/logger/Logger.js +127 -0
- package/src/logger/LoggerFactory.js +36 -0
- package/src/resolvers/byAPIKey.js +11 -0
- package/src/resolvers/byIP.js +11 -0
- package/src/resolvers/byRoute.js +13 -0
- package/src/resolvers/byUser.js +11 -0
- package/src/resolvers/composite.js +18 -0
- package/src/stores/BaseStore.js +69 -0
- package/src/stores/MemoryStore.js +129 -0
- package/src/stores/RedisStore.js +168 -0
- package/src/types/index.js +79 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Abhinav710
|
|
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
CHANGED
|
@@ -1,3 +1,438 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ratelimit-engine
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> Enterprise-grade, algorithm-pluggable rate limiting engine for Node.js
|
|
4
|
+
|
|
5
|
+
[](https://github.com/your-org/ratelimit-engine/actions)
|
|
6
|
+
[](https://www.npmjs.com/package/ratelimit-engine)
|
|
7
|
+
[](./LICENSE)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](https://github.com/Abhinav710)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Version
|
|
14
|
+
|
|
15
|
+
| Version | Status | Node.js | Release Date |
|
|
16
|
+
|---------|------------|----------|--------------|
|
|
17
|
+
| `1.0.0` | **Stable** | `>= 18` | 2024-11-12 |
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Why ratelimit-engine?
|
|
22
|
+
|
|
23
|
+
Most rate limiting libraries give you a fixed algorithm with configurable numbers.
|
|
24
|
+
`ratelimit-engine` treats the **algorithm as a first-class plugin** — swap it, compose it, or write your own without changing a line of engine code.
|
|
25
|
+
|
|
26
|
+
| Feature | express-rate-limit | rate-limiter-flexible | **ratelimit-engine** |
|
|
27
|
+
|-------------------------------|--------------------|-----------------------|----------------------|
|
|
28
|
+
| Pluggable algorithm | No | No | **Yes** |
|
|
29
|
+
| Compose multiple algorithms | No | No | **Yes** |
|
|
30
|
+
| Pluggable store | Partial | Yes | **Yes** |
|
|
31
|
+
| Structured JSON logging | No | No | **Yes** |
|
|
32
|
+
| Custom error factory | No | No | **Yes** |
|
|
33
|
+
| Framework-agnostic core | No | Partial | **Yes** |
|
|
34
|
+
| SOLID architecture | No | No | **Yes** |
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- **5 built-in algorithms** — Fixed Window, Sliding Window, Token Bucket, Leaky Bucket, Composite
|
|
41
|
+
- **Composite mode** — combine algorithms with `ALL` (every must pass) or `ANY` (one must pass)
|
|
42
|
+
- **Pluggable stores** — in-memory (dev/test) and Redis with atomic Lua scripts (production)
|
|
43
|
+
- **Pluggable algorithms** — implement `BaseAlgorithm` to ship your own logic
|
|
44
|
+
- **Key resolvers** — by IP, user ID, API key, route, or any combination
|
|
45
|
+
- **Structured logging** — JSON logs with pluggable transport (Winston, Pino, or default stdout)
|
|
46
|
+
- **Error factory** — every error has a machine-readable code, human message, and context
|
|
47
|
+
- **Framework adapters** — Express and Fastify out of the box
|
|
48
|
+
- **Standard HTTP headers** — `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, `Retry-After`
|
|
49
|
+
- **Fail-open / fail-closed** — configurable behaviour when the store is unavailable
|
|
50
|
+
- **Lifecycle hooks** — `onBlocked` and `onAllowed` callbacks for observability
|
|
51
|
+
- **Zero magic values** — all constants in dedicated files, JSDoc types on every contract
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install ratelimit-engine
|
|
59
|
+
|
|
60
|
+
# Redis store requires ioredis (peer dependency)
|
|
61
|
+
npm install ioredis
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Quick Start
|
|
67
|
+
|
|
68
|
+
```js
|
|
69
|
+
import { createLimiter, TokenBucket, MemoryStore, byIP } from 'ratelimit-engine'
|
|
70
|
+
import express from 'express'
|
|
71
|
+
|
|
72
|
+
const app = express()
|
|
73
|
+
|
|
74
|
+
const limiter = createLimiter({
|
|
75
|
+
algorithm: new TokenBucket({ capacity: 100, refillRate: 10 }),
|
|
76
|
+
store: new MemoryStore(),
|
|
77
|
+
keyBy: byIP,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
app.use(limiter.express())
|
|
81
|
+
|
|
82
|
+
app.get('/', (_req, res) => res.json({ message: 'ok' }))
|
|
83
|
+
app.listen(3000)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## Architecture
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
92
|
+
│ Your Application │
|
|
93
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
94
|
+
│
|
|
95
|
+
┌──────────────────────────▼──────────────────────────────────┐
|
|
96
|
+
│ Framework Adapters │
|
|
97
|
+
│ ExpressAdapter · FastifyAdapter │
|
|
98
|
+
│ Extracts RequestContext from req object │
|
|
99
|
+
└──────────────────────────┬──────────────────────────────────┘
|
|
100
|
+
│ RequestContext
|
|
101
|
+
┌──────────────────────────▼──────────────────────────────────┐
|
|
102
|
+
│ Engine Core │
|
|
103
|
+
│ │
|
|
104
|
+
│ KeyResolver RateLimiter HeaderWriter │
|
|
105
|
+
│ resolves key ──► orchestrates ──► writes headers │
|
|
106
|
+
│ from context check/reset on response │
|
|
107
|
+
└───────────┬────────────────────┬────────────────────────────┘
|
|
108
|
+
│ │
|
|
109
|
+
┌─────────▼──────┐ ┌─────────▼──────────────────────────┐
|
|
110
|
+
│ Algorithm Layer│ │ Store Layer │
|
|
111
|
+
│ │ │ │
|
|
112
|
+
│ BaseAlgorithm │ │ BaseStore │
|
|
113
|
+
│ FixedWindow │ │ MemoryStore (dev / single node) │
|
|
114
|
+
│ SlidingWindow │ │ RedisStore (prod / distributed) │
|
|
115
|
+
│ TokenBucket │ │ │
|
|
116
|
+
│ LeakyBucket │ │ Redis uses atomic Lua scripts — │
|
|
117
|
+
│ Composite │ │ no race conditions across pods │
|
|
118
|
+
│ [Your own] │ │ [Your own store] │
|
|
119
|
+
└────────────────┘ └─────────────────────────────────────┘
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Request Flow
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
Incoming Request
|
|
126
|
+
│
|
|
127
|
+
▼
|
|
128
|
+
Adapter.extractContext(req)
|
|
129
|
+
│ builds RequestContext {ip, userId, apiKey, method, path, headers}
|
|
130
|
+
▼
|
|
131
|
+
KeyResolver.resolve(context)
|
|
132
|
+
│ applies keyBy fn → prefixed string key e.g. "rle:ip:1.2.3.4"
|
|
133
|
+
▼
|
|
134
|
+
Algorithm.check(key, store)
|
|
135
|
+
│ reads/writes store, applies algorithm math
|
|
136
|
+
│ returns RateLimitResult {allowed, limit, remaining, resetAt, retryAfter}
|
|
137
|
+
▼
|
|
138
|
+
HeaderWriter.write(res, result)
|
|
139
|
+
│ sets X-RateLimit-* headers
|
|
140
|
+
▼
|
|
141
|
+
allowed?
|
|
142
|
+
├── YES → next() / continue
|
|
143
|
+
└── NO → HeaderWriter.sendBlocked() → 429 + Retry-After
|
|
144
|
+
│
|
|
145
|
+
▼
|
|
146
|
+
onBlocked hook (optional)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Module Dependency Map
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
index.js
|
|
153
|
+
├── engine/createLimiter.js (factory)
|
|
154
|
+
│ └── engine/RateLimiter.js
|
|
155
|
+
│ ├── engine/KeyResolver.js
|
|
156
|
+
│ ├── engine/HeaderWriter.js
|
|
157
|
+
│ ├── algorithms/BaseAlgorithm.js
|
|
158
|
+
│ └── stores/BaseStore.js
|
|
159
|
+
├── algorithms/*
|
|
160
|
+
├── stores/*
|
|
161
|
+
├── adapters/*
|
|
162
|
+
├── resolvers/*
|
|
163
|
+
├── logger/Logger.js + LoggerFactory.js
|
|
164
|
+
├── errors/ErrorFactory.js ← single source of all errors
|
|
165
|
+
├── constants/* ← single source of all strings/numbers
|
|
166
|
+
└── types/index.js ← single source of all JSDoc contracts
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Algorithms
|
|
172
|
+
|
|
173
|
+
### Fixed Window
|
|
174
|
+
|
|
175
|
+
Counts requests in a fixed time slot. Simple and cheap. Boundary bursts are possible.
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
import { FixedWindow } from 'ratelimit-engine'
|
|
179
|
+
|
|
180
|
+
new FixedWindow({ limit: 100, windowMs: 60_000 })
|
|
181
|
+
// 100 requests per minute
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Sliding Window
|
|
185
|
+
|
|
186
|
+
Weighted approximation of a rolling window. No boundary spike problem.
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
import { SlidingWindow } from 'ratelimit-engine'
|
|
190
|
+
|
|
191
|
+
new SlidingWindow({ limit: 100, windowMs: 60_000 })
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Token Bucket
|
|
195
|
+
|
|
196
|
+
Tokens refill at a fixed rate. Allows controlled bursting.
|
|
197
|
+
|
|
198
|
+
```js
|
|
199
|
+
import { TokenBucket } from 'ratelimit-engine'
|
|
200
|
+
|
|
201
|
+
new TokenBucket({ capacity: 100, refillRate: 10 })
|
|
202
|
+
// bucket holds 100 tokens, refills 10/sec
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Leaky Bucket
|
|
206
|
+
|
|
207
|
+
Requests processed at a fixed rate. Excess dropped. Strict output rate control.
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
import { LeakyBucket } from 'ratelimit-engine'
|
|
211
|
+
|
|
212
|
+
new LeakyBucket({ capacity: 100, leakRate: 10 })
|
|
213
|
+
// queue depth 100, drains 10 requests/sec
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Composite
|
|
217
|
+
|
|
218
|
+
Combines multiple algorithms. Use `ALL` (every must pass) or `ANY` (one must pass).
|
|
219
|
+
|
|
220
|
+
```js
|
|
221
|
+
import { Composite, TokenBucket, SlidingWindow, COMPOSITE_MODES } from 'ratelimit-engine'
|
|
222
|
+
|
|
223
|
+
// Allow burst but enforce a per-minute hard cap
|
|
224
|
+
new Composite({
|
|
225
|
+
algorithms: [
|
|
226
|
+
new TokenBucket({ capacity: 50, refillRate: 10 }),
|
|
227
|
+
new SlidingWindow({ limit: 200, windowMs: 60_000 }),
|
|
228
|
+
],
|
|
229
|
+
mode: COMPOSITE_MODES.ALL,
|
|
230
|
+
})
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Custom Algorithm
|
|
234
|
+
|
|
235
|
+
Implement `BaseAlgorithm` to plug in your own logic:
|
|
236
|
+
|
|
237
|
+
```js
|
|
238
|
+
import { BaseAlgorithm } from 'ratelimit-engine'
|
|
239
|
+
|
|
240
|
+
class MyAlgorithm extends BaseAlgorithm {
|
|
241
|
+
constructor() {
|
|
242
|
+
super('my-algorithm')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async check(key, store) {
|
|
246
|
+
// your logic here
|
|
247
|
+
// store.get / store.set / store.increment available
|
|
248
|
+
return {
|
|
249
|
+
allowed: true,
|
|
250
|
+
limit: 100,
|
|
251
|
+
remaining: 99,
|
|
252
|
+
resetAt: new Date(Date.now() + 60_000),
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async reset(key, store) {
|
|
257
|
+
await store.delete(key)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
## Stores
|
|
265
|
+
|
|
266
|
+
### MemoryStore
|
|
267
|
+
|
|
268
|
+
Single-instance, in-process. Best for development and testing.
|
|
269
|
+
|
|
270
|
+
```js
|
|
271
|
+
import { MemoryStore } from 'ratelimit-engine'
|
|
272
|
+
|
|
273
|
+
new MemoryStore()
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### RedisStore
|
|
277
|
+
|
|
278
|
+
Distributed, atomic Lua scripts. Required for multi-pod / Kubernetes deployments.
|
|
279
|
+
|
|
280
|
+
```js
|
|
281
|
+
import { RedisStore } from 'ratelimit-engine'
|
|
282
|
+
import Redis from 'ioredis'
|
|
283
|
+
|
|
284
|
+
const redis = new Redis({ host: 'localhost', port: 6379 })
|
|
285
|
+
|
|
286
|
+
new RedisStore({ client: redis, prefix: 'myapp:rl:' })
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Custom Store
|
|
290
|
+
|
|
291
|
+
Implement `BaseStore` to use any backend (Postgres, Valkey, DragonflyDB, etc.):
|
|
292
|
+
|
|
293
|
+
```js
|
|
294
|
+
import { BaseStore } from 'ratelimit-engine'
|
|
295
|
+
|
|
296
|
+
class PostgresStore extends BaseStore {
|
|
297
|
+
constructor(pool) {
|
|
298
|
+
super('postgres')
|
|
299
|
+
this._pool = pool
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
async get(key) { /* ... */ }
|
|
303
|
+
async set(key, entry, ttlMs) { /* ... */ }
|
|
304
|
+
async increment(key, ttlMs) { /* ... */ }
|
|
305
|
+
async delete(key) { /* ... */ }
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Key Resolvers
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
import { byIP, byUser, byAPIKey, byRoute, compositeResolver } from 'ratelimit-engine'
|
|
315
|
+
|
|
316
|
+
byIP // "ip:1.2.3.4"
|
|
317
|
+
byUser // "user:abc123"
|
|
318
|
+
byAPIKey // "apikey:sk-..."
|
|
319
|
+
byRoute // "route:GET:/api/data"
|
|
320
|
+
|
|
321
|
+
// Combine resolvers — scopes limit to user + route together
|
|
322
|
+
compositeResolver([byUser, byRoute]) // "user:abc123|route:GET:/api/data"
|
|
323
|
+
|
|
324
|
+
// Custom resolver
|
|
325
|
+
const byTenant = (ctx) => `tenant:${ctx.rawRequest.headers['x-tenant-id']}`
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Framework Adapters
|
|
331
|
+
|
|
332
|
+
### Express
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
const limiter = createLimiter({ algorithm, store, keyBy })
|
|
336
|
+
|
|
337
|
+
app.use(limiter.express()) // global
|
|
338
|
+
app.use('/api', limiter.express()) // route-scoped
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Fastify
|
|
342
|
+
|
|
343
|
+
```js
|
|
344
|
+
fastify.addHook('onRequest', limiter.fastify())
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Framework-agnostic
|
|
348
|
+
|
|
349
|
+
```js
|
|
350
|
+
const result = await limiter.check({
|
|
351
|
+
ip: '1.2.3.4',
|
|
352
|
+
userId: 'user:99',
|
|
353
|
+
method: 'GET',
|
|
354
|
+
path: '/api/data',
|
|
355
|
+
headers: {},
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
if (!result.allowed) {
|
|
359
|
+
// handle 429 yourself
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Logging
|
|
366
|
+
|
|
367
|
+
Default output is structured JSON to stdout/stderr. Plug in Winston or Pino:
|
|
368
|
+
|
|
369
|
+
```js
|
|
370
|
+
import { createLimiter, wrapExternalLogger } from 'ratelimit-engine'
|
|
371
|
+
import winston from 'winston'
|
|
372
|
+
|
|
373
|
+
const logger = winston.createLogger({ /* ... */ })
|
|
374
|
+
|
|
375
|
+
const limiter = createLimiter({
|
|
376
|
+
algorithm, store, keyBy,
|
|
377
|
+
logger: wrapExternalLogger(logger),
|
|
378
|
+
})
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Log levels: `debug` → `info` → `warn` → `error`
|
|
382
|
+
|
|
383
|
+
---
|
|
384
|
+
|
|
385
|
+
## Error Handling
|
|
386
|
+
|
|
387
|
+
Every error has a machine-readable `code` from `ERROR_CODES`:
|
|
388
|
+
|
|
389
|
+
```js
|
|
390
|
+
import { ErrorFactory, ERROR_CODES } from 'ratelimit-engine'
|
|
391
|
+
|
|
392
|
+
try {
|
|
393
|
+
await limiter.check(context)
|
|
394
|
+
} catch (err) {
|
|
395
|
+
if (err.code === ERROR_CODES.STORE_UNAVAILABLE) {
|
|
396
|
+
// handle store outage
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
403
|
+
## API Reference
|
|
404
|
+
|
|
405
|
+
### `createLimiter(config)`
|
|
406
|
+
|
|
407
|
+
| Field | Type | Required | Default | Description |
|
|
408
|
+
|--------------|-------------------|----------|---------|------------------------------------------|
|
|
409
|
+
| `algorithm` | `BaseAlgorithm` | Yes | — | Algorithm instance |
|
|
410
|
+
| `store` | `BaseStore` | Yes | — | Store instance |
|
|
411
|
+
| `keyBy` | `Function` | Yes | — | Key resolver function |
|
|
412
|
+
| `failOpen` | `boolean` | No | `true` | Allow requests when store is unavailable |
|
|
413
|
+
| `onBlocked` | `Function` | No | — | Hook called when request is blocked |
|
|
414
|
+
| `onAllowed` | `Function` | No | — | Hook called when request is allowed |
|
|
415
|
+
| `logger` | `LoggerInterface` | No | — | Custom logger instance |
|
|
416
|
+
|
|
417
|
+
Returns `{ limiter, express(), fastify(), check(), reset(), destroy() }`
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Examples
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
node examples/express-basic/index.js # Token Bucket + MemoryStore + byIP
|
|
425
|
+
node examples/express-composite/index.js # Composite + MemoryStore + byUser
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
## Contributing
|
|
431
|
+
|
|
432
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md).
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## License
|
|
437
|
+
|
|
438
|
+
[MIT](./LICENSE)
|