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.
@@ -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, null);
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 defaultKeyValuePairs = _.pairs(_.omit(defaultKeyStates, keysToPreserve));
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(() => Storage.multiSet(defaultKeyValuePairs));
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.72",
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",