4.4 KiB
OpenSpec Change: enterprise-gaming-game-persistence
- Change ID: enterprise-gaming-game-persistence
- Package:
@suaveplan/game-persistence - Category: gaming/engine
- Target path:
gaming/engine/game-persistence - Status: PROPOSED
- Date: 2026-04-24
- Reference spec:
/mnt/s/web/suaveplan/enterprise/game-persistence.md
Summary
Deliver @suaveplan/game-persistence on the genesis branch from scratch at enterprise quality. The package provides save/load game state with versioned migrations — a universal persistence substrate for both client-side saves and server-side session snapshots. A SaveManager holds named slots per player, auto-increments version on re-save, enforces configurable slot-count and size limits, and fires @suaveplan/events lifecycle events. A MigrationRunner upgrades older saves through a registered migration chain. Genesis adds Zod schemas, @suaveplan/serialize for binary/JSON round-trips, OTel instrumentation, structured errors, and full 100% coverage.
Current-state audit (genesis)
Package does not exist on genesis yet — fresh build.
Available workspace deps on genesis
| Package | Role |
|---|---|
@suaveplan/error (workspace:^) |
GamePersistenceError; SAVE.* error codes |
@suaveplan/types (workspace:^) |
StorageLike, Clock |
@suaveplan/events (workspace:^) |
Save lifecycle events |
@suaveplan/serialize (workspace:^) |
Tagged JSON + MessagePack round-trip for save data |
zod (catalog:forms) |
SaveSlot, MigrationConfig schemas |
@opentelemetry/api (catalog:observability) |
Save/load span instrumentation |
Public API surface
function createSaveManager(options: SaveManagerOptions): SaveManager
interface SaveManagerOptions {
maxSlotsPerPlayer?: number // default 10
maxSizeBytes?: number // default 1MB per slot
autosaveSlot?: string // default 'autosave'
storage?: StorageLike // pluggable; default: in-memory Map
clock?: Clock
migrations?: Migration[]
}
interface SaveManager {
save(playerId: string, slotId: string, data: unknown): Promise<SaveSlot>
load(playerId: string, slotId: string): Promise<SaveSlot>
delete(playerId: string, slotId: string): Promise<void>
listSlots(playerId: string): Promise<SaveSlot[]>
autosave(playerId: string, data: unknown): Promise<SaveSlot>
hasSlot(playerId: string, slotId: string): Promise<boolean>
}
interface SaveSlot {
id: string
playerId: string
version: number
createdAt: number
updatedAt: number
sizeBytes: number
data: unknown
}
// Migration
interface Migration {
fromVersion: number
toVersion: number
migrate(data: unknown): unknown
}
function createMigrationRunner(migrations: Migration[]): MigrationRunner
interface MigrationRunner {
run(data: unknown, fromVersion: number, toVersion: number): unknown
canMigrate(fromVersion: number, toVersion: number): boolean
}
Error codes
type SaveErrorCode =
| 'SAVE_SLOT_NOT_FOUND'
| 'SAVE_SLOT_LIMIT_EXCEEDED'
| 'SAVE_SIZE_EXCEEDED'
| 'SAVE_MIGRATION_FAILED'
| 'SAVE_SERIALIZATION_FAILED'
| 'SAVE_STORAGE_ERROR'
New additions for genesis
- Binary serialization —
@suaveplan/serializeMessagePack profile for compact save blobs; JSON fallback. - Migration chain validation —
MigrationRunnervalidates no gaps or cycles in version sequence at construction time. - OTel —
save.write_duration_msandsave.read_duration_mshistograms;save.slot_countgauge. - Events —
save:created,save:updated,save:loaded,save:deleted,save:autosave,save:migration_applied.
File layout
gaming/engine/game-persistence/
package.json
tsconfig.json
vitest.config.ts
src/
index.ts
error-codes.ts
constants/index.ts
types/common.ts
types/common.md
core/
save-manager/
save-manager.ts
save-manager.md
save-manager.types.ts
save-manager.test.ts
index.ts
migration-runner/
migration-runner.ts
migration-runner.md
migration-runner.types.ts
migration-runner.test.ts
index.ts
tests/
integration.test.ts
Impact
Downstream: @suaveplan/api-plugin-game-persistence wraps this in HTTP routes. Prerequisites: @suaveplan/error, @suaveplan/types, @suaveplan/events, @suaveplan/serialize.