diff --git a/API-INTERNAL.md b/API-INTERNAL.md
index 33d5f4de..0dc8b5d8 100644
--- a/API-INTERNAL.md
+++ b/API-INTERNAL.md
@@ -20,6 +20,12 @@
getEvictionBlocklist()
Getter - returns the eviction block list.
+getSkippableCollectionMemberIDs()
+Getter - returns the skippable collection member IDs.
+
+setSkippableCollectionMemberIDs()
+Setter - sets the skippable collection member IDs.
+
initStoreValues(keys, initialKeyStates, safeEvictionKeys)
Sets the initial values for the Onyx store
@@ -53,7 +59,7 @@ The resulting collection will only contain items that are returned by the select
Checks to see if the subscriber's supplied key
is associated with a collection of keys.
-splitCollectionMemberKey(key) ⇒
+splitCollectionMemberKey(key, collectionKey) ⇒
Splits a collection member key into the collection key part and the ID part.
isKeyMatch()
@@ -187,6 +193,18 @@ Getter - returns the deffered init task.
## getEvictionBlocklist()
Getter - returns the eviction block list.
+**Kind**: global function
+
+
+## getSkippableCollectionMemberIDs()
+Getter - returns the skippable collection member IDs.
+
+**Kind**: global function
+
+
+## setSkippableCollectionMemberIDs()
+Setter - sets the skippable collection member IDs.
+
**Kind**: global function
@@ -268,7 +286,7 @@ is associated with a collection of keys.
**Kind**: global function
-## splitCollectionMemberKey(key) ⇒
+## splitCollectionMemberKey(key, collectionKey) ⇒
Splits a collection member key into the collection key part and the ID part.
**Kind**: global function
@@ -278,6 +296,7 @@ or throws an Error if the key is not a collection one.
| Param | Description |
| --- | --- |
| key | The collection member key to split. |
+| collectionKey | The collection key of the `key` param that can be passed in advance to optimize the function. |
diff --git a/lib/Onyx.ts b/lib/Onyx.ts
index a2fcdde3..baf7c068 100644
--- a/lib/Onyx.ts
+++ b/lib/Onyx.ts
@@ -44,6 +44,7 @@ function init({
shouldSyncMultipleInstances = Boolean(global.localStorage),
debugSetState = false,
enablePerformanceMetrics = false,
+ skippableCollectionMemberIDs = [],
}: InitOptions): void {
if (enablePerformanceMetrics) {
GlobalSettings.setPerformanceMetricsEnabled(true);
@@ -52,6 +53,8 @@ function init({
Storage.init();
+ OnyxUtils.setSkippableCollectionMemberIDs(new Set(skippableCollectionMemberIDs));
+
if (shouldSyncMultipleInstances) {
Storage.keepInstancesSync?.((key, value) => {
const prevValue = cache.get(key, false) as OnyxValue;
@@ -134,6 +137,20 @@ function set(key: TKey, value: OnyxSetInput): Promis
delete OnyxUtils.getMergeQueue()[key];
}
+ const skippableCollectionMemberIDs = OnyxUtils.getSkippableCollectionMemberIDs();
+ if (skippableCollectionMemberIDs.size) {
+ try {
+ const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key);
+ if (skippableCollectionMemberIDs.has(collectionMemberID)) {
+ // The key is a skippable one, so we set the new value to null.
+ // eslint-disable-next-line no-param-reassign
+ value = null;
+ }
+ } catch (e) {
+ // The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
+ }
+ }
+
// Onyx.set will ignore `undefined` values as inputs, therefore we can return early.
if (value === undefined) {
return Promise.resolve();
@@ -196,7 +213,27 @@ function set(key: TKey, value: OnyxSetInput): Promis
* @param data object keyed by ONYXKEYS and the values to set
*/
function multiSet(data: OnyxMultiSetInput): Promise {
- const keyValuePairsToSet = OnyxUtils.prepareKeyValuePairsForStorage(data, true);
+ let newData = data;
+
+ const skippableCollectionMemberIDs = OnyxUtils.getSkippableCollectionMemberIDs();
+ if (skippableCollectionMemberIDs.size) {
+ newData = Object.keys(newData).reduce((result: OnyxMultiSetInput, key) => {
+ try {
+ const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key);
+ // If the collection member key is a skippable one we set its value to null.
+ // eslint-disable-next-line no-param-reassign
+ result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? newData[key] : null;
+ } catch {
+ // The key is not a collection one or something went wrong during split, so we assign the data to result anyway.
+ // eslint-disable-next-line no-param-reassign
+ result[key] = newData[key];
+ }
+
+ return result;
+ }, {});
+ }
+
+ const keyValuePairsToSet = OnyxUtils.prepareKeyValuePairsForStorage(newData, true);
const updatePromises = keyValuePairsToSet.map(([key, value]) => {
const prevValue = cache.get(key, false);
@@ -207,9 +244,9 @@ function multiSet(data: OnyxMultiSetInput): Promise {
});
return Storage.multiSet(keyValuePairsToSet)
- .catch((error) => OnyxUtils.evictStorageAndRetry(error, multiSet, data))
+ .catch((error) => OnyxUtils.evictStorageAndRetry(error, multiSet, newData))
.then(() => {
- OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MULTI_SET, undefined, data);
+ OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MULTI_SET, undefined, newData);
return Promise.all(updatePromises);
})
.then(() => undefined);
@@ -232,6 +269,20 @@ function multiSet(data: OnyxMultiSetInput): Promise {
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
*/
function merge(key: TKey, changes: OnyxMergeInput): Promise {
+ const skippableCollectionMemberIDs = OnyxUtils.getSkippableCollectionMemberIDs();
+ if (skippableCollectionMemberIDs.size) {
+ try {
+ const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key);
+ if (skippableCollectionMemberIDs.has(collectionMemberID)) {
+ // The key is a skippable one, so we set the new changes to undefined.
+ // eslint-disable-next-line no-param-reassign
+ changes = undefined;
+ }
+ } catch (e) {
+ // The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
+ }
+ }
+
const mergeQueue = OnyxUtils.getMergeQueue();
const mergeQueuePromise = OnyxUtils.getMergeQueuePromise();
@@ -346,19 +397,38 @@ function mergeCollection(collectionKey: TK
return Promise.resolve();
}
- const mergedCollection: OnyxInputKeyValueMapping = collection;
+ let resultCollection: OnyxInputKeyValueMapping = collection;
+ let resultCollectionKeys = Object.keys(resultCollection);
// Confirm all the collection keys belong to the same parent
- const mergedCollectionKeys = Object.keys(mergedCollection);
- if (!OnyxUtils.doAllCollectionItemsBelongToSameParent(collectionKey, mergedCollectionKeys)) {
+ if (!OnyxUtils.doAllCollectionItemsBelongToSameParent(collectionKey, resultCollectionKeys)) {
return Promise.resolve();
}
+ const skippableCollectionMemberIDs = OnyxUtils.getSkippableCollectionMemberIDs();
+ if (skippableCollectionMemberIDs.size) {
+ resultCollection = resultCollectionKeys.reduce((result: OnyxInputKeyValueMapping, key) => {
+ try {
+ const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key, collectionKey);
+ // If the collection member key is a skippable one we set its value to null.
+ // eslint-disable-next-line no-param-reassign
+ result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? resultCollection[key] : null;
+ } catch {
+ // Something went wrong during split, so we assign the data to result anyway.
+ // eslint-disable-next-line no-param-reassign
+ result[key] = resultCollection[key];
+ }
+
+ return result;
+ }, {});
+ }
+ resultCollectionKeys = Object.keys(resultCollection);
+
return OnyxUtils.getAllKeys()
.then((persistedKeys) => {
// Split to keys that exist in storage and keys that don't
- const keys = mergedCollectionKeys.filter((key) => {
- if (mergedCollection[key] === null) {
+ const keys = resultCollectionKeys.filter((key) => {
+ if (resultCollection[key] === null) {
OnyxUtils.remove(key);
return false;
}
@@ -370,13 +440,13 @@ function mergeCollection(collectionKey: TK
const cachedCollectionForExistingKeys = OnyxUtils.getCachedCollection(collectionKey, existingKeys);
const existingKeyCollection = existingKeys.reduce((obj: OnyxInputKeyValueMapping, key) => {
- const {isCompatible, existingValueType, newValueType} = utils.checkCompatibilityWithExistingValue(mergedCollection[key], cachedCollectionForExistingKeys[key]);
+ const {isCompatible, existingValueType, newValueType} = utils.checkCompatibilityWithExistingValue(resultCollection[key], cachedCollectionForExistingKeys[key]);
if (!isCompatible) {
Logger.logAlert(logMessages.incompatibleUpdateAlert(key, 'mergeCollection', existingValueType, newValueType));
return obj;
}
// eslint-disable-next-line no-param-reassign
- obj[key] = mergedCollection[key];
+ obj[key] = resultCollection[key];
return obj;
}, {}) as Record>;
@@ -385,7 +455,7 @@ function mergeCollection(collectionKey: TK
if (persistedKeys.has(key)) {
return;
}
- newCollection[key] = mergedCollection[key];
+ newCollection[key] = resultCollection[key];
});
// When (multi-)merging the values with the existing values in storage,
@@ -424,9 +494,9 @@ function mergeCollection(collectionKey: TK
});
return Promise.all(promises)
- .catch((error) => OnyxUtils.evictStorageAndRetry(error, mergeCollection, collectionKey, mergedCollection))
+ .catch((error) => OnyxUtils.evictStorageAndRetry(error, mergeCollection, collectionKey, resultCollection))
.then(() => {
- OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MERGE_COLLECTION, undefined, mergedCollection);
+ OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MERGE_COLLECTION, undefined, resultCollection);
return promiseUpdate;
});
})
@@ -735,8 +805,6 @@ function update(data: OnyxUpdate[]): Promise {
.then(() => undefined);
}
-type BaseCollection = Record;
-
/**
* Sets a collection by replacing all existing collection members with new values.
* Any existing collection members not included in the new data will be removed.
@@ -750,22 +818,43 @@ type BaseCollection = Record;
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
* @param collection Object collection keyed by individual collection member keys and values
*/
-function setCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise {
- const newCollectionKeys = Object.keys(collection);
+function setCollection(collectionKey: TKey, collection: OnyxMergeCollectionInput): Promise {
+ let resultCollection: OnyxInputKeyValueMapping = collection;
+ let resultCollectionKeys = Object.keys(resultCollection);
- if (!OnyxUtils.doAllCollectionItemsBelongToSameParent(collectionKey, newCollectionKeys)) {
+ // Confirm all the collection keys belong to the same parent
+ if (!OnyxUtils.doAllCollectionItemsBelongToSameParent(collectionKey, resultCollectionKeys)) {
Logger.logAlert(`setCollection called with keys that do not belong to the same parent ${collectionKey}. Skipping this update.`);
return Promise.resolve();
}
+ const skippableCollectionMemberIDs = OnyxUtils.getSkippableCollectionMemberIDs();
+ if (skippableCollectionMemberIDs.size) {
+ resultCollection = resultCollectionKeys.reduce((result: OnyxInputKeyValueMapping, key) => {
+ try {
+ const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key, collectionKey);
+ // If the collection member key is a skippable one we set its value to null.
+ // eslint-disable-next-line no-param-reassign
+ result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? resultCollection[key] : null;
+ } catch {
+ // Something went wrong during split, so we assign the data to result anyway.
+ // eslint-disable-next-line no-param-reassign
+ result[key] = resultCollection[key];
+ }
+
+ return result;
+ }, {});
+ }
+ resultCollectionKeys = Object.keys(resultCollection);
+
return OnyxUtils.getAllKeys().then((persistedKeys) => {
- const mutableCollection: BaseCollection = {...collection};
+ const mutableCollection: OnyxInputKeyValueMapping = {...resultCollection};
persistedKeys.forEach((key) => {
if (!key.startsWith(collectionKey)) {
return;
}
- if (newCollectionKeys.includes(key)) {
+ if (resultCollectionKeys.includes(key)) {
return;
}
diff --git a/lib/OnyxUtils.ts b/lib/OnyxUtils.ts
index 15863624..a3ef656a 100644
--- a/lib/OnyxUtils.ts
+++ b/lib/OnyxUtils.ts
@@ -88,6 +88,9 @@ let lastSubscriptionID = 0;
// Connections can be made before `Onyx.init`. They would wait for this task before resolving
const deferredInitTask = createDeferredTask();
+// Holds a set of collection member IDs which updates will be ignored when using Onyx methods.
+let skippableCollectionMemberIDs = new Set();
+
function getSnapshotKey(): OnyxKey | null {
return snapshotKey;
}
@@ -127,6 +130,20 @@ function getEvictionBlocklist(): Record {
return evictionBlocklist;
}
+/**
+ * Getter - returns the skippable collection member IDs.
+ */
+function getSkippableCollectionMemberIDs(): Set {
+ return skippableCollectionMemberIDs;
+}
+
+/**
+ * Setter - sets the skippable collection member IDs.
+ */
+function setSkippableCollectionMemberIDs(ids: Set): void {
+ skippableCollectionMemberIDs = ids;
+}
+
/**
* Sets the initial values for the Onyx store
*
@@ -257,6 +274,19 @@ function get>(key: TKey): P
// Otherwise retrieve the value from storage and capture a promise to aid concurrent usages
const promise = Storage.getItem(key)
.then((val) => {
+ if (skippableCollectionMemberIDs.size) {
+ try {
+ const [, collectionMemberID] = splitCollectionMemberKey(key);
+ if (skippableCollectionMemberIDs.has(collectionMemberID)) {
+ // The key is a skippable one, so we set the value to undefined.
+ // eslint-disable-next-line no-param-reassign
+ val = undefined as OnyxValue;
+ }
+ } catch (e) {
+ // The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
+ }
+ }
+
if (val === undefined) {
cache.addNullishStorageKey(key);
return undefined;
@@ -335,6 +365,18 @@ function multiGet(keys: CollectionKeyBase[]): Promise