SQLite-backed persistence for TanStack DB collections. persistedCollectionOptions wraps any adapter (Electric, Query, PowerSync, or local-only) with durable local storage. Platform adapters: browser (WA-SQLite OPFS), React Native (op-sqlite), Expo (expo-sqlite), Electron (IPC), Node (better-sqlite3), Capacitor, Tauri, Cloudflare Durable Objects. Multi-tab/multi-process coordination via BrowserCollectionCoordinator / ElectronCollectionCoordinator / SingleProcessCoordinator. schemaVersion for migration resets. Local-only mode for offline-first without a server. Applied transaction log pruning and safe full-reload recovery.
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
Une commande directe contourne le prompt de vérification. Examinez la source avant de l'exécuter.
SQLite-backed persistence for TanStack DB collections. persistedCollectionOptions wraps any adapter (Electric, Query, PowerSync, or local-only) with durable local storage. Platform adapters: browser (WA-SQLite OPFS), React Native (op-sqlite), Expo (expo-sqlite), Electron (IPC), Node (better-sqlite3), Capacitor, Tauri, Cloudflare Durable Objects. Multi-tab/multi-process coordination via BrowserCollectionCoordinator / ElectronCollectionCoordinator / SingleProcessCoordinator. schemaVersion for migration resets. Local-only mode for offline-first without a server. Applied transaction log pruning and safe full-reload recovery.
This skill builds on db-core and db-core/collection-setup. Read those first.
SQLite Persistence
TanStack DB persistence adds a durable SQLite-backed layer to any collection. Data survives page reloads, app restarts, and offline periods. The server remains authoritative for synced collections -- persistence provides a local cache that hydrates instantly.
This works with any adapter: electricCollectionOptions, queryCollectionOptions, powerSyncCollectionOptions, etc. The persistedCollectionOptions wrapper intercepts the sync layer to persist data as it flows through.
Multi-Tab / Multi-Process Coordination
Coordinators handle leader election and cross-instance communication so only one tab/process owns the database writer.
Platform
Coordinator
Mechanism
Browser
BrowserCollectionCoordinator
BroadcastChannel + Web Locks
Electron
ElectronCollectionCoordinator
BroadcastChannel + Web Locks
Single-process (RN, Expo, Node, etc.)
SingleProcessCoordinator
No-op (always leader)
Browser persistence uses single-process semantics by default. That is correct
when the app runs in one tab at a time or each tab has its own database. Pass a
BrowserCollectionCoordinator only when multiple tabs share one OPFS database.
Electron persistence calls cross the renderer/main boundary through IPC. The
ElectronCollectionCoordinator separately coordinates renderer instances with
BroadcastChannel and Web Locks.
Schema Versioning
schemaVersion tracks the shape of persisted data. When the stored version doesn't match the code, the collection resets (drops and reloads from server for synced collections, or throws for local-only).
persistedCollectionOptions({
// ...schemaVersion: 2, // bump when you change the data shape
})
There is no custom migration function -- a version mismatch triggers a full reset. For synced collections this is safe because the server re-supplies the data.
Applied Transaction Log Pruning
The SQLite applied_tx log is a replay cache, not permanent history. Browser,
Capacitor, Cloudflare Durable Objects, Expo, Node, React Native, and Tauri
wrappers prune it inside write transactions by default, per collection:
appliedTxPruneMaxRows: 1_000
appliedTxPruneMaxAgeSeconds: 86_400 (24 hours)
Set either option to 0 to disable that limit, or raise it to retain a longer
replay window:
If a follower asks to recover from a point older than the retained log, it
falls back to a full reload. Pruning does not itself shrink the SQLite file;
use SQLite vacuum settings or separate maintenance when disk reclamation
matters. The defaults are exported as
DEFAULT_APPLIED_TX_PRUNE_MAX_ROWS and
DEFAULT_APPLIED_TX_PRUNE_MAX_AGE_SECONDS.
Raw createSQLiteCorePersistenceAdapter calls do not inject these defaults.
Electron uses whichever persistence adapter the main process supplies.
Key Options
Option
Type
Description
persistence
PersistedCollectionPersistence
Platform adapter + coordinator
schemaVersion
number
Data version (default 1). Bump on schema changes
id
string
Required for local-only. Collection identifier in SQLite
Common Mistakes
CRITICAL Using local-only persistence without an id
Wrong:
persistedCollectionOptions({
getKey: (d) => d.id,
persistence,
// missing id — generates random UUID each session, data won't persist across reloads
})
Without an explicit id, the code generates a random UUID each session, so persisted data is silently abandoned on every reload. Local-only persisted collections must always provide an id. Synced collections derive it from the adapter config.
HIGH Sharing one browser database across tabs without a coordinator
Wrong:
const persistence = createBrowserWASQLitePersistence({ database })
// Unsafe if multiple tabs share this database
Without a coordinator, multiple browser tabs that share one OPFS database can
write concurrently. Use BrowserCollectionCoordinator for that case. Do not
add it to a single-tab app merely because the runtime is a browser.
HIGH Not bumping schemaVersion after changing data shape
If you add, remove, or rename fields in your collection type but keep the same schemaVersion, the persisted SQLite data will have the old shape. For synced collections, bump the version to trigger a reset and re-sync.
MEDIUM Not disposing the coordinator on cleanup
// On app shutdown or hot module reload
coordinator.dispose()
await database.close?.()
Failing to dispose leaks BroadcastChannel subscriptions and Web Lock handles.
See also: db-core/collection-setup/SKILL.md — for adapter selection and collection configuration.
See also: offline/SKILL.md — for offline transaction queueing (complements persistence).