Roves
roves-api

saves

An async, IndexedDB-shaped key/value store for player save data.

Edit on GitHub
import { saves } from "@drincs/roves-api/saves";

await saves.writeText("slot-1", gameState); // any JSON-serializable value, not just a string
const saved = await saves.readJSON<typeof gameState>("slot-1");

await saves.list();       // ["slot-1", "autosave", ...] — local disk only, see the Callout below
await saves.getMostRecent(); // { key: "slot-1", modifiedMs: 1735689600000 } | null
await saves.delete("slot-1");
await saves.clear();      // delete every save — a "reset save data" button, not routine use

See Save-game storage for where these actually land on disk (it depends on how the game was shipped) and how Steam Cloud sync works. Check core's isAvailable() before calling any of this if your code might also run outside Roves.

API

interface SavesApi {
  isAvailable(): Promise<boolean>;
  write(key: string | number, data: Uint8Array): Promise<boolean>;
  writeText(key: string | number, data: unknown): Promise<boolean>;
  read(key: string | number): Promise<Uint8Array | null>;
  readText(key: string | number): Promise<string | null>;
  readJSON<T>(key: string | number): Promise<T | null>;
  delete(key: string | number): Promise<boolean>;
  list(): Promise<string[]>;
  getMostRecent(): Promise<{ key: string; modifiedMs: number } | null>;
  clear(): Promise<boolean>;
}
  • write/read work with raw bytes — reach for these if a save isn't plain text (a compressed or binary format you produce yourself).
  • writeText/readText are the convenience pair for a text save — handles the UTF-8 encode/decode for you. writeText accepts any JSON-serializable value too, not just a string — a plain string is written as-is, anything else is JSON.stringify'd for you.
  • readJSON<T> pairs with a writeText call that passed a non-string value — decodes and JSON.parses the stored text for you, typed as T, instead of every caller doing readText + JSON.parse by hand.
  • key accepts a number as well as a string — handy for a numeric save-slot id; it's converted to a string before being sent over the wire, so saves.write(3, data) and saves.write("3", data) land on the same save.
  • getMostRecent() answers "which save is newest" without reading (or downloading) any save's own content — see Save-game storage's Steam Cloud section for why this, unlike list(), can see a save made on another machine before anything pulls it down locally.
  • isAvailable() is almost always true on desktop today; it exists for the future case of a console port with no save API wired up yet, where every method here degrades to a harmless default (false/null/[]) instead of throwing.
  • Every method already catches its own errors and resolves to a safe default rather than rejecting — a failed save shows up as write()/delete() returning false, not as an unhandled promise rejection.

Keys, not paths

A key is a short, arbitrary name your game chooses ("slot-1", "autosave", "settings") — not a filesystem path. Roves sanitizes it before touching disk, so path separators or .. in a key are rejected rather than doing anything unexpected.

On this page