import { Redis } from 'ioredis' import { CacheError, type CachePort } from '../port.js' import { wrapValue } from '../wrap.js' export interface RedisLike { get(key: string): Promise set(key: string, value: string, secondsToken?: 'EX', seconds?: number): Promise del(key: string): Promise exists(key: string): Promise } export interface RedisAdapterOptions { client?: RedisLike url?: string keyPrefix?: string } export function redisAdapter(options: RedisAdapterOptions = {}): CachePort { const defaultClient = () => (options.url ? new Redis(options.url) : new Redis()) as unknown as RedisLike const client: RedisLike = options.client ?? defaultClient() const prefix = options.keyPrefix ?? '' const k = (key: string) => `${prefix}${key}` const port: CachePort = { name: 'redis', async get(key: string) { try { const raw = await client.get(k(key)) if (raw === null) return null return JSON.parse(raw) as T } catch (cause) { throw new CacheError('Failed to read from Redis', { adapter: 'redis', cause }) } }, async set(key: string, value: T, ttlSeconds?: number) { try { const json = JSON.stringify(value) if (ttlSeconds === undefined) await client.set(k(key), json) else await client.set(k(key), json, 'EX', ttlSeconds) } catch (cause) { throw new CacheError('Failed to write to Redis', { adapter: 'redis', cause }) } }, async delete(key: string) { try { await client.del(k(key)) } catch (cause) { throw new CacheError('Failed to delete from Redis', { adapter: 'redis', cause }) } }, async has(key: string) { try { return (await client.exists(k(key))) > 0 } catch (cause) { throw new CacheError('Failed to query Redis', { adapter: 'redis', cause }) } }, wrap(key: string, factory: () => Promise, ttlSeconds?: number) { return wrapValue(port, key, factory, ttlSeconds) }, } return port }