nest-ratelimit-pro 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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +359 -0
  3. package/dist/constants.d.ts +13 -0
  4. package/dist/constants.js +17 -0
  5. package/dist/constants.js.map +1 -0
  6. package/dist/decorators/rate-limit.decorator.d.ts +3 -0
  7. package/dist/decorators/rate-limit.decorator.js +14 -0
  8. package/dist/decorators/rate-limit.decorator.js.map +1 -0
  9. package/dist/exceptions/rate-limit.exception.d.ts +5 -0
  10. package/dist/exceptions/rate-limit.exception.js +17 -0
  11. package/dist/exceptions/rate-limit.exception.js.map +1 -0
  12. package/dist/guards/rate-limit.guard.d.ts +12 -0
  13. package/dist/guards/rate-limit.guard.js +75 -0
  14. package/dist/guards/rate-limit.guard.js.map +1 -0
  15. package/dist/index.d.ts +12 -0
  16. package/dist/index.js +29 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/interfaces/rate-limit-options.interface.d.ts +45 -0
  19. package/dist/interfaces/rate-limit-options.interface.js +19 -0
  20. package/dist/interfaces/rate-limit-options.interface.js.map +1 -0
  21. package/dist/interfaces/rate-limit.interface.d.ts +20 -0
  22. package/dist/interfaces/rate-limit.interface.js +3 -0
  23. package/dist/interfaces/rate-limit.interface.js.map +1 -0
  24. package/dist/interfaces/storage.interface.d.ts +12 -0
  25. package/dist/interfaces/storage.interface.js +3 -0
  26. package/dist/interfaces/storage.interface.js.map +1 -0
  27. package/dist/rate-limit.module.d.ts +8 -0
  28. package/dist/rate-limit.module.js +76 -0
  29. package/dist/rate-limit.module.js.map +1 -0
  30. package/dist/services/rate-limit.service.d.ts +18 -0
  31. package/dist/services/rate-limit.service.js +164 -0
  32. package/dist/services/rate-limit.service.js.map +1 -0
  33. package/dist/storage/memory.storage.d.ts +19 -0
  34. package/dist/storage/memory.storage.js +124 -0
  35. package/dist/storage/memory.storage.js.map +1 -0
  36. package/dist/utils/key.utils.d.ts +5 -0
  37. package/dist/utils/key.utils.js +47 -0
  38. package/dist/utils/key.utils.js.map +1 -0
  39. package/dist/utils/time-utils.d.ts +4 -0
  40. package/dist/utils/time-utils.js +30 -0
  41. package/dist/utils/time-utils.js.map +1 -0
  42. package/package.json +91 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alireza Aminzadeh
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,359 @@
1
+ # nest-ratelimit-pro
2
+
3
+ [![npm version](https://badge.fury.io/js/nest-ratelimit-pro.svg)](https://www.npmjs.com/package/nest-ratelimit-pro)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Test Coverage](https://img.shields.io/badge/coverage-65%25-brightgreen.svg)](https://github.com/syeedalireza/nest-ratelimit-pro)
6
+
7
+ Advanced rate limiting and throttling middleware for NestJS with support for multiple algorithms, distributed systems, and flexible storage backends.
8
+
9
+ ## 🚀 Features
10
+
11
+ - **Multiple Rate Limiting Algorithms**
12
+ - Fixed Window Counter
13
+ - Sliding Window Log
14
+ - Token Bucket
15
+ - Sliding Window Counter
16
+
17
+ - **Flexible Key Strategies**
18
+ - IP-based rate limiting
19
+ - User ID-based (requires authentication)
20
+ - API Key-based
21
+ - Custom key extraction
22
+
23
+ - **Storage Backends**
24
+ - In-memory storage (single instance)
25
+ - Redis support (distributed)
26
+ - Custom storage adapters
27
+
28
+ - **Advanced Features**
29
+ - Cost-based rate limiting
30
+ - Per-route configuration
31
+ - Skip conditions
32
+ - Automatic cleanup
33
+ - Response headers
34
+ - Custom error messages
35
+
36
+ - **Production-Ready**
37
+ - TypeScript with strict typing
38
+ - Comprehensive test coverage
39
+ - Docker support
40
+ - Monitoring & metrics ready
41
+
42
+ ## 📦 Installation
43
+
44
+ ```bash
45
+ npm install nest-ratelimit-pro
46
+ ```
47
+
48
+ **Optional dependencies:**
49
+
50
+ ```bash
51
+ # For Redis storage
52
+ npm install ioredis
53
+ ```
54
+
55
+ ## 🔧 Quick Start
56
+
57
+ ### 1. Basic Setup
58
+
59
+ ```typescript
60
+ import { Module } from '@nestjs/common';
61
+ import { RateLimitModule } from 'nest-ratelimit-pro';
62
+
63
+ @Module({
64
+ imports: [
65
+ RateLimitModule.forRoot({
66
+ limit: 100, // 100 requests
67
+ duration: '1m', // per 1 minute
68
+ keyStrategy: 'ip', // Rate limit by IP address
69
+ }),
70
+ ],
71
+ })
72
+ export class AppModule {}
73
+ ```
74
+
75
+ ### 2. Apply Guard to Controllers
76
+
77
+ ```typescript
78
+ import { Controller, Get, UseGuards } from '@nestjs/common';
79
+ import { RateLimitGuard } from 'nest-ratelimit-pro';
80
+
81
+ @Controller('api')
82
+ @UseGuards(RateLimitGuard)
83
+ export class ApiController {
84
+ @Get('data')
85
+ async getData() {
86
+ return { message: 'Success!' };
87
+ }
88
+ }
89
+ ```
90
+
91
+ ### 3. Per-Route Configuration
92
+
93
+ ```typescript
94
+ import { RateLimit } from 'nest-ratelimit-pro';
95
+
96
+ @Controller('api')
97
+ export class ApiController {
98
+ // Override global limit for this route
99
+ @RateLimit({ limit: 5, duration: '1m' })
100
+ @Get('expensive')
101
+ async expensiveOperation() {
102
+ return { data: 'Heavy computation' };
103
+ }
104
+
105
+ // Skip rate limiting for this route
106
+ @SkipRateLimit()
107
+ @Get('health')
108
+ async healthCheck() {
109
+ return { status: 'ok' };
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## 📖 Configuration Options
115
+
116
+ ### Module Options
117
+
118
+ | Option | Type | Default | Description |
119
+ |--------|------|---------|-------------|
120
+ | `limit` | `number` | **Required** | Maximum requests allowed |
121
+ | `duration` | `string` | **Required** | Time window (e.g., '1m', '1h') |
122
+ | `algorithm` | `RateLimitAlgorithm` | `FIXED_WINDOW` | Algorithm to use |
123
+ | `keyStrategy` | `RateLimitKeyStrategy` | `IP` | How to identify requesters |
124
+ | `keyExtractor` | `Function` | - | Custom key extraction |
125
+ | `storage` | `Type<RateLimitStorage>` | `MemoryStorage` | Storage backend |
126
+ | `keyPrefix` | `string` | `'ratelimit:'` | Prefix for storage keys |
127
+ | `apiKeyHeader` | `string` | `'X-API-Key'` | API key header name |
128
+ | `enableCostBased` | `boolean` | `false` | Enable cost-based limiting |
129
+ | `blockDuration` | `number` | `0` | Block duration in seconds |
130
+ | `skipPaths` | `string[]` | `[]` | Paths to skip |
131
+ | `enabled` | `boolean` | `true` | Enable/disable globally |
132
+ | `errorMessage` | `string` | - | Custom error message |
133
+ | `includeHeaders` | `boolean` | `true` | Include rate limit headers |
134
+
135
+ ## 🎯 Rate Limiting Algorithms
136
+
137
+ ### Fixed Window
138
+
139
+ Simple counter that resets at fixed intervals:
140
+
141
+ ```typescript
142
+ RateLimitModule.forRoot({
143
+ algorithm: RateLimitAlgorithm.FIXED_WINDOW,
144
+ limit: 100,
145
+ duration: '1m',
146
+ });
147
+ ```
148
+
149
+ **Pros:** Simple, low memory
150
+ **Cons:** Burst at window edges
151
+
152
+ ### Sliding Window
153
+
154
+ More accurate, prevents burst:
155
+
156
+ ```typescript
157
+ RateLimitModule.forRoot({
158
+ algorithm: RateLimitAlgorithm.SLIDING_WINDOW,
159
+ limit: 100,
160
+ duration: '1m',
161
+ });
162
+ ```
163
+
164
+ **Pros:** Smooth rate limiting
165
+ **Cons:** Higher memory usage
166
+
167
+ ### Token Bucket
168
+
169
+ Allows bursts with token refill:
170
+
171
+ ```typescript
172
+ RateLimitModule.forRoot({
173
+ algorithm: RateLimitAlgorithm.TOKEN_BUCKET,
174
+ limit: 100,
175
+ duration: '1m',
176
+ });
177
+ ```
178
+
179
+ **Pros:** Flexible, allows controlled bursts
180
+ **Cons:** More complex
181
+
182
+ ## 🔑 Key Strategies
183
+
184
+ ### IP-Based
185
+
186
+ ```typescript
187
+ RateLimitModule.forRoot({
188
+ keyStrategy: RateLimitKeyStrategy.IP,
189
+ limit: 100,
190
+ duration: '1m',
191
+ });
192
+ ```
193
+
194
+ ### User ID-Based
195
+
196
+ ```typescript
197
+ RateLimitModule.forRoot({
198
+ keyStrategy: RateLimitKeyStrategy.USER_ID,
199
+ limit: 1000,
200
+ duration: '1h',
201
+ });
202
+ ```
203
+
204
+ Requires authentication middleware that sets `request.user.id`.
205
+
206
+ ### API Key-Based
207
+
208
+ ```typescript
209
+ RateLimitModule.forRoot({
210
+ keyStrategy: RateLimitKeyStrategy.API_KEY,
211
+ apiKeyHeader: 'X-API-Key',
212
+ limit: 10000,
213
+ duration: '1d',
214
+ });
215
+ ```
216
+
217
+ ### Custom Key Extractor
218
+
219
+ ```typescript
220
+ RateLimitModule.forRoot({
221
+ keyStrategy: RateLimitKeyStrategy.CUSTOM,
222
+ keyExtractor: (request) => {
223
+ return `${request.user?.id}-${request.headers['x-tenant-id']}`;
224
+ },
225
+ limit: 100,
226
+ duration: '1m',
227
+ });
228
+ ```
229
+
230
+ ## 💰 Cost-Based Rate Limiting
231
+
232
+ Assign different costs to different operations:
233
+
234
+ ```typescript
235
+ @Controller('api')
236
+ export class ApiController {
237
+ @RateLimit({ cost: 1 })
238
+ @Get('light')
239
+ async lightOperation() {
240
+ return { data: 'Quick response' };
241
+ }
242
+
243
+ @RateLimit({ cost: 10 })
244
+ @Post('heavy')
245
+ async heavyOperation() {
246
+ return { data: 'Expensive computation' };
247
+ }
248
+ }
249
+ ```
250
+
251
+ With a limit of 100, you can make:
252
+ - 100 light operations, or
253
+ - 10 heavy operations, or
254
+ - Any combination totaling 100 cost
255
+
256
+ ## 🗄️ Storage Backends
257
+
258
+ ### Memory Storage (Default)
259
+
260
+ ```typescript
261
+ import { MemoryRateLimitStorage } from 'nest-ratelimit-pro';
262
+
263
+ RateLimitModule.forRoot({
264
+ storage: MemoryRateLimitStorage,
265
+ limit: 100,
266
+ duration: '1m',
267
+ });
268
+ ```
269
+
270
+ **Use for:** Single-instance applications
271
+
272
+ ### Redis Storage
273
+
274
+ ```typescript
275
+ import { RedisRateLimitStorage } from 'nest-ratelimit-pro';
276
+
277
+ RateLimitModule.forRoot({
278
+ storage: RedisRateLimitStorage,
279
+ redisUrl: 'redis://localhost:6379',
280
+ limit: 100,
281
+ duration: '1m',
282
+ });
283
+ ```
284
+
285
+ **Use for:** Distributed applications, multiple instances
286
+
287
+ ## 📊 Response Headers
288
+
289
+ When rate limiting is active, these headers are included:
290
+
291
+ ```
292
+ X-RateLimit-Limit: 100
293
+ X-RateLimit-Remaining: 45
294
+ X-RateLimit-Reset: 1234567890
295
+ Retry-After: 30 (when limit exceeded)
296
+ ```
297
+
298
+ ## 🛡️ Skip Conditions
299
+
300
+ ### Skip Specific Paths
301
+
302
+ ```typescript
303
+ RateLimitModule.forRoot({
304
+ skipPaths: ['/health', '/metrics', '/public/*'],
305
+ limit: 100,
306
+ duration: '1m',
307
+ });
308
+ ```
309
+
310
+ ### Skip with Custom Logic
311
+
312
+ ```typescript
313
+ RateLimitModule.forRoot({
314
+ skipIf: (request) => {
315
+ return request.headers['x-internal-request'] === 'true';
316
+ },
317
+ limit: 100,
318
+ duration: '1m',
319
+ });
320
+ ```
321
+
322
+ ## 🧪 Testing
323
+
324
+ ```bash
325
+ npm test # Run tests
326
+ npm run test:cov # With coverage
327
+ npm run test:watch # Watch mode
328
+ ```
329
+
330
+ ## 📝 Example Application
331
+
332
+ See the [examples](./examples) directory for complete implementations:
333
+ - Basic rate limiting
334
+ - Cost-based limiting
335
+ - Redis integration
336
+ - Custom storage adapter
337
+
338
+ ## 🤝 Contributing
339
+
340
+ Contributions are welcome! Please read [CONTRIBUTING.md](./CONTRIBUTING.md).
341
+
342
+ ## 📄 License
343
+
344
+ MIT License - see [LICENSE](./LICENSE) file for details.
345
+
346
+ ## 👤 Author
347
+
348
+ **Alireza Aminzadeh**
349
+ - Email: alireza.aminzadeh@hotmail.com
350
+ - GitHub: [@syeedalireza](https://github.com/syeedalireza)
351
+ - NPM: [@syeedalireza](https://www.npmjs.com/~syeedalireza)
352
+
353
+ ## ⭐ Support
354
+
355
+ If this package helped you, please give it a ⭐ on [GitHub](https://github.com/syeedalireza/nest-ratelimit-pro)!
356
+
357
+ ---
358
+
359
+ **Built with ❤️ for the NestJS community**
@@ -0,0 +1,13 @@
1
+ export declare const RATE_LIMIT_OPTIONS = "RATE_LIMIT_OPTIONS";
2
+ export declare const RATE_LIMIT_STORAGE = "RATE_LIMIT_STORAGE";
3
+ export declare const RATE_LIMIT_SERVICE = "RATE_LIMIT_SERVICE";
4
+ export declare const RATE_LIMIT_METADATA_KEY = "ratelimit:metadata";
5
+ export declare const DEFAULT_KEY_PREFIX = "ratelimit:";
6
+ export declare const DEFAULT_API_KEY_HEADER = "X-API-Key";
7
+ export declare const DEFAULT_LIMIT = 100;
8
+ export declare const DEFAULT_DURATION = "1m";
9
+ export declare const HEADER_LIMIT = "X-RateLimit-Limit";
10
+ export declare const HEADER_REMAINING = "X-RateLimit-Remaining";
11
+ export declare const HEADER_RESET = "X-RateLimit-Reset";
12
+ export declare const HEADER_RETRY_AFTER = "Retry-After";
13
+ export declare const DEFAULT_ERROR_MESSAGE = "Too many requests. Please try again later.";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_ERROR_MESSAGE = exports.HEADER_RETRY_AFTER = exports.HEADER_RESET = exports.HEADER_REMAINING = exports.HEADER_LIMIT = exports.DEFAULT_DURATION = exports.DEFAULT_LIMIT = exports.DEFAULT_API_KEY_HEADER = exports.DEFAULT_KEY_PREFIX = exports.RATE_LIMIT_METADATA_KEY = exports.RATE_LIMIT_SERVICE = exports.RATE_LIMIT_STORAGE = exports.RATE_LIMIT_OPTIONS = void 0;
4
+ exports.RATE_LIMIT_OPTIONS = 'RATE_LIMIT_OPTIONS';
5
+ exports.RATE_LIMIT_STORAGE = 'RATE_LIMIT_STORAGE';
6
+ exports.RATE_LIMIT_SERVICE = 'RATE_LIMIT_SERVICE';
7
+ exports.RATE_LIMIT_METADATA_KEY = 'ratelimit:metadata';
8
+ exports.DEFAULT_KEY_PREFIX = 'ratelimit:';
9
+ exports.DEFAULT_API_KEY_HEADER = 'X-API-Key';
10
+ exports.DEFAULT_LIMIT = 100;
11
+ exports.DEFAULT_DURATION = '1m';
12
+ exports.HEADER_LIMIT = 'X-RateLimit-Limit';
13
+ exports.HEADER_REMAINING = 'X-RateLimit-Remaining';
14
+ exports.HEADER_RESET = 'X-RateLimit-Reset';
15
+ exports.HEADER_RETRY_AFTER = 'Retry-After';
16
+ exports.DEFAULT_ERROR_MESSAGE = 'Too many requests. Please try again later.';
17
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":";;;AAGa,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAC1C,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAC1C,QAAA,kBAAkB,GAAG,oBAAoB,CAAC;AAK1C,QAAA,uBAAuB,GAAG,oBAAoB,CAAC;AAK/C,QAAA,kBAAkB,GAAG,YAAY,CAAC;AAClC,QAAA,sBAAsB,GAAG,WAAW,CAAC;AACrC,QAAA,aAAa,GAAG,GAAG,CAAC;AACpB,QAAA,gBAAgB,GAAG,IAAI,CAAC;AAKxB,QAAA,YAAY,GAAG,mBAAmB,CAAC;AACnC,QAAA,gBAAgB,GAAG,uBAAuB,CAAC;AAC3C,QAAA,YAAY,GAAG,mBAAmB,CAAC;AACnC,QAAA,kBAAkB,GAAG,aAAa,CAAC;AAKnC,QAAA,qBAAqB,GAAG,4CAA4C,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { RateLimitDecoratorOptions } from '../interfaces/rate-limit-options.interface';
2
+ export declare const RateLimit: (options?: RateLimitDecoratorOptions) => MethodDecorator;
3
+ export declare const SkipRateLimit: () => MethodDecorator;
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SkipRateLimit = exports.RateLimit = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ const constants_1 = require("../constants");
6
+ const RateLimit = (options) => {
7
+ return (0, common_1.SetMetadata)(constants_1.RATE_LIMIT_METADATA_KEY, options || {});
8
+ };
9
+ exports.RateLimit = RateLimit;
10
+ const SkipRateLimit = () => {
11
+ return (0, common_1.SetMetadata)(constants_1.RATE_LIMIT_METADATA_KEY, { skip: true });
12
+ };
13
+ exports.SkipRateLimit = SkipRateLimit;
14
+ //# sourceMappingURL=rate-limit.decorator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.decorator.js","sourceRoot":"","sources":["../../src/decorators/rate-limit.decorator.ts"],"names":[],"mappings":";;;AAAA,2CAA6C;AAE7C,4CAAuD;AAyBhD,MAAM,SAAS,GAAG,CAAC,OAAmC,EAAmB,EAAE;IAChF,OAAO,IAAA,oBAAW,EAAC,mCAAuB,EAAE,OAAO,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC,CAAC;AAFW,QAAA,SAAS,aAEpB;AAcK,MAAM,aAAa,GAAG,GAAoB,EAAE;IACjD,OAAO,IAAA,oBAAW,EAAC,mCAAuB,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;AAC9D,CAAC,CAAC;AAFW,QAAA,aAAa,iBAExB"}
@@ -0,0 +1,5 @@
1
+ import { HttpException } from '@nestjs/common';
2
+ export declare class RateLimitExceededException extends HttpException {
3
+ readonly retryAfter?: number | undefined;
4
+ constructor(message?: string, retryAfter?: number | undefined);
5
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RateLimitExceededException = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ class RateLimitExceededException extends common_1.HttpException {
6
+ constructor(message = 'Too many requests. Please try again later.', retryAfter) {
7
+ super({
8
+ statusCode: common_1.HttpStatus.TOO_MANY_REQUESTS,
9
+ message,
10
+ error: 'Too Many Requests',
11
+ retryAfter,
12
+ }, common_1.HttpStatus.TOO_MANY_REQUESTS);
13
+ this.retryAfter = retryAfter;
14
+ }
15
+ }
16
+ exports.RateLimitExceededException = RateLimitExceededException;
17
+ //# sourceMappingURL=rate-limit.exception.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.exception.js","sourceRoot":"","sources":["../../src/exceptions/rate-limit.exception.ts"],"names":[],"mappings":";;;AAAA,2CAA2D;AAK3D,MAAa,0BAA2B,SAAQ,sBAAa;IAC3D,YACE,UAAkB,4CAA4C,EAC9C,UAAmB;QAEnC,KAAK,CACH;YACE,UAAU,EAAE,mBAAU,CAAC,iBAAiB;YACxC,OAAO;YACP,KAAK,EAAE,mBAAmB;YAC1B,UAAU;SACX,EACD,mBAAU,CAAC,iBAAiB,CAC7B,CAAC;QAVc,eAAU,GAAV,UAAU,CAAS;IAWrC,CAAC;CACF;AAfD,gEAeC"}
@@ -0,0 +1,12 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { RateLimitService } from '../services/rate-limit.service';
4
+ import { RateLimitModuleOptions } from '../interfaces/rate-limit-options.interface';
5
+ export declare class RateLimitGuard implements CanActivate {
6
+ private readonly reflector;
7
+ private readonly rateLimitService;
8
+ private readonly options;
9
+ constructor(reflector: Reflector, rateLimitService: RateLimitService, options: RateLimitModuleOptions);
10
+ canActivate(context: ExecutionContext): Promise<boolean>;
11
+ private shouldSkipPath;
12
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ 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;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.RateLimitGuard = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@nestjs/core");
18
+ const rate_limit_service_1 = require("../services/rate-limit.service");
19
+ const rate_limit_exception_1 = require("../exceptions/rate-limit.exception");
20
+ const constants_1 = require("../constants");
21
+ const constants_2 = require("../constants");
22
+ let RateLimitGuard = class RateLimitGuard {
23
+ constructor(reflector, rateLimitService, options) {
24
+ this.reflector = reflector;
25
+ this.rateLimitService = rateLimitService;
26
+ this.options = options;
27
+ }
28
+ async canActivate(context) {
29
+ const request = context.switchToHttp().getRequest();
30
+ const response = context.switchToHttp().getResponse();
31
+ const decoratorOptions = this.reflector.get(constants_1.RATE_LIMIT_METADATA_KEY, context.getHandler());
32
+ if (decoratorOptions?.skip) {
33
+ return true;
34
+ }
35
+ if (this.shouldSkipPath(request.path)) {
36
+ return true;
37
+ }
38
+ const result = await this.rateLimitService.check(request, {
39
+ cost: decoratorOptions?.cost,
40
+ });
41
+ if (this.options.includeHeaders !== false) {
42
+ response.setHeader(constants_1.HEADER_LIMIT, result.limit.toString());
43
+ response.setHeader(constants_1.HEADER_REMAINING, result.remaining.toString());
44
+ response.setHeader(constants_1.HEADER_RESET, result.resetAt.toString());
45
+ }
46
+ if (!result.allowed) {
47
+ if (result.retryAfter) {
48
+ response.setHeader(constants_1.HEADER_RETRY_AFTER, result.retryAfter.toString());
49
+ }
50
+ const errorMessage = decoratorOptions?.errorMessage || this.options.errorMessage || constants_1.DEFAULT_ERROR_MESSAGE;
51
+ throw new rate_limit_exception_1.RateLimitExceededException(errorMessage, result.retryAfter);
52
+ }
53
+ return true;
54
+ }
55
+ shouldSkipPath(path) {
56
+ if (!this.options.skipPaths || this.options.skipPaths.length === 0) {
57
+ return false;
58
+ }
59
+ return this.options.skipPaths.some((skipPath) => {
60
+ if (skipPath.endsWith('*')) {
61
+ const prefix = skipPath.slice(0, -1);
62
+ return path.startsWith(prefix);
63
+ }
64
+ return path === skipPath;
65
+ });
66
+ }
67
+ };
68
+ exports.RateLimitGuard = RateLimitGuard;
69
+ exports.RateLimitGuard = RateLimitGuard = __decorate([
70
+ (0, common_1.Injectable)(),
71
+ __param(2, (0, common_1.Inject)(constants_2.RATE_LIMIT_OPTIONS)),
72
+ __metadata("design:paramtypes", [core_1.Reflector,
73
+ rate_limit_service_1.RateLimitService, Object])
74
+ ], RateLimitGuard);
75
+ //# sourceMappingURL=rate-limit.guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.guard.js","sourceRoot":"","sources":["../../src/guards/rate-limit.guard.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export * from './rate-limit.module';
2
+ export * from './services/rate-limit.service';
3
+ export * from './guards/rate-limit.guard';
4
+ export * from './decorators/rate-limit.decorator';
5
+ export * from './interfaces/rate-limit.interface';
6
+ export * from './interfaces/rate-limit-options.interface';
7
+ export * from './interfaces/storage.interface';
8
+ export * from './storage/memory.storage';
9
+ export * from './exceptions/rate-limit.exception';
10
+ export * from './constants';
11
+ export * from './utils/time-utils';
12
+ export * from './utils/key.utils';
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./rate-limit.module"), exports);
18
+ __exportStar(require("./services/rate-limit.service"), exports);
19
+ __exportStar(require("./guards/rate-limit.guard"), exports);
20
+ __exportStar(require("./decorators/rate-limit.decorator"), exports);
21
+ __exportStar(require("./interfaces/rate-limit.interface"), exports);
22
+ __exportStar(require("./interfaces/rate-limit-options.interface"), exports);
23
+ __exportStar(require("./interfaces/storage.interface"), exports);
24
+ __exportStar(require("./storage/memory.storage"), exports);
25
+ __exportStar(require("./exceptions/rate-limit.exception"), exports);
26
+ __exportStar(require("./constants"), exports);
27
+ __exportStar(require("./utils/time-utils"), exports);
28
+ __exportStar(require("./utils/key.utils"), exports);
29
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AACA,sDAAoC;AAGpC,gEAA8C;AAG9C,4DAA0C;AAG1C,oEAAkD;AAGlD,oEAAkD;AAClD,4EAA0D;AAC1D,iEAA+C;AAG/C,2DAAyC;AAGzC,oEAAkD;AAGlD,8CAA4B;AAG5B,qDAAmC;AACnC,oDAAkC"}
@@ -0,0 +1,45 @@
1
+ import { ModuleMetadata, Type } from '@nestjs/common';
2
+ import { RateLimitStorage } from './storage.interface';
3
+ export declare enum RateLimitAlgorithm {
4
+ TOKEN_BUCKET = "token-bucket",
5
+ FIXED_WINDOW = "fixed-window",
6
+ SLIDING_WINDOW = "sliding-window",
7
+ SLIDING_WINDOW_COUNTER = "sliding-window-counter"
8
+ }
9
+ export declare enum RateLimitKeyStrategy {
10
+ IP = "ip",
11
+ USER_ID = "user-id",
12
+ API_KEY = "api-key",
13
+ CUSTOM = "custom",
14
+ COMBINED = "combined"
15
+ }
16
+ export interface RateLimitModuleOptions {
17
+ algorithm?: RateLimitAlgorithm;
18
+ limit: number;
19
+ duration: string;
20
+ keyStrategy?: RateLimitKeyStrategy;
21
+ keyExtractor?: (request: any) => string | Promise<string>;
22
+ storage?: Type<RateLimitStorage>;
23
+ redisUrl?: string;
24
+ keyPrefix?: string;
25
+ apiKeyHeader?: string;
26
+ enableCostBased?: boolean;
27
+ blockDuration?: number;
28
+ skipPaths?: string[];
29
+ skipIf?: (request: any) => boolean | Promise<boolean>;
30
+ enabled?: boolean;
31
+ errorMessage?: string;
32
+ includeHeaders?: boolean;
33
+ }
34
+ export interface RateLimitModuleAsyncOptions extends Pick<ModuleMetadata, 'imports'> {
35
+ useFactory?: (...args: any[]) => Promise<RateLimitModuleOptions> | RateLimitModuleOptions;
36
+ inject?: any[];
37
+ }
38
+ export interface RateLimitDecoratorOptions {
39
+ limit?: number;
40
+ duration?: string;
41
+ keyExtractor?: (request: any) => string | Promise<string>;
42
+ cost?: number;
43
+ skip?: boolean;
44
+ errorMessage?: string;
45
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RateLimitKeyStrategy = exports.RateLimitAlgorithm = void 0;
4
+ var RateLimitAlgorithm;
5
+ (function (RateLimitAlgorithm) {
6
+ RateLimitAlgorithm["TOKEN_BUCKET"] = "token-bucket";
7
+ RateLimitAlgorithm["FIXED_WINDOW"] = "fixed-window";
8
+ RateLimitAlgorithm["SLIDING_WINDOW"] = "sliding-window";
9
+ RateLimitAlgorithm["SLIDING_WINDOW_COUNTER"] = "sliding-window-counter";
10
+ })(RateLimitAlgorithm || (exports.RateLimitAlgorithm = RateLimitAlgorithm = {}));
11
+ var RateLimitKeyStrategy;
12
+ (function (RateLimitKeyStrategy) {
13
+ RateLimitKeyStrategy["IP"] = "ip";
14
+ RateLimitKeyStrategy["USER_ID"] = "user-id";
15
+ RateLimitKeyStrategy["API_KEY"] = "api-key";
16
+ RateLimitKeyStrategy["CUSTOM"] = "custom";
17
+ RateLimitKeyStrategy["COMBINED"] = "combined";
18
+ })(RateLimitKeyStrategy || (exports.RateLimitKeyStrategy = RateLimitKeyStrategy = {}));
19
+ //# sourceMappingURL=rate-limit-options.interface.js.map