Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
066b642
fix: enforce crash-only persistence coordination
KyleAMathews Sep 17, 2026
56ac0f4
chore: add persistence coordination changeset
KyleAMathews Sep 17, 2026
5ff207b
fix(sqlite): address coordinator review findings
KyleAMathews Sep 17, 2026
2934f15
ci: trigger PR checks
KyleAMathews Sep 18, 2026
ca446bc
docs: make persistence oracles literate
KyleAMathews Sep 21, 2026
1e1d2bc
Merge remote-tracking branch 'github/main' into rfc-1659-ws2-red-oracle
KyleAMathews Sep 21, 2026
dd22805
fix(sqlite): recover retained remote subset demand
KyleAMathews Sep 21, 2026
76434cb
Merge remote-tracking branch 'origin/main' into eval/pr-1845
KyleAMathews Sep 21, 2026
a64fd26
Merge remote-tracking branch 'github/main' into rfc-1659-ws2-red-oracle
KyleAMathews Sep 21, 2026
805da90
test(electric): bind external recovery coordinator
KyleAMathews Sep 21, 2026
de2da98
fix(sqlite): preserve follower remote subset routing
KyleAMathews Sep 21, 2026
40c2124
Merge concurrent PR #1845 updates
KyleAMathews Sep 21, 2026
10320f2
docs: close resolved persistence review gaps
KyleAMathews Sep 21, 2026
3665806
fix(sqlite): harden coordinator protocol boundaries
KyleAMathews Sep 22, 2026
e064fc5
fix(sqlite): preserve local subset lifecycle options
KyleAMathews Sep 22, 2026
a5b7077
docs(oracles): label coordinator coverage limits
KyleAMathews Sep 22, 2026
60d4c38
refactor(sqlite): share host coordinator engine
KyleAMathews Sep 22, 2026
9bb7eb8
fix(sqlite): pace leadership recovery
KyleAMathews Sep 22, 2026
93c924a
fix(sqlite): preserve coordinator failure boundaries
KyleAMathews Sep 22, 2026
e9085cf
fix(sqlite): harden remote subset lifecycles
KyleAMathews Sep 23, 2026
8fd406c
Merge remote-tracking branch 'origin/main' into rfc-1659-ws2-red-oracle
KyleAMathews Sep 23, 2026
365ee24
test(sqlite): strengthen coordinator oracle campaigns
KyleAMathews Sep 23, 2026
3ee18e5
test(browser-sqlite): preserve delivered mutation success
KyleAMathews Sep 23, 2026
11388db
Merge remote-tracking branch 'github/main' into rfc-1659-ws2-red-oracle
KyleAMathews Sep 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/enforce-crash-only-persistence-coordination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@tanstack/browser-db-sqlite-persistence': patch
'@tanstack/db-sqlite-persistence-core': patch
'@tanstack/electron-db-sqlite-persistence': patch
'@tanstack/db': patch
---

Require coordinators to route complete committed transactions through the per-collection persistence owner, with named fail-stop errors for indeterminate commits and durability failures. Add clone-safe remote-subset leases with exact release, recursive wire validation, and matching Browser and Electron coordination.
2 changes: 1 addition & 1 deletion docs/contributing/oracle-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ comment and the current API/architecture contract before extending its model.
| Opaque backend pagination | [window oracle](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.oracle.test.ts), [cache histories](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.cache-oracle.test.ts), [cache publication](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.publication-oracle.test.ts), [browser acquisition boundaries](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.boundary-oracle.test.ts), [QueryCollection integration](https://github.com/TanStack/db/blob/main/packages/query-db-collection/tests/cursor-pagination.integration.test.ts) | Full filter/sort/slice reference, opaque token transport, actual Query cache expiry/invalidation/GC, forced refresh during growth, protocol failure publication/recovery, bounded slice work, nested cancellation/replacement, reader abort, browser retry defaults, manual-write cache isolation, and production window publications. Stable backend sequences; not snapshot guarantees for changing endpoints. Peek-ahead remains enabled. |
| Electric and TrailBase | [Electric histories](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-oracle.property.test.ts), [recovery histories](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-recovery-oracle.test.ts), [held resume snapshots](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-resume-snapshot-races.test.ts), [PostgreSQL semantics](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/e2e/sql-predicate-semantics.e2e.test.ts), [TrailBase contract](https://github.com/TanStack/db/blob/main/packages/trailbase-db-collection/tests/ORACLE.md) | Installed SDK delivery/framing, independent predicates, exact subscription arguments, restart/reset lineage, held certification races, and late errors. The recovery fixtures use a mocked ShapeStream; they do not establish live Electric-service framing or native persistence-host behavior. |
| PowerSync | [tests](https://github.com/TanStack/db/tree/main/packages/powersync-db-collection/tests), `tests/correctness-oracle.test.ts` | Applied receipt positions crossed with held peers, native SQLite/SDK and cleanup evidence. Run the focused owner with the package's `test:oracles` command. A timeout mutant proves a progress failure, not every value assertion. |
| SQLite persistence and native hosts | [persisted histories](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/persisted.test.ts), [reset/resume histories](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/sqlite-core-adapter.test.ts), [dual-adapter resume snapshots](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/sqlite-resume-snapshot.test.ts), [driver contracts](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/contracts/sqlite-driver-contract.ts), [Node shared-handle scheduling](https://github.com/TanStack/db/blob/main/packages/node-db-sqlite-persistence/tests/node-driver.test.ts), [OP-SQLite shared-handle scheduling](https://github.com/TanStack/db/blob/main/packages/react-native-db-sqlite-persistence/tests/op-sqlite-driver.test.ts), [browser OPFS lifecycle](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/opfs-page-lifecycle-oracle.test.ts), [worker diagnostics](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/opfs-worker-diagnostics-oracle.test.ts), [113-law manifest](https://github.com/TanStack/db/blob/main/packages/db-collection-e2e/src/fixtures/persisted-conformance-manifest.ts) | Cache/remote rejection/peer/reopen histories, atomic reset/resume lineage, key-set evidence, dual-adapter races, exact driver results, same-handle Node and OP-SQLite transaction admission, controlled page/worker ownership, and diagnostic-cause retention. The reset/resume owners use sqlite3 CLI and in-memory node:sqlite seams; they do not prove multi-process WAL, mobile/Tauri, or other native-device execution. Distinct database handles rely on SQLite lock admission rather than one in-process queue. React Native hosts without async-context propagation must use the transaction driver supplied to the callback for nested work. Fake workers and synthetic page events do not prove native handle release or real bfcache admission. The manifest excludes progressive and move suites; registration and shim runs are not device execution. |
| SQLite persistence and native hosts | [persisted histories](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/persisted.test.ts), [reset/resume histories](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/sqlite-core-adapter.test.ts), [dual-adapter resume snapshots](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/sqlite-resume-snapshot.test.ts), [Browser composed-owner histories](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/per-collection-coordinator-oracle.test.ts), [Browser coordinator RPC](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/browser-coordinator.test.ts), [driver contracts](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/contracts/sqlite-driver-contract.ts), [Node shared-handle scheduling](https://github.com/TanStack/db/blob/main/packages/node-db-sqlite-persistence/tests/node-driver.test.ts), [OP-SQLite shared-handle scheduling](https://github.com/TanStack/db/blob/main/packages/react-native-db-sqlite-persistence/tests/op-sqlite-driver.test.ts), [browser OPFS lifecycle](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/opfs-page-lifecycle-oracle.test.ts), [worker diagnostics](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/opfs-worker-diagnostics-oracle.test.ts), [Electron IPC and composed owner](https://github.com/TanStack/db/blob/main/packages/electron-db-sqlite-persistence/tests/electron-ipc.test.ts), [113-law manifest](https://github.com/TanStack/db/blob/main/packages/db-collection-e2e/src/fixtures/persisted-conformance-manifest.ts) | Core cache/remote rejection/peer/reopen histories, atomic reset/resume lineage, key-set evidence, dual-adapter races, and exact driver results. Browser composes public source commits with per-collection elected-owner routing and covers the complete committed-transaction wire partition through deterministic Node transport seams. Remote-subset histories distinguish logical demand, physical acquisitions, exact acquisition leases, and released replay tombstones. Electron composes source commits with a per-collection renderer owner, IPC adapter, real SQLite, and reopen checks. Same-handle Node and OP-SQLite tests cover transaction admission. Controlled OPFS page/worker histories cover ownership and diagnostic-cause retention. The reset/resume owners use sqlite3 CLI and in-memory node:sqlite seams; they do not prove multi-process WAL, mobile/Tauri, or other native-device execution. Distinct database handles rely on SQLite lock admission rather than one in-process queue. React Native hosts without async-context propagation must use the transaction driver supplied to the callback for nested work. Fake workers and synthetic page events do not prove native handle release or real bfcache admission. The Browser composed seams are not real multi-context/OPFS-worker execution; the Electron harness is not an actual Electron process unless its explicit runtime-bridge mode runs. An ownerless elected node suppresses core routing, while a follower may route demand to the elected owner; host coordinators retry only classified transport or admission failures while demand remains retained. The manifest excludes progressive and move suites; registration and shim runs are not device execution. |
| Offline execution | [scheduler](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/KeyScheduler.property.test.ts), [leadership](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/leadership-replay.property.test.ts), [settlement](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/transaction-settlement.property.test.ts), [serialization](https://github.com/TanStack/db/blob/main/packages/offline-transactions/tests/transaction-serializer.property.test.ts) | Declarative FIFO eligibility, per-transaction outcomes, durable state and typed wire trees. Issued work may finish after ownership loss, but new work must not start. Exactly-once network execution is not promised. |
| Frameworks | [React conformance](https://github.com/TanStack/db/blob/main/packages/react-db/tests/conformance.test.tsx), [React pagination](https://github.com/TanStack/db/blob/main/packages/react-db/tests/infinite-query-conformance.test.tsx), [shared suites](https://github.com/TanStack/db/tree/main/packages/db-collection-e2e/src/suites) | Exact exposed rows/pages and each framework's own lifecycle cuts. A React witness does not prove Vue/Solid/Angular/Svelte scheduling. Preserve their receiving registrations. |
| Structural values and ordered primitives | [hash values](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash.property.test.ts), [hash graphs](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash-graph.property.test.ts), [mixed hash graphs](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash-mixed-graph.property.test.ts), [hash retry](https://github.com/TanStack/db/blob/main/packages/db-ivm/tests/hash-failure-retry.property.test.ts), [comparison](https://github.com/TanStack/db/blob/main/packages/db/tests/comparison.property.test.ts), [deep equality](https://github.com/TanStack/db/blob/main/packages/db/tests/utils.property.test.ts), [cursor](https://github.com/TanStack/db/blob/main/packages/db/tests/cursor.property.test.ts), [indexes](https://github.com/TanStack/db/blob/main/packages/db/tests/index-update.property.test.ts), [query identity](https://github.com/TanStack/db/blob/main/packages/db/tests/query/identity-output-shape-oracle.test.ts), [LIKE semantics](https://github.com/TanStack/db/blob/main/packages/db/tests/query/compiler/evaluators.test.ts) | Independent flat values, graph topology, algebraic laws, Map/group/sort recomputation, expression denotation, LIKE wildcard refinement, and compiled output bags. The LIKE owner covers boolean string matching and bounded work, not nullish three-valued logic or a general Unicode collation contract. Hash collision freedom is not promised. Unsupported composite cursors reject. |
Expand Down
56 changes: 55 additions & 1 deletion packages/browser-db-sqlite-persistence/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ is opt-in by passing a `BrowserCollectionCoordinator`.

- `createBrowserWASQLitePersistence(...)`
- `openBrowserWASQLiteOPFSDatabase(...)`
- `BrowserCollectionCoordinator`
- `persistedCollectionOptions(...)` (re-exported from core)

## Quick start (single-tab)
Expand Down Expand Up @@ -97,6 +98,58 @@ export const todosCollection = createCollection(
See [`examples/react/offline-transactions`](../../examples/react/offline-transactions/src/db/persisted-todos.ts)
for a full multi-tab example.

### Committed transaction ownership

The persisted sync wrapper sends every source transaction with durable effects
through the coordinator's required
`requestApplyCommittedTx(collectionId, tx)` method.
`BrowserCollectionCoordinator` routes the complete transaction to the current
leader for that collection. The leader applies it with the adapter registered
for the same collection id, including that collection's resolved mode and
`schemaVersion`.

The route preserves truncation, row changes, row metadata, collection metadata,
and stream position as one `PersistedTx`. It does not feature-detect a partial
route or fall back to row-only mutation RPC. A custom coordinator that omits
`requestApplyCommittedTx` is rejected while the collection is configured,
before its sync source can publish rows. An effect-free source commit neither
publishes nor consumes a coordinator sequence.

Single-tab mode uses the same complete transaction contract. Its
`SingleProcessCoordinator` skips election and channel traffic but still routes
the transaction to the resolved adapter for that collection.

If a mutating RPC loses its response, Browser coordination replays it only
while the requester still knows the same non-null leader id and term. An
unknown initial route or any leader/term change rejects with
`IndeterminateCommitError`; the application must reconcile the outcome. The
coordinator does not retry that mutation against an unknown or replacement
leader.

### Remote subset requests

`BrowserCollectionCoordinator.requestEnsureRemoteSubset(...)` validates and
projects the request before it chooses the local leader or `BroadcastChannel`
route. Registered owners receive the exported
`TransportedLoadSubsetOptions` type. It contains the supported
structured-clone wire data and excludes live `signal` and `subscription`
fields. Unsupported nested values fail immediately with
`RemoteSubsetWireValueError` and the exact value path; no owner callback or
channel post occurs. See the core package's remote subset wire contract for the
complete supported domain.

Each accepted request is an explicit lease. Retries of the same request object
reuse its acquisition identity, while distinct equal request objects remain
independent. Release is routed to the elected collection owner and unloads the
exact acquired options once. Leadership loss unloads the retiring owner's live
leases, and requesters replay still-live acquisitions against the next leader.
Registering a second owner for one collection throws
`DuplicateRemoteSubsetOwnerError`; no adapter fallback replaces the owner.
Remote follower transport/admission failures reject and retain demand for its
normal retry without entering the follower's local owner lifecycle. Terminal
release identities expire after the existing RPC dedupe horizon while delayed
duplicates inside that horizon remain idempotent.

## Notes

- `openBrowserWASQLiteOPFSDatabase(...)` starts a dedicated Web Worker and
Expand All @@ -112,7 +165,8 @@ for a full multi-tab example.
possible. The `pagehide` cleanup releases resources; it cannot await pending
writes or serve as a durability guarantee.
- Single-tab mode does not require `BroadcastChannel` or Web Locks for
correctness.
election, but committed transactions still go through the collection's
registered persistence owner.
- Multi-tab mode requires `BroadcastChannel` and the Web Locks API; both are
available in all modern browsers.
- OPFS capability failures are surfaced as `PersistenceUnavailableError`.
Expand Down
Loading
Loading