nestjs-advanced-rate-limiter 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +347 -0
- package/dist/decorators/index.d.ts +3 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +3 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/skip-throttle.decorator.d.ts +12 -0
- package/dist/decorators/skip-throttle.decorator.d.ts.map +1 -0
- package/dist/decorators/skip-throttle.decorator.js +14 -0
- package/dist/decorators/skip-throttle.decorator.js.map +1 -0
- package/dist/decorators/throttle.decorator.d.ts +13 -0
- package/dist/decorators/throttle.decorator.d.ts.map +1 -0
- package/dist/decorators/throttle.decorator.js +14 -0
- package/dist/decorators/throttle.decorator.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/throttler.constants.d.ts +4 -0
- package/dist/throttler.constants.d.ts.map +1 -0
- package/dist/throttler.constants.js +4 -0
- package/dist/throttler.constants.js.map +1 -0
- package/dist/throttler.guard.d.ts +18 -0
- package/dist/throttler.guard.d.ts.map +1 -0
- package/dist/throttler.guard.js +118 -0
- package/dist/throttler.guard.js.map +1 -0
- package/dist/throttler.interfaces.d.ts +21 -0
- package/dist/throttler.interfaces.d.ts.map +1 -0
- package/dist/throttler.interfaces.js +2 -0
- package/dist/throttler.interfaces.js.map +1 -0
- package/dist/throttler.module.d.ts +9 -0
- package/dist/throttler.module.d.ts.map +1 -0
- package/dist/throttler.module.js +65 -0
- package/dist/throttler.module.js.map +1 -0
- package/dist/trackers.d.ts +32 -0
- package/dist/trackers.d.ts.map +1 -0
- package/dist/trackers.js +84 -0
- package/dist/trackers.js.map +1 -0
- package/package.json +78 -0
package/README.md
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# nestjs-advanced-rate-limiter
|
|
2
|
+
|
|
3
|
+
Advanced rate limiter for [NestJS](https://nestjs.com). Drop-in replacement for [`@nestjs/throttler`](https://docs.nestjs.com/security/rate-limiting) with real algorithms: token bucket (GCRA), sliding window, and fixed window. In-memory or Redis.
|
|
4
|
+
|
|
5
|
+
Built on [elysia-advanced-rate-limiter](https://github.com/mrtcmn/elysia-advanced-rate-limitter) core -- the same algorithms Stripe and Cloudflare run in production.
|
|
6
|
+
|
|
7
|
+
## Why not `@nestjs/throttler`?
|
|
8
|
+
|
|
9
|
+
| | `@nestjs/throttler` | `nestjs-advanced-rate-limiter` |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| Algorithms | TTL-based counter | Token bucket (GCRA), sliding window, fixed window |
|
|
12
|
+
| Burst control | None | GCRA allows controlled bursts while enforcing steady rate |
|
|
13
|
+
| Boundary accuracy | Resets at TTL expiry | Sliding window eliminates 2x burst at boundaries |
|
|
14
|
+
| Storage efficiency | Stores timestamps | 21-64 bytes per key depending on algorithm |
|
|
15
|
+
| Redis | Via separate adapter | Built-in `RedisStore` with circuit breaker (`ResilientStore`) |
|
|
16
|
+
| Per-route override | Multiple throttler configs | `@Throttle()` decorator with any algorithm |
|
|
17
|
+
| User tracking | Manual | Built-in JWT/session `userTracker` |
|
|
18
|
+
| IP resolution | Basic | Cloudflare, nginx, proxy-depth-aware |
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install nestjs-advanced-rate-limiter
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Module } from "@nestjs/common";
|
|
30
|
+
import { APP_GUARD } from "@nestjs/core";
|
|
31
|
+
import { ThrottlerModule, ThrottlerGuard } from "nestjs-advanced-rate-limiter";
|
|
32
|
+
|
|
33
|
+
@Module({
|
|
34
|
+
imports: [
|
|
35
|
+
ThrottlerModule.forRoot({
|
|
36
|
+
algorithm: { algorithm: "token-bucket", capacity: 100, refillRate: 10 },
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
providers: [
|
|
40
|
+
{
|
|
41
|
+
provide: APP_GUARD,
|
|
42
|
+
useExisting: ThrottlerGuard,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
export class AppModule {}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Works out of the box. In-memory, token bucket, 100 capacity, 10 tokens/sec. Every route is protected.
|
|
50
|
+
|
|
51
|
+
## Algorithms
|
|
52
|
+
|
|
53
|
+
Three algorithms. All O(1) time and space per request.
|
|
54
|
+
|
|
55
|
+
### Token Bucket (GCRA) -- Default
|
|
56
|
+
|
|
57
|
+
Allows bursts up to `capacity` while enforcing a steady `refillRate` per second. Uses GCRA (Generic Cell Rate Algorithm) internally -- no refill loops, no locks, one number comparison.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
ThrottlerModule.forRoot({
|
|
61
|
+
algorithm: { algorithm: "token-bucket", capacity: 100, refillRate: 10 },
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
capacity = 5, refillRate = 1/sec
|
|
67
|
+
|
|
68
|
+
t=0ms Req #1 ALLOWED (5 tokens -> 4)
|
|
69
|
+
t=0ms Req #2 ALLOWED (4 tokens -> 3)
|
|
70
|
+
t=0ms Req #3 ALLOWED (3 tokens -> 2)
|
|
71
|
+
t=0ms Req #4 ALLOWED (2 tokens -> 1)
|
|
72
|
+
t=0ms Req #5 ALLOWED (1 tokens -> 0)
|
|
73
|
+
t=0ms Req #6 DENIED retryAfter=1s
|
|
74
|
+
t=1000 Req #7 ALLOWED (1 token refilled)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Storage: **21 bytes per key**
|
|
78
|
+
|
|
79
|
+
### Fixed Window
|
|
80
|
+
|
|
81
|
+
Simplest option. Divides time into equal blocks, counts requests per block. Counter resets at each boundary.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
ThrottlerModule.forRoot({
|
|
85
|
+
algorithm: { algorithm: "fixed-window", limit: 100, windowMs: 60_000 },
|
|
86
|
+
})
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Storage: **39 bytes per key**
|
|
90
|
+
|
|
91
|
+
> **Note:** Clients can send `limit` requests at the end of one window and `limit` at the start of the next, getting 2x the limit in a short span. If this matters, use sliding window.
|
|
92
|
+
|
|
93
|
+
### Sliding Window
|
|
94
|
+
|
|
95
|
+
Blends current and previous window counts to eliminate the boundary burst. Uses the two-counter approximation.
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
ThrottlerModule.forRoot({
|
|
99
|
+
algorithm: { algorithm: "sliding-window", limit: 100, windowMs: 60_000 },
|
|
100
|
+
})
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Storage: **64 bytes per key**
|
|
104
|
+
|
|
105
|
+
### Algorithm Comparison
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Fixed Window Sliding Window Token Bucket (GCRA)
|
|
109
|
+
+----------------+------------------+--------------------+
|
|
110
|
+
Time | O(1) | O(1) | O(1) |
|
|
111
|
+
Space/key | 39 bytes | 64 bytes | 21 bytes |
|
|
112
|
+
Redis cmds | 1 INCR | INCR + GET | 1 EVAL (Lua) |
|
|
113
|
+
Burst | 2x at edges | Smoothed | Controlled |
|
|
114
|
+
Precision | Exact | Approximate | Exact |
|
|
115
|
+
Best for | Simplicity | Smooth limiting | APIs / billing |
|
|
116
|
+
+----------------+------------------+--------------------+
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Storage
|
|
120
|
+
|
|
121
|
+
### In-Memory (default)
|
|
122
|
+
|
|
123
|
+
No dependencies. Good for single-process deployments.
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { MemoryStore } from "nestjs-advanced-rate-limiter";
|
|
127
|
+
|
|
128
|
+
ThrottlerModule.forRoot({
|
|
129
|
+
store: new MemoryStore({ maxKeys: 100_000, cleanupIntervalMs: 60_000 }),
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Redis
|
|
134
|
+
|
|
135
|
+
For multi-instance deployments. Works with ioredis or any compatible client.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
import Redis from "ioredis";
|
|
139
|
+
import { RedisStore } from "nestjs-advanced-rate-limiter";
|
|
140
|
+
|
|
141
|
+
ThrottlerModule.forRoot({
|
|
142
|
+
store: new RedisStore(new Redis()),
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Resilient Store
|
|
147
|
+
|
|
148
|
+
Wraps any store with circuit breaker. If Redis goes down, your app keeps running.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
import { ResilientStore, RedisStore } from "nestjs-advanced-rate-limiter";
|
|
152
|
+
|
|
153
|
+
ThrottlerModule.forRoot({
|
|
154
|
+
store: new ResilientStore(new RedisStore(redis), {
|
|
155
|
+
failMode: "open", // allow traffic when store is down (default)
|
|
156
|
+
threshold: 5, // open circuit after 5 consecutive failures
|
|
157
|
+
cooldownMs: 30_000, // retry after 30s
|
|
158
|
+
onError: (err) => console.error(err),
|
|
159
|
+
}),
|
|
160
|
+
})
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Decorators
|
|
164
|
+
|
|
165
|
+
### `@Throttle()` -- Per-Route Override
|
|
166
|
+
|
|
167
|
+
Override the default algorithm for specific routes or controllers.
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { Controller, Post, Get } from "@nestjs/common";
|
|
171
|
+
import { Throttle } from "nestjs-advanced-rate-limiter";
|
|
172
|
+
|
|
173
|
+
@Controller("auth")
|
|
174
|
+
export class AuthController {
|
|
175
|
+
@Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
|
|
176
|
+
@Post("login")
|
|
177
|
+
login() {
|
|
178
|
+
// strict: 5 attempts per minute
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@Get("profile")
|
|
182
|
+
profile() {
|
|
183
|
+
// uses the global default
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### `@SkipThrottle()` -- Skip Rate Limiting
|
|
189
|
+
|
|
190
|
+
Disable rate limiting for specific routes or entire controllers.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import { Controller, Get } from "@nestjs/common";
|
|
194
|
+
import { SkipThrottle } from "nestjs-advanced-rate-limiter";
|
|
195
|
+
|
|
196
|
+
@SkipThrottle()
|
|
197
|
+
@Controller("health")
|
|
198
|
+
export class HealthController {
|
|
199
|
+
@Get()
|
|
200
|
+
check() {
|
|
201
|
+
return "ok";
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## User Tracking
|
|
207
|
+
|
|
208
|
+
Built-in JWT/session-based tracker. Identifies users from `Authorization: Bearer <token>` or session cookies. Falls back to IP.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { userTracker } from "nestjs-advanced-rate-limiter";
|
|
212
|
+
|
|
213
|
+
ThrottlerModule.forRoot({
|
|
214
|
+
getTracker: userTracker(),
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
// Custom cookie name
|
|
218
|
+
ThrottlerModule.forRoot({
|
|
219
|
+
getTracker: userTracker({ cookieName: "session" }),
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Custom JWT parser
|
|
223
|
+
ThrottlerModule.forRoot({
|
|
224
|
+
getTracker: userTracker({
|
|
225
|
+
parseJwt: (token) => {
|
|
226
|
+
const payload = JSON.parse(atob(token.split(".")[1]));
|
|
227
|
+
return payload.sub;
|
|
228
|
+
},
|
|
229
|
+
}),
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
// Disable IP fallback
|
|
233
|
+
ThrottlerModule.forRoot({
|
|
234
|
+
getTracker: userTracker({ fallbackToIp: false }),
|
|
235
|
+
})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### IP Resolution
|
|
239
|
+
|
|
240
|
+
The default tracker (and `userTracker` fallback) checks headers in this order:
|
|
241
|
+
|
|
242
|
+
| Priority | Header | Set by |
|
|
243
|
+
|---|---|---|
|
|
244
|
+
| 1 | `cf-connecting-ip` | Cloudflare |
|
|
245
|
+
| 2 | `x-real-ip` | nginx / load balancer |
|
|
246
|
+
| 3 | `x-forwarded-for` (first IP) | Any proxy |
|
|
247
|
+
| 4 | `req.ip` / `req.socket.remoteAddress` | Express / Fastify |
|
|
248
|
+
|
|
249
|
+
Works with both Express and Fastify.
|
|
250
|
+
|
|
251
|
+
## Async Configuration
|
|
252
|
+
|
|
253
|
+
Use `forRootAsync` when you need to inject dependencies (e.g., `ConfigService`).
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { ThrottlerModule } from "nestjs-advanced-rate-limiter";
|
|
257
|
+
import { ConfigModule, ConfigService } from "@nestjs/config";
|
|
258
|
+
|
|
259
|
+
@Module({
|
|
260
|
+
imports: [
|
|
261
|
+
ThrottlerModule.forRootAsync({
|
|
262
|
+
imports: [ConfigModule],
|
|
263
|
+
inject: [ConfigService],
|
|
264
|
+
useFactory: (config: ConfigService) => ({
|
|
265
|
+
algorithm: {
|
|
266
|
+
algorithm: "token-bucket",
|
|
267
|
+
capacity: config.get("RATE_LIMIT_CAPACITY", 100),
|
|
268
|
+
refillRate: config.get("RATE_LIMIT_REFILL", 10),
|
|
269
|
+
},
|
|
270
|
+
}),
|
|
271
|
+
}),
|
|
272
|
+
],
|
|
273
|
+
})
|
|
274
|
+
export class AppModule {}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## All Options
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
ThrottlerModule.forRoot({
|
|
281
|
+
// Algorithm (default: token-bucket, 100 capacity, 10/sec)
|
|
282
|
+
algorithm: { algorithm: "token-bucket", capacity: 100, refillRate: 10 },
|
|
283
|
+
|
|
284
|
+
// Storage backend (default: in-memory)
|
|
285
|
+
store: new MemoryStore(),
|
|
286
|
+
|
|
287
|
+
// Key prefix for storage (default: "rl:")
|
|
288
|
+
prefix: "rl:",
|
|
289
|
+
|
|
290
|
+
// Custom tracker function (default: IP-based)
|
|
291
|
+
getTracker: (req) => req.ip,
|
|
292
|
+
|
|
293
|
+
// Programmatic skip (default: none)
|
|
294
|
+
skipIf: (req) => req.url.includes("/health"),
|
|
295
|
+
|
|
296
|
+
// Ignore specific user agents (default: [])
|
|
297
|
+
ignoreUserAgents: [/googlebot/i, /bingbot/i],
|
|
298
|
+
|
|
299
|
+
// Custom error message or response factory (default: "Too Many Requests")
|
|
300
|
+
errorMessage: "Rate limit exceeded",
|
|
301
|
+
// or
|
|
302
|
+
errorMessage: (result) => ({
|
|
303
|
+
error: "rate_limited",
|
|
304
|
+
retryAfter: Math.ceil(result.retryAfterMs / 1000),
|
|
305
|
+
}),
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Response Headers
|
|
310
|
+
|
|
311
|
+
Every response includes rate limit headers:
|
|
312
|
+
|
|
313
|
+
| Header | When | Example |
|
|
314
|
+
|---|---|---|
|
|
315
|
+
| `X-RateLimit-Limit` | Always | `100` |
|
|
316
|
+
| `X-RateLimit-Remaining` | Always | `95` |
|
|
317
|
+
| `X-RateLimit-Reset` | Always | `1712678460` (Unix timestamp) |
|
|
318
|
+
| `Retry-After` | 429 only | `5` (seconds) |
|
|
319
|
+
|
|
320
|
+
## Migrating from `@nestjs/throttler`
|
|
321
|
+
|
|
322
|
+
```diff
|
|
323
|
+
- import { ThrottlerModule, ThrottlerGuard } from "@nestjs/throttler";
|
|
324
|
+
+ import { ThrottlerModule, ThrottlerGuard } from "nestjs-advanced-rate-limiter";
|
|
325
|
+
|
|
326
|
+
@Module({
|
|
327
|
+
imports: [
|
|
328
|
+
- ThrottlerModule.forRoot({ ttl: 60000, limit: 10 }),
|
|
329
|
+
+ ThrottlerModule.forRoot({
|
|
330
|
+
+ algorithm: { algorithm: "fixed-window", limit: 10, windowMs: 60_000 },
|
|
331
|
+
+ }),
|
|
332
|
+
],
|
|
333
|
+
providers: [{ provide: APP_GUARD, useExisting: ThrottlerGuard }],
|
|
334
|
+
})
|
|
335
|
+
export class AppModule {}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
The `@SkipThrottle()` decorator works the same way. Replace `@Throttle()` with the new config-based syntax:
|
|
339
|
+
|
|
340
|
+
```diff
|
|
341
|
+
- @Throttle({ default: { limit: 5, ttl: 60000 } })
|
|
342
|
+
+ @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## License
|
|
346
|
+
|
|
347
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/decorators/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skip rate limiting for a specific controller or route.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* @SkipThrottle()
|
|
7
|
+
* @Get("health")
|
|
8
|
+
* health() { return "ok"; }
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare const SkipThrottle: () => import("@nestjs/common").CustomDecorator<string>;
|
|
12
|
+
//# sourceMappingURL=skip-throttle.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skip-throttle.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/skip-throttle.decorator.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,wDAA0C,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SetMetadata } from "@nestjs/common";
|
|
2
|
+
import { THROTTLER_SKIP } from "../throttler.constants";
|
|
3
|
+
/**
|
|
4
|
+
* Skip rate limiting for a specific controller or route.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* @SkipThrottle()
|
|
9
|
+
* @Get("health")
|
|
10
|
+
* health() { return "ok"; }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const SkipThrottle = () => SetMetadata(THROTTLER_SKIP, true);
|
|
14
|
+
//# sourceMappingURL=skip-throttle.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skip-throttle.decorator.js","sourceRoot":"","sources":["../../src/decorators/skip-throttle.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,GAAG,EAAE,CAAC,WAAW,CAAC,cAAc,EAAE,IAAI,CAAC,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AlgorithmConfig } from "elysia-advanced-rate-limiter";
|
|
2
|
+
/**
|
|
3
|
+
* Override the default rate limit algorithm for a specific controller or route.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* ```ts
|
|
7
|
+
* @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
|
|
8
|
+
* @Post("login")
|
|
9
|
+
* login() { ... }
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export declare const Throttle: (config: AlgorithmConfig) => import("@nestjs/common").CustomDecorator<string>;
|
|
13
|
+
//# sourceMappingURL=throttle.decorator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle.decorator.d.ts","sourceRoot":"","sources":["../../src/decorators/throttle.decorator.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAGpE;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GAAI,QAAQ,eAAe,qDACN,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SetMetadata } from "@nestjs/common";
|
|
2
|
+
import { THROTTLER_ALGORITHM } from "../throttler.constants";
|
|
3
|
+
/**
|
|
4
|
+
* Override the default rate limit algorithm for a specific controller or route.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* @Throttle({ algorithm: "fixed-window", limit: 5, windowMs: 60_000 })
|
|
9
|
+
* @Post("login")
|
|
10
|
+
* login() { ... }
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export const Throttle = (config) => SetMetadata(THROTTLER_ALGORITHM, config);
|
|
14
|
+
//# sourceMappingURL=throttle.decorator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttle.decorator.js","sourceRoot":"","sources":["../../src/decorators/throttle.decorator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE7C,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAE7D;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,MAAuB,EAAE,EAAE,CAClD,WAAW,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { SkipThrottle, Throttle } from "./decorators";
|
|
2
|
+
export { ThrottlerGuard } from "./throttler.guard";
|
|
3
|
+
export { ThrottlerModule } from "./throttler.module";
|
|
4
|
+
export { userTracker } from "./trackers";
|
|
5
|
+
export type { UserTrackerOptions } from "./trackers";
|
|
6
|
+
export { THROTTLER_OPTIONS } from "./throttler.constants";
|
|
7
|
+
export type { ThrottlerAsyncOptions, ThrottlerModuleOptions, ThrottlerOptionsFactory, } from "./throttler.interfaces";
|
|
8
|
+
export type { AlgorithmConfig, RateLimitResult, RateLimitStore, TokenBucketConfig, SlidingWindowConfig, FixedWindowConfig, } from "elysia-advanced-rate-limiter";
|
|
9
|
+
export { MemoryStore, RedisStore, ResilientStore, } from "elysia-advanced-rate-limiter";
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGtD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAGrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAG1D,YAAY,EACV,qBAAqB,EACrB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,wBAAwB,CAAC;AAGhC,YAAY,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,GACf,MAAM,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Decorators
|
|
2
|
+
export { SkipThrottle, Throttle } from "./decorators";
|
|
3
|
+
// Guard
|
|
4
|
+
export { ThrottlerGuard } from "./throttler.guard";
|
|
5
|
+
// Module
|
|
6
|
+
export { ThrottlerModule } from "./throttler.module";
|
|
7
|
+
// Trackers
|
|
8
|
+
export { userTracker } from "./trackers";
|
|
9
|
+
// Constants
|
|
10
|
+
export { THROTTLER_OPTIONS } from "./throttler.constants";
|
|
11
|
+
export { MemoryStore, RedisStore, ResilientStore, } from "elysia-advanced-rate-limiter";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,aAAa;AACb,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEtD,QAAQ;AACR,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD,SAAS;AACT,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,WAAW;AACX,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGzC,YAAY;AACZ,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAmB1D,OAAO,EACL,WAAW,EACX,UAAU,EACV,cAAc,GACf,MAAM,8BAA8B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.constants.d.ts","sourceRoot":"","sources":["../src/throttler.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iBAAiB,sBAAsB,CAAC;AACrD,eAAO,MAAM,mBAAmB,wBAAwB,CAAC;AACzD,eAAO,MAAM,cAAc,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.constants.js","sourceRoot":"","sources":["../src/throttler.constants.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAC;AACrD,MAAM,CAAC,MAAM,mBAAmB,GAAG,qBAAqB,CAAC;AACzD,MAAM,CAAC,MAAM,cAAc,GAAG,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { CanActivate, ExecutionContext } from "@nestjs/common";
|
|
2
|
+
import { Reflector } from "@nestjs/core";
|
|
3
|
+
import type { ThrottlerModuleOptions } from "./throttler.interfaces";
|
|
4
|
+
export declare class ThrottlerGuard implements CanActivate {
|
|
5
|
+
private readonly options;
|
|
6
|
+
private readonly reflector;
|
|
7
|
+
private readonly store;
|
|
8
|
+
private readonly defaultAlgorithm;
|
|
9
|
+
private readonly prefix;
|
|
10
|
+
private readonly skipIf?;
|
|
11
|
+
private readonly getTracker;
|
|
12
|
+
private readonly ignoreUserAgents;
|
|
13
|
+
private readonly errorMessage;
|
|
14
|
+
constructor(options: ThrottlerModuleOptions, reflector: Reflector);
|
|
15
|
+
canActivate(context: ExecutionContext): Promise<boolean>;
|
|
16
|
+
private defaultGetTracker;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=throttler.guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.guard.d.ts","sourceRoot":"","sources":["../src/throttler.guard.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,gBAAgB,EAKjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAQzC,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAkBrE,qBACa,cAAe,YAAW,WAAW;IAYnB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACnD,OAAO,CAAC,QAAQ,CAAC,SAAS;IAZ5B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;IACvC,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAwB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuB;IAClD,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAW;IAC5C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAEsB;gBAGL,OAAO,EAAE,sBAAsB,EAC1D,SAAS,EAAE,SAAS;IAejC,WAAW,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAiE9D,OAAO,CAAC,iBAAiB;CAe1B"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
8
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
9
|
+
};
|
|
10
|
+
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
|
11
|
+
return function (target, key) { decorator(target, key, paramIndex); }
|
|
12
|
+
};
|
|
13
|
+
import { HttpException, HttpStatus, Inject, Injectable, } from "@nestjs/common";
|
|
14
|
+
import { Reflector } from "@nestjs/core";
|
|
15
|
+
import { MemoryStore } from "elysia-advanced-rate-limiter";
|
|
16
|
+
import { THROTTLER_ALGORITHM, THROTTLER_OPTIONS, THROTTLER_SKIP } from "./throttler.constants";
|
|
17
|
+
const MAX_RETRY_MS = 2_147_483_647;
|
|
18
|
+
function sanitize(val) {
|
|
19
|
+
return Number.isFinite(val) ? val : MAX_RETRY_MS;
|
|
20
|
+
}
|
|
21
|
+
function computeTtlMs(config) {
|
|
22
|
+
switch (config.algorithm) {
|
|
23
|
+
case "token-bucket":
|
|
24
|
+
return Math.ceil((config.capacity / config.refillRate) * 1000) + 60_000;
|
|
25
|
+
case "sliding-window":
|
|
26
|
+
case "fixed-window":
|
|
27
|
+
return config.windowMs + 60_000;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
let ThrottlerGuard = class ThrottlerGuard {
|
|
31
|
+
options;
|
|
32
|
+
reflector;
|
|
33
|
+
store;
|
|
34
|
+
defaultAlgorithm;
|
|
35
|
+
prefix;
|
|
36
|
+
skipIf;
|
|
37
|
+
getTracker;
|
|
38
|
+
ignoreUserAgents;
|
|
39
|
+
errorMessage;
|
|
40
|
+
constructor(options, reflector) {
|
|
41
|
+
this.options = options;
|
|
42
|
+
this.reflector = reflector;
|
|
43
|
+
this.defaultAlgorithm = options.algorithm ?? {
|
|
44
|
+
algorithm: "token-bucket",
|
|
45
|
+
capacity: 100,
|
|
46
|
+
refillRate: 10,
|
|
47
|
+
};
|
|
48
|
+
this.store = options.store ?? new MemoryStore({ maxKeys: 100_000 });
|
|
49
|
+
this.prefix = options.prefix ?? "rl:";
|
|
50
|
+
this.skipIf = options.skipIf;
|
|
51
|
+
this.getTracker = options.getTracker ?? this.defaultGetTracker;
|
|
52
|
+
this.ignoreUserAgents = options.ignoreUserAgents ?? [];
|
|
53
|
+
this.errorMessage = options.errorMessage ?? "Too Many Requests";
|
|
54
|
+
}
|
|
55
|
+
async canActivate(context) {
|
|
56
|
+
const skipClass = this.reflector.get(THROTTLER_SKIP, context.getClass());
|
|
57
|
+
if (skipClass)
|
|
58
|
+
return true;
|
|
59
|
+
const skipHandler = this.reflector.get(THROTTLER_SKIP, context.getHandler());
|
|
60
|
+
if (skipHandler)
|
|
61
|
+
return true;
|
|
62
|
+
const req = context.switchToHttp().getRequest();
|
|
63
|
+
const res = context.switchToHttp().getResponse();
|
|
64
|
+
if (this.skipIf?.(req))
|
|
65
|
+
return true;
|
|
66
|
+
const userAgent = req.headers?.["user-agent"] ?? "";
|
|
67
|
+
if (this.ignoreUserAgents.some((regex) => regex.test(userAgent))) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
const algorithmConfig = this.reflector.getAllAndOverride(THROTTLER_ALGORITHM, [
|
|
71
|
+
context.getHandler(),
|
|
72
|
+
context.getClass(),
|
|
73
|
+
]) ?? this.defaultAlgorithm;
|
|
74
|
+
const tracker = this.getTracker(req);
|
|
75
|
+
const storeKey = `${this.prefix}${tracker}`;
|
|
76
|
+
const ttlMs = computeTtlMs(algorithmConfig);
|
|
77
|
+
const nowMs = Date.now();
|
|
78
|
+
const result = await this.store.check(storeKey, algorithmConfig, nowMs, ttlMs);
|
|
79
|
+
const remaining = sanitize(result.remaining);
|
|
80
|
+
const resetMs = sanitize(result.resetMs);
|
|
81
|
+
const resetTimestamp = String(Math.ceil((nowMs + resetMs) / 1000));
|
|
82
|
+
res.header("X-RateLimit-Limit", String(result.limit));
|
|
83
|
+
res.header("X-RateLimit-Remaining", String(remaining));
|
|
84
|
+
res.header("X-RateLimit-Reset", resetTimestamp);
|
|
85
|
+
if (!result.allowed) {
|
|
86
|
+
const retryAfterMs = sanitize(result.retryAfterMs);
|
|
87
|
+
const retryAfter = String(Math.ceil(retryAfterMs / 1000));
|
|
88
|
+
res.header("Retry-After", retryAfter);
|
|
89
|
+
const body = typeof this.errorMessage === "function"
|
|
90
|
+
? this.errorMessage(result)
|
|
91
|
+
: { statusCode: 429, message: this.errorMessage };
|
|
92
|
+
throw new HttpException(body, HttpStatus.TOO_MANY_REQUESTS);
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
defaultGetTracker(req) {
|
|
97
|
+
const cfIp = req.headers?.["cf-connecting-ip"];
|
|
98
|
+
if (cfIp)
|
|
99
|
+
return cfIp;
|
|
100
|
+
const realIp = req.headers?.["x-real-ip"];
|
|
101
|
+
if (realIp)
|
|
102
|
+
return realIp;
|
|
103
|
+
const xff = req.headers?.["x-forwarded-for"];
|
|
104
|
+
if (xff) {
|
|
105
|
+
const first = typeof xff === "string" ? xff.split(",")[0]?.trim() : xff[0];
|
|
106
|
+
if (first)
|
|
107
|
+
return first;
|
|
108
|
+
}
|
|
109
|
+
return req.ip ?? req.socket?.remoteAddress ?? "anonymous";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
ThrottlerGuard = __decorate([
|
|
113
|
+
Injectable(),
|
|
114
|
+
__param(0, Inject(THROTTLER_OPTIONS)),
|
|
115
|
+
__metadata("design:paramtypes", [Object, Reflector])
|
|
116
|
+
], ThrottlerGuard);
|
|
117
|
+
export { ThrottlerGuard };
|
|
118
|
+
//# sourceMappingURL=throttler.guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.guard.js","sourceRoot":"","sources":["../src/throttler.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,OAAO,EAGL,aAAa,EACb,UAAU,EACV,MAAM,EACN,UAAU,GACX,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAM3D,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAG/F,MAAM,YAAY,GAAG,aAAa,CAAC;AAEnC,SAAS,QAAQ,CAAC,GAAW;IAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC;AACnD,CAAC;AAED,SAAS,YAAY,CAAC,MAAuB;IAC3C,QAAQ,MAAM,CAAC,SAAS,EAAE,CAAC;QACzB,KAAK,cAAc;YACjB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;QAC1E,KAAK,gBAAgB,CAAC;QACtB,KAAK,cAAc;YACjB,OAAO,MAAM,CAAC,QAAQ,GAAG,MAAM,CAAC;IACpC,CAAC;AACH,CAAC;AAGM,IAAM,cAAc,GAApB,MAAM,cAAc;IAYqB;IAC3B;IAZF,KAAK,CAAiB;IACtB,gBAAgB,CAAkB;IAClC,MAAM,CAAS;IACf,MAAM,CAAyB;IAC/B,UAAU,CAAuB;IACjC,gBAAgB,CAAW;IAC3B,YAAY,CAEsB;IAEnD,YAC8C,OAA+B,EAC1D,SAAoB;QADO,YAAO,GAAP,OAAO,CAAwB;QAC1D,cAAS,GAAT,SAAS,CAAW;QAErC,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,SAAS,IAAI;YAC3C,SAAS,EAAE,cAAc;YACzB,QAAQ,EAAE,GAAG;YACb,UAAU,EAAE,EAAE;SACf,CAAC;QACF,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,WAAW,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;QACpE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;QACtC,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC7B,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,iBAAiB,CAAC;QAC/D,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,IAAI,EAAE,CAAC;QACvD,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,mBAAmB,CAAC;IAClE,CAAC;IAED,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAClC,cAAc,EACd,OAAO,CAAC,QAAQ,EAAE,CACnB,CAAC;QACF,IAAI,SAAS;YAAE,OAAO,IAAI,CAAC;QAE3B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CACpC,cAAc,EACd,OAAO,CAAC,UAAU,EAAE,CACrB,CAAC;QACF,IAAI,WAAW;YAAE,OAAO,IAAI,CAAC;QAE7B,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAE,CAAC;QAChD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,WAAW,EAAE,CAAC;QAEjD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC;QAEpC,MAAM,SAAS,GAAW,GAAG,CAAC,OAAO,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC;QAC5D,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC;YACjE,OAAO,IAAI,CAAC;QACd,CAAC;QAED,MAAM,eAAe,GACnB,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAkB,mBAAmB,EAAE;YACrE,OAAO,CAAC,UAAU,EAAE;YACpB,OAAO,CAAC,QAAQ,EAAE;SACnB,CAAC,IAAI,IAAI,CAAC,gBAAgB,CAAC;QAE9B,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACrC,MAAM,QAAQ,GAAG,GAAG,IAAI,CAAC,MAAM,GAAG,OAAO,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,YAAY,CAAC,eAAe,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QAEzB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,KAAK,CACnC,QAAQ,EACR,eAAe,EACf,KAAK,EACL,KAAK,CACN,CAAC;QAEF,MAAM,SAAS,GAAG,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzC,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QAEnE,GAAG,CAAC,MAAM,CAAC,mBAAmB,EAAE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QACtD,GAAG,CAAC,MAAM,CAAC,uBAAuB,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,GAAG,CAAC,MAAM,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC;QAEhD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YACnD,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC;YAC1D,GAAG,CAAC,MAAM,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC;YAEtC,MAAM,IAAI,GACR,OAAO,IAAI,CAAC,YAAY,KAAK,UAAU;gBACrC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC;gBAC3B,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC;YAEtD,MAAM,IAAI,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,iBAAiB,CAAC,CAAC;QAC9D,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,iBAAiB,CAAC,GAAQ;QAChC,MAAM,IAAI,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,kBAAkB,CAAC,CAAC;QAC/C,IAAI,IAAI;YAAE,OAAO,IAAI,CAAC;QAEtB,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;QAC1C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAE1B,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC,iBAAiB,CAAC,CAAC;QAC7C,IAAI,GAAG,EAAE,CAAC;YACR,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC3E,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC;QAC1B,CAAC;QAED,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,EAAE,aAAa,IAAI,WAAW,CAAC;IAC5D,CAAC;CACF,CAAA;AA5GY,cAAc;IAD1B,UAAU,EAAE;IAaR,WAAA,MAAM,CAAC,iBAAiB,CAAC,CAAA;6CACE,SAAS;GAb5B,cAAc,CA4G1B"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ModuleMetadata, Type } from "@nestjs/common";
|
|
2
|
+
import type { AlgorithmConfig, RateLimitResult, RateLimitStore } from "elysia-advanced-rate-limiter";
|
|
3
|
+
export interface ThrottlerModuleOptions {
|
|
4
|
+
algorithm?: AlgorithmConfig;
|
|
5
|
+
store?: RateLimitStore;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
errorMessage?: string | ((result: RateLimitResult) => string | object);
|
|
8
|
+
skipIf?: (request: any) => boolean;
|
|
9
|
+
getTracker?: (request: any) => string;
|
|
10
|
+
ignoreUserAgents?: RegExp[];
|
|
11
|
+
}
|
|
12
|
+
export interface ThrottlerOptionsFactory {
|
|
13
|
+
createThrottlerOptions(): ThrottlerModuleOptions | Promise<ThrottlerModuleOptions>;
|
|
14
|
+
}
|
|
15
|
+
export interface ThrottlerAsyncOptions extends Pick<ModuleMetadata, "imports"> {
|
|
16
|
+
useExisting?: Type<ThrottlerOptionsFactory>;
|
|
17
|
+
useClass?: Type<ThrottlerOptionsFactory>;
|
|
18
|
+
useFactory?: (...args: any[]) => ThrottlerModuleOptions | Promise<ThrottlerModuleOptions>;
|
|
19
|
+
inject?: any[];
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=throttler.interfaces.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.interfaces.d.ts","sourceRoot":"","sources":["../src/throttler.interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,cAAc,EACf,MAAM,8BAA8B,CAAC;AAEtC,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,eAAe,CAAC;IAC5B,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,MAAM,EAAE,eAAe,KAAK,MAAM,GAAG,MAAM,CAAC,CAAC;IACvE,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,MAAM,CAAC;IACtC,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,uBAAuB;IACtC,sBAAsB,IAClB,sBAAsB,GACtB,OAAO,CAAC,sBAAsB,CAAC,CAAC;CACrC;AAED,MAAM,WAAW,qBAAsB,SAAQ,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC;IAC5E,WAAW,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAC5C,QAAQ,CAAC,EAAE,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE,CACX,GAAG,IAAI,EAAE,GAAG,EAAE,KACX,sBAAsB,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC9D,MAAM,CAAC,EAAE,GAAG,EAAE,CAAC;CAChB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.interfaces.js","sourceRoot":"","sources":["../src/throttler.interfaces.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DynamicModule } from "@nestjs/common";
|
|
2
|
+
import type { ThrottlerAsyncOptions, ThrottlerModuleOptions } from "./throttler.interfaces";
|
|
3
|
+
export declare class ThrottlerModule {
|
|
4
|
+
static forRoot(options?: ThrottlerModuleOptions): DynamicModule;
|
|
5
|
+
static forRootAsync(options: ThrottlerAsyncOptions): DynamicModule;
|
|
6
|
+
private static createAsyncProviders;
|
|
7
|
+
private static createAsyncOptionsProvider;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=throttler.module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.module.d.ts","sourceRoot":"","sources":["../src/throttler.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAoB,MAAM,gBAAgB,CAAC;AAGjE,OAAO,KAAK,EACV,qBAAqB,EACrB,sBAAsB,EAEvB,MAAM,wBAAwB,CAAC;AAEhC,qBACa,eAAe;IAC1B,MAAM,CAAC,OAAO,CAAC,OAAO,GAAE,sBAA2B,GAAG,aAAa;IAYnE,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,qBAAqB,GAAG,aAAa;IAWlE,OAAO,CAAC,MAAM,CAAC,oBAAoB;IAiBnC,OAAO,CAAC,MAAM,CAAC,0BAA0B;CAmB1C"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
2
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
4
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
|
+
};
|
|
7
|
+
var ThrottlerModule_1;
|
|
8
|
+
import { Module } from "@nestjs/common";
|
|
9
|
+
import { THROTTLER_OPTIONS } from "./throttler.constants";
|
|
10
|
+
import { ThrottlerGuard } from "./throttler.guard";
|
|
11
|
+
let ThrottlerModule = ThrottlerModule_1 = class ThrottlerModule {
|
|
12
|
+
static forRoot(options = {}) {
|
|
13
|
+
return {
|
|
14
|
+
module: ThrottlerModule_1,
|
|
15
|
+
global: true,
|
|
16
|
+
providers: [
|
|
17
|
+
{ provide: THROTTLER_OPTIONS, useValue: options },
|
|
18
|
+
ThrottlerGuard,
|
|
19
|
+
],
|
|
20
|
+
exports: [THROTTLER_OPTIONS, ThrottlerGuard],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
static forRootAsync(options) {
|
|
24
|
+
const providers = this.createAsyncProviders(options);
|
|
25
|
+
return {
|
|
26
|
+
module: ThrottlerModule_1,
|
|
27
|
+
global: true,
|
|
28
|
+
imports: options.imports ?? [],
|
|
29
|
+
providers: [...providers, ThrottlerGuard],
|
|
30
|
+
exports: [THROTTLER_OPTIONS, ThrottlerGuard],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
static createAsyncProviders(options) {
|
|
34
|
+
if (options.useExisting || options.useFactory) {
|
|
35
|
+
return [this.createAsyncOptionsProvider(options)];
|
|
36
|
+
}
|
|
37
|
+
if (options.useClass) {
|
|
38
|
+
return [
|
|
39
|
+
this.createAsyncOptionsProvider(options),
|
|
40
|
+
{ provide: options.useClass, useClass: options.useClass },
|
|
41
|
+
];
|
|
42
|
+
}
|
|
43
|
+
return [{ provide: THROTTLER_OPTIONS, useValue: {} }];
|
|
44
|
+
}
|
|
45
|
+
static createAsyncOptionsProvider(options) {
|
|
46
|
+
if (options.useFactory) {
|
|
47
|
+
return {
|
|
48
|
+
provide: THROTTLER_OPTIONS,
|
|
49
|
+
useFactory: options.useFactory,
|
|
50
|
+
inject: options.inject ?? [],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const inject = options.useExisting ?? options.useClass;
|
|
54
|
+
return {
|
|
55
|
+
provide: THROTTLER_OPTIONS,
|
|
56
|
+
useFactory: (factory) => factory.createThrottlerOptions(),
|
|
57
|
+
inject: inject ? [inject] : [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
ThrottlerModule = ThrottlerModule_1 = __decorate([
|
|
62
|
+
Module({})
|
|
63
|
+
], ThrottlerModule);
|
|
64
|
+
export { ThrottlerModule };
|
|
65
|
+
//# sourceMappingURL=throttler.module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"throttler.module.js","sourceRoot":"","sources":["../src/throttler.module.ts"],"names":[],"mappings":";;;;;;;AAAA,OAAO,EAAiB,MAAM,EAAY,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAQ5C,IAAM,eAAe,uBAArB,MAAM,eAAe;IAC1B,MAAM,CAAC,OAAO,CAAC,UAAkC,EAAE;QACjD,OAAO;YACL,MAAM,EAAE,iBAAe;YACvB,MAAM,EAAE,IAAI;YACZ,SAAS,EAAE;gBACT,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,OAAO,EAAE;gBACjD,cAAc;aACf;YACD,OAAO,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SAC7C,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAA8B;QAChD,MAAM,SAAS,GAAG,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACrD,OAAO;YACL,MAAM,EAAE,iBAAe;YACvB,MAAM,EAAE,IAAI;YACZ,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,SAAS,EAAE,CAAC,GAAG,SAAS,EAAE,cAAc,CAAC;YACzC,OAAO,EAAE,CAAC,iBAAiB,EAAE,cAAc,CAAC;SAC7C,CAAC;IACJ,CAAC;IAEO,MAAM,CAAC,oBAAoB,CACjC,OAA8B;QAE9B,IAAI,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO;gBACL,IAAI,CAAC,0BAA0B,CAAC,OAAO,CAAC;gBACxC,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE;aAC1D,CAAC;QACJ,CAAC;QAED,OAAO,CAAC,EAAE,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAC;IACxD,CAAC;IAEO,MAAM,CAAC,0BAA0B,CACvC,OAA8B;QAE9B,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;YACvB,OAAO;gBACL,OAAO,EAAE,iBAAiB;gBAC1B,UAAU,EAAE,OAAO,CAAC,UAAU;gBAC9B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,EAAE;aAC7B,CAAC;QACJ,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,QAAQ,CAAC;QACvD,OAAO;YACL,OAAO,EAAE,iBAAiB;YAC1B,UAAU,EAAE,CAAC,OAAgC,EAAE,EAAE,CAC/C,OAAO,CAAC,sBAAsB,EAAE;YAClC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE;SAC/B,CAAC;IACJ,CAAC;CACF,CAAA;AA5DY,eAAe;IAD3B,MAAM,CAAC,EAAE,CAAC;GACE,eAAe,CA4D3B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface UserTrackerOptions {
|
|
2
|
+
/**
|
|
3
|
+
* Cookie name to look for a JWT session token.
|
|
4
|
+
* @default "better-auth.session_token"
|
|
5
|
+
*/
|
|
6
|
+
cookieName?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Custom JWT parser. Receives the raw token, returns a user ID or null.
|
|
9
|
+
* Default: decodes JWT payload and extracts sub/userId/id.
|
|
10
|
+
*/
|
|
11
|
+
parseJwt?: (token: string) => string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Fall back to IP-based tracking when no user identity is found.
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
fallbackToIp?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* NestJS-compatible tracker that identifies users from JWT/session,
|
|
20
|
+
* with optional IP fallback.
|
|
21
|
+
*
|
|
22
|
+
* Works with Express and Fastify request objects.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* ThrottlerModule.forRoot({
|
|
27
|
+
* getTracker: userTracker({ cookieName: "session", fallbackToIp: true }),
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function userTracker(options?: UserTrackerOptions): (req: any) => string;
|
|
32
|
+
//# sourceMappingURL=trackers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackers.d.ts","sourceRoot":"","sources":["../src/trackers.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IAE5C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AA6BD;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CA6ClF"}
|
package/dist/trackers.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
function defaultParseJwt(token) {
|
|
2
|
+
try {
|
|
3
|
+
const parts = token.split(".");
|
|
4
|
+
if (parts.length !== 3)
|
|
5
|
+
return null;
|
|
6
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
7
|
+
return (payload.sub ?? payload.userId ?? payload.id);
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function extractIp(req) {
|
|
14
|
+
const cfIp = req.headers?.["cf-connecting-ip"];
|
|
15
|
+
if (cfIp)
|
|
16
|
+
return cfIp;
|
|
17
|
+
const realIp = req.headers?.["x-real-ip"];
|
|
18
|
+
if (realIp)
|
|
19
|
+
return realIp;
|
|
20
|
+
const xff = req.headers?.["x-forwarded-for"];
|
|
21
|
+
if (xff) {
|
|
22
|
+
const first = typeof xff === "string" ? xff.split(",")[0]?.trim() : xff[0];
|
|
23
|
+
if (first)
|
|
24
|
+
return first;
|
|
25
|
+
}
|
|
26
|
+
return req.ip ?? req.socket?.remoteAddress ?? "anonymous";
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* NestJS-compatible tracker that identifies users from JWT/session,
|
|
30
|
+
* with optional IP fallback.
|
|
31
|
+
*
|
|
32
|
+
* Works with Express and Fastify request objects.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* ThrottlerModule.forRoot({
|
|
37
|
+
* getTracker: userTracker({ cookieName: "session", fallbackToIp: true }),
|
|
38
|
+
* })
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function userTracker(options = {}) {
|
|
42
|
+
const cookieName = options.cookieName ?? "better-auth.session_token";
|
|
43
|
+
const parseJwt = options.parseJwt ?? defaultParseJwt;
|
|
44
|
+
const fallbackToIp = options.fallbackToIp ?? true;
|
|
45
|
+
return (req) => {
|
|
46
|
+
// Try Authorization header
|
|
47
|
+
const auth = req.headers?.["authorization"];
|
|
48
|
+
if (auth?.startsWith("Bearer ")) {
|
|
49
|
+
const userId = parseJwt(auth.slice(7));
|
|
50
|
+
if (userId)
|
|
51
|
+
return `user:${userId}`;
|
|
52
|
+
}
|
|
53
|
+
// Try cookie
|
|
54
|
+
const cookieHeader = typeof req.cookies === "object"
|
|
55
|
+
? req.cookies?.[cookieName]
|
|
56
|
+
: req.headers?.["cookie"];
|
|
57
|
+
if (cookieHeader) {
|
|
58
|
+
let token;
|
|
59
|
+
if (typeof req.cookies === "object") {
|
|
60
|
+
// Express with cookie-parser / Fastify cookie
|
|
61
|
+
token = req.cookies[cookieName];
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
// Raw cookie header
|
|
65
|
+
const match = cookieHeader
|
|
66
|
+
.split(";")
|
|
67
|
+
.map((c) => c.trim())
|
|
68
|
+
.find((c) => c.startsWith(`${cookieName}=`));
|
|
69
|
+
if (match) {
|
|
70
|
+
token = match.split("=").slice(1).join("=");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (token) {
|
|
74
|
+
const userId = parseJwt(token);
|
|
75
|
+
if (userId)
|
|
76
|
+
return `user:${userId}`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (fallbackToIp)
|
|
80
|
+
return extractIp(req);
|
|
81
|
+
return "anonymous";
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=trackers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trackers.js","sourceRoot":"","sources":["../src/trackers.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nestjs-advanced-rate-limiter",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Advanced rate limiter for NestJS. Token bucket (GCRA), sliding window, fixed window. In-memory or Redis. Drop-in replacement for @nestjs/throttler.",
|
|
6
|
+
"author": "mrtcmn",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/mrtcmn/nestjs-advanced-rate-limitter.git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/mrtcmn/nestjs-advanced-rate-limitter/issues"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/mrtcmn/nestjs-advanced-rate-limitter#readme",
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nestjs",
|
|
18
|
+
"rate-limiter",
|
|
19
|
+
"rate-limiting",
|
|
20
|
+
"throttler",
|
|
21
|
+
"token-bucket",
|
|
22
|
+
"gcra",
|
|
23
|
+
"sliding-window",
|
|
24
|
+
"fixed-window",
|
|
25
|
+
"redis",
|
|
26
|
+
"guard",
|
|
27
|
+
"decorator",
|
|
28
|
+
"middleware"
|
|
29
|
+
],
|
|
30
|
+
"main": "dist/index.js",
|
|
31
|
+
"types": "dist/index.d.ts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"types": "./dist/index.d.ts",
|
|
35
|
+
"import": "./dist/index.js"
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"README.md",
|
|
41
|
+
"LICENSE"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "rm -rf dist && tsc --project tsconfig.build.json",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"test": "NODE_OPTIONS='--experimental-vm-modules' jest",
|
|
47
|
+
"test:watch": "jest --watch",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"dependencies": {
|
|
51
|
+
"elysia-advanced-rate-limiter": "^0.4.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"@nestjs/common": ">=10.0.0",
|
|
55
|
+
"@nestjs/core": ">=10.0.0",
|
|
56
|
+
"reflect-metadata": ">=0.1.0"
|
|
57
|
+
},
|
|
58
|
+
"peerDependenciesMeta": {
|
|
59
|
+
"reflect-metadata": {
|
|
60
|
+
"optional": false
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@nestjs/common": "^10.0.0",
|
|
65
|
+
"@nestjs/core": "^10.0.0",
|
|
66
|
+
"@nestjs/platform-express": "^11.1.18",
|
|
67
|
+
"@nestjs/testing": "^11.1.18",
|
|
68
|
+
"@types/express": "^5.0.6",
|
|
69
|
+
"@types/jest": "^30.0.0",
|
|
70
|
+
"@types/supertest": "^7.2.0",
|
|
71
|
+
"jest": "^30.3.0",
|
|
72
|
+
"reflect-metadata": "^0.2.2",
|
|
73
|
+
"rxjs": "^7.8.1",
|
|
74
|
+
"supertest": "^7.2.2",
|
|
75
|
+
"ts-jest": "^29.4.9",
|
|
76
|
+
"typescript": "^5.8.3"
|
|
77
|
+
}
|
|
78
|
+
}
|