react-native-onyx 3.0.84 → 3.0.86
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/OnyxCache.d.ts
CHANGED
|
@@ -122,8 +122,10 @@ declare class OnyxCache {
|
|
|
122
122
|
addEvictableKeysToRecentlyAccessedList(isCollectionKeyFn: (key: OnyxKey) => boolean, getAllKeysFn: () => Promise<Set<OnyxKey>>): Promise<void>;
|
|
123
123
|
/**
|
|
124
124
|
* Finds the least recently accessed key that can be safely evicted from storage.
|
|
125
|
+
* `excludeKeys` skips keys that must not be evicted (e.g. the in-flight write's own keys,
|
|
126
|
+
* whose cache value is the merge base the retry depends on).
|
|
125
127
|
*/
|
|
126
|
-
getKeyForEviction(): OnyxKey | undefined;
|
|
128
|
+
getKeyForEviction(excludeKeys?: Set<OnyxKey>): OnyxKey | undefined;
|
|
127
129
|
/**
|
|
128
130
|
* Set the collection keys for optimized storage
|
|
129
131
|
*/
|
package/dist/OnyxCache.js
CHANGED
|
@@ -265,11 +265,18 @@ class OnyxCache {
|
|
|
265
265
|
}
|
|
266
266
|
/**
|
|
267
267
|
* Finds the least recently accessed key that can be safely evicted from storage.
|
|
268
|
+
* `excludeKeys` skips keys that must not be evicted (e.g. the in-flight write's own keys,
|
|
269
|
+
* whose cache value is the merge base the retry depends on).
|
|
268
270
|
*/
|
|
269
|
-
getKeyForEviction() {
|
|
271
|
+
getKeyForEviction(excludeKeys) {
|
|
270
272
|
// recentlyAccessedKeys is ordered from least to most recently accessed,
|
|
271
|
-
// so the first
|
|
272
|
-
|
|
273
|
+
// so the first non-excluded key is the best candidate for eviction.
|
|
274
|
+
for (const key of this.recentlyAccessedKeys) {
|
|
275
|
+
if (!(excludeKeys === null || excludeKeys === void 0 ? void 0 : excludeKeys.has(key))) {
|
|
276
|
+
return key;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return undefined;
|
|
273
280
|
}
|
|
274
281
|
/**
|
|
275
282
|
* Set the collection keys for optimized storage
|
package/dist/OnyxUtils.d.ts
CHANGED
|
@@ -140,7 +140,7 @@ declare function reportStorageQuota(error?: Error): Promise<void>;
|
|
|
140
140
|
* - Non-retriable errors: logs an alert and resolves without retrying
|
|
141
141
|
* - Other errors: retries the operation
|
|
142
142
|
*/
|
|
143
|
-
declare function retryOperation<TMethod extends RetriableOnyxOperation>(error: Error, onyxMethod: TMethod, defaultParams: Parameters<TMethod>[0], retryAttempt: number | undefined): Promise<void>;
|
|
143
|
+
declare function retryOperation<TMethod extends RetriableOnyxOperation>(error: Error, onyxMethod: TMethod, defaultParams: Parameters<TMethod>[0], retryAttempt: number | undefined, inFlightKeys?: Set<OnyxKey>): Promise<void>;
|
|
144
144
|
/**
|
|
145
145
|
* Notifies subscribers and writes current value to cache
|
|
146
146
|
*/
|
package/dist/OnyxUtils.js
CHANGED
|
@@ -679,7 +679,7 @@ function reportStorageQuota(error) {
|
|
|
679
679
|
* - Non-retriable errors: logs an alert and resolves without retrying
|
|
680
680
|
* - Other errors: retries the operation
|
|
681
681
|
*/
|
|
682
|
-
function retryOperation(error, onyxMethod, defaultParams, retryAttempt) {
|
|
682
|
+
function retryOperation(error, onyxMethod, defaultParams, retryAttempt, inFlightKeys) {
|
|
683
683
|
var _a, _b, _c, _d;
|
|
684
684
|
const currentRetryAttempt = retryAttempt !== null && retryAttempt !== void 0 ? retryAttempt : 0;
|
|
685
685
|
const nextRetryAttempt = currentRetryAttempt + 1;
|
|
@@ -704,8 +704,10 @@ function retryOperation(error, onyxMethod, defaultParams, retryAttempt) {
|
|
|
704
704
|
// @ts-expect-error No overload matches this call.
|
|
705
705
|
return onyxMethod(defaultParams, nextRetryAttempt);
|
|
706
706
|
}
|
|
707
|
-
// Find the least recently accessed evictable key that we can remove
|
|
708
|
-
|
|
707
|
+
// Find the least recently accessed evictable key that we can remove. Never evict an in-flight
|
|
708
|
+
// key — its cache value is the merge base this retry depends on, so dropping it would truncate
|
|
709
|
+
// the write to just the delta and diverge cache from storage.
|
|
710
|
+
const keyForRemoval = OnyxCache_1.default.getKeyForEviction(inFlightKeys);
|
|
709
711
|
if (!keyForRemoval) {
|
|
710
712
|
// If we have no acceptable keys to remove then we are possibly trying to save mission critical data. If this is the case,
|
|
711
713
|
// then we should stop retrying as there is not much the user can do to fix this. Instead of getting them stuck in an infinite loop we
|
|
@@ -1177,21 +1179,29 @@ function multiSetWithRetry(data, retryAttempt) {
|
|
|
1177
1179
|
// so re-entrant callbacks (e.g. Onyx.set inside a callback) see consistent cache
|
|
1178
1180
|
// and subscriber state, matching the original per-key notification semantics.
|
|
1179
1181
|
OnyxCache_1.default.set(key, value);
|
|
1180
|
-
|
|
1182
|
+
// Skip subscriber notification on retry — already notified on attempt 0.
|
|
1183
|
+
// waitForCollectionCallback subscribers re-fire on every keyChanged by contract.
|
|
1184
|
+
if (!retryAttempt) {
|
|
1185
|
+
keyChanged(key, value);
|
|
1186
|
+
}
|
|
1181
1187
|
}
|
|
1182
1188
|
}
|
|
1183
1189
|
// One keysChanged() per collection — fires each collection-level subscriber once and lets
|
|
1184
1190
|
// keysChanged() internally decide which individual member subscribers need notification.
|
|
1185
|
-
|
|
1186
|
-
|
|
1191
|
+
// Skip on retry — already notified on attempt 0 (see same-reason comment above).
|
|
1192
|
+
if (!retryAttempt) {
|
|
1193
|
+
for (const [collectionKey, batch] of collectionBatches) {
|
|
1194
|
+
keysChanged(collectionKey, batch.partial, batch.previous);
|
|
1195
|
+
}
|
|
1187
1196
|
}
|
|
1188
1197
|
const keyValuePairsToStore = keyValuePairsToSet.filter((keyValuePair) => {
|
|
1189
1198
|
const [key] = keyValuePair;
|
|
1190
1199
|
// Filter out the RAM-only key value pairs, as they should not be saved to storage
|
|
1191
1200
|
return !OnyxKeys_1.default.isRamOnlyKey(key);
|
|
1192
1201
|
});
|
|
1202
|
+
const inFlightKeys = new Set(keyValuePairsToSet.map(([key]) => key));
|
|
1193
1203
|
return storage_1.default.multiSet(keyValuePairsToStore)
|
|
1194
|
-
.catch((error) => OnyxUtils.retryOperation(error, multiSetWithRetry, newData, retryAttempt))
|
|
1204
|
+
.catch((error) => OnyxUtils.retryOperation(error, multiSetWithRetry, newData, retryAttempt, inFlightKeys))
|
|
1195
1205
|
.then(() => {
|
|
1196
1206
|
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MULTI_SET, undefined, newData);
|
|
1197
1207
|
});
|
|
@@ -1247,14 +1257,19 @@ function setCollectionWithRetry({ collectionKey, collection }, retryAttempt) {
|
|
|
1247
1257
|
const previousCollection = OnyxUtils.getCachedCollection(collectionKey);
|
|
1248
1258
|
for (const [key, value] of keyValuePairs)
|
|
1249
1259
|
OnyxCache_1.default.set(key, value);
|
|
1250
|
-
|
|
1260
|
+
// Skip subscriber notification on retry — already notified on attempt 0.
|
|
1261
|
+
// waitForCollectionCallback subscribers re-fire on every keysChanged by contract.
|
|
1262
|
+
if (!retryAttempt) {
|
|
1263
|
+
keysChanged(collectionKey, mutableCollection, previousCollection);
|
|
1264
|
+
}
|
|
1251
1265
|
// RAM-only keys are not supposed to be saved to storage
|
|
1252
1266
|
if (OnyxKeys_1.default.isRamOnlyKey(collectionKey)) {
|
|
1253
1267
|
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1254
1268
|
return;
|
|
1255
1269
|
}
|
|
1270
|
+
const inFlightKeys = new Set(keyValuePairs.map(([key]) => key));
|
|
1256
1271
|
return storage_1.default.multiSet(keyValuePairs)
|
|
1257
|
-
.catch((error) => OnyxUtils.retryOperation(error, setCollectionWithRetry, { collectionKey, collection }, retryAttempt))
|
|
1272
|
+
.catch((error) => OnyxUtils.retryOperation(error, setCollectionWithRetry, { collectionKey, collection }, retryAttempt, inFlightKeys))
|
|
1258
1273
|
.then(() => {
|
|
1259
1274
|
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1260
1275
|
});
|
|
@@ -1364,10 +1379,15 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
|
|
|
1364
1379
|
// write fails.
|
|
1365
1380
|
const previousCollection = getCachedCollection(collectionKey, existingKeys);
|
|
1366
1381
|
OnyxCache_1.default.merge(finalMergedCollection);
|
|
1367
|
-
|
|
1382
|
+
// Skip subscriber notification on retry — already notified on attempt 0.
|
|
1383
|
+
// waitForCollectionCallback subscribers re-fire on every keysChanged by contract.
|
|
1384
|
+
if (!retryAttempt) {
|
|
1385
|
+
keysChanged(collectionKey, finalMergedCollection, previousCollection);
|
|
1386
|
+
}
|
|
1368
1387
|
const promises = [];
|
|
1369
|
-
// New keys
|
|
1370
|
-
//
|
|
1388
|
+
// New keys go through multiSet and existing keys through multiMerge. multiMerge on a
|
|
1389
|
+
// missing key stores the value just like multiSet across all backends; splitting them lets
|
|
1390
|
+
// multiSet strip nested nulls (the merge layer keeps them to delete nested storage keys).
|
|
1371
1391
|
// We can skip this step for RAM-only keys as they should never be saved to storage
|
|
1372
1392
|
if (!OnyxKeys_1.default.isRamOnlyKey(collectionKey) && keyValuePairsForExistingCollection.length > 0) {
|
|
1373
1393
|
promises.push(storage_1.default.multiMerge(keyValuePairsForExistingCollection));
|
|
@@ -1376,8 +1396,9 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
|
|
|
1376
1396
|
if (!OnyxKeys_1.default.isRamOnlyKey(collectionKey) && keyValuePairsForNewCollection.length > 0) {
|
|
1377
1397
|
promises.push(storage_1.default.multiSet(keyValuePairsForNewCollection));
|
|
1378
1398
|
}
|
|
1399
|
+
const inFlightKeys = new Set(Object.keys(finalMergedCollection));
|
|
1379
1400
|
return Promise.all(promises)
|
|
1380
|
-
.catch((error) => retryOperation(error, mergeCollectionWithPatches, { collectionKey, collection: resultCollection, mergeReplaceNullPatches, isProcessingCollectionUpdate }, retryAttempt))
|
|
1401
|
+
.catch((error) => retryOperation(error, mergeCollectionWithPatches, { collectionKey, collection: resultCollection, mergeReplaceNullPatches, isProcessingCollectionUpdate }, retryAttempt, inFlightKeys))
|
|
1381
1402
|
.then(() => {
|
|
1382
1403
|
sendActionToDevTools(METHOD.MERGE_COLLECTION, undefined, resultCollection);
|
|
1383
1404
|
});
|
|
@@ -1427,13 +1448,18 @@ function partialSetCollection({ collectionKey, collection }, retryAttempt) {
|
|
|
1427
1448
|
const keyValuePairs = prepareKeyValuePairsForStorage(mutableCollection, true, undefined, true);
|
|
1428
1449
|
for (const [key, value] of keyValuePairs)
|
|
1429
1450
|
OnyxCache_1.default.set(key, value);
|
|
1430
|
-
|
|
1451
|
+
// Skip subscriber notification on retry — already notified on attempt 0.
|
|
1452
|
+
// waitForCollectionCallback subscribers re-fire on every keysChanged by contract.
|
|
1453
|
+
if (!retryAttempt) {
|
|
1454
|
+
keysChanged(collectionKey, mutableCollection, previousCollection);
|
|
1455
|
+
}
|
|
1431
1456
|
if (OnyxKeys_1.default.isRamOnlyKey(collectionKey)) {
|
|
1432
1457
|
sendActionToDevTools(METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1433
1458
|
return;
|
|
1434
1459
|
}
|
|
1460
|
+
const inFlightKeys = new Set(keyValuePairs.map(([key]) => key));
|
|
1435
1461
|
return storage_1.default.multiSet(keyValuePairs)
|
|
1436
|
-
.catch((error) => retryOperation(error, partialSetCollection, { collectionKey, collection }, retryAttempt))
|
|
1462
|
+
.catch((error) => retryOperation(error, partialSetCollection, { collectionKey, collection }, retryAttempt, inFlightKeys))
|
|
1437
1463
|
.then(() => {
|
|
1438
1464
|
sendActionToDevTools(METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1439
1465
|
});
|
|
@@ -41,6 +41,17 @@ const utils_1 = __importDefault(require("../../../utils"));
|
|
|
41
41
|
const createStore_1 = __importDefault(require("./createStore"));
|
|
42
42
|
const DB_NAME = 'OnyxDB';
|
|
43
43
|
const STORE_NAME = 'keyvaluepairs';
|
|
44
|
+
/**
|
|
45
|
+
* Awaits an IndexedDB write transaction. idb-keyval's promisifyRequest rejects with
|
|
46
|
+
* `transaction.error`, which is `null` for an abort not caused by its own request
|
|
47
|
+
* (connection close / versionchange / a sibling transaction aborting). Normalize that
|
|
48
|
+
* `null` into a tagged AbortError.
|
|
49
|
+
*/
|
|
50
|
+
function promisifyWriteTransaction(transaction) {
|
|
51
|
+
return IDB.promisifyRequest(transaction).catch((error) => {
|
|
52
|
+
throw error !== null && error !== void 0 ? error : new DOMException('IDB write transaction aborted without an error', 'AbortError');
|
|
53
|
+
});
|
|
54
|
+
}
|
|
44
55
|
const provider = {
|
|
45
56
|
// We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB
|
|
46
57
|
// which might not be available in certain environments that load the bundle (e.g. electron main process).
|
|
@@ -66,7 +77,13 @@ const provider = {
|
|
|
66
77
|
if (value === null) {
|
|
67
78
|
return provider.removeItem(key);
|
|
68
79
|
}
|
|
69
|
-
|
|
80
|
+
// Drive the write through the manual store transaction so promisifyWriteTransaction can
|
|
81
|
+
// normalize a null abort error — idb-keyval's IDB.set() awaits the raw transaction and
|
|
82
|
+
// would propagate the unclassifiable "Error: null".
|
|
83
|
+
return provider.store('readwrite', (store) => {
|
|
84
|
+
store.put(value, key);
|
|
85
|
+
return promisifyWriteTransaction(store.transaction);
|
|
86
|
+
});
|
|
70
87
|
},
|
|
71
88
|
multiGet(keysParam) {
|
|
72
89
|
if (!provider.store) {
|
|
@@ -95,7 +112,7 @@ const provider = {
|
|
|
95
112
|
store.put(newValue, key);
|
|
96
113
|
}
|
|
97
114
|
}
|
|
98
|
-
return
|
|
115
|
+
return promisifyWriteTransaction(store.transaction);
|
|
99
116
|
});
|
|
100
117
|
});
|
|
101
118
|
},
|
|
@@ -116,7 +133,7 @@ const provider = {
|
|
|
116
133
|
store.put(value, key);
|
|
117
134
|
}
|
|
118
135
|
}
|
|
119
|
-
return
|
|
136
|
+
return promisifyWriteTransaction(store.transaction);
|
|
120
137
|
});
|
|
121
138
|
},
|
|
122
139
|
clear() {
|
|
@@ -149,13 +166,21 @@ const provider = {
|
|
|
149
166
|
if (!provider.store) {
|
|
150
167
|
throw new Error('Store not initialized!');
|
|
151
168
|
}
|
|
152
|
-
return
|
|
169
|
+
return provider.store('readwrite', (store) => {
|
|
170
|
+
store.delete(key);
|
|
171
|
+
return promisifyWriteTransaction(store.transaction);
|
|
172
|
+
});
|
|
153
173
|
},
|
|
154
174
|
removeItems(keysParam) {
|
|
155
175
|
if (!provider.store) {
|
|
156
176
|
throw new Error('Store not initialized!');
|
|
157
177
|
}
|
|
158
|
-
return
|
|
178
|
+
return provider.store('readwrite', (store) => {
|
|
179
|
+
for (const key of keysParam) {
|
|
180
|
+
store.delete(key);
|
|
181
|
+
}
|
|
182
|
+
return promisifyWriteTransaction(store.transaction);
|
|
183
|
+
});
|
|
159
184
|
},
|
|
160
185
|
getDatabaseSize() {
|
|
161
186
|
if (!provider.store) {
|