Appearance
Storage overview
Persistence in IdleKit is a three-method contract. Everything else — where the bytes go, how they're versioned, when they're written — hangs off it.
The SaveAdapter contract
Defined in @idlekitjs/types, implemented by @idlekitjs/storage backends (or by you):
ts
interface SaveAdapter {
read(key: string): string | null | Promise<string | null>;
write(key: string, value: string): void | Promise<void>;
remove(key: string): void | Promise<void>;
}Methods may be sync or async — the SaveManager always awaits them, so a synchronous localStorage adapter and an async IndexedDB adapter are interchangeable from the game's point of view.
How the pieces relate
txt
game state (plain object)
│ JSON + version + savedAt
▼
SaveManager (@idlekitjs/core) what a save IS
│ read/write/remove strings
▼
SaveAdapter (@idlekitjs/storage) where a save LIVES
▲
│ drives save timing
autosave (@idlekitjs/plugins) when a save HAPPENSThe layering means each concern changes independently: migrating your schema touches only SaveManager options; moving from localStorage to IndexedDB touches only the adapter; changing save frequency touches only autosave.
Available backends
| Backend | Import | Use |
|---|---|---|
MemoryAdapter | @idlekitjs/storage/memory | Tests, SSR, ephemeral sessions |
LocalStorageAdapter | @idlekitjs/storage/local-storage | Browser games |
Planned backends (IndexedDB, SQLite/native) will follow the same one-folder, one-subpath convention — additive, no API change.
Choosing
- Browser game →
LocalStorageAdapter. Synchronous writes survivepagehide, which is exactly what close-time autosaves need. - Tests / headless →
MemoryAdapter. Zero environment, instant. - Anything else → write an adapter; it's ~15 lines.
Boundaries
@idlekitjs/storage depends only on @idlekitjs/types. Core never imports a concrete backend; mechanics never touch storage at all. Swapping backends is invisible to every line of game logic.