Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Skippable Collection Member IDs #604

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions API-INTERNAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@
<dt><a href="#getEvictionBlocklist">getEvictionBlocklist()</a></dt>
<dd><p>Getter - returns the eviction block list.</p>
</dd>
<dt><a href="#getSkippableCollectionMemberIDs">getSkippableCollectionMemberIDs()</a></dt>
<dd><p>Getter - returns the skippable collection member IDs.</p>
</dd>
<dt><a href="#setSkippableCollectionMemberIDs">setSkippableCollectionMemberIDs()</a></dt>
<dd><p>Setter - sets the skippable collection member IDs.</p>
</dd>
<dt><a href="#initStoreValues">initStoreValues(keys, initialKeyStates, safeEvictionKeys)</a></dt>
<dd><p>Sets the initial values for the Onyx store</p>
</dd>
Expand Down Expand Up @@ -53,7 +59,7 @@ The resulting collection will only contain items that are returned by the select
<dd><p>Checks to see if the subscriber&#39;s supplied key
is associated with a collection of keys.</p>
</dd>
<dt><a href="#splitCollectionMemberKey">splitCollectionMemberKey(key)</a> ⇒</dt>
<dt><a href="#splitCollectionMemberKey">splitCollectionMemberKey(key, collectionKey)</a> ⇒</dt>
<dd><p>Splits a collection member key into the collection key part and the ID part.</p>
</dd>
<dt><a href="#isKeyMatch">isKeyMatch()</a></dt>
Expand Down Expand Up @@ -187,6 +193,18 @@ Getter - returns the deffered init task.
## getEvictionBlocklist()
Getter - returns the eviction block list.

**Kind**: global function
<a name="getSkippableCollectionMemberIDs"></a>

## getSkippableCollectionMemberIDs()
Getter - returns the skippable collection member IDs.

**Kind**: global function
<a name="setSkippableCollectionMemberIDs"></a>

## setSkippableCollectionMemberIDs()
Setter - sets the skippable collection member IDs.

**Kind**: global function
<a name="initStoreValues"></a>

Expand Down Expand Up @@ -268,7 +286,7 @@ is associated with a collection of keys.
**Kind**: global function
<a name="splitCollectionMemberKey"></a>

## splitCollectionMemberKey(key) ⇒
## splitCollectionMemberKey(key, collectionKey) ⇒
Splits a collection member key into the collection key part and the ID part.

**Kind**: global function
Expand All @@ -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. |

<a name="isKeyMatch"></a>

Expand Down
129 changes: 109 additions & 20 deletions lib/Onyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function init({
shouldSyncMultipleInstances = Boolean(global.localStorage),
debugSetState = false,
enablePerformanceMetrics = false,
skippableCollectionMemberIDs = [],
}: InitOptions): void {
if (enablePerformanceMetrics) {
GlobalSettings.setPerformanceMetricsEnabled(true);
Expand All @@ -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<typeof key>;
Expand Down Expand Up @@ -134,6 +137,20 @@ function set<TKey extends OnyxKey>(key: TKey, value: OnyxSetInput<TKey>): 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;
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
}
} catch (e) {
// The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Onyx.set will ignore `undefined` values as inputs, therefore we can return early.
if (value === undefined) {
return Promise.resolve();
Expand Down Expand Up @@ -196,7 +213,27 @@ function set<TKey extends OnyxKey>(key: TKey, value: OnyxSetInput<TKey>): Promis
* @param data object keyed by ONYXKEYS and the values to set
*/
function multiSet(data: OnyxMultiSetInput): Promise<void> {
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;
}, {});
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
}

const keyValuePairsToSet = OnyxUtils.prepareKeyValuePairsForStorage(newData, true);

const updatePromises = keyValuePairsToSet.map(([key, value]) => {
const prevValue = cache.get(key, false);
Expand All @@ -207,9 +244,9 @@ function multiSet(data: OnyxMultiSetInput): Promise<void> {
});

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);
Expand All @@ -232,6 +269,20 @@ function multiSet(data: OnyxMultiSetInput): Promise<void> {
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
*/
function merge<TKey extends OnyxKey>(key: TKey, changes: OnyxMergeInput<TKey>): Promise<void> {
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.
fabioh8010 marked this conversation as resolved.
Show resolved Hide resolved
}
}

const mergeQueue = OnyxUtils.getMergeQueue();
const mergeQueuePromise = OnyxUtils.getMergeQueuePromise();

Expand Down Expand Up @@ -346,19 +397,38 @@ function mergeCollection<TKey extends CollectionKeyBase, TMap>(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;
}
Expand All @@ -370,13 +440,13 @@ function mergeCollection<TKey extends CollectionKeyBase, TMap>(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<OnyxKey, OnyxInput<TKey>>;

Expand All @@ -385,7 +455,7 @@ function mergeCollection<TKey extends CollectionKeyBase, TMap>(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,
Expand Down Expand Up @@ -424,9 +494,9 @@ function mergeCollection<TKey extends CollectionKeyBase, TMap>(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;
});
})
Expand Down Expand Up @@ -735,8 +805,6 @@ function update(data: OnyxUpdate[]): Promise<void> {
.then(() => undefined);
}

type BaseCollection<TMap> = Record<string, TMap | null>;

/**
* 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.
Expand All @@ -750,22 +818,43 @@ type BaseCollection<TMap> = Record<string, TMap | null>;
* @param collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
* @param collection Object collection keyed by individual collection member keys and values
*/
function setCollection<TKey extends CollectionKeyBase, TMap extends string>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TMap>): Promise<void> {
const newCollectionKeys = Object.keys(collection);
function setCollection<TKey extends CollectionKeyBase, TMap>(collectionKey: TKey, collection: OnyxMergeCollectionInput<TKey, TMap>): Promise<void> {
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<TMap> = {...collection};
const mutableCollection: OnyxInputKeyValueMapping = {...resultCollection};

persistedKeys.forEach((key) => {
if (!key.startsWith(collectionKey)) {
return;
}
if (newCollectionKeys.includes(key)) {
if (resultCollectionKeys.includes(key)) {
return;
}

Expand Down
Loading
Loading