@@ -220,12 +220,16 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
220220 }
221221 mergeQueue [ key ] = [ changes ] ;
222222
223- mergeQueuePromise [ key ] = OnyxUtils . get ( key ) . then ( ( existingValue ) => {
223+ mergeQueuePromise [ key ] = Promise . resolve ( ) . then ( ( ) => {
224224 // Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
225225 if ( mergeQueue [ key ] == null ) {
226226 return Promise . resolve ( ) ;
227227 }
228228
229+ // Read the existing value at merge application time (not at queue time) so that
230+ // any intervening synchronous cache updates (e.g. from mergeCollection) are picked up.
231+ const existingValue = OnyxUtils . get ( key ) ;
232+
229233 try {
230234 const validChanges = mergeQueue [ key ] . filter ( ( change ) => {
231235 const { isCompatible, existingValueType, newValueType, isEmptyArrayCoercion} = utils . checkCompatibilityWithExistingValue ( change , existingValue ) ;
@@ -313,92 +317,98 @@ function clear(keysToPreserve: OnyxKey[] = []): Promise<void> {
313317 const defaultKeyStates = OnyxUtils . getDefaultKeyStates ( ) ;
314318 const initialKeys = Object . keys ( defaultKeyStates ) ;
315319
316- const promise = OnyxUtils . getAllKeys ( )
317- . then ( ( cachedKeys ) => {
318- cache . clearNullishStorageKeys ( ) ;
319-
320- const keysToBeClearedFromStorage : OnyxKey [ ] = [ ] ;
321- const keyValuesToResetIndividually : KeyValueMapping = { } ;
322- // We need to store old and new values for collection keys to properly notify subscribers when clearing Onyx
323- // because the notification process needs the old values in cache but at that point they will be already removed from it.
324- const keyValuesToResetAsCollection : Record <
325- OnyxKey ,
326- { oldValues : Record < string , KeyValueMapping [ OnyxKey ] | undefined > ; newValues : Record < string , KeyValueMapping [ OnyxKey ] | undefined > }
327- > = { } ;
328-
329- const allKeys = new Set ( [ ...cachedKeys , ...initialKeys ] ) ;
330-
331- // The only keys that should not be cleared are:
332- // 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
333- // status, or activeClients need to remain in Onyx even when signed out)
334- // 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
335- // to null would cause unknown behavior)
336- // 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
337- for ( const key of allKeys ) {
338- const isKeyToPreserve = keysToPreserve . some ( ( preserveKey ) => OnyxKeys . isKeyMatch ( preserveKey , key ) ) ;
339- const isDefaultKey = key in defaultKeyStates ;
340-
341- // If the key is being removed or reset to default:
342- // 1. Update it in the cache
343- // 2. Figure out whether it is a collection key or not,
344- // since collection key subscribers need to be updated differently
345- if ( ! isKeyToPreserve ) {
346- const oldValue = cache . get ( key ) ;
347- const newValue = defaultKeyStates [ key ] ?? null ;
348- if ( newValue !== oldValue ) {
349- cache . set ( key , newValue ) ;
350-
351- const collectionKey = OnyxKeys . getCollectionKey ( key ) ;
352-
353- if ( collectionKey ) {
354- if ( ! keyValuesToResetAsCollection [ collectionKey ] ) {
355- keyValuesToResetAsCollection [ collectionKey ] = { oldValues : { } , newValues : { } } ;
356- }
357- keyValuesToResetAsCollection [ collectionKey ] . oldValues [ key ] = oldValue ;
358- keyValuesToResetAsCollection [ collectionKey ] . newValues [ key ] = newValue ?? undefined ;
359- } else {
360- keyValuesToResetIndividually [ key ] = newValue ?? undefined ;
361- }
362- }
363- }
320+ const cachedKeys = OnyxUtils . getAllKeys ( ) ;
321+ cache . clearNullishStorageKeys ( ) ;
364322
365- if ( isKeyToPreserve || isDefaultKey ) {
366- continue ;
367- }
323+ // Clear pending merge queues so that any in-flight Onyx.merge() calls
324+ // don't overwrite the default values we're about to set.
325+ const mergeQueue = OnyxUtils . getMergeQueue ( ) ;
326+ const mergeQueuePromise = OnyxUtils . getMergeQueuePromise ( ) ;
327+ for ( const key of Object . keys ( mergeQueue ) ) {
328+ delete mergeQueue [ key ] ;
329+ delete mergeQueuePromise [ key ] ;
330+ }
368331
369- // If it isn't preserved and doesn't have a default, we'll remove it
370- keysToBeClearedFromStorage . push ( key ) ;
332+ const keysToBeClearedFromStorage : OnyxKey [ ] = [ ] ;
333+ const keyValuesToResetIndividually : KeyValueMapping = { } ;
334+ // We need to store old and new values for collection keys to properly notify subscribers when clearing Onyx
335+ // because the notification process needs the old values in cache but at that point they will be already removed from it.
336+ const keyValuesToResetAsCollection : Record <
337+ OnyxKey ,
338+ { oldValues : Record < string , KeyValueMapping [ OnyxKey ] | undefined > ; newValues : Record < string , KeyValueMapping [ OnyxKey ] | undefined > }
339+ > = { } ;
340+
341+ const allKeys = new Set ( [ ...cachedKeys , ...initialKeys ] ) ;
342+
343+ // The only keys that should not be cleared are:
344+ // 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
345+ // status, or activeClients need to remain in Onyx even when signed out)
346+ // 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
347+ // to null would cause unknown behavior)
348+ // 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
349+ for ( const key of allKeys ) {
350+ const isKeyToPreserve = keysToPreserve . some ( ( preserveKey ) => OnyxKeys . isKeyMatch ( preserveKey , key ) ) ;
351+ const isDefaultKey = key in defaultKeyStates ;
352+
353+ // If the key is being removed or reset to default:
354+ // 1. Update it in the cache
355+ // 2. Figure out whether it is a collection key or not,
356+ // since collection key subscribers need to be updated differently
357+ if ( ! isKeyToPreserve ) {
358+ const oldValue = cache . get ( key ) ;
359+ const newValue = defaultKeyStates [ key ] ?? null ;
360+ if ( newValue !== oldValue ) {
361+ cache . set ( key , newValue ) ;
362+
363+ const collectionKey = OnyxKeys . getCollectionKey ( key ) ;
364+
365+ if ( collectionKey ) {
366+ if ( ! keyValuesToResetAsCollection [ collectionKey ] ) {
367+ keyValuesToResetAsCollection [ collectionKey ] = { oldValues : { } , newValues : { } } ;
368+ }
369+ keyValuesToResetAsCollection [ collectionKey ] . oldValues [ key ] = oldValue ;
370+ keyValuesToResetAsCollection [ collectionKey ] . newValues [ key ] = newValue ?? undefined ;
371+ } else {
372+ keyValuesToResetIndividually [ key ] = newValue ?? undefined ;
373+ }
371374 }
375+ }
372376
373- // Exclude RAM-only keys to prevent them from being saved to storage
374- const defaultKeyValuePairs = Object . entries (
375- Object . keys ( defaultKeyStates )
376- . filter ( ( key ) => ! keysToPreserve . some ( ( preserveKey ) => OnyxKeys . isKeyMatch ( preserveKey , key ) ) && ! OnyxKeys . isRamOnlyKey ( key ) )
377- . reduce ( ( obj : KeyValueMapping , key ) => {
378- // eslint-disable-next-line no-param-reassign
379- obj [ key ] = defaultKeyStates [ key ] ;
380- return obj ;
381- } , { } ) ,
382- ) ;
377+ if ( isKeyToPreserve || isDefaultKey ) {
378+ continue ;
379+ }
383380
384- // Remove only the items that we want cleared from storage, and reset others to default
385- for ( const key of keysToBeClearedFromStorage ) cache . drop ( key ) ;
386- return Storage . removeItems ( keysToBeClearedFromStorage )
387- . then ( ( ) => connectionManager . refreshSessionID ( ) )
388- . then ( ( ) => Storage . multiSet ( defaultKeyValuePairs ) )
389- . then ( ( ) => {
390- DevTools . clearState ( keysToPreserve ) ;
391-
392- // Notify the subscribers for each key/value group so they can receive the new values
393- for ( const [ key , value ] of Object . entries ( keyValuesToResetIndividually ) ) {
394- OnyxUtils . keyChanged ( key , value ) ;
395- }
396- for ( const [ key , value ] of Object . entries ( keyValuesToResetAsCollection ) ) {
397- OnyxUtils . keysChanged ( key , value . newValues , value . oldValues ) ;
398- }
399- } ) ;
400- } )
401- . then ( ( ) => undefined ) ;
381+ // If it isn't preserved and doesn't have a default, we'll remove it
382+ keysToBeClearedFromStorage . push ( key ) ;
383+ }
384+
385+ // Exclude RAM-only keys to prevent them from being saved to storage
386+ const defaultKeyValuePairs = Object . entries (
387+ Object . keys ( defaultKeyStates )
388+ . filter ( ( key ) => ! keysToPreserve . some ( ( preserveKey ) => OnyxKeys . isKeyMatch ( preserveKey , key ) ) && ! OnyxKeys . isRamOnlyKey ( key ) )
389+ . reduce ( ( obj : KeyValueMapping , key ) => {
390+ // eslint-disable-next-line no-param-reassign
391+ obj [ key ] = defaultKeyStates [ key ] ;
392+ return obj ;
393+ } , { } ) ,
394+ ) ;
395+
396+ // Remove only the items that we want cleared from storage, and reset others to default
397+ for ( const key of keysToBeClearedFromStorage ) cache . drop ( key ) ;
398+ const promise = Storage . removeItems ( keysToBeClearedFromStorage )
399+ . then ( ( ) => connectionManager . refreshSessionID ( ) )
400+ . then ( ( ) => Storage . multiSet ( defaultKeyValuePairs ) )
401+ . then ( ( ) => {
402+ DevTools . clearState ( keysToPreserve ) ;
403+
404+ // Notify the subscribers for each key/value group so they can receive the new values
405+ for ( const [ key , value ] of Object . entries ( keyValuesToResetIndividually ) ) {
406+ OnyxUtils . keyChanged ( key , value ) ;
407+ }
408+ for ( const [ key , value ] of Object . entries ( keyValuesToResetAsCollection ) ) {
409+ OnyxUtils . keysChanged ( key , value . newValues , value . oldValues ) ;
410+ }
411+ } ) ;
402412
403413 return cache . captureTask ( TASK . CLEAR , promise ) as Promise < void > ;
404414 } ) ;
@@ -519,6 +529,11 @@ function update<TKey extends OnyxKey>(data: Array<OnyxUpdate<TKey>>): Promise<vo
519529 } ,
520530 ) ;
521531
532+ // Set operations must run before merge operations so their cache writes are
533+ // visible when mergeCollectionWithPatches reads previous values synchronously.
534+ if ( ! utils . isEmptyObject ( batchedCollectionUpdates . set ) ) {
535+ promises . push ( ( ) => OnyxUtils . partialSetCollection ( { collectionKey, collection : batchedCollectionUpdates . set as OnyxSetCollectionInput < OnyxKey > } ) ) ;
536+ }
522537 if ( ! utils . isEmptyObject ( batchedCollectionUpdates . merge ) ) {
523538 promises . push ( ( ) =>
524539 OnyxUtils . mergeCollectionWithPatches ( {
@@ -529,9 +544,6 @@ function update<TKey extends OnyxKey>(data: Array<OnyxUpdate<TKey>>): Promise<vo
529544 } ) ,
530545 ) ;
531546 }
532- if ( ! utils . isEmptyObject ( batchedCollectionUpdates . set ) ) {
533- promises . push ( ( ) => OnyxUtils . partialSetCollection ( { collectionKey, collection : batchedCollectionUpdates . set as OnyxSetCollectionInput < OnyxKey > } ) ) ;
534- }
535547 }
536548
537549 for ( const [ key , operations ] of Object . entries ( updateQueue ) ) {
0 commit comments