react-native-onyx 1.0.72 → 1.0.73
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/API.md +1 -0
- package/README.md +10 -0
- package/dist/web.development.js +5782 -879
- package/dist/web.development.js.map +1 -1
- package/dist/web.min.js +2 -1
- package/dist/web.min.js.LICENSE.txt +1 -0
- package/dist/web.min.js.map +1 -1
- package/lib/DevTools.js +49 -0
- package/lib/Onyx.js +48 -7
- package/package.json +2 -1
package/lib/DevTools.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import {connectViaExtension} from 'remotedev';
|
|
2
|
+
import _ from 'underscore';
|
|
3
|
+
|
|
4
|
+
class DevTools {
|
|
5
|
+
/**
|
|
6
|
+
* @callback onStateChange
|
|
7
|
+
* @param {object} state
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Creates an instance of DevTools, with an internal state that mirrors the storage.
|
|
11
|
+
*
|
|
12
|
+
* @param {object} initialState - initial state of the storage
|
|
13
|
+
* @param {onStateChange} onStateChange - callback which is triggered when we timetravel to a different registered action
|
|
14
|
+
*/
|
|
15
|
+
constructor(initialState = {}) {
|
|
16
|
+
this.state = initialState;
|
|
17
|
+
this.remotedev = connectViaExtension();
|
|
18
|
+
this.remotedev.init(this.state);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Registers an action that updated the current state of the storage
|
|
23
|
+
*
|
|
24
|
+
* @param {string} type - name of the action
|
|
25
|
+
* @param {any} payload - data written to the storage
|
|
26
|
+
* @param {object} stateChanges - partial state that got updated after the changes
|
|
27
|
+
*/
|
|
28
|
+
registerAction(type, payload = undefined, stateChanges = {}) {
|
|
29
|
+
const newState = {
|
|
30
|
+
...this.state,
|
|
31
|
+
...stateChanges,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.remotedev.send({type, payload}, newState);
|
|
35
|
+
this.state = newState;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* This clears the internal state of the DevTools, preserving the keys not included in `keyToBeRemoved`
|
|
40
|
+
*
|
|
41
|
+
* @param {string[]} keysToBeRemoved
|
|
42
|
+
*/
|
|
43
|
+
clearState(keysToBeRemoved = []) {
|
|
44
|
+
const pairs = _.map(keysToBeRemoved, key => [key, undefined]);
|
|
45
|
+
this.registerAction('CLEAR', undefined, _.object((pairs)));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default DevTools;
|
package/lib/Onyx.js
CHANGED
|
@@ -8,6 +8,7 @@ import createDeferredTask from './createDeferredTask';
|
|
|
8
8
|
import fastMerge from './fastMerge';
|
|
9
9
|
import * as PerformanceUtils from './metrics/PerformanceUtils';
|
|
10
10
|
import Storage from './storage';
|
|
11
|
+
import DevTools from './DevTools';
|
|
11
12
|
|
|
12
13
|
// Method constants
|
|
13
14
|
const METHOD = {
|
|
@@ -37,6 +38,11 @@ let recentlyAccessedKeys = [];
|
|
|
37
38
|
// whatever appears in this list it will NEVER be a candidate for eviction.
|
|
38
39
|
let evictionAllowList = [];
|
|
39
40
|
|
|
41
|
+
let devTools = {
|
|
42
|
+
registerAction: () => {},
|
|
43
|
+
clearState: () => {},
|
|
44
|
+
};
|
|
45
|
+
|
|
40
46
|
// Holds a map of keys and connectionID arrays whose keys will never be automatically evicted as
|
|
41
47
|
// long as we have at least one subscriber that returns false for the canEvict property.
|
|
42
48
|
const evictionBlocklist = {};
|
|
@@ -842,7 +848,10 @@ function notifyCollectionSubscribersOnNextTick(key, value) {
|
|
|
842
848
|
function remove(key) {
|
|
843
849
|
cache.drop(key);
|
|
844
850
|
notifySubscribersOnNextTick(key, null);
|
|
845
|
-
return Storage.removeItem(key)
|
|
851
|
+
return Storage.removeItem(key).then((result) => {
|
|
852
|
+
devTools.registerAction(`REMOVE/${key.toUpperCase()}`, undefined, {[key]: undefined});
|
|
853
|
+
return result;
|
|
854
|
+
});
|
|
846
855
|
}
|
|
847
856
|
|
|
848
857
|
/**
|
|
@@ -966,6 +975,7 @@ function set(key, value) {
|
|
|
966
975
|
const hasChanged = cache.hasValueChanged(key, valueWithNullRemoved);
|
|
967
976
|
|
|
968
977
|
// This approach prioritizes fast UI changes without waiting for data to be stored in device storage.
|
|
978
|
+
broadcastUpdate(key, value, 'set');
|
|
969
979
|
broadcastUpdate(key, valueWithNullRemoved, hasChanged, 'set');
|
|
970
980
|
|
|
971
981
|
// If the value has not changed, calling Storage.setItem() would be redundant and a waste of performance, so return early instead.
|
|
@@ -974,6 +984,10 @@ function set(key, value) {
|
|
|
974
984
|
}
|
|
975
985
|
|
|
976
986
|
return Storage.setItem(key, valueWithNullRemoved)
|
|
987
|
+
.then((result) => {
|
|
988
|
+
devTools.registerAction(`SET/${key.toUpperCase()}`, valueWithNullRemoved, {[key]: valueWithNullRemoved});
|
|
989
|
+
return result;
|
|
990
|
+
})
|
|
977
991
|
.catch(error => evictStorageAndRetry(error, set, key, valueWithNullRemoved));
|
|
978
992
|
}
|
|
979
993
|
|
|
@@ -1004,6 +1018,11 @@ function multiSet(data) {
|
|
|
1004
1018
|
// Update cache and optimistically inform subscribers on the next tick
|
|
1005
1019
|
cache.set(key, val);
|
|
1006
1020
|
notifySubscribersOnNextTick(key, val);
|
|
1021
|
+
if (_.isNull(val)) {
|
|
1022
|
+
devTools.registerAction(`REMOVE/${key.toUpperCase()}`, val, {[key]: undefined});
|
|
1023
|
+
} else {
|
|
1024
|
+
devTools.registerAction(`SET/${key.toUpperCase()}`, val, {[key]: val});
|
|
1025
|
+
}
|
|
1007
1026
|
});
|
|
1008
1027
|
|
|
1009
1028
|
return Storage.multiSet(keyValuePairs)
|
|
@@ -1098,7 +1117,10 @@ function merge(key, changes) {
|
|
|
1098
1117
|
return Promise.resolve();
|
|
1099
1118
|
}
|
|
1100
1119
|
|
|
1101
|
-
return Storage.mergeItem(key, batchedChanges, modifiedData)
|
|
1120
|
+
return Storage.mergeItem(key, batchedChanges, modifiedData).then((results) => {
|
|
1121
|
+
devTools.registerAction(`MERGE/${key.toUpperCase()}`, modifiedData, {[key]: modifiedData});
|
|
1122
|
+
return results;
|
|
1123
|
+
});
|
|
1102
1124
|
} catch (error) {
|
|
1103
1125
|
Logger.logAlert(`An error occurred while applying merge for key: ${key}, Error: ${error}`);
|
|
1104
1126
|
}
|
|
@@ -1110,9 +1132,10 @@ function merge(key, changes) {
|
|
|
1110
1132
|
/**
|
|
1111
1133
|
* Merge user provided default key value pairs.
|
|
1112
1134
|
* @private
|
|
1135
|
+
* @param {boolean} enableDevTools
|
|
1113
1136
|
* @returns {Promise}
|
|
1114
1137
|
*/
|
|
1115
|
-
function initializeWithDefaultKeyStates() {
|
|
1138
|
+
function initializeWithDefaultKeyStates(enableDevTools = false) {
|
|
1116
1139
|
return Storage.multiGet(_.keys(defaultKeyStates))
|
|
1117
1140
|
.then((pairs) => {
|
|
1118
1141
|
const asObject = _.object(pairs);
|
|
@@ -1120,6 +1143,9 @@ function initializeWithDefaultKeyStates() {
|
|
|
1120
1143
|
const merged = fastMerge(asObject, defaultKeyStates);
|
|
1121
1144
|
cache.merge(merged);
|
|
1122
1145
|
_.each(merged, (val, key) => keyChanged(key, val));
|
|
1146
|
+
if (enableDevTools) {
|
|
1147
|
+
devTools = new DevTools(merged);
|
|
1148
|
+
}
|
|
1123
1149
|
});
|
|
1124
1150
|
}
|
|
1125
1151
|
|
|
@@ -1167,7 +1193,7 @@ function clear(keysToPreserve = []) {
|
|
|
1167
1193
|
// since collection key subscribers need to be updated differently
|
|
1168
1194
|
if (!isKeyToPreserve) {
|
|
1169
1195
|
const oldValue = cache.getValue(key);
|
|
1170
|
-
const newValue = _.get(defaultKeyStates, key,
|
|
1196
|
+
const newValue = _.get(defaultKeyStates, key, undefined);
|
|
1171
1197
|
if (newValue !== oldValue) {
|
|
1172
1198
|
cache.set(key, newValue);
|
|
1173
1199
|
const collectionKey = key.substring(0, key.indexOf('_') + 1);
|
|
@@ -1198,11 +1224,15 @@ function clear(keysToPreserve = []) {
|
|
|
1198
1224
|
notifyCollectionSubscribersOnNextTick(key, value);
|
|
1199
1225
|
});
|
|
1200
1226
|
|
|
1201
|
-
const
|
|
1227
|
+
const defaultKeyValueState = _.omit(defaultKeyStates, keysToPreserve);
|
|
1228
|
+
const defaultKeyValuePairs = _.pairs(defaultKeyValueState);
|
|
1202
1229
|
|
|
1203
1230
|
// Remove only the items that we want cleared from storage, and reset others to default
|
|
1204
1231
|
_.each(keysToBeClearedFromStorage, key => cache.drop(key));
|
|
1205
|
-
return Storage.removeItems(keysToBeClearedFromStorage).then(() =>
|
|
1232
|
+
return Storage.removeItems(keysToBeClearedFromStorage).then(() => {
|
|
1233
|
+
devTools.clearState(keysToBeClearedFromStorage);
|
|
1234
|
+
return Storage.multiSet(defaultKeyValuePairs);
|
|
1235
|
+
});
|
|
1206
1236
|
});
|
|
1207
1237
|
}
|
|
1208
1238
|
|
|
@@ -1276,6 +1306,11 @@ function mergeCollection(collectionKey, collection) {
|
|
|
1276
1306
|
Promise.all(_.map(existingKeys, get)).then(() => {
|
|
1277
1307
|
cache.merge(collection);
|
|
1278
1308
|
keysChanged(collectionKey, collection);
|
|
1309
|
+
if (_.isNull(collection)) {
|
|
1310
|
+
devTools.registerAction(`REMOVE/${collectionKey.toUpperCase()}`, collection, {[collectionKey]: undefined});
|
|
1311
|
+
} else {
|
|
1312
|
+
devTools.registerAction(`SET/${collectionKey.toUpperCase()}`, collection, {[collectionKey]: collection});
|
|
1313
|
+
}
|
|
1279
1314
|
});
|
|
1280
1315
|
|
|
1281
1316
|
return Promise.all(promises)
|
|
@@ -1377,6 +1412,7 @@ function init({
|
|
|
1377
1412
|
captureMetrics = false,
|
|
1378
1413
|
shouldSyncMultipleInstances = Boolean(global.localStorage),
|
|
1379
1414
|
debugSetState = false,
|
|
1415
|
+
enableDevTools = false,
|
|
1380
1416
|
} = {}) {
|
|
1381
1417
|
if (captureMetrics) {
|
|
1382
1418
|
// The code here is only bundled and applied when the captureMetrics is set
|
|
@@ -1404,7 +1440,7 @@ function init({
|
|
|
1404
1440
|
// Initialize all of our keys with data provided then give green light to any pending connections
|
|
1405
1441
|
Promise.all([
|
|
1406
1442
|
addAllSafeEvictionKeysToRecentlyAccessedList(),
|
|
1407
|
-
initializeWithDefaultKeyStates(),
|
|
1443
|
+
initializeWithDefaultKeyStates(enableDevTools),
|
|
1408
1444
|
])
|
|
1409
1445
|
.then(deferredInitTask.resolve);
|
|
1410
1446
|
|
|
@@ -1412,6 +1448,11 @@ function init({
|
|
|
1412
1448
|
Storage.keepInstancesSync((key, value) => {
|
|
1413
1449
|
cache.set(key, value);
|
|
1414
1450
|
keyChanged(key, value);
|
|
1451
|
+
if (_.isNull(value)) {
|
|
1452
|
+
devTools.registerAction(`REMOVE/${key.toUpperCase()}`, value, {[key]: undefined});
|
|
1453
|
+
} else {
|
|
1454
|
+
devTools.registerAction(`SET/${key.toUpperCase()}`, value, {[key]: value});
|
|
1455
|
+
}
|
|
1415
1456
|
});
|
|
1416
1457
|
}
|
|
1417
1458
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-onyx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.73",
|
|
4
4
|
"author": "Expensify, Inc.",
|
|
5
5
|
"homepage": "https://expensify.com",
|
|
6
6
|
"description": "State management for React Native",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"react-native-performance": "^2.0.0",
|
|
72
72
|
"react-native-quick-sqlite": "^8.0.0-beta.2",
|
|
73
73
|
"react-test-renderer": "18.1.0",
|
|
74
|
+
"remotedev": "^0.2.9",
|
|
74
75
|
"type-fest": "^3.12.0",
|
|
75
76
|
"webpack": "^5.72.1",
|
|
76
77
|
"webpack-cli": "^4.9.2",
|