Skip to content

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 HAPPENS

The 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

BackendImportUse
MemoryAdapter@idlekitjs/storage/memoryTests, SSR, ephemeral sessions
LocalStorageAdapter@idlekitjs/storage/local-storageBrowser games

Planned backends (IndexedDB, SQLite/native) will follow the same one-folder, one-subpath convention — additive, no API change.

Choosing

  • Browser gameLocalStorageAdapter. Synchronous writes survive pagehide, which is exactly what close-time autosaves need.
  • Tests / headlessMemoryAdapter. Zero environment, instant.
  • Anything elsewrite 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.