3.2 KiB
3.2 KiB
PersistencePlugin
(no submodules)
Purpose
PersistencePlugin tracks per-session game state via a GameEventEmitter
and persists it to an injectable StorageLike backend. It subscribes to
session:created, game:started, game:state:changed, player:joined,
player:left, and game:ended so engine state is captured without
intrusive instrumentation.
The plugin saves on game:ended automatically (toggleable) and exposes
explicit save, load, and delete operations so callers can drive
persistence from outside the event stream too — for example, during an
emergency shutdown hook.
Features
- Subscribes to the game emitter for transparent state capture.
- Accumulates
gameStatefromgame:state:changedevents. - Tracks per-session player membership.
- Save record is Zod-validated on load — corrupt files throw
GAME_SERVER.PERSISTENCE_LOAD_FAILED. - Saves on
game:ended(configurable viasaveOnGameEnd). - Customisable key prefix (
prefix, default"game:save:"). - Lifecycle-safe: unsubscribes and clears tracked sessions on uninstall.
Usage: Basic
import { GameEventEmitter } from "@suaveplan/game-server";
import { PersistencePlugin } from "@suaveplan/game-server/plugins/persistence-plugin";
const emitter = new GameEventEmitter();
const storage = createMyKvAdapter();
const plugin = new PersistencePlugin({ emitter, storage });
await plugin.onInstall(ctx);
Usage: Advanced
import { PersistencePlugin } from "@suaveplan/game-server/plugins/persistence-plugin";
const plugin = new PersistencePlugin({
emitter,
storage,
prefix: "tournament:",
saveOnGameEnd: false,
clock: () => 1_700_000_000_000,
});
await plugin.onInstall(ctx);
emitter.emit("game:started", { sessionId: "s1" });
emitter.emit("game:state:changed", {
sessionId: "s1",
newState: { score: 42 },
});
const saved = await plugin.save("s1");
const loaded = await plugin.load("s1");
await plugin.delete("s1");
API Reference
class PersistencePlugin extends BasePluginconstructor(options: PersistencePluginOptions)save(sessionId): Promise<SavedGameState>load(sessionId): Promise<SavedGameState | null>delete(sessionId): Promise<boolean>peek(sessionId): SavedGameState | undefinedmergeState(sessionId, fragment): voidsize(): number
Implementation Notes
game:state:changedcarriesnewState: unknown. The plugin merges object payloads into the tracked map and wraps non-object payloads as{ value: <payload> }so the saved record always has a stable shape.loadperforms three checks: storageget, JSON parse, and Zod schema validation. Any failure surfaces asPERSISTENCE_LOAD_FAILEDwith the original error preserved as the cause;nullis reserved for "nothing stored at this key".- Errors in the auto-save path (
saveOnGameEnd) are caught and swallowed after a logger.error so a storage outage cannot stall the emitter. Manualsavecalls still surface the error to the caller. mergeStateis a public hook for callers that want to capture state from a non-event source (e.g. an external scheduler). It writes through to the in-memory row only; persistence still requiressave.