roves-api
saves
An async, IndexedDB-shaped key/value store for player save data.
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 useSee 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/readwork with raw bytes — reach for these if a save isn't plain text (a compressed or binary format you produce yourself).writeText/readTextare the convenience pair for a text save — handles the UTF-8 encode/decode for you.writeTextaccepts any JSON-serializable value too, not just astring— a plain string is written as-is, anything else isJSON.stringify'd for you.readJSON<T>pairs with awriteTextcall that passed a non-string value — decodes andJSON.parses the stored text for you, typed asT, instead of every caller doingreadText+JSON.parseby hand.keyaccepts anumberas well as astring— handy for a numeric save-slot id; it's converted to a string before being sent over the wire, sosaves.write(3, data)andsaves.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, unlikelist(), can see a save made on another machine before anything pulls it down locally.isAvailable()is almost alwaystrueon 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()returningfalse, 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.