web-gatekeeper-js 1.0.7 → 1.0.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-gatekeeper-js",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Redis based rate limiter and throttler using sliding window and token bucket algorithms",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
@@ -1,5 +1,5 @@
1
1
  import { slidingWindowScript } from "./scripts/slidingWindow.lua.js";
2
- import { tokenBucketScript } from "./scripts/tokenBucket.lua.js"
2
+ import { tokenBucketScript } from "./scripts/tokenBucket.lua.js";
3
3
  import { RedisStore } from "./store/RedisStore.js";
4
4
 
5
5
  export class RateLimiter {
@@ -13,8 +13,16 @@ export class RateLimiter {
13
13
  if (!redisClient) throw new Error("redisClient is required");
14
14
  if (!windowSize) throw new Error("windowSize is required");
15
15
  if (!limit) throw new Error("limit is required");
16
- if (!maxToken) throw new Error("maxToken is required");
17
- if (!refillRate) throw new Error("refillRate is required");
16
+ if (maxToken == null) throw new Error("maxToken is required");
17
+ if (refillRate == null) throw new Error("refillRate is required");
18
+
19
+ if (refillRate <= 0) {
20
+ throw new Error("refillRate must be greater than 0");
21
+ }
22
+
23
+ if (maxToken <= 0) {
24
+ throw new Error("maxToken must be greater than 0");
25
+ }
18
26
 
19
27
  this.#store = new RedisStore(redisClient);
20
28
  this.#windowSize = windowSize;
@@ -23,72 +31,86 @@ export class RateLimiter {
23
31
  this.#refillRate = refillRate;
24
32
  }
25
33
 
26
- async consume(identifier){
27
- const now = Date.now()
34
+ async consume(identifier) {
35
+ const now = Date.now();
28
36
 
29
- const slidingResult = await this.#runSlidingWindow(identifier, now)
30
- if(!slidingResult.allowed){
31
- return {
32
- allowed: false,
33
- reason: 'rate_limit_exceeded',
34
- resetAfter: slidingResult.resetAfter
35
- }
37
+ const slidingResult = await this.#runSlidingWindow(identifier, now);
38
+ if (!slidingResult.allowed) {
39
+ return {
40
+ allowed: false,
41
+ reason: "rate_limit_exceeded",
42
+ resetAfter: slidingResult.resetAfter,
43
+ };
36
44
  }
37
45
 
38
- const bucketResult = await this.#runTokenBucket(identifier, now)
39
- if(!bucketResult.allowed){
40
- return {
41
- allowed: false,
42
- reason: 'burst_limit_exceeded',
43
- retryAfter: bucketResult.retryAfter
44
- }
46
+ const bucketResult = await this.#runTokenBucket(identifier, now);
47
+ if (!bucketResult.allowed) {
48
+ return {
49
+ allowed: false,
50
+ reason: "burst_limit_exceeded",
51
+ retryAfter: bucketResult.retryAfter,
52
+ };
45
53
  }
46
54
 
47
55
  return {
48
- allowed : true,
49
- remaining : slidingResult.remaining,
50
- tokensRemaining : bucketResult.tokensLeft
51
- }
56
+ allowed: true,
57
+ remaining: slidingResult.remaining,
58
+ tokensRemaining: bucketResult.tokensLeft,
59
+ };
52
60
  }
53
61
 
54
- async #runSlidingWindow(identifier,now){
55
- const key = `rl:sliding:${identifier}`
56
- const ttl = Math.ceil((this.#windowSize / 1000) * 2)
62
+ async #runSlidingWindow(identifier, now) {
63
+ const key = `rl:sliding:${identifier}`;
64
+ const ttl = Math.ceil((this.#windowSize / 1000) * 2);
65
+ console.log(typeof ttl, ttl);
66
+ console.log({
67
+ now,
68
+ maxToken : this.#maxToken,
69
+ refillRate : this.#refillRate,
70
+ ttl,
71
+ });
57
72
 
58
73
  const result = await this.#store.evalScript(
59
- slidingWindowScript,
60
- key,
61
- this.#windowSize,
62
- this.#limit,
63
- now,
64
- ttl
65
- )
74
+ slidingWindowScript,
75
+ key,
76
+ this.#windowSize,
77
+ this.#limit,
78
+ now,
79
+ ttl,
80
+ );
66
81
 
67
82
  return {
68
- allowed : result[0] === 1,
69
- current : result[1],
70
- remaining : result[2],
71
- resetAfter: result[3]
72
- }
83
+ allowed: result[0] === 1,
84
+ current: result[1],
85
+ remaining: result[2],
86
+ resetAfter: result[3],
87
+ };
73
88
  }
74
89
 
75
90
  async #runTokenBucket(identifier, now) {
76
- const key = `rl:bucket:${identifier}`
77
- const ttl = Math.max(Math.ceil(this.#maxToken / this.#refillRate) * 2, 60)
78
-
91
+ const key = `rl:bucket:${identifier}`;
92
+ const ttl = Math.max(Math.ceil(this.#maxToken / this.#refillRate) * 2, 60);
93
+
94
+ console.log({
95
+ now,
96
+ maxToken : this.#maxToken,
97
+ refillRate : this.#refillRate,
98
+ ttl,
99
+ });
100
+
79
101
  const result = await this.#store.evalScript(
80
102
  tokenBucketScript,
81
103
  key,
82
- now,
83
- this.#maxToken,
84
- this.#refillRate,
85
- ttl
86
- )
104
+ now,
105
+ this.#maxToken,
106
+ this.#refillRate,
107
+ ttl,
108
+ );
87
109
 
88
110
  return {
89
- allowed : result[0] === 1,
90
- tokensLeft : result[1],
91
- retryAfter : result[2]
92
- }
93
- }
111
+ allowed: result[0] === 1,
112
+ tokensLeft: result[1],
113
+ retryAfter: result[2],
114
+ };
115
+ }
94
116
  }
@@ -21,7 +21,7 @@ export const slidingWindowScript = `
21
21
  'currentCount', 1,
22
22
  'previousCount', 0
23
23
  )
24
- redis.call('EXPIRE', key, math.floor(ttl))
24
+ redis.call('PEXPIRE', key, math.floor(ttl))
25
25
  return { 1, 1, limit - 1, resetAfter } -- ← 4 values
26
26
  end
27
27
 
@@ -55,7 +55,7 @@ export const slidingWindowScript = `
55
55
  'currentCount', newCurrentCount,
56
56
  'previousCount', newPreviousCount
57
57
  )
58
- redis.call('EXPIRE', key, math.floor(ttl))
58
+ redis.call('PEXPIRE', key, math.floor(ttl))
59
59
 
60
60
  local remaining = math.floor(limit - effectiveCount - 1)
61
61
  return { 1, newCurrentCount, remaining, resetAfter } -- ← 4 values
@@ -27,7 +27,7 @@ export const throttlerScript = `
27
27
 
28
28
  -- save and set TTL
29
29
  redis.call('HSET', key, 'nextAllowedTime', newNextAllowedTime)
30
- redis.call('EXPIRE', key, math.floor(ttl))
30
+ redis.call('PEXPIRE', key, math.floor(ttl))
31
31
 
32
32
  -- return allowed + waitTime so Node.js knows how long to delay
33
33
  return { 1, math.max(waitTime, 0) }
@@ -15,7 +15,7 @@ export const tokenBucketScript = `
15
15
  'time', now,
16
16
  'tokenLeft', maxToken - 1
17
17
  )
18
- redis.call('EXPIRE', key, math.floor(ttl))
18
+ redis.call('PEXPIRE', key, math.floor(ttl))
19
19
  return { 1, maxToken - 1 }
20
20
  end
21
21
 
@@ -29,7 +29,7 @@ export const tokenBucketScript = `
29
29
  'time', now,
30
30
  'tokenLeft', updatedToken
31
31
  )
32
- redis.call('EXPIRE', key, math.floor(ttl))
32
+ redis.call('PEXPIRE', key, math.floor(ttl))
33
33
 
34
34
  local retryAfter = math.ceil((1 - updatedToken) / refillRate)
35
35
  return { 0, 0, retryAfter }
@@ -40,7 +40,7 @@ export const tokenBucketScript = `
40
40
  'time', now,
41
41
  'tokenLeft', updatedToken - 1
42
42
  )
43
- redis.call('EXPIRE', key, math.floor(ttl))
43
+ redis.call('PEXPIRE', key, math.floor(ttl))
44
44
 
45
45
  return { 1, updatedToken - 1, 0 }
46
46
  `