-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Operating System
macOS Tahoe 26.3.1
Environment (if applicable)
Node.js v24.13.0
Firebase SDK Version
12.1.0
Firebase SDK Product(s)
DataConnect
Project Tooling
Angular 21 (but issue is framework-agnostic)
Detailed Problem Description
After executing a mutation via Firebase Data Connect SDK, related query caches are not invalidated. Subsequent query subscriptions continue to return stale cached data instead of fetching fresh results from the server.
Steps and code to reproduce issue
- Subscribe to a query (e.g.,
ListItems) usingsubscribe() - Execute a mutation that modifies the underlying data (e.g.,
CreateItem) - Observe that the query subscription still returns the old cached data
- Only a manual page refresh or explicit
executeQuery()call fetches updated data
Expected Behavior
After a mutation completes, related query caches should be invalidated (or at minimum, re-fetched), so that active subscriptions receive updated data.
Actual Behavior
Query caches persist indefinitely after mutations. The MutationManager and QueryManager are completely isolated with no cross-communication.
Root Cause Analysis
After reviewing the SDK source code (@firebase/data-connect/dist/index.esm.js), the following issues were identified:
1. PREFER_CACHE behavior is hardcoded with no fetch policy option
In QueryManager.addSubscription(), the SDK always returns cached data if available and only fetches from the server when no cache exists:
// QueryManager.addSubscription()
if (trackedQuery.currentCache !== null) {
// Returns cached data immediately
onResultCallback({
data: cachedData,
source: SOURCE_CACHE,
ref: queryRef,
fetchTime: trackedQuery.currentCache.fetchTime
});
}
// Only fetches if NO cache exists
if (!trackedQuery.currentCache) {
const promise = this.executeQuery(queryRef);
}There is no way to configure fetch policies like cache-and-network or network-only.
2. MutationManager is completely isolated from QueryManager
class MutationManager {
executeMutation(mutationRef) {
const result = this._transport.invokeMutation(mutationRef.name, mutationRef.variables);
// ... only tracks the promise in _inflight array
// NO cache invalidation logic
return withRefPromise;
}
}Both managers are instantiated independently with no reference to each other:
this._queryManager = new QueryManager(this._transport);
this._mutationManager = new MutationManager(this._transport);3. No dependency tracking between mutations and queries
TrackedQuery contains no metadata about data dependencies:
const newTrackedQuery = {
ref,
subscriptions: [],
currentCache: initialCache || null,
lastError: null
// No dependency info, no invalidation flags
};4. Date comparison is only used for cache version ordering
compareDates() is only used to compare two cached versions of the same query. It never checks whether a mutation has occurred since the cache was set.