first commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# PersistencePlugin
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## 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
|
||||
|
||||
```ts
|
||||
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
|
||||
|
||||
```ts
|
||||
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`.
|
||||
Reference in New Issue
Block a user