From 16e76eb15c3ce803f74a27f49a6ce0ab078ae309 Mon Sep 17 00:00:00 2001 From: matt strayer Date: Tue, 1 Sep 2026 17:34:50 -0400 Subject: [PATCH] feat(redis): support ioredis v6 Widen the ioredis peer range to `^5.3.2 || ^6.0.0` so package managers stop installing a private second copy when another dependency needs v6. Type the `connection` option as a structural `RedisConnection` interface that lists the five commands the driver calls, instead of the ioredis `Redis` class. This accepts a v5 client, a v6 client, a `Redis<"resp3">` client and a `Cluster` instance, and no longer depends on the class shape. Closes #18 --- docs/content/docs/drivers.md | 4 ++-- packages/verrou/package.json | 2 +- packages/verrou/src/drivers/redis.ts | 6 ++---- packages/verrou/src/types/drivers.ts | 20 +++++++++++++++++--- packages/verrou/tests/drivers/redis.spec.ts | 7 +++++++ 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/drivers.md b/docs/content/docs/drivers.md index 2d6eebf..4152c8a 100644 --- a/docs/content/docs/drivers.md +++ b/docs/content/docs/drivers.md @@ -8,7 +8,7 @@ Verrou supports multiple drivers to store the locks. No matter which driver you ## Redis -You will need to install `ioredis` to use this driver. +You will need to install `ioredis` to use this driver. Both ioredis v5 and v6 are supported. Note that ioredis v6 requires Node.js 20 or newer. The Redis driver can be used with many different providers: @@ -55,7 +55,7 @@ const lockFactory = new LockFactory(store) | Option | Description | Default | |--------------|--------------------------|---------| -| `connection` | An instance of `ioredis` | N/A | +| `connection` | An ioredis `Redis` or `Cluster` instance | N/A | ### Implementation details diff --git a/packages/verrou/package.json b/packages/verrou/package.json index d2071ee..e2fb181 100644 --- a/packages/verrou/package.json +++ b/packages/verrou/package.json @@ -30,7 +30,7 @@ }, "peerDependencies": { "@aws-sdk/client-dynamodb": "^3.484.0", - "ioredis": "^5.3.2" + "ioredis": "^5.3.2 || ^6.0.0" }, "peerDependenciesMeta": { "@aws-sdk/client-dynamodb": { diff --git a/packages/verrou/src/drivers/redis.ts b/packages/verrou/src/drivers/redis.ts index e25027f..77ef110 100644 --- a/packages/verrou/src/drivers/redis.ts +++ b/packages/verrou/src/drivers/redis.ts @@ -1,7 +1,5 @@ -import type { Redis as IoRedis } from 'ioredis' - import { E_LOCK_NOT_OWNED } from '../errors.js' -import type { LockStore, RedisStoreOptions } from '../types/main.js' +import type { LockStore, RedisConnection, RedisStoreOptions } from '../types/main.js' /** * Create a new Redis store @@ -14,7 +12,7 @@ export class RedisStore implements LockStore { /** * IORedis connection instance */ - #connection: IoRedis + #connection: RedisConnection constructor(options: RedisStoreOptions) { this.#connection = options.connection diff --git a/packages/verrou/src/types/drivers.ts b/packages/verrou/src/types/drivers.ts index 233d882..70270d8 100644 --- a/packages/verrou/src/types/drivers.ts +++ b/packages/verrou/src/types/drivers.ts @@ -1,6 +1,5 @@ import type { Knex } from 'knex' import type { Kysely } from 'kysely' -import type { Redis as IoRedis } from 'ioredis' import type { DynamoDBClient } from '@aws-sdk/client-dynamodb' /** @@ -42,14 +41,29 @@ export interface KyselyOptions extends DatabaseOptions { connection: Kysely } +/** + * The subset of the ioredis API used by the Redis store. + * + * Typed structurally rather than against the `Redis` class so that + * any ioredis version (v5, v6), reply mapping (`legacy`, `resp3`) + * or `Cluster` instance is accepted. + */ +export interface RedisConnection { + set(key: string, value: string, px: 'PX', ms: number, nx: 'NX'): Promise<'OK' | null> + setnx(key: string, value: string): Promise + eval(script: string, numKeys: number, ...args: (string | number)[]): Promise + del(key: string): Promise + get(key: string): Promise +} + /** * Options for the Redis store */ export type RedisStoreOptions = { /** - * The Redis connection + * The Redis connection. Any ioredis `Redis` or `Cluster` instance */ - connection: IoRedis + connection: RedisConnection } /** diff --git a/packages/verrou/tests/drivers/redis.spec.ts b/packages/verrou/tests/drivers/redis.spec.ts index 081faf0..f35a9ae 100644 --- a/packages/verrou/tests/drivers/redis.spec.ts +++ b/packages/verrou/tests/drivers/redis.spec.ts @@ -1,7 +1,9 @@ import { Redis } from 'ioredis' import { test } from '@japa/runner' +import type { Cluster } from 'ioredis' import { RedisStore } from '../../src/drivers/redis.js' +import type { RedisConnection } from '../../src/types/main.js' import { REDIS_CREDENTIALS } from '../../test_helpers/index.js' import { registerStoreTestSuite } from '../../src/test_suite.js' @@ -21,6 +23,11 @@ test.group('Redis Driver', (group) => { createStore: () => new RedisStore({ connection: ioredis }), }) + test('accepts Redis and Cluster instances as connection', ({ expectTypeOf }) => { + expectTypeOf().toMatchTypeOf() + expectTypeOf().toMatchTypeOf() + }) + test('null ttl', async ({ assert }) => { const store = new RedisStore({ connection: ioredis }) await store.save('foo', 'bar', null)