22import { deepEqual } from 'fast-equals' ;
33import lodashClone from 'lodash/clone' ;
44import type { ValueOf } from 'type-fest' ;
5+ import lodashPick from 'lodash/pick' ;
56import DevTools from './DevTools' ;
67import * as Logger from './Logger' ;
78import type Onyx from './Onyx' ;
@@ -74,6 +75,8 @@ const lastConnectionCallbackData = new Map<number, OnyxValue<OnyxKey>>();
7475
7576let snapshotKey : OnyxKey | null = null ;
7677
78+ let fullyMergedSnapshotKeys : Set < string > | undefined ;
79+
7780// Keeps track of the last subscriptionID that was used so we can keep incrementing it
7881let lastSubscriptionID = 0 ;
7982
@@ -135,8 +138,9 @@ function setSkippableCollectionMemberIDs(ids: Set<string>): void {
135138 * @param keys - `ONYXKEYS` constants object from Onyx.init()
136139 * @param initialKeyStates - initial data to set when `init()` and `clear()` are called
137140 * @param evictableKeys - This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal.
141+ * @param fullyMergedSnapshotKeys - Array of snapshot collection keys where full merge is supported and data structure can be changed after merge.
138142 */
139- function initStoreValues ( keys : DeepRecord < string , OnyxKey > , initialKeyStates : Partial < KeyValueMapping > , evictableKeys : OnyxKey [ ] ) : void {
143+ function initStoreValues ( keys : DeepRecord < string , OnyxKey > , initialKeyStates : Partial < KeyValueMapping > , evictableKeys : OnyxKey [ ] , fullyMergedSnapshotKeysParam ?: string [ ] ) : void {
140144 // We need the value of the collection keys later for checking if a
141145 // key is a collection. We store it in a map for faster lookup.
142146 const collectionValues = Object . values ( keys . COLLECTION ?? { } ) as string [ ] ;
@@ -155,6 +159,7 @@ function initStoreValues(keys: DeepRecord<string, OnyxKey>, initialKeyStates: Pa
155159
156160 if ( typeof keys . COLLECTION === 'object' && typeof keys . COLLECTION . SNAPSHOT === 'string' ) {
157161 snapshotKey = keys . COLLECTION . SNAPSHOT ;
162+ fullyMergedSnapshotKeys = new Set ( fullyMergedSnapshotKeysParam ?? [ ] ) ;
158163 }
159164}
160165
@@ -449,8 +454,8 @@ function isCollectionKey(key: OnyxKey): key is CollectionKeyBase {
449454 return onyxCollectionKeySet . has ( key ) ;
450455}
451456
452- function isCollectionMemberKey < TCollectionKey extends CollectionKeyBase > ( collectionKey : TCollectionKey , key : string , collectionKeyLength : number ) : key is `${TCollectionKey } ${string } ` {
453- return key . startsWith ( collectionKey ) && key . length > collectionKeyLength ;
457+ function isCollectionMemberKey < TCollectionKey extends CollectionKeyBase > ( collectionKey : TCollectionKey , key : string ) : key is `${TCollectionKey } ${string } ` {
458+ return key . startsWith ( collectionKey ) && key . length > collectionKey . length ;
454459}
455460
456461/**
@@ -464,7 +469,7 @@ function splitCollectionMemberKey<TKey extends CollectionKey, CollectionKeyType
464469 key : TKey ,
465470 collectionKey ?: string ,
466471) : [ CollectionKeyType , string ] {
467- if ( collectionKey && ! isCollectionMemberKey ( collectionKey , key , collectionKey . length ) ) {
472+ if ( collectionKey && ! isCollectionMemberKey ( collectionKey , key ) ) {
468473 throw new Error ( `Invalid '${ collectionKey } ' collection key provided, it isn't compatible with '${ key } ' key.` ) ;
469474 }
470475
@@ -559,14 +564,12 @@ function getCachedCollection<TKey extends CollectionKeyBase>(collectionKey: TKey
559564 const allKeys = collectionMemberKeys || cache . getAllKeys ( ) ;
560565 const collection : OnyxCollection < KeyValueMapping [ TKey ] > = { } ;
561566
562- const collectionKeyLength = collectionKey . length ;
563-
564567 // forEach exists on both Set and Array
565568 allKeys . forEach ( ( key ) => {
566569 // If we don't have collectionMemberKeys array then we have to check whether a key is a collection member key.
567570 // Because in that case the keys will be coming from `cache.getAllKeys()` and we need to filter out the keys that
568571 // are not part of the collection.
569- if ( ! collectionMemberKeys && ! isCollectionMemberKey ( collectionKey , key , collectionKeyLength ) ) {
572+ if ( ! collectionMemberKeys && ! isCollectionMemberKey ( collectionKey , key ) ) {
570573 return ;
571574 }
572575
@@ -602,7 +605,6 @@ function keysChanged<TKey extends CollectionKeyBase>(
602605 // individual collection key member for the collection that is being updated. It is important to note that the collection parameter cane be a PARTIAL collection
603606 // and does not represent all of the combined keys and values for a collection key. It is just the "new" data that was merged in via mergeCollection().
604607 const stateMappingKeys = Object . keys ( callbackToStateMapping ) ;
605- const collectionKeyLength = collectionKey . length ;
606608
607609 for ( const stateMappingKey of stateMappingKeys ) {
608610 const subscriber = callbackToStateMapping [ stateMappingKey ] ;
@@ -623,7 +625,7 @@ function keysChanged<TKey extends CollectionKeyBase>(
623625 /**
624626 * e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
625627 */
626- const isSubscribedToCollectionMemberKey = isCollectionMemberKey ( collectionKey , subscriber . key , collectionKeyLength ) ;
628+ const isSubscribedToCollectionMemberKey = isCollectionMemberKey ( collectionKey , subscriber . key ) ;
627629
628630 // Regular Onyx.connect() subscriber found.
629631 if ( typeof subscriber . callback === 'function' ) {
@@ -1409,7 +1411,6 @@ function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge): Array<
14091411 const promises : Array < ( ) => Promise < void > > = [ ] ;
14101412
14111413 const snapshotCollection = OnyxUtils . getCachedCollection ( snapshotCollectionKey ) ;
1412- const snapshotCollectionKeyLength = snapshotCollectionKey . length ;
14131414
14141415 Object . entries ( snapshotCollection ) . forEach ( ( [ snapshotEntryKey , snapshotEntryValue ] ) => {
14151416 // Snapshots may not be present in cache. We don't know how to update them so we skip.
@@ -1421,7 +1422,7 @@ function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge): Array<
14211422
14221423 data . forEach ( ( { key, value} ) => {
14231424 // snapshots are normal keys so we want to skip update if they are written to Onyx
1424- if ( OnyxUtils . isCollectionMemberKey ( snapshotCollectionKey , key , snapshotCollectionKeyLength ) ) {
1425+ if ( OnyxUtils . isCollectionMemberKey ( snapshotCollectionKey , key ) ) {
14251426 return ;
14261427 }
14271428
@@ -1445,8 +1446,17 @@ function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge): Array<
14451446 }
14461447
14471448 const oldValue = updatedData [ key ] || { } ;
1449+ let collectionKey : string | undefined ;
1450+ try {
1451+ collectionKey = getCollectionKey ( key ) ;
1452+ } catch ( e ) {
1453+ // If getCollectionKey() throws an error it means the key is not a collection key.
1454+ collectionKey = undefined ;
1455+ }
1456+ const shouldFullyMerge = fullyMergedSnapshotKeys ?. has ( collectionKey || key ) ;
1457+ const newValue = shouldFullyMerge ? value : lodashPick ( value , Object . keys ( snapshotData [ key ] ) ) ;
14481458
1449- updatedData = { ...updatedData , [ key ] : Object . assign ( oldValue , value ) } ;
1459+ updatedData = { ...updatedData , [ key ] : Object . assign ( oldValue , newValue ) } ;
14501460 } ) ;
14511461
14521462 // Skip the update if there's no data to be merged
0 commit comments