@@ -347,9 +347,10 @@ function multiGet<TKey extends OnyxKey>(keys: CollectionKeyBase[]): Promise<Map<
347347 continue ;
348348 }
349349
350- const cacheValue = cache . get ( key ) as OnyxValue < TKey > ;
351- if ( cacheValue ) {
352- dataMap . set ( key , cacheValue ) ;
350+ // hasCacheForKey catches cached falsy values (0, '', false, null) as cache hits, which
351+ // a truthy check on the value would miss.
352+ if ( cache . hasCacheForKey ( key ) ) {
353+ dataMap . set ( key , cache . get ( key ) as OnyxValue < TKey > ) ;
353354 continue ;
354355 }
355356
@@ -401,6 +402,13 @@ function multiGet<TKey extends OnyxKey>(keys: CollectionKeyBase[]): Promise<Map<
401402 }
402403 }
403404
405+ // Prefer cache over stale storage if a concurrent write populated it during
406+ // the read — otherwise cache.merge(temp) below would resurrect dropped fields.
407+ if ( cache . hasCacheForKey ( key ) ) {
408+ dataMap . set ( key , cache . get ( key ) as OnyxValue < TKey > ) ;
409+ continue ;
410+ }
411+
404412 dataMap . set ( key , value as OnyxValue < TKey > ) ;
405413 temp [ key ] = value as OnyxValue < TKey > ;
406414 }
@@ -782,8 +790,12 @@ function remove<TKey extends OnyxKey>(key: TKey, isProcessingCollectionUpdate?:
782790
783791function reportStorageQuota ( error ?: Error ) : Promise < void > {
784792 return Storage . getDatabaseSize ( )
785- . then ( ( { bytesUsed, bytesRemaining} ) => {
786- Logger . logInfo ( `Storage Quota Check -- bytesUsed: ${ bytesUsed } bytesRemaining: ${ bytesRemaining } . Original error: ${ error } ` ) ;
793+ . then ( ( { bytesUsed, bytesRemaining, usageDetails} ) => {
794+ Logger . logInfo (
795+ `Storage Quota Check -- bytesUsed: ${ bytesUsed } bytesRemaining: ${ bytesRemaining } ${
796+ usageDetails ? ` usageDetails: ${ JSON . stringify ( usageDetails ) } ` : ''
797+ } . Original error: ${ error } `,
798+ ) ;
787799 } )
788800 . catch ( ( dbSizeError ) => {
789801 Logger . logAlert ( `Unable to get database size. getDatabaseSize error: ${ dbSizeError } . Original error: ${ error } ` ) ;
@@ -1597,12 +1609,17 @@ function mergeCollectionWithPatches<TKey extends CollectionKeyBase>(
15971609 // finalMergedCollection contains all the keys that were merged, without the keys of incompatible updates
15981610 const finalMergedCollection = { ...existingKeyCollection , ...newCollection } ;
15991611
1600- // Pre-warm cache for any existing storage keys that aren't yet in cache. get() is a no-op
1601- // (sync-resolved) for cache hits, and on a cache miss it reads from storage and writes the
1602- // value back to cache. This is required so the subsequent cache.merge() merges the new delta
1603- // into the real previous storage value (rather than starting from `undefined` and dropping
1604- // the existing keys).
1605- return Promise . all ( existingKeys . map ( ( key ) => get ( key ) ) ) . then ( ( ) => {
1612+ // Pre-warm cache for cache-miss existingKeys so cache.merge() merges the new delta into
1613+ // the real previous storage value. Fast path (all warm) skips the pre-warm to preserve
1614+ // promise-chain depth; slow path batches the misses into one Storage.multiGet.
1615+ const hasColdExistingKey = existingKeys . some ( ( key ) => ! cache . hasCacheForKey ( key ) ) ;
1616+ // Swallow pre-warm read failures so a transient Storage.multiGet rejection doesn't
1617+ // skip the cache.merge() + keysChanged() below. Subscribers still see the merge even
1618+ // when storage reads fail.
1619+ const prewarmPromise = hasColdExistingKey
1620+ ? multiGet ( existingKeys ) . catch ( ( err ) => Logger . logInfo ( `mergeCollectionWithPatches pre-warm failed; proceeding with cache-only merge. Error: ${ err } ` ) )
1621+ : Promise . resolve ( ) ;
1622+ return prewarmPromise . then ( ( ) => {
16061623 // Snapshot previous values from the (now-warm) cache for keysChanged's diff, then update
16071624 // cache and notify subscribers synchronously BEFORE issuing storage writes. This matches
16081625 // the cache-first / storage-second invariant followed by every other Onyx write method
0 commit comments