react-native-onyx 2.0.111 → 2.0.113

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.d.ts CHANGED
@@ -2,7 +2,7 @@ import * as Logger from './Logger';
2
2
  import type { CollectionKeyBase, ConnectOptions, InitOptions, Mapping, OnyxKey, OnyxMergeCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetInput, OnyxUpdate } from './types';
3
3
  import type { Connection } from './OnyxConnectionManager';
4
4
  /** Initialize the store with actions and listening for storage events */
5
- declare function init({ keys, initialKeyStates, evictableKeys, maxCachedKeysCount, shouldSyncMultipleInstances, debugSetState, enablePerformanceMetrics, skippableCollectionMemberIDs, }: InitOptions): void;
5
+ declare function init({ keys, initialKeyStates, evictableKeys, maxCachedKeysCount, shouldSyncMultipleInstances, debugSetState, enablePerformanceMetrics, skippableCollectionMemberIDs, fullyMergedSnapshotKeys, }: InitOptions): void;
6
6
  /**
7
7
  * Connects to an Onyx key given the options passed and listens to its changes.
8
8
  *
package/dist/Onyx.js CHANGED
@@ -40,7 +40,7 @@ const OnyxConnectionManager_1 = __importDefault(require("./OnyxConnectionManager
40
40
  const GlobalSettings = __importStar(require("./GlobalSettings"));
41
41
  const metrics_1 = __importDefault(require("./metrics"));
42
42
  /** Initialize the store with actions and listening for storage events */
43
- function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = !!global.localStorage, debugSetState = false, enablePerformanceMetrics = false, skippableCollectionMemberIDs = [], }) {
43
+ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = !!global.localStorage, debugSetState = false, enablePerformanceMetrics = false, skippableCollectionMemberIDs = [], fullyMergedSnapshotKeys = [], }) {
44
44
  var _a;
45
45
  if (enablePerformanceMetrics) {
46
46
  GlobalSettings.setPerformanceMetricsEnabled(true);
@@ -61,7 +61,7 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedK
61
61
  if (maxCachedKeysCount > 0) {
62
62
  OnyxCache_1.default.setRecentKeysLimit(maxCachedKeysCount);
63
63
  }
64
- OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, evictableKeys);
64
+ OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, evictableKeys, fullyMergedSnapshotKeys);
65
65
  // Initialize all of our keys with data provided then give green light to any pending connections
66
66
  Promise.all([OnyxCache_1.default.addEvictableKeysToRecentlyAccessedList(OnyxUtils_1.default.isCollectionKey, OnyxUtils_1.default.getAllKeys), OnyxUtils_1.default.initializeWithDefaultKeyStates()]).then(OnyxUtils_1.default.getDeferredInitTask().resolve);
67
67
  }
@@ -43,8 +43,9 @@ declare function setSkippableCollectionMemberIDs(ids: Set<string>): void;
43
43
  * @param keys - `ONYXKEYS` constants object from Onyx.init()
44
44
  * @param initialKeyStates - initial data to set when `init()` and `clear()` are called
45
45
  * @param evictableKeys - This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal.
46
+ * @param fullyMergedSnapshotKeys - Array of snapshot collection keys where full merge is supported and data structure can be changed after merge.
46
47
  */
47
- declare function initStoreValues(keys: DeepRecord<string, OnyxKey>, initialKeyStates: Partial<KeyValueMapping>, evictableKeys: OnyxKey[]): void;
48
+ declare function initStoreValues(keys: DeepRecord<string, OnyxKey>, initialKeyStates: Partial<KeyValueMapping>, evictableKeys: OnyxKey[], fullyMergedSnapshotKeysParam?: string[]): void;
48
49
  /**
49
50
  * Sends an action to DevTools extension
50
51
  *
@@ -105,7 +106,7 @@ declare function getCollectionKeys(): Set<OnyxKey>;
105
106
  * is associated with a collection of keys.
106
107
  */
107
108
  declare function isCollectionKey(key: OnyxKey): key is CollectionKeyBase;
108
- declare function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>(collectionKey: TCollectionKey, key: string, collectionKeyLength: number): key is `${TCollectionKey}${string}`;
109
+ declare function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>(collectionKey: TCollectionKey, key: string): key is `${TCollectionKey}${string}`;
109
110
  /**
110
111
  * Splits a collection member key into the collection key part and the ID part.
111
112
  * @param key - The collection member key to split.
package/dist/OnyxUtils.js CHANGED
@@ -29,6 +29,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
29
29
  /* eslint-disable no-continue */
30
30
  const fast_equals_1 = require("fast-equals");
31
31
  const clone_1 = __importDefault(require("lodash/clone"));
32
+ const pick_1 = __importDefault(require("lodash/pick"));
32
33
  const DevTools_1 = __importDefault(require("./DevTools"));
33
34
  const Logger = __importStar(require("./Logger"));
34
35
  const OnyxCache_1 = __importStar(require("./OnyxCache"));
@@ -65,6 +66,7 @@ let batchUpdatesQueue = [];
65
66
  // Used for comparison with a new update to avoid invoking the Onyx.connect callback with the same data.
66
67
  const lastConnectionCallbackData = new Map();
67
68
  let snapshotKey = null;
69
+ let fullyMergedSnapshotKeys;
68
70
  // Keeps track of the last subscriptionID that was used so we can keep incrementing it
69
71
  let lastSubscriptionID = 0;
70
72
  // Connections can be made before `Onyx.init`. They would wait for this task before resolving
@@ -116,8 +118,9 @@ function setSkippableCollectionMemberIDs(ids) {
116
118
  * @param keys - `ONYXKEYS` constants object from Onyx.init()
117
119
  * @param initialKeyStates - initial data to set when `init()` and `clear()` are called
118
120
  * @param evictableKeys - This is an array of keys (individual or collection patterns) that when provided to Onyx are flagged as "safe" for removal.
121
+ * @param fullyMergedSnapshotKeys - Array of snapshot collection keys where full merge is supported and data structure can be changed after merge.
119
122
  */
120
- function initStoreValues(keys, initialKeyStates, evictableKeys) {
123
+ function initStoreValues(keys, initialKeyStates, evictableKeys, fullyMergedSnapshotKeysParam) {
121
124
  var _a;
122
125
  // We need the value of the collection keys later for checking if a
123
126
  // key is a collection. We store it in a map for faster lookup.
@@ -133,6 +136,7 @@ function initStoreValues(keys, initialKeyStates, evictableKeys) {
133
136
  OnyxCache_1.default.setEvictionAllowList(evictableKeys);
134
137
  if (typeof keys.COLLECTION === 'object' && typeof keys.COLLECTION.SNAPSHOT === 'string') {
135
138
  snapshotKey = keys.COLLECTION.SNAPSHOT;
139
+ fullyMergedSnapshotKeys = new Set(fullyMergedSnapshotKeysParam !== null && fullyMergedSnapshotKeysParam !== void 0 ? fullyMergedSnapshotKeysParam : []);
136
140
  }
137
141
  }
138
142
  function sendActionToDevTools(method, key, value, mergedValue = undefined) {
@@ -361,8 +365,8 @@ function getCollectionKeys() {
361
365
  function isCollectionKey(key) {
362
366
  return onyxCollectionKeySet.has(key);
363
367
  }
364
- function isCollectionMemberKey(collectionKey, key, collectionKeyLength) {
365
- return key.startsWith(collectionKey) && key.length > collectionKeyLength;
368
+ function isCollectionMemberKey(collectionKey, key) {
369
+ return key.startsWith(collectionKey) && key.length > collectionKey.length;
366
370
  }
367
371
  /**
368
372
  * Splits a collection member key into the collection key part and the ID part.
@@ -372,7 +376,7 @@ function isCollectionMemberKey(collectionKey, key, collectionKeyLength) {
372
376
  * or throws an Error if the key is not a collection one.
373
377
  */
374
378
  function splitCollectionMemberKey(key, collectionKey) {
375
- if (collectionKey && !isCollectionMemberKey(collectionKey, key, collectionKey.length)) {
379
+ if (collectionKey && !isCollectionMemberKey(collectionKey, key)) {
376
380
  throw new Error(`Invalid '${collectionKey}' collection key provided, it isn't compatible with '${key}' key.`);
377
381
  }
378
382
  if (!collectionKey) {
@@ -450,13 +454,12 @@ function tryGetCachedValue(key, mapping) {
450
454
  function getCachedCollection(collectionKey, collectionMemberKeys) {
451
455
  const allKeys = collectionMemberKeys || OnyxCache_1.default.getAllKeys();
452
456
  const collection = {};
453
- const collectionKeyLength = collectionKey.length;
454
457
  // forEach exists on both Set and Array
455
458
  allKeys.forEach((key) => {
456
459
  // If we don't have collectionMemberKeys array then we have to check whether a key is a collection member key.
457
460
  // Because in that case the keys will be coming from `cache.getAllKeys()` and we need to filter out the keys that
458
461
  // are not part of the collection.
459
- if (!collectionMemberKeys && !isCollectionMemberKey(collectionKey, key, collectionKeyLength)) {
462
+ if (!collectionMemberKeys && !isCollectionMemberKey(collectionKey, key)) {
460
463
  return;
461
464
  }
462
465
  const cachedValue = OnyxCache_1.default.get(key);
@@ -479,7 +482,6 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
479
482
  // 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
480
483
  // 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().
481
484
  const stateMappingKeys = Object.keys(callbackToStateMapping);
482
- const collectionKeyLength = collectionKey.length;
483
485
  for (const stateMappingKey of stateMappingKeys) {
484
486
  const subscriber = callbackToStateMapping[stateMappingKey];
485
487
  if (!subscriber) {
@@ -496,7 +498,7 @@ function keysChanged(collectionKey, partialCollection, partialPreviousCollection
496
498
  /**
497
499
  * e.g. Onyx.connect({key: `${ONYXKEYS.COLLECTION.REPORT}{reportID}`, callback: ...});
498
500
  */
499
- const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key, collectionKeyLength);
501
+ const isSubscribedToCollectionMemberKey = isCollectionMemberKey(collectionKey, subscriber.key);
500
502
  // Regular Onyx.connect() subscriber found.
501
503
  if (typeof subscriber.callback === 'function') {
502
504
  if (!notifyConnectSubscribers) {
@@ -1046,12 +1048,21 @@ function subscribeToKey(connectOptions) {
1046
1048
  // can send data back to the subscriber. Note that multiple keys can match as a subscriber could either be
1047
1049
  // subscribed to a "collection key" or a single key.
1048
1050
  const matchingKeys = [];
1049
- keys.forEach((key) => {
1050
- if (!isKeyMatch(mapping.key, key)) {
1051
- return;
1051
+ // Performance optimization: For single key subscriptions, avoid O(n) iteration
1052
+ if (!isCollectionKey(mapping.key)) {
1053
+ if (keys.has(mapping.key)) {
1054
+ matchingKeys.push(mapping.key);
1052
1055
  }
1053
- matchingKeys.push(key);
1054
- });
1056
+ }
1057
+ else {
1058
+ // Collection case - need to iterate through all keys to find matches (O(n))
1059
+ keys.forEach((key) => {
1060
+ if (!isKeyMatch(mapping.key, key)) {
1061
+ return;
1062
+ }
1063
+ matchingKeys.push(key);
1064
+ });
1065
+ }
1055
1066
  // If the key being connected to does not exist we initialize the value with null. For subscribers that connected
1056
1067
  // directly via connect() they will simply get a null value sent to them without any information about which key matched
1057
1068
  // since there are none matched. In withOnyx() we wait for all connected keys to return a value before rendering the child
@@ -1121,7 +1132,6 @@ function updateSnapshots(data, mergeFn) {
1121
1132
  return [];
1122
1133
  const promises = [];
1123
1134
  const snapshotCollection = OnyxUtils.getCachedCollection(snapshotCollectionKey);
1124
- const snapshotCollectionKeyLength = snapshotCollectionKey.length;
1125
1135
  Object.entries(snapshotCollection).forEach(([snapshotEntryKey, snapshotEntryValue]) => {
1126
1136
  // Snapshots may not be present in cache. We don't know how to update them so we skip.
1127
1137
  if (!snapshotEntryValue) {
@@ -1130,7 +1140,7 @@ function updateSnapshots(data, mergeFn) {
1130
1140
  let updatedData = {};
1131
1141
  data.forEach(({ key, value }) => {
1132
1142
  // snapshots are normal keys so we want to skip update if they are written to Onyx
1133
- if (OnyxUtils.isCollectionMemberKey(snapshotCollectionKey, key, snapshotCollectionKeyLength)) {
1143
+ if (OnyxUtils.isCollectionMemberKey(snapshotCollectionKey, key)) {
1134
1144
  return;
1135
1145
  }
1136
1146
  if (typeof snapshotEntryValue !== 'object' || !('data' in snapshotEntryValue)) {
@@ -1149,7 +1159,17 @@ function updateSnapshots(data, mergeFn) {
1149
1159
  return;
1150
1160
  }
1151
1161
  const oldValue = updatedData[key] || {};
1152
- updatedData = Object.assign(Object.assign({}, updatedData), { [key]: Object.assign(oldValue, value) });
1162
+ let collectionKey;
1163
+ try {
1164
+ collectionKey = getCollectionKey(key);
1165
+ }
1166
+ catch (e) {
1167
+ // If getCollectionKey() throws an error it means the key is not a collection key.
1168
+ collectionKey = undefined;
1169
+ }
1170
+ const shouldFullyMerge = fullyMergedSnapshotKeys === null || fullyMergedSnapshotKeys === void 0 ? void 0 : fullyMergedSnapshotKeys.has(collectionKey || key);
1171
+ const newValue = shouldFullyMerge ? value : (0, pick_1.default)(value, Object.keys(snapshotData[key]));
1172
+ updatedData = Object.assign(Object.assign({}, updatedData), { [key]: Object.assign(oldValue, newValue) });
1153
1173
  });
1154
1174
  // Skip the update if there's no data to be merged
1155
1175
  if (utils_1.default.isEmptyObject(updatedData)) {
package/dist/types.d.ts CHANGED
@@ -403,6 +403,13 @@ type InitOptions = {
403
403
  * Additionally, any subscribers from these keys to won't receive any data from Onyx.
404
404
  */
405
405
  skippableCollectionMemberIDs?: string[];
406
+ /**
407
+ * Array of snapshot collection keys where full merge is supported and data structure can be changed after merge.
408
+ * For e.g. if oldSnapshotData is {report_1: {name 'Fitsum'}} and BE update is {report_1: {name:'Fitsum2', nickName:'Fitse'}}
409
+ * if it is fullyMergedSnapshotkey the `nickName` prop that didn't exist in the previous data will be merged
410
+ * otherwise only existing prop will be picked from the BE update and merged (in this case only name).
411
+ */
412
+ fullyMergedSnapshotKeys?: string[];
406
413
  };
407
414
  type GenericFunction = (...args: any[]) => any;
408
415
  /**
package/dist/useOnyx.js CHANGED
@@ -74,9 +74,7 @@ function useOnyx(key, options, dependencies = []) {
74
74
  try {
75
75
  const previousCollectionKey = OnyxUtils_1.default.splitCollectionMemberKey(previousKey)[0];
76
76
  const collectionKey = OnyxUtils_1.default.splitCollectionMemberKey(key)[0];
77
- if (OnyxUtils_1.default.isCollectionMemberKey(previousCollectionKey, previousKey, previousCollectionKey.length) &&
78
- OnyxUtils_1.default.isCollectionMemberKey(collectionKey, key, collectionKey.length) &&
79
- previousCollectionKey === collectionKey) {
77
+ if (OnyxUtils_1.default.isCollectionMemberKey(previousCollectionKey, previousKey) && OnyxUtils_1.default.isCollectionMemberKey(collectionKey, key) && previousCollectionKey === collectionKey) {
80
78
  return;
81
79
  }
82
80
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "2.0.111",
3
+ "version": "2.0.113",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",