extend redis client backend to support redis specific functionality

This commit is contained in:
Narendranath Gogineni
2025-10-10 18:30:20 +05:30
parent b372b2fa7c
commit a30cfffddc
3 changed files with 29 additions and 29 deletions
+16 -22
View File
@@ -6,6 +6,8 @@ import Redis from 'ioredis';
import { CacheBackend, CacheEntry, CacheOptions, CacheStats } from '../types';
type RedisClient = Redis;
// Using console.log for now to avoid build issues
const logger = {
debug: (msg: string, ...args: any[]) =>
@@ -18,22 +20,6 @@ const logger = {
console.error(`[RedisCache] ${msg}`, ...args),
};
// Redis client interface matching ioredis
interface RedisClient {
get(key: string): Promise<string | null>;
set(
key: string,
value: string,
expiryMode?: string | any,
time?: number | string
): Promise<'OK' | null>;
del(...keys: string[]): Promise<number>;
exists(...keys: string[]): Promise<number>;
keys(pattern: string): Promise<string[]>;
flushdb(): Promise<'OK'>;
quit(): Promise<'OK'>;
}
export class RedisCacheBackend implements CacheBackend {
private client: RedisClient;
private dbName: string;
@@ -52,12 +38,6 @@ export class RedisCacheBackend implements CacheBackend {
this.dbName = dbName;
}
private getFullKey(key: string, namespace?: string): string {
return namespace
? `${this.dbName}:${namespace}:${key}`
: `${this.dbName}:default:${key}`;
}
private serializeEntry<T>(entry: CacheEntry<T>): string {
return JSON.stringify(entry);
}
@@ -70,6 +50,12 @@ export class RedisCacheBackend implements CacheBackend {
return entry.expiresAt !== undefined && entry.expiresAt <= Date.now();
}
getFullKey(key: string, namespace?: string): string {
return namespace
? `${this.dbName}:${namespace}:${key}`
: `${this.dbName}:default:${key}`;
}
async get<T = any>(
key: string,
namespace?: string
@@ -216,6 +202,14 @@ export class RedisCacheBackend implements CacheBackend {
}
}
async script(mode: 'LOAD' | 'EXISTS', script: string): Promise<unknown> {
return await this.client.script('LOAD', script);
}
async evalsha(sha: string, keys: string[], args: string[]): Promise<unknown> {
return await this.client.evalsha(sha, keys.length, ...keys, ...args);
}
async cleanup(): Promise<void> {
// Redis handles TTL automatically, so this is mostly a no-op
// We could scan for entries with manual expiration and clean them up
+4
View File
@@ -253,6 +253,10 @@ export class CacheService {
}));
return Promise.all(promises);
}
getClient(): CacheBackend {
return this.backend;
}
}
// Default cache instances for different use cases
+9 -7
View File
@@ -1,5 +1,6 @@
import { Redis, Cluster } from 'ioredis';
import { RateLimiterKeyTypes } from '../../../../globals';
import { RedisCacheBackend } from '../backends/redis';
const RATE_LIMIT_LUA = `
local tokensKey = KEYS[1]
@@ -81,7 +82,7 @@ return {allowed, waitTime, currentTokens}
`;
class RedisRateLimiter {
private redis: Redis | Cluster;
private redis: RedisCacheBackend;
private capacity: number;
private windowSize: number;
private tokensKey: string;
@@ -92,7 +93,7 @@ class RedisRateLimiter {
private key: string;
constructor(
redisClient: Redis | Cluster,
redisClient: RedisCacheBackend,
capacity: number,
windowSize: number,
key: string,
@@ -103,8 +104,8 @@ class RedisRateLimiter {
const tag = `{rate:${key}}`; // ensures same hash slot
this.capacity = capacity;
this.windowSize = windowSize;
this.tokensKey = `${tag}:tokens`;
this.lastRefillKey = `${tag}:lastRefill`;
this.tokensKey = `default:default:${tag}:tokens`;
this.lastRefillKey = `default:default:${tag}:lastRefill`;
this.keyTTL = windowSize * ttlFactor; // dynamic TTL
this.keyType = keyType;
this.key = key;
@@ -126,12 +127,12 @@ class RedisRateLimiter {
const sha = await this.loadOrGetScriptSha();
try {
return await this.redis.evalsha(sha, keys.length, ...keys, ...args);
return await this.redis.evalsha(sha, keys, args);
} catch (error: any) {
if (error.message.includes('NOSCRIPT')) {
// Script not loaded on target node - load it and retry with same SHA
await this.redis.script('LOAD', RATE_LIMIT_LUA);
return await this.redis.evalsha(sha, keys.length, ...keys, ...args);
return await this.redis.evalsha(sha, keys, args);
}
throw error;
}
@@ -171,7 +172,8 @@ class RedisRateLimiter {
}
async getToken(): Promise<string | null> {
return this.redis.get(this.tokensKey);
const cacheEntry = await this.redis.get(this.tokensKey);
return cacheEntry ? cacheEntry.value : null;
}
async decrementToken(