import { Store , IncrementResponse , Options as RateLimitConfiguration , } from "express-rate-limit" ; import { Options , SendCommandFn } from "./types.js" ; /**
 * A `Store` for the `express-rate-limit` package that stores hit counts in
 * Redis.
 */ class RedisStore implements Store { /**
     * The function used to send raw commands to Redis.
     */ sendCommand : SendCommandFn ; /**
     * The text to prepend to the key in Redis.
     */ prefix : string ; /**
     * Whether to reset the expiry for a particular key whenever its hit count
     * changes.
     */ resetExpiryOnChange : boolean ; /**
     * Stores the loaded SHA1 of the LUA script for executing the increment operations.
     */ loadedScriptSha1 : Promise < string > ; /**
     * The number of milliseconds to remember that user's requests.
     */ windowMs ! : number ; constructor param options { Options } constructor ( options ) { this . sendCommand = options . sendCommand ; this . prefix = options . prefix ?? "rl:" ; this . resetExpiryOnChange = options . resetExpiryOnChange ?? false ; this . loadedScriptSha1 = this . loadScript ( ) ; } async loadScript ( ) { const result = await this . sendCommand ( "SCRIPT" , "LOAD" , `
        local totalHits = redis.call("INCR", KEYS[1])
        local timeToExpire = redis.call("PTTL", KEYS[1])
        if timeToExpire <= 0 or ARGV[1] == "1"
        then
            redis.call("PEXPIRE", KEYS[1], tonumber(ARGV[2]))
            timeToExpire = tonumber(ARGV[2])
        end

        return { totalHits, timeToExpire }
    ` . replace ( /^\s+/gm , "" ) . trim ( ) ) ; if ( typeof result !== "string" ) { throw new TypeError ( "unexpected reply from redis client" ) ; } return result ; } param key { string } returns { string } prefixKey ( key ) { return `${ this . prefix }${ key }` ; } param options { RateLimitConfiguration } init ( options ) { this . windowMs = options . windowMs ; } param key { string } returns { IncrementResponse } async increment ( key ) { const results = await this . sendCommand ( "EVALSHA" , await this . loadedScriptSha1 , "1" , this . prefixKey ( key ) , this . resetExpiryOnChange ? "1" : "0" , this . windowMs . toString ( ) ) ; if ( ! Array . isArray ( results ) ) { throw new TypeError ( "Expected result to be array of values" ) ; } if ( results . length !== 2 ) { throw new Error ( `Expected 2 replies, got ${ results . length }` ) ; } const totalHits = results [ 0 ] ; if ( typeof totalHits !== "number" ) { throw new TypeError ( "Expected value to be a number" ) ; } const timeToExpire = results [ 1 ] ; if ( typeof timeToExpire !== "number" ) { throw new TypeError ( "Expected value to be a number" ) ; } const resetTime = new Date ( Date . now ( ) + timeToExpire ) ; return { totalHits , resetTime , } ; } param key { string } async decrement ( key ) { await this . sendCommand ( "DECR" , this . prefixKey ( key ) ) ; } param key { string } async resetKey ( key ) { await this . sendCommand ( "DEL" , this . prefixKey ( key ) ) ; } } export default RedisStore ;