npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

persistence-save

v0.4.0

Published

Save-game persistence for Capacitor games: a SQLite save/load facade (jeep-sqlite/sql.js on the web, native SQLite with SQLCipher on device), chained snapshot migrations, corrupt-save isolation, an autosave scheduler and a Preferences key-value bridge.

Readme

persistence-save

npm CI License: MIT

Save-game persistence for Capacitor games. One API stores saves in SQLite on every platform: jeep-sqlite over sql.js and IndexedDB on the web, and native SQLite (optionally SQLCipher-encrypted) on iOS and Android.

  • A save/load/list/delete facade over @capacitor-community/sqlite, opened lazily and isolating corrupt rows instead of failing the whole list.
  • Chained snapshot migrations: pure N → N+1 steps that are validated, frozen and walked to the exact target version, so old saves load safely.
  • An autosave scheduler with debounce, a leading-edge throttle and re-entrant suppression, for games that save every tick.
  • A namespaced key-value bridge over @capacitor/preferences for small settings.
  • The matching sql.js WebAssembly binaries, shipped as package assets.

Documentation · API · Architecture · Changelog

Install

npm install persistence-save @capacitor/core @capacitor-community/sqlite @capacitor/preferences

The three Capacitor packages are peer dependencies of the root entry. The 0.2 line is verified against @capacitor/core 8.5, @capacitor-community/sqlite 8.1 and @capacitor/preferences 8.0. Node.js 22, 24 and 26 are supported.

The autosave scheduler and the snapshot-migration walker have their own entries, persistence-save/autosave and persistence-save/migrations, which load no Capacitor code. The Capacitor peers are optional, so a game without Capacitor installs persistence-save alone and imports those. A game on another Capacitor major can import them too, but an optional peer is still checked when it is present: the install reports the peers unmet (a warning by default, an error under strict-peer-dependencies). The root entry needs the 8.x peers.

On the web, copy both WebAssembly assets into the directory you pass as wasmAssetsPath (for example with your bundler's static-copy step):

node_modules/persistence-save/dist/assets/sql-wasm.wasm
node_modules/persistence-save/dist/assets/sql-wasm-browser.wasm

They are also exported as persistence-save/assets/sql-wasm.wasm and persistence-save/assets/sql-wasm-browser.wasm.

Quick start

import { createPersistence } from 'persistence-save';

interface State {
  name: string;
  gold: number;
}

interface Snapshot extends State {
  version: number;
}

const saves = createPersistence<State, Snapshot>({
  dbName: 'com.example.game_v1',
  snapshotVersion: 2,
  serialize: (state) => ({ version: 2, ...state }),
  deserialize: ({ name, gold }) => ({ name, gold }),
  migrations: {
    1: (old) => ({ ...old, version: 2, gold: old.gold ?? 0 }),
  },
});

await saves.save('Slot 1', { name: 'Player One', gold: 7 });
const [latest] = await saves.list(); // newest first; corrupt rows are isolated, not thrown
const record = latest ? await saves.load(latest.id) : null;
console.log(record?.snapshot.gold); // 7

Migrations

Every source version, target version and migration output is a safe integer. Each migration receives a deeply frozen clone and is a pure N → N+1 step. Mutation attempts, null or non-object output, skips, overshoots, fractions, and a walk that does not end exactly at the target are rejected as typed version errors. Reading an older save migrates it in memory without rewriting the stored row; only an explicit save() writes the current format.

serialize() is validated before SQLite is opened: it must return a non-array object whose version exactly equals snapshotVersion. Snapshot size caps are measured in UTF-8 bytes on both read and write.

Sharing the connection

Games with their own SQLite tables can pass an idempotent initializeSchema(connection) callback and use withConnection() and flush() for their own statements. This keeps one connection manager and one web-store flush path. encryptionKeyPreference keeps an existing native SQLCipher key name when you adopt the package in a game that already encrypts its database.

Settings

Preferences store strings, so serialize structured values explicitly, and always use a stable reverse-domain namespace:

import { createPreferencesKv } from 'persistence-save';

const settings = createPreferencesKv('com.example.game');
await settings.set('audio.muted', 'false');

Autosave

import { createAutoSaveScheduler } from 'persistence-save';

const autosave = createAutoSaveScheduler({
  provider: () => (inMenu ? null : currentState()), // null skips the write
  save: (state) => saves.save('AutoSave', state),
  debounceMs: 400,
});

autosave.schedule(); // after a mutation; bursts coalesce into one write
autosave.scheduleThrottled('player.health', 2_000); // per-frame callers
addEventListener('pagehide', () => void autosave.flush()); // always saves
await autosave.flushPending(); // wait for a queued save; writes nothing if none is
autosave.pending; // true while a debounced or throttled save is queued

The scheduler takes any save, so it works over your own storage too, from the Capacitor-free entry:

import { createAutoSaveScheduler } from 'persistence-save/autosave';
import { migrateSnapshot } from 'persistence-save/migrations';

See the API reference for every option.

Development

mise install            # or: npm install --global corepack && corepack enable
pnpm install
pnpm verify

pnpm verify runs Biome, markdownlint, strict TypeScript, the tests with coverage, the ESM, CommonJS and type builds, publint, Are The Types Wrong, and a packed-consumer proof. That proof packs the package, installs the tarball with the exact Capacitor peers into a clean project with an empty home directory and only the public registry, then runs the ESM and CommonJS entry points, typechecks a separately written consumer, and compiles both WebAssembly assets. pnpm test:web-cold-reload separately packs the built package, starts a clean Vite consumer and uses a headed, muted Chromium session to prove that a flushed save has IndexedDB bytes and reopens in a freshly navigated JavaScript realm. Set PERSISTENCE_SAVE_CONSUMER_SOURCE=persistence-save@<version> to run the same proof against a published version.

See CONTRIBUTING.md. Releases are automated with Release Please and published to npm with provenance.

License

MIT. The bundled sql.js WebAssembly binaries are MIT-licensed by the sql.js authors; see THIRD_PARTY_NOTICES.md.