import { GameRecord } from "@mancala/core"; import { Valkey } from "iovalkey"; import { scanKeys } from "./valkey"; const LIVE_PREFIX = "mancala:live:"; const PENDING_SAVE_PREFIX = "mancala:pending-save:"; // Durable copy of state that otherwise only lives in memory: live games, so they survive a restart, and finished // games that could not be saved to the database yet. Methods reject when Valkey is unreachable. export interface LiveGameStore { saveGame(gameId: string, snapshot: unknown): Promise; removeGame(gameId: string): Promise; loadGames(): Promise; savePendingRecord(record: GameRecord): Promise; removePendingRecord(gameId: string): Promise; loadPendingRecords(): Promise; } export class ValkeyLiveGameStore implements LiveGameStore { constructor(private readonly client: Valkey) { } async saveGame(gameId: string, snapshot: unknown): Promise { await this.client.set(LIVE_PREFIX + gameId, JSON.stringify(snapshot)); } async removeGame(gameId: string): Promise { await this.client.del(LIVE_PREFIX + gameId); } loadGames(): Promise { return this.loadAll(LIVE_PREFIX); } async savePendingRecord(record: GameRecord): Promise { await this.client.set(PENDING_SAVE_PREFIX + record.id, JSON.stringify(record)); } async removePendingRecord(gameId: string): Promise { await this.client.del(PENDING_SAVE_PREFIX + gameId); } loadPendingRecords(): Promise { return this.loadAll(PENDING_SAVE_PREFIX) as Promise; } private async loadAll(prefix: string): Promise { const keys = await scanKeys(this.client, prefix + "*"); if (keys.length === 0) return []; const values = await this.client.mget(keys); const parsed: unknown[] = []; values.forEach((value, i) => { if (value === null) return; try { parsed.push(JSON.parse(value)); } catch { console.error(`(Valkey) Ignoring unreadable value in ${keys[i]}`); } }); return parsed; } }