Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
1c46713
docs: propose persisted demand coverage for offline on-demand queries
Oct 21, 2026
7925876
test(persistence): add persisted demand coverage oracle
Oct 21, 2026
5573948
test(persistence): close review gaps in demand coverage oracle
Oct 21, 2026
9f4bb3c
test(persistence): close second-round demand coverage review gaps
Oct 21, 2026
8537286
test(persistence): add order-only demand history to coverage oracle
Oct 21, 2026
601c4e6
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 21, 2026
4fecdc7
feat(persistence): settle covered on-demand demands from SQLite under…
Oct 21, 2026
42e15a3
test(persistence): end persisted demand coverage at a source truncate
Oct 21, 2026
cd2acea
docs: describe network-first initial rendering for on-demand Collections
Oct 21, 2026
0fc7c6e
docs: record persisted demand coverage implementation review
Oct 21, 2026
ba110cd
test(query-db): receive persisted demand coverage offline
Oct 21, 2026
929d986
test(react-db,solid-db): receive the joined on-demand network-first gate
Oct 21, 2026
a99106c
test(persistence): check the joined gate through a nested live query
Oct 21, 2026
22df9d4
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 21, 2026
5c0e04d
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 26, 2026
e4add5b
test(persistence): reach resets during a load and a source with two r…
Oct 26, 2026
3e83494
fix(persistence): record coverage only for current loads and gate eve…
Oct 26, 2026
00938e8
perf(db): ship the on-demand network-first gate only with persistence
Oct 26, 2026
3de5a92
docs(changeset): state the coordinator limit and obsolete-load rule
Oct 26, 2026
06e0285
test(persistence): reach row deletes, rotation, refetch, and source f…
Oct 26, 2026
f62f9dc
fix(persistence): end coverage with lost rows, rotation, and refetch
Oct 26, 2026
88ce0dd
test(persistence): reach a failure at once and a synchronous answer
Oct 26, 2026
e09bc62
fix(persistence): observe fast failures and keep synchronous answers
Oct 26, 2026
5ba9076
test(query-db): give the retention witness time on a loaded runner
Oct 26, 2026
0949bc4
test(persistence): close open coverage-oracle limits
Oct 26, 2026
200fd93
test(persistence): reach an expired cache claim around coverage reads
Oct 26, 2026
aebe8bd
fix(persistence): recover an expired claim instead of failing coverag…
Oct 26, 2026
5b0cc93
test(persistence): share one serialized node:sqlite test driver
Oct 26, 2026
5d85ce2
docs: relate persisted demand coverage to the cache eviction design
Oct 26, 2026
5948a3c
test: extend the demand coverage oracle for the fifth review round
Oct 26, 2026
fe6ba66
fix: keep coverage bookkeeping from changing a load, and windows exact
Oct 26, 2026
4293b00
test: pin the row-count rule for unordered demands
Oct 26, 2026
3464286
test: cover window ties and unindexed page moves in the demand covera…
Oct 26, 2026
b9a5e48
test: add a generated campaign and failure and release sweeps for dem…
Oct 26, 2026
eff9689
test: receive persisted demand coverage in Vue, Svelte, and Angular
Oct 26, 2026
692e27e
test: receive persisted demand coverage on better-sqlite3 and OPFS
Oct 26, 2026
bd4f286
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 26, 2026
deaf201
test: kill mutation-sweep survivors in the demand coverage oracle
Oct 27, 2026
d99b37f
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 27, 2026
0db1fa6
fix: follow main's Electric cleanup and persisted option types
Oct 27, 2026
f725123
test: close the remaining demand coverage and readiness mutant gaps
Oct 27, 2026
4855c67
test: receive persisted demand coverage on Electron, Cloudflare, and …
Oct 27, 2026
696d319
test: share the demand coverage judgment and add offline premises to …
Oct 27, 2026
cd9544c
chore: commit the demand coverage mutant harness and a nightly oracle…
Oct 27, 2026
86efc7f
test: judge the fixed demand coverage histories with the shared model
Oct 27, 2026
544c643
test: run every receiving witness through one shared contract and jud…
Oct 27, 2026
5f04443
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 27, 2026
a046c05
docs: record the shared demand coverage judgment and receiving contract
Oct 27, 2026
79f0fcf
test: register the campaign's replay owner and export the contract's …
Oct 27, 2026
b1cc549
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 27, 2026
0eb906a
test: make the campaign reach a window that holds other rows
Oct 27, 2026
9262ec8
Merge origin/main (#2168 source health) into investigate/progressive-…
Oct 28, 2026
5504fe7
test(persistence): observe the on-demand Collection's status in deman…
Oct 28, 2026
83b10fd
docs: type the progressive Electric example's rows
Oct 28, 2026
de9fec4
fix(persistence): end demand coverage at snapshots, aborts, and scope…
Oct 28, 2026
32404fa
Merge origin/main (#2278 oracle part declarations) into investigate/p…
Oct 28, 2026
d9bc29d
test(electric): judge progressive offline reads with the shared model
Oct 28, 2026
fb1ab96
test(persistence): close the final review's coverage questions
Oct 28, 2026
ff40b5a
Merge remote-tracking branch 'origin/main' into investigate/progressi…
Oct 28, 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
11 changes: 11 additions & 0 deletions .changeset/on-demand-network-first.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@tanstack/db-sqlite-persistence-core': minor
---

`initialRender: { strategy: 'network-first' }` now works for on-demand persisted Collections, including Electric's `progressive` mode. Previously the configuration threw `InvalidPersistedCollectionConfigError`.

A subset load that settles online records persisted demand coverage for its exact demand. In a later session, a load of the same demand settles on its rows from SQLite once `networkTimeoutMs` (default 3 seconds) has passed, or at once if the network load fails. The network load keeps running, and its rows publish when they arrive. If it fails after that, the query stays ready. A demand that never settled online still waits for the network. Coverage proves that the rows were complete when they were loaded, not that they are still current. A source truncate, a snapshot transaction, scoped recovery, or a cache-generation rotation clears it. A load that a reset made obsolete, a load that was released before it settled, and a load during the rest of a scoped-recovery sync run record none. Coverage also ends when SQLite holds fewer rows for the demand than when it settled, for example after a source delete or Query DB retention expiry. A `refetch: true` load always waits for the network. An ordered window with a `limit` stays covered only while SQLite holds the same rows for it.

A query that joins eager and on-demand sources becomes ready when every eager source is ready, which network-first allows from its restored rows after the deadline, and every demand the query reaches on an on-demand source has settled. A network failure alone does not settle an on-demand demand that is not covered.

This applies only with the default single-process coordinator. With a multi-tab coordinator, such as `BrowserCollectionCoordinator`, on-demand loads still wait for the network.
35 changes: 35 additions & 0 deletions .github/workflows/oracle-nightly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Nightly oracles

# Longer oracle campaigns than PR CI can afford. The generated demand
# coverage campaign runs with ten times its PR budget, and every hostile
# mutant in scripts/demand-coverage-mutants.mjs must fail a named test.
on:
schedule:
- cron: '17 6 * * *'
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

permissions:
contents: read

jobs:
demand-coverage:
name: Demand coverage campaigns and mutants
runs-on: ubuntu-latest
timeout-minutes: 240
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup Tools
uses: tanstack/config/.github/setup@e4b48f16568324f76f467aa4c2aac2f05db632c3
- name: Run the generated campaign with a larger budget
env:
TANSTACK_DB_ORACLE_RUNS_MULTIPLIER: '10'
run: pnpm --filter @tanstack/db-sqlite-persistence-core exec vitest run --coverage.enabled=false tests/persisted-demand-coverage-campaign-oracle.test.ts
- name: Run the demand coverage mutants
run: node scripts/demand-coverage-mutants.mjs
8 changes: 8 additions & 0 deletions docs/collections/electric-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,14 @@ isolated but needs a separate cross-version cleanup policy while older tabs
may still use it. Scoped recovery
does not certify the new generation for offline reuse.

With `initialRender: { strategy: 'network-first' }`, an on-demand or
progressive Collection can render a demand offline from SQLite after that exact
demand settled online in an earlier session. Each demand keeps its own
coverage in the current persisted cache generation. Scoped recovery, cache-claim
expiry, and a must-refetch end it, so after any of them a demand waits for the
network again. A demand that loads during the rest of a scoped-recovery sync
run records no coverage. See [On-demand Collections offline](../guides/sqlite-persistence.md#on-demand-collections-offline).

During scoped recovery, coordinator notifications about SQLite changes do not
hydrate, truncate, or replace public source rows. The Electric change stream
and demanded source snapshots own those rows until the sync run ends. A change
Expand Down
4 changes: 3 additions & 1 deletion docs/contributing/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ production queues, caches, or semantic helpers merely to share their names.
| persisted readiness | The persisted wrapper's internal record that the current sync run's persisted restore completed or failed. Network-first initial render reads it; Collection status is the only public signal. | Collection readiness, network availability, or row nonemptiness. |
| persisted cache generation | One version of an on-demand Collection's durable rows and resume metadata, identified by a monotonic generation number and a distinct storage ID. One current generation accepts new claims; a retired generation remains available to sync runs that already claim it. A stale run may create a new retired generation for its own recovery without replacing the current one. Rotating a generation does not change the public Collection identity. | Sync run, provider session, or source snapshot. |
| persisted cache claim | A sync run's expiring authority to read and write one persisted cache generation. It is checked before durable access and is separate from a TanStack DB acquisition lease. | Acquisition lease, subset demand, or proof that cached rows are authoritative. |
| network-first initial render | An opt-in persisted Collection setting, `initialRender: { strategy: 'network-first', networkTimeoutMs }`. The Collection waits for its sync adapter until the deadline passes or the remote fails, then a completed persisted restore that installed rows makes it `ready`. An empty restore keeps it `loading`. A remote failure before the restore completes waits for the restore. Network sync continues. | Collection readiness from the sync adapter, an SSR seed, or a separate status. |
| persisted subset restore | The persisted wrapper's read of one demand's request data from the current persisted cache generation, and publication of those rows to the Collection, in the current sync run. The result may be empty. | Persisted restore of an eager Collection, or proof that the rows are complete. |
| persisted demand coverage | A record, in one persisted cache generation, that a load of one exact demand reached applied settlement with its rows durably committed, and how many rows the demand read then. It counts only while the persisted subset restore returns at least that many rows; for an ordered window, only while it returns the same rows. It is the durable, cross-session form of the coverage that a settled acquisition establishes in one sync run: when a covered demand settles from SQLite, it is covered in that sense too. It proves completeness when that load settled, not freshness or broader extent. | Rows present in SQLite, a broader or overlapping demand, or persisted readiness. |
| network-first initial render | An opt-in persisted Collection setting, `initialRender: { strategy: 'network-first', networkTimeoutMs }`. The Collection waits for its sync adapter until the deadline passes or the remote fails, then a completed persisted restore that installed rows makes it `ready`. An empty restore keeps it `loading`. A remote failure before the restore completes waits for the restore. Network sync continues. An on-demand Collection instead settles each covered demand's load from SQLite once that load's wait passes or its remote load fails (persisted demand coverage); query readiness follows its loads. | Collection readiness from the sync adapter, an SSR seed, or a separate status. |

`source` names a role in a data flow; it does not own a lifecycle. Do not coin a
`source session`. Use `sync run` for the local Collection sync invocation
Expand Down
Loading
Loading