@joint-ops/hitlimit-bun 1.1.0 → 1.1.1

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,36 @@
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 - 6M+ ops/sec with memory-first design | Elysia, Hono & Bun.serve
9
+ > The fastest rate limiter for Bun - 7M+ ops/sec with memory-first design | Elysia, Hono & Bun.serve
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 6.10M ops/sec performance (15.7x faster than SQLite). Optional persistence with native bun:sqlite or Redis when you need it.
11
+ **hitlimit-bun** is a blazing-fast, Bun-native rate limiting library for Bun.serve, Elysia, and Hono applications. **Memory-first by default** with 7.29M ops/sec performance (14.6x faster than SQLite). Optional persistence with native bun:sqlite or Redis when you need it.
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
15
  ## ⚡ Why hitlimit-bun?
16
16
 
17
- **Memory-first for maximum performance.** 15.7x faster than SQLite in high-traffic scenarios.
17
+ **Memory-first for maximum performance.** 14.6x faster than SQLite.
18
18
 
19
19
  ```
20
20
  ┌─────────────────────────────────────────────────────────────────┐
21
21
  │ │
22
- │ Memory (v1.1+) ██████████████████████████████ 6.10M ops/s │
23
- │ SQLite (v1.0) █░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 386K ops/s │
22
+ │ Memory (v1.1+) ██████████████████████████████ 7.29M ops/s │
23
+ │ SQLite (v1.0) █░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 500K ops/s │
24
24
  │ │
25
- 15.7x performance improvement with memory default │
25
+ 14.6x performance improvement with memory default │
26
26
  │ │
27
27
  └─────────────────────────────────────────────────────────────────┘
28
28
  ```
29
29
 
30
- - **🚀 Memory-First** - 6.10M ops/sec by default (v1.1.0+), 15.7x faster than SQLite
30
+ - **🚀 Memory-First** - 7.29M ops/sec by default (v1.1.0+), 14.6x faster than SQLite
31
31
  - **Bun Native** - Built specifically for Bun's runtime, not a Node.js port
32
32
  - **Zero Config** - Works out of the box with sensible defaults
33
33
  - **Framework Support** - First-class Elysia and Hono integration
34
- - **Optional Persistence** - SQLite (386K ops/sec) or Redis (6.9K ops/sec) when needed
34
+ - **Optional Persistence** - SQLite (500K ops/sec) or Redis (6.6K ops/sec) when needed
35
35
  - **TypeScript First** - Full type safety and IntelliSense support
36
36
  - **Auto-Ban** - Automatically ban repeat offenders after threshold violations
37
37
  - **Shared Limits** - Group rate limits via groupId for teams/tenants
38
- - **Tiny Footprint** - ~23KB total, zero runtime dependencies
38
+ - **Tiny Footprint** - ~18KB core bundle, zero runtime dependencies
39
39
 
40
40
  ## Installation
41
41
 
@@ -249,7 +249,7 @@ hitlimit({
249
249
  retryAfter: true // Retry-After header on 429
250
250
  },
251
251
 
252
- // Store (default: bun:sqlite)
252
+ // Store (default: memory)
253
253
  store: sqliteStore({ path: './ratelimit.db' }),
254
254
 
255
255
  // Skip rate limiting
@@ -274,39 +274,30 @@ hitlimit({
274
274
 
275
275
  ## Storage Backends
276
276
 
277
- ### SQLite Store (Default)
277
+ ### Memory Store (Default)
278
278
 
279
- Uses Bun's native bun:sqlite for maximum performance. Default store.
279
+ Fastest option, used by default. No persistence.
280
280
 
281
281
  ```typescript
282
282
  import { hitlimit } from '@joint-ops/hitlimit-bun'
283
283
 
284
- // Default - uses bun:sqlite with in-memory database
284
+ // Default - uses memory store (no config needed)
285
285
  Bun.serve({
286
286
  fetch: hitlimit({}, handler)
287
287
  })
288
-
289
- // Custom path for persistence
290
- import { sqliteStore } from '@joint-ops/hitlimit-bun'
291
-
292
- Bun.serve({
293
- fetch: hitlimit({
294
- store: sqliteStore({ path: './ratelimit.db' })
295
- }, handler)
296
- })
297
288
  ```
298
289
 
299
- ### Memory Store
290
+ ### SQLite Store
300
291
 
301
- For simple use cases without persistence.
292
+ Uses Bun's native bun:sqlite for persistent rate limiting.
302
293
 
303
294
  ```typescript
304
295
  import { hitlimit } from '@joint-ops/hitlimit-bun'
305
- import { memoryStore } from '@joint-ops/hitlimit-bun/stores/memory'
296
+ import { sqliteStore } from '@joint-ops/hitlimit-bun'
306
297
 
307
298
  Bun.serve({
308
299
  fetch: hitlimit({
309
- store: memoryStore()
300
+ store: sqliteStore({ path: './ratelimit.db' })
310
301
  }, handler)
311
302
  })
312
303
  ```
@@ -365,9 +356,9 @@ hitlimit-bun is optimized for Bun's runtime with native performance:
365
356
 
366
357
  | Store | Operations/sec | vs Node.js |
367
358
  |-------|----------------|------------|
368
- | **Memory** | 7,210,000+ | +130% faster |
369
- | **bun:sqlite** | 520,000+ | **+10% faster** 🔥 |
370
- | **Redis** | 6,900+ | +3% faster |
359
+ | **Memory** | 7,290,000+ | +52% faster |
360
+ | **bun:sqlite** | 500,000+ | ~same |
361
+ | **Redis** | 6,600+ | ~same |
371
362
 
372
363
  ### HTTP Throughput
373
364
 
@@ -437,7 +428,7 @@ new Elysia()
437
428
  Node.js rate limiters like express-rate-limit use better-sqlite3 which relies on N-API bindings. In Bun, this adds overhead and loses the performance benefits of Bun's native runtime.
438
429
 
439
430
  **hitlimit-bun** is built specifically for Bun:
440
- - Uses native `bun:sqlite` (2.7x faster than better-sqlite3)
431
+ - Uses native `bun:sqlite` (no N-API overhead)
441
432
  - No FFI overhead or Node.js polyfills
442
433
  - First-class Elysia framework support
443
434
  - Optimized for Bun.serve's request handling
@@ -1 +1 @@
1
- {"version":3,"file":"elysia.d.ts","sourceRoot":"","sources":["../src/elysia.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAKhE,MAAM,WAAW,qBAAsB,SAAQ,eAAe,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;IAClF;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAQD,wBAAgB,QAAQ,CAAC,OAAO,GAAE,qBAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuD3D"}
1
+ {"version":3,"file":"elysia.d.ts","sourceRoot":"","sources":["../src/elysia.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,KAAK,EAAE,eAAe,EAAgE,MAAM,2BAA2B,CAAA;AAK9H,MAAM,WAAW,qBAAsB,SAAQ,eAAe,CAAC;IAAE,OAAO,EAAE,OAAO,CAAA;CAAE,CAAC;IAClF;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAkBD,wBAAgB,QAAQ,CAAC,OAAO,GAAE,qBAA0B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyG3D"}
package/dist/elysia.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // src/stores/memory.ts
3
3
  class MemoryStore {
4
+ isSync = true;
4
5
  hits = new Map;
5
6
  bans = new Map;
6
7
  violations = new Map;
@@ -280,6 +281,12 @@ var instanceCounter = 0;
280
281
  function getDefaultKey(_ctx) {
281
282
  return "unknown";
282
283
  }
284
+ function buildResponseBody(response, info) {
285
+ if (typeof response === "function") {
286
+ return response(info);
287
+ }
288
+ return { ...response, limit: info.limit, remaining: info.remaining, resetIn: info.resetIn };
289
+ }
283
290
  function hitlimit(options = {}) {
284
291
  const pluginName = options.name ?? `hitlimit-${instanceCounter++}`;
285
292
  if (options.sqlitePath && !options.store) {
@@ -287,6 +294,56 @@ function hitlimit(options = {}) {
287
294
  }
288
295
  const store = options.store ?? memoryStore();
289
296
  const config = resolveConfig(options, store, getDefaultKey);
297
+ const hasSkip = !!config.skip;
298
+ const hasTiers = !!(config.tier && config.tiers);
299
+ const hasBan = !!config.ban;
300
+ const hasGroup = !!config.group;
301
+ const standardHeaders = config.headers.standard;
302
+ const legacyHeaders = config.headers.legacy;
303
+ const retryAfterHeader = config.headers.retryAfter;
304
+ const limit = config.limit;
305
+ const windowMs = config.windowMs;
306
+ const responseConfig = config.response;
307
+ const isSyncStore = store.isSync === true;
308
+ const isSyncKey = !options.key;
309
+ if (!hasSkip && !hasTiers && !hasBan && !hasGroup && isSyncStore && isSyncKey) {
310
+ return new Elysia({ name: pluginName }).onBeforeHandle({ as: "scoped" }, ({ set }) => {
311
+ const key = "unknown";
312
+ const result = store.hit(key, windowMs, limit);
313
+ const allowed = result.count <= limit;
314
+ const remaining = Math.max(0, limit - result.count);
315
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
316
+ if (standardHeaders) {
317
+ set.headers["RateLimit-Limit"] = String(limit);
318
+ set.headers["RateLimit-Remaining"] = String(remaining);
319
+ set.headers["RateLimit-Reset"] = String(resetIn);
320
+ }
321
+ if (legacyHeaders) {
322
+ set.headers["X-RateLimit-Limit"] = String(limit);
323
+ set.headers["X-RateLimit-Remaining"] = String(remaining);
324
+ set.headers["X-RateLimit-Reset"] = String(Math.ceil(result.resetAt / 1000));
325
+ }
326
+ if (!allowed) {
327
+ if (retryAfterHeader) {
328
+ set.headers["Retry-After"] = String(resetIn);
329
+ }
330
+ const body = buildResponseBody(responseConfig, {
331
+ limit,
332
+ remaining: 0,
333
+ resetIn,
334
+ resetAt: result.resetAt,
335
+ key
336
+ });
337
+ set.status = 429;
338
+ const headers = { "Content-Type": "application/json" };
339
+ for (const [k, v] of Object.entries(set.headers)) {
340
+ if (v != null)
341
+ headers[k] = String(v);
342
+ }
343
+ return new Response(JSON.stringify(body), { status: 429, headers });
344
+ }
345
+ });
346
+ }
290
347
  return new Elysia({ name: pluginName }).onBeforeHandle({ as: "scoped" }, async ({ request, set }) => {
291
348
  const ctx = { request };
292
349
  if (config.skip) {
@@ -302,13 +359,12 @@ function hitlimit(options = {}) {
302
359
  });
303
360
  if (!result.allowed) {
304
361
  set.status = 429;
305
- return new Response(JSON.stringify(result.body), {
306
- status: 429,
307
- headers: {
308
- "Content-Type": "application/json",
309
- ...result.headers
310
- }
311
- });
362
+ const headers = { "Content-Type": "application/json" };
363
+ for (const [k, v] of Object.entries(result.headers)) {
364
+ if (v != null)
365
+ headers[k] = String(v);
366
+ }
367
+ return new Response(JSON.stringify(result.body), { status: 429, headers });
312
368
  }
313
369
  } catch (error) {
314
370
  const action = await config.onStoreError(error, ctx);
@@ -1 +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;AAWhE,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM;;kBAgC9D"}
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,EAAgE,MAAM,2BAA2B,CAAA;AAqB9H,wBAAgB,QAAQ,CAAC,OAAO,GAAE,eAAe,CAAC,OAAO,CAAM;;kBAkF9D"}
package/dist/hono.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // src/stores/memory.ts
3
3
  class MemoryStore {
4
+ isSync = true;
4
5
  hits = new Map;
5
6
  bans = new Map;
6
7
  violations = new Map;
@@ -279,9 +280,60 @@ async function checkLimit(config, req) {
279
280
  function getDefaultKey(c) {
280
281
  return c.req.header("x-forwarded-for")?.split(",")[0]?.trim() || c.req.header("x-real-ip") || "unknown";
281
282
  }
283
+ function buildResponseBody(response, info) {
284
+ if (typeof response === "function") {
285
+ return response(info);
286
+ }
287
+ return { ...response, limit: info.limit, remaining: info.remaining, resetIn: info.resetIn };
288
+ }
282
289
  function hitlimit(options = {}) {
283
290
  const store = options.store ?? memoryStore();
284
291
  const config = resolveConfig(options, store, getDefaultKey);
292
+ const hasSkip = !!config.skip;
293
+ const hasTiers = !!(config.tier && config.tiers);
294
+ const hasBan = !!config.ban;
295
+ const hasGroup = !!config.group;
296
+ const standardHeaders = config.headers.standard;
297
+ const legacyHeaders = config.headers.legacy;
298
+ const retryAfterHeader = config.headers.retryAfter;
299
+ const limit = config.limit;
300
+ const windowMs = config.windowMs;
301
+ const responseConfig = config.response;
302
+ const isSyncStore = store.isSync === true;
303
+ const isSyncKey = !options.key;
304
+ if (!hasSkip && !hasTiers && !hasBan && !hasGroup && isSyncStore && isSyncKey) {
305
+ return createMiddleware(async (c, next) => {
306
+ const key = c.req.header("x-forwarded-for")?.split(",")[0]?.trim() || c.req.header("x-real-ip") || "unknown";
307
+ const result = store.hit(key, windowMs, limit);
308
+ const allowed = result.count <= limit;
309
+ const remaining = Math.max(0, limit - result.count);
310
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
311
+ if (standardHeaders) {
312
+ c.header("RateLimit-Limit", String(limit));
313
+ c.header("RateLimit-Remaining", String(remaining));
314
+ c.header("RateLimit-Reset", String(resetIn));
315
+ }
316
+ if (legacyHeaders) {
317
+ c.header("X-RateLimit-Limit", String(limit));
318
+ c.header("X-RateLimit-Remaining", String(remaining));
319
+ c.header("X-RateLimit-Reset", String(Math.ceil(result.resetAt / 1000)));
320
+ }
321
+ if (!allowed) {
322
+ if (retryAfterHeader) {
323
+ c.header("Retry-After", String(resetIn));
324
+ }
325
+ const body = buildResponseBody(responseConfig, {
326
+ limit,
327
+ remaining: 0,
328
+ resetIn,
329
+ resetAt: result.resetAt,
330
+ key
331
+ });
332
+ return c.json(body, 429);
333
+ }
334
+ await next();
335
+ });
336
+ }
285
337
  return createMiddleware(async (c, next) => {
286
338
  if (config.skip) {
287
339
  const shouldSkip = await config.skip(c);
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAEhE,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;AAErB,MAAM,WAAW,kBAAmB,SAAQ,eAAe,CAAC,OAAO,CAAC;IAClE;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,KAAK,SAAS,GAAG;IAAE,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,CAAA;AAExE,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAMrF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,YAAY,GACpB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CA2JnE;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAClF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACzC;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU,CA0D3E"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,2BAA2B,CAAA;AAEhE,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;AAErB,MAAM,WAAW,kBAAmB,SAAQ,eAAe,CAAC,OAAO,CAAC;IAClE;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB;AAED,KAAK,SAAS,GAAG;IAAE,SAAS,CAAC,GAAG,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;CAAE,CAAA;AAExE,KAAK,YAAY,GAAG,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;AAMrF,wBAAgB,QAAQ,CACtB,OAAO,EAAE,kBAAkB,EAC3B,OAAO,EAAE,YAAY,GACpB,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,KAAK,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAsPnE;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAA;IAClF,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CACzC;AAED,wBAAgB,cAAc,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU,CA0D3E"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // src/stores/memory.ts
3
3
  class MemoryStore {
4
+ isSync = true;
4
5
  hits = new Map;
5
6
  bans = new Map;
6
7
  violations = new Map;
@@ -303,6 +304,80 @@ function hitlimit(options, handler) {
303
304
  const response = config.response;
304
305
  const customKey = options.key;
305
306
  const blockedBody = JSON.stringify(response);
307
+ const isSyncStore = store.isSync === true;
308
+ const isSyncKey = !customKey;
309
+ if (!hasSkip && !hasTiers && !hasBan && !hasGroup && isSyncStore && isSyncKey) {
310
+ return (req, server) => {
311
+ const ip = server.requestIP(req)?.address || "unknown";
312
+ const result = store.hit(ip, windowMs, limit);
313
+ const allowed = result.count <= limit;
314
+ if (!allowed) {
315
+ const headers = { "Content-Type": "application/json" };
316
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
317
+ if (standardHeaders) {
318
+ headers["RateLimit-Limit"] = String(limit);
319
+ headers["RateLimit-Remaining"] = "0";
320
+ headers["RateLimit-Reset"] = String(resetIn);
321
+ }
322
+ if (legacyHeaders) {
323
+ headers["X-RateLimit-Limit"] = String(limit);
324
+ headers["X-RateLimit-Remaining"] = "0";
325
+ headers["X-RateLimit-Reset"] = String(Math.ceil(result.resetAt / 1000));
326
+ }
327
+ if (retryAfterHeader) {
328
+ headers["Retry-After"] = String(resetIn);
329
+ }
330
+ return new Response(blockedBody, { status: 429, headers });
331
+ }
332
+ const res = handler(req, server);
333
+ if (res instanceof Promise) {
334
+ return res.then((response2) => {
335
+ if (standardHeaders || legacyHeaders) {
336
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
337
+ const remaining = Math.max(0, limit - result.count);
338
+ const newHeaders = new Headers(response2.headers);
339
+ if (standardHeaders) {
340
+ newHeaders.set("RateLimit-Limit", String(limit));
341
+ newHeaders.set("RateLimit-Remaining", String(remaining));
342
+ newHeaders.set("RateLimit-Reset", String(resetIn));
343
+ }
344
+ if (legacyHeaders) {
345
+ newHeaders.set("X-RateLimit-Limit", String(limit));
346
+ newHeaders.set("X-RateLimit-Remaining", String(remaining));
347
+ newHeaders.set("X-RateLimit-Reset", String(Math.ceil(result.resetAt / 1000)));
348
+ }
349
+ return new Response(response2.body, {
350
+ status: response2.status,
351
+ statusText: response2.statusText,
352
+ headers: newHeaders
353
+ });
354
+ }
355
+ return response2;
356
+ });
357
+ }
358
+ if (standardHeaders || legacyHeaders) {
359
+ const resetIn = Math.ceil((result.resetAt - Date.now()) / 1000);
360
+ const remaining = Math.max(0, limit - result.count);
361
+ const newHeaders = new Headers(res.headers);
362
+ if (standardHeaders) {
363
+ newHeaders.set("RateLimit-Limit", String(limit));
364
+ newHeaders.set("RateLimit-Remaining", String(remaining));
365
+ newHeaders.set("RateLimit-Reset", String(resetIn));
366
+ }
367
+ if (legacyHeaders) {
368
+ newHeaders.set("X-RateLimit-Limit", String(limit));
369
+ newHeaders.set("X-RateLimit-Remaining", String(remaining));
370
+ newHeaders.set("X-RateLimit-Reset", String(Math.ceil(result.resetAt / 1000)));
371
+ }
372
+ return new Response(res.body, {
373
+ status: res.status,
374
+ statusText: res.statusText,
375
+ headers: newHeaders
376
+ });
377
+ }
378
+ return res;
379
+ };
380
+ }
306
381
  if (!hasSkip && !hasTiers && !hasBan && !hasGroup) {
307
382
  return async (req, server) => {
308
383
  try {
@@ -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;AAgJ3E,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;AAiJ3E,wBAAgB,WAAW,IAAI,aAAa,CAE3C"}
@@ -1,6 +1,7 @@
1
1
  // @bun
2
2
  // src/stores/memory.ts
3
3
  class MemoryStore {
4
+ isSync = true;
4
5
  hits = new Map;
5
6
  bans = new Map;
6
7
  violations = new Map;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite.d.ts","sourceRoot":"","sources":["../../src/stores/sqlite.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAE3E,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA+GD,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":"AACA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AAE3E,MAAM,WAAW,kBAAkB;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAgHD,wBAAgB,WAAW,CAAC,OAAO,CAAC,EAAE,kBAAkB,GAAG,aAAa,CAEvE"}
@@ -3,6 +3,7 @@
3
3
  import { Database } from "bun:sqlite";
4
4
 
5
5
  class BunSqliteStore {
6
+ isSync = true;
6
7
  db;
7
8
  hitStmt;
8
9
  getStmt;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joint-ops/hitlimit-bun",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
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.0"
120
+ "@joint-ops/hitlimit-types": "1.1.1"
121
121
  },
122
122
  "peerDependencies": {
123
123
  "elysia": ">=1.0.0",