react-native-onyx 1.0.97 → 1.0.99

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.
@@ -1107,6 +1107,25 @@ function hasPendingMergeForKey(key) {
1107
1107
  return Boolean(mergeQueue[key]);
1108
1108
  }
1109
1109
 
1110
+ /**
1111
+ * Removes a key from storage if the value is null.
1112
+ * Otherwise removes all nested null values in objects and returns the object
1113
+ * @param {String} key
1114
+ * @param {Mixed} value
1115
+ * @returns {Mixed} `null` if the key got removed completely, otherwise the value without null values
1116
+ */
1117
+ function removeNullValues(key, value) {
1118
+ if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isNull(value)) {
1119
+ remove(key);
1120
+ return null;
1121
+ }
1122
+
1123
+ // We can remove all null values in an object by merging it with itself
1124
+ // utils.fastMerge recursively goes through the object and removes all null values
1125
+ // Passing two identical objects as source and target to fastMerge will not change it, but only remove the null values
1126
+ return _utils__WEBPACK_IMPORTED_MODULE_9__["default"].removeNestedNullValues(value);
1127
+ }
1128
+
1110
1129
  /**
1111
1130
  * Write a value to our store with the given key
1112
1131
  *
@@ -1116,28 +1135,28 @@ function hasPendingMergeForKey(key) {
1116
1135
  * @returns {Promise}
1117
1136
  */
1118
1137
  function set(key, value) {
1119
- if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isNull(value)) {
1120
- return remove(key);
1138
+ const valueWithoutNull = removeNullValues(key, value);
1139
+
1140
+ if (valueWithoutNull === null) {
1141
+ return Promise.resolve();
1121
1142
  }
1122
1143
 
1123
1144
  if (hasPendingMergeForKey(key)) {
1124
1145
  _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
1146
  }
1126
1147
 
1127
- const valueWithNullRemoved = _utils__WEBPACK_IMPORTED_MODULE_9__["default"].removeNullObjectValues(value);
1128
-
1129
- const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, valueWithNullRemoved);
1148
+ const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, valueWithoutNull);
1130
1149
 
1131
1150
  // This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
1132
- const updatePromise = broadcastUpdate(key, valueWithNullRemoved, hasChanged, 'set');
1151
+ const updatePromise = broadcastUpdate(key, valueWithoutNull, hasChanged, 'set');
1133
1152
 
1134
1153
  // If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
1135
1154
  if (!hasChanged) {
1136
1155
  return updatePromise;
1137
1156
  }
1138
1157
 
1139
- return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].setItem(key, valueWithNullRemoved).
1140
- catch((error) => evictStorageAndRetry(error, set, key, valueWithNullRemoved)).
1158
+ return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].setItem(key, valueWithoutNull).
1159
+ catch((error) => evictStorageAndRetry(error, set, key, valueWithoutNull)).
1141
1160
  then(() => updatePromise);
1142
1161
  }
1143
1162
 
@@ -1170,7 +1189,16 @@ function multiSet(data) {
1170
1189
  return scheduleSubscriberUpdate(key, val);
1171
1190
  });
1172
1191
 
1173
- return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].multiSet(keyValuePairs).
1192
+ const keyValuePairsWithoutNull = underscore__WEBPACK_IMPORTED_MODULE_1___default().filter(underscore__WEBPACK_IMPORTED_MODULE_1___default().map(keyValuePairs, (_ref2) => {let [key, value] = _ref2;
1193
+ const valueWithoutNull = removeNullValues(key, value);
1194
+
1195
+ if (valueWithoutNull === null) {
1196
+ return;
1197
+ }
1198
+ return [key, valueWithoutNull];
1199
+ }), Boolean);
1200
+
1201
+ return _storage__WEBPACK_IMPORTED_MODULE_5__["default"].multiSet(keyValuePairsWithoutNull).
1174
1202
  catch((error) => evictStorageAndRetry(error, multiSet, data)).
1175
1203
  then(() => Promise.all(updatePromises));
1176
1204
  }
@@ -1181,9 +1209,10 @@ function multiSet(data) {
1181
1209
  * @private
1182
1210
  * @param {*} existingValue
1183
1211
  * @param {Array<*>} changes Array of changes that should be applied to the existing value
1212
+ * @param {Boolean} shouldRemoveNullObjectValues
1184
1213
  * @returns {*}
1185
1214
  */
1186
- function applyMerge(existingValue, changes) {
1215
+ function applyMerge(existingValue, changes, shouldRemoveNullObjectValues) {
1187
1216
  const lastChange = underscore__WEBPACK_IMPORTED_MODULE_1___default().last(changes);
1188
1217
 
1189
1218
  if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isArray(lastChange)) {
@@ -1192,7 +1221,7 @@ function applyMerge(existingValue, changes) {
1192
1221
 
1193
1222
  if (underscore__WEBPACK_IMPORTED_MODULE_1___default().some(changes, (underscore__WEBPACK_IMPORTED_MODULE_1___default().isObject))) {
1194
1223
  // 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),
1224
+ return underscore__WEBPACK_IMPORTED_MODULE_1___default().reduce(changes, (modifiedData, change) => _utils__WEBPACK_IMPORTED_MODULE_9__["default"].fastMerge(modifiedData, change, shouldRemoveNullObjectValues),
1196
1225
  existingValue || {});
1197
1226
  }
1198
1227
 
@@ -1240,10 +1269,12 @@ function merge(key, changes) {
1240
1269
  then((existingValue) => {
1241
1270
  try {
1242
1271
  // 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]);
1272
+ // We don't want to remove null values from the "batchedChanges", because SQLite uses them to remove keys from storage natively.
1273
+ let batchedChanges = applyMerge(undefined, mergeQueue[key], false);
1244
1274
 
1245
1275
  if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isNull(batchedChanges)) {
1246
- return remove(key);
1276
+ remove(key);
1277
+ return;
1247
1278
  }
1248
1279
 
1249
1280
  // The presence of a `null` in the merge queue instructs us to drop the existing value.
@@ -1255,15 +1286,16 @@ function merge(key, changes) {
1255
1286
  delete mergeQueuePromise[key];
1256
1287
 
1257
1288
  // 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);
1289
+ // We can remove null values from the "modifiedData", because "null" implicates that the user wants to remove a value from storage.
1290
+ // The "modifiedData" will be directly "set" in storage instead of being merged
1291
+ const modifiedData = shouldOverwriteExistingValue ? batchedChanges : applyMerge(existingValue, [batchedChanges], true);
1260
1292
 
1261
1293
  // On native platforms we use SQLite which utilises JSON_PATCH to merge changes.
1262
- // 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.
1294
+ // JSON_PATCH generally removes null values from the stored object.
1295
+ // 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.
1296
+ // Therefore we need to remove null values from the `batchedChanges` which are sent to the SQLite, if no existing value is present.
1265
1297
  if (!existingValue) {
1266
- batchedChanges = _utils__WEBPACK_IMPORTED_MODULE_9__["default"].removeNullObjectValues(batchedChanges);
1298
+ batchedChanges = applyMerge(undefined, [batchedChanges], true);
1267
1299
  }
1268
1300
 
1269
1301
  const hasChanged = _OnyxCache__WEBPACK_IMPORTED_MODULE_4__["default"].hasValueChanged(key, modifiedData);
@@ -1432,6 +1464,13 @@ function mergeCollection(collectionKey, collection) {
1432
1464
  then((persistedKeys) => {
1433
1465
  // Split to keys that exist in storage and keys that don't
1434
1466
  const [existingKeys, newKeys] = underscore__WEBPACK_IMPORTED_MODULE_1___default().chain(collection).
1467
+ pick((value, key) => {
1468
+ if (underscore__WEBPACK_IMPORTED_MODULE_1___default().isNull(value)) {
1469
+ remove(key);
1470
+ return false;
1471
+ }
1472
+ return true;
1473
+ }).
1435
1474
  keys().
1436
1475
  partition((key) => persistedKeys.includes(key)).
1437
1476
  value();
@@ -1474,7 +1513,7 @@ function mergeCollection(collectionKey, collection) {
1474
1513
  */
1475
1514
  function update(data) {
1476
1515
  // First, validate the Onyx object is in the format we expect
1477
- underscore__WEBPACK_IMPORTED_MODULE_1___default().each(data, (_ref2) => {let { onyxMethod, key, value } = _ref2;
1516
+ underscore__WEBPACK_IMPORTED_MODULE_1___default().each(data, (_ref3) => {let { onyxMethod, key, value } = _ref3;
1478
1517
  if (!underscore__WEBPACK_IMPORTED_MODULE_1___default().contains([METHOD.CLEAR, METHOD.SET, METHOD.MERGE, METHOD.MERGE_COLLECTION, METHOD.MULTI_SET], onyxMethod)) {
1479
1518
  throw new Error(`Invalid onyxMethod ${onyxMethod} in Onyx update.`);
1480
1519
  }
@@ -1491,7 +1530,7 @@ function update(data) {
1491
1530
  const promises = [];
1492
1531
  let clearPromise = Promise.resolve();
1493
1532
 
1494
- underscore__WEBPACK_IMPORTED_MODULE_1___default().each(data, (_ref3) => {let { onyxMethod, key, value } = _ref3;
1533
+ underscore__WEBPACK_IMPORTED_MODULE_1___default().each(data, (_ref4) => {let { onyxMethod, key, value } = _ref4;
1495
1534
  switch (onyxMethod) {
1496
1535
  case METHOD.SET:
1497
1536
  promises.push(() => set(key, value));
@@ -2279,6 +2318,7 @@ const provider = {
2279
2318
  /**
2280
2319
  * Multiple merging of existing and new values in a batch
2281
2320
  * @param {Array<[key, value]>} pairs
2321
+ * This function also removes all nested null values from an object.
2282
2322
  * @return {Promise<void>}
2283
2323
  */
2284
2324
  multiMerge: (pairs) => getCustomStore()('readwrite', (store) => {
@@ -2290,8 +2330,8 @@ const provider = {
2290
2330
  return getValues.then((values) => {
2291
2331
  const upsertMany = underscore__WEBPACK_IMPORTED_MODULE_1___default().map(pairs, (_ref2, index) => {let [key, value] = _ref2;
2292
2332
  const prev = values[index];
2293
- const newValue = underscore__WEBPACK_IMPORTED_MODULE_1___default().isObject(prev) ? _utils__WEBPACK_IMPORTED_MODULE_2__["default"].fastMerge(prev, value) : value;
2294
- return (0,idb_keyval__WEBPACK_IMPORTED_MODULE_0__.promisifyRequest)(store.put(_utils__WEBPACK_IMPORTED_MODULE_2__["default"].removeNullObjectValues(newValue), key));
2333
+ const newValue = _utils__WEBPACK_IMPORTED_MODULE_2__["default"].fastMarge(prev, value);
2334
+ return (0,idb_keyval__WEBPACK_IMPORTED_MODULE_0__.promisifyRequest)(store.put(newValue, key));
2295
2335
  });
2296
2336
  return Promise.all(upsertMany);
2297
2337
  });
@@ -2396,8 +2436,8 @@ function areObjectsEmpty(a, b) {
2396
2436
  return (
2397
2437
  typeof a === 'object' &&
2398
2438
  typeof b === 'object' &&
2399
- underscore__WEBPACK_IMPORTED_MODULE_0__.isEmpty(a) &&
2400
- underscore__WEBPACK_IMPORTED_MODULE_0__.isEmpty(b));
2439
+ underscore__WEBPACK_IMPORTED_MODULE_0___default().isEmpty(a) &&
2440
+ underscore__WEBPACK_IMPORTED_MODULE_0___default().isEmpty(b));
2401
2441
 
2402
2442
  }
2403
2443
 
@@ -2411,15 +2451,18 @@ function isMergeableObject(val) {
2411
2451
  const nonNullObject = val != null ? typeof val === 'object' : false;
2412
2452
  return nonNullObject &&
2413
2453
  Object.prototype.toString.call(val) !== '[object RegExp]' &&
2414
- Object.prototype.toString.call(val) !== '[object Date]';
2454
+ Object.prototype.toString.call(val) !== '[object Date]'
2455
+ // eslint-disable-next-line rulesdir/prefer-underscore-method
2456
+ && !Array.isArray(val);
2415
2457
  }
2416
2458
 
2417
2459
  /**
2418
2460
  * @param {Object} target
2419
2461
  * @param {Object} source
2462
+ * @param {Boolean} shouldRemoveNullObjectValues
2420
2463
  * @returns {Object}
2421
2464
  */
2422
- function mergeObject(target, source) {
2465
+ function mergeObject(target, source) {let shouldRemoveNullObjectValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
2423
2466
  const destination = {};
2424
2467
  if (isMergeableObject(target)) {
2425
2468
  // lodash adds a small overhead so we don't use it here
@@ -2427,7 +2470,14 @@ function mergeObject(target, source) {
2427
2470
  const targetKeys = Object.keys(target);
2428
2471
  for (let i = 0; i < targetKeys.length; ++i) {
2429
2472
  const key = targetKeys[i];
2430
- destination[key] = target[key];
2473
+
2474
+ // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
2475
+ const isSourceOrTargetNull = target[key] === null || source[key] === null;
2476
+ const shouldOmitSourceKey = shouldRemoveNullObjectValues && isSourceOrTargetNull;
2477
+
2478
+ if (!shouldOmitSourceKey) {
2479
+ destination[key] = target[key];
2480
+ }
2431
2481
  }
2432
2482
  }
2433
2483
 
@@ -2436,15 +2486,24 @@ function mergeObject(target, source) {
2436
2486
  const sourceKeys = Object.keys(source);
2437
2487
  for (let i = 0; i < sourceKeys.length; ++i) {
2438
2488
  const key = sourceKeys[i];
2439
- if (source[key] === undefined) {
2440
- // eslint-disable-next-line no-continue
2441
- continue;
2442
- }
2443
- if (!isMergeableObject(source[key]) || !target[key]) {
2444
- destination[key] = source[key];
2445
- } else {
2446
- // eslint-disable-next-line no-use-before-define
2447
- destination[key] = fastMerge(target[key], source[key]);
2489
+
2490
+ // If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
2491
+ const shouldOmitSourceKey = shouldRemoveNullObjectValues && source[key] === null;
2492
+
2493
+ // If we pass undefined as the updated value for a key, we want to generally ignore it
2494
+ const isSourceKeyUndefined = source[key] === undefined;
2495
+
2496
+ if (!isSourceKeyUndefined && !shouldOmitSourceKey) {
2497
+ const isSourceKeyMergable = isMergeableObject(source[key]);
2498
+
2499
+ if (isSourceKeyMergable && target[key]) {
2500
+ if (!shouldRemoveNullObjectValues || isSourceKeyMergable) {
2501
+ // eslint-disable-next-line no-use-before-define
2502
+ destination[key] = fastMerge(target[key], source[key], shouldRemoveNullObjectValues);
2503
+ }
2504
+ } else if (!shouldRemoveNullObjectValues || source[key] !== null) {
2505
+ destination[key] = source[key];
2506
+ }
2448
2507
  }
2449
2508
  }
2450
2509
 
@@ -2452,41 +2511,36 @@ function mergeObject(target, source) {
2452
2511
  }
2453
2512
 
2454
2513
  /**
2514
+ * Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true
2515
+ *
2516
+ * 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.
2517
+ * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
2518
+ * To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
2519
+ *
2455
2520
  * @param {Object|Array} target
2456
2521
  * @param {Object|Array} source
2522
+ * @param {Boolean} shouldRemoveNullObjectValues
2457
2523
  * @returns {Object|Array}
2458
2524
  */
2459
- function fastMerge(target, source) {
2525
+ function fastMerge(target, source) {let shouldRemoveNullObjectValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
2460
2526
  // We have to ignore arrays and nullish values here,
2461
2527
  // otherwise "mergeObject" will throw an error,
2462
2528
  // because it expects an object as "source"
2463
- if (underscore__WEBPACK_IMPORTED_MODULE_0__.isArray(source) || underscore__WEBPACK_IMPORTED_MODULE_0__.isNull(source) || underscore__WEBPACK_IMPORTED_MODULE_0__.isUndefined(source)) {
2529
+ if (underscore__WEBPACK_IMPORTED_MODULE_0___default().isArray(source) || source === null || source === undefined) {
2464
2530
  return source;
2465
2531
  }
2466
- return mergeObject(target, source);
2532
+ return mergeObject(target, source, shouldRemoveNullObjectValues);
2467
2533
  }
2468
2534
 
2469
- /**
2470
- * 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.
2471
- * On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
2472
- * To be consistent with the behaviour for merge, we'll also want to remove nullish values for "set" operations.
2473
- * On web, IndexedDB will keep the top-level keys along with a null value and this uses up storage and memory.
2474
- * 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.
2475
- * @private
2476
- * @param {*} value
2477
- * @returns {*}
2478
- */
2479
- function removeNullObjectValues(value) {
2480
- if (underscore__WEBPACK_IMPORTED_MODULE_0__.isArray(value) || !underscore__WEBPACK_IMPORTED_MODULE_0__.isObject(value)) {
2481
- return value;
2535
+ function removeNestedNullValues(value) {
2536
+ if (typeof value === 'object' && !underscore__WEBPACK_IMPORTED_MODULE_0___default().isArray(value)) {
2537
+ return fastMerge(value, value);
2482
2538
  }
2483
2539
 
2484
- const objectWithoutNullObjectValues = underscore__WEBPACK_IMPORTED_MODULE_0__.omit(value, (objectValue) => underscore__WEBPACK_IMPORTED_MODULE_0__.isNull(objectValue));
2485
-
2486
- return objectWithoutNullObjectValues;
2540
+ return value;
2487
2541
  }
2488
2542
 
2489
- /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ removeNullObjectValues, areObjectsEmpty, fastMerge });
2543
+ /* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({ areObjectsEmpty, fastMerge, removeNestedNullValues });
2490
2544
 
2491
2545
  /***/ }),
2492
2546