From cdfaa6983a15313e04e1e58d0e6fff272e6df6c7 Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Sat, 29 Nov 2025 14:51:43 +0200 Subject: [PATCH 1/4] bind sendCommand to the current instance --- source/lib.ts | 4 ++-- test/store-test.ts | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/source/lib.ts b/source/lib.ts index 167ee01..a83f573 100644 --- a/source/lib.ts +++ b/source/lib.ts @@ -95,9 +95,9 @@ export class RedisStore implements Store { if ('sendCommand' in options && !('sendCommandCluster' in options)) { // Normal case: wrap the sendCommand function to convert from cluster to regular this.sendCommand = async ({ command }: SendCommandClusterDetails) => - options.sendCommand(...command) + options.sendCommand.bind(this)(...command) } else if (!('sendCommand' in options) && 'sendCommandCluster' in options) { - this.sendCommand = options.sendCommandCluster + this.sendCommand = options.sendCommandCluster.bind(this) } else { throw new Error( 'rate-limit-redis: Error: options must include either sendCommand or sendCommandCluster (but not both)', diff --git a/test/store-test.ts b/test/store-test.ts index e1b5958..bb4027e 100644 --- a/test/store-test.ts +++ b/test/store-test.ts @@ -345,4 +345,31 @@ describe('redis store test', () => { // With NEW script we expect a fresh window: hits=1 and ttl reset expect(result.totalHits).toEqual(1) }) + + it('should bind sendCommand to this', async () => { + // A custom sendCommand that verifies `this` is bound to the RedisStore instance + const customSendCommand = async function ( + this: RedisStore, + ...args: string[] + ) { + if (!(this instanceof RedisStore)) { + throw new TypeError('this is not bound to RedisStore instance') + } + + return sendCommand(...args) + } + + class CustomRedisStore extends RedisStore { + constructor() { + super({ + sendCommand: customSendCommand, + }) + } + } + const store = new CustomRedisStore() + store.init({ windowMs: 60 } as Options) + const key = 'test-store' + const { totalHits } = await store.increment(key) + expect(totalHits).toEqual(1) + }) }) From bccebdcb072aa244c44f3711139315c58ee679a4 Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Sun, 30 Nov 2025 08:36:47 +0200 Subject: [PATCH 2/4] bind sendCommand once --- source/lib.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/lib.ts b/source/lib.ts index a83f573..36b8f90 100644 --- a/source/lib.ts +++ b/source/lib.ts @@ -94,8 +94,9 @@ export class RedisStore implements Store { if ('sendCommand' in options && !('sendCommandCluster' in options)) { // Normal case: wrap the sendCommand function to convert from cluster to regular + const sendCommandFn = options.sendCommand.bind(this) this.sendCommand = async ({ command }: SendCommandClusterDetails) => - options.sendCommand.bind(this)(...command) + sendCommandFn(...command) } else if (!('sendCommand' in options) && 'sendCommandCluster' in options) { this.sendCommand = options.sendCommandCluster.bind(this) } else { From 0df5b661d507912bd3e78c8284236f7416b322b0 Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Sun, 30 Nov 2025 10:13:57 +0200 Subject: [PATCH 3/4] test sendCommandCluster binding --- test/store-test.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test/store-test.ts b/test/store-test.ts index bb4027e..b389e7d 100644 --- a/test/store-test.ts +++ b/test/store-test.ts @@ -8,6 +8,7 @@ import MockRedisClient from 'ioredis-mock' import DefaultExportRedisStore, { RedisStore, type RedisReply, + type SendCommandClusterDetails, } from '../source/index.js' // The mock redis client to use. @@ -372,4 +373,31 @@ describe('redis store test', () => { const { totalHits } = await store.increment(key) expect(totalHits).toEqual(1) }) + + it('should bind sendCommandCluster to this', async () => { + // A custom sendCommand that verifies `this` is bound to the RedisStore instance + const customSendCommandCluster = async function ( + this: RedisStore, + commandDetails: SendCommandClusterDetails, + ) { + if (!(this instanceof RedisStore)) { + throw new TypeError('this is not bound to RedisStore instance') + } + + return sendCommand(...commandDetails.command) + } + + class CustomRedisClusterStore extends RedisStore { + constructor() { + super({ + sendCommandCluster: customSendCommandCluster, + }) + } + } + const store = new CustomRedisClusterStore() + store.init({ windowMs: 60 } as Options) + const key = 'test-store' + const { totalHits } = await store.increment(key) + expect(totalHits).toEqual(1) + }) }) From f6294386b41b85f61f8136b315468fd25b3b74ca Mon Sep 17 00:00:00 2001 From: Kyriakos Barbounakis Date: Sun, 30 Nov 2025 17:32:57 +0200 Subject: [PATCH 4/4] update sendCommand binding test --- test/store-test.ts | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/test/store-test.ts b/test/store-test.ts index b389e7d..b4f512b 100644 --- a/test/store-test.ts +++ b/test/store-test.ts @@ -350,13 +350,18 @@ describe('redis store test', () => { it('should bind sendCommand to this', async () => { // A custom sendCommand that verifies `this` is bound to the RedisStore instance const customSendCommand = async function ( - this: RedisStore, + this: CustomRedisStore, ...args: string[] ) { - if (!(this instanceof RedisStore)) { + if (!(this instanceof CustomRedisStore)) { throw new TypeError('this is not bound to RedisStore instance') } + // Throw an error on DECR to test disableDecrement provided by the store + if (args[0] === 'DECR' && this.disableDecrement) { + throw new Error('Decrement not supported in this test') + } + return sendCommand(...args) } @@ -365,12 +370,19 @@ describe('redis store test', () => { super({ sendCommand: customSendCommand, }) + this.init({ windowMs: 60 } as Options) + } + + public get disableDecrement() { + return true } } const store = new CustomRedisStore() - store.init({ windowMs: 60 } as Options) const key = 'test-store' const { totalHits } = await store.increment(key) + await expect(store.decrement(key)).rejects.toThrow( + 'Decrement not supported in this test', + ) expect(totalHits).toEqual(1) }) @@ -387,14 +399,9 @@ describe('redis store test', () => { return sendCommand(...commandDetails.command) } - class CustomRedisClusterStore extends RedisStore { - constructor() { - super({ - sendCommandCluster: customSendCommandCluster, - }) - } - } - const store = new CustomRedisClusterStore() + const store = new RedisStore({ + sendCommandCluster: customSendCommandCluster, + }) store.init({ windowMs: 60 } as Options) const key = 'test-store' const { totalHits } = await store.increment(key)