-
Notifications
You must be signed in to change notification settings - Fork 4
feat: add support for redis client #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,9 +14,12 @@ import type { | |
| export interface RedisCacheHandlerOptions extends CacheHandlerOptions { | ||
| /** | ||
| * Redis connection options (ioredis) | ||
| * Can be a URL string or RedisOptions object | ||
| * Can be: | ||
| * - A Redis client instance (ioredis) | ||
| * - A URL string | ||
| * - A RedisOptions object | ||
| */ | ||
| redis?: string | RedisOptions; | ||
| redis?: Redis | string | RedisOptions; | ||
|
|
||
| /** | ||
| * Key prefix for all cache entries | ||
|
|
@@ -58,6 +61,7 @@ export interface RedisCacheHandlerOptions extends CacheHandlerOptions { | |
| * // In cache-handler.mjs or data-cache-handler.mjs | ||
| * import { RedisCacheHandler } from "@mrjasonroy/cache-components-cache-handler/handlers/redis"; | ||
| * | ||
| * // Option 1: Pass a Redis URL or config | ||
| * export default class NextCacheHandler extends RedisCacheHandler { | ||
| * constructor(options) { | ||
| * super({ | ||
|
|
@@ -68,6 +72,26 @@ export interface RedisCacheHandlerOptions extends CacheHandlerOptions { | |
| * }); | ||
| * } | ||
| * } | ||
| * | ||
| * // Option 2: Pass an existing Redis client instance | ||
| * import Redis from "ioredis"; | ||
| * | ||
| * const redisClient = new Redis({ | ||
| * host: "localhost", | ||
| * port: 6379, | ||
| * // ... other options | ||
| * }); | ||
| * | ||
| * export default class NextCacheHandler extends RedisCacheHandler { | ||
| * constructor(options) { | ||
| * super({ | ||
| * ...options, | ||
| * redis: redisClient, // Pass existing client | ||
| * keyPrefix: "nextjs:cache:", | ||
| * defaultTTL: 3600 | ||
| * }); | ||
| * } | ||
| * } | ||
| * ``` | ||
| */ | ||
| export class RedisCacheHandler implements CacheHandler { | ||
|
|
@@ -78,24 +102,39 @@ export class RedisCacheHandler implements CacheHandler { | |
| private readonly tagPrefix: string; | ||
| private readonly defaultTTL?: number; | ||
| private readonly debug: boolean; | ||
| private didCreateClient = false; | ||
|
|
||
| constructor(options: RedisCacheHandlerOptions = {}) { | ||
| // Initialize Redis connection | ||
| if (typeof options.redis === "string") { | ||
| // Initialize Redis connection - detect existing client via duck typing | ||
| // (instanceof can break across package versions or bundler setups) | ||
| if ( | ||
| options.redis && | ||
| typeof options.redis === "object" && | ||
| "get" in options.redis && | ||
| "set" in options.redis && | ||
| "del" in options.redis && | ||
| typeof (options.redis as Redis).get === "function" | ||
| ) { | ||
| this.redis = options.redis as Redis; | ||
| } else if (typeof options.redis === "string") { | ||
| this.redis = new Redis(options.redis); | ||
| this.didCreateClient = true; | ||
| } else { | ||
| this.redis = new Redis(options.redis || {}); | ||
| this.redis = new Redis((options.redis as RedisOptions) || {}); | ||
| this.didCreateClient = true; | ||
| } | ||
|
|
||
| this.keyPrefix = options.keyPrefix ?? "nextjs:cache:"; | ||
| this.tagPrefix = options.tagPrefix ?? "nextjs:tags:"; | ||
| this.defaultTTL = options.defaultTTL; | ||
| this.debug = options.debug ?? false; | ||
|
|
||
| // Handle Redis connection errors | ||
| this.redis.on("error", (err) => { | ||
| console.error("[RedisCacheHandler] Redis connection error:", err); | ||
| }); | ||
| // Only attach error listener for clients we created | ||
| if (this.didCreateClient) { | ||
| this.redis.on("error", (err) => { | ||
| console.error("[RedisCacheHandler] Redis connection error:", err); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Foot-gun: if the consumer also forgot to add an error listener, Node.js will crash. Multiple if (this.redis.listenerCount('error') === 0) {
this.redis.on('error', (err) => {
console.error('[RedisCacheHandler] Redis connection error:', err);
});
}At minimum, the JSDoc for the shared-client option should document that the caller is responsible for error handling. |
||
| }); | ||
| } | ||
|
|
||
| if (this.debug) { | ||
| console.log("[RedisCacheHandler] Initialized", { | ||
|
|
@@ -289,11 +328,17 @@ export class RedisCacheHandler implements CacheHandler { | |
| /** | ||
| * Close the Redis connection | ||
| * Call this when shutting down your application | ||
| * Note: Only closes connections created by this handler, not shared clients | ||
| */ | ||
| async close(): Promise<void> { | ||
| try { | ||
| await this.redis.quit(); | ||
| this.log("Connection closed"); | ||
| // Only close if we created the client | ||
| if (this.didCreateClient) { | ||
| await this.redis.quit(); | ||
| this.log("Connection closed"); | ||
| } else { | ||
| this.log("Skipping close (using shared client)"); | ||
| } | ||
| } catch (error) { | ||
| console.error("[RedisCacheHandler] close error:", error); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Nit: should be
readonly.This flag is set once in the constructor and never mutated after. Mark it
readonlyfor clarity and to let the type system enforce that:Then assign it explicitly in each constructor branch (e.g.
this.didCreateClient = true/false).