Skip to content

Commit 405f255

Browse files
committed
Implement internal mergeCollectionWithPatches function
1 parent 466feb1 commit 405f255

4 files changed

Lines changed: 167 additions & 131 deletions

File tree

API-INTERNAL.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ It will also mark deep nested objects that need to be entirely replaced during t
149149
<dt><a href="#unsubscribeFromKey">unsubscribeFromKey(subscriptionID)</a></dt>
150150
<dd><p>Disconnects and removes the listener from the Onyx key.</p>
151151
</dd>
152+
<dt><a href="#mergeCollectionWithPatches">mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches)</a></dt>
153+
<dd><p>Merges a collection based on their keys.
154+
Serves as core implementation for <code>Onyx.mergeCollection()</code> public function, the difference being
155+
that this internal function allows passing an additional <code>mergeReplaceNullPatches</code> parameter.</p>
156+
</dd>
152157
<dt><a href="#clearOnyxUtilsInternals">clearOnyxUtilsInternals()</a></dt>
153158
<dd><p>Clear internal variables used in this file, useful in test environments.</p>
154159
</dd>
@@ -503,6 +508,21 @@ Disconnects and removes the listener from the Onyx key.
503508
| --- | --- |
504509
| subscriptionID | Subscription ID returned by calling `OnyxUtils.subscribeToKey()`. |
505510

511+
<a name="mergeCollectionWithPatches"></a>
512+
513+
## mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches)
514+
Merges a collection based on their keys.
515+
Serves as core implementation for `Onyx.mergeCollection()` public function, the difference being
516+
that this internal function allows passing an additional `mergeReplaceNullPatches` parameter.
517+
518+
**Kind**: global function
519+
520+
| Param | Description |
521+
| --- | --- |
522+
| collectionKey | e.g. `ONYXKEYS.COLLECTION.REPORT` |
523+
| collection | Object collection keyed by individual collection member keys and values |
524+
| mergeReplaceNullPatches | Record where the key is a collection member key and the value is a list of tuples that we'll use to replace the nested objects of that collection member record with something else. |
525+
506526
<a name="clearOnyxUtilsInternals"></a>
507527

508528
## clearOnyxUtilsInternals()

API.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ Values of type <code>Object</code> get merged with the old value, whilst for <co
2828
applied in the order they were called. Note: <code>Onyx.set()</code> calls do not work this way so use caution when mixing
2929
<code>Onyx.merge()</code> and <code>Onyx.set()</code>.</p>
3030
</dd>
31-
<dt><a href="#mergeCollection">mergeCollection(collectionKey, collection, mergeReplaceNullPatches)</a></dt>
32-
<dd><p>Merges a collection based on their keys</p>
31+
<dt><a href="#mergeCollection">mergeCollection(collectionKey, collection)</a></dt>
32+
<dd><p>Merges a collection based on their keys.</p>
3333
</dd>
3434
<dt><a href="#clear">clear(keysToPreserve)</a></dt>
3535
<dd><p>Clear out all the data in the store</p>
@@ -157,16 +157,15 @@ Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Wor
157157
```
158158
<a name="mergeCollection"></a>
159159

160-
## mergeCollection(collectionKey, collection, mergeReplaceNullPatches)
161-
Merges a collection based on their keys
160+
## mergeCollection(collectionKey, collection)
161+
Merges a collection based on their keys.
162162

163163
**Kind**: global function
164164

165165
| Param | Description |
166166
| --- | --- |
167167
| collectionKey | e.g. `ONYXKEYS.COLLECTION.REPORT` |
168168
| collection | Object collection keyed by individual collection member keys and values |
169-
| mergeReplaceNullPatches | Record where the key is a collection member key and the value is a list of tuples that we'll use to replace the nested objects of that collection member record with something else. |
170169

171170
**Example**
172171
```js

lib/Onyx.ts

Lines changed: 8 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import type {
2424
OnyxValue,
2525
OnyxInput,
2626
OnyxMethodMap,
27-
MultiMergeReplaceNullPatches,
2827
} from './types';
2928
import OnyxUtils from './OnyxUtils';
3029
import logMessages from './logMessages';
@@ -345,7 +344,7 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
345344
}
346345

347346
/**
348-
* Merges a collection based on their keys
347+
* Merges a collection based on their keys.
349348
*
350349
* @example
351350
*
@@ -356,125 +355,9 @@ function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>):
356355
*
357356
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
358357
* @param collection Object collection keyed by individual collection member keys and values
359-
* @param mergeReplaceNullPatches Record where the key is a collection member key and the value is a list of
360-
* tuples that we'll use to replace the nested objects of that collection member record with something else.
361358
*/
362-
function mergeCollection<TKey extends CollectionKeyBase, TMap>(
363-
collectionKey: TKey,
364-
collection: OnyxMergeCollectionInput<TKey, TMap>,
365-
mergeReplaceNullPatches?: MultiMergeReplaceNullPatches,
366-
): Promise<void> {
367-
if (!OnyxUtils.isValidNonEmptyCollectionForMerge(collection)) {
368-
Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.');
369-
return Promise.resolve();
370-
}
371-
372-
let resultCollection: OnyxInputKeyValueMapping = collection;
373-
let resultCollectionKeys = Object.keys(resultCollection);
374-
375-
// Confirm all the collection keys belong to the same parent
376-
if (!OnyxUtils.doAllCollectionItemsBelongToSameParent(collectionKey, resultCollectionKeys)) {
377-
return Promise.resolve();
378-
}
379-
380-
const skippableCollectionMemberIDs = OnyxUtils.getSkippableCollectionMemberIDs();
381-
if (skippableCollectionMemberIDs.size) {
382-
resultCollection = resultCollectionKeys.reduce((result: OnyxInputKeyValueMapping, key) => {
383-
try {
384-
const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key, collectionKey);
385-
// If the collection member key is a skippable one we set its value to null.
386-
// eslint-disable-next-line no-param-reassign
387-
result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? resultCollection[key] : null;
388-
} catch {
389-
// Something went wrong during split, so we assign the data to result anyway.
390-
// eslint-disable-next-line no-param-reassign
391-
result[key] = resultCollection[key];
392-
}
393-
394-
return result;
395-
}, {});
396-
}
397-
resultCollectionKeys = Object.keys(resultCollection);
398-
399-
return OnyxUtils.getAllKeys()
400-
.then((persistedKeys) => {
401-
// Split to keys that exist in storage and keys that don't
402-
const keys = resultCollectionKeys.filter((key) => {
403-
if (resultCollection[key] === null) {
404-
OnyxUtils.remove(key);
405-
return false;
406-
}
407-
return true;
408-
});
409-
410-
const existingKeys = keys.filter((key) => persistedKeys.has(key));
411-
412-
const cachedCollectionForExistingKeys = OnyxUtils.getCachedCollection(collectionKey, existingKeys);
413-
414-
const existingKeyCollection = existingKeys.reduce((obj: OnyxInputKeyValueMapping, key) => {
415-
const {isCompatible, existingValueType, newValueType} = utils.checkCompatibilityWithExistingValue(resultCollection[key], cachedCollectionForExistingKeys[key]);
416-
417-
if (!isCompatible) {
418-
Logger.logAlert(logMessages.incompatibleUpdateAlert(key, 'mergeCollection', existingValueType, newValueType));
419-
return obj;
420-
}
421-
422-
// eslint-disable-next-line no-param-reassign
423-
obj[key] = resultCollection[key];
424-
return obj;
425-
}, {}) as Record<OnyxKey, OnyxInput<TKey>>;
426-
427-
const newCollection: Record<OnyxKey, OnyxInput<TKey>> = {};
428-
keys.forEach((key) => {
429-
if (persistedKeys.has(key)) {
430-
return;
431-
}
432-
newCollection[key] = resultCollection[key];
433-
});
434-
435-
// When (multi-)merging the values with the existing values in storage,
436-
// we don't want to remove nested null values from the data that we pass to the storage layer,
437-
// because the storage layer uses them to remove nested keys from storage natively.
438-
const keyValuePairsForExistingCollection = OnyxUtils.prepareKeyValuePairsForStorage(existingKeyCollection, false, mergeReplaceNullPatches);
439-
440-
// We can safely remove nested null values when using (multi-)set,
441-
// because we will simply overwrite the existing values in storage.
442-
const keyValuePairsForNewCollection = OnyxUtils.prepareKeyValuePairsForStorage(newCollection, true);
443-
444-
const promises = [];
445-
446-
// We need to get the previously existing values so we can compare the new ones
447-
// against them, to avoid unnecessary subscriber updates.
448-
const previousCollectionPromise = Promise.all(existingKeys.map((key) => OnyxUtils.get(key).then((value) => [key, value]))).then(Object.fromEntries);
449-
450-
// New keys will be added via multiSet while existing keys will be updated using multiMerge
451-
// This is because setting a key that doesn't exist yet with multiMerge will throw errors
452-
if (keyValuePairsForExistingCollection.length > 0) {
453-
promises.push(Storage.multiMerge(keyValuePairsForExistingCollection));
454-
}
455-
456-
if (keyValuePairsForNewCollection.length > 0) {
457-
promises.push(Storage.multiSet(keyValuePairsForNewCollection));
458-
}
459-
460-
// finalMergedCollection contains all the keys that were merged, without the keys of incompatible updates
461-
const finalMergedCollection = {...existingKeyCollection, ...newCollection};
462-
463-
// Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
464-
// and update all subscribers
465-
const promiseUpdate = previousCollectionPromise.then((previousCollection) => {
466-
cache.merge(finalMergedCollection);
467-
return OnyxUtils.scheduleNotifyCollectionSubscribers(collectionKey, finalMergedCollection, previousCollection);
468-
});
469-
470-
return Promise.all(promises)
471-
.catch((error) => OnyxUtils.evictStorageAndRetry(error, mergeCollection, collectionKey, resultCollection))
472-
.then(() => {
473-
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MERGE_COLLECTION, undefined, resultCollection);
474-
return promiseUpdate;
475-
});
476-
})
477-
.then(() => undefined);
359+
function mergeCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>): Promise<void> {
360+
return OnyxUtils.mergeCollectionWithPatches(collectionKey, collection);
478361
}
479362

480363
/**
@@ -708,7 +591,11 @@ function update(data: OnyxUpdate[]): Promise<void> {
708591

709592
if (!utils.isEmptyObject(batchedCollectionUpdates.merge)) {
710593
promises.push(() =>
711-
mergeCollection(collectionKey, batchedCollectionUpdates.merge as Collection<CollectionKey, unknown, unknown>, batchedCollectionUpdates.mergeReplaceNullPatches),
594+
OnyxUtils.mergeCollectionWithPatches(
595+
collectionKey,
596+
batchedCollectionUpdates.merge as Collection<CollectionKey, unknown, unknown>,
597+
batchedCollectionUpdates.mergeReplaceNullPatches,
598+
),
712599
);
713600
}
714601
if (!utils.isEmptyObject(batchedCollectionUpdates.set)) {

0 commit comments

Comments
 (0)