Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions packages/cache-handler/src/data-cache/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,17 @@ function loadIoredis(type: string): typeof import("ioredis").default {
}

/**
* Create adapter for ioredis (lowercase methods) to match RedisClient interface (camelCase)
* Create adapter for ioredis (lowercase methods) to match RedisClient interface (camelCase).
* Translates node-redis-style SET options `{ EX: seconds }` to ioredis positional args.
*/
function createRedisAdapter(redis: import("ioredis").default): RedisClient {
return {
get: (key) => redis.get(key),
set: (key, value, exFlag?, ttl?) => {
if (exFlag === "EX" && typeof ttl === "number") {
return redis.set(key, value, "EX", ttl) as Promise<unknown>;
set: (key, value, ...args) => {
// node-redis style: set(key, value, { EX: seconds })
const opts = args[0] as Record<string, unknown> | undefined;
if (opts && typeof opts === "object" && typeof opts.EX === "number") {
return redis.set(key, value, "EX", opts.EX) as Promise<unknown>;
}
return redis.set(key, value) as Promise<unknown>;
},
Expand Down
17 changes: 9 additions & 8 deletions packages/cache-handler/src/data-cache/redis.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ class FakeRedis {
this.setCalls.push({ key, args });

let expireAt: number | undefined;
// ioredis style: set(key, value, "EX", seconds)
if (args[0] === "EX" && typeof args[1] === "number") {
expireAt = Date.now() + args[1] * 1000;
// node-redis style: set(key, value, { EX: seconds })
const opts = args[0] as { EX?: number } | undefined;
if (opts && typeof opts === "object" && typeof opts.EX === "number") {
expireAt = Date.now() + opts.EX * 1000;
}

this.store.set(key, { value, expireAt });
Expand Down Expand Up @@ -147,7 +148,7 @@ describe("RedisDataCacheHandler", () => {
expect(redis.setCalls).toHaveLength(1);
expect(redis.setCalls[0]).toMatchObject({
key: "nextjs:data-cache:cache-key",
args: ["EX", 120],
args: [{ EX: 120 }],
});

const result = await handler.get("cache-key", []);
Expand Down Expand Up @@ -231,7 +232,7 @@ describe("RedisDataCacheHandler", () => {
expect(redis.delCalls).toContainEqual(["nextjs:data-cache:invalidate-key"]);
});

test("sets TTL correctly with ioredis style args (fixes #16)", async () => {
test("sets TTL correctly with node-redis style options (fixes #16)", async () => {
vi.useFakeTimers();
vi.setSystemTime(BASE_TIME);

Expand All @@ -241,11 +242,11 @@ describe("RedisDataCacheHandler", () => {
const entry = createEntry("ttl-test", { expire: 60, revalidate: 30 });
await handler.set("ttl-key", Promise.resolve(entry));

// Verify the set call used ioredis style: "EX", seconds
// Verify the set call used node-redis style: { EX: seconds }
expect(redis.setCalls).toHaveLength(1);
const setCall = redis.setCalls[0];
expect(setCall.key).toBe("nextjs:data-cache:ttl-key");
expect(setCall.args).toEqual(["EX", 60]);
expect(setCall.args).toEqual([{ EX: 60 }]);
});

test("TTL causes entry to expire after specified time", async () => {
Expand Down Expand Up @@ -286,6 +287,6 @@ describe("RedisDataCacheHandler", () => {
await handler.set("default-ttl-key", Promise.resolve(entry));

expect(redis.setCalls).toHaveLength(1);
expect(redis.setCalls[0].args).toEqual(["EX", 3600]);
expect(redis.setCalls[0].args).toEqual([{ EX: 3600 }]);
});
});
8 changes: 4 additions & 4 deletions packages/cache-handler/src/data-cache/redis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { DataCacheEntry, DataCacheHandler } from "./types.js";

export interface RedisDataCacheHandlerOptions {
/**
* Redis client instance (ioredis compatible)
* Redis client instance (node-redis)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This change correctly specifies that a node-redis client instance is expected. However, other parts of the repository still refer to ioredis. To ensure consistency and prevent confusion for users of this package, it's important to update all related documentation and examples to reflect this switch.

Specifically:

  • docs/redis.md still contains instructions and examples for ioredis.
  • apps/redis-example/data-cache-handler.mjs also uses ioredis for its client setup.

These should be updated to use and refer to node-redis.

*/
redis: RedisClient;

Expand Down Expand Up @@ -41,8 +41,8 @@ export interface RedisDataCacheHandlerOptions {
}

/**
* Redis client interface (ioredis compatible)
* Uses ioredis-style SET with "EX" positional args: set(key, value, "EX", seconds)
* Redis client interface (node-redis compatible)
* Uses node-redis-style SET with options object: set(key, value, { EX: seconds })
*/
export interface RedisClient {
get(key: string): Promise<string | null>;
Expand Down Expand Up @@ -275,7 +275,7 @@ export function createRedisDataCacheHandler(
const ttl = entry.expire < 4294967294 ? entry.expire : defaultTTL;

// Store in Redis with TTL
await redis.set(key, JSON.stringify(serialized), "EX", Math.ceil(ttl));
await redis.set(key, JSON.stringify(serialized), { EX: Math.ceil(ttl) });

log?.("set", cacheKey, "done", { ttl });
} catch (error) {
Expand Down
Loading