Skip to main content

db-core-persistence

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.

Informations de source

Dépôt
TanStack/db
Dernière activité de la source
24 septembre 2026 à 10:04
Langue détectée de SKILL.md
anglais
Étoiles
3 958
Forks
278

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
db-core/persistence
description
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.
type
sub-skill
library
db
library_version
0.6.17
sources
["TanStack/db:packages/db-sqlite-persistence-core/src/persisted.ts","TanStack/db:packages/browser-db-sqlite-persistence/src/index.ts","TanStack/db:packages/react-native-db-sqlite-persistence/src/index.ts","TanStack/db:packages/expo-db-sqlite-persistence/src/index.ts","TanStack/db:packages/electron-db-sqlite-persistence/src/index.ts","TanStack/db:packages/node-db-sqlite-persistence/src/index.ts","TanStack/db:examples/react/offline-transactions/src/db/persisted-todos.ts","TanStack/db:examples/react-native/shopping-list/src/db/collections.ts"]
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. ## Choosing a Platform Package | Platform | Package | Create function | | -------------- | ------------------------------------------------------------ | -------------------------------------------- | | Browser (OPFS) | `@tanstack/browser-db-sqlite-persistence` | `createBrowserWASQLitePersistence` | | React Native | `@tanstack/react-native-db-sqlite-persistence` | `createReactNativeSQLitePersistence` | | Expo | `@tanstack/expo-db-sqlite-persistence` | `createExpoSQLitePersistence` | | Electron | `@tanstack/electron-db-sqlite-persistence` | `createElectronSQLitePersistence` (renderer) | | Node.js | `@tanstack/node-db-sqlite-persistence` | `createNodeSQLitePersistence` | | Capacitor | `@tanstack/capacitor-db-sqlite-persistence` | `createCapacitorSQLitePersistence` | | Tauri | `@tanstack/tauri-db-sqlite-persistence` | `createTauriSQLitePersistence` | | Cloudflare DO | `@tanstack/cloudflare-durable-objects-db-sqlite-persistence` | `createCloudflareDOSQLitePersistence` | All platform packages re-export `persistedCollectionOptions` from the core. ## Local-Only Persistence (No Server) For purely local data with no sync backend: ```ts import { createCollection } from '@tanstack/react-db' import { createBrowserWASQLitePersistence, openBrowserWASQLiteOPFSDatabase, persistedCollectionOptions, } from '@tanstack/browser-db-sqlite-persistence' const database = await openBrowserWASQLiteOPFSDatabase({ databaseName: 'my-app.sqlite', }) const persistence = createBrowserWASQLitePersistence({ database, }) const draftsCollection = createCollection( persistedCollectionOptions<Draft, string>({ id: 'drafts', getKey: (d) => d.id, persistence, schemaVersion: 1, }), ) ``` Local-only collections provide `collection.utils.acceptMutations()` for applying mutations directly. ## Synced Persistence (Wrapping an Adapter) Spread an existing adapter's options into `persistedCollectionOptions` to add persistence on top of sync: ```ts import { createCollection } from '@tanstack/react-db' import { electricCollectionOptions } from '@tanstack/electric-db-collection' import { createReactNativeSQLitePersistence, persistedCollectionOptions, } from '@tanstack/react-native-db-sqlite-persistence' const persistence = createReactNativeSQLitePersistence({ database }) const todosCollection = createCollection( persistedCollectionOptions({ ...electricCollectionOptions({ id: 'todos', shapeOptions: { url: '/api/electric/todos' }, getKey: (item) => item.id, }), persistence, schemaVersion: 1, }), ) ``` 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` | Always leader; direct per-collection adapter routing | Every coordinator must implement `requestApplyCommittedTx(collectionId, tx)`. The method routes a complete committed transaction to the collection's supported writer, including truncate, rows, row metadata, collection metadata, and stream position. This is a required contract, not an optional capability. Do not feature-detect it, fall back to row-only mutation routing, or bypass the coordinator after source publication. Untyped custom coordinators that omit the method fail during collection configuration. `SingleProcessCoordinator` has no election or cross-process communication, but it is not a no-op persistence owner. It registers the resolved adapter for each collection and applies complete committed transactions through that adapter. 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. Browser example: ```ts import { BrowserCollectionCoordinator } from '@tanstack/browser-db-sqlite-persistence' const coordinator = new BrowserCollectionCoordinator({ dbName: 'my-app', }) // Pass to persistence const persistence = createBrowserWASQLitePersistence({ database, coordinator }) // Cleanup on shutdown coordinator.dispose() ``` Electron requires setup in both processes: ```ts // Main process import { exposeElectronSQLitePersistence } from '@tanstack/electron-db-sqlite-persistence' import { app, ipcMain } from 'electron' const disposeIpc = exposeElectronSQLitePersistence({ persistence, ipcMain }) app.on('before-quit', () => { disposeIpc() }) // Renderer process import { createElectronSQLitePersistence, ElectronCollectionCoordinator, } from '@tanstack/electron-db-sqlite-persistence' const coordinator = new ElectronCollectionCoordinator({ dbName: 'my-app' }) const persistence = createElectronSQLitePersistence({ ipcRenderer: window.electron.ipcRenderer, coordinator, }) ``` Electron persistence calls cross the renderer/main boundary through IPC. The `ElectronCollectionCoordinator` separately coordinates renderer instances with `BroadcastChannel` and Web Locks. It elects an owner per collection and routes the complete committed transaction through that collection's resolved renderer adapter to the main-process persistence owner. ## 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). ```ts 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: ```ts const persistence = createNodeSQLitePersistence({ database, appliedTxPruneMaxRows: 5_000, appliedTxPruneMaxAgeSeconds: 0, }) ``` 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: ```ts persistedCollectionOptions({ getKey: (d) => d.id, persistence, // missing id — generates random UUID each session, data won't persist across reloads }) ``` Correct: ```ts persistedCollectionOptions({ id: 'drafts', getKey: (d) => d.id, persistence, }) ``` 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: ```ts const persistence = createBrowserWASQLitePersistence({ database }) // Unsafe if multiple tabs share this database ``` Correct: ```ts const coordinator = new BrowserCollectionCoordinator({ dbName: 'my-app' }) const persistence = createBrowserWASQLitePersistence({ database, coordinator }) ``` 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 ```ts // 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).
Voir sur GitHub