Skip to content

Commit 8e00add

Browse files
authored
Bind sendCommand & sendCommandCluster to the current instance (#227)
This restores earlier behavior for users who subclass this library.
1 parent 7e8cb4b commit 8e00add

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

source/lib.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,10 +94,11 @@ export class RedisStore implements Store {
9494

9595
if ('sendCommand' in options && !('sendCommandCluster' in options)) {
9696
// Normal case: wrap the sendCommand function to convert from cluster to regular
97+
const sendCommandFn = options.sendCommand.bind(this)
9798
this.sendCommand = async ({ command }: SendCommandClusterDetails) =>
98-
options.sendCommand(...command)
99+
sendCommandFn(...command)
99100
} else if (!('sendCommand' in options) && 'sendCommandCluster' in options) {
100-
this.sendCommand = options.sendCommandCluster
101+
this.sendCommand = options.sendCommandCluster.bind(this)
101102
} else {
102103
throw new Error(
103104
'rate-limit-redis: Error: options must include either sendCommand or sendCommandCluster (but not both)',

test/store-test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import MockRedisClient from 'ioredis-mock'
88
import DefaultExportRedisStore, {
99
RedisStore,
1010
type RedisReply,
11+
type SendCommandClusterDetails,
1112
} from '../source/index.js'
1213

1314
// The mock redis client to use.
@@ -345,4 +346,65 @@ describe('redis store test', () => {
345346
// With NEW script we expect a fresh window: hits=1 and ttl reset
346347
expect(result.totalHits).toEqual(1)
347348
})
349+
350+
it('should bind sendCommand to this', async () => {
351+
// A custom sendCommand that verifies `this` is bound to the RedisStore instance
352+
const customSendCommand = async function (
353+
this: CustomRedisStore,
354+
...args: string[]
355+
) {
356+
if (!(this instanceof CustomRedisStore)) {
357+
throw new TypeError('this is not bound to RedisStore instance')
358+
}
359+
360+
// Throw an error on DECR to test disableDecrement provided by the store
361+
if (args[0] === 'DECR' && this.disableDecrement) {
362+
throw new Error('Decrement not supported in this test')
363+
}
364+
365+
return sendCommand(...args)
366+
}
367+
368+
class CustomRedisStore extends RedisStore {
369+
constructor() {
370+
super({
371+
sendCommand: customSendCommand,
372+
})
373+
this.init({ windowMs: 60 } as Options)
374+
}
375+
376+
public get disableDecrement() {
377+
return true
378+
}
379+
}
380+
const store = new CustomRedisStore()
381+
const key = 'test-store'
382+
const { totalHits } = await store.increment(key)
383+
await expect(store.decrement(key)).rejects.toThrow(
384+
'Decrement not supported in this test',
385+
)
386+
expect(totalHits).toEqual(1)
387+
})
388+
389+
it('should bind sendCommandCluster to this', async () => {
390+
// A custom sendCommand that verifies `this` is bound to the RedisStore instance
391+
const customSendCommandCluster = async function (
392+
this: RedisStore,
393+
commandDetails: SendCommandClusterDetails,
394+
) {
395+
if (!(this instanceof RedisStore)) {
396+
throw new TypeError('this is not bound to RedisStore instance')
397+
}
398+
399+
return sendCommand(...commandDetails.command)
400+
}
401+
402+
const store = new RedisStore({
403+
sendCommandCluster: customSendCommandCluster,
404+
})
405+
store.init({ windowMs: 60 } as Options)
406+
const key = 'test-store'
407+
const { totalHits } = await store.increment(key)
408+
expect(totalHits).toEqual(1)
409+
})
348410
})

0 commit comments

Comments
 (0)