diff --git a/docs/2.drivers/redis.md b/docs/2.drivers/redis.md index bb4b9bd8e..d8d57fdb5 100644 --- a/docs/2.drivers/redis.md +++ b/docs/2.drivers/redis.md @@ -72,6 +72,7 @@ const storage = createStorage({ - `ttl`: Default TTL for all items in **seconds**. - `scanCount`: How many keys to scan at once ([redis documentation](https://redis.io/docs/latest/commands/scan/#the-count-option)). - `preConnect`: Whether to initialize the redis instance immediately. Otherwise, it will be initialized on the first read/write call. Default: `false`. +- `clientInfoTag`: Tag to append to the library name in `CLIENT SETINFO` (e.g., `ioredis(unstorage_v2.0.0)`). This helps identify the higher-level library using ioredis. Default: `"unstorage_vX.X.X"` (with version) or `"unstorage"` (fallback). See [ioredis](https://github.com/redis/ioredis/blob/master/API.md#new-redisport-host-options) for all available options. diff --git a/src/drivers/redis.ts b/src/drivers/redis.ts index 8d00fa116..c90eb94e8 100644 --- a/src/drivers/redis.ts +++ b/src/drivers/redis.ts @@ -1,5 +1,6 @@ import { type DriverFactory, joinKeys } from "./utils/index.ts"; import { Cluster, Redis } from "ioredis"; +import pkg from "../../package.json" with { type: "json" }; import type { ClusterOptions, ClusterNode, RedisOptions as _RedisOptions } from "ioredis"; @@ -42,22 +43,55 @@ export interface RedisOptions extends _RedisOptions { * @default false */ preConnect?: boolean; + + /** + * Tag to append to the library name in CLIENT SETINFO (ioredis(tag)). + * This helps identify the higher-level library using ioredis. + * @link https://redis.io/docs/latest/commands/client-setinfo/ + * @default "unstorage_vX.X.X" (with version) or "unstorage" (fallback) + */ + clientInfoTag?: string; } const DRIVER_NAME = "redis"; -const driver: DriverFactory = (opts) => { +/** + * Returns the default client info tag for Redis CLIENT SETINFO. + * Uses the package version if available, otherwise falls back to "unstorage". + */ +function getDefaultClientInfoTag(): string { + if (pkg.version) { + return `unstorage_v${pkg.version}`; + } + return "unstorage"; +} + +const driver: DriverFactory = (opts: RedisOptions) => { let redisClient: Redis | Cluster; const getRedisClient = () => { if (redisClient) { return redisClient; } - if (opts.cluster) { - redisClient = new Redis.Cluster(opts.cluster, opts.clusterOptions); - } else if (opts.url) { - redisClient = new Redis(opts.url, opts); + + // Set default clientInfoTag to "unstorage_vX.X.X" if not explicitly set + const options = { + ...opts, + clientInfoTag: opts.clientInfoTag ?? getDefaultClientInfoTag(), + }; + + if (options.cluster) { + const clusterOptions = { + ...options.clusterOptions, + redisOptions: { + ...options.clusterOptions?.redisOptions, + clientInfoTag: options.clientInfoTag, + }, + }; + redisClient = new Redis.Cluster(options.cluster, clusterOptions); + } else if (options.url) { + redisClient = new Redis(options.url, options); } else { - redisClient = new Redis(opts); + redisClient = new Redis(options); } return redisClient; }; diff --git a/test/drivers/redis.test.ts b/test/drivers/redis.test.ts index 295f2c85d..94c364dff 100644 --- a/test/drivers/redis.test.ts +++ b/test/drivers/redis.test.ts @@ -39,6 +39,24 @@ describe("drivers: redis", () => { await client.disconnect(); }); + + it("sets default clientInfoTag with version", () => { + const instance = ctx.driver.getInstance?.(); + const tag = (instance?.options as any)?.clientInfoTag; + // Should be either "unstorage_vX.X.X" or "unstorage" (fallback) + expect(tag).toMatch(/^unstorage(_v[\d.]+.*)?$/); + }); + + it("allows custom clientInfoTag", () => { + const customDriver = redisDriver({ + base: "test:", + url: "ioredis://localhost:6379/0", + lazyConnect: false, + clientInfoTag: "my-custom-app", + }); + const instance = customDriver.getInstance?.(); + expect((instance?.options as any)?.clientInfoTag).toBe("my-custom-app"); + }); }, }); });