import { createClient, type RedisClientType } from 'redis'; import type { CacheService, CacheSetOptions } from './cache-service'; import { getServerAppServices } from '../create-services.server'; export type RedisCacheServiceOptions = { url: string; keyPrefix?: string; }; // Thin wrapper around shared Redis (platform provides host `redis`). // Values are JSON-serialized. export class RedisCacheService implements CacheService { private readonly client: RedisClientType; private readonly keyPrefix: string; constructor(options: RedisCacheServiceOptions) { this.client = createClient({ url: options.url }); this.keyPrefix = options.keyPrefix ?? ''; // Fire-and-forget connect. this.client.connect().catch((err) => { // We can't use getServerAppServices() here because it might cause a circular dependency // during initialization. But we can log to console as a fallback or use a global logger if we had one. // For now, let's just use console.error as this is a low-level service. console.error('Redis connection error:', err); }); } private k(key: string) { return `${this.keyPrefix}${key}`; } async get(key: string): Promise { const raw = await this.client.get(this.k(key)); if (raw == null) return undefined; return JSON.parse(raw) as T; } async set(key: string, value: T, options?: CacheSetOptions): Promise { const ttl = options?.ttlSeconds; const raw = JSON.stringify(value); if (ttl && ttl > 0) { await this.client.set(this.k(key), raw, { EX: ttl }); return; } await this.client.set(this.k(key), raw); } async del(key: string): Promise { await this.client.del(this.k(key)); } }