@joint-ops/hitlimit-bun 1.1.2 → 1.1.3

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,36 +6,24 @@
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
7
  [![Bun](https://img.shields.io/badge/Bun-Native-black.svg)](https://bun.sh)
8
8
 
9
- > The fastest rate limiter for Bun - 5M+ ops/sec under real-world load | Elysia, Hono & Bun.serve
9
+ > The fastest rate limiter for Bun 5.6M+ ops/sec | Bun.serve, Elysia & Hono
10
10
 
11
- **hitlimit-bun** is a blazing-fast, Bun-native rate limiting library for Bun.serve, Elysia, and Hono applications. **Memory-first by default** with 5.62M ops/sec under real-world load (~15x faster than SQLite). Optional persistence with native bun:sqlite or Redis when you need it.
11
+ **hitlimit-bun** is a Bun-native rate limiting library. Memory-first with 5.62M ops/sec under real-world load. Atomic Redis Lua scripts for distributed systems. Native bun:sqlite for persistence. Zero runtime dependencies.
12
12
 
13
13
  **[Documentation](https://hitlimit.jointops.dev/docs/bun)** | **[GitHub](https://github.com/JointOps/hitlimit-monorepo)** | **[npm](https://www.npmjs.com/package/@joint-ops/hitlimit-bun)**
14
14
 
15
- ## Why hitlimit-bun?
15
+ ## Why hitlimit-bun?
16
16
 
17
- **Memory-first for maximum performance.** ~15x faster than SQLite.
18
-
19
- ```
20
- ┌─────────────────────────────────────────────────────────────────┐
21
- │ │
22
- │ Memory (v1.1+) ██████████████████████████████ 5.62M ops/s
23
- │ SQLite (v1.0) █░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 383K ops/s │
24
- │ │
25
- │ ~15x performance improvement with memory default (10K IPs) │
26
- │ │
27
- └─────────────────────────────────────────────────────────────────┘
28
- ```
29
-
30
- - **🚀 Memory-First** - 5.62M ops/sec under real-world load (v1.1+), ~15x faster than SQLite
31
- - **Bun Native** - Built specifically for Bun's runtime, not a Node.js port
32
- - **Zero Config** - Works out of the box with sensible defaults
33
- - **Framework Support** - First-class Elysia and Hono integration
34
- - **Optional Persistence** - SQLite (383K ops/sec) or Redis (6.6K ops/sec) when needed
35
- - **TypeScript First** - Full type safety and IntelliSense support
36
- - **Auto-Ban** - Automatically ban repeat offenders after threshold violations
37
- - **Shared Limits** - Group rate limits via groupId for teams/tenants
38
- - **Tiny Footprint** - ~18KB core bundle, zero runtime dependencies
17
+ - **5.62M ops/sec** under real-world load (10K IPs), ~15x faster than SQLite
18
+ - **Bun native** — built for Bun's runtime, not a Node.js port
19
+ - **3 frameworks** — Bun.serve, Elysia, Hono from one package
20
+ - **3 storage backends** — Memory, bun:sqlite, Redis (atomic Lua scripts)
21
+ - **Atomic Redis** — Single-roundtrip Lua scripts with EVALSHA caching
22
+ - **Zero runtime dependencies** — nothing extra to install
23
+ - **Human-readable windows** — `'1m'`, `'15m'`, `'1h'` instead of milliseconds
24
+ - **Tiered limits** — Free/Pro/Enterprise in 8 lines
25
+ - **Auto-ban** Ban repeat offenders after threshold violations
26
+ - **TypeScript native** — Full type safety and IntelliSense
39
27
 
40
28
  ## Installation
41
29
 
@@ -304,7 +292,7 @@ Bun.serve({
304
292
 
305
293
  ### Redis Store
306
294
 
307
- For distributed systems and multi-server deployments.
295
+ For distributed systems and multi-server deployments. Uses atomic Lua scripts — single-roundtrip with EVALSHA caching.
308
296
 
309
297
  ```typescript
310
298
  import { hitlimit } from '@joint-ops/hitlimit-bun'
@@ -352,13 +340,13 @@ Retry-After: 42
352
340
 
353
341
  hitlimit-bun is optimized for Bun's runtime with native performance:
354
342
 
355
- ### Store Benchmarks (Bun 1.3)
343
+ ### Store Benchmarks
356
344
 
357
345
  | Store | Operations/sec | vs Node.js |
358
346
  |-------|----------------|------------|
359
347
  | **Memory** | 5,620,000+ | +68% faster |
360
348
  | **bun:sqlite** | 383,000+ | ~same |
361
- | **Redis** | 6,600+ | ~same |
349
+ | **Redis** | 6,800+ | ~same |
362
350
 
363
351
  ### HTTP Throughput
364
352
 
@@ -367,7 +355,7 @@ hitlimit-bun is optimized for Bun's runtime with native performance:
367
355
  | **Bun.serve** | 105,000 req/s | 12% |
368
356
  | **Elysia** | 115,000 req/s | 11% |
369
357
 
370
- > **Note:** Benchmark results vary by hardware and environment. Run your own benchmarks to see results on your specific setup.
358
+ > **Note:** These are our benchmarks and we've done our best to keep them fair and reproducible. Results vary by hardware and environment — clone the repo and run them yourself. They're not set in stone if you find issues or have suggestions for improvement, please open an issue or PR.
371
359
 
372
360
  ### Why bun:sqlite is So Fast
373
361
 
@@ -421,7 +409,7 @@ new Elysia()
421
409
 
422
410
  ## Related Packages
423
411
 
424
- - [@joint-ops/hitlimit](https://www.npmjs.com/package/@joint-ops/hitlimit) - Node.js rate limiting for Express, NestJS
412
+ - [@joint-ops/hitlimit](https://www.npmjs.com/package/@joint-ops/hitlimit) - Node.js rate limiting for Express, Fastify, Hono, NestJS
425
413
 
426
414
  ## Why Not Use Node.js Rate Limiters in Bun?
427
415
 
@@ -429,14 +417,11 @@ Node.js rate limiters like express-rate-limit use better-sqlite3 which relies on
429
417
 
430
418
  **hitlimit-bun** is built specifically for Bun:
431
419
  - Uses native `bun:sqlite` (no N-API overhead)
420
+ - Atomic Redis Lua scripts for distributed deployments
432
421
  - No FFI overhead or Node.js polyfills
433
- - First-class Elysia framework support
434
- - Optimized for Bun.serve's request handling
422
+ - First-class Bun.serve, Elysia, and Hono support
435
423
 
436
424
  ## License
437
425
 
438
426
  MIT - Use freely in personal and commercial projects.
439
427
 
440
- ## Keywords
441
-
442
- bun rate limit, bun rate limiter, bun middleware, bun api, bun server, bun serve, bun framework, bun native, bun sqlite, elysia rate limit, elysia plugin, elysia middleware, elysia throttle, elysia framework, api rate limiting, throttle requests, request throttling, bun api protection, ddos protection, brute force protection, login protection, redis rate limit, high performance rate limit, fast rate limiter, sliding window, fixed window, rate-limiter-flexible bun, express-rate-limit bun, bun http, bun backend, bun rest api
@@ -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;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
+ {"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,CAgErB;AAGD,wBAAsB,UAAU,CAAC,QAAQ,EACvC,MAAM,EAAE,cAAc,CAAC,QAAQ,CAAC,EAChC,GAAG,EAAE,QAAQ,GACZ,OAAO,CAAC,cAAc,CAAC,CA2GzB"}
package/dist/elysia.js CHANGED
@@ -202,6 +202,49 @@ async function checkLimit(config, req) {
202
202
  tierName = await config.tier(req);
203
203
  }
204
204
  const { limit, windowMs } = resolveTier(config, tierName);
205
+ if (limit === Infinity) {
206
+ return {
207
+ allowed: true,
208
+ info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
209
+ headers: {},
210
+ body: {}
211
+ };
212
+ }
213
+ if (config.ban && config.store.hitWithBan) {
214
+ const r = await config.store.hitWithBan(key, windowMs, limit, config.ban.threshold, config.ban.durationMs);
215
+ if (r.banned && r.count === 0) {
216
+ const info3 = {
217
+ limit,
218
+ remaining: 0,
219
+ resetIn: Math.ceil((r.banExpiresAt - Date.now()) / 1000),
220
+ resetAt: r.banExpiresAt,
221
+ key,
222
+ tier: tierName,
223
+ banned: true,
224
+ banExpiresAt: r.banExpiresAt,
225
+ group: groupId
226
+ };
227
+ return { allowed: false, info: info3, headers: buildHeaders(info3, config.headers, false), body: buildBody(config.response, info3) };
228
+ }
229
+ const now2 = Date.now();
230
+ const allowed2 = r.count <= limit;
231
+ const info2 = {
232
+ limit,
233
+ remaining: Math.max(0, limit - r.count),
234
+ resetIn: Math.max(0, Math.ceil((r.resetAt - now2) / 1000)),
235
+ resetAt: r.resetAt,
236
+ key,
237
+ tier: tierName,
238
+ group: groupId
239
+ };
240
+ if (r.violations > 0)
241
+ info2.violations = r.violations;
242
+ if (r.banned) {
243
+ info2.banned = true;
244
+ info2.banExpiresAt = r.banExpiresAt;
245
+ }
246
+ return { allowed: allowed2, info: info2, headers: buildHeaders(info2, config.headers, allowed2), body: allowed2 ? {} : buildBody(config.response, info2) };
247
+ }
205
248
  if (config.ban && config.store.isBanned) {
206
249
  const banned = await config.store.isBanned(key);
207
250
  if (banned) {
@@ -225,14 +268,6 @@ async function checkLimit(config, req) {
225
268
  };
226
269
  }
227
270
  }
228
- if (limit === Infinity) {
229
- return {
230
- allowed: true,
231
- info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
232
- headers: {},
233
- body: {}
234
- };
235
- }
236
271
  const result = await config.store.hit(key, windowMs, limit);
237
272
  const now = Date.now();
238
273
  const resetIn = Math.max(0, Math.ceil((result.resetAt - now) / 1000));
package/dist/hono.js CHANGED
@@ -202,6 +202,49 @@ async function checkLimit(config, req) {
202
202
  tierName = await config.tier(req);
203
203
  }
204
204
  const { limit, windowMs } = resolveTier(config, tierName);
205
+ if (limit === Infinity) {
206
+ return {
207
+ allowed: true,
208
+ info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
209
+ headers: {},
210
+ body: {}
211
+ };
212
+ }
213
+ if (config.ban && config.store.hitWithBan) {
214
+ const r = await config.store.hitWithBan(key, windowMs, limit, config.ban.threshold, config.ban.durationMs);
215
+ if (r.banned && r.count === 0) {
216
+ const info3 = {
217
+ limit,
218
+ remaining: 0,
219
+ resetIn: Math.ceil((r.banExpiresAt - Date.now()) / 1000),
220
+ resetAt: r.banExpiresAt,
221
+ key,
222
+ tier: tierName,
223
+ banned: true,
224
+ banExpiresAt: r.banExpiresAt,
225
+ group: groupId
226
+ };
227
+ return { allowed: false, info: info3, headers: buildHeaders(info3, config.headers, false), body: buildBody(config.response, info3) };
228
+ }
229
+ const now2 = Date.now();
230
+ const allowed2 = r.count <= limit;
231
+ const info2 = {
232
+ limit,
233
+ remaining: Math.max(0, limit - r.count),
234
+ resetIn: Math.max(0, Math.ceil((r.resetAt - now2) / 1000)),
235
+ resetAt: r.resetAt,
236
+ key,
237
+ tier: tierName,
238
+ group: groupId
239
+ };
240
+ if (r.violations > 0)
241
+ info2.violations = r.violations;
242
+ if (r.banned) {
243
+ info2.banned = true;
244
+ info2.banExpiresAt = r.banExpiresAt;
245
+ }
246
+ return { allowed: allowed2, info: info2, headers: buildHeaders(info2, config.headers, allowed2), body: allowed2 ? {} : buildBody(config.response, info2) };
247
+ }
205
248
  if (config.ban && config.store.isBanned) {
206
249
  const banned = await config.store.isBanned(key);
207
250
  if (banned) {
@@ -225,14 +268,6 @@ async function checkLimit(config, req) {
225
268
  };
226
269
  }
227
270
  }
228
- if (limit === Infinity) {
229
- return {
230
- allowed: true,
231
- info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
232
- headers: {},
233
- body: {}
234
- };
235
- }
236
271
  const result = await config.store.hit(key, windowMs, limit);
237
272
  const now = Date.now();
238
273
  const resetIn = Math.max(0, Math.ceil((result.resetAt - now) / 1000));
package/dist/index.js CHANGED
@@ -199,6 +199,49 @@ async function checkLimit(config, req) {
199
199
  tierName = await config.tier(req);
200
200
  }
201
201
  const { limit, windowMs } = resolveTier(config, tierName);
202
+ if (limit === Infinity) {
203
+ return {
204
+ allowed: true,
205
+ info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
206
+ headers: {},
207
+ body: {}
208
+ };
209
+ }
210
+ if (config.ban && config.store.hitWithBan) {
211
+ const r = await config.store.hitWithBan(key, windowMs, limit, config.ban.threshold, config.ban.durationMs);
212
+ if (r.banned && r.count === 0) {
213
+ const info3 = {
214
+ limit,
215
+ remaining: 0,
216
+ resetIn: Math.ceil((r.banExpiresAt - Date.now()) / 1000),
217
+ resetAt: r.banExpiresAt,
218
+ key,
219
+ tier: tierName,
220
+ banned: true,
221
+ banExpiresAt: r.banExpiresAt,
222
+ group: groupId
223
+ };
224
+ return { allowed: false, info: info3, headers: buildHeaders(info3, config.headers, false), body: buildBody(config.response, info3) };
225
+ }
226
+ const now2 = Date.now();
227
+ const allowed2 = r.count <= limit;
228
+ const info2 = {
229
+ limit,
230
+ remaining: Math.max(0, limit - r.count),
231
+ resetIn: Math.max(0, Math.ceil((r.resetAt - now2) / 1000)),
232
+ resetAt: r.resetAt,
233
+ key,
234
+ tier: tierName,
235
+ group: groupId
236
+ };
237
+ if (r.violations > 0)
238
+ info2.violations = r.violations;
239
+ if (r.banned) {
240
+ info2.banned = true;
241
+ info2.banExpiresAt = r.banExpiresAt;
242
+ }
243
+ return { allowed: allowed2, info: info2, headers: buildHeaders(info2, config.headers, allowed2), body: allowed2 ? {} : buildBody(config.response, info2) };
244
+ }
202
245
  if (config.ban && config.store.isBanned) {
203
246
  const banned = await config.store.isBanned(key);
204
247
  if (banned) {
@@ -222,14 +265,6 @@ async function checkLimit(config, req) {
222
265
  };
223
266
  }
224
267
  }
225
- if (limit === Infinity) {
226
- return {
227
- allowed: true,
228
- info: { limit, remaining: Infinity, resetIn: 0, resetAt: 0, key, tier: tierName, group: groupId },
229
- headers: {},
230
- body: {}
231
- };
232
- }
233
268
  const result = await config.store.hit(key, windowMs, limit);
234
269
  const now = Date.now();
235
270
  const resetIn = Math.max(0, Math.ceil((result.resetAt - now) / 1000));
@@ -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;AAqED,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,EAAiC,MAAM,2BAA2B,CAAA;AAG7F,MAAM,WAAW,iBAAiB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AA6KD,wBAAgB,UAAU,CAAC,OAAO,CAAC,EAAE,iBAAiB,GAAG,aAAa,CAErE"}
@@ -1,30 +1,121 @@
1
1
  // @bun
2
2
  // src/stores/redis.ts
3
3
  import Redis from "ioredis";
4
+ var HIT_SCRIPT = `
5
+ local key = KEYS[1]
6
+ local windowMs = tonumber(ARGV[1])
7
+ local count = redis.call('INCR', key)
8
+ local ttl = redis.call('PTTL', key)
9
+ if ttl < 0 then
10
+ redis.call('PEXPIRE', key, windowMs)
11
+ ttl = windowMs
12
+ end
13
+ return {count, ttl}
14
+ `;
15
+ var HIT_WITH_BAN_SCRIPT = `
16
+ local hitKey = KEYS[1]
17
+ local banKey = KEYS[2]
18
+ local violationKey = KEYS[3]
19
+ local windowMs = tonumber(ARGV[1])
20
+ local limit = tonumber(ARGV[2])
21
+ local banThreshold = tonumber(ARGV[3])
22
+ local banDurationMs = tonumber(ARGV[4])
23
+
24
+ -- Check ban first
25
+ local banTTL = redis.call('PTTL', banKey)
26
+ if banTTL > 0 then
27
+ return {-1, banTTL, 1, 0}
28
+ end
29
+
30
+ -- Hit counter
31
+ local count = redis.call('INCR', hitKey)
32
+ local ttl = redis.call('PTTL', hitKey)
33
+ if ttl < 0 then
34
+ redis.call('PEXPIRE', hitKey, windowMs)
35
+ ttl = windowMs
36
+ end
37
+
38
+ -- Track violations if over limit
39
+ local banned = 0
40
+ local violations = 0
41
+ if count > limit then
42
+ violations = redis.call('INCR', violationKey)
43
+ local vTTL = redis.call('PTTL', violationKey)
44
+ if vTTL < 0 then
45
+ redis.call('PEXPIRE', violationKey, banDurationMs)
46
+ end
47
+ if violations >= banThreshold then
48
+ redis.call('SET', banKey, '1', 'PX', banDurationMs)
49
+ banned = 1
50
+ end
51
+ end
52
+ return {count, ttl, banned, violations}
53
+ `;
4
54
 
5
55
  class RedisStore {
6
56
  redis;
7
57
  prefix;
8
58
  banPrefix;
9
59
  violationPrefix;
60
+ hitSHA = null;
61
+ hitWithBanSHA = null;
10
62
  constructor(options = {}) {
11
63
  this.redis = new Redis(options.url ?? "redis://localhost:6379");
12
64
  this.prefix = options.keyPrefix ?? "hitlimit:";
13
65
  this.banPrefix = (options.keyPrefix ?? "hitlimit:") + "ban:";
14
66
  this.violationPrefix = (options.keyPrefix ?? "hitlimit:") + "violations:";
15
67
  }
68
+ async loadScripts() {
69
+ if (!this.hitSHA) {
70
+ this.hitSHA = await this.redis.script("LOAD", HIT_SCRIPT);
71
+ }
72
+ if (!this.hitWithBanSHA) {
73
+ this.hitWithBanSHA = await this.redis.script("LOAD", HIT_WITH_BAN_SCRIPT);
74
+ }
75
+ }
76
+ async evalScript(sha, script, keys, args) {
77
+ try {
78
+ return await this.redis.evalsha(sha, keys.length, ...keys, ...args);
79
+ } catch (err) {
80
+ if (err.message && err.message.includes("NOSCRIPT")) {
81
+ const newSHA = await this.redis.script("LOAD", script);
82
+ if (script === HIT_SCRIPT)
83
+ this.hitSHA = newSHA;
84
+ else if (script === HIT_WITH_BAN_SCRIPT)
85
+ this.hitWithBanSHA = newSHA;
86
+ return await this.redis.evalsha(newSHA, keys.length, ...keys, ...args);
87
+ }
88
+ throw err;
89
+ }
90
+ }
16
91
  async hit(key, windowMs, _limit) {
92
+ await this.loadScripts();
17
93
  const redisKey = this.prefix + key;
18
- const now = Date.now();
19
- const results = await this.redis.multi().incr(redisKey).pttl(redisKey).exec();
20
- const count = results[0][1];
21
- let ttl = results[1][1];
22
- if (ttl < 0) {
23
- await this.redis.pexpire(redisKey, windowMs);
24
- ttl = windowMs;
94
+ const result = await this.evalScript(this.hitSHA, HIT_SCRIPT, [redisKey], [windowMs]);
95
+ const count = result[0];
96
+ const ttl = result[1];
97
+ return { count, resetAt: Date.now() + ttl };
98
+ }
99
+ async hitWithBan(key, windowMs, limit, banThreshold, banDurationMs) {
100
+ await this.loadScripts();
101
+ const hitKey = this.prefix + key;
102
+ const banKey = this.banPrefix + key;
103
+ const violationKey = this.violationPrefix + key;
104
+ const result = await this.evalScript(this.hitWithBanSHA, HIT_WITH_BAN_SCRIPT, [hitKey, banKey, violationKey], [windowMs, limit, banThreshold, banDurationMs]);
105
+ const count = result[0];
106
+ const ttl = result[1];
107
+ const banned = result[2] === 1;
108
+ const violations = result[3];
109
+ if (count === -1) {
110
+ return { count: 0, resetAt: Date.now() + ttl, banned: true, violations: 0, banExpiresAt: Date.now() + ttl };
25
111
  }
26
- const resetAt = now + ttl;
27
- return { count, resetAt };
112
+ return {
113
+ count,
114
+ resetAt: Date.now() + ttl,
115
+ banned,
116
+ violations,
117
+ banExpiresAt: banned ? Date.now() + banDurationMs : 0
118
+ };
28
119
  }
29
120
  async isBanned(key) {
30
121
  const result = await this.redis.exists(this.banPrefix + key);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joint-ops/hitlimit-bun",
3
- "version": "1.1.2",
3
+ "version": "1.1.3",
4
4
  "description": "Ultra-fast Bun-native rate limiting - Memory-first with 6M+ ops/sec for Bun.serve, Elysia & Hono",
5
5
  "author": {
6
6
  "name": "Shayan M Hussain",
@@ -117,7 +117,7 @@
117
117
  "test:watch": "bun test --watch"
118
118
  },
119
119
  "dependencies": {
120
- "@joint-ops/hitlimit-types": "1.1.2"
120
+ "@joint-ops/hitlimit-types": "1.1.3"
121
121
  },
122
122
  "peerDependencies": {
123
123
  "elysia": ">=1.0.0",