Skip to content

Commit 42083a2

Browse files
committed
feat: add sliding window rate limiter
1 parent 6235e1a commit 42083a2

File tree

2 files changed

+43
-0
lines changed

2 files changed

+43
-0
lines changed

src/@types/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export interface IRateLimiterOptions {
2+
period: number;
3+
rate: number;
4+
}
5+
6+
export interface IRateLimiter {
7+
hit(key: string, step: number, options: IRateLimiterOptions): Promise<boolean>
8+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { IRateLimiter, IRateLimiterOptions } from '../@types/utils'
2+
import { createLogger } from '../factories/logger-factory'
3+
import { ICacheAdapter } from '../@types/adapters'
4+
5+
const debug = createLogger('sliding-window-rate-limiter')
6+
7+
export class SlidingWindowRateLimiter implements IRateLimiter {
8+
public constructor(
9+
private readonly cache: ICacheAdapter,
10+
) {}
11+
12+
public async hit(
13+
key: string,
14+
step: number,
15+
options: IRateLimiterOptions,
16+
): Promise<boolean> {
17+
const timestamp = Date.now()
18+
const { period } = options
19+
20+
debug('add %d hits on %s bucket', step, key)
21+
22+
const [,, entries] = await Promise.all([
23+
this.cache.removeRangeByScoreFromSortedSet(key, 0, timestamp - period),
24+
this.cache.addToSortedSet(key, { [`${timestamp}:${step}`]: timestamp.toString() }),
25+
this.cache.getRangeFromSortedSet(key, 0, -1),
26+
this.cache.setKeyExpiry(key, period),
27+
])
28+
29+
const hits = entries.reduce((acc, timestampAndStep) => acc + Number(timestampAndStep.split(':')[1]), 0)
30+
31+
debug('hit count on %s bucket: %d', key, hits)
32+
33+
return hits > options.rate
34+
}
35+
}

0 commit comments

Comments
 (0)