Skip to content

Local storage adapter

The browser backend: a SaveAdapter over window.localStorage. The default choice for shipping a web game.

ts
import { LocalStorageAdapter } from "@idlekitjs/storage/local-storage";

Usage

ts
import { SaveManager } from "@idlekitjs/core";
import { LocalStorageAdapter } from "@idlekitjs/storage/local-storage";

const save = new SaveManager<State>({
  key: "my-game", // the localStorage key
  version: 1,
  adapter: new LocalStorageAdapter(),
});

Why localStorage (and not IndexedDB) by default

localStorage is synchronous — a write started in a pagehide handler completes before the page unloads. That is precisely the guarantee the autosave plugin relies on for its close-time save. IndexedDB is async and can lose that race. For the size of an idle-game save (kilobytes of JSON), synchronous is a feature, not a bottleneck.

Complete autosave setup

ts
import { createEngine, SaveManager } from "@idlekitjs/core";
import { LocalStorageAdapter } from "@idlekitjs/storage/local-storage";
import { autosave } from "@idlekitjs/plugins/autosave";
import { offlineProgress } from "@idlekitjs/plugins/offline-progress";
import { pageLifecycle } from "@idlekitjs/browser/page-lifecycle";

const engine = createEngine<State>({ initialState /* , renderer, scheduler */ });

const save = new SaveManager<State>({
  key: "my-game",
  version: 1,
  adapter: new LocalStorageAdapter(),
});

engine.use(pageLifecycle());
engine.use(offlineProgress({ maxMs: 8 * 60 * 60 * 1000 }));
engine.use(autosave({ manager: save, getState: () => engine.state, intervalMs: 15_000 }));

await engine.load(save);
engine.start();

A reset button is one call:

ts
resetEl.addEventListener("click", () => {
  void save.clear().then(() => location.reload());
});

Notes and limits

  • Quota — typically ~5 MB per origin; monumental for JSON saves, but don't store assets in it.
  • Corruption is handled — a hand-edited or truncated save makes load() return null (fresh start), never throw; prototype-pollution via edited saves is neutralized by the SaveManager.
  • Private browsing — some browsers throw on write when storage is full or restricted; the SaveManager catches read errors, but a fully unavailable localStorage is a platform decision your game may want to surface.
  • Not for Node/SSR — the adapter assumes the global exists; use MemoryAdapter in headless contexts.