157 lines
3.8 KiB
TypeScript
157 lines
3.8 KiB
TypeScript
import Redis from 'ioredis';
|
|
|
|
// Redis/Valkey connection instance
|
|
let redisClient: Redis | null = null;
|
|
|
|
/**
|
|
* Get or create Redis connection
|
|
* Uses REDIS_URL from environment or defaults to localhost
|
|
*/
|
|
export function getRedisClient(): Redis {
|
|
if (!redisClient) {
|
|
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
|
|
redisClient = new Redis(redisUrl, {
|
|
maxRetriesPerRequest: 3,
|
|
retryStrategy(times) {
|
|
const delay = Math.min(times * 50, 2000);
|
|
return delay;
|
|
},
|
|
reconnectOnError(err) {
|
|
const targetErrors = ['READONLY', 'ECONNRESET', 'ETIMEDOUT'];
|
|
return targetErrors.some(targetError => err.message.includes(targetError));
|
|
}
|
|
});
|
|
|
|
redisClient.on('error', (err) => {
|
|
console.error('Redis connection error:', { message: err.message });
|
|
});
|
|
|
|
redisClient.on('connect', () => {
|
|
console.log('Redis connected successfully');
|
|
});
|
|
}
|
|
|
|
return redisClient;
|
|
}
|
|
|
|
/**
|
|
* Cache helper functions
|
|
*/
|
|
|
|
interface CacheOptions {
|
|
ttl?: number; // Time to live in seconds (default: 300 = 5 minutes)
|
|
}
|
|
|
|
/**
|
|
* Get cached data by key
|
|
*/
|
|
export async function getCached<T>(key: string): Promise<T | null> {
|
|
try {
|
|
const redis = getRedisClient();
|
|
const data = await redis.get(key);
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(data) as T;
|
|
} catch (err) {
|
|
console.error('Cache get error:', { key, message: (err as Error).message });
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set cached data with optional TTL
|
|
*/
|
|
export async function setCached(key: string, data: any, options: CacheOptions = {}): Promise<boolean> {
|
|
try {
|
|
const redis = getRedisClient();
|
|
const ttl = options.ttl || 300; // Default 5 minutes
|
|
const serialized = JSON.stringify(data);
|
|
|
|
await redis.setex(key, ttl, serialized);
|
|
return true;
|
|
} catch (err) {
|
|
console.error('Cache set error:', { key, message: (err as Error).message });
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Delete cached data by key or pattern
|
|
*/
|
|
export async function deleteCached(keyOrPattern: string): Promise<number> {
|
|
try {
|
|
const redis = getRedisClient();
|
|
|
|
// If it contains wildcards, use scan + delete
|
|
if (keyOrPattern.includes('*')) {
|
|
let cursor = '0';
|
|
let deletedCount = 0;
|
|
|
|
do {
|
|
const [nextCursor, keys] = await redis.scan(cursor, 'MATCH', keyOrPattern, 'COUNT', 100);
|
|
cursor = nextCursor;
|
|
|
|
if (keys.length > 0) {
|
|
const deleted = await redis.del(...keys);
|
|
deletedCount += deleted;
|
|
}
|
|
} while (cursor !== '0');
|
|
|
|
return deletedCount;
|
|
} else {
|
|
// Direct key deletion
|
|
return await redis.del(keyOrPattern);
|
|
}
|
|
} catch (err) {
|
|
console.error('Cache delete error:', { keyOrPattern, message: (err as Error).message });
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Search functionality using Redis
|
|
*/
|
|
|
|
export interface SearchOptions {
|
|
fields?: string[]; // Fields to search in
|
|
limit?: number; // Max results
|
|
fuzzy?: boolean; // Enable fuzzy matching
|
|
}
|
|
|
|
/**
|
|
* Cache search results
|
|
*/
|
|
export async function cacheSearchResults(query: string, results: any[], options: CacheOptions = {}): Promise<boolean> {
|
|
const cacheKey = `search:${query.toLowerCase().trim()}`;
|
|
return setCached(cacheKey, results, options);
|
|
}
|
|
|
|
/**
|
|
* Get cached search results
|
|
*/
|
|
export async function getCachedSearchResults(query: string): Promise<any[] | null> {
|
|
const cacheKey = `search:${query.toLowerCase().trim()}`;
|
|
return getCached<any[]>(cacheKey);
|
|
}
|
|
|
|
/**
|
|
* Invalidate all search caches
|
|
*/
|
|
export async function invalidateSearchCache(): Promise<number> {
|
|
return deleteCached('search:*');
|
|
}
|
|
|
|
/**
|
|
* Close Redis connection (for cleanup)
|
|
*/
|
|
export async function closeRedisConnection(): Promise<void> {
|
|
if (redisClient) {
|
|
await redisClient.quit();
|
|
redisClient = null;
|
|
}
|
|
}
|