Files
skills/docs/gaming/games/game-server/src/plugins/persistence-plugin/persistence-plugin.md
2026-05-29 08:20:56 +01:00

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 gameState from game:state:changed events.
  • 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 via saveOnGameEnd).
  • 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 BasePlugin
  • constructor(options: PersistencePluginOptions)
  • save(sessionId): Promise<SavedGameState>
  • load(sessionId): Promise<SavedGameState | null>
  • delete(sessionId): Promise<boolean>
  • peek(sessionId): SavedGameState | undefined
  • mergeState(sessionId, fragment): void
  • size(): number

Implementation Notes

  • game:state:changed carries newState: 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.
  • load performs three checks: storage get, JSON parse, and Zod schema validation. Any failure surfaces as PERSISTENCE_LOAD_FAILED with the original error preserved as the cause; null is 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. Manual save calls still surface the error to the caller.
  • mergeState is 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 requires save.