@dloizides/save-state
v1.0.0
Published
Framework-agnostic local save/progress primitives for games: a KeyValueStore port (localStorage/memory), corruption-safe JSON I/O, and a generic versioned, slotted SaveStore. Zero deps, isomorphic. Extracted from Morphe + Aurora.
Maintainers
Readme
@dloizides/save-state
Framework-agnostic local save/progress primitives for games. Zero runtime dependencies, isomorphic (works in browser games, React Native shells, and tests — the store is structural and injectable).
Three layers, adopt what fits your model:
- Storage port —
KeyValueStoreinterface +MemoryKeyValueStore+defaultKeyValueStore()(browserlocalStoragewhen present, in-memory otherwise). - Corruption-safe JSON I/O —
readJson/writeJson/removeKeys. None ever throw into the game loop. SlotStore<T>— a generic, versioned, slotted save store (one key per slot), modelled on the common "N save slots, version-guarded, corruption-safe" pattern.
Install
npm install @dloizides/save-stateSlotStore (one key per slot)
import { SlotStore } from '@dloizides/save-state';
interface MySave { version: number; timestamp?: number; level: number }
const saves = new SlotStore<MySave>({ prefix: 'mygame:save:', version: 3 });
saves.save(1, { version: 3, timestamp: Date.now(), level: 7 });
saves.load(1); // MySave | null (null if absent, corrupt, or a different version)
saves.exists(1); // boolean
saves.meta(1); // { slot, exists, timestamp? } — for a save-picker, no full load
saves.delete(1);load() returns null for any stored save whose version differs from the configured schema version —
the caller migrates separately if it wants to (migration is game-specific).
Storage port + JSON helpers (custom model)
For a bespoke layout (e.g. all slots in one blob, or multi-version migration), build on the lower layers:
import { defaultKeyValueStore, readJson, writeJson, removeKeys } from '@dloizides/save-state';
const store = defaultKeyValueStore();
const save = readJson<MySave>(store, 'mygame:save', { validate: isMySave }); // null-safe
writeJson(store, 'mygame:save', save); // false if blocked (private mode/quota)
removeKeys(store, ['mygame:save', 'mygame:v1']); // best-effort, never throwsAPI
KeyValueStore,MemoryKeyValueStore,defaultKeyValueStore()readJson<T>(store, key, { validate? }),writeJson(store, key, value),removeKeys(store, keys)SlotStore<T extends VersionedSave>({ prefix, version, store? })—save / load / delete / exists / meta- types:
VersionedSave,SlotMeta,SlotStoreOptions,ReadJsonOptions
License
MIT
