@tanstack/db@0.9.2 · @tanstack/db-sqlite-persistence-core@0.2.23 · @tanstack/react-db@0.4.1 (latest at time of writing)
npm install
npm run node # the cause, no React, no browser
npm run browser # the symptom, in real ChromiumLoadSubsetFn is declared (options: LoadSubsetOptions) => true | Promise<void>.
A bare true is how an implementation says "this subset is already loaded,
there is nothing to await", and CollectionSyncManager.loadSubset branches on
it: if (result instanceof Promise) { trackLoadPromise(result) … } return true.
createWrappedSyncConfig in db-sqlite-persistence-core returns
loadSubset: async (options) => { … }. Being async, it is a Promise even
when everything it awaits is already settled and the underlying sync answered
true, so the true branch is unreachable for any persisted collection.
node-repro.mjs — the same collection options, with and without
persistedCollectionOptions:
no persistence : ready (2 rows loaded)
persisted : loading (2 rows loaded)
suspense.test.jsx — what that costs under useLiveSuspenseQuery. A
component that suspends before it mounts loses its refs, so every retry builds
a fresh live query, which is loading, which throws a fresh promise, which
resolves, which retries:
✓ an unpersisted on-demand collection settles under Suspense 54ms
× the same collection with persistence never leaves its fallback
→ the boundary never released; the component re-suspended 13778 times
There is no SQLite here. memory-adapter.js is an in-memory
PersistenceAdapter of the same shape as this repo's own
createRecordingAdapter in packages/db-sqlite-persistence-core/tests/persisted.test.ts.
The defect is in the sync wrapper that persistedCollectionOptions puts in
front of any adapter, so the storage backend is irrelevant.
The browser run uses @vitest/browser + playwright against real Chromium, so
the render loop is not a jsdom artefact.