react-native-onyx 1.0.53 → 1.0.55

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,38 @@ 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 {Boolean} hasChanged
936
+ * @param {String} method
937
+ */
938
+ function broadcastUpdate(key, value, hasChanged, method) {
939
+ // Logging properties only since values could be sensitive things we don't want to log
940
+ _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(',')}` : ''}`);
941
+
942
+ // Update subscribers if the cached value has changed, or when the subscriber specifically requires
943
+ // all updates regardless of value changes (indicated by initWithStoredValues set to false).
944
+ if (hasChanged) {
945
+ _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].set(key, value);
946
+ } else {
947
+ _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].addToAccessedKeys(key);
948
+ }
949
+
950
+ notifySubscribersOnNextTick(key, value, (subscriber) => hasChanged || subscriber.initWithStoredValues === false);
951
+ }
952
+
953
+ /**
954
+ * @private
955
+ * @param {String} key
956
+ * @returns {Boolean}
957
+ */
958
+ function hasPendingMergeForKey(key) {
959
+ return Boolean(mergeQueue[key]);
960
+ }
961
+
927
962
  /**
928
963
  * Write a value to our store with the given key
929
964
  *
@@ -937,24 +972,20 @@ function set(key, value) {
937
972
  return remove(key);
938
973
  }
939
974
 
940
- // eslint-disable-next-line no-use-before-define
941
975
  if (hasPendingMergeForKey(key)) {
942
976
  _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
977
  }
944
978
 
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);
979
+ const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, value);
980
+
981
+ // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
982
+ broadcastUpdate(key, value, hasChanged, 'set');
983
+
984
+ // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
985
+ if (!hasChanged) {
950
986
  return Promise.resolve();
951
987
  }
952
988
 
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
989
  return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].setItem(key, value).
959
990
  catch((error) => evictStorageAndRetry(error, set, key, value));
960
991
  }
@@ -992,67 +1023,39 @@ function multiSet(data) {
992
1023
  catch((error) => evictStorageAndRetry(error, multiSet, data));
993
1024
  }
994
1025
 
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
1026
  /**
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.
1027
+ * Merges an array of changes with an existing value
1012
1028
  *
1013
1029
  * @private
1014
- * @param {String} key
1015
- * @param {*} data
1016
- *
1030
+ * @param {*} existingValue
1031
+ * @param {Array<*>} changes Array of changes that should be applied to the existing value
1017
1032
  * @returns {*}
1018
1033
  */
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))) {
1031
- // Object values are merged one after the other
1032
- return underscore__WEBPACK_IMPORTED_MODULE_2___default().reduce(mergeValues, (modifiedData, mergeValue) => {
1033
- // lodash adds a small overhead so we don't use it here
1034
- // 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));
1034
+ function applyMerge(existingValue, changes) {
1035
+ const lastChange = underscore__WEBPACK_IMPORTED_MODULE_2___default().last(changes);
1036
1036
 
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
- // 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 || {});
1037
+ if (underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray(existingValue) || underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray(lastChange)) {
1038
+ return lastChange;
1039
+ }
1040
+
1041
+ if (underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject(existingValue) || underscore__WEBPACK_IMPORTED_MODULE_2___default().every(changes, (underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject))) {
1042
+ // Object values are merged one after the other
1043
+ // lodash adds a small overhead so we don't use it here
1044
+ // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
1045
+ return underscore__WEBPACK_IMPORTED_MODULE_2___default().reduce(changes, (modifiedData, change) => (0,_fastMerge__WEBPACK_IMPORTED_MODULE_9__["default"])(modifiedData, change),
1046
+ existingValue || {});
1042
1047
  }
1043
1048
 
1044
1049
  // If we have anything else we can't merge it so we'll
1045
1050
  // simply return the last value that was queued
1046
- return underscore__WEBPACK_IMPORTED_MODULE_2___default().last(mergeValues);
1051
+ return lastChange;
1047
1052
  }
1048
1053
 
1049
1054
  /**
1050
1055
  * Merge a new value into an existing value at a key.
1051
1056
  *
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
1057
+ * The types of values that can be merged are `Object` and `Array`. To set another type of value use `Onyx.set()`.
1058
+ * 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
1059
  *
1057
1060
  * Calls to `Onyx.merge()` are batched so that any calls performed in a single tick will stack in a queue and get
1058
1061
  * applied in the order they were called. Note: `Onyx.set()` calls do not work this way so use caution when mixing
@@ -1065,26 +1068,48 @@ function applyMerge(key, data) {
1065
1068
  * Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
1066
1069
  *
1067
1070
  * @param {String} key ONYXKEYS key
1068
- * @param {(Object|Array)} value Object or Array value to merge
1071
+ * @param {(Object|Array)} changes Object or Array value to merge
1069
1072
  * @returns {Promise}
1070
1073
  */
1071
- function merge(key, value) {
1074
+ function merge(key, changes) {
1075
+ // Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
1076
+ // Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
1072
1077
  if (mergeQueue[key]) {
1073
- mergeQueue[key].push(value);
1078
+ mergeQueue[key].push(changes);
1074
1079
  return Promise.resolve();
1075
1080
  }
1081
+ mergeQueue[key] = [changes];
1076
1082
 
1077
- mergeQueue[key] = [value];
1078
1083
  return get(key).
1079
- then((data) => {
1084
+ then((existingValue) => {
1080
1085
  try {
1081
- const modifiedData = applyMerge(key, data);
1086
+ // 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)
1087
+ const batchedChanges = applyMerge(undefined, mergeQueue[key]);
1082
1088
 
1083
1089
  // Clean up the write queue so we
1084
1090
  // don't apply these changes again
1085
1091
  delete mergeQueue[key];
1086
1092
 
1087
- return set(key, modifiedData);
1093
+ // After that we merge the batched changes with the existing value
1094
+ let modifiedData = applyMerge(existingValue, [batchedChanges]);
1095
+
1096
+ // For objects, the key for null values needs to be removed from the object to ensure the value will get removed from storage completely.
1097
+ // On native, SQLite will remove top-level keys that are null. To be consistent, we remove them on web too.
1098
+ if (!underscore__WEBPACK_IMPORTED_MODULE_2___default().isArray(modifiedData) && underscore__WEBPACK_IMPORTED_MODULE_2___default().isObject(modifiedData)) {
1099
+ modifiedData = underscore__WEBPACK_IMPORTED_MODULE_2___default().omit(modifiedData, (value) => underscore__WEBPACK_IMPORTED_MODULE_2___default().isNull(value));
1100
+ }
1101
+
1102
+ const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, modifiedData);
1103
+
1104
+ // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
1105
+ broadcastUpdate(key, modifiedData, hasChanged, 'merge');
1106
+
1107
+ // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
1108
+ if (!hasChanged) {
1109
+ return Promise.resolve();
1110
+ }
1111
+
1112
+ return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].mergeItem(key, batchedChanges, modifiedData);
1088
1113
  } catch (error) {
1089
1114
  _Logger__WEBPACK_IMPORTED_MODULE_6__.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
1090
1115
  }
@@ -1401,6 +1426,7 @@ const Onyx = {
1401
1426
  multiSet,
1402
1427
  merge,
1403
1428
  mergeCollection,
1429
+ hasPendingMergeForKey,
1404
1430
  update,
1405
1431
  clear,
1406
1432
  getAllKeys,
@@ -2208,6 +2234,16 @@ const provider = {
2208
2234
  return localforage__WEBPACK_IMPORTED_MODULE_0___default().setItem(key, value);
2209
2235
  }),
2210
2236
 
2237
+ /**
2238
+ * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
2239
+ * @param {String} key
2240
+ * @param {*} value
2241
+ * @return {Promise<void>}
2242
+ */
2243
+ setItem(key, value) {
2244
+ return this.setItemQueue.push({ key, value });
2245
+ },
2246
+
2211
2247
  /**
2212
2248
  * Get multiple key-value pairs for the give array of keys in a batch
2213
2249
  * @param {String[]} keys
@@ -2235,6 +2271,17 @@ const provider = {
2235
2271
  return Promise.all(tasks).then(() => Promise.resolve());
2236
2272
  },
2237
2273
 
2274
+ /**
2275
+ * Merging an existing value with a new one
2276
+ * @param {String} key
2277
+ * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
2278
+ * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
2279
+ * @return {Promise<void>}
2280
+ */
2281
+ mergeItem(key, _changes, modifiedData) {
2282
+ return this.setItem(key, modifiedData);
2283
+ },
2284
+
2238
2285
  /**
2239
2286
  * Stores multiple key-value pairs in a batch
2240
2287
  * @param {Array<[key, value]>} pairs
@@ -2285,16 +2332,6 @@ const provider = {
2285
2332
  return localforage__WEBPACK_IMPORTED_MODULE_0___default().removeItems(keys);
2286
2333
  },
2287
2334
 
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
2335
  /**
2299
2336
  * @param {string[]} keyList
2300
2337
  */