Skip to content

Commit aee8920

Browse files
committed
Fix coalescing breaking isLoading/activeQueryId invariant (v1.0.15)
CRITICAL BUG: The impossible state (isLoading=true + activeQueryId=null) was caused by coalescing paths setting isLoading without setting activeQueryId. The Bug You Found: User reported seeing isLoading=true but activeQueryId=null, which should be impossible. This breaks the core invariant that activeQueryId should be set whenever isLoading=true. Root Cause: When a request coalesced to an existing in-flight request: - Line 1519: Collection coalescing set isLoading=true, no activeQueryId - Line 1576: Item coalescing set isLoading=true, no activeQueryId Timeline: 1. First request: activeQueryId=qid1, isLoading=true 2. Second request coalesces: isLoading=true (again), activeQueryId still qid1 3. First request completes: activeQueryId=null, isLoading=false 4. Second request sees: activeQueryId=null, isLoading=true ← IMPOSSIBLE STATE The Fix: 1. Removed isLoading=true assignments from coalescing paths 2. Added isCollectionLoading() function (parallel to isItemLoading) 3. Updated fetchCollection to sync isLoading with in-flight state 4. Updated fetchItem already had this sync (line 1713-1716) 5. Exported isCollectionLoading in public API Now both fetchItem and fetchCollection sync isLoading with actual in-flight state, maintaining the invariant without breaking it in coalescing paths. The Invariant: if (isLoading === true) then (activeQueryId !== null OR in-flight request exists) Version: 1.0.14 → 1.0.15
1 parent 9d80f0e commit aee8920

File tree

2 files changed

+19
-4
lines changed

2 files changed

+19
-4
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "verity-dl",
3-
"version": "1.0.14",
3+
"version": "1.0.15",
44
"description": "A framework agnostic data layer built to handle the communication layer between the server and the client seamlessly and keep the ui state always up to date with the source of truth",
55
"main": "verity/shared/static/lib/core.js",
66
"module": "verity/shared/static/lib/core.js",

verity/shared/static/lib/core.js

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,11 @@ function isItemLoading(typeName, id, levelName = null) {
12411241
return G.inFlightItm.has(key);
12421242
}
12431243

1244+
function isCollectionLoading(name, params) {
1245+
const key = collectionKey(name, params);
1246+
return G.inFlightCol.has(key);
1247+
}
1248+
12441249
function hasAnyInFlightRequests() {
12451250
return G.inFlightItm.size > 0 || G.inFlightCol.size > 0;
12461251
}
@@ -1516,7 +1521,8 @@ async function _startCollectionFetch(name, { force = false, params = undefined }
15161521

15171522
if (G.inFlightCol.has(inFlightKey)) {
15181523
const bucket = G.inFlightCol.get(inFlightKey);
1519-
if (!ref.meta.isLoading) assignRef(ref, { meta: { ...ref.meta, isLoading: true, error: null } });
1524+
// Don't update isLoading here - it will be synced with in-flight state if needed
1525+
// Setting isLoading=true without activeQueryId breaks the invariant
15201526
emitLifecycle("collection:fetch:coalesced", { name, params: snapshot, key: inFlightKey });
15211527
return bucket.promise;
15221528
}
@@ -1572,8 +1578,8 @@ async function _startItemFetch(typeName, id, levelName, { loud = false, force =
15721578
// If a request for this item/level is already in-flight, coalesce.
15731579
if (G.inFlightItm.has(key)) {
15741580
const bucket = G.inFlightItm.get(key);
1575-
// If a later caller is loud, reflect spinner even though we're reusing the same request
1576-
if (loud && !ref.meta.isLoading) assignRef(ref, { meta: { ...ref.meta, isLoading: true } });
1581+
// Don't update isLoading here - it will be synced at the end of fetchItem
1582+
// Setting isLoading=true without activeQueryId breaks the invariant (isLoading=true + activeQueryId=null)
15771583
emitLifecycle("item:fetch:coalesced", { ...eventBase, loud: !!loud, key });
15781584
return bucket.promise;
15791585
}
@@ -1699,6 +1705,13 @@ function fetchCollection(name, opts = {}) {
16991705
ref.meta.lastUsedAt = nowISO();
17001706
scheduleMemorySweep();
17011707
_startCollectionFetch(name, opts);
1708+
1709+
// Set isLoading based on actual in-flight state (source of truth)
1710+
const actuallyLoading = isCollectionLoading(name, opts.params);
1711+
if (ref.meta.isLoading !== actuallyLoading) {
1712+
assignRef(ref, { meta: { ...ref.meta, isLoading: actuallyLoading } });
1713+
}
1714+
17021715
return ref;
17031716
}
17041717

@@ -2139,6 +2152,7 @@ const DLCore = {
21392152
disconnectSse,
21402153
ingestDirectiveEnvelope,
21412154
isItemLoading,
2155+
isCollectionLoading,
21422156
hasAnyInFlightRequests,
21432157
};
21442158

@@ -2164,6 +2178,7 @@ if (typeof exports !== "undefined") {
21642178
exports.disconnectSse = disconnectSse;
21652179
exports.ingestDirectiveEnvelope = ingestDirectiveEnvelope;
21662180
exports.isItemLoading = isItemLoading;
2181+
exports.isCollectionLoading = isCollectionLoading;
21672182
exports.hasAnyInFlightRequests = hasAnyInFlightRequests;
21682183
}
21692184

0 commit comments

Comments
 (0)