react-native-onyx 1.0.61 → 1.0.63

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.
@@ -1,10 +1,10 @@
1
1
  /**
2
- * This file is here to wrap LocalForage with a layer that provides data-changed events like the ones that exist
2
+ * This file is here to wrap IDBKeyVal with a layer that provides data-changed events like the ones that exist
3
3
  * when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
4
4
  * data changes and then stay up-to-date with everything happening in Onyx.
5
5
  */
6
6
  import _ from 'underscore';
7
- import Storage from './providers/LocalForage';
7
+ import Storage from './providers/IDBKeyVal';
8
8
 
9
9
  const SYNC_ONYX = 'SYNC_ONYX';
10
10
 
@@ -8,7 +8,7 @@ const set = jest.fn((key, value) => {
8
8
  return Promise.resolve(value);
9
9
  });
10
10
 
11
- const localForageMock = {
11
+ const idbKeyvalMock = {
12
12
  setItem(key, value) {
13
13
  return set(key, value);
14
14
  },
@@ -57,23 +57,23 @@ const localForageMock = {
57
57
  config() {},
58
58
  };
59
59
 
60
- const localForageMockSpy = {
61
- localForageSet: set,
62
- setItem: jest.fn(localForageMock.setItem),
63
- getItem: jest.fn(localForageMock.getItem),
64
- removeItem: jest.fn(localForageMock.removeItem),
65
- removeItems: jest.fn(localForageMock.removeItems),
66
- clear: jest.fn(localForageMock.clear),
67
- getAllKeys: jest.fn(localForageMock.getAllKeys),
68
- config: jest.fn(localForageMock.config),
69
- multiGet: jest.fn(localForageMock.multiGet),
70
- multiSet: jest.fn(localForageMock.multiSet),
71
- multiMerge: jest.fn(localForageMock.multiMerge),
72
- mergeItem: jest.fn(localForageMock.mergeItem),
60
+ const idbKeyvalMockSpy = {
61
+ idbKeyvalSet: set,
62
+ setItem: jest.fn(idbKeyvalMock.setItem),
63
+ getItem: jest.fn(idbKeyvalMock.getItem),
64
+ removeItem: jest.fn(idbKeyvalMock.removeItem),
65
+ removeItems: jest.fn(idbKeyvalMock.removeItems),
66
+ clear: jest.fn(idbKeyvalMock.clear),
67
+ getAllKeys: jest.fn(idbKeyvalMock.getAllKeys),
68
+ config: jest.fn(idbKeyvalMock.config),
69
+ multiGet: jest.fn(idbKeyvalMock.multiGet),
70
+ multiSet: jest.fn(idbKeyvalMock.multiSet),
71
+ multiMerge: jest.fn(idbKeyvalMock.multiMerge),
72
+ mergeItem: jest.fn(idbKeyvalMock.mergeItem),
73
73
  getStorageMap: jest.fn(() => storageMapInternal),
74
74
  setInitialMockData: jest.fn((data) => {
75
75
  storageMapInternal = data;
76
76
  }),
77
77
  };
78
78
 
79
- export default localForageMockSpy;
79
+ export default idbKeyvalMockSpy;
@@ -0,0 +1,118 @@
1
+ import {
2
+ set,
3
+ keys,
4
+ getMany,
5
+ setMany,
6
+ get,
7
+ clear,
8
+ del,
9
+ delMany,
10
+ createStore,
11
+ promisifyRequest,
12
+ } from 'idb-keyval';
13
+ import _ from 'underscore';
14
+ import fastMerge from '../../fastMerge';
15
+
16
+ // We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB
17
+ // which might not be available in certain environments that load the bundle (e.g. electron main process).
18
+ let customStoreInstance;
19
+ const getCustomStore = () => {
20
+ if (!customStoreInstance) {
21
+ customStoreInstance = createStore('OnyxDB', 'keyvaluepairs');
22
+ }
23
+ return customStoreInstance;
24
+ };
25
+
26
+ const provider = {
27
+ /**
28
+ * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
29
+ * @param {String} key
30
+ * @param {*} value
31
+ * @return {Promise<void>}
32
+ */
33
+ setItem: (key, value) => set(key, value, getCustomStore()),
34
+
35
+ /**
36
+ * Get multiple key-value pairs for the give array of keys in a batch.
37
+ * This is optimized to use only one database transaction.
38
+ * @param {String[]} keysParam
39
+ * @return {Promise<Array<[key, value]>>}
40
+ */
41
+ multiGet: keysParam => getMany(keysParam, getCustomStore())
42
+ .then(values => _.map(values, (value, index) => [keysParam[index], value])),
43
+
44
+ /**
45
+ * Multiple merging of existing and new values in a batch
46
+ * @param {Array<[key, value]>} pairs
47
+ * @return {Promise<void>}
48
+ */
49
+ multiMerge: pairs => getCustomStore()('readwrite', (store) => {
50
+ // Note: we are using the manual store transaction here, to fit the read and update
51
+ // of the items in one transaction to achieve best performance.
52
+
53
+ const getValues = Promise.all(_.map(pairs, ([key]) => promisifyRequest(store.get(key))));
54
+
55
+ return getValues.then((values) => {
56
+ const upsertMany = _.map(pairs, ([key, value], index) => {
57
+ const prev = values[index];
58
+ const newValue = _.isObject(prev) ? fastMerge(prev, value) : value;
59
+ return promisifyRequest(store.put(newValue, key));
60
+ });
61
+ return Promise.all(upsertMany);
62
+ });
63
+ }),
64
+
65
+ /**
66
+ * Merging an existing value with a new one
67
+ * @param {String} key
68
+ * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
69
+ * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
70
+ * @return {Promise<void>}
71
+ */
72
+ mergeItem(key, _changes, modifiedData) {
73
+ return provider.multiMerge([[key, modifiedData]]);
74
+ },
75
+
76
+ /**
77
+ * Stores multiple key-value pairs in a batch
78
+ * @param {Array<[key, value]>} pairs
79
+ * @return {Promise<void>}
80
+ */
81
+ multiSet: pairs => setMany(pairs, getCustomStore()),
82
+
83
+ /**
84
+ * Clear everything from storage and also stops the SyncQueue from adding anything more to storage
85
+ * @returns {Promise<void>}
86
+ */
87
+ clear: () => clear(getCustomStore()),
88
+
89
+ /**
90
+ * Returns all keys available in storage
91
+ * @returns {Promise<String[]>}
92
+ */
93
+ getAllKeys: () => keys(getCustomStore()),
94
+
95
+ /**
96
+ * Get the value of a given key or return `null` if it's not available in storage
97
+ * @param {String} key
98
+ * @return {Promise<*>}
99
+ */
100
+ getItem: key => get(key, getCustomStore()),
101
+
102
+ /**
103
+ * Remove given key and it's value from storage
104
+ * @param {String} key
105
+ * @returns {Promise<void>}
106
+ */
107
+ removeItem: key => del(key, getCustomStore()),
108
+
109
+ /**
110
+ * Remove given keys and their values from storage
111
+ *
112
+ * @param {Array} keysParam
113
+ * @returns {Promise}
114
+ */
115
+ removeItems: keysParam => delMany(keysParam, getCustomStore()),
116
+ };
117
+
118
+ export default provider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-onyx",
3
- "version": "1.0.61",
3
+ "version": "1.0.63",
4
4
  "author": "Expensify, Inc.",
5
5
  "homepage": "https://expensify.com",
6
6
  "description": "State management for React Native",
@@ -59,10 +59,10 @@
59
59
  "eslint-config-expensify": "^2.0.38",
60
60
  "eslint-plugin-jsx-a11y": "^6.6.1",
61
61
  "eslint-plugin-react": "^7.31.10",
62
+ "idb-keyval": "^6.2.1",
62
63
  "jest": "^26.5.2",
63
64
  "jest-cli": "^26.5.2",
64
65
  "jsdoc-to-markdown": "^7.1.0",
65
- "localforage": "^1.10.0",
66
66
  "metro-react-native-babel-preset": "^0.72.3",
67
67
  "prop-types": "^15.7.2",
68
68
  "react": "18.2.0",
@@ -76,23 +76,19 @@
76
76
  "webpack-merge": "^5.8.0"
77
77
  },
78
78
  "peerDependencies": {
79
- "localforage": "^1.10.0",
80
- "localforage-removeitems": "^1.4.0",
79
+ "idb-keyval": "^6.2.1",
81
80
  "react": ">=18.1.0",
82
81
  "react-native-performance": "^4.0.0",
83
82
  "react-native-quick-sqlite": "^8.0.0-beta.2"
84
83
  },
85
84
  "peerDependenciesMeta": {
86
- "react-native-performance": {
87
- "optional": true
88
- },
89
- "react-native-quick-sqlite": {
85
+ "idb-keyval": {
90
86
  "optional": true
91
87
  },
92
- "localforage": {
88
+ "react-native-performance": {
93
89
  "optional": true
94
90
  },
95
- "localforage-removeitems": {
91
+ "react-native-quick-sqlite": {
96
92
  "optional": true
97
93
  }
98
94
  },
@@ -1,158 +0,0 @@
1
- /**
2
- * @file
3
- * The storage provider based on localforage allows us to store most anything in its
4
- * natural form in the underlying DB without having to stringify or de-stringify it
5
- */
6
-
7
- import localforage from 'localforage';
8
- import _ from 'underscore';
9
- import {extendPrototype} from 'localforage-removeitems';
10
- import SyncQueue from '../../SyncQueue';
11
- import * as Str from '../../Str';
12
- import fastMerge from '../../fastMerge';
13
-
14
- extendPrototype(localforage);
15
-
16
- localforage.config({
17
- name: 'OnyxDB',
18
- });
19
-
20
- /**
21
- * Keys that will not ever be persisted to disk.
22
- */
23
- let memoryOnlyKeys = [];
24
-
25
- const provider = {
26
- /**
27
- * Writing very quickly to IndexedDB causes performance issues and can lock up the page and lead to jank.
28
- * So, we are slowing this process down by waiting until one write is complete before moving on
29
- * to the next.
30
- */
31
- setItemQueue: new SyncQueue(({key, value, shouldMerge}) => {
32
- if (_.find(memoryOnlyKeys, noCacheKey => Str.startsWith(key, noCacheKey))) {
33
- return Promise.resolve();
34
- }
35
-
36
- if (shouldMerge) {
37
- return localforage.getItem(key)
38
- .then((existingValue) => {
39
- const newValue = _.isObject(existingValue)
40
-
41
- // lodash adds a small overhead so we don't use it here
42
- // eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
43
- ? Object.assign({}, fastMerge(existingValue, value))
44
- : value;
45
- return localforage.setItem(key, newValue);
46
- });
47
- }
48
-
49
- return localforage.setItem(key, value);
50
- }),
51
-
52
- /**
53
- * Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
54
- * @param {String} key
55
- * @param {*} value
56
- * @return {Promise<void>}
57
- */
58
- setItem(key, value) {
59
- return this.setItemQueue.push({key, value});
60
- },
61
-
62
- /**
63
- * Get multiple key-value pairs for the give array of keys in a batch
64
- * @param {String[]} keys
65
- * @return {Promise<Array<[key, value]>>}
66
- */
67
- multiGet(keys) {
68
- const pairs = _.map(
69
- keys,
70
- key => localforage.getItem(key)
71
- .then(value => [key, value]),
72
- );
73
-
74
- return Promise.all(pairs);
75
- },
76
-
77
- /**
78
- * Multiple merging of existing and new values in a batch
79
- * @param {Array<[key, value]>} pairs
80
- * @return {Promise<void>}
81
- */
82
- multiMerge(pairs) {
83
- const tasks = _.map(pairs, ([key, value]) => this.setItemQueue.push({key, value, shouldMerge: true}));
84
-
85
- // We're returning Promise.resolve, otherwise the array of task results will be returned to the caller
86
- return Promise.all(tasks).then(() => Promise.resolve());
87
- },
88
-
89
- /**
90
- * Merging an existing value with a new one
91
- * @param {String} key
92
- * @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
93
- * @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
94
- * @return {Promise<void>}
95
- */
96
- mergeItem(key, _changes, modifiedData) {
97
- return this.setItem(key, modifiedData);
98
- },
99
-
100
- /**
101
- * Stores multiple key-value pairs in a batch
102
- * @param {Array<[key, value]>} pairs
103
- * @return {Promise<void>}
104
- */
105
- multiSet(pairs) {
106
- // We're returning Promise.resolve, otherwise the array of task results will be returned to the caller
107
- const tasks = _.map(pairs, ([key, value]) => this.setItem(key, value));
108
- return Promise.all(tasks).then(() => Promise.resolve());
109
- },
110
-
111
- /**
112
- * Clear everything from storage and also stops the SyncQueue from adding anything more to storage
113
- * @returns {Promise<void>}
114
- */
115
- clear() {
116
- this.setItemQueue.abort();
117
- return localforage.clear();
118
- },
119
-
120
- /**
121
- * Returns all keys available in storage
122
- * @returns {Promise<String[]>}
123
- */
124
- getAllKeys: localforage.keys,
125
-
126
- /**
127
- * Get the value of a given key or return `null` if it's not available in storage
128
- * @param {String} key
129
- * @return {Promise<*>}
130
- */
131
- getItem: localforage.getItem,
132
-
133
- /**
134
- * Remove given key and it's value from storage
135
- * @param {String} key
136
- * @returns {Promise<void>}
137
- */
138
- removeItem: localforage.removeItem,
139
-
140
- /**
141
- * Remove given keys and their values from storage
142
- *
143
- * @param {Array} keys
144
- * @returns {Promise}
145
- */
146
- removeItems(keys) {
147
- return localforage.removeItems(keys);
148
- },
149
-
150
- /**
151
- * @param {string[]} keyList
152
- */
153
- setMemoryOnlyKeys(keyList) {
154
- memoryOnlyKeys = keyList;
155
- },
156
- };
157
-
158
- export default provider;