@joint-ops/hitlimit 1.0.4 → 1.0.6

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 CHANGED
@@ -6,7 +6,7 @@
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
7
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/@joint-ops/hitlimit)](https://bundlephobia.com/package/@joint-ops/hitlimit)
8
8
 
9
- > The fastest rate limiter for Node.js - Express, NestJS, and native HTTP | express-rate-limit alternative
9
+ > The fastest rate limiter for Node.js - Express, Fastify, NestJS, and native HTTP | express-rate-limit alternative
10
10
 
11
11
  **hitlimit** is a high-performance rate limiting middleware for Node.js applications. Protect your APIs from abuse, prevent brute force attacks, and throttle requests with sub-millisecond overhead. A faster, lighter alternative to express-rate-limit and rate-limiter-flexible.
12
12
 
@@ -14,7 +14,7 @@
14
14
 
15
15
  ## Why hitlimit?
16
16
 
17
- - **Blazing Fast** - 400,000+ ops/sec with memory store, ~7% HTTP overhead
17
+ - **Blazing Fast** - 2,450,000+ ops/sec with memory store (multi-IP scenarios), ~7% HTTP overhead
18
18
  - **Zero Config** - Works out of the box with sensible defaults
19
19
  - **Tiny Footprint** - Only ~7KB core, zero runtime dependencies
20
20
  - **Framework Agnostic** - Express, NestJS, Fastify, native HTTP
@@ -22,6 +22,8 @@
22
22
  - **TypeScript First** - Full type safety and IntelliSense support
23
23
  - **Flexible Keys** - Rate limit by IP, user ID, API key, or custom logic
24
24
  - **Tiered Limits** - Different limits for free/pro/enterprise users
25
+ - **Auto-Ban** - Automatically ban repeat offenders after threshold violations
26
+ - **Shared Limits** - Group rate limits via groupId for teams/tenants
25
27
  - **Standard Headers** - RFC-compliant RateLimit-* and X-RateLimit-* headers
26
28
 
27
29
  ## Performance
@@ -32,17 +34,17 @@ hitlimit is designed for speed. Here's how it performs:
32
34
 
33
35
  | Store | Operations/sec | Avg Latency | Use Case |
34
36
  |-------|----------------|-------------|----------|
35
- | **Memory** | 2,320,000+ | 0.43μs | Single instance, no persistence |
36
- | **SQLite** | 393,000+ | 2.54μs | Single instance, persistence needed |
37
+ | **Memory** | 2,450,000+ | 0.41μs | Single instance, no persistence (multi-IP scenarios) |
38
+ | **SQLite** | 390,000+ | 2.56μs | Single instance, persistence needed (multi-IP scenarios) |
37
39
  | **Redis** | 6,500+ | 153μs | Multi-instance, distributed |
38
40
 
39
41
  ### vs Competitors
40
42
 
41
43
  | Library | Memory 10K IPs (ops/s) | Bundle Size |
42
44
  |---------|------------------------|-------------|
43
- | **hitlimit** | **2,320,000** | **~7KB** |
44
- | rate-limiter-flexible | 1,630,000 | ~155KB |
45
- | express-rate-limit | 1,220,000 | ~66KB |
45
+ | **hitlimit** | **2,450,000** | **~7KB** |
46
+ | rate-limiter-flexible | 1,840,000 | ~155KB |
47
+ | express-rate-limit | 1,210,000 | ~66KB |
46
48
 
47
49
  > **Note:** Benchmark results vary by hardware and environment. Run your own benchmarks to see results on your specific setup.
48
50
 
@@ -76,6 +78,12 @@ pnpm add @joint-ops/hitlimit
76
78
  yarn add @joint-ops/hitlimit
77
79
  ```
78
80
 
81
+ For Fastify, also install peer dependencies:
82
+
83
+ ```bash
84
+ npm install fastify fastify-plugin
85
+ ```
86
+
79
87
  ## Quick Start
80
88
 
81
89
  ### Express Rate Limiting
@@ -99,6 +107,23 @@ app.get('/api', (req, res) => res.json({ status: 'ok' }))
99
107
  app.listen(3000)
100
108
  ```
101
109
 
110
+ ### Fastify Rate Limiting
111
+
112
+ ```typescript
113
+ import Fastify from 'fastify'
114
+ import { hitlimit } from '@joint-ops/hitlimit/fastify'
115
+
116
+ const app = Fastify()
117
+
118
+ await app.register(hitlimit, {
119
+ limit: 100,
120
+ window: '1m'
121
+ })
122
+
123
+ app.get('/api', () => ({ status: 'ok' }))
124
+ await app.listen({ port: 3000 })
125
+ ```
126
+
102
127
  ### NestJS Rate Limiting
103
128
 
104
129
  ```typescript
@@ -200,6 +225,37 @@ hitlimit({
200
225
  })
201
226
  ```
202
227
 
228
+ ### Auto-Ban Repeat Offenders
229
+
230
+ Automatically ban IPs that violate rate limits repeatedly.
231
+
232
+ ```javascript
233
+ hitlimit({
234
+ limit: 100,
235
+ window: '1m',
236
+ ban: {
237
+ threshold: 5, // Ban after 5 violations
238
+ duration: '15m' // Ban for 15 minutes
239
+ }
240
+ })
241
+ ```
242
+
243
+ When a client exceeds the rate limit 5 times, they'll be banned for 15 minutes. During the ban, all requests return 429 immediately.
244
+
245
+ ### Shared Rate Limits (Group)
246
+
247
+ Share rate limits across multiple clients using a group identifier.
248
+
249
+ ```javascript
250
+ hitlimit({
251
+ limit: 10000,
252
+ window: '1h',
253
+ group: (req) => req.user.teamId // Share limit across team
254
+ })
255
+ ```
256
+
257
+ All requests with the same team ID share the same rate limit counter. Perfect for team-based SaaS quotas.
258
+
203
259
  ### Skip Certain Requests
204
260
 
205
261
  Whitelist health checks, internal routes, or admin users.
@@ -214,6 +270,39 @@ hitlimit({
214
270
  })
215
271
  ```
216
272
 
273
+ ### Auto-Ban Repeat Offenders
274
+
275
+ Automatically ban clients that repeatedly exceed rate limits.
276
+
277
+ ```javascript
278
+ hitlimit({
279
+ limit: 10,
280
+ window: '1m',
281
+ ban: {
282
+ threshold: 5, // Ban after 5 violations
283
+ duration: '1h' // Ban lasts 1 hour
284
+ }
285
+ })
286
+ ```
287
+
288
+ Banned clients receive `X-RateLimit-Ban: true` header and `banned: true` in the response body.
289
+
290
+ ### Grouped / Shared Limits
291
+
292
+ Rate limit by organization, API key, or any shared identifier.
293
+
294
+ ```javascript
295
+ // Per-API-key rate limiting
296
+ hitlimit({
297
+ limit: 1000,
298
+ window: '1h',
299
+ group: (req) => req.headers['x-api-key'] || 'anonymous'
300
+ })
301
+
302
+ // Static group prefix
303
+ hitlimit({ group: 'api', limit: 100, window: '1m' })
304
+ ```
305
+
217
306
  ## Configuration Options
218
307
 
219
308
  ```javascript
@@ -253,7 +342,16 @@ hitlimit({
253
342
  skip: (req) => req.path === '/health',
254
343
 
255
344
  // Error handling
256
- onStoreError: (error, req) => 'allow' // or 'deny'
345
+ onStoreError: (error, req) => 'allow', // or 'deny'
346
+
347
+ // Ban repeat offenders
348
+ ban: {
349
+ threshold: 5, // violations before ban
350
+ duration: '1h' // ban duration
351
+ },
352
+
353
+ // Group/shared limits
354
+ group: (req) => req.headers['x-api-key'] || 'default'
257
355
  })
258
356
  ```
259
357
 
@@ -381,6 +479,18 @@ import { hitlimit } from '@joint-ops/hitlimit'
381
479
  app.use(hitlimit({ limit: 100, window: '1m' }))
382
480
  ```
383
481
 
482
+ ### From @fastify/rate-limit
483
+
484
+ ```typescript
485
+ // Before (@fastify/rate-limit)
486
+ import rateLimit from '@fastify/rate-limit'
487
+ await app.register(rateLimit, { max: 100, timeWindow: '1 minute' })
488
+
489
+ // After (hitlimit) - tiered limits, SQLite, multi-framework
490
+ import { hitlimit } from '@joint-ops/hitlimit/fastify'
491
+ await app.register(hitlimit, { limit: 100, window: '1m' })
492
+ ```
493
+
384
494
  ### From @nestjs/throttler
385
495
 
386
496
  ```typescript
@@ -400,4 +510,4 @@ MIT - Use freely in personal and commercial projects.
400
510
 
401
511
  ## Keywords
402
512
 
403
- rate limit, rate limiter, rate limiting, express rate limit, express middleware, express-rate-limit, express-rate-limit alternative, nestjs rate limit, nestjs throttler, @nestjs/throttler alternative, nestjs guard, nodejs rate limit, node rate limiter, api rate limiting, throttle requests, request throttling, api throttling, ddos protection, brute force protection, redis rate limit, memory rate limit, sqlite rate limit, sliding window, fixed window, token bucket, leaky bucket, rate-limiter-flexible alternative, api security, request limiter, http rate limit, express slow down, api protection, login protection, authentication rate limit
513
+ rate limit, rate limiter, rate limiting, express rate limit, express middleware, express-rate-limit, express-rate-limit alternative, fastify rate limit, fastify plugin, fastify-rate-limit alternative, nestjs rate limit, nestjs throttler, @nestjs/throttler alternative, nestjs guard, nodejs rate limit, node rate limiter, api rate limiting, throttle requests, request throttling, api throttling, ddos protection, brute force protection, redis rate limit, memory rate limit, sqlite rate limit, sliding window, fixed window, token bucket, leaky bucket, rate-limiter-flexible alternative, api security, request limiter, http rate limit, express slow down, api protection, login protection, authentication rate limit
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACZ,cAAc,EACf,MAAM,2BAA2B,CAAA;AAGlC,wBAAgB,aAAa,CAAC,QAAQ,EACpC,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,EAClC,YAAY,EAAE,aAAa,EAC3B,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,GACjC,cAAc,CAAC,QAAQ,CAAC,CAiB1B"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,aAAa,EACb,YAAY,EACZ,cAAc,EACf,MAAM,2BAA2B,CAAA;AAGlC,wBAAgB,aAAa,CAAC,QAAQ,EACpC,OAAO,EAAE,eAAe,CAAC,QAAQ,CAAC,EAClC,YAAY,EAAE,aAAa,EAC3B,UAAU,EAAE,YAAY,CAAC,QAAQ,CAAC,GACjC,cAAc,CAAC,QAAQ,CAAC,CAqB1B"}
@@ -14,7 +14,11 @@ export function resolveConfig(options, defaultStore, defaultKey) {
14
14
  },
15
15
  store: options.store ?? defaultStore,
16
16
  onStoreError: options.onStoreError ?? (() => 'allow'),
17
- skip: options.skip
17
+ skip: options.skip,
18
+ ban: options.ban
19
+ ? { threshold: options.ban.threshold, durationMs: parseWindow(options.ban.duration) }
20
+ : null,
21
+ group: options.group ?? null
18
22
  };
19
23
  }
20
24
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,MAAM,UAAU,aAAa,CAC3B,OAAkC,EAClC,YAA2B,EAC3B,UAAkC;IAElC,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG;QAC3B,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC;QAC7C,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,UAAU;QAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,kCAAkC,EAAE;QAC7F,OAAO,EAAE;YACP,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,IAAI,IAAI;YAC3C,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,IAAI;YACvC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,IAAI,IAAI;SAChD;QACD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,YAAY;QACpC,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;QACrD,IAAI,EAAE,OAAO,CAAC,IAAI;KACnB,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/core/config.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAExC,MAAM,UAAU,aAAa,CAC3B,OAAkC,EAClC,YAA2B,EAC3B,UAAkC;IAElC,OAAO;QACL,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG;QAC3B,QAAQ,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,IAAI,IAAI,CAAC;QAC7C,GAAG,EAAE,OAAO,CAAC,GAAG,IAAI,UAAU;QAC9B,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,kCAAkC,EAAE;QAC7F,OAAO,EAAE;YACP,QAAQ,EAAE,OAAO,CAAC,OAAO,EAAE,QAAQ,IAAI,IAAI;YAC3C,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,MAAM,IAAI,IAAI;YACvC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,IAAI,IAAI;SAChD;QACD,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,YAAY;QACpC,YAAY,EAAE,OAAO,CAAC,YAAY,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC;QACrD,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,GAAG,EAAE,OAAO,CAAC,GAAG;YACd,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE;YACrF,CAAC,CAAC,IAAI;QACR,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,IAAI;KAC7B,CAAA;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/core/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE5E,wBAAgB,YAAY,CAC1B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC/B,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBxB"}
1
+ {"version":3,"file":"headers.d.ts","sourceRoot":"","sources":["../../src/core/headers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAA;AAE5E,wBAAgB,YAAY,CAC1B,IAAI,EAAE,YAAY,EAClB,MAAM,EAAE,QAAQ,CAAC,aAAa,CAAC,EAC/B,OAAO,EAAE,OAAO,GACf,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA4BxB"}
@@ -11,8 +11,15 @@ export function buildHeaders(info, config, allowed) {
11
11
  headers['X-RateLimit-Reset'] = String(Math.ceil(info.resetAt / 1000));
12
12
  }
13
13
  if (!allowed && config.retryAfter) {
14
+ // When banned, Retry-After reflects ban duration, not window reset
14
15
  headers['Retry-After'] = String(info.resetIn);
15
16
  }
17
+ if (info.banned) {
18
+ headers['X-RateLimit-Ban'] = 'true';
19
+ if (info.banExpiresAt) {
20
+ headers['X-RateLimit-Ban-Expires'] = String(Math.ceil(info.banExpiresAt / 1000));
21
+ }
22
+ }
16
23
  return headers;
17
24
  }
18
25
  //# sourceMappingURL=headers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/core/headers.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAC1B,IAAkB,EAClB,MAA+B,EAC/B,OAAgB;IAEhB,MAAM,OAAO,GAA2B,EAAE,CAAA;IAE1C,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC/C,OAAO,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACvD,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAA;IACrE,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,OAAO,CAAC,uBAAuB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACzD,OAAO,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAA;IACvE,CAAC;IAED,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAClC,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC/C,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
1
+ {"version":3,"file":"headers.js","sourceRoot":"","sources":["../../src/core/headers.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,YAAY,CAC1B,IAAkB,EAClB,MAA+B,EAC/B,OAAgB;IAEhB,MAAM,OAAO,GAA2B,EAAE,CAAA;IAE1C,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAC/C,OAAO,CAAC,qBAAqB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACvD,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAA;IACrE,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,OAAO,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QACjD,OAAO,CAAC,uBAAuB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QACzD,OAAO,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAA;IACvE,CAAC;IAED,IAAI,CAAC,OAAO,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QAClC,mEAAmE;QACnE,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAC/C,CAAC;IAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,OAAO,CAAC,iBAAiB,CAAC,GAAG,MAAM,CAAA;QACnC,IAAI,IAAI,CAAC,YAAY,EAAE,CAAC;YACtB,OAAO,CAAC,yBAAyB,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,CAAA;QAClF,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAM7F,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAGD,wBAAsB,cAAc,CAAC,QAAQ,EAC3C,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,UAAU,CAAC,CA+BrB;AAGD,wBAAsB,UAAU,CAAC,QAAQ,EACvC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,cAAc,CAAC,CAiDzB"}
1
+ {"version":3,"file":"limiter.d.ts","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,cAAc,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAM7F,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,MAAM,CAAA;CAChB;AAsCD,wBAAsB,cAAc,CAAC,QAAQ,EAC3C,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,UAAU,CAAC,CA8CrB;AAGD,wBAAsB,UAAU,CAAC,QAAQ,EACvC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,cAAc,CAAC,CA6EzB"}
@@ -1,19 +1,50 @@
1
1
  import { parseWindow } from './utils.js';
2
2
  import { buildHeaders } from './headers.js';
3
3
  import { buildBody } from './response.js';
4
+ // Resolve group-prefixed key
5
+ async function resolveKey(config, req) {
6
+ let key = await config.key(req);
7
+ let groupId;
8
+ if (config.group) {
9
+ groupId = typeof config.group === 'function'
10
+ ? await config.group(req)
11
+ : config.group;
12
+ key = `group:${groupId}:${key}`;
13
+ }
14
+ return { key, groupId };
15
+ }
16
+ // Resolve tier-specific limit and window
17
+ function resolveTier(config, tierName) {
18
+ if (tierName && config.tiers) {
19
+ const tierConfig = config.tiers[tierName];
20
+ if (tierConfig) {
21
+ return {
22
+ limit: tierConfig.limit,
23
+ windowMs: tierConfig.window ? parseWindow(tierConfig.window) : config.windowMs
24
+ };
25
+ }
26
+ }
27
+ return { limit: config.limit, windowMs: config.windowMs };
28
+ }
4
29
  // Optimized check for tiered limits - returns minimal object
5
30
  export async function checkLimitFast(config, req) {
6
- const key = await config.key(req);
7
- let limit = config.limit;
8
- let windowMs = config.windowMs;
31
+ const { key } = await resolveKey(config, req);
32
+ let tierName;
9
33
  if (config.tier && config.tiers) {
10
- const tierName = await config.tier(req);
11
- const tierConfig = config.tiers[tierName];
12
- if (tierConfig) {
13
- limit = tierConfig.limit;
14
- if (tierConfig.window) {
15
- windowMs = parseWindow(tierConfig.window);
16
- }
34
+ tierName = await config.tier(req);
35
+ }
36
+ const { limit, windowMs } = resolveTier(config, tierName);
37
+ // Check ban status
38
+ if (config.ban && config.store.isBanned) {
39
+ const banned = await config.store.isBanned(key);
40
+ if (banned) {
41
+ return {
42
+ allowed: false,
43
+ limit,
44
+ remaining: 0,
45
+ resetIn: Math.ceil(config.ban.durationMs / 1000),
46
+ resetAt: Date.now() + config.ban.durationMs
47
+ };
17
48
  }
18
49
  }
19
50
  if (limit === Infinity) {
@@ -21,35 +52,59 @@ export async function checkLimitFast(config, req) {
21
52
  }
22
53
  const result = await config.store.hit(key, windowMs, limit);
23
54
  const now = Date.now();
55
+ const allowed = result.count <= limit;
56
+ // Track violations for ban
57
+ if (!allowed && config.ban && config.store.recordViolation) {
58
+ const violations = await config.store.recordViolation(key, config.ban.durationMs);
59
+ if (violations >= config.ban.threshold && config.store.ban) {
60
+ await config.store.ban(key, config.ban.durationMs);
61
+ }
62
+ }
24
63
  return {
25
- allowed: result.count <= limit,
64
+ allowed,
26
65
  limit,
27
66
  remaining: Math.max(0, limit - result.count),
28
67
  resetIn: Math.max(0, Math.ceil((result.resetAt - now) / 1000)),
29
68
  resetAt: result.resetAt
30
69
  };
31
70
  }
32
- // Original full check - used for testing and backwards compatibility
71
+ // Full check with complete info object
33
72
  export async function checkLimit(config, req) {
34
- // Use raw key directly - no hashing needed for rate limit keys
35
- const key = await config.key(req);
36
- let limit = config.limit;
37
- let windowMs = config.windowMs;
73
+ const { key, groupId } = await resolveKey(config, req);
38
74
  let tierName;
39
75
  if (config.tier && config.tiers) {
40
76
  tierName = await config.tier(req);
41
- const tierConfig = config.tiers[tierName];
42
- if (tierConfig) {
43
- limit = tierConfig.limit;
44
- if (tierConfig.window) {
45
- windowMs = parseWindow(tierConfig.window);
46
- }
77
+ }
78
+ const { limit, windowMs } = resolveTier(config, tierName);
79
+ // Check ban status BEFORE hitting store
80
+ if (config.ban && config.store.isBanned) {
81
+ const banned = await config.store.isBanned(key);
82
+ if (banned) {
83
+ const banResetIn = Math.ceil(config.ban.durationMs / 1000);
84
+ const info = {
85
+ limit,
86
+ remaining: 0,
87
+ resetIn: banResetIn,
88
+ resetAt: Date.now() + config.ban.durationMs,
89
+ key,
90
+ tier: tierName,
91
+ banned: true,
92
+ banExpiresAt: Date.now() + config.ban.durationMs,
93
+ group: groupId
94
+ };
95
+ return {
96
+ allowed: false,
97
+ info,
98
+ headers: buildHeaders(info, config.headers, false),
99
+ body: buildBody(config.response, info)
100
+ };
47
101
  }
48
102
  }
103
+ // Infinity limit skips store entirely (no violations possible)
49
104
  if (limit === Infinity) {
50
105
  return {
51
106
  allowed: true,
52
- info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName },
107
+ info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
53
108
  headers: {},
54
109
  body: {}
55
110
  };
@@ -65,8 +120,19 @@ export async function checkLimit(config, req) {
65
120
  resetIn,
66
121
  resetAt: result.resetAt,
67
122
  key,
68
- tier: tierName
123
+ tier: tierName,
124
+ group: groupId
69
125
  };
126
+ // Track violations and check ban threshold
127
+ if (!allowed && config.ban && config.store.recordViolation) {
128
+ const violations = await config.store.recordViolation(key, config.ban.durationMs);
129
+ info.violations = violations;
130
+ if (violations >= config.ban.threshold && config.store.ban) {
131
+ await config.store.ban(key, config.ban.durationMs);
132
+ info.banned = true;
133
+ info.banExpiresAt = now + config.ban.durationMs;
134
+ }
135
+ }
70
136
  return {
71
137
  allowed,
72
138
  info,
@@ -1 +1 @@
1
- {"version":3,"file":"limiter.js","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"limiter.js","sourceRoot":"","sources":["../../src/core/limiter.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAEhG,wBAAgB,SAAS,CACvB,QAAQ,EAAE,cAAc,GAAG,iBAAiB,EAC5C,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAWrB"}
1
+ {"version":3,"file":"response.d.ts","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAEhG,wBAAgB,SAAS,CACvB,QAAQ,EAAE,cAAc,GAAG,iBAAiB,EAC5C,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAmBrB"}
@@ -2,11 +2,17 @@ export function buildBody(response, info) {
2
2
  if (typeof response === 'function') {
3
3
  return response(info);
4
4
  }
5
- return {
5
+ const body = {
6
6
  ...response,
7
7
  limit: info.limit,
8
8
  remaining: info.remaining,
9
9
  resetIn: info.resetIn
10
10
  };
11
+ if (info.banned) {
12
+ body.banned = true;
13
+ body.banExpiresAt = info.banExpiresAt;
14
+ body.message = 'You have been temporarily banned due to repeated rate limit violations';
15
+ }
16
+ return body;
11
17
  }
12
18
  //# sourceMappingURL=response.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CACvB,QAA4C,EAC5C,IAAkB;IAElB,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;IAED,OAAO;QACL,GAAG,QAAQ;QACX,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"response.js","sourceRoot":"","sources":["../../src/core/response.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS,CACvB,QAA4C,EAC5C,IAAkB;IAElB,IAAI,OAAO,QAAQ,KAAK,UAAU,EAAE,CAAC;QACnC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAA;IACvB,CAAC;IAED,MAAM,IAAI,GAAwB;QAChC,GAAG,QAAQ;QACX,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,SAAS,EAAE,IAAI,CAAC,SAAS;QACzB,OAAO,EAAE,IAAI,CAAC,OAAO;KACtB,CAAA;IAED,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAA;QAClB,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAA;QACrC,IAAI,CAAC,OAAO,GAAG,wEAAwE,CAAA;IACzF,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { FastifyInstance, FastifyRequest } from 'fastify';
2
+ import type { HitLimitOptions } from '@joint-ops/hitlimit-types';
3
+ declare function hitlimitPlugin(fastify: FastifyInstance, options: HitLimitOptions<FastifyRequest>): Promise<void>;
4
+ export declare const hitlimit: typeof hitlimitPlugin;
5
+ export {};
6
+ //# sourceMappingURL=fastify.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.d.ts","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAgB,MAAM,SAAS,CAAA;AAC5E,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAShE,iBAAe,cAAc,CAC3B,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,eAAe,CAAC,cAAc,CAAC,iBA8BzC;AAED,eAAO,MAAM,QAAQ,uBAGnB,CAAA"}
@@ -0,0 +1,40 @@
1
+ import fp from 'fastify-plugin';
2
+ import { resolveConfig } from './core/config.js';
3
+ import { checkLimit } from './core/limiter.js';
4
+ import { memoryStore } from './stores/memory.js';
5
+ function getDefaultKey(req) {
6
+ return req.ip || 'unknown';
7
+ }
8
+ async function hitlimitPlugin(fastify, options) {
9
+ const store = options.store ?? memoryStore();
10
+ const config = resolveConfig(options, store, getDefaultKey);
11
+ fastify.addHook('onRequest', async (request, reply) => {
12
+ if (config.skip) {
13
+ const shouldSkip = await config.skip(request);
14
+ if (shouldSkip)
15
+ return;
16
+ }
17
+ try {
18
+ const result = await checkLimit(config, request);
19
+ for (const [key, value] of Object.entries(result.headers)) {
20
+ reply.header(key, value);
21
+ }
22
+ if (!result.allowed) {
23
+ reply.status(429).send(result.body);
24
+ return;
25
+ }
26
+ }
27
+ catch (error) {
28
+ const action = await config.onStoreError(error, request);
29
+ if (action === 'deny') {
30
+ reply.status(429).send({ hitlimit: true, message: 'Rate limit error' });
31
+ return;
32
+ }
33
+ }
34
+ });
35
+ }
36
+ export const hitlimit = fp(hitlimitPlugin, {
37
+ name: 'hitlimit',
38
+ fastify: '>=4.0.0'
39
+ });
40
+ //# sourceMappingURL=fastify.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fastify.js","sourceRoot":"","sources":["../src/fastify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,gBAAgB,CAAA;AAG/B,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAEhD,SAAS,aAAa,CAAC,GAAmB;IACxC,OAAO,GAAG,CAAC,EAAE,IAAI,SAAS,CAAA;AAC5B,CAAC;AAED,KAAK,UAAU,cAAc,CAC3B,OAAwB,EACxB,OAAwC;IAExC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,WAAW,EAAE,CAAA;IAC5C,MAAM,MAAM,GAAG,aAAa,CAAC,OAAO,EAAE,KAAK,EAAE,aAAa,CAAC,CAAA;IAE3D,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,EAAE,OAAuB,EAAE,KAAmB,EAAE,EAAE;QAClF,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;YAC7C,IAAI,UAAU;gBAAE,OAAM;QACxB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;YAEhD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1D,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAC1B,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;gBACnC,OAAM;YACR,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAc,EAAE,OAAO,CAAC,CAAA;YACjE,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAA;gBACvE,OAAM;YACR,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,CAAC,cAAc,EAAE;IACzC,IAAI,EAAE,UAAU;IAChB,OAAO,EAAE,SAAS;CACnB,CAAC,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import type { Request, Response, NextFunction } from 'express';
2
2
  import type { HitLimitOptions } from '@joint-ops/hitlimit-types';
3
- export type { HitLimitOptions, HitLimitInfo, HitLimitResult, HitLimitStore, StoreResult, TierConfig, HeadersConfig, ResolvedConfig, KeyGenerator, TierResolver, SkipFunction, StoreErrorHandler, ResponseFormatter, ResponseConfig } from '@joint-ops/hitlimit-types';
3
+ import { checkLimit } from './core/limiter.js';
4
+ export type { HitLimitOptions, HitLimitInfo, HitLimitResult, HitLimitStore, StoreResult, TierConfig, HeadersConfig, ResolvedConfig, KeyGenerator, TierResolver, SkipFunction, StoreErrorHandler, ResponseFormatter, ResponseConfig, BanConfig, GroupIdResolver } from '@joint-ops/hitlimit-types';
4
5
  export { DEFAULT_LIMIT, DEFAULT_WINDOW, DEFAULT_WINDOW_MS, DEFAULT_MESSAGE } from '@joint-ops/hitlimit-types';
5
6
  export { memoryStore } from './stores/memory.js';
6
- export { checkLimit } from './core/limiter.js';
7
+ export { checkLimit };
7
8
  export declare function hitlimit(options?: HitLimitOptions<Request>): (req: Request, res: Response, next: NextFunction) => Promise<void>;
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,2BAA2B,CAAA;AAIjH,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AACrQ,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAiB9C,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM,SAgBxC,OAAO,OAAO,QAAQ,QAAQ,YAAY,mBA+GhE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC9D,OAAO,KAAK,EAAE,eAAe,EAAmD,MAAM,2BAA2B,CAAA;AAEjH,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAG9C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,WAAW,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,cAAc,EAAE,SAAS,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AACjS,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAC7G,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,CAAA;AAiBrB,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM,SAkBxC,OAAO,OAAO,QAAQ,QAAQ,YAAY,mBA0EhE"}
package/dist/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import { resolveConfig } from './core/config.js';
2
+ import { checkLimit } from './core/limiter.js';
2
3
  import { memoryStore } from './stores/memory.js';
3
4
  export { DEFAULT_LIMIT, DEFAULT_WINDOW, DEFAULT_WINDOW_MS, DEFAULT_MESSAGE } from '@joint-ops/hitlimit-types';
4
5
  export { memoryStore } from './stores/memory.js';
5
- export { checkLimit } from './core/limiter.js';
6
+ export { checkLimit };
6
7
  function getDefaultKey(req) {
7
8
  return req.ip || req.socket?.remoteAddress || 'unknown';
8
9
  }
@@ -19,14 +20,16 @@ export function hitlimit(options = {}) {
19
20
  // Pre-compute flags
20
21
  const hasSkip = !!config.skip;
21
22
  const hasTiers = !!(config.tier && config.tiers);
23
+ const hasBan = !!config.ban;
24
+ const hasGroup = !!config.group;
22
25
  const standardHeaders = config.headers.standard;
23
26
  const legacyHeaders = config.headers.legacy;
24
27
  const retryAfterHeader = config.headers.retryAfter;
25
28
  const limit = config.limit;
26
29
  const windowMs = config.windowMs;
27
30
  const responseConfig = config.response;
28
- // Fast path: no skip, no tiers (most common case ~80%)
29
- if (!hasSkip && !hasTiers) {
31
+ // Fast path: no skip, no tiers, no ban, no group (most common case)
32
+ if (!hasSkip && !hasTiers && !hasBan && !hasGroup) {
30
33
  return async (req, res, next) => {
31
34
  try {
32
35
  const key = await config.key(req);
@@ -67,7 +70,7 @@ export function hitlimit(options = {}) {
67
70
  }
68
71
  };
69
72
  }
70
- // Full path: with skip and/or tiers
73
+ // Full path: with skip, tiers, ban, or group — delegates to core checkLimit
71
74
  return async (req, res, next) => {
72
75
  if (hasSkip) {
73
76
  const shouldSkip = await config.skip(req);
@@ -76,45 +79,12 @@ export function hitlimit(options = {}) {
76
79
  }
77
80
  }
78
81
  try {
79
- const key = await config.key(req);
80
- let effectiveLimit = limit;
81
- let effectiveWindowMs = windowMs;
82
- let tierName;
83
- if (hasTiers) {
84
- tierName = await config.tier(req);
85
- const tierConfig = config.tiers[tierName];
86
- if (tierConfig) {
87
- effectiveLimit = tierConfig.limit;
88
- if (tierConfig.window) {
89
- effectiveWindowMs = parseWindow(tierConfig.window);
90
- }
91
- }
92
- }
93
- if (effectiveLimit === Infinity) {
94
- return next();
95
- }
96
- const result = await config.store.hit(key, effectiveWindowMs, effectiveLimit);
97
- const allowed = result.count <= effectiveLimit;
98
- const remaining = Math.max(0, effectiveLimit - result.count);
99
- const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
100
- if (standardHeaders) {
101
- res.setHeader('RateLimit-Limit', effectiveLimit);
102
- res.setHeader('RateLimit-Remaining', remaining);
103
- res.setHeader('RateLimit-Reset', resetIn);
82
+ const result = await checkLimit(config, req);
83
+ for (const [key, value] of Object.entries(result.headers)) {
84
+ res.setHeader(key, value);
104
85
  }
105
- if (legacyHeaders) {
106
- res.setHeader('X-RateLimit-Limit', effectiveLimit);
107
- res.setHeader('X-RateLimit-Remaining', remaining);
108
- res.setHeader('X-RateLimit-Reset', Math.ceil(result.resetAt / 1000));
109
- }
110
- if (!allowed) {
111
- if (retryAfterHeader) {
112
- res.setHeader('Retry-After', resetIn);
113
- }
114
- const body = buildResponseBody(responseConfig, {
115
- limit: effectiveLimit, remaining: 0, resetIn, resetAt: result.resetAt, key, tier: tierName
116
- });
117
- res.status(429).json(body);
86
+ if (!result.allowed) {
87
+ res.status(429).json(result.body);
118
88
  return;
119
89
  }
120
90
  next();
@@ -129,21 +99,4 @@ export function hitlimit(options = {}) {
129
99
  }
130
100
  };
131
101
  }
132
- function parseWindow(window) {
133
- if (typeof window === 'number')
134
- return window;
135
- const match = window.match(/^(\d+)(ms|s|m|h|d)$/);
136
- if (!match)
137
- return 60000;
138
- const value = parseInt(match[1], 10);
139
- const unit = match[2];
140
- switch (unit) {
141
- case 'ms': return value;
142
- case 's': return value * 1000;
143
- case 'm': return value * 60 * 1000;
144
- case 'h': return value * 60 * 60 * 1000;
145
- case 'd': return value * 24 * 60 * 60 * 1000;
146
- default: return 60000;
147
- }
148
- }
149
102
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AA+D3E,wBAAgB,WAAW,IAAI,aAAa,CAE3C"}
1
+ {"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAgJ3E,wBAAgB,WAAW,IAAI,aAAa,CAE3C"}
@@ -1,5 +1,7 @@
1
1
  class MemoryStore {
2
2
  hits = new Map();
3
+ bans = new Map();
4
+ violations = new Map();
3
5
  hit(key, windowMs, _limit) {
4
6
  const entry = this.hits.get(key);
5
7
  if (entry !== undefined) {
@@ -21,18 +23,79 @@ class MemoryStore {
21
23
  this.hits.set(key, { count: 1, resetAt, timeoutId });
22
24
  return { count: 1, resetAt };
23
25
  }
26
+ isBanned(key) {
27
+ const ban = this.bans.get(key);
28
+ if (!ban)
29
+ return false;
30
+ if (Date.now() >= ban.expiresAt) {
31
+ clearTimeout(ban.timeoutId);
32
+ this.bans.delete(key);
33
+ return false;
34
+ }
35
+ return true;
36
+ }
37
+ ban(key, durationMs) {
38
+ const existing = this.bans.get(key);
39
+ if (existing)
40
+ clearTimeout(existing.timeoutId);
41
+ const expiresAt = Date.now() + durationMs;
42
+ const timeoutId = setTimeout(() => {
43
+ this.bans.delete(key);
44
+ }, durationMs);
45
+ if (typeof timeoutId.unref === 'function') {
46
+ timeoutId.unref();
47
+ }
48
+ this.bans.set(key, { expiresAt, timeoutId });
49
+ }
50
+ recordViolation(key, windowMs) {
51
+ const entry = this.violations.get(key);
52
+ if (entry && Date.now() < entry.resetAt) {
53
+ entry.count++;
54
+ return entry.count;
55
+ }
56
+ // New or expired violation window
57
+ if (entry)
58
+ clearTimeout(entry.timeoutId);
59
+ const resetAt = Date.now() + windowMs;
60
+ const timeoutId = setTimeout(() => {
61
+ this.violations.delete(key);
62
+ }, windowMs);
63
+ if (typeof timeoutId.unref === 'function') {
64
+ timeoutId.unref();
65
+ }
66
+ this.violations.set(key, { count: 1, resetAt, timeoutId });
67
+ return 1;
68
+ }
24
69
  reset(key) {
25
70
  const entry = this.hits.get(key);
26
71
  if (entry) {
27
72
  clearTimeout(entry.timeoutId);
28
73
  this.hits.delete(key);
29
74
  }
75
+ const ban = this.bans.get(key);
76
+ if (ban) {
77
+ clearTimeout(ban.timeoutId);
78
+ this.bans.delete(key);
79
+ }
80
+ const violation = this.violations.get(key);
81
+ if (violation) {
82
+ clearTimeout(violation.timeoutId);
83
+ this.violations.delete(key);
84
+ }
30
85
  }
31
86
  shutdown() {
32
87
  for (const [, entry] of this.hits) {
33
88
  clearTimeout(entry.timeoutId);
34
89
  }
35
90
  this.hits.clear();
91
+ for (const [, entry] of this.bans) {
92
+ clearTimeout(entry.timeoutId);
93
+ }
94
+ this.bans.clear();
95
+ for (const [, entry] of this.violations) {
96
+ clearTimeout(entry.timeoutId);
97
+ }
98
+ this.violations.clear();
36
99
  }
37
100
  }
38
101
  export function memoryStore() {
@@ -1 +1 @@
1
- {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAiBA,MAAM,WAAW;IACE,IAAI,GAAuB,IAAI,GAAG,EAAE,CAAA;IAErD,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAc;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEhC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,qEAAqE;YACrE,wDAAwD;YACxD,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;QACvD,CAAC;QAED,8CAA8C;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,OAAO,GAAG,GAAG,GAAG,QAAQ,CAAA;QAE9B,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC,EAAE,QAAQ,CAAC,CAAA;QAEZ,2BAA2B;QAC3B,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC1C,SAAS,CAAC,KAAK,EAAE,CAAA;QACnB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACpD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAA;IAC9B,CAAC;IAED,KAAK,CAAC,GAAW;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC;IACH,CAAC;IAED,QAAQ;QACN,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;IACnB,CAAC;CACF;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B,CAAC"}
1
+ {"version":3,"file":"memory.js","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AA4BA,MAAM,WAAW;IACE,IAAI,GAAuB,IAAI,GAAG,EAAE,CAAA;IACpC,IAAI,GAA0B,IAAI,GAAG,EAAE,CAAA;IACvC,UAAU,GAAgC,IAAI,GAAG,EAAE,CAAA;IAEpE,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAc;QAC/C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEhC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,qEAAqE;YACrE,wDAAwD;YACxD,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAA;QACvD,CAAC;QAED,8CAA8C;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,OAAO,GAAG,GAAG,GAAG,QAAQ,CAAA;QAE9B,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC,EAAE,QAAQ,CAAC,CAAA;QAEZ,2BAA2B;QAC3B,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC1C,SAAS,CAAC,KAAK,EAAE,CAAA;QACnB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QACpD,OAAO,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,CAAA;IAC9B,CAAC;IAED,QAAQ,CAAC,GAAW;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,CAAC,GAAG;YAAE,OAAO,KAAK,CAAA;QACtB,IAAI,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,CAAC,SAAS,EAAE,CAAC;YAChC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC3B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACrB,OAAO,KAAK,CAAA;QACd,CAAC;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,UAAkB;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,QAAQ;YAAE,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;QAE9C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,CAAA;QACzC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC,EAAE,UAAU,CAAC,CAAA;QAEd,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC1C,SAAS,CAAC,KAAK,EAAE,CAAA;QACnB,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAA;IAC9C,CAAC;IAED,eAAe,CAAC,GAAW,EAAE,QAAgB;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACtC,IAAI,KAAK,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;YACxC,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,OAAO,KAAK,CAAC,KAAK,CAAA;QACpB,CAAC;QAED,kCAAkC;QAClC,IAAI,KAAK;YAAE,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAExC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAA;QACrC,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC7B,CAAC,EAAE,QAAQ,CAAC,CAAA;QAEZ,IAAI,OAAO,SAAS,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;YAC1C,SAAS,CAAC,KAAK,EAAE,CAAA;QACnB,CAAC;QAED,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAA;QAC1D,OAAO,CAAC,CAAA;IACV,CAAC;IAED,KAAK,CAAC,GAAW;QACf,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChC,IAAI,KAAK,EAAE,CAAC;YACV,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;YAC7B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC;QACD,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,GAAG,EAAE,CAAC;YACR,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;YAC3B,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QACvB,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1C,IAAI,SAAS,EAAE,CAAC;YACd,YAAY,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;YACjC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC7B,CAAC;IACH,CAAC;IAED,QAAQ;QACN,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QACjB,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YAClC,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAA;QACjB,KAAK,MAAM,CAAC,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACxC,YAAY,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QAC/B,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAA;IACzB,CAAC;CACF;AAED,MAAM,UAAU,WAAW;IACzB,OAAO,IAAI,WAAW,EAAE,CAAA;AAC1B,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAG3E,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA2CD,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,aAAa,CAErE"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAG3E,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAqED,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,aAAa,CAErE"}
@@ -2,9 +2,13 @@ import Redis from 'ioredis';
2
2
  class RedisStore {
3
3
  redis;
4
4
  prefix;
5
+ banPrefix;
6
+ violationPrefix;
5
7
  constructor(options = {}) {
6
8
  this.redis = new Redis(options.url ?? 'redis://localhost:6379');
7
9
  this.prefix = options.keyPrefix ?? 'hitlimit:';
10
+ this.banPrefix = (options.keyPrefix ?? 'hitlimit:') + 'ban:';
11
+ this.violationPrefix = (options.keyPrefix ?? 'hitlimit:') + 'violations:';
8
12
  }
9
13
  async hit(key, windowMs, _limit) {
10
14
  const redisKey = this.prefix + key;
@@ -23,8 +27,23 @@ class RedisStore {
23
27
  const resetAt = now + ttl;
24
28
  return { count, resetAt };
25
29
  }
30
+ async isBanned(key) {
31
+ const result = await this.redis.exists(this.banPrefix + key);
32
+ return result === 1;
33
+ }
34
+ async ban(key, durationMs) {
35
+ await this.redis.set(this.banPrefix + key, '1', 'PX', durationMs);
36
+ }
37
+ async recordViolation(key, windowMs) {
38
+ const redisKey = this.violationPrefix + key;
39
+ const count = await this.redis.incr(redisKey);
40
+ if (count === 1) {
41
+ await this.redis.pexpire(redisKey, windowMs);
42
+ }
43
+ return count;
44
+ }
26
45
  async reset(key) {
27
- await this.redis.del(this.prefix + key);
46
+ await this.redis.del(this.prefix + key, this.banPrefix + key, this.violationPrefix + key);
28
47
  }
29
48
  async shutdown() {
30
49
  await this.redis.quit();
@@ -1 +1 @@
1
- {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,MAAM,SAAS,CAAA;AAO3B,MAAM,UAAU;IACN,KAAK,CAAO;IACZ,MAAM,CAAQ;IAEtB,YAAY,UAA6B,EAAE;QACzC,IAAI,CAAC,KAAK,GAAG,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,IAAI,wBAAwB,CAAC,CAAA;QAC/D,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,SAAS,IAAI,WAAW,CAAA;IAChD,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAc;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,GAAG,CAAA;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAEtB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK;aAC7B,KAAK,EAAE;aACP,IAAI,CAAC,QAAQ,CAAC;aACd,IAAI,CAAC,QAAQ,CAAC;aACd,IAAI,EAAE,CAAA;QAET,MAAM,KAAK,GAAG,OAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAA;QACtC,IAAI,GAAG,GAAG,OAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAW,CAAA;QAElC,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;YACZ,MAAM,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAA;YAC5C,GAAG,GAAG,QAAQ,CAAA;QAChB,CAAC;QAED,MAAM,OAAO,GAAG,GAAG,GAAG,GAAG,CAAA;QAEzB,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAA;IAC3B,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,GAAW;QACrB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAA;IACzC,CAAC;IAED,KAAK,CAAC,QAAQ;QACZ,MAAM,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;IACzB,CAAC;CACF;AAED,MAAM,UAAU,UAAU,CAAC,OAA2B;IACpD,OAAO,IAAI,UAAU,CAAC,OAAO,CAAC,CAAA;AAChC,CAAC"}
1
+ {"version":3,"file":"redis.js","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAG3E,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAwDD,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,aAAa,CAEvE"}
1
+ {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAG3E,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAgHD,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,aAAa,CAEvE"}
@@ -4,6 +4,12 @@ class SqliteStore {
4
4
  hitStmt;
5
5
  getStmt;
6
6
  resetStmt;
7
+ isBannedStmt;
8
+ banStmt;
9
+ recordViolationStmt;
10
+ getViolationStmt;
11
+ resetBanStmt;
12
+ resetViolationStmt;
7
13
  cleanupTimer;
8
14
  constructor(options = {}) {
9
15
  this.db = new Database(options.path ?? ':memory:');
@@ -14,6 +20,19 @@ class SqliteStore {
14
20
  count INTEGER NOT NULL,
15
21
  reset_at INTEGER NOT NULL
16
22
  )
23
+ `);
24
+ this.db.exec(`
25
+ CREATE TABLE IF NOT EXISTS hitlimit_bans (
26
+ key TEXT PRIMARY KEY,
27
+ expires_at INTEGER NOT NULL
28
+ )
29
+ `);
30
+ this.db.exec(`
31
+ CREATE TABLE IF NOT EXISTS hitlimit_violations (
32
+ key TEXT PRIMARY KEY,
33
+ count INTEGER NOT NULL DEFAULT 1,
34
+ reset_at INTEGER NOT NULL
35
+ )
17
36
  `);
18
37
  this.hitStmt = this.db.prepare(`
19
38
  INSERT INTO hitlimit (key, count, reset_at) VALUES (?, 1, ?)
@@ -23,8 +42,22 @@ class SqliteStore {
23
42
  `);
24
43
  this.getStmt = this.db.prepare('SELECT count, reset_at FROM hitlimit WHERE key = ?');
25
44
  this.resetStmt = this.db.prepare('DELETE FROM hitlimit WHERE key = ?');
45
+ this.isBannedStmt = this.db.prepare('SELECT 1 FROM hitlimit_bans WHERE key = ? AND expires_at > ?');
46
+ this.banStmt = this.db.prepare('INSERT OR REPLACE INTO hitlimit_bans (key, expires_at) VALUES (?, ?)');
47
+ this.recordViolationStmt = this.db.prepare(`
48
+ INSERT INTO hitlimit_violations (key, count, reset_at) VALUES (?, 1, ?)
49
+ ON CONFLICT(key) DO UPDATE SET
50
+ count = CASE WHEN reset_at <= ? THEN 1 ELSE count + 1 END,
51
+ reset_at = CASE WHEN reset_at <= ? THEN excluded.reset_at ELSE reset_at END
52
+ `);
53
+ this.getViolationStmt = this.db.prepare('SELECT count FROM hitlimit_violations WHERE key = ?');
54
+ this.resetBanStmt = this.db.prepare('DELETE FROM hitlimit_bans WHERE key = ?');
55
+ this.resetViolationStmt = this.db.prepare('DELETE FROM hitlimit_violations WHERE key = ?');
26
56
  this.cleanupTimer = setInterval(() => {
27
- this.db.prepare('DELETE FROM hitlimit WHERE reset_at <= ?').run(Date.now());
57
+ const now = Date.now();
58
+ this.db.prepare('DELETE FROM hitlimit WHERE reset_at <= ?').run(now);
59
+ this.db.prepare('DELETE FROM hitlimit_bans WHERE expires_at <= ?').run(now);
60
+ this.db.prepare('DELETE FROM hitlimit_violations WHERE reset_at <= ?').run(now);
28
61
  }, 60000);
29
62
  }
30
63
  hit(key, windowMs, _limit) {
@@ -34,8 +67,23 @@ class SqliteStore {
34
67
  const row = this.getStmt.get(key);
35
68
  return { count: row.count, resetAt: row.reset_at };
36
69
  }
70
+ isBanned(key) {
71
+ return this.isBannedStmt.get(key, Date.now()) !== undefined;
72
+ }
73
+ ban(key, durationMs) {
74
+ this.banStmt.run(key, Date.now() + durationMs);
75
+ }
76
+ recordViolation(key, windowMs) {
77
+ const now = Date.now();
78
+ const resetAt = now + windowMs;
79
+ this.recordViolationStmt.run(key, resetAt, now, now);
80
+ const row = this.getViolationStmt.get(key);
81
+ return row?.count ?? 1;
82
+ }
37
83
  reset(key) {
38
84
  this.resetStmt.run(key);
85
+ this.resetBanStmt.run(key);
86
+ this.resetViolationStmt.run(key);
39
87
  }
40
88
  shutdown() {
41
89
  clearInterval(this.cleanupTimer);
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"AACA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AAMrC,MAAM,WAAW;IACP,EAAE,CAAmB;IACrB,OAAO,CAAoB;IAC3B,OAAO,CAAoB;IAC3B,SAAS,CAAoB;IAC7B,YAAY,CAAgC;IAEpD,YAAY,UAA8B,EAAE;QAC1C,IAAI,CAAC,EAAE,GAAG,IAAI,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,UAAU,CAAC,CAAA;QAClD,IAAI,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,CAAC,CAAA;QAEpC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC;;;;;;KAMZ,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;KAK9B,CAAC,CAAA;QAEF,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oDAAoD,CAAC,CAAA;QACpF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,oCAAoC,CAAC,CAAA;QAEtE,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;YACnC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QAC7E,CAAC,EAAE,KAAK,CAAC,CAAA;IACX,CAAC;IAED,GAAG,CAAC,GAAW,EAAE,QAAgB,EAAE,MAAc;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,OAAO,GAAG,GAAG,GAAG,QAAQ,CAAA;QAE9B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAwC,CAAA;QAExE,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAA;IACpD,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAED,QAAQ;QACN,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAA;QAChC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAA;IACjB,CAAC;CACF;AAED,MAAM,UAAU,WAAW,CAAC,OAA4B;IACtD,OAAO,IAAI,WAAW,CAAC,OAAO,CAAC,CAAA;AACjC,CAAC"}
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@joint-ops/hitlimit",
3
- "version": "1.0.4",
4
- "description": "Fast rate limiting middleware for Express, NestJS & Node.js - API throttling, brute force protection, request limiting",
3
+ "version": "1.0.6",
4
+ "description": "Fast rate limiting middleware for Express, Fastify, NestJS & Node.js - API throttling, brute force protection, request limiting",
5
5
  "author": {
6
6
  "name": "Shayan M Hussain",
7
7
  "email": "shayanhussain48@gmail.com",
@@ -32,6 +32,8 @@
32
32
  "nest-throttler",
33
33
  "fastify",
34
34
  "fastify-rate-limit",
35
+ "fastify-plugin",
36
+ "fastify-middleware",
35
37
  "hono",
36
38
  "hono-rate-limit",
37
39
  "hono-middleware",
@@ -87,6 +89,14 @@
87
89
  "types": "./dist/node.d.ts",
88
90
  "import": "./dist/node.js"
89
91
  },
92
+ "./fastify": {
93
+ "types": "./dist/fastify.d.ts",
94
+ "import": "./dist/fastify.js"
95
+ },
96
+ "./stores/memory": {
97
+ "types": "./dist/stores/memory.d.ts",
98
+ "import": "./dist/stores/memory.js"
99
+ },
90
100
  "./stores/sqlite": {
91
101
  "types": "./dist/stores/sqlite.d.ts",
92
102
  "import": "./dist/stores/sqlite.js"
@@ -119,11 +129,13 @@
119
129
  "test:watch": "vitest"
120
130
  },
121
131
  "dependencies": {
122
- "@joint-ops/hitlimit-types": "1.0.4"
132
+ "@joint-ops/hitlimit-types": "1.0.6"
123
133
  },
124
134
  "peerDependencies": {
125
135
  "@nestjs/common": ">=8.0.0",
126
136
  "@nestjs/core": ">=8.0.0",
137
+ "fastify": ">=4.0.0",
138
+ "fastify-plugin": ">=4.0.0",
127
139
  "better-sqlite3": ">=9.0.0",
128
140
  "ioredis": ">=5.0.0",
129
141
  "pino": ">=8.0.0",
@@ -136,6 +148,12 @@
136
148
  "@nestjs/core": {
137
149
  "optional": true
138
150
  },
151
+ "fastify": {
152
+ "optional": true
153
+ },
154
+ "fastify-plugin": {
155
+ "optional": true
156
+ },
139
157
  "better-sqlite3": {
140
158
  "optional": true
141
159
  },
@@ -160,6 +178,8 @@
160
178
  "@types/supertest": "^6.0.0",
161
179
  "better-sqlite3": "^11.0.0",
162
180
  "express": "^4.18.0",
181
+ "fastify": "^5.7.4",
182
+ "fastify-plugin": "^5.1.0",
163
183
  "ioredis": "^5.3.0",
164
184
  "pino": "^10.3.0",
165
185
  "reflect-metadata": "^0.2.0",