# GamePersistence _(no submodules)_ ## Purpose `GamePersistence` persists game state to a pluggable key-value backend via the `StorageLike` structural contract from `@suaveplan/types`. State, named snapshots, and a bounded audit-trail history are serialised together as a UTF-8 JSON document under a deterministic key (`game-server:save::`), allowing any conformant `AbstractStorage` adapter — in-memory, SurrealDB, SQLite, S3 — to back the engine without code changes. The manager exposes async `save`, `load`, `delete`, `createSnapshot`, and `restoreSnapshot` operations that throw stable `GAME_SERVER.*` errors, plus an OTel save-duration histogram and counter pair so production throughput is observable without bespoke instrumentation. ## Features - Pluggable `StorageLike` backend (in-memory, durable, network). - Named snapshots with rotating `maxSnapshots` cap. - Bounded audit-trail history with `maxHistoryEntries` cap. - Injectable `Clock` and `IdGenerator` for deterministic tests. - Stable error codes: `PERSISTENCE_SAVE_FAILED`, `PERSISTENCE_LOAD_FAILED`, `PERSISTENCE_NOT_FOUND`, `INVALID_SAVE_RECORD`. - OTel counters + duration histogram. - Tracks `currentSaveId` for snapshot context. ## Usage: Basic ```ts import { GamePersistence } from "@suaveplan/game-server/enhanced/persistence/game-persistence"; const gp = new GamePersistence( { gameId: "chess" }, { storage: createSurrealStorage() } ); const result = await gp.save(state, { name: "quicksave" }); const loaded = await gp.load(result.id!); ``` ## Usage: Advanced ```ts import { GamePersistence } from "@suaveplan/game-server/enhanced/persistence/game-persistence"; const gp = new GamePersistence( { gameId: "chess", maxSnapshots: 50, maxHistoryEntries: 200 }, { storage, clock: () => now, idGenerator: () => `save-${++counter}`, meter, } ); const save = await gp.save(state, { name: "checkpoint", tags: ["wave-4"] }); const snap = await gp.createSnapshot(state, "before-boss", "auto"); const restored = await gp.restoreSnapshot(save.id!, snap.id); const history = await gp.getHistory(save.id!); const stats = gp.getStats(); ``` ## API Reference - `new GamePersistence(config, deps)` — `deps.storage: StorageLike`. - `save(state, input): Promise` - `load(id): Promise` - `delete(id): Promise` - `exists(id): Promise` - `createSnapshot(state, name, reason?): Promise` - `restoreSnapshot(saveId, snapshotId): Promise` - `getHistory(saveId): Promise` - `getStats(): PersistenceStats` - `getCurrentSaveId(): string | null` ## Implementation Notes State is encoded as UTF-8 JSON via `TextEncoder`/`TextDecoder` so the on-disk shape is debuggable with any text viewer; binary-only stores will treat the payload as opaque bytes. For game states with large embedded binaries (images, audio), callers should chunk those out to a separate asset store and reference them by id in the persisted state to avoid inflating the JSON payload. Storage keys are namespaced by both `gameId` and the per-save id so a single shared backend can host saves for many concurrent game instances without prefix collisions. The `currentSaveId` cursor is in-memory only — restarting the process requires an explicit `load()` before `createSnapshot()` will succeed. JSON decode failures are mapped to `PERSISTENCE_LOAD_FAILED` rather than silently returning `null`, so corrupted storage surfaces as an inspectable error chain instead of an empty save list. Save failures preserve their underlying cause through `GameServerError.cause`, making storage-side diagnostics directly available to the caller.