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