@@ -1579,47 +1579,52 @@ function mergeCollectionWithPatches<TKey extends CollectionKeyBase>(
15791579 // because we will simply overwrite the existing values in storage.
15801580 const keyValuePairsForNewCollection = prepareKeyValuePairsForStorage ( newCollection , true ) ;
15811581
1582- const promises = [ ] ;
1583-
1584- // We need to get the previously existing values so we can compare the new ones
1585- // against them, to avoid unnecessary subscriber updates.
1586- const previousCollectionPromise = Promise . all ( existingKeys . map ( ( key ) => get ( key ) . then ( ( value ) => [ key , value ] ) ) ) . then ( Object . fromEntries ) ;
1587-
1588- // New keys will be added via multiSet while existing keys will be updated using multiMerge
1589- // This is because setting a key that doesn't exist yet with multiMerge will throw errors
1590- // We can skip this step for RAM-only keys as they should never be saved to storage
1591- if ( ! OnyxKeys . isRamOnlyKey ( collectionKey ) && keyValuePairsForExistingCollection . length > 0 ) {
1592- promises . push ( Storage . multiMerge ( keyValuePairsForExistingCollection ) ) ;
1593- }
1594-
1595- // We can skip this step for RAM-only keys as they should never be saved to storage
1596- if ( ! OnyxKeys . isRamOnlyKey ( collectionKey ) && keyValuePairsForNewCollection . length > 0 ) {
1597- promises . push ( Storage . multiSet ( keyValuePairsForNewCollection ) ) ;
1598- }
1599-
16001582 // finalMergedCollection contains all the keys that were merged, without the keys of incompatible updates
16011583 const finalMergedCollection = { ...existingKeyCollection , ...newCollection } ;
16021584
1603- // Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
1604- // and update all subscribers
1605- const promiseUpdate = previousCollectionPromise . then ( ( previousCollection ) => {
1585+ // Pre-warm cache for any existing storage keys that aren't yet in cache. get() is a no-op
1586+ // (sync-resolved) for cache hits, and on a cache miss it reads from storage and writes the
1587+ // value back to cache. This is required so the subsequent cache.merge() merges the new delta
1588+ // into the real previous storage value (rather than starting from `undefined` and dropping
1589+ // the existing keys).
1590+ return Promise . all ( existingKeys . map ( ( key ) => get ( key ) ) ) . then ( ( ) => {
1591+ // Snapshot previous values from the (now-warm) cache for keysChanged's diff, then update
1592+ // cache and notify subscribers synchronously BEFORE issuing storage writes. This matches
1593+ // the cache-first / storage-second invariant followed by every other Onyx write method
1594+ // (setWithRetry, applyMerge, setCollectionWithRetry, partialSetCollection, clear),
1595+ // ensuring subscribers still reflect the merged data even if the subsequent storage
1596+ // write fails.
1597+ const previousCollection = getCachedCollection ( collectionKey , existingKeys ) ;
16061598 cache . merge ( finalMergedCollection ) ;
16071599 keysChanged ( collectionKey , finalMergedCollection , previousCollection ) ;
1608- } ) ;
16091600
1610- return Promise . all ( promises )
1611- . catch ( ( error ) =>
1612- retryOperation (
1613- error ,
1614- mergeCollectionWithPatches ,
1615- { collectionKey, collection : resultCollection as OnyxMergeCollectionInput < TKey > , mergeReplaceNullPatches, isProcessingCollectionUpdate} ,
1616- retryAttempt ,
1617- ) ,
1618- )
1619- . then ( ( ) => {
1620- sendActionToDevTools ( METHOD . MERGE_COLLECTION , undefined , resultCollection ) ;
1621- return promiseUpdate ;
1622- } ) ;
1601+ const promises = [ ] ;
1602+
1603+ // New keys will be added via multiSet while existing keys will be updated using multiMerge
1604+ // This is because setting a key that doesn't exist yet with multiMerge will throw errors
1605+ // We can skip this step for RAM-only keys as they should never be saved to storage
1606+ if ( ! OnyxKeys . isRamOnlyKey ( collectionKey ) && keyValuePairsForExistingCollection . length > 0 ) {
1607+ promises . push ( Storage . multiMerge ( keyValuePairsForExistingCollection ) ) ;
1608+ }
1609+
1610+ // We can skip this step for RAM-only keys as they should never be saved to storage
1611+ if ( ! OnyxKeys . isRamOnlyKey ( collectionKey ) && keyValuePairsForNewCollection . length > 0 ) {
1612+ promises . push ( Storage . multiSet ( keyValuePairsForNewCollection ) ) ;
1613+ }
1614+
1615+ return Promise . all ( promises )
1616+ . catch ( ( error ) =>
1617+ retryOperation (
1618+ error ,
1619+ mergeCollectionWithPatches ,
1620+ { collectionKey, collection : resultCollection as OnyxMergeCollectionInput < TKey > , mergeReplaceNullPatches, isProcessingCollectionUpdate} ,
1621+ retryAttempt ,
1622+ ) ,
1623+ )
1624+ . then ( ( ) => {
1625+ sendActionToDevTools ( METHOD . MERGE_COLLECTION , undefined , resultCollection ) ;
1626+ } ) ;
1627+ } ) ;
16231628 } )
16241629 . then ( ( ) => undefined ) ;
16251630}
0 commit comments