TanStack

Content temporarily unavailable

Deferred acquisition review, 2026-10-07

Scope: PR #2060 (issue-2023-network-on-subscriber) at 19588268c, reviewed by an external high-effort code review. The repair commit that adds this record sits on top of that revision. Owner: packages/db/tests/live-query-deferred-acquisition-oracle.test.ts.

Law

Normative law 15 in packages/db/src/query/live/ARCHITECTURE.md. Authority: the maintainer's decision that a live query starts no network work until it has a subscriber or a preload, with preload counting and eager sources included. This review sharpened the formulation: the subscriber must ask for data, a subscriber that survives cleanup still counts, and a read that waits for readiness counts as a preload.

Findings and evidence

FindingKindEvidenceOutcome
A source status change restarts deferred demandDefectNew peer command: a peer that starts an idle on-demand source made the live query acquire, 4 cases RED at the acquisition-count assertionGuard in restartDetachedDemands; removing it alone fails the same 4
A truncate reloads deferred demandDefectNew truncate command: 8 cases RED at the acquisition-count assertionGuard in handleTruncate; removing it alone fails the same 8
Cleanup resets the flag while an acquiring subscriber survivesDefect and formulationPinned block: 4 on-demand cases RED, no acquisition after restart, status stuck at loadingCleanup keeps the flag when a surviving subscription does not defer. "Always reset" fails the block; "any surviving subscriber" fails the deferring-survivor control
A deferring subscription raises the source's subscriber countConcept questionA Query Collection probe: the count went 0 to 1, but no refetch happenedNo change; harm not reproduced. Whether subscriberCount should count deferring subscriptions stays open
A resume that throws leaves later sources deferredMechanism confirmedProbe: the subscribe throws the first source's error, the live query enters error, and the second source stays idleNo change. The live query has already failed, the deferral is per subscription so another reader starts the source itself, and the remaining trigger is a throwing listener, a contract breach
toArrayWhenReady() / stateWhenReady() never resumeDefectPinned block: 2 cases RED, no acquisition after the readThe early-return branch marks a preload
The first commit is loading under the async resumeAccepted designMaintainer decision: loading first unless the collection is preloadedNo change
The changeset is patchAccepted designMaintainer decisionNo change
A stale comment promises a ready first commitMaintainabilitySource inspection, React and SvelteComments rewritten
The deferral expression is duplicatedMaintainabilitySource inspectionOne helper

preloaded-collection-first-paint previously rejected the "preload does not count" mutant only by timing out. It now bounds the preload, and the mutant fails the assertion the preload settles in React, Vue, and Svelte.

The mutants above were designed after reading the tests, so they are a self-review of this author's coverage, not an independent mutant gap hunt.

Second review, at c2fe18d21

A medium code review raised ten findings. Each claim was probed on that revision before any change.

FindingKindEvidenceOutcome
A deferring subscription raises the source's subscriber count, so a Query Collection refetchesDefect, openWith the reviewer's precondition (the last direct subscriber left first), a live query with startSync: true and no subscriber raised the count from 0 to 1 and the Query Collection fetched again. The first review's probe had no prior subscriberMaintainer decision: they count only subscribers that ask for data, while garbage collection still counts every subscription. A pinned oracle block and a Query Collection test fail without the change; ten tests that read subscriberCount to watch an abandoned render or orphan hold its source now observe the hold another way: db tests read the retention count, and React tests give the source a 1 ms gcTime and check that it survives while held and is reclaimed after
A resume that throws strands the other sources and makes preload() throwDefectPinned block: preload() threw synchronously instead of returning a rejected promiseListeners all run before the first error is rethrown; the preload entry points reject. Each half fails the block alone
A failed resume clears the subscription's deferring flagDefect by source inspectionObservable only when the failing source is itself a live query that is later cleaned up; no runtime witnessThe source starts before the flag clears
A truncate with retained stale rows stalls publicationRefuted at the preconditionRetained stale rows come only from source cleanup, which puts the dependent live query in terminal error firstNo change
A cleaned-up Suspense collection gets no in-render preloadPre-existing defectThe rerender showed no rows on this PR and on mainThe in-render preload covers cleaned-up; the new React witness fails without it
The first-value helper accepts any not-ready valueWeak assertionSource inspectionThe not-ready value is now exact: loading when the hook started the collection, idle for a supplied window it adjusts first
hasSubscriberOrPreload() falls back to trueRefuted: reachableA non-null assertion failed 8 scheduler tests that drive the builder without its CollectionFallback kept, with a comment naming where it is reached
Pooled preload() leaves the partition deferringRefuted at the preconditionThe partition unsubscribes when its source's cleanup starts, so no survivor is miscountedNo change
The change manager special-cases live-query CollectionsDesignSource inspectionNo change; a config-level capability would add surface for one caller
A test comment is out of orderMaintainabilitySource inspectionFixed

Third review, at 9fb2d810a

FindingKindEvidenceOutcome
A failed source start cannot be retried in the same sync runRefuted under the accepted contractPinned block: the failed start puts the source and its live query in error; cleanup and a new preload start the source again and load itNo change; the block keeps the supported recovery path
A Suspense render could hang after a failed startRefuted at the premiseThe live query does enter error, so useLiveSuspenseQuery surfaces itNo change
A subscriber or preload during a source's subscribe() is missedDefectA handler on the outer live query's own status never ran inside the window. A status handler on an inner live query did: subscribing to the inner query starts it, and a handler that preloads or subscribes to the outer query left it at loading with no acquisition. Pinned block, both requests REDAfter registering its listener, the source subscription resumes at once when a request already arrived
A ready read returns partial local rowsPre-existing contractBoth reads resolve at once when the Collection holds rows; they now also request the restNo change; waiting instead would change every Collection's ready reads
A preload rejected during cleanup leaves the flag setRefuted at the preconditionA live query's cleanup completes before the next call, so the preload was accepted and the restart acquired nothing extraNo change
Pooled preload() does not resume the partitionRefutedPooled views serve eager sources only, whose demand never detaches; the oracle's "preload after build" cases passNo change
Patch changeset, layering, the ?? true fallbackAlready decided or recorded aboveNo change