Commit c1247e8
fix(db): prevent on-demand live query from being marked ready before data loads (#1081)
* fix(react-db): prevent suspense from releasing before data is loaded in on-demand mode
When using useLiveSuspenseQuery with on-demand sync mode, the suspense
boundary would sometimes release before the query's data was actually
loaded. This happened because the live query collection was marked as
ready immediately when the source collection was already ready, even
though the loadSubset operation for the specific query hadn't completed.
This fix ensures that useLiveSuspenseQuery also suspends while
isLoadingSubset is true, waiting for the initial subset load to complete
before releasing the suspense boundary.
* test(react-db): add test for isLoadingSubset suspense behavior
This test verifies that useLiveSuspenseQuery holds the suspense boundary
when isLoadingSubset is true, even if the collection status is 'ready'.
The test confirms:
1. WITHOUT the fix: suspense releases prematurely (test fails)
2. WITH the fix: suspense waits for isLoadingSubset to be false (test passes)
* ci: apply automated fixes
* fix(db): prevent live query from being marked ready before subset data is loaded
In on-demand sync mode, the live query collection was being marked as 'ready'
before the subset data finished loading. This caused useLiveQuery to return
isReady=true with empty data, and useLiveSuspenseQuery to release suspense
prematurely.
The fix:
1. Added isLoadingSubset check in updateLiveQueryStatus() to prevent marking
ready while subset is loading
2. Added listener for loadingSubset:change events to trigger ready check
when subset loading completes
3. Added test case that verifies the correct timing behavior
* ci: apply automated fixes
* test(db): verify status is 'loading' while isLoadingSubset is true
* fix(db): register loadingSubset listener before subscribing to avoid race condition
The loadingSubset:change listener was registered after subscribeToAllCollections(),
which could cause a race condition where the event fires before the listener is
registered. This resulted in the live query never becoming ready.
Also adds await in electric test to account for async subset loading.
* fix(db): fix race condition in subscription status tracking
Register the status:change listener BEFORE checking the current
subscription status to avoid missing status transitions.
Previously, if loadSubset completed very quickly, the status could
change from 'loadingSubset' to 'ready' between checking the status
and registering the listener, causing the tracked promise to never
resolve and the live query to never become ready.
* ci: apply automated fixes
* fix(db): check source collections' isLoadingSubset instead of live query's
The previous fix incorrectly checked isLoadingSubset on the live query
collection itself, but the loadSubset/trackLoadPromise mechanism runs on
SOURCE collections during on-demand sync, so the live query's isLoadingSubset
was always false.
This fix:
- Adds anySourceCollectionLoadingSubset() to check if any source collection
has isLoadingSubset=true
- Listens for loadingSubset:change events on source collections instead of
the live query collection
* ci: apply automated fixes
* fix(db): check live query collection's isLoadingSubset instead of source collections
Reverts the change to check source collections' isLoadingSubset, which was causing
test timeouts in query-db-collection tests. The live query collection's isLoadingSubset
is correctly updated by CollectionSubscriber.trackLoadPromise() which tracks loading
on the live query collection itself.
Also updates changeset to accurately describe the fix.
* ci: apply automated fixes
* fix(db): fix race condition where status listener was registered after snapshot trigger
The subscription's status:change listener was being registered AFTER the snapshot
was triggered (via requestSnapshot/requestLimitedSnapshot). This meant that if the
loadSubset promise resolved quickly (or synchronously), the status transition from
'loadingSubset' to 'ready' could be missed entirely.
Changes:
- Refactored subscribeToChanges() to split subscription creation from snapshot triggering
- subscribeToMatchingChanges() and subscribeToOrderedChanges() now return both the
subscription AND a triggerSnapshot function
- The status listener is registered AFTER getting the subscription but BEFORE calling
triggerSnapshot()
- Added deferSnapshot option to subscribeChanges() to prevent automatic snapshot request
- For non-ordered queries, continue using trackLoadSubsetPromise: false to maintain
compatibility with query-db-collection's destroyed observer handling
- Updated test for source collection isLoadingSubset independence
- Added regression test for the race condition fix
* ci: apply automated fixes
* refactor: streamline on-demand suspense fix
- Remove useLiveSuspenseQuery changes (not needed with core fix)
- Remove artificial test that manually sets isLoadingSubset after ready
- Update changeset to accurately describe the fix
- Remove react-db changeset (no react-db source changes)
The core fix (deferSnapshot + ready gating) is sufficient. The suspense
hook doesn't need additional isLoadingSubset checks because the live
query collection won't be marked ready while isLoadingSubset is true.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ci: apply automated fixes
* test: add sync/instant loadSubset resolution test
Tests the tricky case where loadSubset returns Promise.resolve() immediately.
This proves the race condition fix works even when the status transition
happens synchronously, not just with delayed promises.
Addresses reviewer feedback to strengthen test coverage.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor(db): replace deferSnapshot with onStatusChange option
Simplify the subscription API by replacing the error-prone 3-step
deferSnapshot pattern with a cleaner onStatusChange callback option.
The listener is now registered internally BEFORE any snapshot is
requested, guaranteeing no status transitions are missed regardless
of how quickly loadSubset resolves.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* ci: apply automated fixes
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>1 parent 14d4cac commit c1247e8
File tree
8 files changed
+426
-50
lines changed- .changeset
- packages
- db
- src
- collection
- query/live
- tests/query
- electric-db-collection/tests
- react-db/tests
8 files changed
+426
-50
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
127 | 127 | | |
128 | 128 | | |
129 | 129 | | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
130 | 137 | | |
131 | 138 | | |
132 | 139 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
566 | 566 | | |
567 | 567 | | |
568 | 568 | | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
569 | 584 | | |
570 | 585 | | |
571 | 586 | | |
| |||
793 | 808 | | |
794 | 809 | | |
795 | 810 | | |
796 | | - | |
797 | | - | |
| 811 | + | |
| 812 | + | |
| 813 | + | |
| 814 | + | |
| 815 | + | |
| 816 | + | |
| 817 | + | |
| 818 | + | |
798 | 819 | | |
799 | 820 | | |
800 | 821 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
9 | 12 | | |
10 | 13 | | |
11 | 14 | | |
| |||
53 | 56 | | |
54 | 57 | | |
55 | 58 | | |
56 | | - | |
57 | 59 | | |
58 | | - | |
59 | | - | |
60 | | - | |
61 | | - | |
62 | | - | |
63 | | - | |
64 | | - | |
65 | | - | |
66 | | - | |
67 | | - | |
68 | | - | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | 60 | | |
75 | | - | |
| 61 | + | |
| 62 | + | |
76 | 63 | | |
77 | 64 | | |
78 | 65 | | |
| |||
89 | 76 | | |
90 | 77 | | |
91 | 78 | | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
100 | 83 | | |
101 | | - | |
| 84 | + | |
102 | 85 | | |
103 | 86 | | |
104 | 87 | | |
| |||
108 | 91 | | |
109 | 92 | | |
110 | 93 | | |
111 | | - | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
112 | 122 | | |
113 | 123 | | |
114 | 124 | | |
| |||
119 | 129 | | |
120 | 130 | | |
121 | 131 | | |
122 | | - | |
123 | 132 | | |
124 | 133 | | |
125 | 134 | | |
| |||
179 | 188 | | |
180 | 189 | | |
181 | 190 | | |
182 | | - | |
183 | | - | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
184 | 194 | | |
185 | 195 | | |
186 | 196 | | |
187 | 197 | | |
188 | 198 | | |
189 | 199 | | |
190 | | - | |
191 | | - | |
192 | | - | |
193 | | - | |
194 | | - | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
195 | 203 | | |
196 | 204 | | |
197 | 205 | | |
| 206 | + | |
198 | 207 | | |
199 | 208 | | |
200 | 209 | | |
| |||
203 | 212 | | |
204 | 213 | | |
205 | 214 | | |
206 | | - | |
| 215 | + | |
| 216 | + | |
207 | 217 | | |
208 | 218 | | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
209 | 222 | | |
210 | 223 | | |
211 | 224 | | |
212 | 225 | | |
213 | 226 | | |
214 | | - | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
215 | 231 | | |
216 | 232 | | |
217 | | - | |
218 | | - | |
| 233 | + | |
| 234 | + | |
219 | 235 | | |
220 | 236 | | |
| 237 | + | |
221 | 238 | | |
| 239 | + | |
222 | 240 | | |
223 | 241 | | |
224 | 242 | | |
| |||
236 | 254 | | |
237 | 255 | | |
238 | 256 | | |
| 257 | + | |
239 | 258 | | |
240 | 259 | | |
241 | 260 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
798 | 798 | | |
799 | 799 | | |
800 | 800 | | |
| 801 | + | |
| 802 | + | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
801 | 807 | | |
802 | 808 | | |
803 | 809 | | |
| |||
0 commit comments