react-native-onyx 1.0.53 → 1.0.54

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.
@@ -102,6 +102,9 @@ const METHOD = {
102
102
  CLEAR: 'clear'
103
103
  };
104
104
 
105
+ // Key/value store of Onyx key and arrays of values to merge
106
+ const mergeQueue = {};
107
+
105
108
  // Keeps track of the last connectionID that was used so we can keep incrementing it
106
109
  let lastConnectionID = 0;
107
110
 
@@ -924,6 +927,24 @@ function evictStorageAndRetry(error, onyxMethod) {for (var _len = arguments.leng
924
927
  then(() => onyxMethod(...args));
925
928
  }
926
929
 
930
+ /**
931
+ * Notifys subscribers and writes current value to cache
932
+ *
933
+ * @param {String} key
934
+ * @param {*} value
935
+ * @param {String} method
936
+ */
937
+ function broadcastUpdate(key, value, method) {
938
+ // Logging properties only since values could be sensitive things we don't want to log
939
+ _Logger__WEBPACK_IMPORTED_MODULE_6__.logInfo(`${method}() called for key: ${key}${underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject(value) ? ` properties: ${underscore__WEBPACK_IMPORTED_MODULE_2___default().keys(value).join(',')}` : ''}`);
940
+
941
+ // Update subscribers if the cached value has changed, or when the subscriber specifically requires
942
+ // all updates regardless of value changes (indicated by initWithStoredValues set to false).
943
+ const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, value);
944
+ _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].set(key, value);
945
+ notifySubscribersOnNextTick(key, value, (subscriber) => hasChanged || subscriber.initWithStoredValues === false);
946
+ }
947
+
927
948
  /**
928
949
  * Write a value to our store with the given key
929
950
  *
@@ -937,24 +958,9 @@ function set(key, value) {
937
958
  return remove(key);
938
959
  }
939
960
 
940
- // eslint-disable-next-line no-use-before-define
941
- if (hasPendingMergeForKey(key)) {
942
- _Logger__WEBPACK_IMPORTED_MODULE_6__.logAlert(`Onyx.set() called after Onyx.merge() for key: ${key}. It is recommended to use set() or merge() not both.`);
943
- }
961
+ // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
962
+ broadcastUpdate(key, value, 'set');
944
963
 
945
- // If the value in the cache is the same as what we have then do not update subscribers unless they
946
- // have initWithStoredValues: false then they MUST get all updates even if nothing has changed.
947
- if (!_OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, value)) {
948
- _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].addToAccessedKeys(key);
949
- notifySubscribersOnNextTick(key, value, (subscriber) => subscriber.initWithStoredValues === false);
950
- return Promise.resolve();
951
- }
952
-
953
- // Adds the key to cache when it's not available
954
- _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].set(key, value);
955
- notifySubscribersOnNextTick(key, value);
956
-
957
- // Write the thing to persistent storage, which will trigger a storage event for any other tabs open on this domain
958
964
  return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].setItem(key, value).
959
965
  catch((error) => evictStorageAndRetry(error, set, key, value));
960
966
  }
@@ -992,67 +998,43 @@ function multiSet(data) {
992
998
  catch((error) => evictStorageAndRetry(error, multiSet, data));
993
999
  }
994
1000
 
995
- // Key/value store of Onyx key and arrays of values to merge
996
- const mergeQueue = {};
997
-
998
- /**
999
- * @private
1000
- * @param {String} key
1001
- * @returns {Boolean}
1002
- */
1003
- function hasPendingMergeForKey(key) {
1004
- return Boolean(mergeQueue[key]);
1005
- }
1006
-
1007
1001
  /**
1008
- * Given an Onyx key and value this method will combine all queued
1009
- * value updates and return a single value. Merge attempts are
1010
- * batched. They must occur after a single call to get() so we
1011
- * can avoid race conditions.
1002
+ * Merges an array of changes with an existing value
1012
1003
  *
1013
1004
  * @private
1014
- * @param {String} key
1015
- * @param {*} data
1016
- *
1005
+ * @param {Array<*>} changes Array of changes that should be applied to the existing value
1006
+ * @param {*} existingValue
1017
1007
  * @returns {*}
1018
1008
  */
1019
- function applyMerge(key, data) {
1020
- const mergeValues = mergeQueue[key];
1021
- if (underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray(data) || underscore__WEBPACK_IMPORTED_MODULE_2___default().every(mergeValues, (underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray))) {
1022
- // Array values will always just concatenate
1023
- // more items onto the end of the array
1024
- return underscore__WEBPACK_IMPORTED_MODULE_2___default().reduce(mergeValues, (modifiedData, mergeValue) => [
1025
- ...modifiedData,
1026
- ...mergeValue],
1027
- data || []);
1028
- }
1029
-
1030
- if (underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject(data) || underscore__WEBPACK_IMPORTED_MODULE_2___default().every(mergeValues, (underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject))) {
1009
+ function applyMerge(changes, existingValue) {
1010
+ const lastChange = underscore__WEBPACK_IMPORTED_MODULE_2___default().last(changes);
1011
+
1012
+ if (underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray(existingValue) || underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray(lastChange)) {
1013
+ return lastChange;
1014
+ }
1015
+
1016
+ if (underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject(existingValue) || underscore__WEBPACK_IMPORTED_MODULE_2___default().every(changes, (underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject))) {
1031
1017
  // Object values are merged one after the other
1032
- return underscore__WEBPACK_IMPORTED_MODULE_2___default().reduce(mergeValues, (modifiedData, mergeValue) => {
1018
+ return underscore__WEBPACK_IMPORTED_MODULE_2___default().reduce(changes, (modifiedData, change) => {
1033
1019
  // lodash adds a small overhead so we don't use it here
1034
1020
  // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
1035
- const newData = Object.assign({}, (0,_fastMerge__WEBPACK_IMPORTED_MODULE_9__["default"])(modifiedData, mergeValue));
1021
+ const newData = Object.assign({}, (0,_fastMerge__WEBPACK_IMPORTED_MODULE_9__["default"])(modifiedData, change));
1036
1022
 
1037
- // We will also delete any object keys that are undefined or null.
1038
- // Deleting keys is not supported by AsyncStorage so we do it this way.
1039
1023
  // Remove all first level keys that are explicitly set to null.
1040
- return underscore__WEBPACK_IMPORTED_MODULE_2___default().omit(newData, (value, finalObjectKey) => underscore__WEBPACK_IMPORTED_MODULE_2___default().isNull(mergeValue[finalObjectKey]));
1041
- }, data || {});
1024
+ return underscore__WEBPACK_IMPORTED_MODULE_2___default().omit(newData, (value) => underscore__WEBPACK_IMPORTED_MODULE_2___default().isNull(value));
1025
+ }, existingValue || {});
1042
1026
  }
1043
1027
 
1044
1028
  // If we have anything else we can't merge it so we'll
1045
1029
  // simply return the last value that was queued
1046
- return underscore__WEBPACK_IMPORTED_MODULE_2___default().last(mergeValues);
1030
+ return lastChange;
1047
1031
  }
1048
1032
 
1049
1033
  /**
1050
1034
  * Merge a new value into an existing value at a key.
1051
1035
  *
1052
- * The types of values that can be merged are `Object` and `Array`. To set another type of value use `Onyx.set()`. Merge
1053
- * behavior uses lodash/merge under the hood for `Object` and simple concatenation for `Array`. However, it's important
1054
- * to note that if you have an array value property on an `Object` that the default behavior of lodash/merge is not to
1055
- * concatenate. See here: https://github.com/lodash/lodash/issues/2872
1036
+ * The types of values that can be merged are `Object` and `Array`. To set another type of value use `Onyx.set()`.
1037
+ * Values of type `Object` get merged with the old value, whilst for `Array`'s we simply replace the current value with the new one.
1056
1038
  *
1057
1039
  * Calls to `Onyx.merge()` are batched so that any calls performed in a single tick will stack in a queue and get
1058
1040
  * applied in the order they were called. Note: `Onyx.set()` calls do not work this way so use caution when mixing
@@ -1065,26 +1047,35 @@ function applyMerge(key, data) {
1065
1047
  * Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
1066
1048
  *
1067
1049
  * @param {String} key ONYXKEYS key
1068
- * @param {(Object|Array)} value Object or Array value to merge
1050
+ * @param {(Object|Array)} changes Object or Array value to merge
1069
1051
  * @returns {Promise}
1070
1052
  */
1071
- function merge(key, value) {
1053
+ function merge(key, changes) {
1054
+ // Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
1055
+ // Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
1072
1056
  if (mergeQueue[key]) {
1073
- mergeQueue[key].push(value);
1057
+ mergeQueue[key].push(changes);
1074
1058
  return Promise.resolve();
1075
1059
  }
1060
+ mergeQueue[key] = [changes];
1076
1061
 
1077
- mergeQueue[key] = [value];
1078
1062
  return get(key).
1079
- then((data) => {
1063
+ then((existingValue) => {
1080
1064
  try {
1081
- const modifiedData = applyMerge(key, data);
1065
+ // We first only merge the changes, so we can provide these to the native implementation (SQLite uses only delta changes in "JSON_PATCH" to merge)
1066
+ const batchedChanges = applyMerge(mergeQueue[key]);
1082
1067
 
1083
1068
  // Clean up the write queue so we
1084
1069
  // don't apply these changes again
1085
1070
  delete mergeQueue[key];
1086
1071
 
1087
- return set(key, modifiedData);
1072
+ // After that we merge the batched changes with the existing value
1073
+ const modifiedData = applyMerge([batchedChanges], existingValue);
1074
+
1075
+ // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
1076
+ broadcastUpdate(key, modifiedData, 'merge');
1077
+
1078
+ return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].mergeItem(key, batchedChanges, modifiedData);
1088
1079
  } catch (error) {
1089
1080
  _Logger__WEBPACK_IMPORTED_MODULE_6__.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
1090
1081
  }
@@ -1093,6 +1084,15 @@ function merge(key, value) {
1093
1084
  });
1094
1085
  }
1095
1086
 
1087
+ /**
1088
+ * @private
1089
+ * @param {String} key
1090
+ * @returns {Boolean}
1091
+ */
1092
+ function hasPendingMergeForKey(key) {
1093
+ return Boolean(mergeQueue[key]);
1094
+ }
1095
+
1096
1096
  /**
1097
1097
  * Merge user provided default key value pairs.
1098
1098
  * @private
@@ -1401,6 +1401,7 @@ const Onyx = {
1401
1401
  multiSet,
1402
1402
  merge,
1403
1403
  mergeCollection,
1404
+ hasPendingMergeForKey,
1404
1405
  update,
1405
1406
  clear,
1406
1407
  getAllKeys,
@@ -2208,6 +2209,16 @@ const provider = {
2208
2209
  return localforage__WEBPACK_IMPORTED_MODULE_0___default().setItem(key, value);
2209
2210
  }),
2210
2211
 
2212
+ /**
2213
+ * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
2214
+ * @param {String} key
2215
+ * @param {*} value
2216
+ * @return {Promise<void>}
2217
+ */
2218
+ setItem(key, value) {
2219
+ return this.setItemQueue.push({ key, value });
2220
+ },
2221
+
2211
2222
  /**
2212
2223
  * Get multiple key-value pairs for the give array of keys in a batch
2213
2224
  * @param {String[]} keys
@@ -2235,6 +2246,17 @@ const provider = {
2235
2246
  return Promise.all(tasks).then(() => Promise.resolve());
2236
2247
  },
2237
2248
 
2249
+ /**
2250
+ * Merging an existing value with a new one
2251
+ * @param {String} key
2252
+ * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
2253
+ * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
2254
+ * @return {Promise<void>}
2255
+ */
2256
+ mergeItem(key, _changes, modifiedData) {
2257
+ return this.setItem(key, modifiedData);
2258
+ },
2259
+
2238
2260
  /**
2239
2261
  * Stores multiple key-value pairs in a batch
2240
2262
  * @param {Array<[key, value]>} pairs
@@ -2285,16 +2307,6 @@ const provider = {
2285
2307
  return localforage__WEBPACK_IMPORTED_MODULE_0___default().removeItems(keys);
2286
2308
  },
2287
2309
 
2288
- /**
2289
- * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
2290
- * @param {String} key
2291
- * @param {*} value
2292
- * @return {Promise<void>}
2293
- */
2294
- setItem(key, value) {
2295
- return this.setItemQueue.push({ key, value });
2296
- },
2297
-
2298
2310
  /**
2299
2311
  * @param {string[]} keyList
2300
2312
  */