@joint-ops/hitlimit 1.0.5 → 1.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 CHANGED
@@ -14,14 +14,16 @@
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
- - **Framework Agnostic** - Express, NestJS, Fastify, native HTTP
20
+ - **Framework Agnostic** - Express, Fastify, Hono, NestJS, native HTTP
21
21
  - **Multiple Stores** - Memory, Redis, SQLite for distributed systems
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
 
@@ -122,6 +124,24 @@ app.get('/api', () => ({ status: 'ok' }))
122
124
  await app.listen({ port: 3000 })
123
125
  ```
124
126
 
127
+ ### Hono Rate Limiting
128
+
129
+ ```typescript
130
+ import { Hono } from 'hono'
131
+ import { serve } from '@hono/node-server'
132
+ import { hitlimit } from '@joint-ops/hitlimit/hono'
133
+
134
+ const app = new Hono()
135
+
136
+ app.use(hitlimit({
137
+ limit: 100,
138
+ window: '1m'
139
+ }))
140
+
141
+ app.get('/api', (c) => c.json({ status: 'ok' }))
142
+ serve({ fetch: app.fetch, port: 3000 })
143
+ ```
144
+
125
145
  ### NestJS Rate Limiting
126
146
 
127
147
  ```typescript
@@ -223,6 +243,37 @@ hitlimit({
223
243
  })
224
244
  ```
225
245
 
246
+ ### Auto-Ban Repeat Offenders
247
+
248
+ Automatically ban IPs that violate rate limits repeatedly.
249
+
250
+ ```javascript
251
+ hitlimit({
252
+ limit: 100,
253
+ window: '1m',
254
+ ban: {
255
+ threshold: 5, // Ban after 5 violations
256
+ duration: '15m' // Ban for 15 minutes
257
+ }
258
+ })
259
+ ```
260
+
261
+ When a client exceeds the rate limit 5 times, they'll be banned for 15 minutes. During the ban, all requests return 429 immediately.
262
+
263
+ ### Shared Rate Limits (Group)
264
+
265
+ Share rate limits across multiple clients using a group identifier.
266
+
267
+ ```javascript
268
+ hitlimit({
269
+ limit: 10000,
270
+ window: '1h',
271
+ group: (req) => req.user.teamId // Share limit across team
272
+ })
273
+ ```
274
+
275
+ All requests with the same team ID share the same rate limit counter. Perfect for team-based SaaS quotas.
276
+
226
277
  ### Skip Certain Requests
227
278
 
228
279
  Whitelist health checks, internal routes, or admin users.
@@ -237,6 +288,39 @@ hitlimit({
237
288
  })
238
289
  ```
239
290
 
291
+ ### Auto-Ban Repeat Offenders
292
+
293
+ Automatically ban clients that repeatedly exceed rate limits.
294
+
295
+ ```javascript
296
+ hitlimit({
297
+ limit: 10,
298
+ window: '1m',
299
+ ban: {
300
+ threshold: 5, // Ban after 5 violations
301
+ duration: '1h' // Ban lasts 1 hour
302
+ }
303
+ })
304
+ ```
305
+
306
+ Banned clients receive `X-RateLimit-Ban: true` header and `banned: true` in the response body.
307
+
308
+ ### Grouped / Shared Limits
309
+
310
+ Rate limit by organization, API key, or any shared identifier.
311
+
312
+ ```javascript
313
+ // Per-API-key rate limiting
314
+ hitlimit({
315
+ limit: 1000,
316
+ window: '1h',
317
+ group: (req) => req.headers['x-api-key'] || 'anonymous'
318
+ })
319
+
320
+ // Static group prefix
321
+ hitlimit({ group: 'api', limit: 100, window: '1m' })
322
+ ```
323
+
240
324
  ## Configuration Options
241
325
 
242
326
  ```javascript
@@ -276,7 +360,16 @@ hitlimit({
276
360
  skip: (req) => req.path === '/health',
277
361
 
278
362
  // Error handling
279
- onStoreError: (error, req) => 'allow' // or 'deny'
363
+ onStoreError: (error, req) => 'allow', // or 'deny'
364
+
365
+ // Ban repeat offenders
366
+ ban: {
367
+ threshold: 5, // violations before ban
368
+ duration: '1h' // ban duration
369
+ },
370
+
371
+ // Group/shared limits
372
+ group: (req) => req.headers['x-api-key'] || 'default'
280
373
  })
281
374
  ```
282
375
 
@@ -404,6 +497,18 @@ import { hitlimit } from '@joint-ops/hitlimit'
404
497
  app.use(hitlimit({ limit: 100, window: '1m' }))
405
498
  ```
406
499
 
500
+ ### From @fastify/rate-limit
501
+
502
+ ```typescript
503
+ // Before (@fastify/rate-limit)
504
+ import rateLimit from '@fastify/rate-limit'
505
+ await app.register(rateLimit, { max: 100, timeWindow: '1 minute' })
506
+
507
+ // After (hitlimit) - tiered limits, SQLite, multi-framework
508
+ import { hitlimit } from '@joint-ops/hitlimit/fastify'
509
+ await app.register(hitlimit, { limit: 100, window: '1m' })
510
+ ```
511
+
407
512
  ### From @nestjs/throttler
408
513
 
409
514
  ```typescript
@@ -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"}
package/dist/hono.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ import type { Context } from 'hono';
2
+ import type { HitLimitOptions } from '@joint-ops/hitlimit-types';
3
+ export declare function hitlimit(options?: HitLimitOptions<Context>): import("hono").MiddlewareHandler<any, string, {}, Response | (Response & import("hono").TypedResponse<{
4
+ [x: string]: any;
5
+ }, 429, "json">)>;
6
+ //# sourceMappingURL=hono.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.d.ts","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAahE,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM;;kBAmC9D"}
package/dist/hono.js ADDED
@@ -0,0 +1,44 @@
1
+ import { createMiddleware } from 'hono/factory';
2
+ import { resolveConfig } from './core/config.js';
3
+ import { checkLimit } from './core/limiter.js';
4
+ import { memoryStore } from './stores/memory.js';
5
+ function getDefaultKey(c) {
6
+ // Hono doesn't expose raw IP — it's runtime-dependent
7
+ // Use standard proxy headers as fallback
8
+ return c.req.header('x-forwarded-for')?.split(',')[0]?.trim()
9
+ || c.req.header('x-real-ip')
10
+ || 'unknown';
11
+ }
12
+ export function hitlimit(options = {}) {
13
+ const store = options.store ?? memoryStore();
14
+ const config = resolveConfig(options, store, getDefaultKey);
15
+ return createMiddleware(async (c, next) => {
16
+ // Skip check
17
+ if (config.skip) {
18
+ const shouldSkip = await config.skip(c);
19
+ if (shouldSkip) {
20
+ await next();
21
+ return;
22
+ }
23
+ }
24
+ try {
25
+ const result = await checkLimit(config, c);
26
+ // Set all rate limit headers
27
+ for (const [key, value] of Object.entries(result.headers)) {
28
+ c.header(key, value);
29
+ }
30
+ // Block if not allowed
31
+ if (!result.allowed) {
32
+ return c.json(result.body, 429);
33
+ }
34
+ }
35
+ catch (error) {
36
+ const action = await config.onStoreError(error, c);
37
+ if (action === 'deny') {
38
+ return c.json({ hitlimit: true, message: 'Rate limit error' }, 429);
39
+ }
40
+ }
41
+ await next();
42
+ });
43
+ }
44
+ //# sourceMappingURL=hono.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hono.js","sourceRoot":"","sources":["../src/hono.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAG/C,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,CAAU;IAC/B,sDAAsD;IACtD,yCAAyC;IACzC,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE;WACxD,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,CAAC;WACzB,SAAS,CAAA;AAChB,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,UAAoC,EAAE;IAC7D,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,gBAAgB,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACxC,aAAa;QACb,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACvC,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,IAAI,EAAE,CAAA;gBACZ,OAAM;YACR,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;YAE1C,6BAA6B;YAC7B,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBAC1D,CAAC,CAAC,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YACtB,CAAC;YAED,uBAAuB;YACvB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;YACjC,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,KAAc,EAAE,CAAC,CAAC,CAAA;YAC3D,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtB,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,kBAAkB,EAAE,EAAE,GAAG,CAAC,CAAA;YACrE,CAAC;QACH,CAAC;QAED,MAAM,IAAI,EAAE,CAAA;IACd,CAAC,CAAC,CAAA;AACJ,CAAC"}
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":"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"}
@@ -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,6 +1,6 @@
1
1
  {
2
2
  "name": "@joint-ops/hitlimit",
3
- "version": "1.0.5",
3
+ "version": "1.1.0",
4
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",
@@ -93,6 +93,10 @@
93
93
  "types": "./dist/fastify.d.ts",
94
94
  "import": "./dist/fastify.js"
95
95
  },
96
+ "./hono": {
97
+ "types": "./dist/hono.d.ts",
98
+ "import": "./dist/hono.js"
99
+ },
96
100
  "./stores/memory": {
97
101
  "types": "./dist/stores/memory.d.ts",
98
102
  "import": "./dist/stores/memory.js"
@@ -129,14 +133,15 @@
129
133
  "test:watch": "vitest"
130
134
  },
131
135
  "dependencies": {
132
- "@joint-ops/hitlimit-types": "1.0.5"
136
+ "@joint-ops/hitlimit-types": "1.1.0"
133
137
  },
134
138
  "peerDependencies": {
135
139
  "@nestjs/common": ">=8.0.0",
136
140
  "@nestjs/core": ">=8.0.0",
141
+ "better-sqlite3": ">=9.0.0",
137
142
  "fastify": ">=4.0.0",
138
143
  "fastify-plugin": ">=4.0.0",
139
- "better-sqlite3": ">=9.0.0",
144
+ "hono": ">=4.0.0",
140
145
  "ioredis": ">=5.0.0",
141
146
  "pino": ">=8.0.0",
142
147
  "winston": ">=3.0.0"
@@ -154,6 +159,9 @@
154
159
  "fastify-plugin": {
155
160
  "optional": true
156
161
  },
162
+ "hono": {
163
+ "optional": true
164
+ },
157
165
  "better-sqlite3": {
158
166
  "optional": true
159
167
  },
@@ -168,6 +176,7 @@
168
176
  }
169
177
  },
170
178
  "devDependencies": {
179
+ "@hono/node-server": "^1.19.9",
171
180
  "@nestjs/common": "^10.0.0",
172
181
  "@nestjs/core": "^10.0.0",
173
182
  "@nestjs/platform-express": "^10.0.0",
@@ -180,6 +189,7 @@
180
189
  "express": "^4.18.0",
181
190
  "fastify": "^5.7.4",
182
191
  "fastify-plugin": "^5.1.0",
192
+ "hono": "^4.11.9",
183
193
  "ioredis": "^5.3.0",
184
194
  "pino": "^10.3.0",
185
195
  "reflect-metadata": "^0.2.0",