Skip to content

Commit 98639f0

Browse files
authored
fix(browser-db-sqlite-persistence): fairly schedule cold hydrations (#1868)
* fix(browser-db-sqlite-persistence): fairly schedule cold hydrations * fix(persistence): preserve unscheduled restart startup * fix(persistence): scope collection reset reloads * fix(persistence): scope sequence-gap recovery * fix(persistence): scope committed reloads * docs(test): explain persistence oracle contracts * test(browser-sqlite): surface OPFS oracle startup errors * test(persistence): clarify synchronous admission checkpoint * docs(oracles): label fairness coverage limits * fix(sqlite-persistence): preserve coordinator progress * test(persistence): complete gap recovery fixture * fix(sqlite-persistence): release coordinator retention state * test(sqlite-persistence): strengthen fairness oracle campaigns * fix(sqlite-persistence): harden mutation envelope deduplication * fix(sqlite-persistence): release queued reloads and fence stale startup reads * test: allow real SQLite restart oracle CI time * fix(sqlite-persistence): keep pre-apply aborts local and restore adapter routing * fix(sqlite-persistence): retain default adapter cache until release
1 parent 34c78be commit 98639f0

27 files changed

Lines changed: 6805 additions & 304 deletions
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@tanstack/db-sqlite-persistence-core': patch
3+
'@tanstack/browser-db-sqlite-persistence': patch
4+
'@tanstack/electron-db-sqlite-persistence': patch
5+
---
6+
7+
Schedule complete SQLite hydration units fairly without holding coordinator work inside the local hydration scope. Fence stale startup rows after a coordinator reset. Preserve per-Collection leader adapter routing, mutation results across transport retries, terminal coordinator disposal, real-adapter restart order, and promise-discovered shared scheduling.

‎.github/workflows/e2e-tests.yml‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ jobs:
7474
cd examples/react/start-ssr-e2e
7575
pnpm exec playwright install --with-deps chromium
7676
77+
- name: Run Browser SQLite OPFS fairness E2E tests
78+
run: |
79+
cd packages/browser-db-sqlite-persistence
80+
pnpm test:opfs-fairness
81+
7782
- name: Run React Start SSR E2E tests
7883
run: |
7984
cd examples/react/start-ssr-e2e

‎docs/contributing/oracle-coverage.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ comment and the current API/architecture contract before extending its model.
111111
| 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. |
112112
| 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 and durability races, source-order publication before durability, and late errors. The queued-presence property runs identical fixed/random generators plus isolated seed-and-path replay across insert, update, delete, and truncate callbacks. The recovery fixtures use a mocked ShapeStream; they do not establish live Electric-service framing or native persistence-host behavior. |
113113
| 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. |
114-
| 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. |
114+
| 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), [shared-driver fairness](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/tests/shared-driver-fairness-oracle.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, queued coordinator reload/gap notifications during startup, unscheduled startup/reset overlap, 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. The shared-driver fairness owner checks K=1 complete-logical-hydrate scheduling with identical fixed, random, and seed-plus-path campaigns. It records public rows, raw dequeue reach, and a persist-first FIFO hostile control. Its Chromium OPFS fixture refines the provider boundary but does not establish a browser matrix, elapsed-time latency, unbounded eventuality, or multi-process coordination. 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. |
115115
| SQLite expression-index planning | [Node expression-index oracle](https://github.com/TanStack/db/blob/main/packages/node-db-sqlite-persistence/tests/expression-index-oracle.test.ts) | RFC #1659 invariant 8 owns identical persisted-index and runtime-expression shapes. Independent expected keys are checked against direct captured SQL, adapter results, and named-index plans. Generated BigInts use SQLite's signed range; one fixed case checks legacy oversized-value reads. Other limits: bounded unqualified JSON paths/scalars, Node BetterSQLite, and no null, arbitrary raw SQL, or native-host planning. Run the package's `test:oracles` campaign. |
116116
| 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. |
117117
| 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. |
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
<link rel="icon" href="data:," />
7+
<title>Shared driver OPFS fairness oracle</title>
8+
</head>
9+
<body>
10+
<output id="oracle-status">running</output>
11+
<script type="module" src="./shared-driver-fairness.opfs.ts"></script>
12+
</body>
13+
</html>
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* Browser checkpoint assertions for the OPFS refinement. Expected public rows
3+
* are rebuilt from the scenario IDs rather than from production output. The
4+
* neutral case proves cold-query reach; the storm case requires no K=1
5+
* violation. Failure-before-checkpoint, semantic mismatch, driver cleanup, and
6+
* OPFS cleanup remain distinct outcomes so setup or teardown cannot satisfy the
7+
* scheduling law.
8+
*/
9+
import { expect, test } from '@playwright/test'
10+
import type { Page } from '@playwright/test'
11+
import type { OPFSOracleResult } from './shared-driver-fairness.opfs'
12+
13+
async function readOracleResult(
14+
page: Page,
15+
mode: `neutral` | `storm`,
16+
): Promise<OPFSOracleResult> {
17+
let rejectPageError!: (error: Error) => void
18+
const pageError = new Promise<never>((_resolve, reject) => {
19+
rejectPageError = reject
20+
})
21+
void pageError.catch(() => undefined)
22+
const onPageError = (error: Error) => {
23+
rejectPageError(
24+
new Error(
25+
`OPFS fairness page failed before publishing a result: ${error.message}`,
26+
),
27+
)
28+
}
29+
page.on(`pageerror`, onPageError)
30+
31+
try {
32+
await page.goto(`/e2e/shared-driver-fairness.opfs.html?mode=${mode}`)
33+
await Promise.race([
34+
page.waitForFunction(
35+
() => window.__tanstackDriverFairnessOracle !== undefined,
36+
),
37+
pageError,
38+
])
39+
return page.evaluate(() => window.__tanstackDriverFairnessOracle!)
40+
} finally {
41+
page.off(`pageerror`, onPageError)
42+
}
43+
}
44+
45+
function expectedHydratedCollections(scenarioId: string, count: number) {
46+
return Array.from({ length: count }, (_, index) => ({
47+
collectionId: `${scenarioId}-hydrate-${index}`,
48+
rows: [
49+
{ id: `row-${index}-0`, value: index * 10 },
50+
{ id: `row-${index}-1`, value: index * 10 + 1 },
51+
],
52+
}))
53+
}
54+
55+
test(`real Chromium OPFS fixture reaches and cleans up cold hydration`, async ({
56+
page,
57+
}) => {
58+
const result = await readOracleResult(page, `neutral`)
59+
60+
if (result.status !== `complete`) throw new Error(result.primaryFailure)
61+
expect(result.provider).toBe(`Chromium OPFSCoopSyncVFS worker`)
62+
expect(result.observation.admittedHydrateIds).toHaveLength(2)
63+
// These are actual public Collection rows captured after preload, compared
64+
// with seed values built independently by this browser assertion.
65+
expect(result.observation.hydratedCollections).toEqual(
66+
expectedHydratedCollections(`opfs-neutral-reach`, 2),
67+
)
68+
expect(
69+
result.observation.rawDequeues.some((entry) =>
70+
entry.sql.startsWith(`SELECT key, value, metadata, row_version FROM`),
71+
),
72+
).toBe(true)
73+
expect(result.observation.cleanupFailures).toEqual([])
74+
expect(result.opfsCleanupFailures).toEqual([])
75+
})
76+
77+
test(`real Chromium OPFS fixture bounds pending cold hydration behind persists`, async ({
78+
page,
79+
}) => {
80+
const result = await readOracleResult(page, `storm`)
81+
82+
if (result.status !== `complete`) throw new Error(result.primaryFailure)
83+
expect(result.provider).toBe(`Chromium OPFSCoopSyncVFS worker`)
84+
expect(result.observation.admittedHydrateIds).toHaveLength(4)
85+
expect(result.observation.hydratedCollections).toEqual(
86+
expectedHydratedCollections(`opfs-fixed-persist-storm`, 4),
87+
)
88+
// This is the semantic RED checkpoint. Setup, wall time, and cleanup are
89+
// reported independently and cannot satisfy this assertion.
90+
if (result.violation !== undefined) {
91+
throw new Error(
92+
`real OPFS fairness mismatch: ${JSON.stringify(result.violation)}; ` +
93+
`logical completion order: ${JSON.stringify(result.observation.logicalCompletionOrder)}; ` +
94+
`driver admissions: ${result.observation.driverAdmissions.length}; ` +
95+
`raw dequeues: ${result.observation.rawDequeues.length}; ` +
96+
`driver cleanup diagnostics: ${JSON.stringify(result.observation.cleanupFailures)}; ` +
97+
`OPFS cleanup diagnostics: ${JSON.stringify(result.opfsCleanupFailures)}`,
98+
)
99+
}
100+
expect(result.observation.cleanupFailures).toEqual([])
101+
expect(result.opfsCleanupFailures).toEqual([])
102+
})

0 commit comments

Comments
 (0)