react-native-onyx 2.0.34 → 2.0.36

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 CHANGED
@@ -36,6 +36,7 @@ const storage_1 = __importDefault(require("./storage"));
36
36
  const utils_1 = __importDefault(require("./utils"));
37
37
  const DevTools_1 = __importDefault(require("./DevTools"));
38
38
  const OnyxUtils_1 = __importDefault(require("./OnyxUtils"));
39
+ const logMessages_1 = __importDefault(require("./logMessages"));
39
40
  // Keeps track of the last connectionID that was used so we can keep incrementing it
40
41
  let lastConnectionID = 0;
41
42
  // Connections can be made before `Onyx.init`. They would wait for this task before resolving
@@ -191,6 +192,13 @@ function disconnect(connectionID, keyToRemoveFromEvictionBlocklist) {
191
192
  * @param value value to store
192
193
  */
193
194
  function set(key, value) {
195
+ // check if the value is compatible with the existing value in the storage
196
+ const existingValue = OnyxCache_1.default.getValue(key, false);
197
+ const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(value, existingValue);
198
+ if (!isCompatible) {
199
+ Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'set', existingValueType, newValueType));
200
+ return Promise.resolve();
201
+ }
194
202
  // If the value is null, we remove the key from storage
195
203
  const { value: valueAfterRemoving, wasRemoved } = OnyxUtils_1.default.removeNullValues(key, value);
196
204
  const valueWithoutNullValues = valueAfterRemoving;
@@ -275,7 +283,17 @@ function merge(key, changes) {
275
283
  try {
276
284
  // 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)
277
285
  // We don't want to remove null values from the "batchedDeltaChanges", because SQLite uses them to remove keys from storage natively.
278
- const batchedDeltaChanges = OnyxUtils_1.default.applyMerge(undefined, mergeQueue[key], false);
286
+ const validChanges = mergeQueue[key].filter((change) => {
287
+ const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(change, existingValue);
288
+ if (!isCompatible) {
289
+ Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'merge', existingValueType, newValueType));
290
+ }
291
+ return isCompatible;
292
+ });
293
+ if (!validChanges.length) {
294
+ return undefined;
295
+ }
296
+ const batchedDeltaChanges = OnyxUtils_1.default.applyMerge(undefined, validChanges, false);
279
297
  // Case (1): When there is no existing value in storage, we want to set the value instead of merge it.
280
298
  // Case (2): The presence of a top-level `null` in the merge queue instructs us to drop the whole existing value.
281
299
  // In this case, we can't simply merge the batched changes with the existing value, because then the null in the merge queue would have no effect
@@ -358,8 +376,14 @@ function mergeCollection(collectionKey, collection) {
358
376
  return true;
359
377
  });
360
378
  const existingKeys = keys.filter((key) => persistedKeys.has(key));
379
+ const cachedCollectionForExistingKeys = OnyxUtils_1.default.getCachedCollection(collectionKey, existingKeys);
361
380
  const newKeys = keys.filter((key) => !persistedKeys.has(key));
362
381
  const existingKeyCollection = existingKeys.reduce((obj, key) => {
382
+ const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(mergedCollection[key], cachedCollectionForExistingKeys[key]);
383
+ if (!isCompatible) {
384
+ Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'mergeCollection', existingValueType, newValueType));
385
+ return obj;
386
+ }
363
387
  // eslint-disable-next-line no-param-reassign
364
388
  obj[key] = mergedCollection[key];
365
389
  return obj;
@@ -377,6 +401,9 @@ function mergeCollection(collectionKey, collection) {
377
401
  // because we will simply overwrite the existing values in storage.
378
402
  const keyValuePairsForNewCollection = OnyxUtils_1.default.prepareKeyValuePairsForStorage(newCollection, true);
379
403
  const promises = [];
404
+ // We need to get the previously existing values so we can compare the new ones
405
+ // against them, to avoid unnecessary subscriber updates.
406
+ const previousCollectionPromise = Promise.all(existingKeys.map((key) => OnyxUtils_1.default.get(key).then((value) => [key, value]))).then(Object.fromEntries);
380
407
  // New keys will be added via multiSet while existing keys will be updated using multiMerge
381
408
  // This is because setting a key that doesn't exist yet with multiMerge will throw errors
382
409
  if (keyValuePairsForExistingCollection.length > 0) {
@@ -385,11 +412,13 @@ function mergeCollection(collectionKey, collection) {
385
412
  if (keyValuePairsForNewCollection.length > 0) {
386
413
  promises.push(storage_1.default.multiSet(keyValuePairsForNewCollection));
387
414
  }
415
+ // finalMergedCollection contains all the keys that were merged, without the keys of incompatible updates
416
+ const finalMergedCollection = Object.assign(Object.assign({}, existingKeyCollection), newCollection);
388
417
  // Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
389
418
  // and update all subscribers
390
- const promiseUpdate = Promise.all(existingKeys.map(OnyxUtils_1.default.get)).then(() => {
391
- OnyxCache_1.default.merge(mergedCollection);
392
- return OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(collectionKey, mergedCollection);
419
+ const promiseUpdate = previousCollectionPromise.then((previousCollection) => {
420
+ OnyxCache_1.default.merge(finalMergedCollection);
421
+ return OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(collectionKey, finalMergedCollection, previousCollection);
393
422
  });
394
423
  return Promise.all(promises)
395
424
  .catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, mergeCollection, collectionKey, mergedCollection))
@@ -103,18 +103,18 @@ declare function addToEvictionBlockList(key: OnyxKey, connectionID: number): voi
103
103
  * removed.
104
104
  */
105
105
  declare function addAllSafeEvictionKeysToRecentlyAccessedList(): Promise<void>;
106
- declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionKey: TKey): NonNullable<OnyxCollection<KeyValueMapping[TKey]>>;
106
+ declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionKey: TKey, collectionMemberKeys?: string[]): NonNullable<OnyxCollection<KeyValueMapping[TKey]>>;
107
107
  /**
108
108
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
109
109
  */
110
- declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, notifyRegularSubscibers?: boolean, notifyWithOnyxSubscibers?: boolean): void;
110
+ declare function keysChanged<TKey extends CollectionKeyBase>(collectionKey: TKey, partialCollection: OnyxCollection<KeyValueMapping[TKey]>, previousPartialCollection: OnyxCollection<KeyValueMapping[TKey]> | undefined, notifyRegularSubscibers?: boolean, notifyWithOnyxSubscibers?: boolean): void;
111
111
  /**
112
112
  * When a key change happens, search for any callbacks matching the key or collection key and trigger those callbacks
113
113
  *
114
114
  * @example
115
115
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
116
116
  */
117
- declare function keyChanged<TKey extends OnyxKey>(key: TKey, data: OnyxValue<TKey>, prevData: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, notifyRegularSubscibers?: boolean, notifyWithOnyxSubscibers?: boolean): void;
117
+ declare function keyChanged<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean, notifyRegularSubscibers?: boolean, notifyWithOnyxSubscibers?: boolean): void;
118
118
  /**
119
119
  * Sends the data obtained from the keys to the connection. It either:
120
120
  * - sets state on the withOnyxInstances
@@ -136,13 +136,13 @@ declare function getCollectionDataAndSendAsObject<TKey extends OnyxKey>(matching
136
136
  * @example
137
137
  * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
138
138
  */
139
- declare function scheduleSubscriberUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, prevValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean): Promise<void>;
139
+ declare function scheduleSubscriberUpdate<TKey extends OnyxKey>(key: TKey, value: OnyxValue<TKey>, previousValue: OnyxValue<TKey>, canUpdateSubscriber?: (subscriber?: Mapping<OnyxKey>) => boolean): Promise<void>;
140
140
  /**
141
141
  * This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
142
142
  * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
143
143
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
144
144
  */
145
- declare function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(key: TKey, value: OnyxCollection<KeyValueMapping[TKey]>): Promise<void>;
145
+ declare function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(key: TKey, value: OnyxCollection<KeyValueMapping[TKey]>, previousValue?: OnyxCollection<KeyValueMapping[TKey]>): Promise<void>;
146
146
  /**
147
147
  * Remove a key from Onyx and update the subscribers
148
148
  */
package/dist/OnyxUtils.js CHANGED
@@ -323,9 +323,9 @@ function addAllSafeEvictionKeysToRecentlyAccessedList() {
323
323
  });
324
324
  });
325
325
  }
326
- function getCachedCollection(collectionKey) {
327
- const collectionMemberKeys = Array.from(OnyxCache_1.default.getAllKeys()).filter((storedKey) => isCollectionMemberKey(collectionKey, storedKey));
328
- return collectionMemberKeys.reduce((prev, key) => {
326
+ function getCachedCollection(collectionKey, collectionMemberKeys) {
327
+ const resolvedCollectionMemberKeys = collectionMemberKeys || Array.from(OnyxCache_1.default.getAllKeys()).filter((storedKey) => isCollectionMemberKey(collectionKey, storedKey));
328
+ return resolvedCollectionMemberKeys.reduce((prev, key) => {
329
329
  const cachedValue = OnyxCache_1.default.getValue(key);
330
330
  if (!cachedValue) {
331
331
  return prev;
@@ -338,7 +338,16 @@ function getCachedCollection(collectionKey) {
338
338
  /**
339
339
  * When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
340
340
  */
341
- function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
341
+ function keysChanged(collectionKey, partialCollection, previousPartialCollection, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
342
+ const previousCollectionWithoutNestedNulls = previousPartialCollection === undefined ? {} : utils_1.default.removeNestedNullValues(previousPartialCollection);
343
+ // We prepare the "cached collection" which is the entire collection + the new partial data that
344
+ // was merged in via mergeCollection().
345
+ const cachedCollection = getCachedCollection(collectionKey);
346
+ const cachedCollectionWithoutNestedNulls = utils_1.default.removeNestedNullValues(cachedCollection);
347
+ // If the previous collection equals the new collection then we do not need to notify any subscribers.
348
+ if (previousPartialCollection !== undefined && (0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls, previousCollectionWithoutNestedNulls)) {
349
+ return;
350
+ }
342
351
  // We are iterating over all subscribers similar to keyChanged(). However, we are looking for subscribers who are subscribing to either a collection key or
343
352
  // individual collection key member for the collection that is being updated. It is important to note that the collection parameter cane be a PARTIAL collection
344
353
  // and does not represent all of the combined keys and values for a collection key. It is just the "new" data that was merged in via mergeCollection().
@@ -360,10 +369,6 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
360
369
  * e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
361
370
  */
362
371
  const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
363
- // We prepare the "cached collection" which is the entire collection + the new partial data that
364
- // was merged in via mergeCollection().
365
- const cachedCollection = getCachedCollection(collectionKey);
366
- const cachedCollectionWithoutNestedNulls = utils_1.default.removeNestedNullValues(cachedCollection);
367
372
  // Regular Onyx.connect() subscriber found.
368
373
  if (typeof subscriber.callback === 'function') {
369
374
  if (!notifyRegularSubscibers) {
@@ -381,6 +386,9 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
381
386
  const dataKeys = Object.keys(partialCollection !== null && partialCollection !== void 0 ? partialCollection : {});
382
387
  for (let j = 0; j < dataKeys.length; j++) {
383
388
  const dataKey = dataKeys[j];
389
+ if ((0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls[dataKey], previousCollectionWithoutNestedNulls[dataKey])) {
390
+ continue;
391
+ }
384
392
  subscriber.callback(cachedCollectionWithoutNestedNulls[dataKey], dataKey);
385
393
  }
386
394
  continue;
@@ -388,6 +396,9 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
388
396
  // And if the subscriber is specifically only tracking a particular collection member key then we will
389
397
  // notify them with the cached data for that key only.
390
398
  if (isSubscribedToCollectionMemberKey) {
399
+ if ((0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls[subscriber.key], previousCollectionWithoutNestedNulls[subscriber.key])) {
400
+ continue;
401
+ }
391
402
  const subscriberCallback = subscriber.callback;
392
403
  subscriberCallback(cachedCollectionWithoutNestedNulls[subscriber.key], subscriber.key);
393
404
  continue;
@@ -435,6 +446,9 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
435
446
  }
436
447
  // If a React component is only interested in a single key then we can set the cached value directly to the state name.
437
448
  if (isSubscribedToCollectionMemberKey) {
449
+ if ((0, fast_equals_1.deepEqual)(cachedCollectionWithoutNestedNulls[subscriber.key], previousCollectionWithoutNestedNulls[subscriber.key])) {
450
+ continue;
451
+ }
438
452
  // However, we only want to update this subscriber if the partial data contains a change.
439
453
  // Otherwise, we would update them with a value they already have and trigger an unnecessary re-render.
440
454
  const dataFromCollection = partialCollection === null || partialCollection === void 0 ? void 0 : partialCollection[subscriber.key];
@@ -484,9 +498,9 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
484
498
  * @example
485
499
  * keyChanged(key, value, subscriber => subscriber.initWithStoredValues === false)
486
500
  */
487
- function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
501
+ function keyChanged(key, value, previousValue, canUpdateSubscriber = () => true, notifyRegularSubscibers = true, notifyWithOnyxSubscibers = true) {
488
502
  // Add or remove this key from the recentlyAccessedKeys lists
489
- if (data !== null) {
503
+ if (value !== null) {
490
504
  addLastAccessedKey(key);
491
505
  }
492
506
  else {
@@ -509,13 +523,13 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
509
523
  if (isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
510
524
  const cachedCollection = getCachedCollection(subscriber.key);
511
525
  const cachedCollectionWithoutNestedNulls = utils_1.default.removeNestedNullValues(cachedCollection);
512
- cachedCollectionWithoutNestedNulls[key] = data;
526
+ cachedCollectionWithoutNestedNulls[key] = value;
513
527
  subscriber.callback(cachedCollectionWithoutNestedNulls);
514
528
  continue;
515
529
  }
516
- const dataWithoutNestedNulls = utils_1.default.removeNestedNullValues(data);
530
+ const valueWithoutNestedNulls = utils_1.default.removeNestedNullValues(value);
517
531
  const subscriberCallback = subscriber.callback;
518
- subscriberCallback(dataWithoutNestedNulls, key);
532
+ subscriberCallback(valueWithoutNestedNulls, key);
519
533
  continue;
520
534
  }
521
535
  // Subscriber connected via withOnyx() HOC
@@ -532,7 +546,7 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
532
546
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
533
547
  const prevWithOnyxData = prevState[subscriber.statePropertyName];
534
548
  const newWithOnyxData = {
535
- [key]: selector(data, subscriber.withOnyxInstance.state),
549
+ [key]: selector(value, subscriber.withOnyxInstance.state),
536
550
  };
537
551
  const prevDataWithNewData = Object.assign(Object.assign({}, prevWithOnyxData), newWithOnyxData);
538
552
  if (!(0, fast_equals_1.deepEqual)(prevWithOnyxData, prevDataWithNewData)) {
@@ -547,7 +561,7 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
547
561
  }
548
562
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
549
563
  const collection = prevState[subscriber.statePropertyName] || {};
550
- const newCollection = Object.assign(Object.assign({}, collection), { [key]: data });
564
+ const newCollection = Object.assign(Object.assign({}, collection), { [key]: value });
551
565
  PerformanceUtils.logSetStateCall(subscriber, collection, newCollection, 'keyChanged', key);
552
566
  return {
553
567
  [subscriber.statePropertyName]: newCollection,
@@ -559,9 +573,9 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
559
573
  // returned by the selector and only if the selected data has changed.
560
574
  if (selector) {
561
575
  subscriber.withOnyxInstance.setStateProxy(() => {
562
- const previousValue = selector(prevData, subscriber.withOnyxInstance.state);
563
- const newValue = selector(data, subscriber.withOnyxInstance.state);
564
- if (!(0, fast_equals_1.deepEqual)(previousValue, newValue)) {
576
+ const prevValue = selector(previousValue, subscriber.withOnyxInstance.state);
577
+ const newValue = selector(value, subscriber.withOnyxInstance.state);
578
+ if (!(0, fast_equals_1.deepEqual)(prevValue, newValue)) {
565
579
  return {
566
580
  [subscriber.statePropertyName]: newValue,
567
581
  };
@@ -572,17 +586,17 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
572
586
  }
573
587
  // If we did not match on a collection key then we just set the new data to the state property
574
588
  subscriber.withOnyxInstance.setStateProxy((prevState) => {
575
- const prevWithOnyxData = prevState[subscriber.statePropertyName];
589
+ const prevWithOnyxValue = prevState[subscriber.statePropertyName];
576
590
  // Avoids triggering unnecessary re-renders when feeding empty objects
577
- if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(prevWithOnyxData)) {
591
+ if (utils_1.default.isEmptyObject(value) && utils_1.default.isEmptyObject(prevWithOnyxValue)) {
578
592
  return null;
579
593
  }
580
- if (prevWithOnyxData === data) {
594
+ if (prevWithOnyxValue === value) {
581
595
  return null;
582
596
  }
583
- PerformanceUtils.logSetStateCall(subscriber, prevData, data, 'keyChanged', key);
597
+ PerformanceUtils.logSetStateCall(subscriber, previousValue, value, 'keyChanged', key);
584
598
  return {
585
- [subscriber.statePropertyName]: data,
599
+ [subscriber.statePropertyName]: value,
586
600
  };
587
601
  });
588
602
  continue;
@@ -719,9 +733,9 @@ function getCollectionDataAndSendAsObject(matchingKeys, mapping) {
719
733
  * @example
720
734
  * scheduleSubscriberUpdate(key, value, subscriber => subscriber.initWithStoredValues === false)
721
735
  */
722
- function scheduleSubscriberUpdate(key, value, prevValue, canUpdateSubscriber = () => true) {
723
- const promise = Promise.resolve().then(() => keyChanged(key, value, prevValue, canUpdateSubscriber, true, false));
724
- batchUpdates(() => keyChanged(key, value, prevValue, canUpdateSubscriber, false, true));
736
+ function scheduleSubscriberUpdate(key, value, previousValue, canUpdateSubscriber = () => true) {
737
+ const promise = Promise.resolve().then(() => keyChanged(key, value, previousValue, canUpdateSubscriber, true, false));
738
+ batchUpdates(() => keyChanged(key, value, previousValue, canUpdateSubscriber, false, true));
725
739
  return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
726
740
  }
727
741
  /**
@@ -729,9 +743,9 @@ function scheduleSubscriberUpdate(key, value, prevValue, canUpdateSubscriber = (
729
743
  * so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
730
744
  * subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
731
745
  */
732
- function scheduleNotifyCollectionSubscribers(key, value) {
733
- const promise = Promise.resolve().then(() => keysChanged(key, value, true, false));
734
- batchUpdates(() => keysChanged(key, value, false, true));
746
+ function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
747
+ const promise = Promise.resolve().then(() => keysChanged(key, value, previousValue, true, false));
748
+ batchUpdates(() => keysChanged(key, value, previousValue, false, true));
735
749
  return Promise.all([maybeFlushBatchUpdates(), promise]).then(() => undefined);
736
750
  }
737
751
  /**
@@ -0,0 +1,4 @@
1
+ declare const logMessages: {
2
+ incompatibleUpdateAlert: (key: string, operation: string, existingValueType?: string, newValueType?: string) => string;
3
+ };
4
+ export default logMessages;
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const logMessages = {
4
+ incompatibleUpdateAlert: (key, operation, existingValueType, newValueType) => {
5
+ return `Warning: Trying to apply "${operation}" with ${newValueType !== null && newValueType !== void 0 ? newValueType : 'unknown'} type to ${existingValueType !== null && existingValueType !== void 0 ? existingValueType : 'unknown'} type in the key "${key}"`;
6
+ },
7
+ };
8
+ exports.default = logMessages;
package/dist/utils.d.ts CHANGED
@@ -15,10 +15,17 @@ declare function fastMerge<TObject extends Record<string, unknown>>(target: TObj
15
15
  declare function removeNestedNullValues<TObject extends Record<string, unknown>>(value: unknown | unknown[] | TObject): Record<string, unknown> | unknown[] | null;
16
16
  /** Formats the action name by uppercasing and adding the key if provided. */
17
17
  declare function formatActionName(method: string, key?: OnyxKey): string;
18
+ /** validate that the update and the existing value are compatible */
19
+ declare function checkCompatibilityWithExistingValue(value: unknown, existingValue: unknown): {
20
+ isCompatible: boolean;
21
+ existingValueType?: string;
22
+ newValueType?: string;
23
+ };
18
24
  declare const _default: {
19
25
  isEmptyObject: typeof isEmptyObject;
20
26
  fastMerge: typeof fastMerge;
21
27
  formatActionName: typeof formatActionName;
22
28
  removeNestedNullValues: typeof removeNestedNullValues;
29
+ checkCompatibilityWithExistingValue: typeof checkCompatibilityWithExistingValue;
23
30
  };
24
31
  export default _default;
package/dist/utils.js CHANGED
@@ -98,4 +98,24 @@ function removeNestedNullValues(value) {
98
98
  function formatActionName(method, key) {
99
99
  return key ? `${method.toUpperCase()}/${key}` : method.toUpperCase();
100
100
  }
101
- exports.default = { isEmptyObject, fastMerge, formatActionName, removeNestedNullValues };
101
+ /** validate that the update and the existing value are compatible */
102
+ function checkCompatibilityWithExistingValue(value, existingValue) {
103
+ if (!existingValue || !value) {
104
+ return {
105
+ isCompatible: true,
106
+ };
107
+ }
108
+ const existingValueType = Array.isArray(existingValue) ? 'array' : 'non-array';
109
+ const newValueType = Array.isArray(value) ? 'array' : 'non-array';
110
+ if (existingValueType !== newValueType) {
111
+ return {
112
+ isCompatible: false,
113
+ existingValueType,
114
+ newValueType,
115
+ };
116
+ }
117
+ return {
118
+ isCompatible: true,
119
+ };
120
+ }
121
+ exports.default = { isEmptyObject, fastMerge, formatActionName, removeNestedNullValues, checkCompatibilityWithExistingValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.34",
3
+ "version": "2.0.36",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",