Skip to content
Open
1 change: 1 addition & 0 deletions docs/2.drivers/redis.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
46 changes: 40 additions & 6 deletions src/drivers/redis.ts
Original file line number Diff line number Diff line change
@@ -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";

Expand Down Expand Up @@ -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<RedisOptions, Redis | Cluster> = (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";
}
Comment thread
vchomakov marked this conversation as resolved.

const driver: DriverFactory<RedisOptions, Redis | Cluster> = (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,
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
};
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;
};
Expand Down
18 changes: 18 additions & 0 deletions test/drivers/redis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
});
},
});
});