mancala/apps/backend/src/cache/Cache.ts
Halit Aksoy f6136be007 feat(backend): keep live games and caches in Valkey
- Valkey client that fails commands at once while Valkey is down instead
  of queueing them, and logs connection errors once
- ValkeyLiveGameStore: live game snapshots and finished games waiting to
  be saved, loaded with SCAN on startup
- ValkeyCache: JSON values with a TTL; errors count as cache misses
- Integration tests against a real Valkey, including an unreachable one
2026-10-16 00:14:07 +03:00

41 lines
1.3 KiB
TypeScript

import { Valkey } from "iovalkey";
const PREFIX = "mancala:cache:";
// Short-lived cache of JSON values. Never fails: errors count as a miss, so callers just compute the value.
export interface Cache {
getJson<T>(key: string): Promise<T | undefined>;
setJson(key: string, value: unknown, ttlSeconds: number): Promise<void>;
delete(...keys: string[]): Promise<void>;
}
export class ValkeyCache implements Cache {
constructor(private readonly client: Valkey) { }
async getJson<T>(key: string): Promise<T | undefined> {
try {
const value = await this.client.get(PREFIX + key);
return value === null ? undefined : JSON.parse(value) as T;
} catch (err) {
return undefined;
}
}
async setJson(key: string, value: unknown, ttlSeconds: number): Promise<void> {
try {
await this.client.set(PREFIX + key, JSON.stringify(value), "EX", ttlSeconds);
} catch (err) {
// A missing cache entry only costs a database query.
}
}
async delete(...keys: string[]): Promise<void> {
if (keys.length === 0) return;
try {
await this.client.del(...keys.map((key) => PREFIX + key));
} catch (err) {
// Entries expire on their own.
}
}
}