@joint-ops/hitlimit-bun 1.1.1 → 1.1.2
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 +10 -10
- package/dist/elysia.js +42 -54
- package/dist/hono.js +42 -54
- package/dist/index.js +42 -54
- package/dist/stores/memory.d.ts.map +1 -1
- package/dist/stores/memory.js +42 -54
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,32 +6,32 @@
|
|
|
6
6
|
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://bun.sh)
|
|
8
8
|
|
|
9
|
-
> The fastest rate limiter for Bun -
|
|
9
|
+
> The fastest rate limiter for Bun - 5M+ ops/sec under real-world load | 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
|
|
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.
|
|
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.**
|
|
17
|
+
**Memory-first for maximum performance.** ~15x faster than SQLite.
|
|
18
18
|
|
|
19
19
|
```
|
|
20
20
|
┌─────────────────────────────────────────────────────────────────┐
|
|
21
21
|
│ │
|
|
22
|
-
│ Memory (v1.1+) ██████████████████████████████
|
|
23
|
-
│ SQLite (v1.0) █░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
22
|
+
│ Memory (v1.1+) ██████████████████████████████ 5.62M ops/s │
|
|
23
|
+
│ SQLite (v1.0) █░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 383K ops/s │
|
|
24
24
|
│ │
|
|
25
|
-
│
|
|
25
|
+
│ ~15x performance improvement with memory default (10K IPs) │
|
|
26
26
|
│ │
|
|
27
27
|
└─────────────────────────────────────────────────────────────────┘
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
- **🚀 Memory-First** -
|
|
30
|
+
- **🚀 Memory-First** - 5.62M ops/sec under real-world load (v1.1+), ~15x 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 (
|
|
34
|
+
- **Optional Persistence** - SQLite (383K 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
|
|
@@ -356,8 +356,8 @@ hitlimit-bun is optimized for Bun's runtime with native performance:
|
|
|
356
356
|
|
|
357
357
|
| Store | Operations/sec | vs Node.js |
|
|
358
358
|
|-------|----------------|------------|
|
|
359
|
-
| **Memory** |
|
|
360
|
-
| **bun:sqlite** |
|
|
359
|
+
| **Memory** | 5,620,000+ | +68% faster |
|
|
360
|
+
| **bun:sqlite** | 383,000+ | ~same |
|
|
361
361
|
| **Redis** | 6,600+ | ~same |
|
|
362
362
|
|
|
363
363
|
### HTTP Throughput
|
package/dist/elysia.js
CHANGED
|
@@ -5,46 +5,62 @@ class MemoryStore {
|
|
|
5
5
|
hits = new Map;
|
|
6
6
|
bans = new Map;
|
|
7
7
|
violations = new Map;
|
|
8
|
+
_result = { count: 0, resetAt: 0 };
|
|
9
|
+
sweepInterval;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.sweepInterval = setInterval(() => {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
for (const [key, entry] of this.hits) {
|
|
14
|
+
if (now >= entry.resetAt)
|
|
15
|
+
this.hits.delete(key);
|
|
16
|
+
}
|
|
17
|
+
for (const [key, ban] of this.bans) {
|
|
18
|
+
if (now >= ban.expiresAt)
|
|
19
|
+
this.bans.delete(key);
|
|
20
|
+
}
|
|
21
|
+
for (const [key, violation] of this.violations) {
|
|
22
|
+
if (now >= violation.resetAt)
|
|
23
|
+
this.violations.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}, 1e4);
|
|
26
|
+
if (typeof this.sweepInterval.unref === "function") {
|
|
27
|
+
this.sweepInterval.unref();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
8
30
|
hit(key, windowMs, _limit) {
|
|
9
31
|
const entry = this.hits.get(key);
|
|
10
32
|
if (entry !== undefined) {
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
const now2 = Date.now();
|
|
34
|
+
if (now2 >= entry.resetAt) {
|
|
35
|
+
entry.count = 1;
|
|
36
|
+
entry.resetAt = now2 + windowMs;
|
|
37
|
+
} else {
|
|
38
|
+
entry.count++;
|
|
39
|
+
}
|
|
40
|
+
this._result.count = entry.count;
|
|
41
|
+
this._result.resetAt = entry.resetAt;
|
|
42
|
+
return this._result;
|
|
13
43
|
}
|
|
14
44
|
const now = Date.now();
|
|
15
45
|
const resetAt = now + windowMs;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
timeoutId.unref();
|
|
21
|
-
}
|
|
22
|
-
this.hits.set(key, { count: 1, resetAt, timeoutId });
|
|
23
|
-
return { count: 1, resetAt };
|
|
46
|
+
this.hits.set(key, { count: 1, resetAt });
|
|
47
|
+
this._result.count = 1;
|
|
48
|
+
this._result.resetAt = resetAt;
|
|
49
|
+
return this._result;
|
|
24
50
|
}
|
|
25
51
|
isBanned(key) {
|
|
26
52
|
const ban = this.bans.get(key);
|
|
27
53
|
if (!ban)
|
|
28
54
|
return false;
|
|
29
55
|
if (Date.now() >= ban.expiresAt) {
|
|
30
|
-
clearTimeout(ban.timeoutId);
|
|
31
56
|
this.bans.delete(key);
|
|
32
57
|
return false;
|
|
33
58
|
}
|
|
34
59
|
return true;
|
|
35
60
|
}
|
|
36
61
|
ban(key, durationMs) {
|
|
37
|
-
const existing = this.bans.get(key);
|
|
38
|
-
if (existing)
|
|
39
|
-
clearTimeout(existing.timeoutId);
|
|
40
62
|
const expiresAt = Date.now() + durationMs;
|
|
41
|
-
|
|
42
|
-
this.bans.delete(key);
|
|
43
|
-
}, durationMs);
|
|
44
|
-
if (typeof timeoutId.unref === "function") {
|
|
45
|
-
timeoutId.unref();
|
|
46
|
-
}
|
|
47
|
-
this.bans.set(key, { expiresAt, timeoutId });
|
|
63
|
+
this.bans.set(key, { expiresAt });
|
|
48
64
|
}
|
|
49
65
|
recordViolation(key, windowMs) {
|
|
50
66
|
const entry = this.violations.get(key);
|
|
@@ -52,47 +68,19 @@ class MemoryStore {
|
|
|
52
68
|
entry.count++;
|
|
53
69
|
return entry.count;
|
|
54
70
|
}
|
|
55
|
-
if (entry)
|
|
56
|
-
clearTimeout(entry.timeoutId);
|
|
57
71
|
const resetAt = Date.now() + windowMs;
|
|
58
|
-
|
|
59
|
-
this.violations.delete(key);
|
|
60
|
-
}, windowMs);
|
|
61
|
-
if (typeof timeoutId.unref === "function") {
|
|
62
|
-
timeoutId.unref();
|
|
63
|
-
}
|
|
64
|
-
this.violations.set(key, { count: 1, resetAt, timeoutId });
|
|
72
|
+
this.violations.set(key, { count: 1, resetAt });
|
|
65
73
|
return 1;
|
|
66
74
|
}
|
|
67
75
|
reset(key) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.hits.delete(key);
|
|
72
|
-
}
|
|
73
|
-
const ban = this.bans.get(key);
|
|
74
|
-
if (ban) {
|
|
75
|
-
clearTimeout(ban.timeoutId);
|
|
76
|
-
this.bans.delete(key);
|
|
77
|
-
}
|
|
78
|
-
const violation = this.violations.get(key);
|
|
79
|
-
if (violation) {
|
|
80
|
-
clearTimeout(violation.timeoutId);
|
|
81
|
-
this.violations.delete(key);
|
|
82
|
-
}
|
|
76
|
+
this.hits.delete(key);
|
|
77
|
+
this.bans.delete(key);
|
|
78
|
+
this.violations.delete(key);
|
|
83
79
|
}
|
|
84
80
|
shutdown() {
|
|
85
|
-
|
|
86
|
-
clearTimeout(entry.timeoutId);
|
|
87
|
-
}
|
|
81
|
+
clearInterval(this.sweepInterval);
|
|
88
82
|
this.hits.clear();
|
|
89
|
-
for (const [, entry] of this.bans) {
|
|
90
|
-
clearTimeout(entry.timeoutId);
|
|
91
|
-
}
|
|
92
83
|
this.bans.clear();
|
|
93
|
-
for (const [, entry] of this.violations) {
|
|
94
|
-
clearTimeout(entry.timeoutId);
|
|
95
|
-
}
|
|
96
84
|
this.violations.clear();
|
|
97
85
|
}
|
|
98
86
|
}
|
package/dist/hono.js
CHANGED
|
@@ -5,46 +5,62 @@ class MemoryStore {
|
|
|
5
5
|
hits = new Map;
|
|
6
6
|
bans = new Map;
|
|
7
7
|
violations = new Map;
|
|
8
|
+
_result = { count: 0, resetAt: 0 };
|
|
9
|
+
sweepInterval;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.sweepInterval = setInterval(() => {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
for (const [key, entry] of this.hits) {
|
|
14
|
+
if (now >= entry.resetAt)
|
|
15
|
+
this.hits.delete(key);
|
|
16
|
+
}
|
|
17
|
+
for (const [key, ban] of this.bans) {
|
|
18
|
+
if (now >= ban.expiresAt)
|
|
19
|
+
this.bans.delete(key);
|
|
20
|
+
}
|
|
21
|
+
for (const [key, violation] of this.violations) {
|
|
22
|
+
if (now >= violation.resetAt)
|
|
23
|
+
this.violations.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}, 1e4);
|
|
26
|
+
if (typeof this.sweepInterval.unref === "function") {
|
|
27
|
+
this.sweepInterval.unref();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
8
30
|
hit(key, windowMs, _limit) {
|
|
9
31
|
const entry = this.hits.get(key);
|
|
10
32
|
if (entry !== undefined) {
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
const now2 = Date.now();
|
|
34
|
+
if (now2 >= entry.resetAt) {
|
|
35
|
+
entry.count = 1;
|
|
36
|
+
entry.resetAt = now2 + windowMs;
|
|
37
|
+
} else {
|
|
38
|
+
entry.count++;
|
|
39
|
+
}
|
|
40
|
+
this._result.count = entry.count;
|
|
41
|
+
this._result.resetAt = entry.resetAt;
|
|
42
|
+
return this._result;
|
|
13
43
|
}
|
|
14
44
|
const now = Date.now();
|
|
15
45
|
const resetAt = now + windowMs;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
timeoutId.unref();
|
|
21
|
-
}
|
|
22
|
-
this.hits.set(key, { count: 1, resetAt, timeoutId });
|
|
23
|
-
return { count: 1, resetAt };
|
|
46
|
+
this.hits.set(key, { count: 1, resetAt });
|
|
47
|
+
this._result.count = 1;
|
|
48
|
+
this._result.resetAt = resetAt;
|
|
49
|
+
return this._result;
|
|
24
50
|
}
|
|
25
51
|
isBanned(key) {
|
|
26
52
|
const ban = this.bans.get(key);
|
|
27
53
|
if (!ban)
|
|
28
54
|
return false;
|
|
29
55
|
if (Date.now() >= ban.expiresAt) {
|
|
30
|
-
clearTimeout(ban.timeoutId);
|
|
31
56
|
this.bans.delete(key);
|
|
32
57
|
return false;
|
|
33
58
|
}
|
|
34
59
|
return true;
|
|
35
60
|
}
|
|
36
61
|
ban(key, durationMs) {
|
|
37
|
-
const existing = this.bans.get(key);
|
|
38
|
-
if (existing)
|
|
39
|
-
clearTimeout(existing.timeoutId);
|
|
40
62
|
const expiresAt = Date.now() + durationMs;
|
|
41
|
-
|
|
42
|
-
this.bans.delete(key);
|
|
43
|
-
}, durationMs);
|
|
44
|
-
if (typeof timeoutId.unref === "function") {
|
|
45
|
-
timeoutId.unref();
|
|
46
|
-
}
|
|
47
|
-
this.bans.set(key, { expiresAt, timeoutId });
|
|
63
|
+
this.bans.set(key, { expiresAt });
|
|
48
64
|
}
|
|
49
65
|
recordViolation(key, windowMs) {
|
|
50
66
|
const entry = this.violations.get(key);
|
|
@@ -52,47 +68,19 @@ class MemoryStore {
|
|
|
52
68
|
entry.count++;
|
|
53
69
|
return entry.count;
|
|
54
70
|
}
|
|
55
|
-
if (entry)
|
|
56
|
-
clearTimeout(entry.timeoutId);
|
|
57
71
|
const resetAt = Date.now() + windowMs;
|
|
58
|
-
|
|
59
|
-
this.violations.delete(key);
|
|
60
|
-
}, windowMs);
|
|
61
|
-
if (typeof timeoutId.unref === "function") {
|
|
62
|
-
timeoutId.unref();
|
|
63
|
-
}
|
|
64
|
-
this.violations.set(key, { count: 1, resetAt, timeoutId });
|
|
72
|
+
this.violations.set(key, { count: 1, resetAt });
|
|
65
73
|
return 1;
|
|
66
74
|
}
|
|
67
75
|
reset(key) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.hits.delete(key);
|
|
72
|
-
}
|
|
73
|
-
const ban = this.bans.get(key);
|
|
74
|
-
if (ban) {
|
|
75
|
-
clearTimeout(ban.timeoutId);
|
|
76
|
-
this.bans.delete(key);
|
|
77
|
-
}
|
|
78
|
-
const violation = this.violations.get(key);
|
|
79
|
-
if (violation) {
|
|
80
|
-
clearTimeout(violation.timeoutId);
|
|
81
|
-
this.violations.delete(key);
|
|
82
|
-
}
|
|
76
|
+
this.hits.delete(key);
|
|
77
|
+
this.bans.delete(key);
|
|
78
|
+
this.violations.delete(key);
|
|
83
79
|
}
|
|
84
80
|
shutdown() {
|
|
85
|
-
|
|
86
|
-
clearTimeout(entry.timeoutId);
|
|
87
|
-
}
|
|
81
|
+
clearInterval(this.sweepInterval);
|
|
88
82
|
this.hits.clear();
|
|
89
|
-
for (const [, entry] of this.bans) {
|
|
90
|
-
clearTimeout(entry.timeoutId);
|
|
91
|
-
}
|
|
92
83
|
this.bans.clear();
|
|
93
|
-
for (const [, entry] of this.violations) {
|
|
94
|
-
clearTimeout(entry.timeoutId);
|
|
95
|
-
}
|
|
96
84
|
this.violations.clear();
|
|
97
85
|
}
|
|
98
86
|
}
|
package/dist/index.js
CHANGED
|
@@ -5,46 +5,62 @@ class MemoryStore {
|
|
|
5
5
|
hits = new Map;
|
|
6
6
|
bans = new Map;
|
|
7
7
|
violations = new Map;
|
|
8
|
+
_result = { count: 0, resetAt: 0 };
|
|
9
|
+
sweepInterval;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.sweepInterval = setInterval(() => {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
for (const [key, entry] of this.hits) {
|
|
14
|
+
if (now >= entry.resetAt)
|
|
15
|
+
this.hits.delete(key);
|
|
16
|
+
}
|
|
17
|
+
for (const [key, ban] of this.bans) {
|
|
18
|
+
if (now >= ban.expiresAt)
|
|
19
|
+
this.bans.delete(key);
|
|
20
|
+
}
|
|
21
|
+
for (const [key, violation] of this.violations) {
|
|
22
|
+
if (now >= violation.resetAt)
|
|
23
|
+
this.violations.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}, 1e4);
|
|
26
|
+
if (typeof this.sweepInterval.unref === "function") {
|
|
27
|
+
this.sweepInterval.unref();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
8
30
|
hit(key, windowMs, _limit) {
|
|
9
31
|
const entry = this.hits.get(key);
|
|
10
32
|
if (entry !== undefined) {
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
const now2 = Date.now();
|
|
34
|
+
if (now2 >= entry.resetAt) {
|
|
35
|
+
entry.count = 1;
|
|
36
|
+
entry.resetAt = now2 + windowMs;
|
|
37
|
+
} else {
|
|
38
|
+
entry.count++;
|
|
39
|
+
}
|
|
40
|
+
this._result.count = entry.count;
|
|
41
|
+
this._result.resetAt = entry.resetAt;
|
|
42
|
+
return this._result;
|
|
13
43
|
}
|
|
14
44
|
const now = Date.now();
|
|
15
45
|
const resetAt = now + windowMs;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
timeoutId.unref();
|
|
21
|
-
}
|
|
22
|
-
this.hits.set(key, { count: 1, resetAt, timeoutId });
|
|
23
|
-
return { count: 1, resetAt };
|
|
46
|
+
this.hits.set(key, { count: 1, resetAt });
|
|
47
|
+
this._result.count = 1;
|
|
48
|
+
this._result.resetAt = resetAt;
|
|
49
|
+
return this._result;
|
|
24
50
|
}
|
|
25
51
|
isBanned(key) {
|
|
26
52
|
const ban = this.bans.get(key);
|
|
27
53
|
if (!ban)
|
|
28
54
|
return false;
|
|
29
55
|
if (Date.now() >= ban.expiresAt) {
|
|
30
|
-
clearTimeout(ban.timeoutId);
|
|
31
56
|
this.bans.delete(key);
|
|
32
57
|
return false;
|
|
33
58
|
}
|
|
34
59
|
return true;
|
|
35
60
|
}
|
|
36
61
|
ban(key, durationMs) {
|
|
37
|
-
const existing = this.bans.get(key);
|
|
38
|
-
if (existing)
|
|
39
|
-
clearTimeout(existing.timeoutId);
|
|
40
62
|
const expiresAt = Date.now() + durationMs;
|
|
41
|
-
|
|
42
|
-
this.bans.delete(key);
|
|
43
|
-
}, durationMs);
|
|
44
|
-
if (typeof timeoutId.unref === "function") {
|
|
45
|
-
timeoutId.unref();
|
|
46
|
-
}
|
|
47
|
-
this.bans.set(key, { expiresAt, timeoutId });
|
|
63
|
+
this.bans.set(key, { expiresAt });
|
|
48
64
|
}
|
|
49
65
|
recordViolation(key, windowMs) {
|
|
50
66
|
const entry = this.violations.get(key);
|
|
@@ -52,47 +68,19 @@ class MemoryStore {
|
|
|
52
68
|
entry.count++;
|
|
53
69
|
return entry.count;
|
|
54
70
|
}
|
|
55
|
-
if (entry)
|
|
56
|
-
clearTimeout(entry.timeoutId);
|
|
57
71
|
const resetAt = Date.now() + windowMs;
|
|
58
|
-
|
|
59
|
-
this.violations.delete(key);
|
|
60
|
-
}, windowMs);
|
|
61
|
-
if (typeof timeoutId.unref === "function") {
|
|
62
|
-
timeoutId.unref();
|
|
63
|
-
}
|
|
64
|
-
this.violations.set(key, { count: 1, resetAt, timeoutId });
|
|
72
|
+
this.violations.set(key, { count: 1, resetAt });
|
|
65
73
|
return 1;
|
|
66
74
|
}
|
|
67
75
|
reset(key) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.hits.delete(key);
|
|
72
|
-
}
|
|
73
|
-
const ban = this.bans.get(key);
|
|
74
|
-
if (ban) {
|
|
75
|
-
clearTimeout(ban.timeoutId);
|
|
76
|
-
this.bans.delete(key);
|
|
77
|
-
}
|
|
78
|
-
const violation = this.violations.get(key);
|
|
79
|
-
if (violation) {
|
|
80
|
-
clearTimeout(violation.timeoutId);
|
|
81
|
-
this.violations.delete(key);
|
|
82
|
-
}
|
|
76
|
+
this.hits.delete(key);
|
|
77
|
+
this.bans.delete(key);
|
|
78
|
+
this.violations.delete(key);
|
|
83
79
|
}
|
|
84
80
|
shutdown() {
|
|
85
|
-
|
|
86
|
-
clearTimeout(entry.timeoutId);
|
|
87
|
-
}
|
|
81
|
+
clearInterval(this.sweepInterval);
|
|
88
82
|
this.hits.clear();
|
|
89
|
-
for (const [, entry] of this.bans) {
|
|
90
|
-
clearTimeout(entry.timeoutId);
|
|
91
|
-
}
|
|
92
83
|
this.bans.clear();
|
|
93
|
-
for (const [, entry] of this.violations) {
|
|
94
|
-
clearTimeout(entry.timeoutId);
|
|
95
|
-
}
|
|
96
84
|
this.violations.clear();
|
|
97
85
|
}
|
|
98
86
|
}
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../../src/stores/memory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,2BAA2B,CAAA;AA2G3E,wBAAgB,WAAW,IAAI,aAAa,CAE3C"}
|
package/dist/stores/memory.js
CHANGED
|
@@ -5,46 +5,62 @@ class MemoryStore {
|
|
|
5
5
|
hits = new Map;
|
|
6
6
|
bans = new Map;
|
|
7
7
|
violations = new Map;
|
|
8
|
+
_result = { count: 0, resetAt: 0 };
|
|
9
|
+
sweepInterval;
|
|
10
|
+
constructor() {
|
|
11
|
+
this.sweepInterval = setInterval(() => {
|
|
12
|
+
const now = Date.now();
|
|
13
|
+
for (const [key, entry] of this.hits) {
|
|
14
|
+
if (now >= entry.resetAt)
|
|
15
|
+
this.hits.delete(key);
|
|
16
|
+
}
|
|
17
|
+
for (const [key, ban] of this.bans) {
|
|
18
|
+
if (now >= ban.expiresAt)
|
|
19
|
+
this.bans.delete(key);
|
|
20
|
+
}
|
|
21
|
+
for (const [key, violation] of this.violations) {
|
|
22
|
+
if (now >= violation.resetAt)
|
|
23
|
+
this.violations.delete(key);
|
|
24
|
+
}
|
|
25
|
+
}, 1e4);
|
|
26
|
+
if (typeof this.sweepInterval.unref === "function") {
|
|
27
|
+
this.sweepInterval.unref();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
8
30
|
hit(key, windowMs, _limit) {
|
|
9
31
|
const entry = this.hits.get(key);
|
|
10
32
|
if (entry !== undefined) {
|
|
11
|
-
|
|
12
|
-
|
|
33
|
+
const now2 = Date.now();
|
|
34
|
+
if (now2 >= entry.resetAt) {
|
|
35
|
+
entry.count = 1;
|
|
36
|
+
entry.resetAt = now2 + windowMs;
|
|
37
|
+
} else {
|
|
38
|
+
entry.count++;
|
|
39
|
+
}
|
|
40
|
+
this._result.count = entry.count;
|
|
41
|
+
this._result.resetAt = entry.resetAt;
|
|
42
|
+
return this._result;
|
|
13
43
|
}
|
|
14
44
|
const now = Date.now();
|
|
15
45
|
const resetAt = now + windowMs;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
timeoutId.unref();
|
|
21
|
-
}
|
|
22
|
-
this.hits.set(key, { count: 1, resetAt, timeoutId });
|
|
23
|
-
return { count: 1, resetAt };
|
|
46
|
+
this.hits.set(key, { count: 1, resetAt });
|
|
47
|
+
this._result.count = 1;
|
|
48
|
+
this._result.resetAt = resetAt;
|
|
49
|
+
return this._result;
|
|
24
50
|
}
|
|
25
51
|
isBanned(key) {
|
|
26
52
|
const ban = this.bans.get(key);
|
|
27
53
|
if (!ban)
|
|
28
54
|
return false;
|
|
29
55
|
if (Date.now() >= ban.expiresAt) {
|
|
30
|
-
clearTimeout(ban.timeoutId);
|
|
31
56
|
this.bans.delete(key);
|
|
32
57
|
return false;
|
|
33
58
|
}
|
|
34
59
|
return true;
|
|
35
60
|
}
|
|
36
61
|
ban(key, durationMs) {
|
|
37
|
-
const existing = this.bans.get(key);
|
|
38
|
-
if (existing)
|
|
39
|
-
clearTimeout(existing.timeoutId);
|
|
40
62
|
const expiresAt = Date.now() + durationMs;
|
|
41
|
-
|
|
42
|
-
this.bans.delete(key);
|
|
43
|
-
}, durationMs);
|
|
44
|
-
if (typeof timeoutId.unref === "function") {
|
|
45
|
-
timeoutId.unref();
|
|
46
|
-
}
|
|
47
|
-
this.bans.set(key, { expiresAt, timeoutId });
|
|
63
|
+
this.bans.set(key, { expiresAt });
|
|
48
64
|
}
|
|
49
65
|
recordViolation(key, windowMs) {
|
|
50
66
|
const entry = this.violations.get(key);
|
|
@@ -52,47 +68,19 @@ class MemoryStore {
|
|
|
52
68
|
entry.count++;
|
|
53
69
|
return entry.count;
|
|
54
70
|
}
|
|
55
|
-
if (entry)
|
|
56
|
-
clearTimeout(entry.timeoutId);
|
|
57
71
|
const resetAt = Date.now() + windowMs;
|
|
58
|
-
|
|
59
|
-
this.violations.delete(key);
|
|
60
|
-
}, windowMs);
|
|
61
|
-
if (typeof timeoutId.unref === "function") {
|
|
62
|
-
timeoutId.unref();
|
|
63
|
-
}
|
|
64
|
-
this.violations.set(key, { count: 1, resetAt, timeoutId });
|
|
72
|
+
this.violations.set(key, { count: 1, resetAt });
|
|
65
73
|
return 1;
|
|
66
74
|
}
|
|
67
75
|
reset(key) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
this.hits.delete(key);
|
|
72
|
-
}
|
|
73
|
-
const ban = this.bans.get(key);
|
|
74
|
-
if (ban) {
|
|
75
|
-
clearTimeout(ban.timeoutId);
|
|
76
|
-
this.bans.delete(key);
|
|
77
|
-
}
|
|
78
|
-
const violation = this.violations.get(key);
|
|
79
|
-
if (violation) {
|
|
80
|
-
clearTimeout(violation.timeoutId);
|
|
81
|
-
this.violations.delete(key);
|
|
82
|
-
}
|
|
76
|
+
this.hits.delete(key);
|
|
77
|
+
this.bans.delete(key);
|
|
78
|
+
this.violations.delete(key);
|
|
83
79
|
}
|
|
84
80
|
shutdown() {
|
|
85
|
-
|
|
86
|
-
clearTimeout(entry.timeoutId);
|
|
87
|
-
}
|
|
81
|
+
clearInterval(this.sweepInterval);
|
|
88
82
|
this.hits.clear();
|
|
89
|
-
for (const [, entry] of this.bans) {
|
|
90
|
-
clearTimeout(entry.timeoutId);
|
|
91
|
-
}
|
|
92
83
|
this.bans.clear();
|
|
93
|
-
for (const [, entry] of this.violations) {
|
|
94
|
-
clearTimeout(entry.timeoutId);
|
|
95
|
-
}
|
|
96
84
|
this.violations.clear();
|
|
97
85
|
}
|
|
98
86
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@joint-ops/hitlimit-bun",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
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.
|
|
120
|
+
"@joint-ops/hitlimit-types": "1.1.2"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
123
123
|
"elysia": ">=1.0.0",
|