react-native-onyx 1.0.93 → 1.0.95

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.
@@ -1124,7 +1124,13 @@ function set(key, value) {
1124
1124
  _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.`);
1125
1125
  }
1126
1126
 
1127
- const valueWithNullRemoved = _utils__WEBPACK_IMPORTED_MODULE_9__["default"].removeNullObjectValues(value);
1127
+ // We can remove all null values in an object by merging it with itself
1128
+ // utils.fastMerge recursively goes through the object and removes all null values
1129
+ // Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
1130
+ let valueWithNullRemoved = value;
1131
+ if (typeof value === 'object' && !underscore__WEBPACK_IMPORTED_MODULE_1___default().isArray(value)) {
1132
+ valueWithNullRemoved = _utils__WEBPACK_IMPORTED_MODULE_9__["default"].fastMerge(value, value);
1133
+ }
1128
1134
 
1129
1135
  const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, valueWithNullRemoved);
1130
1136
 
@@ -1181,9 +1187,10 @@ function multiSet(data) {
1181
1187
  * @private
1182
1188
  * @param {*} existingValue
1183
1189
  * @param {Array<*>} changes Array of changes that should be applied to the existing value
1190
+ * @param {Boolean} shouldRemoveNullObjectValues
1184
1191
  * @returns {*}
1185
1192
  */
1186
- function applyMerge(existingValue, changes) {
1193
+ function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
1187
1194
  const lastChange = underscore__WEBPACK_IMPORTED_MODULE_1___default().last(changes);
1188
1195
 
1189
1196
  if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isArray(lastChange)) {
@@ -1192,7 +1199,7 @@ function applyMerge(existingValue, changes) {
1192
1199
 
1193
1200
  if (underscore__WEBPACK_IMPORTED_MODULE_1___default().some(changes, (underscore__WEBPACK_IMPORTED_MODULE_1___default().isObject))) {
1194
1201
  // Object values are then merged one after the other
1195
- return underscore__WEBPACK_IMPORTED_MODULE_1___default().reduce(changes, (modifiedData, change) => _utils__WEBPACK_IMPORTED_MODULE_9__["default"].fastMerge(modifiedData, change),
1202
+ return underscore__WEBPACK_IMPORTED_MODULE_1___default().reduce(changes, (modifiedData, change) => _utils__WEBPACK_IMPORTED_MODULE_9__["default"].fastMerge(modifiedData, change, shouldRemoveNullObjectValues),
1196
1203
  existingValue || {});
1197
1204
  }
1198
1205
 
@@ -1240,7 +1247,8 @@ function merge(key, changes) {
1240
1247
  then((existingValue) => {
1241
1248
  try {
1242
1249
  // 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)
1243
- let batchedChanges = applyMerge(undefined, mergeQueue[key]);
1250
+ // We don't want to remove null values from the "batchedChanges", because SQLite uses them to remove keys from storage natively.
1251
+ let batchedChanges = applyMerge(undefined, mergeQueue[key], false);
1244
1252
 
1245
1253
  if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isNull(batchedChanges)) {
1246
1254
  return remove(key);
@@ -1255,15 +1263,16 @@ function merge(key, changes) {
1255
1263
  delete mergeQueuePromise[key];
1256
1264
 
1257
1265
  // After that we merge the batched changes with the existing value
1258
- const updatedValue = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges]);
1259
- const modifiedData = _utils__WEBPACK_IMPORTED_MODULE_9__["default"].removeNullObjectValues(updatedValue);
1266
+ // We can remove null values from the "modifiedData", because "null" implicates that the user wants to remove a value from storage.
1267
+ // The "modifiedData" will be directly "set" in storage instead of being merged
1268
+ const modifiedData = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges], true);
1260
1269
 
1261
1270
  // On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
1262
1271
  // JSON_PATCH generally removes top-level nullish values from the stored object.
1263
- // When there is no existing value though, SQLite will just insert the changes as a new value and thus the top-level nullish values won't be removed.
1264
- // Therefore we need to remove nullish values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
1272
+ // When there is no existing value though, SQLite will just insert the changes as a new value and thus the null values won't be removed.
1273
+ // Therefore we need to remove null values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
1265
1274
  if (!existingValue) {
1266
- batchedChanges = _utils__WEBPACK_IMPORTED_MODULE_9__["default"].removeNullObjectValues(batchedChanges);
1275
+ batchedChanges = applyMerge(undefined, mergeQueue[key], true);
1267
1276
  }
1268
1277
 
1269
1278
  const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, modifiedData);
@@ -1719,9 +1728,11 @@ class OnyxCache {
1719
1728
  /**
1720
1729
  * @private
1721
1730
  * Captured pending tasks for already running storage methods
1722
- * @type {Record<string, Promise>}
1731
+ * Using a map yields better performance on operations such a delete
1732
+ * https://www.zhenghao.io/posts/object-vs-map
1733
+ * @type {Map<string, Promise>}
1723
1734
  */
1724
- this.pendingPromises = {};
1735
+ this.pendingPromises = new Map();
1725
1736
 
1726
1737
  // bind all public methods to prevent problems with `this`
1727
1738
  underscore__WEBPACK_IMPORTED_MODULE_0___default().bindAll(
@@ -1818,7 +1829,7 @@ class OnyxCache {
1818
1829
  * @returns {*}
1819
1830
  */
1820
1831
  hasPendingTask(taskName) {
1821
- return isDefined(this.pendingPromises[taskName]);
1832
+ return isDefined(this.pendingPromises.get(taskName));
1822
1833
  }
1823
1834
 
1824
1835
  /**
@@ -1830,7 +1841,7 @@ class OnyxCache {
1830
1841
  * @returns {Promise<T>}
1831
1842
  */
1832
1843
  getTaskPromise(taskName) {
1833
- return this.pendingPromises[taskName];
1844
+ return this.pendingPromises.get(taskName);
1834
1845
  }
1835
1846
 
1836
1847
  /**
@@ -1842,11 +1853,13 @@ class OnyxCache {
1842
1853
  * @returns {Promise<T>}
1843
1854
  */
1844
1855
  captureTask(taskName, promise) {
1845
- this.pendingPromises[taskName] = promise.finally(() => {
1846
- delete this.pendingPromises[taskName];
1856
+ const returnPromise = promise.finally(() => {
1857
+ this.pendingPromises.delete(taskName);
1847
1858
  });
1848
1859
 
1849
- return this.pendingPromises[taskName];
1860
+ this.pendingPromises.set(taskName, returnPromise);
1861
+
1862
+ return returnPromise;
1850
1863
  }
1851
1864
 
1852
1865
  /**
@@ -2287,7 +2300,7 @@ const provider = {
2287
2300
  const upsertMany = underscore__WEBPACK_IMPORTED_MODULE_1___default().map(pairs, (_ref2, index) => {let [key, value] = _ref2;
2288
2301
  const prev = values[index];
2289
2302
  const newValue = underscore__WEBPACK_IMPORTED_MODULE_1___default().isObject(prev) ? _utils__WEBPACK_IMPORTED_MODULE_2__["default"].fastMerge(prev, value) : value;
2290
- return (0,idb_keyval__WEBPACK_IMPORTED_MODULE_0__.promisifyRequest)(store.put(_utils__WEBPACK_IMPORTED_MODULE_2__["default"].removeNullObjectValues(newValue), key));
2303
+ return (0,idb_keyval__WEBPACK_IMPORTED_MODULE_0__.promisifyRequest)(store.put(newValue, key));
2291
2304
  });
2292
2305
  return Promise.all(upsertMany);
2293
2306
  });
@@ -2392,8 +2405,8 @@ function areObjectsEmpty(a, b) {
2392
2405
  return (
2393
2406
  typeof a === 'object' &&
2394
2407
  typeof b === 'object' &&
2395
- underscore__WEBPACK_IMPORTED_MODULE_0__.isEmpty(a) &&
2396
- underscore__WEBPACK_IMPORTED_MODULE_0__.isEmpty(b));
2408
+ underscore__WEBPACK_IMPORTED_MODULE_0___default().isEmpty(a) &&
2409
+ underscore__WEBPACK_IMPORTED_MODULE_0___default().isEmpty(b));
2397
2410
 
2398
2411
  }
2399
2412
 
@@ -2413,9 +2426,12 @@ function isMergeableObject(val) {
2413
2426
  /**
2414
2427
  * @param {Object} target
2415
2428
  * @param {Object} source
2429
+ * @param {Boolean} shouldRemoveNullObjectValues
2416
2430
  * @returns {Object}
2417
2431
  */
2418
- function mergeObject(target, source) {
2432
+ function mergeObject(target, source) {let shouldRemoveNullObjectValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
2433
+ const targetAndSourceIdentical = target === source;
2434
+
2419
2435
  const destination = {};
2420
2436
  if (isMergeableObject(target)) {
2421
2437
  // lodash adds a small overhead so we don't use it here
@@ -2423,6 +2439,13 @@ function mergeObject(target, source) {
2423
2439
  const targetKeys = Object.keys(target);
2424
2440
  for (let i = 0; i < targetKeys.length; ++i) {
2425
2441
  const key = targetKeys[i];
2442
+
2443
+ // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
2444
+ if (shouldRemoveNullObjectValues && (target[key] === null || source[key] === null)) {
2445
+ // eslint-disable-next-line no-continue
2446
+ continue;
2447
+ }
2448
+
2426
2449
  destination[key] = target[key];
2427
2450
  }
2428
2451
  }
@@ -2432,15 +2455,22 @@ function mergeObject(target, source) {
2432
2455
  const sourceKeys = Object.keys(source);
2433
2456
  for (let i = 0; i < sourceKeys.length; ++i) {
2434
2457
  const key = sourceKeys[i];
2435
- if (source[key] === undefined) {
2458
+
2459
+ // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
2460
+ if (shouldRemoveNullObjectValues && source[key] === null) {
2436
2461
  // eslint-disable-next-line no-continue
2437
2462
  continue;
2438
2463
  }
2464
+
2439
2465
  if (!isMergeableObject(source[key]) || !target[key]) {
2466
+ if (targetAndSourceIdentical) {
2467
+ // eslint-disable-next-line no-continue
2468
+ continue;
2469
+ }
2440
2470
  destination[key] = source[key];
2441
2471
  } else {
2442
2472
  // eslint-disable-next-line no-use-before-define
2443
- destination[key] = fastMerge(target[key], source[key]);
2473
+ destination[key] = fastMerge(target[key], source[key], shouldRemoveNullObjectValues);
2444
2474
  }
2445
2475
  }
2446
2476
 
@@ -2448,41 +2478,28 @@ function mergeObject(target, source) {
2448
2478
  }
2449
2479
 
2450
2480
  /**
2481
+ * Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true
2482
+ *
2483
+ * We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
2484
+ * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
2485
+ * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
2486
+ *
2451
2487
  * @param {Object|Array} target
2452
2488
  * @param {Object|Array} source
2489
+ * @param {Boolean} shouldRemoveNullObjectValues
2453
2490
  * @returns {Object|Array}
2454
2491
  */
2455
- function fastMerge(target, source) {
2492
+ function fastMerge(target, source) {let shouldRemoveNullObjectValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
2456
2493
  // We have to ignore arrays and nullish values here,
2457
2494
  // otherwise "mergeObject" will throw an error,
2458
2495
  // because it expects an object as "source"
2459
- if (underscore__WEBPACK_IMPORTED_MODULE_0__.isArray(source) || underscore__WEBPACK_IMPORTED_MODULE_0__.isNull(source) || underscore__WEBPACK_IMPORTED_MODULE_0__.isUndefined(source)) {
2496
+ if (underscore__WEBPACK_IMPORTED_MODULE_0___default().isArray(source) || source === null || source === undefined) {
2460
2497
  return source;
2461
2498
  }
2462
- return mergeObject(target, source);
2463
- }
2464
-
2465
- /**
2466
- * We generally want to remove top-level nullish values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
2467
- * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
2468
- * To be consistent with the behaviour for merge, we'll also want to remove nullish values for "set" operations.
2469
- * On web, IndexedDB will keep the top-level keys along with a null value and this uses up storage and memory.
2470
- * This method will ensure that keys for null values are removed before an object is written to disk and cache so that all platforms are storing the data in the same efficient way.
2471
- * @private
2472
- * @param {*} value
2473
- * @returns {*}
2474
- */
2475
- function removeNullObjectValues(value) {
2476
- if (underscore__WEBPACK_IMPORTED_MODULE_0__.isArray(value) || !underscore__WEBPACK_IMPORTED_MODULE_0__.isObject(value)) {
2477
- return value;
2478
- }
2479
-
2480
- const objectWithoutNullObjectValues = underscore__WEBPACK_IMPORTED_MODULE_0__.omit(value, (objectValue) => underscore__WEBPACK_IMPORTED_MODULE_0__.isNull(objectValue));
2481
-
2482
- return objectWithoutNullObjectValues;
2499
+ return mergeObject(target, source, shouldRemoveNullObjectValues);
2483
2500
  }
2484
2501
 
2485
- /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ removeNullObjectValues, areObjectsEmpty, fastMerge });
2502
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ areObjectsEmpty, fastMerge });
2486
2503
 
2487
2504
  /***/ }),
2488
2505