react-native-onyx 3.0.51 → 3.0.53

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
@@ -78,8 +78,13 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedK
78
78
  OnyxCache_1.default.setRecentKeysLimit(maxCachedKeysCount);
79
79
  }
80
80
  OnyxUtils_1.default.initStoreValues(keys, initialKeyStates, evictableKeys);
81
- // Initialize all of our keys with data provided then give green light to any pending connections
82
- Promise.all([OnyxCache_1.default.addEvictableKeysToRecentlyAccessedList(OnyxUtils_1.default.isCollectionKey, OnyxUtils_1.default.getAllKeys), OnyxUtils_1.default.initializeWithDefaultKeyStates()]).then(OnyxUtils_1.default.getDeferredInitTask().resolve);
81
+ // Initialize all of our keys with data provided then give green light to any pending connections.
82
+ // addEvictableKeysToRecentlyAccessedList must run after initializeWithDefaultKeyStates because
83
+ // eager cache loading populates the key index (cache.setAllKeys) inside initializeWithDefaultKeyStates,
84
+ // and the evictable keys list depends on that index being populated.
85
+ OnyxUtils_1.default.initializeWithDefaultKeyStates()
86
+ .then(() => OnyxCache_1.default.addEvictableKeysToRecentlyAccessedList(OnyxUtils_1.default.isCollectionKey, OnyxUtils_1.default.getAllKeys))
87
+ .then(OnyxUtils_1.default.getDeferredInitTask().resolve);
83
88
  }
84
89
  /**
85
90
  * Connects to an Onyx key given the options passed and listens to its changes.
package/dist/OnyxUtils.js CHANGED
@@ -91,7 +91,9 @@ let snapshotKey = null;
91
91
  let lastSubscriptionID = 0;
92
92
  // Connections can be made before `Onyx.init`. They would wait for this task before resolving
93
93
  const deferredInitTask = (0, createDeferredTask_1.default)();
94
- // Holds a set of collection member IDs which updates will be ignored when using Onyx methods.
94
+ // Collection member IDs that Onyx should silently ignore across all operations reads, writes, cache, and subscriber
95
+ // notifications. This is used to filter out keys formed from invalid/default IDs (e.g. "-1", "0",
96
+ // "undefined", "null", "NaN") that can appear when an ID variable is accidentally coerced to string.
95
97
  let skippableCollectionMemberIDs = new Set();
96
98
  // Holds a set of keys that should always be merged into snapshot entries.
97
99
  let snapshotMergeKeys = new Set();
@@ -906,17 +908,65 @@ function mergeInternal(mode, changes, existingValue) {
906
908
  * Merge user provided default key value pairs.
907
909
  */
908
910
  function initializeWithDefaultKeyStates() {
909
- // Filter out RAM-only keys from storage reads as they may have stale persisted data
910
- // from before the key was migrated to RAM-only.
911
- const keysToFetch = Object.keys(defaultKeyStates).filter((key) => !isRamOnlyKey(key));
912
- return storage_1.default.multiGet(keysToFetch).then((pairs) => {
913
- const existingDataAsObject = Object.fromEntries(pairs);
914
- const merged = utils_1.default.fastMerge(existingDataAsObject, defaultKeyStates, {
911
+ // Eagerly load the entire database into cache in a single batch read.
912
+ // This is faster than lazy-loading individual keys because:
913
+ // 1. One DB transaction instead of hundreds
914
+ // 2. All subsequent reads are synchronous cache hits
915
+ return storage_1.default.getAll()
916
+ .then((pairs) => {
917
+ const allDataFromStorage = {};
918
+ for (const [key, value] of pairs) {
919
+ // RAM-only keys should not be cached from storage as they may have stale persisted data
920
+ // from before the key was migrated to RAM-only.
921
+ if (isRamOnlyKey(key)) {
922
+ continue;
923
+ }
924
+ // Skip collection members that are marked as skippable
925
+ if (skippableCollectionMemberIDs.size && getCollectionKey(key)) {
926
+ const [, collectionMemberID] = splitCollectionMemberKey(key);
927
+ if (skippableCollectionMemberIDs.has(collectionMemberID)) {
928
+ continue;
929
+ }
930
+ }
931
+ allDataFromStorage[key] = value;
932
+ }
933
+ // Load all storage data into cache silently (no subscriber notifications)
934
+ OnyxCache_1.default.setAllKeys(Object.keys(allDataFromStorage));
935
+ OnyxCache_1.default.merge(allDataFromStorage);
936
+ // For keys that have a developer-defined default (via `initialKeyStates`), merge the
937
+ // persisted value with the default so new properties added in code updates are applied
938
+ // without wiping user data that already exists in storage.
939
+ const defaultKeysFromStorage = Object.keys(defaultKeyStates).reduce((obj, key) => {
940
+ if (key in allDataFromStorage) {
941
+ // eslint-disable-next-line no-param-reassign
942
+ obj[key] = allDataFromStorage[key];
943
+ }
944
+ return obj;
945
+ }, {});
946
+ const merged = utils_1.default.fastMerge(defaultKeysFromStorage, defaultKeyStates, {
915
947
  shouldRemoveNestedNulls: true,
916
948
  }).result;
917
949
  OnyxCache_1.default.merge(merged !== null && merged !== void 0 ? merged : {});
918
- for (const [key, value] of Object.entries(merged !== null && merged !== void 0 ? merged : {}))
950
+ // Notify subscribers about default key states so that any subscriber that connected
951
+ // before init (e.g. during module load) receives the merged default values immediately
952
+ for (const [key, value] of Object.entries(merged !== null && merged !== void 0 ? merged : {})) {
919
953
  keyChanged(key, value);
954
+ }
955
+ })
956
+ .catch((error) => {
957
+ Logger.logAlert(`Failed to load data from storage during init. The app will boot with default key states only. Error: ${error}`);
958
+ // Populate the key index so getAllKeys() returns correct results for default keys.
959
+ // Without this, subscribers that check getAllKeys() would see an empty set even
960
+ // though we have default values in cache.
961
+ OnyxCache_1.default.setAllKeys(Object.keys(defaultKeyStates));
962
+ // Boot with defaults so the app renders instead of deadlocking.
963
+ // Users will get a fresh-install experience but the app won't be bricked.
964
+ OnyxCache_1.default.merge(defaultKeyStates);
965
+ // Notify subscribers about default key states so that any subscriber that connected
966
+ // before init (e.g. during module load) receives the merged default values immediately
967
+ for (const [key, value] of Object.entries(defaultKeyStates)) {
968
+ keyChanged(key, value);
969
+ }
920
970
  });
921
971
  }
922
972
  /**
@@ -1041,7 +1091,7 @@ function unsubscribeFromKey(subscriptionID) {
1041
1091
  if (!callbackToStateMapping[subscriptionID]) {
1042
1092
  return;
1043
1093
  }
1044
- deleteKeyBySubscriptions(lastSubscriptionID);
1094
+ deleteKeyBySubscriptions(subscriptionID);
1045
1095
  delete callbackToStateMapping[subscriptionID];
1046
1096
  }
1047
1097
  function updateSnapshots(data, mergeFn) {
@@ -10,6 +10,7 @@ declare const StorageMock: {
10
10
  removeItems: jest.Mock<Promise<void>, [keys: import("../providers/types").StorageKeyList], any>;
11
11
  clear: jest.Mock<Promise<void>, [], any>;
12
12
  getAllKeys: jest.Mock<Promise<import("../providers/types").StorageKeyList>, [], any>;
13
+ getAll: jest.Mock<Promise<import("../providers/types").StorageKeyValuePair[]>, [], any>;
13
14
  getDatabaseSize: jest.Mock<Promise<{
14
15
  bytesUsed: number;
15
16
  bytesRemaining: number;
@@ -48,6 +48,7 @@ const StorageMock = {
48
48
  removeItems: jest.fn(MemoryOnlyProvider_1.default.removeItems),
49
49
  clear: jest.fn(MemoryOnlyProvider_1.default.clear),
50
50
  getAllKeys: jest.fn(MemoryOnlyProvider_1.default.getAllKeys),
51
+ getAll: jest.fn(MemoryOnlyProvider_1.default.getAll),
51
52
  getDatabaseSize: jest.fn(MemoryOnlyProvider_1.default.getDatabaseSize),
52
53
  keepInstancesSync: jest.fn(),
53
54
  getMockStore: jest.fn(() => MemoryOnlyProvider_1.mockStore),
@@ -181,6 +181,10 @@ const storage = {
181
181
  * Returns all available keys
182
182
  */
183
183
  getAllKeys: () => tryOrDegradePerformance(() => provider.getAllKeys()),
184
+ /**
185
+ * Returns all key-value pairs from storage in a single batch operation
186
+ */
187
+ getAll: () => tryOrDegradePerformance(() => provider.getAll()),
184
188
  /**
185
189
  * Gets the total bytes of the store
186
190
  */
@@ -211,5 +215,6 @@ GlobalSettings.addGlobalSettingsChangeListener(({ enablePerformanceMetrics }) =>
211
215
  storage.removeItems = (0, metrics_1.default)(storage.removeItems, 'Storage.removeItems');
212
216
  storage.clear = (0, metrics_1.default)(storage.clear, 'Storage.clear');
213
217
  storage.getAllKeys = (0, metrics_1.default)(storage.getAllKeys, 'Storage.getAllKeys');
218
+ storage.getAll = (0, metrics_1.default)(storage.getAll, 'Storage.getAll');
214
219
  });
215
220
  exports.default = storage;
@@ -131,6 +131,12 @@ const provider = {
131
131
  }
132
132
  return IDB.keys(provider.store);
133
133
  },
134
+ getAll() {
135
+ if (!provider.store) {
136
+ throw new Error('Store not initialized!');
137
+ }
138
+ return IDB.entries(provider.store);
139
+ },
134
140
  getItem(key) {
135
141
  if (!provider.store) {
136
142
  throw new Error('Store not initialized!');
@@ -111,6 +111,12 @@ const provider = {
111
111
  getAllKeys() {
112
112
  return Promise.resolve(underscore_1.default.keys(provider.store));
113
113
  },
114
+ /**
115
+ * Returns all key-value pairs from memory
116
+ */
117
+ getAll() {
118
+ return Promise.resolve(Object.entries(provider.store));
119
+ },
114
120
  /**
115
121
  * Gets the total bytes of the store.
116
122
  * `bytesRemaining` will always be `Number.POSITIVE_INFINITY` since we don't have a hard limit on memory.
@@ -73,6 +73,12 @@ const provider = {
73
73
  getAllKeys() {
74
74
  return Promise.resolve([]);
75
75
  },
76
+ /**
77
+ * Returns all key-value pairs from storage
78
+ */
79
+ getAll() {
80
+ return Promise.resolve([]);
81
+ },
76
82
  /**
77
83
  * Gets the total bytes of the store.
78
84
  * `bytesRemaining` will always be `Number.POSITIVE_INFINITY` since we don't have a hard limit on memory.
@@ -147,6 +147,16 @@ const provider = {
147
147
  return (result !== null && result !== void 0 ? result : []);
148
148
  });
149
149
  },
150
+ getAll() {
151
+ if (!provider.store) {
152
+ throw new Error('Store is not initialized!');
153
+ }
154
+ return provider.store.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs;').then(({ rows }) => {
155
+ // eslint-disable-next-line no-underscore-dangle
156
+ const result = rows === null || rows === void 0 ? void 0 : rows._array.map((row) => [row.record_key, JSON.parse(row.valueJSON)]);
157
+ return (result !== null && result !== void 0 ? result : []);
158
+ });
159
+ },
150
160
  removeItem(key) {
151
161
  if (!provider.store) {
152
162
  throw new Error('Store is not initialized!');
@@ -46,6 +46,11 @@ type StorageProvider<TStore> = {
46
46
  * Returns all keys available in storage
47
47
  */
48
48
  getAllKeys: () => Promise<StorageKeyList>;
49
+ /**
50
+ * Returns all key-value pairs from storage in a single batch operation.
51
+ * More efficient than getAllKeys + multiGet for loading the entire database.
52
+ */
53
+ getAll: () => Promise<StorageKeyValuePair[]>;
49
54
  /**
50
55
  * Removes given key and its value from storage
51
56
  */
package/dist/types.d.ts CHANGED
@@ -360,8 +360,9 @@ type InitOptions = {
360
360
  */
361
361
  enableDevTools?: boolean;
362
362
  /**
363
- * Array of collection member IDs which updates will be ignored when using Onyx methods.
364
- * Additionally, any subscribers from these keys to won't receive any data from Onyx.
363
+ * Array of collection member IDs that Onyx should silently ignore across all operations.
364
+ * This prevents keys formed from invalid or default IDs (e.g. "-1", "0", "undefined") from
365
+ * polluting cache or triggering subscriber notifications.
365
366
  */
366
367
  skippableCollectionMemberIDs?: string[];
367
368
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "3.0.51",
3
+ "version": "3.0.53",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",