v1.0.0-alpha4: Add Redis/Valkey caching and search with principal agent auth for global settings
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user