react-native-onyx 3.0.11 → 3.0.13
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 +11 -143
- package/dist/OnyxUtils.d.ts +57 -28
- package/dist/OnyxUtils.js +237 -78
- package/dist/types.d.ts +17 -1
- package/package.json +1 -1
package/dist/Onyx.js
CHANGED
|
@@ -145,62 +145,7 @@ function disconnect(connection) {
|
|
|
145
145
|
* @param options optional configuration object
|
|
146
146
|
*/
|
|
147
147
|
function set(key, value, options) {
|
|
148
|
-
|
|
149
|
-
// before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
|
|
150
|
-
if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
|
|
151
|
-
delete OnyxUtils_1.default.getMergeQueue()[key];
|
|
152
|
-
}
|
|
153
|
-
const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
|
|
154
|
-
if (skippableCollectionMemberIDs.size) {
|
|
155
|
-
try {
|
|
156
|
-
const [, collectionMemberID] = OnyxUtils_1.default.splitCollectionMemberKey(key);
|
|
157
|
-
if (skippableCollectionMemberIDs.has(collectionMemberID)) {
|
|
158
|
-
// The key is a skippable one, so we set the new value to null.
|
|
159
|
-
// eslint-disable-next-line no-param-reassign
|
|
160
|
-
value = null;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
catch (e) {
|
|
164
|
-
// The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
// Onyx.set will ignore `undefined` values as inputs, therefore we can return early.
|
|
168
|
-
if (value === undefined) {
|
|
169
|
-
return Promise.resolve();
|
|
170
|
-
}
|
|
171
|
-
const existingValue = OnyxCache_1.default.get(key, false);
|
|
172
|
-
// If the existing value as well as the new value are null, we can return early.
|
|
173
|
-
if (existingValue === undefined && value === null) {
|
|
174
|
-
return Promise.resolve();
|
|
175
|
-
}
|
|
176
|
-
// Check if the value is compatible with the existing value in the storage
|
|
177
|
-
const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(value, existingValue);
|
|
178
|
-
if (!isCompatible) {
|
|
179
|
-
Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'set', existingValueType, newValueType));
|
|
180
|
-
return Promise.resolve();
|
|
181
|
-
}
|
|
182
|
-
// If the change is null, we can just delete the key.
|
|
183
|
-
// Therefore, we don't need to further broadcast and update the value so we can return early.
|
|
184
|
-
if (value === null) {
|
|
185
|
-
OnyxUtils_1.default.remove(key);
|
|
186
|
-
OnyxUtils_1.default.logKeyRemoved(OnyxUtils_1.default.METHOD.SET, key);
|
|
187
|
-
return Promise.resolve();
|
|
188
|
-
}
|
|
189
|
-
const valueWithoutNestedNullValues = utils_1.default.removeNestedNullValues(value);
|
|
190
|
-
const hasChanged = (options === null || options === void 0 ? void 0 : options.skipCacheCheck) ? true : OnyxCache_1.default.hasValueChanged(key, valueWithoutNestedNullValues);
|
|
191
|
-
OnyxUtils_1.default.logKeyChanged(OnyxUtils_1.default.METHOD.SET, key, value, hasChanged);
|
|
192
|
-
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
193
|
-
const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, valueWithoutNestedNullValues, hasChanged);
|
|
194
|
-
// If the value has not changed or the key got removed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
195
|
-
if (!hasChanged) {
|
|
196
|
-
return updatePromise;
|
|
197
|
-
}
|
|
198
|
-
return storage_1.default.setItem(key, valueWithoutNestedNullValues)
|
|
199
|
-
.catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, set, key, valueWithoutNestedNullValues))
|
|
200
|
-
.then(() => {
|
|
201
|
-
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.SET, key, valueWithoutNestedNullValues);
|
|
202
|
-
return updatePromise;
|
|
203
|
-
});
|
|
148
|
+
return OnyxUtils_1.default.setWithRetry({ key, value, options });
|
|
204
149
|
}
|
|
205
150
|
/**
|
|
206
151
|
* Sets multiple keys and values
|
|
@@ -210,42 +155,7 @@ function set(key, value, options) {
|
|
|
210
155
|
* @param data object keyed by ONYXKEYS and the values to set
|
|
211
156
|
*/
|
|
212
157
|
function multiSet(data) {
|
|
213
|
-
|
|
214
|
-
const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
|
|
215
|
-
if (skippableCollectionMemberIDs.size) {
|
|
216
|
-
newData = Object.keys(newData).reduce((result, key) => {
|
|
217
|
-
try {
|
|
218
|
-
const [, collectionMemberID] = OnyxUtils_1.default.splitCollectionMemberKey(key);
|
|
219
|
-
// If the collection member key is a skippable one we set its value to null.
|
|
220
|
-
// eslint-disable-next-line no-param-reassign
|
|
221
|
-
result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? newData[key] : null;
|
|
222
|
-
}
|
|
223
|
-
catch (_a) {
|
|
224
|
-
// The key is not a collection one or something went wrong during split, so we assign the data to result anyway.
|
|
225
|
-
// eslint-disable-next-line no-param-reassign
|
|
226
|
-
result[key] = newData[key];
|
|
227
|
-
}
|
|
228
|
-
return result;
|
|
229
|
-
}, {});
|
|
230
|
-
}
|
|
231
|
-
const keyValuePairsToSet = OnyxUtils_1.default.prepareKeyValuePairsForStorage(newData, true);
|
|
232
|
-
const updatePromises = keyValuePairsToSet.map(([key, value]) => {
|
|
233
|
-
// When we use multiSet to set a key we want to clear the current delta changes from Onyx.merge that were queued
|
|
234
|
-
// before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
|
|
235
|
-
if (OnyxUtils_1.default.hasPendingMergeForKey(key)) {
|
|
236
|
-
delete OnyxUtils_1.default.getMergeQueue()[key];
|
|
237
|
-
}
|
|
238
|
-
// Update cache and optimistically inform subscribers on the next tick
|
|
239
|
-
OnyxCache_1.default.set(key, value);
|
|
240
|
-
return OnyxUtils_1.default.scheduleSubscriberUpdate(key, value);
|
|
241
|
-
});
|
|
242
|
-
return storage_1.default.multiSet(keyValuePairsToSet)
|
|
243
|
-
.catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, multiSet, newData))
|
|
244
|
-
.then(() => {
|
|
245
|
-
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.MULTI_SET, undefined, newData);
|
|
246
|
-
return Promise.all(updatePromises);
|
|
247
|
-
})
|
|
248
|
-
.then(() => undefined);
|
|
158
|
+
return OnyxUtils_1.default.multiSetWithRetry(data);
|
|
249
159
|
}
|
|
250
160
|
/**
|
|
251
161
|
* Merge a new value into an existing value at a key.
|
|
@@ -344,7 +254,7 @@ function merge(key, changes) {
|
|
|
344
254
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
345
255
|
*/
|
|
346
256
|
function mergeCollection(collectionKey, collection) {
|
|
347
|
-
return OnyxUtils_1.default.mergeCollectionWithPatches(collectionKey, collection,
|
|
257
|
+
return OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection, isProcessingCollectionUpdate: true });
|
|
348
258
|
}
|
|
349
259
|
/**
|
|
350
260
|
* Clear out all the data in the store
|
|
@@ -553,10 +463,15 @@ function update(data) {
|
|
|
553
463
|
set: {},
|
|
554
464
|
});
|
|
555
465
|
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.merge)) {
|
|
556
|
-
promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches(
|
|
466
|
+
promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches({
|
|
467
|
+
collectionKey,
|
|
468
|
+
collection: batchedCollectionUpdates.merge,
|
|
469
|
+
mergeReplaceNullPatches: batchedCollectionUpdates.mergeReplaceNullPatches,
|
|
470
|
+
isProcessingCollectionUpdate: true,
|
|
471
|
+
}));
|
|
557
472
|
}
|
|
558
473
|
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.set)) {
|
|
559
|
-
promises.push(() => OnyxUtils_1.default.partialSetCollection(collectionKey, batchedCollectionUpdates.set));
|
|
474
|
+
promises.push(() => OnyxUtils_1.default.partialSetCollection({ collectionKey, collection: batchedCollectionUpdates.set }));
|
|
560
475
|
}
|
|
561
476
|
});
|
|
562
477
|
Object.entries(updateQueue).forEach(([key, operations]) => {
|
|
@@ -588,54 +503,7 @@ function update(data) {
|
|
|
588
503
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
589
504
|
*/
|
|
590
505
|
function setCollection(collectionKey, collection) {
|
|
591
|
-
|
|
592
|
-
let resultCollectionKeys = Object.keys(resultCollection);
|
|
593
|
-
// Confirm all the collection keys belong to the same parent
|
|
594
|
-
if (!OnyxUtils_1.default.doAllCollectionItemsBelongToSameParent(collectionKey, resultCollectionKeys)) {
|
|
595
|
-
Logger.logAlert(`setCollection called with keys that do not belong to the same parent ${collectionKey}. Skipping this update.`);
|
|
596
|
-
return Promise.resolve();
|
|
597
|
-
}
|
|
598
|
-
const skippableCollectionMemberIDs = OnyxUtils_1.default.getSkippableCollectionMemberIDs();
|
|
599
|
-
if (skippableCollectionMemberIDs.size) {
|
|
600
|
-
resultCollection = resultCollectionKeys.reduce((result, key) => {
|
|
601
|
-
try {
|
|
602
|
-
const [, collectionMemberID] = OnyxUtils_1.default.splitCollectionMemberKey(key, collectionKey);
|
|
603
|
-
// If the collection member key is a skippable one we set its value to null.
|
|
604
|
-
// eslint-disable-next-line no-param-reassign
|
|
605
|
-
result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? resultCollection[key] : null;
|
|
606
|
-
}
|
|
607
|
-
catch (_a) {
|
|
608
|
-
// Something went wrong during split, so we assign the data to result anyway.
|
|
609
|
-
// eslint-disable-next-line no-param-reassign
|
|
610
|
-
result[key] = resultCollection[key];
|
|
611
|
-
}
|
|
612
|
-
return result;
|
|
613
|
-
}, {});
|
|
614
|
-
}
|
|
615
|
-
resultCollectionKeys = Object.keys(resultCollection);
|
|
616
|
-
return OnyxUtils_1.default.getAllKeys().then((persistedKeys) => {
|
|
617
|
-
const mutableCollection = Object.assign({}, resultCollection);
|
|
618
|
-
persistedKeys.forEach((key) => {
|
|
619
|
-
if (!key.startsWith(collectionKey)) {
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
if (resultCollectionKeys.includes(key)) {
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
mutableCollection[key] = null;
|
|
626
|
-
});
|
|
627
|
-
const keyValuePairs = OnyxUtils_1.default.prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
|
|
628
|
-
const previousCollection = OnyxUtils_1.default.getCachedCollection(collectionKey);
|
|
629
|
-
// Preserve references for unchanged items in setCollection
|
|
630
|
-
const preservedCollection = OnyxUtils_1.default.preserveCollectionReferences(keyValuePairs);
|
|
631
|
-
const updatePromise = OnyxUtils_1.default.scheduleNotifyCollectionSubscribers(collectionKey, preservedCollection, previousCollection);
|
|
632
|
-
return storage_1.default.multiSet(keyValuePairs)
|
|
633
|
-
.catch((error) => OnyxUtils_1.default.evictStorageAndRetry(error, setCollection, collectionKey, collection))
|
|
634
|
-
.then(() => {
|
|
635
|
-
OnyxUtils_1.default.sendActionToDevTools(OnyxUtils_1.default.METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
636
|
-
return updatePromise;
|
|
637
|
-
});
|
|
638
|
-
});
|
|
506
|
+
return OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection });
|
|
639
507
|
}
|
|
640
508
|
const Onyx = {
|
|
641
509
|
METHOD: OnyxUtils_1.default.METHOD,
|
package/dist/OnyxUtils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ValueOf } from 'type-fest';
|
|
2
2
|
import type Onyx from './Onyx';
|
|
3
|
-
import type { CollectionKey, CollectionKeyBase, ConnectOptions, DeepRecord, KeyValueMapping, CallbackToStateMapping, MultiMergeReplaceNullPatches, OnyxCollection, OnyxEntry, OnyxInput,
|
|
3
|
+
import type { CollectionKey, CollectionKeyBase, ConnectOptions, DeepRecord, KeyValueMapping, CallbackToStateMapping, MultiMergeReplaceNullPatches, OnyxCollection, OnyxEntry, OnyxInput, OnyxKey, OnyxMergeCollectionInput, OnyxUpdate, OnyxValue, Selector, MergeCollectionWithPatchesParams, SetCollectionParams, SetParams, OnyxMultiSetInput, RetriableOnyxOperation } from './types';
|
|
4
4
|
import type { FastMergeResult } from './utils';
|
|
5
5
|
import type { DeferredTask } from './createDeferredTask';
|
|
6
6
|
import type { StorageKeyValuePair } from './storage/providers/types';
|
|
@@ -138,18 +138,6 @@ declare function getCollectionKey(key: CollectionKey): string;
|
|
|
138
138
|
* If the requested key is a collection, it will return an object with all the collection members.
|
|
139
139
|
*/
|
|
140
140
|
declare function tryGetCachedValue<TKey extends OnyxKey>(key: TKey): OnyxValue<OnyxKey>;
|
|
141
|
-
/**
|
|
142
|
-
* Utility function to preserve object references for unchanged items in collection operations.
|
|
143
|
-
* Compares new values with cached values using deep equality and preserves references when data is identical.
|
|
144
|
-
* @returns The preserved collection with unchanged references maintained
|
|
145
|
-
*/
|
|
146
|
-
declare function preserveCollectionReferences(keyValuePairs: StorageKeyValuePair[]): OnyxInputKeyValueMapping;
|
|
147
|
-
/**
|
|
148
|
-
* Utility function for merge operations that preserves references after cache merge has been performed.
|
|
149
|
-
* Compares merged values with original cached values and preserves references when data is unchanged.
|
|
150
|
-
* @returns The preserved collection with unchanged references maintained
|
|
151
|
-
*/
|
|
152
|
-
declare function preserveCollectionReferencesAfterMerge(collection: Record<string, OnyxValue<OnyxKey>>, originalCachedValues: Record<string, OnyxValue<OnyxKey>>): Record<string, OnyxValue<OnyxKey>>;
|
|
153
141
|
declare function getCachedCollection<TKey extends CollectionKeyBase>(collectionKey: TKey, collectionMemberKeys?: string[]): NonNullable<OnyxCollection<KeyValueMapping[TKey]>>;
|
|
154
142
|
/**
|
|
155
143
|
* When a collection of keys change, search for any callbacks matching the collection key and trigger those callbacks
|
|
@@ -194,11 +182,12 @@ declare function scheduleNotifyCollectionSubscribers<TKey extends OnyxKey>(key:
|
|
|
194
182
|
declare function remove<TKey extends OnyxKey>(key: TKey, isProcessingCollectionUpdate?: boolean): Promise<void>;
|
|
195
183
|
declare function reportStorageQuota(): Promise<void>;
|
|
196
184
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
185
|
+
* Handles storage operation failures based on the error type:
|
|
186
|
+
* - Storage capacity errors: evicts data and retries the operation
|
|
187
|
+
* - Invalid data errors: logs an alert and throws an error
|
|
188
|
+
* - Other errors: retries the operation
|
|
200
189
|
*/
|
|
201
|
-
declare function
|
|
190
|
+
declare function retryOperation<TMethod extends RetriableOnyxOperation>(error: Error, onyxMethod: TMethod, defaultParams: Parameters<TMethod>[0], retryAttempt: number | undefined): Promise<void>;
|
|
202
191
|
/**
|
|
203
192
|
* Notifies subscribers and writes current value to cache
|
|
204
193
|
*/
|
|
@@ -253,25 +242,64 @@ declare function subscribeToKey<TKey extends OnyxKey>(connectOptions: ConnectOpt
|
|
|
253
242
|
*/
|
|
254
243
|
declare function unsubscribeFromKey(subscriptionID: number): void;
|
|
255
244
|
declare function updateSnapshots(data: OnyxUpdate[], mergeFn: typeof Onyx.merge): Array<() => Promise<void>>;
|
|
245
|
+
/**
|
|
246
|
+
* Writes a value to our store with the given key.
|
|
247
|
+
* Serves as core implementation for `Onyx.set()` public function, the difference being
|
|
248
|
+
* that this internal function allows passing an additional `retryAttempt` parameter to retry on failure.
|
|
249
|
+
*
|
|
250
|
+
* @param params - set parameters
|
|
251
|
+
* @param params.key ONYXKEY to set
|
|
252
|
+
* @param params.value value to store
|
|
253
|
+
* @param params.options optional configuration object
|
|
254
|
+
* @param retryAttempt retry attempt
|
|
255
|
+
*/
|
|
256
|
+
declare function setWithRetry<TKey extends OnyxKey>({ key, value, options }: SetParams<TKey>, retryAttempt?: number): Promise<void>;
|
|
257
|
+
/**
|
|
258
|
+
* Sets multiple keys and values.
|
|
259
|
+
* Serves as core implementation for `Onyx.multiSet()` public function, the difference being
|
|
260
|
+
* that this internal function allows passing an additional `retryAttempt` parameter to retry on failure.
|
|
261
|
+
*
|
|
262
|
+
* @param data object keyed by ONYXKEYS and the values to set
|
|
263
|
+
* @param retryAttempt retry attempt
|
|
264
|
+
*/
|
|
265
|
+
declare function multiSetWithRetry(data: OnyxMultiSetInput, retryAttempt?: number): Promise<void>;
|
|
266
|
+
/**
|
|
267
|
+
* Sets a collection by replacing all existing collection members with new values.
|
|
268
|
+
* Any existing collection members not included in the new data will be removed.
|
|
269
|
+
* Serves as core implementation for `Onyx.setCollection()` public function, the difference being
|
|
270
|
+
* that this internal function allows passing an additional `retryAttempt` parameter to retry on failure.
|
|
271
|
+
*
|
|
272
|
+
* @param params - collection parameters
|
|
273
|
+
* @param params.collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
274
|
+
* @param params.collection Object collection keyed by individual collection member keys and values
|
|
275
|
+
* @param retryAttempt retry attempt
|
|
276
|
+
*/
|
|
277
|
+
declare function setCollectionWithRetry<TKey extends CollectionKeyBase>({ collectionKey, collection }: SetCollectionParams<TKey>, retryAttempt?: number): Promise<void>;
|
|
256
278
|
/**
|
|
257
279
|
* Merges a collection based on their keys.
|
|
258
280
|
* Serves as core implementation for `Onyx.mergeCollection()` public function, the difference being
|
|
259
|
-
* that this internal function allows passing an additional `mergeReplaceNullPatches` parameter.
|
|
281
|
+
* that this internal function allows passing an additional `mergeReplaceNullPatches` parameter and retries on failure.
|
|
260
282
|
*
|
|
261
|
-
* @param
|
|
262
|
-
* @param
|
|
263
|
-
* @param
|
|
283
|
+
* @param params - mergeCollection parameters
|
|
284
|
+
* @param params.collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
285
|
+
* @param params.collection Object collection keyed by individual collection member keys and values
|
|
286
|
+
* @param params.mergeReplaceNullPatches Record where the key is a collection member key and the value is a list of
|
|
264
287
|
* tuples that we'll use to replace the nested objects of that collection member record with something else.
|
|
288
|
+
* @param params.isProcessingCollectionUpdate whether this is part of a collection update operation.
|
|
289
|
+
* @param retryAttempt retry attempt
|
|
265
290
|
*/
|
|
266
|
-
declare function mergeCollectionWithPatches<TKey extends CollectionKeyBase>(collectionKey
|
|
291
|
+
declare function mergeCollectionWithPatches<TKey extends CollectionKeyBase>({ collectionKey, collection, mergeReplaceNullPatches, isProcessingCollectionUpdate }: MergeCollectionWithPatchesParams<TKey>, retryAttempt?: number): Promise<void>;
|
|
267
292
|
/**
|
|
268
293
|
* Sets keys in a collection by replacing all targeted collection members with new values.
|
|
269
294
|
* Any existing collection members not included in the new data will not be removed.
|
|
295
|
+
* Retries on failure.
|
|
270
296
|
*
|
|
271
|
-
* @param
|
|
272
|
-
* @param
|
|
297
|
+
* @param params - collection parameters
|
|
298
|
+
* @param params.collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
299
|
+
* @param params.collection Object collection keyed by individual collection member keys and values
|
|
300
|
+
* @param retryAttempt retry attempt
|
|
273
301
|
*/
|
|
274
|
-
declare function partialSetCollection<TKey extends CollectionKeyBase>(collectionKey
|
|
302
|
+
declare function partialSetCollection<TKey extends CollectionKeyBase>({ collectionKey, collection }: SetCollectionParams<TKey>, retryAttempt?: number): Promise<void>;
|
|
275
303
|
declare function logKeyChanged(onyxMethod: Extract<OnyxMethod, 'set' | 'merge'>, key: OnyxKey, value: unknown, hasChanged: boolean): void;
|
|
276
304
|
declare function logKeyRemoved(onyxMethod: Extract<OnyxMethod, 'set' | 'merge'>, key: OnyxKey): void;
|
|
277
305
|
/**
|
|
@@ -313,7 +341,7 @@ declare const OnyxUtils: {
|
|
|
313
341
|
scheduleNotifyCollectionSubscribers: typeof scheduleNotifyCollectionSubscribers;
|
|
314
342
|
remove: typeof remove;
|
|
315
343
|
reportStorageQuota: typeof reportStorageQuota;
|
|
316
|
-
|
|
344
|
+
retryOperation: typeof retryOperation;
|
|
317
345
|
broadcastUpdate: typeof broadcastUpdate;
|
|
318
346
|
hasPendingMergeForKey: typeof hasPendingMergeForKey;
|
|
319
347
|
prepareKeyValuePairsForStorage: typeof prepareKeyValuePairsForStorage;
|
|
@@ -336,10 +364,11 @@ declare const OnyxUtils: {
|
|
|
336
364
|
updateSnapshots: typeof updateSnapshots;
|
|
337
365
|
mergeCollectionWithPatches: typeof mergeCollectionWithPatches;
|
|
338
366
|
partialSetCollection: typeof partialSetCollection;
|
|
339
|
-
preserveCollectionReferences: typeof preserveCollectionReferences;
|
|
340
|
-
preserveCollectionReferencesAfterMerge: typeof preserveCollectionReferencesAfterMerge;
|
|
341
367
|
logKeyChanged: typeof logKeyChanged;
|
|
342
368
|
logKeyRemoved: typeof logKeyRemoved;
|
|
369
|
+
setWithRetry: typeof setWithRetry;
|
|
370
|
+
multiSetWithRetry: typeof multiSetWithRetry;
|
|
371
|
+
setCollectionWithRetry: typeof setCollectionWithRetry;
|
|
343
372
|
};
|
|
344
373
|
export type { OnyxMethod };
|
|
345
374
|
export default OnyxUtils;
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -61,6 +61,19 @@ const METHOD = {
|
|
|
61
61
|
MULTI_SET: 'multiset',
|
|
62
62
|
CLEAR: 'clear',
|
|
63
63
|
};
|
|
64
|
+
// IndexedDB errors that indicate storage capacity issues where eviction can help
|
|
65
|
+
const IDB_STORAGE_ERRORS = [
|
|
66
|
+
'quotaexceedederror', // Browser storage quota exceeded
|
|
67
|
+
];
|
|
68
|
+
// SQLite errors that indicate storage capacity issues where eviction can help
|
|
69
|
+
const SQLITE_STORAGE_ERRORS = [
|
|
70
|
+
'database or disk is full', // Device storage is full
|
|
71
|
+
'disk I/O error', // File system I/O failure, often due to insufficient space or corrupted storage
|
|
72
|
+
'out of memory', // Insufficient RAM or storage space to complete the operation
|
|
73
|
+
];
|
|
74
|
+
const STORAGE_ERRORS = [...IDB_STORAGE_ERRORS, ...SQLITE_STORAGE_ERRORS];
|
|
75
|
+
// Max number of retries for failed storage operations
|
|
76
|
+
const MAX_STORAGE_OPERATION_RETRY_ATTEMPTS = 5;
|
|
64
77
|
// Key/value store of Onyx key and arrays of values to merge
|
|
65
78
|
let mergeQueue = {};
|
|
66
79
|
let mergeQueuePromise = {};
|
|
@@ -452,51 +465,6 @@ function tryGetCachedValue(key) {
|
|
|
452
465
|
}
|
|
453
466
|
return val;
|
|
454
467
|
}
|
|
455
|
-
/**
|
|
456
|
-
* Utility function to preserve object references for unchanged items in collection operations.
|
|
457
|
-
* Compares new values with cached values using deep equality and preserves references when data is identical.
|
|
458
|
-
* @returns The preserved collection with unchanged references maintained
|
|
459
|
-
*/
|
|
460
|
-
function preserveCollectionReferences(keyValuePairs) {
|
|
461
|
-
const preservedCollection = {};
|
|
462
|
-
keyValuePairs.forEach(([key, value]) => {
|
|
463
|
-
const cachedValue = OnyxCache_1.default.get(key, false);
|
|
464
|
-
// If no cached value exists, we need to add the new value (skip expensive deep equality check)
|
|
465
|
-
// Use deep equality check to preserve references for unchanged items
|
|
466
|
-
if (cachedValue !== undefined && (0, fast_equals_1.deepEqual)(value, cachedValue)) {
|
|
467
|
-
// Keep the existing reference
|
|
468
|
-
preservedCollection[key] = cachedValue;
|
|
469
|
-
}
|
|
470
|
-
else {
|
|
471
|
-
// Update cache only for changed items
|
|
472
|
-
OnyxCache_1.default.set(key, value);
|
|
473
|
-
preservedCollection[key] = value;
|
|
474
|
-
}
|
|
475
|
-
});
|
|
476
|
-
return preservedCollection;
|
|
477
|
-
}
|
|
478
|
-
/**
|
|
479
|
-
* Utility function for merge operations that preserves references after cache merge has been performed.
|
|
480
|
-
* Compares merged values with original cached values and preserves references when data is unchanged.
|
|
481
|
-
* @returns The preserved collection with unchanged references maintained
|
|
482
|
-
*/
|
|
483
|
-
function preserveCollectionReferencesAfterMerge(collection, originalCachedValues) {
|
|
484
|
-
const preservedCollection = {};
|
|
485
|
-
Object.keys(collection).forEach((key) => {
|
|
486
|
-
const newMergedValue = OnyxCache_1.default.get(key, false);
|
|
487
|
-
const originalValue = originalCachedValues[key];
|
|
488
|
-
// Use deep equality check to preserve references for unchanged items
|
|
489
|
-
if (originalValue !== undefined && (0, fast_equals_1.deepEqual)(newMergedValue, originalValue)) {
|
|
490
|
-
// Keep the existing reference and update cache
|
|
491
|
-
preservedCollection[key] = originalValue;
|
|
492
|
-
OnyxCache_1.default.set(key, originalValue);
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
preservedCollection[key] = newMergedValue;
|
|
496
|
-
}
|
|
497
|
-
});
|
|
498
|
-
return preservedCollection;
|
|
499
|
-
}
|
|
500
468
|
function getCachedCollection(collectionKey, collectionMemberKeys) {
|
|
501
469
|
// Use optimized collection data retrieval when cache is populated
|
|
502
470
|
const collectionData = OnyxCache_1.default.getCollectionData(collectionKey);
|
|
@@ -757,16 +725,31 @@ function reportStorageQuota() {
|
|
|
757
725
|
});
|
|
758
726
|
}
|
|
759
727
|
/**
|
|
760
|
-
*
|
|
761
|
-
*
|
|
762
|
-
*
|
|
728
|
+
* Handles storage operation failures based on the error type:
|
|
729
|
+
* - Storage capacity errors: evicts data and retries the operation
|
|
730
|
+
* - Invalid data errors: logs an alert and throws an error
|
|
731
|
+
* - Other errors: retries the operation
|
|
763
732
|
*/
|
|
764
|
-
function
|
|
765
|
-
|
|
733
|
+
function retryOperation(error, onyxMethod, defaultParams, retryAttempt) {
|
|
734
|
+
var _a, _b, _c, _d;
|
|
735
|
+
const currentRetryAttempt = retryAttempt !== null && retryAttempt !== void 0 ? retryAttempt : 0;
|
|
736
|
+
const nextRetryAttempt = currentRetryAttempt + 1;
|
|
737
|
+
Logger.logInfo(`Failed to save to storage. Error: ${error}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${currentRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`);
|
|
766
738
|
if (error && Str.startsWith(error.message, "Failed to execute 'put' on 'IDBObjectStore'")) {
|
|
767
739
|
Logger.logAlert('Attempted to set invalid data set in Onyx. Please ensure all data is serializable.');
|
|
768
740
|
throw error;
|
|
769
741
|
}
|
|
742
|
+
const errorMessage = (_b = (_a = error === null || error === void 0 ? void 0 : error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
743
|
+
const errorName = (_d = (_c = error === null || error === void 0 ? void 0 : error.name) === null || _c === void 0 ? void 0 : _c.toLowerCase) === null || _d === void 0 ? void 0 : _d.call(_c);
|
|
744
|
+
const isStorageCapacityError = STORAGE_ERRORS.some((storageError) => (errorName === null || errorName === void 0 ? void 0 : errorName.includes(storageError)) || (errorMessage === null || errorMessage === void 0 ? void 0 : errorMessage.includes(storageError)));
|
|
745
|
+
if (nextRetryAttempt > MAX_STORAGE_OPERATION_RETRY_ATTEMPTS) {
|
|
746
|
+
Logger.logAlert(`Storage operation failed after 5 retries. Error: ${error}. onyxMethod: ${onyxMethod.name}.`);
|
|
747
|
+
return Promise.resolve();
|
|
748
|
+
}
|
|
749
|
+
if (!isStorageCapacityError) {
|
|
750
|
+
// @ts-expect-error No overload matches this call.
|
|
751
|
+
return onyxMethod(defaultParams, nextRetryAttempt);
|
|
752
|
+
}
|
|
770
753
|
// Find the first key that we can remove that has no subscribers in our blocklist
|
|
771
754
|
const keyForRemoval = OnyxCache_1.default.getKeyForEviction();
|
|
772
755
|
if (!keyForRemoval) {
|
|
@@ -780,7 +763,7 @@ function evictStorageAndRetry(error, onyxMethod, ...args) {
|
|
|
780
763
|
Logger.logInfo(`Out of storage. Evicting least recently accessed key (${keyForRemoval}) and retrying.`);
|
|
781
764
|
reportStorageQuota();
|
|
782
765
|
// @ts-expect-error No overload matches this call.
|
|
783
|
-
return remove(keyForRemoval).then(() => onyxMethod(
|
|
766
|
+
return remove(keyForRemoval).then(() => onyxMethod(defaultParams, nextRetryAttempt));
|
|
784
767
|
}
|
|
785
768
|
/**
|
|
786
769
|
* Notifies subscribers and writes current value to cache
|
|
@@ -1050,17 +1033,192 @@ function updateSnapshots(data, mergeFn) {
|
|
|
1050
1033
|
});
|
|
1051
1034
|
return promises;
|
|
1052
1035
|
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Writes a value to our store with the given key.
|
|
1038
|
+
* Serves as core implementation for `Onyx.set()` public function, the difference being
|
|
1039
|
+
* that this internal function allows passing an additional `retryAttempt` parameter to retry on failure.
|
|
1040
|
+
*
|
|
1041
|
+
* @param params - set parameters
|
|
1042
|
+
* @param params.key ONYXKEY to set
|
|
1043
|
+
* @param params.value value to store
|
|
1044
|
+
* @param params.options optional configuration object
|
|
1045
|
+
* @param retryAttempt retry attempt
|
|
1046
|
+
*/
|
|
1047
|
+
function setWithRetry({ key, value, options }, retryAttempt) {
|
|
1048
|
+
// When we use Onyx.set to set a key we want to clear the current delta changes from Onyx.merge that were queued
|
|
1049
|
+
// before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
|
|
1050
|
+
if (OnyxUtils.hasPendingMergeForKey(key)) {
|
|
1051
|
+
delete OnyxUtils.getMergeQueue()[key];
|
|
1052
|
+
}
|
|
1053
|
+
if (skippableCollectionMemberIDs.size) {
|
|
1054
|
+
try {
|
|
1055
|
+
const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key);
|
|
1056
|
+
if (skippableCollectionMemberIDs.has(collectionMemberID)) {
|
|
1057
|
+
// The key is a skippable one, so we set the new value to null.
|
|
1058
|
+
// eslint-disable-next-line no-param-reassign
|
|
1059
|
+
value = null;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
catch (e) {
|
|
1063
|
+
// The key is not a collection one or something went wrong during split, so we proceed with the function's logic.
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
// Onyx.set will ignore `undefined` values as inputs, therefore we can return early.
|
|
1067
|
+
if (value === undefined) {
|
|
1068
|
+
return Promise.resolve();
|
|
1069
|
+
}
|
|
1070
|
+
const existingValue = OnyxCache_1.default.get(key, false);
|
|
1071
|
+
// If the existing value as well as the new value are null, we can return early.
|
|
1072
|
+
if (existingValue === undefined && value === null) {
|
|
1073
|
+
return Promise.resolve();
|
|
1074
|
+
}
|
|
1075
|
+
// Check if the value is compatible with the existing value in the storage
|
|
1076
|
+
const { isCompatible, existingValueType, newValueType } = utils_1.default.checkCompatibilityWithExistingValue(value, existingValue);
|
|
1077
|
+
if (!isCompatible) {
|
|
1078
|
+
Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'set', existingValueType, newValueType));
|
|
1079
|
+
return Promise.resolve();
|
|
1080
|
+
}
|
|
1081
|
+
// If the change is null, we can just delete the key.
|
|
1082
|
+
// Therefore, we don't need to further broadcast and update the value so we can return early.
|
|
1083
|
+
if (value === null) {
|
|
1084
|
+
OnyxUtils.remove(key);
|
|
1085
|
+
OnyxUtils.logKeyRemoved(OnyxUtils.METHOD.SET, key);
|
|
1086
|
+
return Promise.resolve();
|
|
1087
|
+
}
|
|
1088
|
+
const valueWithoutNestedNullValues = utils_1.default.removeNestedNullValues(value);
|
|
1089
|
+
const hasChanged = (options === null || options === void 0 ? void 0 : options.skipCacheCheck) ? true : OnyxCache_1.default.hasValueChanged(key, valueWithoutNestedNullValues);
|
|
1090
|
+
OnyxUtils.logKeyChanged(OnyxUtils.METHOD.SET, key, value, hasChanged);
|
|
1091
|
+
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
1092
|
+
const updatePromise = OnyxUtils.broadcastUpdate(key, valueWithoutNestedNullValues, hasChanged);
|
|
1093
|
+
// If the value has not changed and this isn't a retry attempt, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
1094
|
+
if (!hasChanged && !retryAttempt) {
|
|
1095
|
+
return updatePromise;
|
|
1096
|
+
}
|
|
1097
|
+
return storage_1.default.setItem(key, valueWithoutNestedNullValues)
|
|
1098
|
+
.catch((error) => OnyxUtils.retryOperation(error, setWithRetry, { key, value: valueWithoutNestedNullValues, options }, retryAttempt))
|
|
1099
|
+
.then(() => {
|
|
1100
|
+
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET, key, valueWithoutNestedNullValues);
|
|
1101
|
+
return updatePromise;
|
|
1102
|
+
});
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Sets multiple keys and values.
|
|
1106
|
+
* Serves as core implementation for `Onyx.multiSet()` public function, the difference being
|
|
1107
|
+
* that this internal function allows passing an additional `retryAttempt` parameter to retry on failure.
|
|
1108
|
+
*
|
|
1109
|
+
* @param data object keyed by ONYXKEYS and the values to set
|
|
1110
|
+
* @param retryAttempt retry attempt
|
|
1111
|
+
*/
|
|
1112
|
+
function multiSetWithRetry(data, retryAttempt) {
|
|
1113
|
+
let newData = data;
|
|
1114
|
+
if (skippableCollectionMemberIDs.size) {
|
|
1115
|
+
newData = Object.keys(newData).reduce((result, key) => {
|
|
1116
|
+
try {
|
|
1117
|
+
const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key);
|
|
1118
|
+
// If the collection member key is a skippable one we set its value to null.
|
|
1119
|
+
// eslint-disable-next-line no-param-reassign
|
|
1120
|
+
result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? newData[key] : null;
|
|
1121
|
+
}
|
|
1122
|
+
catch (_a) {
|
|
1123
|
+
// The key is not a collection one or something went wrong during split, so we assign the data to result anyway.
|
|
1124
|
+
// eslint-disable-next-line no-param-reassign
|
|
1125
|
+
result[key] = newData[key];
|
|
1126
|
+
}
|
|
1127
|
+
return result;
|
|
1128
|
+
}, {});
|
|
1129
|
+
}
|
|
1130
|
+
const keyValuePairsToSet = OnyxUtils.prepareKeyValuePairsForStorage(newData, true);
|
|
1131
|
+
const updatePromises = keyValuePairsToSet.map(([key, value]) => {
|
|
1132
|
+
// When we use multiSet to set a key we want to clear the current delta changes from Onyx.merge that were queued
|
|
1133
|
+
// before the value was set. If Onyx.merge is currently reading the old value from storage, it will then not apply the changes.
|
|
1134
|
+
if (OnyxUtils.hasPendingMergeForKey(key)) {
|
|
1135
|
+
delete OnyxUtils.getMergeQueue()[key];
|
|
1136
|
+
}
|
|
1137
|
+
// Update cache and optimistically inform subscribers on the next tick
|
|
1138
|
+
OnyxCache_1.default.set(key, value);
|
|
1139
|
+
return OnyxUtils.scheduleSubscriberUpdate(key, value);
|
|
1140
|
+
});
|
|
1141
|
+
return storage_1.default.multiSet(keyValuePairsToSet)
|
|
1142
|
+
.catch((error) => OnyxUtils.retryOperation(error, multiSetWithRetry, newData, retryAttempt))
|
|
1143
|
+
.then(() => {
|
|
1144
|
+
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MULTI_SET, undefined, newData);
|
|
1145
|
+
return Promise.all(updatePromises);
|
|
1146
|
+
})
|
|
1147
|
+
.then(() => undefined);
|
|
1148
|
+
}
|
|
1149
|
+
/**
|
|
1150
|
+
* Sets a collection by replacing all existing collection members with new values.
|
|
1151
|
+
* Any existing collection members not included in the new data will be removed.
|
|
1152
|
+
* Serves as core implementation for `Onyx.setCollection()` public function, the difference being
|
|
1153
|
+
* that this internal function allows passing an additional `retryAttempt` parameter to retry on failure.
|
|
1154
|
+
*
|
|
1155
|
+
* @param params - collection parameters
|
|
1156
|
+
* @param params.collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
1157
|
+
* @param params.collection Object collection keyed by individual collection member keys and values
|
|
1158
|
+
* @param retryAttempt retry attempt
|
|
1159
|
+
*/
|
|
1160
|
+
function setCollectionWithRetry({ collectionKey, collection }, retryAttempt) {
|
|
1161
|
+
let resultCollection = collection;
|
|
1162
|
+
let resultCollectionKeys = Object.keys(resultCollection);
|
|
1163
|
+
// Confirm all the collection keys belong to the same parent
|
|
1164
|
+
if (!OnyxUtils.doAllCollectionItemsBelongToSameParent(collectionKey, resultCollectionKeys)) {
|
|
1165
|
+
Logger.logAlert(`setCollection called with keys that do not belong to the same parent ${collectionKey}. Skipping this update.`);
|
|
1166
|
+
return Promise.resolve();
|
|
1167
|
+
}
|
|
1168
|
+
if (skippableCollectionMemberIDs.size) {
|
|
1169
|
+
resultCollection = resultCollectionKeys.reduce((result, key) => {
|
|
1170
|
+
try {
|
|
1171
|
+
const [, collectionMemberID] = OnyxUtils.splitCollectionMemberKey(key, collectionKey);
|
|
1172
|
+
// If the collection member key is a skippable one we set its value to null.
|
|
1173
|
+
// eslint-disable-next-line no-param-reassign
|
|
1174
|
+
result[key] = !skippableCollectionMemberIDs.has(collectionMemberID) ? resultCollection[key] : null;
|
|
1175
|
+
}
|
|
1176
|
+
catch (_a) {
|
|
1177
|
+
// Something went wrong during split, so we assign the data to result anyway.
|
|
1178
|
+
// eslint-disable-next-line no-param-reassign
|
|
1179
|
+
result[key] = resultCollection[key];
|
|
1180
|
+
}
|
|
1181
|
+
return result;
|
|
1182
|
+
}, {});
|
|
1183
|
+
}
|
|
1184
|
+
resultCollectionKeys = Object.keys(resultCollection);
|
|
1185
|
+
return OnyxUtils.getAllKeys().then((persistedKeys) => {
|
|
1186
|
+
const mutableCollection = Object.assign({}, resultCollection);
|
|
1187
|
+
persistedKeys.forEach((key) => {
|
|
1188
|
+
if (!key.startsWith(collectionKey)) {
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1191
|
+
if (resultCollectionKeys.includes(key)) {
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
mutableCollection[key] = null;
|
|
1195
|
+
});
|
|
1196
|
+
const keyValuePairs = OnyxUtils.prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
|
|
1197
|
+
const previousCollection = OnyxUtils.getCachedCollection(collectionKey);
|
|
1198
|
+
keyValuePairs.forEach(([key, value]) => OnyxCache_1.default.set(key, value));
|
|
1199
|
+
const updatePromise = OnyxUtils.scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
|
|
1200
|
+
return storage_1.default.multiSet(keyValuePairs)
|
|
1201
|
+
.catch((error) => OnyxUtils.retryOperation(error, setCollectionWithRetry, { collectionKey, collection }, retryAttempt))
|
|
1202
|
+
.then(() => {
|
|
1203
|
+
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1204
|
+
return updatePromise;
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1053
1208
|
/**
|
|
1054
1209
|
* Merges a collection based on their keys.
|
|
1055
1210
|
* Serves as core implementation for `Onyx.mergeCollection()` public function, the difference being
|
|
1056
|
-
* that this internal function allows passing an additional `mergeReplaceNullPatches` parameter.
|
|
1211
|
+
* that this internal function allows passing an additional `mergeReplaceNullPatches` parameter and retries on failure.
|
|
1057
1212
|
*
|
|
1058
|
-
* @param
|
|
1059
|
-
* @param
|
|
1060
|
-
* @param
|
|
1213
|
+
* @param params - mergeCollection parameters
|
|
1214
|
+
* @param params.collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
1215
|
+
* @param params.collection Object collection keyed by individual collection member keys and values
|
|
1216
|
+
* @param params.mergeReplaceNullPatches Record where the key is a collection member key and the value is a list of
|
|
1061
1217
|
* tuples that we'll use to replace the nested objects of that collection member record with something else.
|
|
1218
|
+
* @param params.isProcessingCollectionUpdate whether this is part of a collection update operation.
|
|
1219
|
+
* @param retryAttempt retry attempt
|
|
1062
1220
|
*/
|
|
1063
|
-
function mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullPatches, isProcessingCollectionUpdate = false) {
|
|
1221
|
+
function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNullPatches, isProcessingCollectionUpdate = false }, retryAttempt) {
|
|
1064
1222
|
if (!isValidNonEmptyCollectionForMerge(collection)) {
|
|
1065
1223
|
Logger.logInfo('mergeCollection() called with invalid or empty value. Skipping this update.');
|
|
1066
1224
|
return Promise.resolve();
|
|
@@ -1139,21 +1297,13 @@ function mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullP
|
|
|
1139
1297
|
// finalMergedCollection contains all the keys that were merged, without the keys of incompatible updates
|
|
1140
1298
|
const finalMergedCollection = Object.assign(Object.assign({}, existingKeyCollection), newCollection);
|
|
1141
1299
|
// Prefill cache if necessary by calling get() on any existing keys and then merge original data to cache
|
|
1142
|
-
// and update all subscribers
|
|
1300
|
+
// and update all subscribers
|
|
1143
1301
|
const promiseUpdate = previousCollectionPromise.then((previousCollection) => {
|
|
1144
|
-
// Capture the original cached values before merging
|
|
1145
|
-
const originalCachedValues = {};
|
|
1146
|
-
Object.keys(finalMergedCollection).forEach((key) => {
|
|
1147
|
-
originalCachedValues[key] = OnyxCache_1.default.get(key, false);
|
|
1148
|
-
});
|
|
1149
|
-
// Then merge all the data into cache as normal
|
|
1150
1302
|
OnyxCache_1.default.merge(finalMergedCollection);
|
|
1151
|
-
|
|
1152
|
-
const preservedCollection = preserveCollectionReferencesAfterMerge(finalMergedCollection, originalCachedValues);
|
|
1153
|
-
return scheduleNotifyCollectionSubscribers(collectionKey, preservedCollection, previousCollection);
|
|
1303
|
+
return scheduleNotifyCollectionSubscribers(collectionKey, finalMergedCollection, previousCollection);
|
|
1154
1304
|
});
|
|
1155
1305
|
return Promise.all(promises)
|
|
1156
|
-
.catch((error) =>
|
|
1306
|
+
.catch((error) => retryOperation(error, mergeCollectionWithPatches, { collectionKey, collection: resultCollection, mergeReplaceNullPatches, isProcessingCollectionUpdate }, retryAttempt))
|
|
1157
1307
|
.then(() => {
|
|
1158
1308
|
sendActionToDevTools(METHOD.MERGE_COLLECTION, undefined, resultCollection);
|
|
1159
1309
|
return promiseUpdate;
|
|
@@ -1164,11 +1314,14 @@ function mergeCollectionWithPatches(collectionKey, collection, mergeReplaceNullP
|
|
|
1164
1314
|
/**
|
|
1165
1315
|
* Sets keys in a collection by replacing all targeted collection members with new values.
|
|
1166
1316
|
* Any existing collection members not included in the new data will not be removed.
|
|
1317
|
+
* Retries on failure.
|
|
1167
1318
|
*
|
|
1168
|
-
* @param
|
|
1169
|
-
* @param
|
|
1319
|
+
* @param params - collection parameters
|
|
1320
|
+
* @param params.collectionKey e.g. `ONYXKEYS.COLLECTION.REPORT`
|
|
1321
|
+
* @param params.collection Object collection keyed by individual collection member keys and values
|
|
1322
|
+
* @param retryAttempt retry attempt
|
|
1170
1323
|
*/
|
|
1171
|
-
function partialSetCollection(collectionKey, collection) {
|
|
1324
|
+
function partialSetCollection({ collectionKey, collection }, retryAttempt) {
|
|
1172
1325
|
let resultCollection = collection;
|
|
1173
1326
|
let resultCollectionKeys = Object.keys(resultCollection);
|
|
1174
1327
|
// Confirm all the collection keys belong to the same parent
|
|
@@ -1198,11 +1351,10 @@ function partialSetCollection(collectionKey, collection) {
|
|
|
1198
1351
|
const existingKeys = resultCollectionKeys.filter((key) => persistedKeys.has(key));
|
|
1199
1352
|
const previousCollection = getCachedCollection(collectionKey, existingKeys);
|
|
1200
1353
|
const keyValuePairs = prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
|
|
1201
|
-
|
|
1202
|
-
const
|
|
1203
|
-
const updatePromise = scheduleNotifyCollectionSubscribers(collectionKey, preservedCollection, previousCollection);
|
|
1354
|
+
keyValuePairs.forEach(([key, value]) => OnyxCache_1.default.set(key, value));
|
|
1355
|
+
const updatePromise = scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
|
|
1204
1356
|
return storage_1.default.multiSet(keyValuePairs)
|
|
1205
|
-
.catch((error) =>
|
|
1357
|
+
.catch((error) => retryOperation(error, partialSetCollection, { collectionKey, collection }, retryAttempt))
|
|
1206
1358
|
.then(() => {
|
|
1207
1359
|
sendActionToDevTools(METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1208
1360
|
return updatePromise;
|
|
@@ -1254,7 +1406,7 @@ const OnyxUtils = {
|
|
|
1254
1406
|
scheduleNotifyCollectionSubscribers,
|
|
1255
1407
|
remove,
|
|
1256
1408
|
reportStorageQuota,
|
|
1257
|
-
|
|
1409
|
+
retryOperation,
|
|
1258
1410
|
broadcastUpdate,
|
|
1259
1411
|
hasPendingMergeForKey,
|
|
1260
1412
|
prepareKeyValuePairsForStorage,
|
|
@@ -1277,10 +1429,11 @@ const OnyxUtils = {
|
|
|
1277
1429
|
updateSnapshots,
|
|
1278
1430
|
mergeCollectionWithPatches,
|
|
1279
1431
|
partialSetCollection,
|
|
1280
|
-
preserveCollectionReferences,
|
|
1281
|
-
preserveCollectionReferencesAfterMerge,
|
|
1282
1432
|
logKeyChanged,
|
|
1283
1433
|
logKeyRemoved,
|
|
1434
|
+
setWithRetry,
|
|
1435
|
+
multiSetWithRetry,
|
|
1436
|
+
setCollectionWithRetry,
|
|
1284
1437
|
};
|
|
1285
1438
|
GlobalSettings.addGlobalSettingsChangeListener(({ enablePerformanceMetrics }) => {
|
|
1286
1439
|
if (!enablePerformanceMetrics) {
|
|
@@ -1314,7 +1467,7 @@ GlobalSettings.addGlobalSettingsChangeListener(({ enablePerformanceMetrics }) =>
|
|
|
1314
1467
|
// @ts-expect-error Reassign
|
|
1315
1468
|
reportStorageQuota = (0, metrics_1.default)(reportStorageQuota, 'OnyxUtils.reportStorageQuota');
|
|
1316
1469
|
// @ts-expect-error Complex type signature
|
|
1317
|
-
|
|
1470
|
+
retryOperation = (0, metrics_1.default)(retryOperation, 'OnyxUtils.retryOperation');
|
|
1318
1471
|
// @ts-expect-error Reassign
|
|
1319
1472
|
broadcastUpdate = (0, metrics_1.default)(broadcastUpdate, 'OnyxUtils.broadcastUpdate');
|
|
1320
1473
|
// @ts-expect-error Reassign
|
|
@@ -1325,5 +1478,11 @@ GlobalSettings.addGlobalSettingsChangeListener(({ enablePerformanceMetrics }) =>
|
|
|
1325
1478
|
tupleGet = (0, metrics_1.default)(tupleGet, 'OnyxUtils.tupleGet');
|
|
1326
1479
|
// @ts-expect-error Reassign
|
|
1327
1480
|
subscribeToKey = (0, metrics_1.default)(subscribeToKey, 'OnyxUtils.subscribeToKey');
|
|
1481
|
+
// @ts-expect-error Reassign
|
|
1482
|
+
setWithRetry = (0, metrics_1.default)(setWithRetry, 'OnyxUtils.setWithRetry');
|
|
1483
|
+
// @ts-expect-error Reassign
|
|
1484
|
+
multiSetWithRetry = (0, metrics_1.default)(multiSetWithRetry, 'OnyxUtils.multiSetWithRetry');
|
|
1485
|
+
// @ts-expect-error Reassign
|
|
1486
|
+
setCollectionWithRetry = (0, metrics_1.default)(setCollectionWithRetry, 'OnyxUtils.setCollectionWithRetry');
|
|
1328
1487
|
});
|
|
1329
1488
|
exports.default = OnyxUtils;
|
package/dist/types.d.ts
CHANGED
|
@@ -311,6 +311,22 @@ type SetOptions = {
|
|
|
311
311
|
/** Skip the deep equality check against the cached value. Improves performance for large objects. */
|
|
312
312
|
skipCacheCheck?: boolean;
|
|
313
313
|
};
|
|
314
|
+
type SetParams<TKey extends OnyxKey> = {
|
|
315
|
+
key: TKey;
|
|
316
|
+
value: OnyxSetInput<TKey>;
|
|
317
|
+
options?: SetOptions;
|
|
318
|
+
};
|
|
319
|
+
type SetCollectionParams<TKey extends CollectionKeyBase> = {
|
|
320
|
+
collectionKey: TKey;
|
|
321
|
+
collection: OnyxSetCollectionInput<TKey>;
|
|
322
|
+
};
|
|
323
|
+
type MergeCollectionWithPatchesParams<TKey extends CollectionKeyBase> = {
|
|
324
|
+
collectionKey: TKey;
|
|
325
|
+
collection: OnyxMergeCollectionInput<TKey>;
|
|
326
|
+
mergeReplaceNullPatches?: MultiMergeReplaceNullPatches;
|
|
327
|
+
isProcessingCollectionUpdate?: boolean;
|
|
328
|
+
};
|
|
329
|
+
type RetriableOnyxOperation = typeof OnyxUtils.setWithRetry | typeof OnyxUtils.multiSetWithRetry | typeof OnyxUtils.setCollectionWithRetry | typeof OnyxUtils.mergeCollectionWithPatches | typeof OnyxUtils.partialSetCollection;
|
|
314
330
|
/**
|
|
315
331
|
* Represents the options used in `Onyx.init()` method.
|
|
316
332
|
*/
|
|
@@ -368,4 +384,4 @@ type MixedOperationsQueue = {
|
|
|
368
384
|
mergeReplaceNullPatches: MultiMergeReplaceNullPatches;
|
|
369
385
|
set: OnyxInputKeyValueMapping;
|
|
370
386
|
};
|
|
371
|
-
export type { BaseConnectOptions, Collection, CollectionConnectCallback, CollectionConnectOptions, CollectionKey, CollectionKeyBase, ConnectOptions, CustomTypeOptions, DeepRecord, DefaultConnectCallback, DefaultConnectOptions, ExtractOnyxCollectionValue, GenericFunction, InitOptions, Key, KeyValueMapping, CallbackToStateMapping, NonNull, NonUndefined, OnyxInputKeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxSetCollectionInput, OnyxMethod, OnyxMethodMap, OnyxUpdate, OnyxValue, Selector, SetOptions, MultiMergeReplaceNullPatches, MixedOperationsQueue, };
|
|
387
|
+
export type { BaseConnectOptions, Collection, CollectionConnectCallback, CollectionConnectOptions, CollectionKey, CollectionKeyBase, ConnectOptions, CustomTypeOptions, DeepRecord, DefaultConnectCallback, DefaultConnectOptions, ExtractOnyxCollectionValue, GenericFunction, InitOptions, Key, KeyValueMapping, CallbackToStateMapping, NonNull, NonUndefined, OnyxInputKeyValueMapping, NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxInputValue, OnyxCollectionInputValue, OnyxInput, OnyxSetInput, OnyxMultiSetInput, OnyxMergeInput, OnyxMergeCollectionInput, OnyxSetCollectionInput, OnyxMethod, OnyxMethodMap, OnyxUpdate, OnyxValue, Selector, SetOptions, SetParams, SetCollectionParams, MergeCollectionWithPatchesParams, MultiMergeReplaceNullPatches, MixedOperationsQueue, RetriableOnyxOperation, };
|