Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/fix-persisted-dual-source-readiness.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@tanstack/db-sqlite-persistence-core': patch
'@tanstack/browser-db-sqlite-persistence': patch
'@tanstack/electric-db-collection': patch
---

Allow eager persisted collections to become ready from either a compatible SQLite snapshot, including an empty snapshot, or an authoritative upstream snapshot. Keep usable local rows visible after a later upstream failure, while durable-write failures still enter the Collection error state and a later upstream ready signal may recover it. Serialize browser source snapshots with coordinator mutations and preserve exact startup, supersession, and durability failure behavior.
5 changes: 5 additions & 0 deletions .github/workflows/e2e-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ jobs:
cd examples/react/start-ssr-e2e
pnpm exec playwright install --with-deps chromium

- name: Run browser SQLite readiness E2E tests
run: |
cd packages/browser-db-sqlite-persistence
pnpm test:e2e:readiness

- name: Run React Start SSR E2E tests
run: |
cd examples/react/start-ssr-e2e
Expand Down
2 changes: 1 addition & 1 deletion docs/contributing/oracle-coverage.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,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), [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 and late errors. SDK fixtures and a real service test earn different credit. |
| 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), [driver contracts](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/contracts/sqlite-driver-contract.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, exact driver results, controlled page/worker ownership, and diagnostic-cause retention. 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), [Electric recovery](https://github.com/TanStack/db/blob/main/packages/electric-db-collection/tests/electric-recovery-oracle.test.ts), [coordinator readiness E2E](https://github.com/TanStack/db/blob/main/packages/browser-db-sqlite-persistence/e2e/electric-coordinator-readiness.opfs.spec.ts), [driver contracts](https://github.com/TanStack/db/blob/main/packages/db-sqlite-persistence-core/tests/contracts/sqlite-driver-contract.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) | Dual-source readiness, cache/remote rejection, queued hydration, peer/reopen histories, exact driver results, controlled page/worker ownership, and diagnostic-cause retention. 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. |
| 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) | Independent flat values, graph topology, algebraic laws, Map/group/sort recomputation, expression denotation, and compiled output bags. Hash collision freedom is not promised. Unsupported composite cursors reject. |
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="data:," />
<title>Electric coordinator readiness oracle</title>
</head>
<body>
<output id="oracle-status">running</output>
<script
type="module"
src="./electric-coordinator-readiness.opfs.ts"
></script>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { expect, test } from '@playwright/test'
import type { Page } from '@playwright/test'
import type { ReadinessOracleResult } from './electric-coordinator-readiness.opfs'

/**
* # Does the Chromium driver refine dual-source readiness?
*
* The contract is the same independent dual-source law as the core suite: an
* eager Collection becomes ready from a compatible local snapshot or an
* authoritative upstream source snapshot, while on-demand remains upstream
* gated. The four literal expected cells are independent of the browser driver
* and are bounded exhaustiveness over this declared matrix.
*
* Each test reads the fixture only after its observation checkpoint, then
* compares exact public rows, Collection status, ready-event and work counters,
* provider identity, race ordering, and cleanup results. Replay by Playwright
* title (the eager title includes its mode). A pre-fix network winner remains
* loading or exposes the stale OPFS row; a pre-fix eager local case remains
* blocked on Electric. The fixture limits still apply: one Chromium context,
* controlled HTTP responses, no live Electric service, and no PowerSync path.
*/

type Mode = `non-empty` | `empty` | `network-wins` | `on-demand`

async function readResult(
page: Page,
mode: Mode,
): Promise<ReadinessOracleResult> {
await page.goto(`/e2e/electric-coordinator-readiness.opfs.html?mode=${mode}`)
await page.waitForFunction(
() => window.__tanstackElectricCoordinatorReadiness !== undefined,
)
return page.evaluate(() => window.__tanstackElectricCoordinatorReadiness!)
}

function expectComplete(
result: ReadinessOracleResult,
): asserts result is Extract<ReadinessOracleResult, { status: `complete` }> {
if (result.status !== `complete`) {
throw new Error(result.primaryFailure)
}
}

const eagerCases = [
{
mode: `non-empty` as const,
rows: [{ id: `persisted`, title: `Persisted while upstream is pending` }],
},
{ mode: `empty` as const, rows: [] },
] as const

for (const expected of eagerCases) {
test(`eager ${expected.mode} OPFS snapshot becomes ready while Electric remains pending`, async ({
page,
}) => {
const result = await readResult(page, expected.mode)

expectComplete(result)
expect(result.status).toBe(`complete`)
expect(result.provider).toBe(
`Chromium OPFSCoopSyncVFS + BrowserCollectionCoordinator + Electric ShapeStream`,
)
expect(result.observation).toEqual({
mode: expected.mode,
status: `ready`,
rows: expected.rows,
readyEvents: 1,
hydrationCalls: 1,
upstreamRequests: 1,
readyBeforeHydrationRelease: false,
})
expect(result.cleanupFailures).toEqual([])
})
}

test(`on-demand remains upstream-gated with the same browser stack`, async ({
page,
}) => {
const result = await readResult(page, `on-demand`)

expectComplete(result)
expect(result.status).toBe(`complete`)
expect(result.observation).toEqual({
mode: `on-demand`,
status: `loading`,
rows: [],
readyEvents: 0,
hydrationCalls: 0,
upstreamRequests: 1,
readyBeforeHydrationRelease: false,
})
expect(result.cleanupFailures).toEqual([])
})

test(`an authoritative Electric snapshot wins before OPFS hydration finishes`, async ({
page,
}) => {
const result = await readResult(page, `network-wins`)

expectComplete(result)
expect(result.status).toBe(`complete`)
expect(result.observation.mode).toBe(`network-wins`)
expect(result.observation.status).toBe(`ready`)
expect(result.observation.rows).toEqual([
{ id: `network`, title: `Network winner` },
])
expect(result.observation.readyEvents).toBe(1)
expect(result.observation.hydrationCalls).toBe(1)
expect(result.observation.upstreamRequests).toBeGreaterThanOrEqual(1)
expect(result.observation.readyBeforeHydrationRelease).toBe(true)
expect(result.cleanupFailures).toEqual([])
})
Loading
Loading