react-native-onyx 3.0.34 → 3.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 +261 -257
- package/dist/OnyxUtils.d.ts +10 -0
- package/dist/OnyxUtils.js +15 -0
- package/dist/createDeferredTask.d.ts +2 -1
- package/dist/createDeferredTask.js +9 -4
- package/dist/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/Onyx.js
CHANGED
|
@@ -151,7 +151,7 @@ function disconnect(connection) {
|
|
|
151
151
|
* @param options optional configuration object
|
|
152
152
|
*/
|
|
153
153
|
function set(key, value, options) {
|
|
154
|
-
return OnyxUtils_1.default.setWithRetry({ key, value, options });
|
|
154
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setWithRetry({ key, value, options }));
|
|
155
155
|
}
|
|
156
156
|
/**
|
|
157
157
|
* Sets multiple keys and values
|
|
@@ -161,7 +161,7 @@ function set(key, value, options) {
|
|
|
161
161
|
* @param data object keyed by ONYXKEYS and the values to set
|
|
162
162
|
*/
|
|
163
163
|
function multiSet(data) {
|
|
164
|
-
return OnyxUtils_1.default.multiSetWithRetry(data);
|
|
164
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.multiSetWithRetry(data));
|
|
165
165
|
}
|
|
166
166
|
/**
|
|
167
167
|
* Merge a new value into an existing value at a key.
|
|
@@ -180,71 +180,73 @@ function multiSet(data) {
|
|
|
180
180
|
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
|
|
181
181
|
*/
|
|
182
182
|
function merge(key, changes) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
183
|
+
return OnyxUtils_1.default.afterInit(() => {
|
|
184
|
+
const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
|
|
185
|
+
if (skippableCollectionMemberIDs.size) {
|
|
186
|
+
try {
|
|
187
|
+
const [, collectionMemberID] = OnyxUtils_1.default.splitCollectionMemberKey(key);
|
|
188
|
+
if (skippableCollectionMemberIDs.has(collectionMemberID)) {
|
|
189
|
+
// The key is a skippable one, so we set the new changes to undefined.
|
|
190
|
+
// eslint-disable-next-line no-param-reassign
|
|
191
|
+
changes = undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
// The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
|
|
191
196
|
}
|
|
192
197
|
}
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
const mergeQueue = OnyxUtils_1.default.getMergeQueue();
|
|
199
|
+
const mergeQueuePromise = OnyxUtils_1.default.getMergeQueuePromise();
|
|
200
|
+
// Top-level undefined values are ignored
|
|
201
|
+
// Therefore, we need to prevent adding them to the merge queue
|
|
202
|
+
if (changes === undefined) {
|
|
203
|
+
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
|
|
195
204
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (changes === undefined) {
|
|
202
|
-
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
|
|
203
|
-
}
|
|
204
|
-
// Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
|
|
205
|
-
// Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
|
|
206
|
-
if (mergeQueue[key]) {
|
|
207
|
-
mergeQueue[key].push(changes);
|
|
208
|
-
return mergeQueuePromise[key];
|
|
209
|
-
}
|
|
210
|
-
mergeQueue[key] = [changes];
|
|
211
|
-
mergeQueuePromise[key] = OnyxUtils_1.default.get(key).then((existingValue) => {
|
|
212
|
-
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
|
|
213
|
-
if (mergeQueue[key] == null) {
|
|
214
|
-
return Promise.resolve();
|
|
205
|
+
// Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
|
|
206
|
+
// Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
|
|
207
|
+
if (mergeQueue[key]) {
|
|
208
|
+
mergeQueue[key].push(changes);
|
|
209
|
+
return mergeQueuePromise[key];
|
|
215
210
|
}
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'merge', existingValueType, newValueType));
|
|
221
|
-
}
|
|
222
|
-
return isCompatible;
|
|
223
|
-
});
|
|
224
|
-
// Clean up the write queue, so we don't apply these changes again.
|
|
225
|
-
delete mergeQueue[key];
|
|
226
|
-
delete mergeQueuePromise[key];
|
|
227
|
-
if (!validChanges.length) {
|
|
211
|
+
mergeQueue[key] = [changes];
|
|
212
|
+
mergeQueuePromise[key] = OnyxUtils_1.default.get(key).then((existingValue) => {
|
|
213
|
+
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
|
|
214
|
+
if (mergeQueue[key] == null) {
|
|
228
215
|
return Promise.resolve();
|
|
229
216
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
217
|
+
try {
|
|
218
|
+
const validChanges = mergeQueue[key].filter((change) => {
|
|
219
|
+
const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(change, existingValue);
|
|
220
|
+
if (!isCompatible) {
|
|
221
|
+
Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'merge', existingValueType, newValueType));
|
|
222
|
+
}
|
|
223
|
+
return isCompatible;
|
|
224
|
+
});
|
|
225
|
+
// Clean up the write queue, so we don't apply these changes again.
|
|
226
|
+
delete mergeQueue[key];
|
|
227
|
+
delete mergeQueuePromise[key];
|
|
228
|
+
if (!validChanges.length) {
|
|
229
|
+
return Promise.resolve();
|
|
230
|
+
}
|
|
231
|
+
// If the last change is null, we can just delete the key.
|
|
232
|
+
// Therefore, we don't need to further broadcast and update the value so we can return early.
|
|
233
|
+
if (validChanges.at(-1) === null) {
|
|
234
|
+
OnyxUtils_1.default.remove(key);
|
|
235
|
+
OnyxUtils_1.default.logKeyRemoved(OnyxUtils_1.default.METHOD.MERGE, key);
|
|
236
|
+
return Promise.resolve();
|
|
237
|
+
}
|
|
238
|
+
return OnyxMerge_1.default.applyMerge(key, existingValue, validChanges).then(({ mergedValue, updatePromise }) => {
|
|
239
|
+
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MERGE, key, changes, mergedValue);
|
|
240
|
+
return updatePromise;
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
Logger.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
|
|
235
245
|
return Promise.resolve();
|
|
236
246
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
return updatePromise;
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
catch (error) {
|
|
243
|
-
Logger.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
|
|
244
|
-
return Promise.resolve();
|
|
245
|
-
}
|
|
247
|
+
});
|
|
248
|
+
return mergeQueuePromise[key];
|
|
246
249
|
});
|
|
247
|
-
return mergeQueuePromise[key];
|
|
248
250
|
}
|
|
249
251
|
/**
|
|
250
252
|
* Merges a collection based on their keys.
|
|
@@ -260,7 +262,7 @@ function merge(key, changes) {
|
|
|
260
262
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
261
263
|
*/
|
|
262
264
|
function mergeCollection(collectionKey, collection) {
|
|
263
|
-
return OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection, isProcessingCollectionUpdate: true });
|
|
265
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection, isProcessingCollectionUpdate: true }));
|
|
264
266
|
}
|
|
265
267
|
/**
|
|
266
268
|
* Clear out all the data in the store
|
|
@@ -284,91 +286,93 @@ function mergeCollection(collectionKey, collection) {
|
|
|
284
286
|
* @param keysToPreserve is a list of ONYXKEYS that should not be cleared with the rest of the data
|
|
285
287
|
*/
|
|
286
288
|
function clear(keysToPreserve = []) {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
.
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
if (
|
|
327
|
-
keyValuesToResetAsCollection[collectionKey]
|
|
289
|
+
return OnyxUtils_1.default.afterInit(() => {
|
|
290
|
+
const defaultKeyStates = OnyxUtils_1.default.getDefaultKeyStates();
|
|
291
|
+
const initialKeys = Object.keys(defaultKeyStates);
|
|
292
|
+
const promise = OnyxUtils_1.default.getAllKeys()
|
|
293
|
+
.then((cachedKeys) => {
|
|
294
|
+
var _a;
|
|
295
|
+
OnyxCache_1.default.clearNullishStorageKeys();
|
|
296
|
+
const keysToBeClearedFromStorage = [];
|
|
297
|
+
const keyValuesToResetIndividually = {};
|
|
298
|
+
// We need to store old and new values for collection keys to properly notify subscribers when clearing Onyx
|
|
299
|
+
// because the notification process needs the old values in cache but at that point they will be already removed from it.
|
|
300
|
+
const keyValuesToResetAsCollection = {};
|
|
301
|
+
const allKeys = new Set([...cachedKeys, ...initialKeys]);
|
|
302
|
+
// The only keys that should not be cleared are:
|
|
303
|
+
// 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
|
|
304
|
+
// status, or activeClients need to remain in Onyx even when signed out)
|
|
305
|
+
// 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
|
|
306
|
+
// to null would cause unknown behavior)
|
|
307
|
+
// 2.1 However, if a default key was explicitly set to null, we need to reset it to the default value
|
|
308
|
+
for (const key of allKeys) {
|
|
309
|
+
const isKeyToPreserve = keysToPreserve.includes(key);
|
|
310
|
+
const isDefaultKey = key in defaultKeyStates;
|
|
311
|
+
// If the key is being removed or reset to default:
|
|
312
|
+
// 1. Update it in the cache
|
|
313
|
+
// 2. Figure out whether it is a collection key or not,
|
|
314
|
+
// since collection key subscribers need to be updated differently
|
|
315
|
+
if (!isKeyToPreserve) {
|
|
316
|
+
const oldValue = OnyxCache_1.default.get(key);
|
|
317
|
+
const newValue = (_a = defaultKeyStates[key]) !== null && _a !== void 0 ? _a : null;
|
|
318
|
+
if (newValue !== oldValue) {
|
|
319
|
+
OnyxCache_1.default.set(key, newValue);
|
|
320
|
+
let collectionKey;
|
|
321
|
+
try {
|
|
322
|
+
collectionKey = OnyxUtils_1.default.getCollectionKey(key);
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
// If getCollectionKey() throws an error it means the key is not a collection key.
|
|
326
|
+
collectionKey = undefined;
|
|
327
|
+
}
|
|
328
|
+
if (collectionKey) {
|
|
329
|
+
if (!keyValuesToResetAsCollection[collectionKey]) {
|
|
330
|
+
keyValuesToResetAsCollection[collectionKey] = { oldValues: {}, newValues: {} };
|
|
331
|
+
}
|
|
332
|
+
keyValuesToResetAsCollection[collectionKey].oldValues[key] = oldValue;
|
|
333
|
+
keyValuesToResetAsCollection[collectionKey].newValues[key] = newValue !== null && newValue !== void 0 ? newValue : undefined;
|
|
334
|
+
}
|
|
335
|
+
else {
|
|
336
|
+
keyValuesToResetIndividually[key] = newValue !== null && newValue !== void 0 ? newValue : undefined;
|
|
328
337
|
}
|
|
329
|
-
keyValuesToResetAsCollection[collectionKey].oldValues[key] = oldValue;
|
|
330
|
-
keyValuesToResetAsCollection[collectionKey].newValues[key] = newValue !== null && newValue !== void 0 ? newValue : undefined;
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
keyValuesToResetIndividually[key] = newValue !== null && newValue !== void 0 ? newValue : undefined;
|
|
334
338
|
}
|
|
335
339
|
}
|
|
340
|
+
if (isKeyToPreserve || isDefaultKey) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
// If it isn't preserved and doesn't have a default, we'll remove it
|
|
344
|
+
keysToBeClearedFromStorage.push(key);
|
|
336
345
|
}
|
|
337
|
-
|
|
338
|
-
|
|
346
|
+
const updatePromises = [];
|
|
347
|
+
// Notify the subscribers for each key/value group so they can receive the new values
|
|
348
|
+
for (const [key, value] of Object.entries(keyValuesToResetIndividually)) {
|
|
349
|
+
updatePromises.push(OnyxUtils_1.default.scheduleSubscriberUpdate(key, value));
|
|
339
350
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.then(() => {
|
|
366
|
-
DevTools_1.default.clearState(keysToPreserve);
|
|
367
|
-
return Promise.all(updatePromises);
|
|
368
|
-
});
|
|
369
|
-
})
|
|
370
|
-
.then(() => undefined);
|
|
371
|
-
return OnyxCache_1.default.captureTask(OnyxCache_1.TASK.CLEAR, promise);
|
|
351
|
+
for (const [key, value] of Object.entries(keyValuesToResetAsCollection)) {
|
|
352
|
+
updatePromises.push(OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(key, value.newValues, value.oldValues));
|
|
353
|
+
}
|
|
354
|
+
// Exclude RAM-only keys to prevent them from being saved to storage
|
|
355
|
+
const defaultKeyValuePairs = Object.entries(Object.keys(defaultKeyStates)
|
|
356
|
+
.filter((key) => !keysToPreserve.includes(key) && !OnyxUtils_1.default.isRamOnlyKey(key))
|
|
357
|
+
.reduce((obj, key) => {
|
|
358
|
+
// eslint-disable-next-line no-param-reassign
|
|
359
|
+
obj[key] = defaultKeyStates[key];
|
|
360
|
+
return obj;
|
|
361
|
+
}, {}));
|
|
362
|
+
// Remove only the items that we want cleared from storage, and reset others to default
|
|
363
|
+
for (const key of keysToBeClearedFromStorage)
|
|
364
|
+
OnyxCache_1.default.drop(key);
|
|
365
|
+
return storage_1.default.removeItems(keysToBeClearedFromStorage)
|
|
366
|
+
.then(() => OnyxConnectionManager_1.default.refreshSessionID())
|
|
367
|
+
.then(() => storage_1.default.multiSet(defaultKeyValuePairs))
|
|
368
|
+
.then(() => {
|
|
369
|
+
DevTools_1.default.clearState(keysToPreserve);
|
|
370
|
+
return Promise.all(updatePromises);
|
|
371
|
+
});
|
|
372
|
+
})
|
|
373
|
+
.then(() => undefined);
|
|
374
|
+
return OnyxCache_1.default.captureTask(OnyxCache_1.TASK.CLEAR, promise);
|
|
375
|
+
});
|
|
372
376
|
}
|
|
373
377
|
/**
|
|
374
378
|
* Insert API responses and lifecycle data into Onyx
|
|
@@ -377,132 +381,132 @@ function clear(keysToPreserve = []) {
|
|
|
377
381
|
* @returns resolves when all operations are complete
|
|
378
382
|
*/
|
|
379
383
|
function update(data) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
//
|
|
387
|
-
|
|
388
|
-
|
|
384
|
+
return OnyxUtils_1.default.afterInit(() => {
|
|
385
|
+
// The queue of operations within a single `update` call in the format of <item key - list of operations updating the item>.
|
|
386
|
+
// This allows us to batch the operations per item and merge them into one operation in the order they were requested.
|
|
387
|
+
const updateQueue = {};
|
|
388
|
+
const enqueueSetOperation = (key, value) => {
|
|
389
|
+
// If a `set` operation is enqueued, we should clear the whole queue.
|
|
390
|
+
// Since the `set` operation replaces the value entirely, there's no need to perform any previous operations.
|
|
391
|
+
// To do this, we first put `null` in the queue, which removes the existing value, and then merge the new value.
|
|
392
|
+
updateQueue[key] = [null, value];
|
|
393
|
+
};
|
|
394
|
+
const enqueueMergeOperation = (key, value) => {
|
|
395
|
+
if (value === null) {
|
|
396
|
+
// If we merge `null`, the value is removed and all the previous operations are discarded.
|
|
397
|
+
updateQueue[key] = [null];
|
|
398
|
+
}
|
|
399
|
+
else if (!updateQueue[key]) {
|
|
400
|
+
updateQueue[key] = [value];
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
updateQueue[key].push(value);
|
|
389
404
|
}
|
|
390
|
-
}
|
|
391
|
-
else if (onyxMethod !== OnyxUtils_1.default.METHOD.CLEAR && typeof key !== 'string') {
|
|
392
|
-
throw new Error(`Invalid ${typeof key} key provided in Onyx update. Onyx key must be of type string.`);
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
// The queue of operations within a single `update` call in the format of <item key - list of operations updating the item>.
|
|
396
|
-
// This allows us to batch the operations per item and merge them into one operation in the order they were requested.
|
|
397
|
-
const updateQueue = {};
|
|
398
|
-
const enqueueSetOperation = (key, value) => {
|
|
399
|
-
// If a `set` operation is enqueued, we should clear the whole queue.
|
|
400
|
-
// Since the `set` operation replaces the value entirely, there's no need to perform any previous operations.
|
|
401
|
-
// To do this, we first put `null` in the queue, which removes the existing value, and then merge the new value.
|
|
402
|
-
updateQueue[key] = [null, value];
|
|
403
|
-
};
|
|
404
|
-
const enqueueMergeOperation = (key, value) => {
|
|
405
|
-
if (value === null) {
|
|
406
|
-
// If we merge `null`, the value is removed and all the previous operations are discarded.
|
|
407
|
-
updateQueue[key] = [null];
|
|
408
|
-
}
|
|
409
|
-
else if (!updateQueue[key]) {
|
|
410
|
-
updateQueue[key] = [value];
|
|
411
|
-
}
|
|
412
|
-
else {
|
|
413
|
-
updateQueue[key].push(value);
|
|
414
|
-
}
|
|
415
|
-
};
|
|
416
|
-
const promises = [];
|
|
417
|
-
let clearPromise = Promise.resolve();
|
|
418
|
-
for (const { onyxMethod, key, value } of data) {
|
|
419
|
-
const handlers = {
|
|
420
|
-
[OnyxUtils_1.default.METHOD.SET]: enqueueSetOperation,
|
|
421
|
-
[OnyxUtils_1.default.METHOD.MERGE]: enqueueMergeOperation,
|
|
422
|
-
[OnyxUtils_1.default.METHOD.MERGE_COLLECTION]: () => {
|
|
423
|
-
const collection = value;
|
|
424
|
-
if (!OnyxUtils_1.default.isValidNonEmptyCollectionForMerge(collection)) {
|
|
425
|
-
Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.');
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
// Confirm all the collection keys belong to the same parent
|
|
429
|
-
const collectionKeys = Object.keys(collection);
|
|
430
|
-
if (OnyxUtils_1.default.doAllCollectionItemsBelongToSameParent(key, collectionKeys)) {
|
|
431
|
-
const mergedCollection = collection;
|
|
432
|
-
for (const collectionKey of collectionKeys)
|
|
433
|
-
enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]);
|
|
434
|
-
}
|
|
435
|
-
},
|
|
436
|
-
[OnyxUtils_1.default.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v)),
|
|
437
|
-
[OnyxUtils_1.default.METHOD.MULTI_SET]: (k, v) => {
|
|
438
|
-
for (const [entryKey, entryValue] of Object.entries(v))
|
|
439
|
-
enqueueSetOperation(entryKey, entryValue);
|
|
440
|
-
},
|
|
441
|
-
[OnyxUtils_1.default.METHOD.CLEAR]: () => {
|
|
442
|
-
clearPromise = clear();
|
|
443
|
-
},
|
|
444
405
|
};
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
406
|
+
const promises = [];
|
|
407
|
+
let clearPromise = Promise.resolve();
|
|
408
|
+
const onyxMethods = Object.values(OnyxUtils_1.default.METHOD);
|
|
409
|
+
for (const { onyxMethod, key, value } of data) {
|
|
410
|
+
if (!onyxMethods.includes(onyxMethod)) {
|
|
411
|
+
Logger.logInfo(`Invalid onyxMethod ${onyxMethod} in Onyx update. Skipping this operation.`);
|
|
412
|
+
continue;
|
|
413
|
+
}
|
|
414
|
+
if (onyxMethod !== OnyxUtils_1.default.METHOD.CLEAR && onyxMethod !== OnyxUtils_1.default.METHOD.MULTI_SET && typeof key !== 'string') {
|
|
415
|
+
Logger.logInfo(`Invalid ${typeof key} key provided in Onyx update. Key must be of type string. Skipping this operation.`);
|
|
416
|
+
continue;
|
|
417
|
+
}
|
|
418
|
+
const handlers = {
|
|
419
|
+
[OnyxUtils_1.default.METHOD.SET]: enqueueSetOperation,
|
|
420
|
+
[OnyxUtils_1.default.METHOD.MERGE]: enqueueMergeOperation,
|
|
421
|
+
[OnyxUtils_1.default.METHOD.MERGE_COLLECTION]: () => {
|
|
422
|
+
const collection = value;
|
|
423
|
+
if (!OnyxUtils_1.default.isValidNonEmptyCollectionForMerge(collection)) {
|
|
424
|
+
Logger.logInfo('Invalid or empty value provided in Onyx mergeCollection. Skipping this operation.');
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
// Confirm all the collection keys belong to the same parent
|
|
428
|
+
const collectionKeys = Object.keys(collection);
|
|
429
|
+
if (OnyxUtils_1.default.doAllCollectionItemsBelongToSameParent(key, collectionKeys)) {
|
|
430
|
+
const mergedCollection = collection;
|
|
431
|
+
for (const collectionKey of collectionKeys)
|
|
432
|
+
enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]);
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
[OnyxUtils_1.default.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v)),
|
|
436
|
+
[OnyxUtils_1.default.METHOD.MULTI_SET]: (k, v) => {
|
|
437
|
+
if (typeof value !== 'object' || Array.isArray(value) || typeof value === 'function') {
|
|
438
|
+
Logger.logInfo(`Invalid value provided in Onyx multiSet. Value must be of type object. Skipping this operation.`);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
for (const [entryKey, entryValue] of Object.entries(v))
|
|
442
|
+
enqueueSetOperation(entryKey, entryValue);
|
|
443
|
+
},
|
|
444
|
+
[OnyxUtils_1.default.METHOD.CLEAR]: () => {
|
|
445
|
+
clearPromise = clear();
|
|
446
|
+
},
|
|
447
|
+
};
|
|
448
|
+
handlers[onyxMethod](key, value);
|
|
456
449
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
if (
|
|
463
|
-
//
|
|
464
|
-
|
|
450
|
+
// Group all the collection-related keys and update each collection in a single `mergeCollection` call.
|
|
451
|
+
// This is needed to prevent multiple `mergeCollection` calls for the same collection and `merge` calls for the individual items of the said collection.
|
|
452
|
+
// This way, we ensure there is no race condition in the queued updates of the same key.
|
|
453
|
+
for (const collectionKey of OnyxUtils_1.default.getCollectionKeys()) {
|
|
454
|
+
const collectionItemKeys = Object.keys(updateQueue).filter((key) => OnyxUtils_1.default.isKeyMatch(collectionKey, key));
|
|
455
|
+
if (collectionItemKeys.length <= 1) {
|
|
456
|
+
// If there are no items of this collection in the updateQueue, we should skip it.
|
|
457
|
+
// If there is only one item, we should update it individually, therefore retain it in the updateQueue.
|
|
458
|
+
continue;
|
|
465
459
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
460
|
+
const batchedCollectionUpdates = collectionItemKeys.reduce((queue, key) => {
|
|
461
|
+
const operations = updateQueue[key];
|
|
462
|
+
// Remove the collection-related key from the updateQueue so that it won't be processed individually.
|
|
463
|
+
delete updateQueue[key];
|
|
464
|
+
const batchedChanges = OnyxUtils_1.default.mergeAndMarkChanges(operations);
|
|
465
|
+
if (operations[0] === null) {
|
|
466
|
+
// eslint-disable-next-line no-param-reassign
|
|
467
|
+
queue.set[key] = batchedChanges.result;
|
|
468
|
+
}
|
|
469
|
+
else {
|
|
470
470
|
// eslint-disable-next-line no-param-reassign
|
|
471
|
-
queue.
|
|
471
|
+
queue.merge[key] = batchedChanges.result;
|
|
472
|
+
if (batchedChanges.replaceNullPatches.length > 0) {
|
|
473
|
+
// eslint-disable-next-line no-param-reassign
|
|
474
|
+
queue.mergeReplaceNullPatches[key] = batchedChanges.replaceNullPatches;
|
|
475
|
+
}
|
|
472
476
|
}
|
|
477
|
+
return queue;
|
|
478
|
+
}, {
|
|
479
|
+
merge: {},
|
|
480
|
+
mergeReplaceNullPatches: {},
|
|
481
|
+
set: {},
|
|
482
|
+
});
|
|
483
|
+
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.merge)) {
|
|
484
|
+
promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches({
|
|
485
|
+
collectionKey,
|
|
486
|
+
collection: batchedCollectionUpdates.merge,
|
|
487
|
+
mergeReplaceNullPatches: batchedCollectionUpdates.mergeReplaceNullPatches,
|
|
488
|
+
isProcessingCollectionUpdate: true,
|
|
489
|
+
}));
|
|
490
|
+
}
|
|
491
|
+
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.set)) {
|
|
492
|
+
promises.push(() => OnyxUtils_1.default.partialSetCollection({ collectionKey, collection: batchedCollectionUpdates.set }));
|
|
473
493
|
}
|
|
474
|
-
return queue;
|
|
475
|
-
}, {
|
|
476
|
-
merge: {},
|
|
477
|
-
mergeReplaceNullPatches: {},
|
|
478
|
-
set: {},
|
|
479
|
-
});
|
|
480
|
-
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.merge)) {
|
|
481
|
-
promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches({
|
|
482
|
-
collectionKey,
|
|
483
|
-
collection: batchedCollectionUpdates.merge,
|
|
484
|
-
mergeReplaceNullPatches: batchedCollectionUpdates.mergeReplaceNullPatches,
|
|
485
|
-
isProcessingCollectionUpdate: true,
|
|
486
|
-
}));
|
|
487
|
-
}
|
|
488
|
-
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.set)) {
|
|
489
|
-
promises.push(() => OnyxUtils_1.default.partialSetCollection({ collectionKey, collection: batchedCollectionUpdates.set }));
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
for (const [key, operations] of Object.entries(updateQueue)) {
|
|
493
|
-
if (operations[0] === null) {
|
|
494
|
-
const batchedChanges = OnyxUtils_1.default.mergeChanges(operations).result;
|
|
495
|
-
promises.push(() => set(key, batchedChanges));
|
|
496
|
-
continue;
|
|
497
494
|
}
|
|
498
|
-
for (const
|
|
499
|
-
|
|
495
|
+
for (const [key, operations] of Object.entries(updateQueue)) {
|
|
496
|
+
if (operations[0] === null) {
|
|
497
|
+
const batchedChanges = OnyxUtils_1.default.mergeChanges(operations).result;
|
|
498
|
+
promises.push(() => set(key, batchedChanges));
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
for (const operation of operations) {
|
|
502
|
+
promises.push(() => merge(key, operation));
|
|
503
|
+
}
|
|
500
504
|
}
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
505
|
+
const snapshotPromises = OnyxUtils_1.default.updateSnapshots(data, merge);
|
|
506
|
+
// We need to run the snapshot updates before the other updates so the snapshot data can be updated before the loading state in the snapshot
|
|
507
|
+
const finalPromises = snapshotPromises.concat(promises);
|
|
508
|
+
return clearPromise.then(() => Promise.all(finalPromises.map((p) => p()))).then(() => undefined);
|
|
509
|
+
});
|
|
506
510
|
}
|
|
507
511
|
/**
|
|
508
512
|
* Sets a collection by replacing all existing collection members with new values.
|
|
@@ -518,7 +522,7 @@ function update(data) {
|
|
|
518
522
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
519
523
|
*/
|
|
520
524
|
function setCollection(collectionKey, collection) {
|
|
521
|
-
return OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection });
|
|
525
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection }));
|
|
522
526
|
}
|
|
523
527
|
const Onyx = {
|
|
524
528
|
METHOD: OnyxUtils_1.default.METHOD,
|
package/dist/OnyxUtils.d.ts
CHANGED
|
@@ -30,6 +30,15 @@ declare function getDefaultKeyStates(): Record<OnyxKey, OnyxValue<OnyxKey>>;
|
|
|
30
30
|
* Getter - returns the deffered init task.
|
|
31
31
|
*/
|
|
32
32
|
declare function getDeferredInitTask(): DeferredTask;
|
|
33
|
+
/**
|
|
34
|
+
* Executes an action after Onyx has been initialized.
|
|
35
|
+
* If Onyx is already initialized, the action is executed immediately.
|
|
36
|
+
* Otherwise, it waits for initialization to complete before executing.
|
|
37
|
+
*
|
|
38
|
+
* @param action The action to execute after initialization
|
|
39
|
+
* @returns The result of the action
|
|
40
|
+
*/
|
|
41
|
+
declare function afterInit<T>(action: () => Promise<T>): Promise<T>;
|
|
33
42
|
/**
|
|
34
43
|
* Getter - returns the skippable collection member IDs.
|
|
35
44
|
*/
|
|
@@ -343,6 +352,7 @@ declare const OnyxUtils: {
|
|
|
343
352
|
getMergeQueuePromise: typeof getMergeQueuePromise;
|
|
344
353
|
getDefaultKeyStates: typeof getDefaultKeyStates;
|
|
345
354
|
getDeferredInitTask: typeof getDeferredInitTask;
|
|
355
|
+
afterInit: typeof afterInit;
|
|
346
356
|
initStoreValues: typeof initStoreValues;
|
|
347
357
|
sendActionToDevTools: typeof sendActionToDevTools;
|
|
348
358
|
get: typeof get;
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -122,6 +122,20 @@ function getDefaultKeyStates() {
|
|
|
122
122
|
function getDeferredInitTask() {
|
|
123
123
|
return deferredInitTask;
|
|
124
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Executes an action after Onyx has been initialized.
|
|
127
|
+
* If Onyx is already initialized, the action is executed immediately.
|
|
128
|
+
* Otherwise, it waits for initialization to complete before executing.
|
|
129
|
+
*
|
|
130
|
+
* @param action The action to execute after initialization
|
|
131
|
+
* @returns The result of the action
|
|
132
|
+
*/
|
|
133
|
+
function afterInit(action) {
|
|
134
|
+
if (deferredInitTask.isResolved) {
|
|
135
|
+
return action();
|
|
136
|
+
}
|
|
137
|
+
return deferredInitTask.promise.then(action);
|
|
138
|
+
}
|
|
125
139
|
/**
|
|
126
140
|
* Getter - returns the skippable collection member IDs.
|
|
127
141
|
*/
|
|
@@ -1446,6 +1460,7 @@ const OnyxUtils = {
|
|
|
1446
1460
|
getMergeQueuePromise,
|
|
1447
1461
|
getDefaultKeyStates,
|
|
1448
1462
|
getDeferredInitTask,
|
|
1463
|
+
afterInit,
|
|
1449
1464
|
initStoreValues,
|
|
1450
1465
|
sendActionToDevTools,
|
|
1451
1466
|
get,
|
|
@@ -7,9 +7,14 @@ exports.default = createDeferredTask;
|
|
|
7
7
|
* Useful when we want to wait for a tasks that is resolved from an external action
|
|
8
8
|
*/
|
|
9
9
|
function createDeferredTask() {
|
|
10
|
-
const
|
|
11
|
-
deferred
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
const { promise, resolve: originalResolve } = Promise.withResolvers();
|
|
11
|
+
const deferred = {
|
|
12
|
+
promise,
|
|
13
|
+
resolve: () => {
|
|
14
|
+
deferred.isResolved = true;
|
|
15
|
+
originalResolve();
|
|
16
|
+
},
|
|
17
|
+
isResolved: false,
|
|
18
|
+
};
|
|
14
19
|
return deferred;
|
|
15
20
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -274,7 +274,7 @@ type ExpandOnyxKeys<TKey extends OnyxKey> = TKey extends CollectionKeyBase ? NoI
|
|
|
274
274
|
* If a new method is added to OnyxUtils.METHOD constant, it must be added to OnyxMethodValueMap type.
|
|
275
275
|
* Otherwise it will show static type errors.
|
|
276
276
|
*/
|
|
277
|
-
type OnyxUpdate<TKey extends OnyxKey
|
|
277
|
+
type OnyxUpdate<TKey extends OnyxKey> = {
|
|
278
278
|
[K in TKey]: {
|
|
279
279
|
onyxMethod: typeof OnyxUtils.METHOD.SET;
|
|
280
280
|
key: ExpandOnyxKeys<K>;
|