web-gatekeeper-js 1.0.10 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web-gatekeeper-js",
3
- "version": "1.0.10",
3
+ "version": "1.2.0",
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,30 +1,31 @@
1
1
  export const slidingWindowScript = `
2
- local key = KEYS[1]
3
- local windowSize = tonumber(ARGV[1])
4
- local limit = tonumber(ARGV[2])
5
- local now = tonumber(ARGV[3])
6
- local ttl = tonumber(ARGV[4])
2
+ local key = KEYS[1]
3
+ local windowSize = math.floor(tonumber(ARGV[1]))
4
+ local limit = math.floor(tonumber(ARGV[2]))
5
+ local now = math.floor(tonumber(ARGV[3]))
6
+ local ttl = math.floor(tonumber(ARGV[4]))
7
7
 
8
8
  local windowStart = math.floor(now / windowSize) * windowSize
9
+ local resetAfter = math.ceil((windowSize - (now - windowStart)) / 1000)
9
10
 
10
- local storedWindowStart = tonumber(redis.call('HGET', key, 'windowStart') or 0)
11
- local currentCount = tonumber(redis.call('HGET', key, 'currentCount') or 0)
12
- local previousCount = tonumber(redis.call('HGET', key, 'previousCount') or 0)
13
-
14
- -- resetAfter calculated once, used in all returns
15
- local resetAfter = math.ceil((windowSize - (now - windowStart)) / 1000)
11
+ -- check BEFORE reading
12
+ local rawWindowStart = redis.call('HGET', key, 'windowStart')
16
13
 
17
14
  -- first time user
18
- if storedWindowStart == 0 then
15
+ if not rawWindowStart then
19
16
  redis.call('HSET', key,
20
17
  'windowStart', windowStart,
21
18
  'currentCount', 1,
22
19
  'previousCount', 0
23
20
  )
24
- redis.call('PEXPIRE', key, math.floor(ttl))
25
- return { 1, 1, limit - 1, resetAfter } -- ← 4 values
21
+ redis.call('EXPIRE', key, ttl)
22
+ return { 1, 1, limit - 1, resetAfter }
26
23
  end
27
24
 
25
+ local storedWindowStart = tonumber(rawWindowStart)
26
+ local currentCount = tonumber(redis.call('HGET', key, 'currentCount'))
27
+ local previousCount = tonumber(redis.call('HGET', key, 'previousCount'))
28
+
28
29
  local windowsPassed = math.floor((windowStart - storedWindowStart) / windowSize)
29
30
 
30
31
  local newPreviousCount = previousCount
@@ -42,12 +43,10 @@ export const slidingWindowScript = `
42
43
  local overlap = 1 - (elapsedTime / (windowSize / 1000))
43
44
  local effectiveCount = (overlap * newPreviousCount) + newCurrentCount
44
45
 
45
- -- blocked
46
46
  if effectiveCount >= limit then
47
- return { 0, newCurrentCount, 0, resetAfter } -- ← 4 values
47
+ return { 0, newCurrentCount, 0, resetAfter }
48
48
  end
49
49
 
50
- -- allowed
51
50
  newCurrentCount = newCurrentCount + 1
52
51
 
53
52
  redis.call('HSET', key,
@@ -55,8 +54,8 @@ export const slidingWindowScript = `
55
54
  'currentCount', newCurrentCount,
56
55
  'previousCount', newPreviousCount
57
56
  )
58
- redis.call('PEXPIRE', key, math.floor(ttl))
57
+ redis.call('EXPIRE', key, ttl)
59
58
 
60
59
  local remaining = math.floor(limit - effectiveCount - 1)
61
- return { 1, newCurrentCount, remaining, resetAfter } -- ← 4 values
60
+ return { 1, newCurrentCount, remaining, resetAfter }
62
61
  `
@@ -1,9 +1,9 @@
1
1
  export const throttlerScript = `
2
2
  local key = KEYS[1]
3
- local now = tonumber(ARGV[1])
3
+ local now = math.floor(tonumber(ARGV[1]))
4
4
  local refillRate = tonumber(ARGV[2])
5
- local maxWait = tonumber(ARGV[3])
6
- local ttl = tonumber(ARGV[4])
5
+ local maxWait = math.floor(tonumber(ARGV[3]))
6
+ local ttl = math.floor(tonumber(ARGV[4]))
7
7
 
8
8
  -- safely read nextAllowedTime
9
9
  local raw = redis.call('HGET', key, 'nextAllowedTime')
@@ -14,21 +14,21 @@ export const throttlerScript = `
14
14
 
15
15
  -- reject if queue too full
16
16
  if waitTime > maxWait then
17
- return { 0, waitTime }
17
+ return { 0, math.floor(waitTime) }
18
18
  end
19
19
 
20
20
  -- calculate new nextAllowedTime
21
21
  local newNextAllowedTime
22
22
  if waitTime <= 0 then
23
- newNextAllowedTime = now + (1000 / refillRate)
23
+ newNextAllowedTime = math.floor(now + (1000 / refillRate))
24
24
  else
25
- newNextAllowedTime = nextAllowedTime + (1000 / refillRate)
25
+ newNextAllowedTime = math.floor(nextAllowedTime + (1000 / refillRate))
26
26
  end
27
27
 
28
28
  -- save and set TTL
29
29
  redis.call('HSET', key, 'nextAllowedTime', newNextAllowedTime)
30
- redis.call('PEXPIRE', key, math.floor(ttl))
30
+ redis.call('EXPIRE', key, math.floor(ttl))
31
31
 
32
32
  -- return allowed + waitTime so Node.js knows how long to delay
33
- return { 1, math.max(waitTime, 0) }
33
+ return { 1, math.floor(math.max(waitTime, 0)) }
34
34
  `
@@ -1,46 +1,41 @@
1
1
  export const tokenBucketScript = `
2
2
  local key = KEYS[1]
3
- local now = tonumber(ARGV[1])
3
+ local now = math.floor(tonumber(ARGV[1]))
4
4
  local maxToken = tonumber(ARGV[2])
5
5
  local refillRate = tonumber(ARGV[3])
6
- local ttl = tonumber(ARGV[4])
6
+ local ttl = math.floor(tonumber(ARGV[4]))
7
7
 
8
- -- read stored state (same as your hGetAll)
9
- local lastReqTime = tonumber(redis.call('HGET', key, 'time') or now)
10
- local tokenLeft = tonumber(redis.call('HGET', key, 'tokenLeft') or maxToken)
8
+ local rawTime = redis.call('HGET', key, 'time')
9
+ local rawTokens = redis.call('HGET', key, 'tokenLeft')
11
10
 
12
- -- first time user (same as your empty check)
13
- if lastReqTime == nil or tokenLeft == nil then
11
+ -- first time user
12
+ if not rawTime then
14
13
  redis.call('HSET', key,
15
14
  'time', now,
16
15
  'tokenLeft', maxToken - 1
17
16
  )
18
- redis.call('PEXPIRE', key, math.floor(ttl))
19
- return { 1, maxToken - 1 }
17
+ redis.call('EXPIRE', key, ttl)
18
+ return { 1, maxToken - 1, 0 }
20
19
  end
21
20
 
22
- -- calculate refill (same as your timeElapsed and updatedToken)
23
- local timeElapsed = (now - lastReqTime) / 1000
24
- local updatedToken = math.min(tokenLeft + (timeElapsed * refillRate), maxToken)
21
+ local lastReqTime = tonumber(rawTime)
22
+ local tokenLeft = tonumber(rawTokens)
25
23
 
26
- -- block if not enough tokens (same as your updatedToken < 1)
27
- if updatedToken < 1 then
28
- redis.call('HSET', key,
29
- 'time', now,
30
- 'tokenLeft', updatedToken
31
- )
32
- redis.call('PEXPIRE', key, math.floor(ttl))
24
+ local timeElapsed = (now - lastReqTime) / 1000
25
+ local updatedToken = math.min(tokenLeft + (timeElapsed * refillRate), maxToken)
33
26
 
27
+ if updatedToken < 1 then
28
+ redis.call('HSET', key, 'time', now, 'tokenLeft', updatedToken)
29
+ redis.call('EXPIRE', key, ttl)
34
30
  local retryAfter = math.ceil((1 - updatedToken) / refillRate)
35
31
  return { 0, 0, retryAfter }
36
32
  end
37
33
 
38
- -- allow — consume 1 token and save (same as your hSet)
39
34
  redis.call('HSET', key,
40
35
  'time', now,
41
36
  'tokenLeft', updatedToken - 1
42
37
  )
43
- redis.call('PEXPIRE', key, math.floor(ttl))
38
+ redis.call('EXPIRE', key, ttl)
44
39
 
45
- return { 1, updatedToken - 1, 0 }
40
+ return { 1, math.floor(updatedToken - 1), 0 }
46
41
  `