Skip to content

Commit 597cc66

Browse files
committed
feature: add support for ioredis
1 parent d5ff640 commit 597cc66

File tree

5 files changed

+252
-1
lines changed

5 files changed

+252
-1
lines changed

README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,26 @@ const redisCacheHandler = createRedisHandler({
158158

159159
**Note:** Redis Cluster support is currently experimental and may have limitations or unexpected bugs. Use it with caution.
160160

161+
### Using ioredis
162+
163+
If you prefer using `ioredis` instead of `@redis/client`, you can use the `ioredisAdapter` helper.
164+
165+
`npm i ioredis`
166+
167+
```js
168+
import Redis from "ioredis";
169+
import createRedisHandler from "@fortedigital/nextjs-cache-handler/redis-strings";
170+
import { ioredisAdapter } from "@fortedigital/nextjs-cache-handler/helpers/ioredisAdapter";
171+
172+
const client = new Redis(process.env.REDIS_URL);
173+
const redisClient = ioredisAdapter(client);
174+
175+
const redisHandler = createRedisHandler({
176+
client: redisClient,
177+
keyPrefix: "my-app:",
178+
});
179+
```
180+
161181
---
162182

163183
### `local-lru`

packages/nextjs-cache-handler/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@
5050
"./helpers/withAbortSignalProxy": {
5151
"require": "./dist/helpers/withAbortSignalProxy.cjs",
5252
"import": "./dist/helpers/withAbortSignalProxy.js"
53+
},
54+
"./helpers/ioredisAdapter": {
55+
"require": "./dist/helpers/ioredisAdapter.cjs",
56+
"import": "./dist/helpers/ioredisAdapter.js"
5357
}
5458
},
5559
"typesVersions": {
@@ -106,6 +110,7 @@
106110
"eslint": "^8.57.1",
107111
"eslint-config-prettier": "^9.1.2",
108112
"globals": "^16.5.0",
113+
"ioredis": "^5.8.2",
109114
"jest": "^30.2.0",
110115
"prettier": "^3.7.4",
111116
"prettier-plugin-packagejson": "2.5.20",
@@ -118,8 +123,14 @@
118123
},
119124
"peerDependencies": {
120125
"@redis/client": ">= 5.5.6",
126+
"ioredis": ">= 5.0.0",
121127
"next": ">=15.2.4"
122128
},
129+
"peerDependenciesMeta": {
130+
"ioredis": {
131+
"optional": true
132+
}
133+
},
123134
"distTags": [
124135
"next15"
125136
],
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import type { RedisClientType } from "@redis/client";
2+
import type { Redis } from "ioredis";
3+
4+
/**
5+
* Adapter to make an ioredis client compatible with the interface expected by createRedisHandler.
6+
*
7+
* @param client - The ioredis client instance.
8+
* @returns A proxy that implements the subset of RedisClientType used by this library.
9+
*/
10+
export function ioredisAdapter(client: Redis): RedisClientType {
11+
return new Proxy(client, {
12+
get(target, prop, receiver) {
13+
if (prop === "isReady") {
14+
return target.status === "ready";
15+
}
16+
17+
if (prop === "hScan") {
18+
return async (
19+
key: string,
20+
cursor: string,
21+
options?: { COUNT?: number },
22+
) => {
23+
let result: [string, string[]];
24+
25+
if (options?.COUNT) {
26+
result = await target.hscan(key, cursor, "COUNT", options.COUNT);
27+
} else {
28+
result = await target.hscan(key, cursor);
29+
}
30+
31+
const [newCursor, items] = result;
32+
33+
const entries = [];
34+
for (let i = 0; i < items.length; i += 2) {
35+
entries.push({ field: items[i], value: items[i + 1] });
36+
}
37+
38+
return {
39+
cursor: newCursor,
40+
entries,
41+
};
42+
};
43+
}
44+
45+
if (prop === "hDel") {
46+
return async (key: string, fields: string | string[]) => {
47+
const args = Array.isArray(fields) ? fields : [fields];
48+
if (args.length === 0) return 0;
49+
return target.hdel(key, ...args);
50+
};
51+
}
52+
53+
if (prop === "unlink") {
54+
return async (keys: string | string[]) => {
55+
const args = Array.isArray(keys) ? keys : [keys];
56+
if (args.length === 0) return 0;
57+
return target.unlink(...args);
58+
};
59+
}
60+
61+
if (prop === "set") {
62+
return async (key: string, value: string, options?: any) => {
63+
const args: (string | number)[] = [key, value];
64+
if (options) {
65+
if (options.EXAT) {
66+
args.push("EXAT", options.EXAT);
67+
} else if (options.PXAT) {
68+
args.push("PXAT", options.PXAT);
69+
} else if (options.EX) {
70+
args.push("EX", options.EX);
71+
} else if (options.PX) {
72+
args.push("PX", options.PX);
73+
} else if (options.KEEPTTL) {
74+
args.push("KEEPTTL");
75+
}
76+
// Add other options if necessary
77+
}
78+
// Cast to a generic signature to avoid overload mismatch issues with dynamic args
79+
const setFn = target.set as unknown as (
80+
key: string,
81+
value: string | number,
82+
...args: (string | number)[]
83+
) => Promise<string | null>;
84+
85+
return setFn(
86+
args[0] as string,
87+
args[1] as string | number,
88+
...args.slice(2),
89+
);
90+
};
91+
}
92+
93+
if (prop === "hmGet") {
94+
return async (key: string, fields: string | string[]) => {
95+
const args = Array.isArray(fields) ? fields : [fields];
96+
if (args.length === 0) return [];
97+
return target.hmget(key, ...args);
98+
};
99+
}
100+
101+
// Handle camelCase to lowercase mapping for other methods
102+
if (typeof prop === "string") {
103+
// Special case for expireAt -> expireat
104+
if (prop === "expireAt") return target.expireat.bind(target);
105+
106+
// hSet -> hset
107+
if (prop === "hSet") return target.hset.bind(target);
108+
109+
// hExists -> hexists
110+
if (prop === "hExists") return target.hexists.bind(target);
111+
112+
// Default fallback to lowercase if exists
113+
const lowerProp = prop.toLowerCase();
114+
if (
115+
lowerProp in target &&
116+
typeof (target as any)[lowerProp] === "function"
117+
) {
118+
return (target as any)[lowerProp].bind(target);
119+
}
120+
}
121+
122+
return Reflect.get(target, prop, receiver);
123+
},
124+
}) as unknown as RedisClientType;
125+
}

packages/nextjs-cache-handler/tsup.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export const tsup = defineConfig({
99
"src/helpers/redisClusterAdapter.ts",
1010
"src/helpers/withAbortSignal.ts",
1111
"src/helpers/withAbortSignalProxy.ts",
12+
"src/helpers/ioredisAdapter.ts",
1213
],
1314
splitting: false,
1415
outDir: "dist",

0 commit comments

Comments
 (0)