react-native-onyx 3.0.33 → 3.0.35
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/README.md +19 -0
- package/dist/Onyx.d.ts +1 -1
- package/dist/Onyx.js +263 -257
- package/dist/OnyxCache.d.ts +10 -0
- package/dist/OnyxCache.js +15 -1
- package/dist/OnyxMerge/index.js +3 -1
- package/dist/OnyxMerge/index.native.js +3 -1
- package/dist/OnyxUtils.d.ts +29 -0
- package/dist/OnyxUtils.js +71 -3
- package/dist/createDeferredTask.d.ts +2 -1
- package/dist/createDeferredTask.js +9 -4
- package/dist/types.d.ts +4 -0
- package/dist/useOnyx.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -460,6 +460,25 @@ Onyx.init({
|
|
|
460
460
|
});
|
|
461
461
|
```
|
|
462
462
|
|
|
463
|
+
### Using RAM-only keys
|
|
464
|
+
|
|
465
|
+
You can choose not to save certain keys on disk and keep them RAM-only, that way their values will reset with each session. You just have to pass an array of `ramOnlyKeys` to the `Onyx.init` method. You can mark entire collections as RAM-only by including the collection key (e.g., `ONYXKEYS.COLLECTION.TEMP_DATA`). This will make all members of that collection RAM-only. Individual collection member keys cannot be selectively marked as RAM-only.
|
|
466
|
+
|
|
467
|
+
```javascript
|
|
468
|
+
import Onyx from 'react-native-onyx';
|
|
469
|
+
|
|
470
|
+
Onyx.init({
|
|
471
|
+
keys: ONYXKEYS,
|
|
472
|
+
ramOnlyKeys: [
|
|
473
|
+
ONYXKEYS.RAM_ONLY_KEY_1,
|
|
474
|
+
ONYXKEYS.RAM_ONLY_KEY_2,
|
|
475
|
+
ONYXKEYS.COLLECTION.TEMP_DATA,
|
|
476
|
+
],
|
|
477
|
+
});
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
> Note: RAM-only keys still consume memory and will remain in cache until explicitly cleared or until Onyx.clear() is called. Use them judiciously for truly ephemeral data.
|
|
481
|
+
|
|
463
482
|
### Usage
|
|
464
483
|
|
|
465
484
|
The extension interface is pretty simple, on the left sidebar you can see all the updates made to the local storage, in ascending order, and on the right pane you can see the whole the current state, payload of an action and the diff between the previous state and the current state after the action was triggered.
|
package/dist/Onyx.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import * as Logger from './Logger';
|
|
|
2
2
|
import type { CollectionKeyBase, ConnectOptions, InitOptions, OnyxKey, OnyxMergeCollectionInput, OnyxSetCollectionInput, OnyxMergeInput, OnyxMultiSetInput, OnyxSetInput, OnyxUpdate, SetOptions } from './types';
|
|
3
3
|
import type { Connection } from './OnyxConnectionManager';
|
|
4
4
|
/** Initialize the store with actions and listening for storage events */
|
|
5
|
-
declare function init({ keys, initialKeyStates, evictableKeys, maxCachedKeysCount, shouldSyncMultipleInstances, enablePerformanceMetrics, enableDevTools, skippableCollectionMemberIDs, snapshotMergeKeys, }: InitOptions): void;
|
|
5
|
+
declare function init({ keys, initialKeyStates, evictableKeys, maxCachedKeysCount, shouldSyncMultipleInstances, enablePerformanceMetrics, enableDevTools, skippableCollectionMemberIDs, ramOnlyKeys, snapshotMergeKeys, }: InitOptions): void;
|
|
6
6
|
/**
|
|
7
7
|
* Connects to an Onyx key given the options passed and listens to its changes.
|
|
8
8
|
* This method will be deprecated soon. Please use `Onyx.connectWithoutView()` instead.
|
package/dist/Onyx.js
CHANGED
|
@@ -48,7 +48,7 @@ const GlobalSettings = __importStar(require("./GlobalSettings"));
|
|
|
48
48
|
const metrics_1 = __importDefault(require("./metrics"));
|
|
49
49
|
const OnyxMerge_1 = __importDefault(require("./OnyxMerge"));
|
|
50
50
|
/** Initialize the store with actions and listening for storage events */
|
|
51
|
-
function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = !!global.localStorage, enablePerformanceMetrics = false, enableDevTools = true, skippableCollectionMemberIDs = [], snapshotMergeKeys = [], }) {
|
|
51
|
+
function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedKeysCount = 1000, shouldSyncMultipleInstances = !!global.localStorage, enablePerformanceMetrics = false, enableDevTools = true, skippableCollectionMemberIDs = [], ramOnlyKeys = [], snapshotMergeKeys = [], }) {
|
|
52
52
|
var _a;
|
|
53
53
|
if (enablePerformanceMetrics) {
|
|
54
54
|
GlobalSettings.setPerformanceMetricsEnabled(true);
|
|
@@ -58,6 +58,7 @@ function init({ keys = {}, initialKeyStates = {}, evictableKeys = [], maxCachedK
|
|
|
58
58
|
storage_1.default.init();
|
|
59
59
|
OnyxUtils_1.default.setSkippableCollectionMemberIDs(new Set(skippableCollectionMemberIDs));
|
|
60
60
|
OnyxUtils_1.default.setSnapshotMergeKeys(new Set(snapshotMergeKeys));
|
|
61
|
+
OnyxCache_1.default.setRamOnlyKeys(new Set(ramOnlyKeys));
|
|
61
62
|
if (shouldSyncMultipleInstances) {
|
|
62
63
|
(_a = storage_1.default.keepInstancesSync) === null || _a === void 0 ? void 0 : _a.call(storage_1.default, (key, value) => {
|
|
63
64
|
OnyxCache_1.default.set(key, value);
|
|
@@ -150,7 +151,7 @@ function disconnect(connection) {
|
|
|
150
151
|
* @param options optional configuration object
|
|
151
152
|
*/
|
|
152
153
|
function set(key, value, options) {
|
|
153
|
-
return OnyxUtils_1.default.setWithRetry({ key, value, options });
|
|
154
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setWithRetry({ key, value, options }));
|
|
154
155
|
}
|
|
155
156
|
/**
|
|
156
157
|
* Sets multiple keys and values
|
|
@@ -160,7 +161,7 @@ function set(key, value, options) {
|
|
|
160
161
|
* @param data object keyed by ONYXKEYS and the values to set
|
|
161
162
|
*/
|
|
162
163
|
function multiSet(data) {
|
|
163
|
-
return OnyxUtils_1.default.multiSetWithRetry(data);
|
|
164
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.multiSetWithRetry(data));
|
|
164
165
|
}
|
|
165
166
|
/**
|
|
166
167
|
* Merge a new value into an existing value at a key.
|
|
@@ -179,71 +180,73 @@ function multiSet(data) {
|
|
|
179
180
|
* Onyx.merge(ONYXKEYS.POLICY, {name: 'My Workspace'}); // -> {id: 1, name: 'My Workspace'}
|
|
180
181
|
*/
|
|
181
182
|
function merge(key, changes) {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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.
|
|
190
196
|
}
|
|
191
197
|
}
|
|
192
|
-
|
|
193
|
-
|
|
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();
|
|
194
204
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (changes === undefined) {
|
|
201
|
-
return mergeQueue[key] ? mergeQueuePromise[key] : Promise.resolve();
|
|
202
|
-
}
|
|
203
|
-
// Merge attempts are batched together. The delta should be applied after a single call to get() to prevent a race condition.
|
|
204
|
-
// Using the initial value from storage in subsequent merge attempts will lead to an incorrect final merged value.
|
|
205
|
-
if (mergeQueue[key]) {
|
|
206
|
-
mergeQueue[key].push(changes);
|
|
207
|
-
return mergeQueuePromise[key];
|
|
208
|
-
}
|
|
209
|
-
mergeQueue[key] = [changes];
|
|
210
|
-
mergeQueuePromise[key] = OnyxUtils_1.default.get(key).then((existingValue) => {
|
|
211
|
-
// Calls to Onyx.set after a merge will terminate the current merge process and clear the merge queue
|
|
212
|
-
if (mergeQueue[key] == null) {
|
|
213
|
-
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];
|
|
214
210
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Logger.logAlert(logMessages_1.default.incompatibleUpdateAlert(key, 'merge', existingValueType, newValueType));
|
|
220
|
-
}
|
|
221
|
-
return isCompatible;
|
|
222
|
-
});
|
|
223
|
-
// Clean up the write queue, so we don't apply these changes again.
|
|
224
|
-
delete mergeQueue[key];
|
|
225
|
-
delete mergeQueuePromise[key];
|
|
226
|
-
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) {
|
|
227
215
|
return Promise.resolve();
|
|
228
216
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
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}`);
|
|
234
245
|
return Promise.resolve();
|
|
235
246
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
return updatePromise;
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
catch (error) {
|
|
242
|
-
Logger.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
|
|
243
|
-
return Promise.resolve();
|
|
244
|
-
}
|
|
247
|
+
});
|
|
248
|
+
return mergeQueuePromise[key];
|
|
245
249
|
});
|
|
246
|
-
return mergeQueuePromise[key];
|
|
247
250
|
}
|
|
248
251
|
/**
|
|
249
252
|
* Merges a collection based on their keys.
|
|
@@ -259,7 +262,7 @@ function merge(key, changes) {
|
|
|
259
262
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
260
263
|
*/
|
|
261
264
|
function mergeCollection(collectionKey, collection) {
|
|
262
|
-
return OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection, isProcessingCollectionUpdate: true });
|
|
265
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.mergeCollectionWithPatches({ collectionKey, collection, isProcessingCollectionUpdate: true }));
|
|
263
266
|
}
|
|
264
267
|
/**
|
|
265
268
|
* Clear out all the data in the store
|
|
@@ -283,90 +286,93 @@ function mergeCollection(collectionKey, collection) {
|
|
|
283
286
|
* @param keysToPreserve is a list of ONYXKEYS that should not be cleared with the rest of the data
|
|
284
287
|
*/
|
|
285
288
|
function clear(keysToPreserve = []) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
if (
|
|
326
|
-
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;
|
|
327
337
|
}
|
|
328
|
-
keyValuesToResetAsCollection[collectionKey].oldValues[key] = oldValue;
|
|
329
|
-
keyValuesToResetAsCollection[collectionKey].newValues[key] = newValue !== null && newValue !== void 0 ? newValue : undefined;
|
|
330
|
-
}
|
|
331
|
-
else {
|
|
332
|
-
keyValuesToResetIndividually[key] = newValue !== null && newValue !== void 0 ? newValue : undefined;
|
|
333
338
|
}
|
|
334
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);
|
|
335
345
|
}
|
|
336
|
-
|
|
337
|
-
|
|
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));
|
|
338
350
|
}
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
.then(() =>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
DevTools_1.default.clearState(keysToPreserve);
|
|
365
|
-
return Promise.all(updatePromises);
|
|
366
|
-
});
|
|
367
|
-
})
|
|
368
|
-
.then(() => undefined);
|
|
369
|
-
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
|
+
});
|
|
370
376
|
}
|
|
371
377
|
/**
|
|
372
378
|
* Insert API responses and lifecycle data into Onyx
|
|
@@ -375,132 +381,132 @@ function clear(keysToPreserve = []) {
|
|
|
375
381
|
* @returns resolves when all operations are complete
|
|
376
382
|
*/
|
|
377
383
|
function update(data) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
//
|
|
385
|
-
|
|
386
|
-
|
|
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);
|
|
387
404
|
}
|
|
388
|
-
}
|
|
389
|
-
else if (onyxMethod !== OnyxUtils_1.default.METHOD.CLEAR && typeof key !== 'string') {
|
|
390
|
-
throw new Error(`Invalid ${typeof key} key provided in Onyx update. Onyx key must be of type string.`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
// The queue of operations within a single `update` call in the format of <item key - list of operations updating the item>.
|
|
394
|
-
// This allows us to batch the operations per item and merge them into one operation in the order they were requested.
|
|
395
|
-
const updateQueue = {};
|
|
396
|
-
const enqueueSetOperation = (key, value) => {
|
|
397
|
-
// If a `set` operation is enqueued, we should clear the whole queue.
|
|
398
|
-
// Since the `set` operation replaces the value entirely, there's no need to perform any previous operations.
|
|
399
|
-
// To do this, we first put `null` in the queue, which removes the existing value, and then merge the new value.
|
|
400
|
-
updateQueue[key] = [null, value];
|
|
401
|
-
};
|
|
402
|
-
const enqueueMergeOperation = (key, value) => {
|
|
403
|
-
if (value === null) {
|
|
404
|
-
// If we merge `null`, the value is removed and all the previous operations are discarded.
|
|
405
|
-
updateQueue[key] = [null];
|
|
406
|
-
}
|
|
407
|
-
else if (!updateQueue[key]) {
|
|
408
|
-
updateQueue[key] = [value];
|
|
409
|
-
}
|
|
410
|
-
else {
|
|
411
|
-
updateQueue[key].push(value);
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
const promises = [];
|
|
415
|
-
let clearPromise = Promise.resolve();
|
|
416
|
-
for (const { onyxMethod, key, value } of data) {
|
|
417
|
-
const handlers = {
|
|
418
|
-
[OnyxUtils_1.default.METHOD.SET]: enqueueSetOperation,
|
|
419
|
-
[OnyxUtils_1.default.METHOD.MERGE]: enqueueMergeOperation,
|
|
420
|
-
[OnyxUtils_1.default.METHOD.MERGE_COLLECTION]: () => {
|
|
421
|
-
const collection = value;
|
|
422
|
-
if (!OnyxUtils_1.default.isValidNonEmptyCollectionForMerge(collection)) {
|
|
423
|
-
Logger.logInfo('mergeCollection enqueued within update() with invalid or empty value. Skipping this operation.');
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
// Confirm all the collection keys belong to the same parent
|
|
427
|
-
const collectionKeys = Object.keys(collection);
|
|
428
|
-
if (OnyxUtils_1.default.doAllCollectionItemsBelongToSameParent(key, collectionKeys)) {
|
|
429
|
-
const mergedCollection = collection;
|
|
430
|
-
for (const collectionKey of collectionKeys)
|
|
431
|
-
enqueueMergeOperation(collectionKey, mergedCollection[collectionKey]);
|
|
432
|
-
}
|
|
433
|
-
},
|
|
434
|
-
[OnyxUtils_1.default.METHOD.SET_COLLECTION]: (k, v) => promises.push(() => setCollection(k, v)),
|
|
435
|
-
[OnyxUtils_1.default.METHOD.MULTI_SET]: (k, v) => {
|
|
436
|
-
for (const [entryKey, entryValue] of Object.entries(v))
|
|
437
|
-
enqueueSetOperation(entryKey, entryValue);
|
|
438
|
-
},
|
|
439
|
-
[OnyxUtils_1.default.METHOD.CLEAR]: () => {
|
|
440
|
-
clearPromise = clear();
|
|
441
|
-
},
|
|
442
405
|
};
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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);
|
|
454
449
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const
|
|
460
|
-
if (
|
|
461
|
-
//
|
|
462
|
-
|
|
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;
|
|
463
459
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
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 {
|
|
468
470
|
// eslint-disable-next-line no-param-reassign
|
|
469
|
-
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
|
+
}
|
|
470
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 }));
|
|
471
493
|
}
|
|
472
|
-
return queue;
|
|
473
|
-
}, {
|
|
474
|
-
merge: {},
|
|
475
|
-
mergeReplaceNullPatches: {},
|
|
476
|
-
set: {},
|
|
477
|
-
});
|
|
478
|
-
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.merge)) {
|
|
479
|
-
promises.push(() => OnyxUtils_1.default.mergeCollectionWithPatches({
|
|
480
|
-
collectionKey,
|
|
481
|
-
collection: batchedCollectionUpdates.merge,
|
|
482
|
-
mergeReplaceNullPatches: batchedCollectionUpdates.mergeReplaceNullPatches,
|
|
483
|
-
isProcessingCollectionUpdate: true,
|
|
484
|
-
}));
|
|
485
|
-
}
|
|
486
|
-
if (!utils_1.default.isEmptyObject(batchedCollectionUpdates.set)) {
|
|
487
|
-
promises.push(() => OnyxUtils_1.default.partialSetCollection({ collectionKey, collection: batchedCollectionUpdates.set }));
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
for (const [key, operations] of Object.entries(updateQueue)) {
|
|
491
|
-
if (operations[0] === null) {
|
|
492
|
-
const batchedChanges = OnyxUtils_1.default.mergeChanges(operations).result;
|
|
493
|
-
promises.push(() => set(key, batchedChanges));
|
|
494
|
-
continue;
|
|
495
494
|
}
|
|
496
|
-
for (const
|
|
497
|
-
|
|
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
|
+
}
|
|
498
504
|
}
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
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
|
+
});
|
|
504
510
|
}
|
|
505
511
|
/**
|
|
506
512
|
* Sets a collection by replacing all existing collection members with new values.
|
|
@@ -516,7 +522,7 @@ function update(data) {
|
|
|
516
522
|
* @param collection Object collection keyed by individual collection member keys and values
|
|
517
523
|
*/
|
|
518
524
|
function setCollection(collectionKey, collection) {
|
|
519
|
-
return OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection });
|
|
525
|
+
return OnyxUtils_1.default.afterInit(() => OnyxUtils_1.default.setCollectionWithRetry({ collectionKey, collection }));
|
|
520
526
|
}
|
|
521
527
|
const Onyx = {
|
|
522
528
|
METHOD: OnyxUtils_1.default.METHOD,
|
package/dist/OnyxCache.d.ts
CHANGED
|
@@ -36,6 +36,8 @@ declare class OnyxCache {
|
|
|
36
36
|
private recentlyAccessedKeys;
|
|
37
37
|
/** Set of collection keys for fast lookup */
|
|
38
38
|
private collectionKeys;
|
|
39
|
+
/** Set of RAM-only keys for fast lookup */
|
|
40
|
+
private ramOnlyKeys;
|
|
39
41
|
constructor();
|
|
40
42
|
/** Get all the storage keys */
|
|
41
43
|
getAllKeys(): Set<OnyxKey>;
|
|
@@ -165,6 +167,14 @@ declare class OnyxCache {
|
|
|
165
167
|
* Get all data for a collection key
|
|
166
168
|
*/
|
|
167
169
|
getCollectionData(collectionKey: OnyxKey): Record<OnyxKey, OnyxValue<OnyxKey>> | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Set the RAM-only keys for optimized storage
|
|
172
|
+
*/
|
|
173
|
+
setRamOnlyKeys(ramOnlyKeys: Set<OnyxKey>): void;
|
|
174
|
+
/**
|
|
175
|
+
* Check if a key is a RAM-only key
|
|
176
|
+
*/
|
|
177
|
+
isRamOnlyKey(key: OnyxKey): boolean;
|
|
168
178
|
}
|
|
169
179
|
declare const instance: OnyxCache;
|
|
170
180
|
export default instance;
|
package/dist/OnyxCache.js
CHANGED
|
@@ -64,6 +64,8 @@ class OnyxCache {
|
|
|
64
64
|
this.recentlyAccessedKeys = new Set();
|
|
65
65
|
/** Set of collection keys for fast lookup */
|
|
66
66
|
this.collectionKeys = new Set();
|
|
67
|
+
/** Set of RAM-only keys for fast lookup */
|
|
68
|
+
this.ramOnlyKeys = new Set();
|
|
67
69
|
this.storageKeys = new Set();
|
|
68
70
|
this.nullishStorageKeys = new Set();
|
|
69
71
|
this.recentKeys = new Set();
|
|
@@ -71,7 +73,7 @@ class OnyxCache {
|
|
|
71
73
|
this.collectionData = {};
|
|
72
74
|
this.pendingPromises = new Map();
|
|
73
75
|
// bind all public methods to prevent problems with `this`
|
|
74
|
-
(0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'addToAccessedKeys', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys', 'setEvictionAllowList', 'getEvictionBlocklist', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'isCollectionKey', 'getCollectionKey', 'getCollectionData');
|
|
76
|
+
(0, bindAll_1.default)(this, 'getAllKeys', 'get', 'hasCacheForKey', 'addKey', 'addNullishStorageKey', 'hasNullishStorageKey', 'clearNullishStorageKeys', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'addToAccessedKeys', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit', 'setAllKeys', 'setEvictionAllowList', 'getEvictionBlocklist', 'isEvictableKey', 'removeLastAccessedKey', 'addLastAccessedKey', 'addEvictableKeysToRecentlyAccessedList', 'getKeyForEviction', 'setCollectionKeys', 'isCollectionKey', 'getCollectionKey', 'getCollectionData', 'setRamOnlyKeys', 'isRamOnlyKey');
|
|
75
77
|
}
|
|
76
78
|
/** Get all the storage keys */
|
|
77
79
|
getAllKeys() {
|
|
@@ -394,6 +396,18 @@ class OnyxCache {
|
|
|
394
396
|
// Return a shallow copy to ensure React detects changes when items are added/removed
|
|
395
397
|
return Object.assign({}, cachedCollection);
|
|
396
398
|
}
|
|
399
|
+
/**
|
|
400
|
+
* Set the RAM-only keys for optimized storage
|
|
401
|
+
*/
|
|
402
|
+
setRamOnlyKeys(ramOnlyKeys) {
|
|
403
|
+
this.ramOnlyKeys = ramOnlyKeys;
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Check if a key is a RAM-only key
|
|
407
|
+
*/
|
|
408
|
+
isRamOnlyKey(key) {
|
|
409
|
+
return this.ramOnlyKeys.has(key);
|
|
410
|
+
}
|
|
397
411
|
}
|
|
398
412
|
const instance = new OnyxCache();
|
|
399
413
|
exports.default = instance;
|
package/dist/OnyxMerge/index.js
CHANGED
|
@@ -14,8 +14,10 @@ const applyMerge = (key, existingValue, validChanges) => {
|
|
|
14
14
|
OnyxUtils_1.default.logKeyChanged(OnyxUtils_1.default.METHOD.MERGE, key, mergedValue, hasChanged);
|
|
15
15
|
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
16
16
|
const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, mergedValue, hasChanged);
|
|
17
|
+
const shouldSkipStorageOperations = !hasChanged || OnyxUtils_1.default.isRamOnlyKey(key);
|
|
17
18
|
// If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
18
|
-
|
|
19
|
+
// If the key is marked as RAM-only, it should not be saved nor updated in the storage.
|
|
20
|
+
if (shouldSkipStorageOperations) {
|
|
19
21
|
return Promise.resolve({ mergedValue, updatePromise });
|
|
20
22
|
}
|
|
21
23
|
// For web platforms we use `setItem` since the object was already merged with its changes before.
|
|
@@ -20,8 +20,10 @@ const applyMerge = (key, existingValue, validChanges) => {
|
|
|
20
20
|
OnyxUtils_1.default.logKeyChanged(OnyxUtils_1.default.METHOD.MERGE, key, mergedValue, hasChanged);
|
|
21
21
|
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
22
22
|
const updatePromise = OnyxUtils_1.default.broadcastUpdate(key, mergedValue, hasChanged);
|
|
23
|
+
const shouldSkipStorageOperations = !hasChanged || OnyxUtils_1.default.isRamOnlyKey(key);
|
|
23
24
|
// If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
24
|
-
|
|
25
|
+
// If the key is marked as RAM-only, it should not be saved nor updated in the storage.
|
|
26
|
+
if (shouldSkipStorageOperations) {
|
|
25
27
|
return Promise.resolve({ mergedValue, updatePromise });
|
|
26
28
|
}
|
|
27
29
|
// For native platforms we use `mergeItem` that will take advantage of JSON_PATCH and JSON_REPLACE SQL operations to
|
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
|
*/
|
|
@@ -113,6 +122,24 @@ declare function isCollectionMemberKey<TCollectionKey extends CollectionKeyBase>
|
|
|
113
122
|
* @returns true if the key is a collection member, false otherwise
|
|
114
123
|
*/
|
|
115
124
|
declare function isCollectionMember(key: OnyxKey): boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Checks if a given key is a RAM-only key, RAM-only collection key, or a RAM-only collection member
|
|
127
|
+
*
|
|
128
|
+
* For example:
|
|
129
|
+
*
|
|
130
|
+
* For the following Onyx setup
|
|
131
|
+
*
|
|
132
|
+
* ramOnlyKeys: ["ramOnlyKey", "ramOnlyCollection_"]
|
|
133
|
+
*
|
|
134
|
+
* - `isRamOnlyKey("ramOnlyKey")` would return true
|
|
135
|
+
* - `isRamOnlyKey("ramOnlyCollection_")` would return true
|
|
136
|
+
* - `isRamOnlyKey("ramOnlyCollection_1")` would return true
|
|
137
|
+
* - `isRamOnlyKey("someOtherKey")` would return false
|
|
138
|
+
*
|
|
139
|
+
* @param key - The key to check
|
|
140
|
+
* @returns true if key is a RAM-only key, RAM-only collection key, or a RAM-only collection member
|
|
141
|
+
*/
|
|
142
|
+
declare function isRamOnlyKey(key: OnyxKey): boolean;
|
|
116
143
|
/**
|
|
117
144
|
* Splits a collection member key into the collection key part and the ID part.
|
|
118
145
|
* @param key - The collection member key to split.
|
|
@@ -325,6 +352,7 @@ declare const OnyxUtils: {
|
|
|
325
352
|
getMergeQueuePromise: typeof getMergeQueuePromise;
|
|
326
353
|
getDefaultKeyStates: typeof getDefaultKeyStates;
|
|
327
354
|
getDeferredInitTask: typeof getDeferredInitTask;
|
|
355
|
+
afterInit: typeof afterInit;
|
|
328
356
|
initStoreValues: typeof initStoreValues;
|
|
329
357
|
sendActionToDevTools: typeof sendActionToDevTools;
|
|
330
358
|
get: typeof get;
|
|
@@ -376,6 +404,7 @@ declare const OnyxUtils: {
|
|
|
376
404
|
setWithRetry: typeof setWithRetry;
|
|
377
405
|
multiSetWithRetry: typeof multiSetWithRetry;
|
|
378
406
|
setCollectionWithRetry: typeof setCollectionWithRetry;
|
|
407
|
+
isRamOnlyKey: typeof isRamOnlyKey;
|
|
379
408
|
};
|
|
380
409
|
export type { OnyxMethod };
|
|
381
410
|
export default OnyxUtils;
|
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
|
*/
|
|
@@ -385,6 +399,34 @@ function isCollectionMember(key) {
|
|
|
385
399
|
return false;
|
|
386
400
|
}
|
|
387
401
|
}
|
|
402
|
+
/**
|
|
403
|
+
* Checks if a given key is a RAM-only key, RAM-only collection key, or a RAM-only collection member
|
|
404
|
+
*
|
|
405
|
+
* For example:
|
|
406
|
+
*
|
|
407
|
+
* For the following Onyx setup
|
|
408
|
+
*
|
|
409
|
+
* ramOnlyKeys: ["ramOnlyKey", "ramOnlyCollection_"]
|
|
410
|
+
*
|
|
411
|
+
* - `isRamOnlyKey("ramOnlyKey")` would return true
|
|
412
|
+
* - `isRamOnlyKey("ramOnlyCollection_")` would return true
|
|
413
|
+
* - `isRamOnlyKey("ramOnlyCollection_1")` would return true
|
|
414
|
+
* - `isRamOnlyKey("someOtherKey")` would return false
|
|
415
|
+
*
|
|
416
|
+
* @param key - The key to check
|
|
417
|
+
* @returns true if key is a RAM-only key, RAM-only collection key, or a RAM-only collection member
|
|
418
|
+
*/
|
|
419
|
+
function isRamOnlyKey(key) {
|
|
420
|
+
try {
|
|
421
|
+
const collectionKey = getCollectionKey(key);
|
|
422
|
+
// If collectionKey exists for a given key, check if it's a RAM-only key
|
|
423
|
+
return OnyxCache_1.default.isRamOnlyKey(collectionKey);
|
|
424
|
+
}
|
|
425
|
+
catch (_a) {
|
|
426
|
+
// If getCollectionKey throws, the key is not a collection member
|
|
427
|
+
}
|
|
428
|
+
return OnyxCache_1.default.isRamOnlyKey(key);
|
|
429
|
+
}
|
|
388
430
|
/**
|
|
389
431
|
* Splits a collection member key into the collection key part and the ID part.
|
|
390
432
|
* @param key - The collection member key to split.
|
|
@@ -713,6 +755,9 @@ function scheduleNotifyCollectionSubscribers(key, value, previousValue) {
|
|
|
713
755
|
function remove(key, isProcessingCollectionUpdate) {
|
|
714
756
|
OnyxCache_1.default.drop(key);
|
|
715
757
|
scheduleSubscriberUpdate(key, undefined, undefined, isProcessingCollectionUpdate);
|
|
758
|
+
if (isRamOnlyKey(key)) {
|
|
759
|
+
return Promise.resolve();
|
|
760
|
+
}
|
|
716
761
|
return storage_1.default.removeItem(key).then(() => undefined);
|
|
717
762
|
}
|
|
718
763
|
function reportStorageQuota() {
|
|
@@ -1103,6 +1148,11 @@ function setWithRetry({ key, value, options }, retryAttempt) {
|
|
|
1103
1148
|
if (!hasChanged && !retryAttempt) {
|
|
1104
1149
|
return updatePromise;
|
|
1105
1150
|
}
|
|
1151
|
+
// If a key is a RAM-only key or a member of RAM-only collection, we skip the step that modifies the storage
|
|
1152
|
+
if (isRamOnlyKey(key)) {
|
|
1153
|
+
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET, key, valueWithoutNestedNullValues);
|
|
1154
|
+
return updatePromise;
|
|
1155
|
+
}
|
|
1106
1156
|
return storage_1.default.setItem(key, valueWithoutNestedNullValues)
|
|
1107
1157
|
.catch((error) => OnyxUtils.retryOperation(error, setWithRetry, { key, value: valueWithoutNestedNullValues, options }, retryAttempt))
|
|
1108
1158
|
.then(() => {
|
|
@@ -1147,7 +1197,12 @@ function multiSetWithRetry(data, retryAttempt) {
|
|
|
1147
1197
|
OnyxCache_1.default.set(key, value);
|
|
1148
1198
|
return OnyxUtils.scheduleSubscriberUpdate(key, value);
|
|
1149
1199
|
});
|
|
1150
|
-
|
|
1200
|
+
const keyValuePairsToStore = keyValuePairsToSet.filter((keyValuePair) => {
|
|
1201
|
+
const [key] = keyValuePair;
|
|
1202
|
+
// Filter out the RAM-only key value pairs, as they should not be saved to storage
|
|
1203
|
+
return !isRamOnlyKey(key);
|
|
1204
|
+
});
|
|
1205
|
+
return storage_1.default.multiSet(keyValuePairsToStore)
|
|
1151
1206
|
.catch((error) => OnyxUtils.retryOperation(error, multiSetWithRetry, newData, retryAttempt))
|
|
1152
1207
|
.then(() => {
|
|
1153
1208
|
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.MULTI_SET, undefined, newData);
|
|
@@ -1207,6 +1262,11 @@ function setCollectionWithRetry({ collectionKey, collection }, retryAttempt) {
|
|
|
1207
1262
|
for (const [key, value] of keyValuePairs)
|
|
1208
1263
|
OnyxCache_1.default.set(key, value);
|
|
1209
1264
|
const updatePromise = OnyxUtils.scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
|
|
1265
|
+
// RAM-only keys are not supposed to be saved to storage
|
|
1266
|
+
if (isRamOnlyKey(collectionKey)) {
|
|
1267
|
+
OnyxUtils.sendActionToDevTools(OnyxUtils.METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1268
|
+
return updatePromise;
|
|
1269
|
+
}
|
|
1210
1270
|
return storage_1.default.multiSet(keyValuePairs)
|
|
1211
1271
|
.catch((error) => OnyxUtils.retryOperation(error, setCollectionWithRetry, { collectionKey, collection }, retryAttempt))
|
|
1212
1272
|
.then(() => {
|
|
@@ -1298,10 +1358,12 @@ function mergeCollectionWithPatches({ collectionKey, collection, mergeReplaceNul
|
|
|
1298
1358
|
const previousCollectionPromise = Promise.all(existingKeys.map((key) => get(key).then((value) => [key, value]))).then(Object.fromEntries);
|
|
1299
1359
|
// New keys will be added via multiSet while existing keys will be updated using multiMerge
|
|
1300
1360
|
// This is because setting a key that doesn't exist yet with multiMerge will throw errors
|
|
1301
|
-
|
|
1361
|
+
// We can skip this step for RAM-only keys as they should never be saved to storage
|
|
1362
|
+
if (!isRamOnlyKey(collectionKey) && keyValuePairsForExistingCollection.length > 0) {
|
|
1302
1363
|
promises.push(storage_1.default.multiMerge(keyValuePairsForExistingCollection));
|
|
1303
1364
|
}
|
|
1304
|
-
|
|
1365
|
+
// We can skip this step for RAM-only keys as they should never be saved to storage
|
|
1366
|
+
if (!isRamOnlyKey(collectionKey) && keyValuePairsForNewCollection.length > 0) {
|
|
1305
1367
|
promises.push(storage_1.default.multiSet(keyValuePairsForNewCollection));
|
|
1306
1368
|
}
|
|
1307
1369
|
// finalMergedCollection contains all the keys that were merged, without the keys of incompatible updates
|
|
@@ -1364,6 +1426,10 @@ function partialSetCollection({ collectionKey, collection }, retryAttempt) {
|
|
|
1364
1426
|
for (const [key, value] of keyValuePairs)
|
|
1365
1427
|
OnyxCache_1.default.set(key, value);
|
|
1366
1428
|
const updatePromise = scheduleNotifyCollectionSubscribers(collectionKey, mutableCollection, previousCollection);
|
|
1429
|
+
if (isRamOnlyKey(collectionKey)) {
|
|
1430
|
+
sendActionToDevTools(METHOD.SET_COLLECTION, undefined, mutableCollection);
|
|
1431
|
+
return updatePromise;
|
|
1432
|
+
}
|
|
1367
1433
|
return storage_1.default.multiSet(keyValuePairs)
|
|
1368
1434
|
.catch((error) => retryOperation(error, partialSetCollection, { collectionKey, collection }, retryAttempt))
|
|
1369
1435
|
.then(() => {
|
|
@@ -1394,6 +1460,7 @@ const OnyxUtils = {
|
|
|
1394
1460
|
getMergeQueuePromise,
|
|
1395
1461
|
getDefaultKeyStates,
|
|
1396
1462
|
getDeferredInitTask,
|
|
1463
|
+
afterInit,
|
|
1397
1464
|
initStoreValues,
|
|
1398
1465
|
sendActionToDevTools,
|
|
1399
1466
|
get,
|
|
@@ -1445,6 +1512,7 @@ const OnyxUtils = {
|
|
|
1445
1512
|
setWithRetry,
|
|
1446
1513
|
multiSetWithRetry,
|
|
1447
1514
|
setCollectionWithRetry,
|
|
1515
|
+
isRamOnlyKey,
|
|
1448
1516
|
};
|
|
1449
1517
|
GlobalSettings.addGlobalSettingsChangeListener(({ enablePerformanceMetrics }) => {
|
|
1450
1518
|
if (!enablePerformanceMetrics) {
|
|
@@ -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
|
@@ -364,6 +364,10 @@ type InitOptions = {
|
|
|
364
364
|
* Additionally, any subscribers from these keys to won't receive any data from Onyx.
|
|
365
365
|
*/
|
|
366
366
|
skippableCollectionMemberIDs?: string[];
|
|
367
|
+
/**
|
|
368
|
+
* Array of keys that when provided to Onyx are flagged as RAM-only keys, and thus are not saved to disk.
|
|
369
|
+
*/
|
|
370
|
+
ramOnlyKeys?: OnyxKey[];
|
|
367
371
|
/**
|
|
368
372
|
* A list of field names that should always be merged into snapshot entries even if those fields are
|
|
369
373
|
* missing in the snapshot. Snapshots are saved "views" of a key's data used to populate read-only
|
package/dist/useOnyx.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ type UseOnyxOptions<TKey extends OnyxKey, TReturnValue> = {
|
|
|
8
8
|
canEvict?: boolean;
|
|
9
9
|
/**
|
|
10
10
|
* If set to `false`, then no data will be prefilled into the component.
|
|
11
|
+
* @deprecated This param is going to be removed soon. Use RAM-only keys instead.
|
|
11
12
|
*/
|
|
12
13
|
initWithStoredValues?: boolean;
|
|
13
14
|
/**
|