react-native-onyx 2.0.56 → 2.0.57
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Onyx.js +83 -20
- package/dist/OnyxUtils.d.ts +17 -2
- package/dist/OnyxUtils.js +39 -7
- package/dist/types.d.ts +8 -1
- package/package.json +1 -1
package/dist/Onyx.js
CHANGED
|
@@ -385,26 +385,14 @@ function merge(key, changes) {
|
|
|
385
385
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
386
386
|
*/
|
|
387
387
|
function mergeCollection(collectionKey, collection) {
|
|
388
|
-
if (
|
|
388
|
+
if (!OnyxUtils_1.default.isValidNonEmptyCollectionForMerge(collection)) {
|
|
389
389
|
Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.');
|
|
390
390
|
return Promise.resolve();
|
|
391
391
|
}
|
|
392
392
|
const mergedCollection = collection;
|
|
393
393
|
// Confirm all the collection keys belong to the same parent
|
|
394
|
-
let hasCollectionKeyCheckFailed = false;
|
|
395
394
|
const mergedCollectionKeys = Object.keys(mergedCollection);
|
|
396
|
-
|
|
397
|
-
if (OnyxUtils_1.default.isKeyMatch(collectionKey, dataKey)) {
|
|
398
|
-
return;
|
|
399
|
-
}
|
|
400
|
-
if (process.env.NODE_ENV === 'development') {
|
|
401
|
-
throw new Error(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
402
|
-
}
|
|
403
|
-
hasCollectionKeyCheckFailed = true;
|
|
404
|
-
Logger.logAlert(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
405
|
-
});
|
|
406
|
-
// Gracefully handle bad mergeCollection updates so it doesn't block the merge queue
|
|
407
|
-
if (hasCollectionKeyCheckFailed) {
|
|
395
|
+
if (!OnyxUtils_1.default.doAllCollectionItemsBelongToSameParent(collectionKey, mergedCollectionKeys)) {
|
|
408
396
|
return Promise.resolve();
|
|
409
397
|
}
|
|
410
398
|
return OnyxUtils_1.default.getAllKeys()
|
|
@@ -620,22 +608,53 @@ function update(data) {
|
|
|
620
608
|
throw new Error(`Invalid ${typeof key} key provided in Onyx update. Onyx key must be of type string.`);
|
|
621
609
|
}
|
|
622
610
|
});
|
|
611
|
+
// The queue of operations within a single `update` call in the format of <item key - list of operations updating the item>.
|
|
612
|
+
// This allows us to batch the operations per item and merge them into one operation in the order they were requested.
|
|
613
|
+
const updateQueue = {};
|
|
614
|
+
const enqueueSetOperation = (key, value) => {
|
|
615
|
+
// If a `set` operation is enqueued, we should clear the whole queue.
|
|
616
|
+
// Since the `set` operation replaces the value entirely, there's no need to perform any previous operations.
|
|
617
|
+
// To do this, we first put `null` in the queue, which removes the existing value, and then merge the new value.
|
|
618
|
+
updateQueue[key] = [null, value];
|
|
619
|
+
};
|
|
620
|
+
const enqueueMergeOperation = (key, value) => {
|
|
621
|
+
if (value === null) {
|
|
622
|
+
// If we merge `null`, the value is removed and all the previous operations are discarded.
|
|
623
|
+
updateQueue[key] = [null];
|
|
624
|
+
}
|
|
625
|
+
else if (!updateQueue[key]) {
|
|
626
|
+
updateQueue[key] = [value];
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
updateQueue[key].push(value);
|
|
630
|
+
}
|
|
631
|
+
};
|
|
623
632
|
const promises = [];
|
|
624
633
|
let clearPromise = Promise.resolve();
|
|
625
634
|
data.forEach(({ onyxMethod, key, value }) => {
|
|
626
635
|
switch (onyxMethod) {
|
|
627
636
|
case OnyxUtils_1.default.METHOD.SET:
|
|
628
|
-
|
|
637
|
+
enqueueSetOperation(key, value);
|
|
629
638
|
break;
|
|
630
639
|
case OnyxUtils_1.default.METHOD.MERGE:
|
|
631
|
-
|
|
640
|
+
enqueueMergeOperation(key, value);
|
|
632
641
|
break;
|
|
633
|
-
case OnyxUtils_1.default.METHOD.MERGE_COLLECTION:
|
|
634
|
-
|
|
635
|
-
|
|
642
|
+
case OnyxUtils_1.default.METHOD.MERGE_COLLECTION: {
|
|
643
|
+
const collection = value;
|
|
644
|
+
if (!OnyxUtils_1.default.isValidNonEmptyCollectionForMerge(collection)) {
|
|
645
|
+
Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.');
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
// Confirm all the collection keys belong to the same parent
|
|
649
|
+
const collectionKeys = Object.keys(collection);
|
|
650
|
+
if (OnyxUtils_1.default.doAllCollectionItemsBelongToSameParent(key, collectionKeys)) {
|
|
651
|
+
const mergedCollection = collection;
|
|
652
|
+
collectionKeys.forEach((collectionKey) => enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]));
|
|
653
|
+
}
|
|
636
654
|
break;
|
|
655
|
+
}
|
|
637
656
|
case OnyxUtils_1.default.METHOD.MULTI_SET:
|
|
638
|
-
|
|
657
|
+
Object.entries(value).forEach(([entryKey, entryValue]) => enqueueSetOperation(entryKey, entryValue));
|
|
639
658
|
break;
|
|
640
659
|
case OnyxUtils_1.default.METHOD.CLEAR:
|
|
641
660
|
clearPromise = clear();
|
|
@@ -644,6 +663,50 @@ function update(data) {
|
|
|
644
663
|
break;
|
|
645
664
|
}
|
|
646
665
|
});
|
|
666
|
+
// Group all the collection-related keys and update each collection in a single `mergeCollection` call.
|
|
667
|
+
// This is needed to prevent multiple `mergeCollection` calls for the same collection and `merge` calls for the individual items of the said collection.
|
|
668
|
+
// This way, we ensure there is no race condition in the queued updates of the same key.
|
|
669
|
+
OnyxUtils_1.default.getCollectionKeys().forEach((collectionKey) => {
|
|
670
|
+
const collectionItemKeys = Object.keys(updateQueue).filter((key) => OnyxUtils_1.default.isKeyMatch(collectionKey, key));
|
|
671
|
+
if (collectionItemKeys.length <= 1) {
|
|
672
|
+
// If there are no items of this collection in the updateQueue, we should skip it.
|
|
673
|
+
// If there is only one item, we should update it individually, therefore retain it in the updateQueue.
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const batchedCollectionUpdates = collectionItemKeys.reduce((queue, key) => {
|
|
677
|
+
const operations = updateQueue[key];
|
|
678
|
+
// Remove the collection-related key from the updateQueue so that it won't be processed individually.
|
|
679
|
+
delete updateQueue[key];
|
|
680
|
+
const updatedValue = OnyxUtils_1.default.applyMerge(undefined, operations, false);
|
|
681
|
+
if (operations[0] === null) {
|
|
682
|
+
// eslint-disable-next-line no-param-reassign
|
|
683
|
+
queue.set[key] = updatedValue;
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
// eslint-disable-next-line no-param-reassign
|
|
687
|
+
queue.merge[key] = updatedValue;
|
|
688
|
+
}
|
|
689
|
+
return queue;
|
|
690
|
+
}, {
|
|
691
|
+
merge: {},
|
|
692
|
+
set: {},
|
|
693
|
+
});
|
|
694
|
+
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.merge)) {
|
|
695
|
+
promises.push(() => mergeCollection(collectionKey, batchedCollectionUpdates.merge));
|
|
696
|
+
}
|
|
697
|
+
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.set)) {
|
|
698
|
+
promises.push(() => multiSet(batchedCollectionUpdates.set));
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
Object.entries(updateQueue).forEach(([key, operations]) => {
|
|
702
|
+
const batchedChanges = OnyxUtils_1.default.applyMerge(undefined, operations, false);
|
|
703
|
+
if (operations[0] === null) {
|
|
704
|
+
promises.push(() => set(key, batchedChanges));
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
promises.push(() => merge(key, batchedChanges));
|
|
708
|
+
}
|
|
709
|
+
});
|
|
647
710
|
return clearPromise
|
|
648
711
|
.then(() => Promise.all(promises.map((p) => p())))
|
|
649
712
|
.then(() => updateSnapshots(data))
|
package/dist/OnyxUtils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ValueOf } from 'type-fest';
|
|
2
2
|
import type Onyx from './Onyx';
|
|
3
|
-
import type { CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, Mapping, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxValue, WithOnyxConnectOptions } from './types';
|
|
3
|
+
import type { CollectionKey, CollectionKeyBase, DeepRecord, KeyValueMapping, Mapping, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxMergeCollectionInput, OnyxValue, WithOnyxConnectOptions } from './types';
|
|
4
4
|
declare const METHOD: {
|
|
5
5
|
readonly SET: "set";
|
|
6
6
|
readonly MERGE: "merge";
|
|
@@ -71,7 +71,11 @@ declare function deleteKeyByConnections(connectionID: number): void;
|
|
|
71
71
|
/** Returns current key names stored in persisted storage */
|
|
72
72
|
declare function getAllKeys(): Promise<Set<OnyxKey>>;
|
|
73
73
|
/**
|
|
74
|
-
*
|
|
74
|
+
* Returns set of all registered collection keys
|
|
75
|
+
*/
|
|
76
|
+
declare function getCollectionKeys(): Set<OnyxKey>;
|
|
77
|
+
/**
|
|
78
|
+
* Checks to see if the subscriber's supplied key
|
|
75
79
|
* is associated with a collection of keys.
|
|
76
80
|
*/
|
|
77
81
|
declare function isCollectionKey(key: OnyxKey): key is CollectionKeyBase;
|
|
@@ -216,6 +220,14 @@ declare function applyMerge<TValue extends OnyxInput<OnyxKey> | undefined, TChan
|
|
|
216
220
|
* Merge user provided default key value pairs.
|
|
217
221
|
*/
|
|
218
222
|
declare function initializeWithDefaultKeyStates(): Promise<void>;
|
|
223
|
+
/**
|
|
224
|
+
* Validate the collection is not empty and has a correct type before applying mergeCollection()
|
|
225
|
+
*/
|
|
226
|
+
declare function isValidNonEmptyCollectionForMerge<TKey extends CollectionKeyBase, TMap>(collection: OnyxMergeCollectionInput<TKey, TMap>): boolean;
|
|
227
|
+
/**
|
|
228
|
+
* Verify if all the collection keys belong to the same parent
|
|
229
|
+
*/
|
|
230
|
+
declare function doAllCollectionItemsBelongToSameParent<TKey extends CollectionKeyBase>(collectionKey: TKey, collectionKeys: string[]): boolean;
|
|
219
231
|
declare const OnyxUtils: {
|
|
220
232
|
METHOD: {
|
|
221
233
|
readonly SET: "set";
|
|
@@ -234,6 +246,7 @@ declare const OnyxUtils: {
|
|
|
234
246
|
batchUpdates: typeof batchUpdates;
|
|
235
247
|
get: typeof get;
|
|
236
248
|
getAllKeys: typeof getAllKeys;
|
|
249
|
+
getCollectionKeys: typeof getCollectionKeys;
|
|
237
250
|
isCollectionKey: typeof isCollectionKey;
|
|
238
251
|
isCollectionMemberKey: typeof isCollectionMemberKey;
|
|
239
252
|
splitCollectionMemberKey: typeof splitCollectionMemberKey;
|
|
@@ -267,5 +280,7 @@ declare const OnyxUtils: {
|
|
|
267
280
|
deleteKeyByConnections: typeof deleteKeyByConnections;
|
|
268
281
|
getSnapshotKey: typeof getSnapshotKey;
|
|
269
282
|
multiGet: typeof multiGet;
|
|
283
|
+
isValidNonEmptyCollectionForMerge: typeof isValidNonEmptyCollectionForMerge;
|
|
284
|
+
doAllCollectionItemsBelongToSameParent: typeof doAllCollectionItemsBelongToSameParent;
|
|
270
285
|
};
|
|
271
286
|
export default OnyxUtils;
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -49,10 +49,10 @@ const METHOD = {
|
|
|
49
49
|
// Key/value store of Onyx key and arrays of values to merge
|
|
50
50
|
const mergeQueue = {};
|
|
51
51
|
const mergeQueuePromise = {};
|
|
52
|
-
// Holds a mapping of all the
|
|
52
|
+
// Holds a mapping of all the React components that want their state subscribed to a store key
|
|
53
53
|
const callbackToStateMapping = {};
|
|
54
54
|
// Keeps a copy of the values of the onyx collection keys as a map for faster lookups
|
|
55
|
-
let
|
|
55
|
+
let onyxCollectionKeySet = new Set();
|
|
56
56
|
// Holds a mapping of the connected key to the connectionID for faster lookups
|
|
57
57
|
const onyxKeyToConnectionIDs = new Map();
|
|
58
58
|
// Holds a list of keys that have been directly subscribed to or recently modified from least to most recent
|
|
@@ -107,10 +107,10 @@ function initStoreValues(keys, initialKeyStates, safeEvictionKeys) {
|
|
|
107
107
|
// We need the value of the collection keys later for checking if a
|
|
108
108
|
// key is a collection. We store it in a map for faster lookup.
|
|
109
109
|
const collectionValues = Object.values((_a = keys.COLLECTION) !== null && _a !== void 0 ? _a : {});
|
|
110
|
-
|
|
111
|
-
acc.
|
|
110
|
+
onyxCollectionKeySet = collectionValues.reduce((acc, val) => {
|
|
111
|
+
acc.add(val);
|
|
112
112
|
return acc;
|
|
113
|
-
}, new
|
|
113
|
+
}, new Set());
|
|
114
114
|
// Set our default key states to use when initializing and clearing Onyx data
|
|
115
115
|
defaultKeyStates = initialKeyStates;
|
|
116
116
|
DevTools_1.default.initState(initialKeyStates);
|
|
@@ -300,11 +300,17 @@ function getAllKeys() {
|
|
|
300
300
|
return OnyxCache_1.default.captureTask(taskName, promise);
|
|
301
301
|
}
|
|
302
302
|
/**
|
|
303
|
-
*
|
|
303
|
+
* Returns set of all registered collection keys
|
|
304
|
+
*/
|
|
305
|
+
function getCollectionKeys() {
|
|
306
|
+
return onyxCollectionKeySet;
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Checks to see if the subscriber's supplied key
|
|
304
310
|
* is associated with a collection of keys.
|
|
305
311
|
*/
|
|
306
312
|
function isCollectionKey(key) {
|
|
307
|
-
return
|
|
313
|
+
return onyxCollectionKeySet.has(key);
|
|
308
314
|
}
|
|
309
315
|
function isCollectionMemberKey(collectionKey, key) {
|
|
310
316
|
return Str.startsWith(key, collectionKey) && key.length > collectionKey.length;
|
|
@@ -955,6 +961,29 @@ function initializeWithDefaultKeyStates() {
|
|
|
955
961
|
Object.entries(merged !== null && merged !== void 0 ? merged : {}).forEach(([key, value]) => keyChanged(key, value, existingDataAsObject));
|
|
956
962
|
});
|
|
957
963
|
}
|
|
964
|
+
/**
|
|
965
|
+
* Validate the collection is not empty and has a correct type before applying mergeCollection()
|
|
966
|
+
*/
|
|
967
|
+
function isValidNonEmptyCollectionForMerge(collection) {
|
|
968
|
+
return typeof collection === 'object' && !Array.isArray(collection) && !utils_1.default.isEmptyObject(collection);
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Verify if all the collection keys belong to the same parent
|
|
972
|
+
*/
|
|
973
|
+
function doAllCollectionItemsBelongToSameParent(collectionKey, collectionKeys) {
|
|
974
|
+
let hasCollectionKeyCheckFailed = false;
|
|
975
|
+
collectionKeys.forEach((dataKey) => {
|
|
976
|
+
if (OnyxUtils.isKeyMatch(collectionKey, dataKey)) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
if (process.env.NODE_ENV === 'development') {
|
|
980
|
+
throw new Error(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
981
|
+
}
|
|
982
|
+
hasCollectionKeyCheckFailed = true;
|
|
983
|
+
Logger.logAlert(`Provided collection doesn't have all its data belonging to the same parent. CollectionKey: ${collectionKey}, DataKey: ${dataKey}`);
|
|
984
|
+
});
|
|
985
|
+
return !hasCollectionKeyCheckFailed;
|
|
986
|
+
}
|
|
958
987
|
const OnyxUtils = {
|
|
959
988
|
METHOD,
|
|
960
989
|
getMergeQueue,
|
|
@@ -967,6 +996,7 @@ const OnyxUtils = {
|
|
|
967
996
|
batchUpdates,
|
|
968
997
|
get,
|
|
969
998
|
getAllKeys,
|
|
999
|
+
getCollectionKeys,
|
|
970
1000
|
isCollectionKey,
|
|
971
1001
|
isCollectionMemberKey,
|
|
972
1002
|
splitCollectionMemberKey,
|
|
@@ -1000,5 +1030,7 @@ const OnyxUtils = {
|
|
|
1000
1030
|
deleteKeyByConnections,
|
|
1001
1031
|
getSnapshotKey,
|
|
1002
1032
|
multiGet,
|
|
1033
|
+
isValidNonEmptyCollectionForMerge,
|
|
1034
|
+
doAllCollectionItemsBelongToSameParent,
|
|
1003
1035
|
};
|
|
1004
1036
|
exports.default = OnyxUtils;
|
package/dist/types.d.ts
CHANGED
|
@@ -365,4 +365,11 @@ type InitOptions = {
|
|
|
365
365
|
debugSetState?: boolean;
|
|
366
366
|
};
|
|
367
367
|
type GenericFunction = (...args: any[]) => any;
|
|
368
|
-
|
|
368
|
+
/**
|
|
369
|
+
* Represents a combination of Merge and Set operations that should be executed in Onyx
|
|
370
|
+
*/
|
|
371
|
+
type MixedOperationsQueue = {
|
|
372
|
+
merge: OnyxInputKeyValueMapping;
|
|
373
|
+
set: OnyxInputKeyValueMapping;
|
|
374
|
+
};
|
|
375
|
+
export type { BaseConnectOptions, Collection, CollectionConnectCallback, CollectionConnectOptions, CollectionKey, CollectionKeyBase, ConnectOptions, CustomTypeOptions, DeepRecord, DefaultConnectCallback, DefaultConnectOptions, ExtractOnyxCollectionValue, GenericFunction, InitOptions, Key, KeyValueMapping, Mapping, NonNull, NonUndefined, OnyxInputKeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, Selector, WithOnyxConnectOptions, MixedOperationsQueue, };
|