react-native-onyx 2.0.4 → 2.0.5
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/Onyx.js +2 -2
- package/dist/OnyxCache.d.ts +34 -81
- package/dist/OnyxCache.js +23 -82
- package/dist/storage/NativeStorage.d.ts +1 -1
- package/dist/storage/WebStorage.d.ts +2 -18
- package/dist/storage/WebStorage.js +4 -6
- package/dist/storage/__mocks__/index.d.ts +21 -21
- package/dist/storage/__mocks__/index.js +7 -8
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.native.d.ts +1 -1
- package/dist/storage/providers/IDBKeyVal.d.ts +2 -25
- package/dist/storage/providers/IDBKeyVal.js +14 -69
- package/dist/storage/providers/SQLiteStorage.d.ts +2 -51
- package/dist/storage/providers/SQLiteStorage.js +19 -81
- package/dist/storage/providers/types.d.ts +68 -0
- package/dist/storage/providers/types.js +2 -0
- package/dist/utils.d.ts +16 -9
- package/dist/utils.js +31 -42
- package/dist/withOnyx.js +1 -1
- package/package.json +3 -1
package/dist/Onyx.js
CHANGED
|
@@ -486,7 +486,7 @@ function keysChanged(collectionKey, partialCollection, notifyRegularSubscibers =
|
|
|
486
486
|
const data = cachedCollection[subscriber.key];
|
|
487
487
|
const previousData = prevState[subscriber.statePropertyName];
|
|
488
488
|
// Avoids triggering unnecessary re-renders when feeding empty objects
|
|
489
|
-
if (utils_1.default.
|
|
489
|
+
if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(previousData)) {
|
|
490
490
|
return null;
|
|
491
491
|
}
|
|
492
492
|
if (data === previousData) {
|
|
@@ -601,7 +601,7 @@ function keyChanged(key, data, prevData, canUpdateSubscriber = () => true, notif
|
|
|
601
601
|
subscriber.withOnyxInstance.setStateProxy((prevState) => {
|
|
602
602
|
const prevWithOnyxData = prevState[subscriber.statePropertyName];
|
|
603
603
|
// Avoids triggering unnecessary re-renders when feeding empty objects
|
|
604
|
-
if (utils_1.default.
|
|
604
|
+
if (utils_1.default.isEmptyObject(data) && utils_1.default.isEmptyObject(prevWithOnyxData)) {
|
|
605
605
|
return null;
|
|
606
606
|
}
|
|
607
607
|
if (prevWithOnyxData === data) {
|
package/dist/OnyxCache.d.ts
CHANGED
|
@@ -1,122 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { Key, Value } from './storage/providers/types';
|
|
2
|
+
type StorageMap = Record<Key, Value>;
|
|
3
3
|
/**
|
|
4
4
|
* In memory cache providing data by reference
|
|
5
5
|
* Encapsulates Onyx cache related functionality
|
|
6
6
|
*/
|
|
7
7
|
declare class OnyxCache {
|
|
8
|
-
/**
|
|
9
|
-
* @private
|
|
10
|
-
* Cache of all the storage keys available in persistent storage
|
|
11
|
-
* @type {Set<string>}
|
|
12
|
-
*/
|
|
8
|
+
/** Cache of all the storage keys available in persistent storage */
|
|
13
9
|
private storageKeys;
|
|
14
|
-
/**
|
|
15
|
-
* @private
|
|
16
|
-
* Unique list of keys maintained in access order (most recent at the end)
|
|
17
|
-
* @type {Set<string>}
|
|
18
|
-
*/
|
|
10
|
+
/** Unique list of keys maintained in access order (most recent at the end) */
|
|
19
11
|
private recentKeys;
|
|
20
|
-
/**
|
|
21
|
-
* @private
|
|
22
|
-
* A map of cached values
|
|
23
|
-
* @type {Record<string, *>}
|
|
24
|
-
*/
|
|
12
|
+
/** A map of cached values */
|
|
25
13
|
private storageMap;
|
|
26
14
|
/**
|
|
27
|
-
* @private
|
|
28
15
|
* Captured pending tasks for already running storage methods
|
|
29
16
|
* Using a map yields better performance on operations such a delete
|
|
30
|
-
* https://www.zhenghao.io/posts/object-vs-map
|
|
31
|
-
* @type {Map<string, Promise>}
|
|
32
17
|
*/
|
|
33
18
|
private pendingPromises;
|
|
34
|
-
/**
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
getAllKeys():
|
|
19
|
+
/** Maximum size of the keys store din cache */
|
|
20
|
+
private maxRecentKeysSize;
|
|
21
|
+
constructor();
|
|
22
|
+
/** Get all the storage keys */
|
|
23
|
+
getAllKeys(): Key[];
|
|
39
24
|
/**
|
|
40
25
|
* Get a cached value from storage
|
|
41
|
-
* @param
|
|
42
|
-
* @param {Boolean} [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
|
|
43
|
-
* @returns {*}
|
|
26
|
+
* @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
|
|
44
27
|
*/
|
|
45
|
-
getValue(key:
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
* @returns {boolean}
|
|
50
|
-
*/
|
|
51
|
-
hasCacheForKey(key: string): boolean;
|
|
52
|
-
/**
|
|
53
|
-
* Saves a key in the storage keys list
|
|
28
|
+
getValue(key: Key, shouldReindexCache?: boolean): Value;
|
|
29
|
+
/** Check whether cache has data for the given key */
|
|
30
|
+
hasCacheForKey(key: Key): boolean;
|
|
31
|
+
/** Saves a key in the storage keys list
|
|
54
32
|
* Serves to keep the result of `getAllKeys` up to date
|
|
55
|
-
* @param {string} key
|
|
56
33
|
*/
|
|
57
|
-
addKey(key:
|
|
34
|
+
addKey(key: Key): void;
|
|
58
35
|
/**
|
|
59
36
|
* Set's a key value in cache
|
|
60
37
|
* Adds the key to the storage keys list as well
|
|
61
|
-
* @param {string} key
|
|
62
|
-
* @param {*} value
|
|
63
|
-
* @returns {*} value - returns the cache value
|
|
64
38
|
*/
|
|
65
|
-
set(key:
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
* @param {string} key
|
|
69
|
-
*/
|
|
70
|
-
drop(key: string): void;
|
|
39
|
+
set(key: Key, value: Value): Value;
|
|
40
|
+
/** Forget the cached value for the given key */
|
|
41
|
+
drop(key: Key): void;
|
|
71
42
|
/**
|
|
72
43
|
* Deep merge data to cache, any non existing keys will be created
|
|
73
|
-
* @param
|
|
44
|
+
* @param data - a map of (cache) key - values
|
|
74
45
|
*/
|
|
75
|
-
merge(data:
|
|
46
|
+
merge(data: StorageMap): void;
|
|
76
47
|
/**
|
|
77
48
|
* Check whether the given task is already running
|
|
78
|
-
* @param
|
|
79
|
-
* @returns {*}
|
|
49
|
+
* @param taskName - unique name given for the task
|
|
80
50
|
*/
|
|
81
|
-
hasPendingTask(taskName: string):
|
|
51
|
+
hasPendingTask(taskName: string): boolean;
|
|
82
52
|
/**
|
|
83
53
|
* Use this method to prevent concurrent calls for the same thing
|
|
84
54
|
* Instead of calling the same task again use the existing promise
|
|
85
55
|
* provided from this function
|
|
86
|
-
* @
|
|
87
|
-
* @param {string} taskName - unique name given for the task
|
|
88
|
-
* @returns {Promise<T>}
|
|
56
|
+
* @param taskName - unique name given for the task
|
|
89
57
|
*/
|
|
90
|
-
getTaskPromise
|
|
58
|
+
getTaskPromise(taskName: string): Promise<unknown> | undefined;
|
|
91
59
|
/**
|
|
92
60
|
* Capture a promise for a given task so other caller can
|
|
93
61
|
* hook up to the promise if it's still pending
|
|
94
|
-
* @
|
|
95
|
-
* @param {string} taskName - unique name for the task
|
|
96
|
-
* @param {Promise<T>} promise
|
|
97
|
-
* @returns {Promise<T>}
|
|
98
|
-
*/
|
|
99
|
-
captureTask<T_1>(taskName: string, promise: Promise<T_1>): Promise<T_1>;
|
|
100
|
-
/**
|
|
101
|
-
* @private
|
|
102
|
-
* Adds a key to the top of the recently accessed keys
|
|
103
|
-
* @param {string} key
|
|
62
|
+
* @param taskName - unique name for the task
|
|
104
63
|
*/
|
|
64
|
+
captureTask(taskName: string, promise: Promise<unknown>): Promise<unknown>;
|
|
65
|
+
/** Adds a key to the top of the recently accessed keys */
|
|
105
66
|
private addToAccessedKeys;
|
|
106
|
-
/**
|
|
107
|
-
* Remove keys that don't fall into the range of recently used keys
|
|
108
|
-
*/
|
|
67
|
+
/** Remove keys that don't fall into the range of recently used keys */
|
|
109
68
|
removeLeastRecentlyUsedKeys(): void;
|
|
110
|
-
/**
|
|
111
|
-
* Set the recent keys list size
|
|
112
|
-
* @param {number} limit
|
|
113
|
-
*/
|
|
69
|
+
/** Set the recent keys list size */
|
|
114
70
|
setRecentKeysLimit(limit: number): void;
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
* @param {String} key
|
|
118
|
-
* @param {*} value
|
|
119
|
-
* @returns {Boolean}
|
|
120
|
-
*/
|
|
121
|
-
hasValueChanged(key: string, value: any): boolean;
|
|
71
|
+
/** Check if the value has changed */
|
|
72
|
+
hasValueChanged(key: Key, value: Value): boolean;
|
|
122
73
|
}
|
|
74
|
+
declare const instance: OnyxCache;
|
|
75
|
+
export default instance;
|
package/dist/OnyxCache.js
CHANGED
|
@@ -3,57 +3,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const underscore_1 = __importDefault(require("underscore"));
|
|
7
6
|
const fast_equals_1 = require("fast-equals");
|
|
7
|
+
const bindAll_1 = __importDefault(require("lodash/bindAll"));
|
|
8
8
|
const utils_1 = __importDefault(require("./utils"));
|
|
9
|
-
const isDefined = underscore_1.default.negate(underscore_1.default.isUndefined);
|
|
10
9
|
/**
|
|
11
10
|
* In memory cache providing data by reference
|
|
12
11
|
* Encapsulates Onyx cache related functionality
|
|
13
12
|
*/
|
|
14
13
|
class OnyxCache {
|
|
15
14
|
constructor() {
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
* Cache of all the storage keys available in persistent storage
|
|
19
|
-
* @type {Set<string>}
|
|
20
|
-
*/
|
|
15
|
+
/** Maximum size of the keys store din cache */
|
|
16
|
+
this.maxRecentKeysSize = 0;
|
|
21
17
|
this.storageKeys = new Set();
|
|
22
|
-
/**
|
|
23
|
-
* @private
|
|
24
|
-
* Unique list of keys maintained in access order (most recent at the end)
|
|
25
|
-
* @type {Set<string>}
|
|
26
|
-
*/
|
|
27
18
|
this.recentKeys = new Set();
|
|
28
|
-
/**
|
|
29
|
-
* @private
|
|
30
|
-
* A map of cached values
|
|
31
|
-
* @type {Record<string, *>}
|
|
32
|
-
*/
|
|
33
19
|
this.storageMap = {};
|
|
34
|
-
/**
|
|
35
|
-
* @private
|
|
36
|
-
* Captured pending tasks for already running storage methods
|
|
37
|
-
* Using a map yields better performance on operations such a delete
|
|
38
|
-
* https://www.zhenghao.io/posts/object-vs-map
|
|
39
|
-
* @type {Map<string, Promise>}
|
|
40
|
-
*/
|
|
41
20
|
this.pendingPromises = new Map();
|
|
42
21
|
// bind all public methods to prevent problems with `this`
|
|
43
|
-
|
|
22
|
+
(0, bindAll_1.default)(this, 'getAllKeys', 'getValue', 'hasCacheForKey', 'addKey', 'set', 'drop', 'merge', 'hasPendingTask', 'getTaskPromise', 'captureTask', 'removeLeastRecentlyUsedKeys', 'setRecentKeysLimit');
|
|
44
23
|
}
|
|
45
|
-
/**
|
|
46
|
-
* Get all the storage keys
|
|
47
|
-
* @returns {string[]}
|
|
48
|
-
*/
|
|
24
|
+
/** Get all the storage keys */
|
|
49
25
|
getAllKeys() {
|
|
50
26
|
return Array.from(this.storageKeys);
|
|
51
27
|
}
|
|
52
28
|
/**
|
|
53
29
|
* Get a cached value from storage
|
|
54
|
-
* @param
|
|
55
|
-
* @param {Boolean} [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
|
|
56
|
-
* @returns {*}
|
|
30
|
+
* @param [shouldReindexCache] – This is an LRU cache, and by default accessing a value will make it become last in line to be evicted. This flag can be used to skip that and just access the value directly without side-effects.
|
|
57
31
|
*/
|
|
58
32
|
getValue(key, shouldReindexCache = true) {
|
|
59
33
|
if (shouldReindexCache) {
|
|
@@ -61,18 +35,12 @@ class OnyxCache {
|
|
|
61
35
|
}
|
|
62
36
|
return this.storageMap[key];
|
|
63
37
|
}
|
|
64
|
-
/**
|
|
65
|
-
* Check whether cache has data for the given key
|
|
66
|
-
* @param {string} key
|
|
67
|
-
* @returns {boolean}
|
|
68
|
-
*/
|
|
38
|
+
/** Check whether cache has data for the given key */
|
|
69
39
|
hasCacheForKey(key) {
|
|
70
|
-
return
|
|
40
|
+
return this.storageMap[key] !== undefined;
|
|
71
41
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Saves a key in the storage keys list
|
|
42
|
+
/** Saves a key in the storage keys list
|
|
74
43
|
* Serves to keep the result of `getAllKeys` up to date
|
|
75
|
-
* @param {string} key
|
|
76
44
|
*/
|
|
77
45
|
addKey(key) {
|
|
78
46
|
this.storageKeys.add(key);
|
|
@@ -80,9 +48,6 @@ class OnyxCache {
|
|
|
80
48
|
/**
|
|
81
49
|
* Set's a key value in cache
|
|
82
50
|
* Adds the key to the storage keys list as well
|
|
83
|
-
* @param {string} key
|
|
84
|
-
* @param {*} value
|
|
85
|
-
* @returns {*} value - returns the cache value
|
|
86
51
|
*/
|
|
87
52
|
set(key, value) {
|
|
88
53
|
this.addKey(key);
|
|
@@ -90,10 +55,7 @@ class OnyxCache {
|
|
|
90
55
|
this.storageMap[key] = value;
|
|
91
56
|
return value;
|
|
92
57
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Forget the cached value for the given key
|
|
95
|
-
* @param {string} key
|
|
96
|
-
*/
|
|
58
|
+
/** Forget the cached value for the given key */
|
|
97
59
|
drop(key) {
|
|
98
60
|
delete this.storageMap[key];
|
|
99
61
|
this.storageKeys.delete(key);
|
|
@@ -101,35 +63,30 @@ class OnyxCache {
|
|
|
101
63
|
}
|
|
102
64
|
/**
|
|
103
65
|
* Deep merge data to cache, any non existing keys will be created
|
|
104
|
-
* @param
|
|
66
|
+
* @param data - a map of (cache) key - values
|
|
105
67
|
*/
|
|
106
68
|
merge(data) {
|
|
107
|
-
if (
|
|
69
|
+
if (typeof data !== 'object' || Array.isArray(data)) {
|
|
108
70
|
throw new Error('data passed to cache.merge() must be an Object of onyx key/value pairs');
|
|
109
71
|
}
|
|
110
|
-
// lodash adds a small overhead so we don't use it here
|
|
111
|
-
// eslint-disable-next-line prefer-object-spread, rulesdir/prefer-underscore-method
|
|
112
72
|
this.storageMap = Object.assign({}, utils_1.default.fastMerge(this.storageMap, data, false));
|
|
113
73
|
const storageKeys = this.getAllKeys();
|
|
114
|
-
const mergedKeys =
|
|
74
|
+
const mergedKeys = Object.keys(data);
|
|
115
75
|
this.storageKeys = new Set([...storageKeys, ...mergedKeys]);
|
|
116
|
-
|
|
76
|
+
mergedKeys.forEach((key) => this.addToAccessedKeys(key));
|
|
117
77
|
}
|
|
118
78
|
/**
|
|
119
79
|
* Check whether the given task is already running
|
|
120
|
-
* @param
|
|
121
|
-
* @returns {*}
|
|
80
|
+
* @param taskName - unique name given for the task
|
|
122
81
|
*/
|
|
123
82
|
hasPendingTask(taskName) {
|
|
124
|
-
return
|
|
83
|
+
return this.pendingPromises.get(taskName) !== undefined;
|
|
125
84
|
}
|
|
126
85
|
/**
|
|
127
86
|
* Use this method to prevent concurrent calls for the same thing
|
|
128
87
|
* Instead of calling the same task again use the existing promise
|
|
129
88
|
* provided from this function
|
|
130
|
-
* @
|
|
131
|
-
* @param {string} taskName - unique name given for the task
|
|
132
|
-
* @returns {Promise<T>}
|
|
89
|
+
* @param taskName - unique name given for the task
|
|
133
90
|
*/
|
|
134
91
|
getTaskPromise(taskName) {
|
|
135
92
|
return this.pendingPromises.get(taskName);
|
|
@@ -137,10 +94,7 @@ class OnyxCache {
|
|
|
137
94
|
/**
|
|
138
95
|
* Capture a promise for a given task so other caller can
|
|
139
96
|
* hook up to the promise if it's still pending
|
|
140
|
-
* @
|
|
141
|
-
* @param {string} taskName - unique name for the task
|
|
142
|
-
* @param {Promise<T>} promise
|
|
143
|
-
* @returns {Promise<T>}
|
|
97
|
+
* @param taskName - unique name for the task
|
|
144
98
|
*/
|
|
145
99
|
captureTask(taskName, promise) {
|
|
146
100
|
const returnPromise = promise.finally(() => {
|
|
@@ -149,19 +103,12 @@ class OnyxCache {
|
|
|
149
103
|
this.pendingPromises.set(taskName, returnPromise);
|
|
150
104
|
return returnPromise;
|
|
151
105
|
}
|
|
152
|
-
/**
|
|
153
|
-
* @private
|
|
154
|
-
* Adds a key to the top of the recently accessed keys
|
|
155
|
-
* @param {string} key
|
|
156
|
-
*/
|
|
106
|
+
/** Adds a key to the top of the recently accessed keys */
|
|
157
107
|
addToAccessedKeys(key) {
|
|
158
|
-
// Removing and re-adding a key ensures it's at the end of the list
|
|
159
108
|
this.recentKeys.delete(key);
|
|
160
109
|
this.recentKeys.add(key);
|
|
161
110
|
}
|
|
162
|
-
/**
|
|
163
|
-
* Remove keys that don't fall into the range of recently used keys
|
|
164
|
-
*/
|
|
111
|
+
/** Remove keys that don't fall into the range of recently used keys */
|
|
165
112
|
removeLeastRecentlyUsedKeys() {
|
|
166
113
|
let numKeysToRemove = this.recentKeys.size - this.maxRecentKeysSize;
|
|
167
114
|
if (numKeysToRemove <= 0) {
|
|
@@ -174,23 +121,17 @@ class OnyxCache {
|
|
|
174
121
|
temp.push(value);
|
|
175
122
|
numKeysToRemove--;
|
|
176
123
|
}
|
|
124
|
+
// eslint-disable-next-line @typescript-eslint/prefer-for-of
|
|
177
125
|
for (let i = 0; i < temp.length; ++i) {
|
|
178
126
|
delete this.storageMap[temp[i]];
|
|
179
127
|
this.recentKeys.delete(temp[i]);
|
|
180
128
|
}
|
|
181
129
|
}
|
|
182
|
-
/**
|
|
183
|
-
* Set the recent keys list size
|
|
184
|
-
* @param {number} limit
|
|
185
|
-
*/
|
|
130
|
+
/** Set the recent keys list size */
|
|
186
131
|
setRecentKeysLimit(limit) {
|
|
187
132
|
this.maxRecentKeysSize = limit;
|
|
188
133
|
}
|
|
189
|
-
/**
|
|
190
|
-
* @param {String} key
|
|
191
|
-
* @param {*} value
|
|
192
|
-
* @returns {Boolean}
|
|
193
|
-
*/
|
|
134
|
+
/** Check if the value has changed */
|
|
194
135
|
hasValueChanged(key, value) {
|
|
195
136
|
return !(0, fast_equals_1.deepEqual)(this.storageMap[key], value);
|
|
196
137
|
}
|
|
@@ -1,19 +1,3 @@
|
|
|
1
|
+
import type StorageProvider from './providers/types';
|
|
2
|
+
declare const webStorage: StorageProvider;
|
|
1
3
|
export default webStorage;
|
|
2
|
-
declare const webStorage: {
|
|
3
|
-
/**
|
|
4
|
-
* @param {Function} onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
|
|
5
|
-
*/
|
|
6
|
-
keepInstancesSync(onStorageKeyChanged: Function): void;
|
|
7
|
-
setItem: (key: string, value: any) => Promise<void>;
|
|
8
|
-
multiGet: (keysParam: string[]) => Promise<[key, value][]>;
|
|
9
|
-
multiMerge: (pairs: [key, value][]) => Promise<void>;
|
|
10
|
-
mergeItem(key: string, _changes: any, modifiedData: any): Promise<void>;
|
|
11
|
-
multiSet: (pairs: [key, value][]) => Promise<void>;
|
|
12
|
-
clear: () => Promise<void>;
|
|
13
|
-
setMemoryOnlyKeys: () => void;
|
|
14
|
-
getAllKeys: () => Promise<string[]>;
|
|
15
|
-
getItem: (key: string) => Promise<any>;
|
|
16
|
-
removeItem: (key: string) => Promise<void>;
|
|
17
|
-
removeItems: (keysParam: any[]) => Promise<any>;
|
|
18
|
-
getDatabaseSize(): Promise<number>;
|
|
19
|
-
};
|
|
@@ -8,25 +8,23 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
8
8
|
* when using LocalStorage APIs in the browser. These events are great because multiple tabs can listen for when
|
|
9
9
|
* data changes and then stay up-to-date with everything happening in Onyx.
|
|
10
10
|
*/
|
|
11
|
-
const underscore_1 = __importDefault(require("underscore"));
|
|
12
11
|
const IDBKeyVal_1 = __importDefault(require("./providers/IDBKeyVal"));
|
|
13
12
|
const SYNC_ONYX = 'SYNC_ONYX';
|
|
14
13
|
/**
|
|
15
14
|
* Raise an event thorough `localStorage` to let other tabs know a value changed
|
|
16
|
-
* @param {String} onyxKey
|
|
17
15
|
*/
|
|
18
16
|
function raiseStorageSyncEvent(onyxKey) {
|
|
19
17
|
global.localStorage.setItem(SYNC_ONYX, onyxKey);
|
|
20
|
-
global.localStorage.removeItem(SYNC_ONYX
|
|
18
|
+
global.localStorage.removeItem(SYNC_ONYX);
|
|
21
19
|
}
|
|
22
20
|
function raiseStorageSyncManyKeysEvent(onyxKeys) {
|
|
23
|
-
|
|
21
|
+
onyxKeys.forEach((onyxKey) => {
|
|
24
22
|
raiseStorageSyncEvent(onyxKey);
|
|
25
23
|
});
|
|
26
24
|
}
|
|
27
25
|
const webStorage = Object.assign(Object.assign({}, IDBKeyVal_1.default), {
|
|
28
26
|
/**
|
|
29
|
-
* @param
|
|
27
|
+
* @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
|
|
30
28
|
*/
|
|
31
29
|
keepInstancesSync(onStorageKeyChanged) {
|
|
32
30
|
// Override set, remove and clear to raise storage events that we intercept in other tabs
|
|
@@ -48,7 +46,7 @@ const webStorage = Object.assign(Object.assign({}, IDBKeyVal_1.default), {
|
|
|
48
46
|
.then(() => {
|
|
49
47
|
// Now that storage is cleared, the storage sync event can happen which is a more atomic action
|
|
50
48
|
// for other browser tabs
|
|
51
|
-
|
|
49
|
+
allKeys.forEach(raiseStorageSyncEvent);
|
|
52
50
|
});
|
|
53
51
|
};
|
|
54
52
|
// This listener will only be triggered by events coming from other tabs
|
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
bytesRemaining: number;
|
|
1
|
+
/// <reference types="jest" />
|
|
2
|
+
import type { KeyValuePairList } from '../providers/types';
|
|
3
|
+
declare const idbKeyvalMockSpy: {
|
|
4
|
+
idbKeyvalSet: jest.Mock<Promise<any>, [key: any, value: any]>;
|
|
5
|
+
setItem: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, [key: string, value: IDBValidKey]>;
|
|
6
|
+
getItem: jest.Mock<Promise<IDBValidKey | null>, [key: string]>;
|
|
7
|
+
removeItem: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, [key: string]>;
|
|
8
|
+
removeItems: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, [keys: import("../providers/types").KeyList]>;
|
|
9
|
+
clear: jest.Mock<Promise<void | import("react-native-quick-sqlite").QueryResult>, []>;
|
|
10
|
+
getAllKeys: jest.Mock<Promise<import("../providers/types").KeyList>, []>;
|
|
11
|
+
multiGet: jest.Mock<Promise<KeyValuePairList>, [keys: import("../providers/types").KeyList]>;
|
|
12
|
+
multiSet: jest.Mock<Promise<void | import("react-native-quick-sqlite").BatchQueryResult>, [pairs: KeyValuePairList]>;
|
|
13
|
+
multiMerge: jest.Mock<Promise<import("react-native-quick-sqlite").BatchQueryResult | IDBValidKey[]>, [pairs: KeyValuePairList]>;
|
|
14
|
+
mergeItem: jest.Mock<Promise<void | import("react-native-quick-sqlite").BatchQueryResult>, [key: string, changes: IDBValidKey, modifiedData: IDBValidKey]>;
|
|
15
|
+
getStorageMap: jest.Mock<Record<string, IDBValidKey>, []>;
|
|
16
|
+
setInitialMockData: jest.Mock<void, [data: any]>;
|
|
17
|
+
getDatabaseSize: jest.Mock<Promise<{
|
|
19
18
|
bytesUsed: number;
|
|
19
|
+
bytesRemaining: number;
|
|
20
20
|
}>, []>;
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
21
|
+
setMemoryOnlyKeys: jest.Mock<void, []>;
|
|
22
|
+
};
|
|
23
|
+
export default idbKeyvalMockSpy;
|
|
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
const underscore_1 = __importDefault(require("underscore"));
|
|
7
6
|
const utils_1 = __importDefault(require("../../utils"));
|
|
8
7
|
let storageMapInternal = {};
|
|
9
8
|
const set = jest.fn((key, value) => {
|
|
@@ -15,19 +14,20 @@ const idbKeyvalMock = {
|
|
|
15
14
|
return set(key, value);
|
|
16
15
|
},
|
|
17
16
|
multiSet(pairs) {
|
|
18
|
-
const setPromises =
|
|
17
|
+
const setPromises = pairs.map(([key, value]) => this.setItem(key, value));
|
|
19
18
|
return new Promise((resolve) => Promise.all(setPromises).then(() => resolve(storageMapInternal)));
|
|
20
19
|
},
|
|
21
20
|
getItem(key) {
|
|
22
21
|
return Promise.resolve(storageMapInternal[key]);
|
|
23
22
|
},
|
|
24
23
|
multiGet(keys) {
|
|
25
|
-
const getPromises =
|
|
24
|
+
const getPromises = keys.map((key) => new Promise((resolve) => this.getItem(key).then((value) => resolve([key, value]))));
|
|
26
25
|
return Promise.all(getPromises);
|
|
27
26
|
},
|
|
28
27
|
multiMerge(pairs) {
|
|
29
|
-
|
|
28
|
+
pairs.forEach(([key, value]) => {
|
|
30
29
|
const existingValue = storageMapInternal[key];
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
31
|
const newValue = utils_1.default.fastMerge(existingValue, value);
|
|
32
32
|
set(key, newValue);
|
|
33
33
|
});
|
|
@@ -41,7 +41,7 @@ const idbKeyvalMock = {
|
|
|
41
41
|
return Promise.resolve();
|
|
42
42
|
},
|
|
43
43
|
removeItems(keys) {
|
|
44
|
-
|
|
44
|
+
keys.forEach((key) => {
|
|
45
45
|
delete storageMapInternal[key];
|
|
46
46
|
});
|
|
47
47
|
return Promise.resolve();
|
|
@@ -51,12 +51,12 @@ const idbKeyvalMock = {
|
|
|
51
51
|
return Promise.resolve();
|
|
52
52
|
},
|
|
53
53
|
getAllKeys() {
|
|
54
|
-
return Promise.resolve(
|
|
54
|
+
return Promise.resolve(Object.keys(storageMapInternal));
|
|
55
55
|
},
|
|
56
|
-
config() { },
|
|
57
56
|
getDatabaseSize() {
|
|
58
57
|
return Promise.resolve({ bytesRemaining: 0, bytesUsed: 99999 });
|
|
59
58
|
},
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
60
60
|
setMemoryOnlyKeys() { },
|
|
61
61
|
};
|
|
62
62
|
const idbKeyvalMockSpy = {
|
|
@@ -67,7 +67,6 @@ const idbKeyvalMockSpy = {
|
|
|
67
67
|
removeItems: jest.fn(idbKeyvalMock.removeItems),
|
|
68
68
|
clear: jest.fn(idbKeyvalMock.clear),
|
|
69
69
|
getAllKeys: jest.fn(idbKeyvalMock.getAllKeys),
|
|
70
|
-
config: jest.fn(idbKeyvalMock.config),
|
|
71
70
|
multiGet: jest.fn(idbKeyvalMock.multiGet),
|
|
72
71
|
multiSet: jest.fn(idbKeyvalMock.multiSet),
|
|
73
72
|
multiMerge: jest.fn(idbKeyvalMock.multiMerge),
|
package/dist/storage/index.d.ts
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
|
+
import type StorageProvider from './types';
|
|
2
|
+
declare const provider: StorageProvider;
|
|
1
3
|
export default provider;
|
|
2
|
-
declare namespace provider {
|
|
3
|
-
function setItem(key: string, value: any): Promise<void>;
|
|
4
|
-
function multiGet(keysParam: string[]): Promise<[key, value][]>;
|
|
5
|
-
function multiMerge(pairs: [key, value][]): Promise<void>;
|
|
6
|
-
/**
|
|
7
|
-
* Merging an existing value with a new one
|
|
8
|
-
* @param {String} key
|
|
9
|
-
* @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
|
|
10
|
-
* @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
|
|
11
|
-
* @return {Promise<void>}
|
|
12
|
-
*/
|
|
13
|
-
function mergeItem(key: string, _changes: any, modifiedData: any): Promise<void>;
|
|
14
|
-
function multiSet(pairs: [key, value][]): Promise<void>;
|
|
15
|
-
function clear(): Promise<void>;
|
|
16
|
-
function setMemoryOnlyKeys(): void;
|
|
17
|
-
function getAllKeys(): Promise<string[]>;
|
|
18
|
-
function getItem(key: string): Promise<any>;
|
|
19
|
-
function removeItem(key: string): Promise<void>;
|
|
20
|
-
function removeItems(keysParam: any[]): Promise<any>;
|
|
21
|
-
/**
|
|
22
|
-
* Gets the total bytes of the database file
|
|
23
|
-
* @returns {Promise<number>}
|
|
24
|
-
*/
|
|
25
|
-
function getDatabaseSize(): Promise<number>;
|
|
26
|
-
}
|
|
@@ -4,115 +4,60 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const idb_keyval_1 = require("idb-keyval");
|
|
7
|
-
const underscore_1 = __importDefault(require("underscore"));
|
|
8
7
|
const utils_1 = __importDefault(require("../../utils"));
|
|
9
8
|
// We don't want to initialize the store while the JS bundle loads as idb-keyval will try to use global.indexedDB
|
|
10
9
|
// which might not be available in certain environments that load the bundle (e.g. electron main process).
|
|
11
10
|
let customStoreInstance;
|
|
12
|
-
|
|
11
|
+
function getCustomStore() {
|
|
13
12
|
if (!customStoreInstance) {
|
|
14
13
|
customStoreInstance = (0, idb_keyval_1.createStore)('OnyxDB', 'keyvaluepairs');
|
|
15
14
|
}
|
|
16
15
|
return customStoreInstance;
|
|
17
|
-
}
|
|
16
|
+
}
|
|
18
17
|
const provider = {
|
|
19
|
-
/**
|
|
20
|
-
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
|
|
21
|
-
* @param {String} key
|
|
22
|
-
* @param {*} value
|
|
23
|
-
* @return {Promise<void>}
|
|
24
|
-
*/
|
|
25
18
|
setItem: (key, value) => (0, idb_keyval_1.set)(key, value, getCustomStore()),
|
|
26
|
-
|
|
27
|
-
* Get multiple key-value pairs for the give array of keys in a batch.
|
|
28
|
-
* This is optimized to use only one database transaction.
|
|
29
|
-
* @param {String[]} keysParam
|
|
30
|
-
* @return {Promise<Array<[key, value]>>}
|
|
31
|
-
*/
|
|
32
|
-
multiGet: (keysParam) => (0, idb_keyval_1.getMany)(keysParam, getCustomStore()).then((values) => underscore_1.default.map(values, (value, index) => [keysParam[index], value])),
|
|
33
|
-
/**
|
|
34
|
-
* Multiple merging of existing and new values in a batch
|
|
35
|
-
* @param {Array<[key, value]>} pairs
|
|
36
|
-
* This function also removes all nested null values from an object.
|
|
37
|
-
* @return {Promise<void>}
|
|
38
|
-
*/
|
|
19
|
+
multiGet: (keysParam) => (0, idb_keyval_1.getMany)(keysParam, getCustomStore()).then((values) => values.map((value, index) => [keysParam[index], value])),
|
|
39
20
|
multiMerge: (pairs) => getCustomStore()('readwrite', (store) => {
|
|
40
21
|
// Note: we are using the manual store transaction here, to fit the read and update
|
|
41
22
|
// of the items in one transaction to achieve best performance.
|
|
42
|
-
const getValues = Promise.all(
|
|
23
|
+
const getValues = Promise.all(pairs.map(([key]) => (0, idb_keyval_1.promisifyRequest)(store.get(key))));
|
|
43
24
|
return getValues.then((values) => {
|
|
44
|
-
const upsertMany =
|
|
25
|
+
const upsertMany = pairs.map(([key, value], index) => {
|
|
45
26
|
const prev = values[index];
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
46
28
|
const newValue = utils_1.default.fastMerge(prev, value);
|
|
47
29
|
return (0, idb_keyval_1.promisifyRequest)(store.put(newValue, key));
|
|
48
30
|
});
|
|
49
31
|
return Promise.all(upsertMany);
|
|
50
32
|
});
|
|
51
33
|
}),
|
|
52
|
-
/**
|
|
53
|
-
* Merging an existing value with a new one
|
|
54
|
-
* @param {String} key
|
|
55
|
-
* @param {any} _changes - not used, as we rely on the pre-merged data from the `modifiedData`
|
|
56
|
-
* @param {any} modifiedData - the pre-merged data from `Onyx.applyMerge`
|
|
57
|
-
* @return {Promise<void>}
|
|
58
|
-
*/
|
|
59
34
|
mergeItem(key, _changes, modifiedData) {
|
|
60
35
|
// Since Onyx also merged the existing value with the changes, we can just set the value directly
|
|
61
36
|
return provider.setItem(key, modifiedData);
|
|
62
37
|
},
|
|
63
|
-
/**
|
|
64
|
-
* Stores multiple key-value pairs in a batch
|
|
65
|
-
* @param {Array<[key, value]>} pairs
|
|
66
|
-
* @return {Promise<void>}
|
|
67
|
-
*/
|
|
68
38
|
multiSet: (pairs) => (0, idb_keyval_1.setMany)(pairs, getCustomStore()),
|
|
69
|
-
/**
|
|
70
|
-
* Clear everything from storage and also stops the SyncQueue from adding anything more to storage
|
|
71
|
-
* @returns {Promise<void>}
|
|
72
|
-
*/
|
|
73
39
|
clear: () => (0, idb_keyval_1.clear)(getCustomStore()),
|
|
74
|
-
//
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
75
41
|
setMemoryOnlyKeys: () => { },
|
|
76
|
-
/**
|
|
77
|
-
* Returns all keys available in storage
|
|
78
|
-
* @returns {Promise<String[]>}
|
|
79
|
-
*/
|
|
80
42
|
getAllKeys: () => (0, idb_keyval_1.keys)(getCustomStore()),
|
|
81
|
-
/**
|
|
82
|
-
* Get the value of a given key or return `null` if it's not available in storage
|
|
83
|
-
* @param {String} key
|
|
84
|
-
* @return {Promise<*>}
|
|
85
|
-
*/
|
|
86
43
|
getItem: (key) => (0, idb_keyval_1.get)(key, getCustomStore())
|
|
87
44
|
// idb-keyval returns undefined for missing items, but this needs to return null so that idb-keyval does the same thing as SQLiteStorage.
|
|
88
45
|
.then((val) => (val === undefined ? null : val)),
|
|
89
|
-
/**
|
|
90
|
-
* Remove given key and it's value from storage
|
|
91
|
-
* @param {String} key
|
|
92
|
-
* @returns {Promise<void>}
|
|
93
|
-
*/
|
|
94
46
|
removeItem: (key) => (0, idb_keyval_1.del)(key, getCustomStore()),
|
|
95
|
-
/**
|
|
96
|
-
* Remove given keys and their values from storage
|
|
97
|
-
*
|
|
98
|
-
* @param {Array} keysParam
|
|
99
|
-
* @returns {Promise}
|
|
100
|
-
*/
|
|
101
47
|
removeItems: (keysParam) => (0, idb_keyval_1.delMany)(keysParam, getCustomStore()),
|
|
102
|
-
/**
|
|
103
|
-
* Gets the total bytes of the database file
|
|
104
|
-
* @returns {Promise<number>}
|
|
105
|
-
*/
|
|
106
48
|
getDatabaseSize() {
|
|
107
49
|
if (!window.navigator || !window.navigator.storage) {
|
|
108
50
|
throw new Error('StorageManager browser API unavailable');
|
|
109
51
|
}
|
|
110
52
|
return window.navigator.storage
|
|
111
53
|
.estimate()
|
|
112
|
-
.then((value) =>
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
54
|
+
.then((value) => {
|
|
55
|
+
var _a, _b, _c;
|
|
56
|
+
return ({
|
|
57
|
+
bytesUsed: (_a = value.usage) !== null && _a !== void 0 ? _a : 0,
|
|
58
|
+
bytesRemaining: ((_b = value.quota) !== null && _b !== void 0 ? _b : 0) - ((_c = value.usage) !== null && _c !== void 0 ? _c : 0),
|
|
59
|
+
});
|
|
60
|
+
})
|
|
116
61
|
.catch((error) => {
|
|
117
62
|
throw new Error(`Unable to estimate web storage quota. Original error: ${error}`);
|
|
118
63
|
});
|
|
@@ -1,52 +1,3 @@
|
|
|
1
|
+
import type StorageProvider from './types';
|
|
2
|
+
declare const provider: StorageProvider;
|
|
1
3
|
export default provider;
|
|
2
|
-
declare namespace provider {
|
|
3
|
-
/**
|
|
4
|
-
* Get the value of a given key or return `null` if it's not available in storage
|
|
5
|
-
* @param {String} key
|
|
6
|
-
* @return {Promise<*>}
|
|
7
|
-
*/
|
|
8
|
-
function getItem(key: string): Promise<any>;
|
|
9
|
-
/**
|
|
10
|
-
* Get multiple key-value pairs for the given array of keys in a batch
|
|
11
|
-
* @param {String[]} keys
|
|
12
|
-
* @return {Promise<Array<[key, value]>>}
|
|
13
|
-
*/
|
|
14
|
-
function multiGet(keys: string[]): Promise<[key, value][]>;
|
|
15
|
-
/**
|
|
16
|
-
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
|
|
17
|
-
* @param {String} key
|
|
18
|
-
* @param {*} value
|
|
19
|
-
* @return {Promise<void>}
|
|
20
|
-
*/
|
|
21
|
-
function setItem(key: string, value: any): Promise<void>;
|
|
22
|
-
/**
|
|
23
|
-
* Stores multiple key-value pairs in a batch
|
|
24
|
-
* @param {Array<[key, value]>} pairs
|
|
25
|
-
* @return {Promise<void>}
|
|
26
|
-
*/
|
|
27
|
-
function multiSet(pairs: [key, value][]): Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
* Multiple merging of existing and new values in a batch
|
|
30
|
-
* @param {Array<[key, value]>} pairs
|
|
31
|
-
* @return {Promise<void>}
|
|
32
|
-
*/
|
|
33
|
-
function multiMerge(pairs: [key, value][]): Promise<void>;
|
|
34
|
-
/**
|
|
35
|
-
* Merges an existing value with a new one by leveraging JSON_PATCH
|
|
36
|
-
* @param {String} key
|
|
37
|
-
* @param {*} changes - the delta for a specific key
|
|
38
|
-
* @return {Promise<void>}
|
|
39
|
-
*/
|
|
40
|
-
function mergeItem(key: string, changes: any): Promise<void>;
|
|
41
|
-
function getAllKeys(): Promise<string[]>;
|
|
42
|
-
function removeItem(key: string): Promise<void>;
|
|
43
|
-
function removeItems(keys: string[]): Promise<void>;
|
|
44
|
-
function clear(): Promise<void>;
|
|
45
|
-
function setMemoryOnlyKeys(): void;
|
|
46
|
-
/**
|
|
47
|
-
* Gets the total bytes of the database file
|
|
48
|
-
* @returns {Promise}
|
|
49
|
-
*/
|
|
50
|
-
function getDatabaseSize(): Promise<any>;
|
|
51
|
-
function keepInstancesSync(): void;
|
|
52
|
-
}
|
|
@@ -3,13 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
/**
|
|
7
|
-
* The SQLiteStorage provider stores everything in a key/value store by
|
|
8
|
-
* converting the value to a JSON string
|
|
9
|
-
*/
|
|
10
6
|
const react_native_quick_sqlite_1 = require("react-native-quick-sqlite");
|
|
11
7
|
const react_native_device_info_1 = require("react-native-device-info");
|
|
12
|
-
const
|
|
8
|
+
const utils_1 = __importDefault(require("../../utils"));
|
|
13
9
|
const DB_NAME = 'OnyxDB';
|
|
14
10
|
const db = (0, react_native_quick_sqlite_1.open)({ name: DB_NAME });
|
|
15
11
|
db.execute('CREATE TABLE IF NOT EXISTS keyvaluepairs (record_key TEXT NOT NULL PRIMARY KEY , valueJSON JSON NOT NULL) WITHOUT ROWID;');
|
|
@@ -19,60 +15,34 @@ db.execute('PRAGMA CACHE_SIZE=-20000;');
|
|
|
19
15
|
db.execute('PRAGMA synchronous=NORMAL;');
|
|
20
16
|
db.execute('PRAGMA journal_mode=WAL;');
|
|
21
17
|
const provider = {
|
|
22
|
-
/**
|
|
23
|
-
* Get the value of a given key or return `null` if it's not available in storage
|
|
24
|
-
* @param {String} key
|
|
25
|
-
* @return {Promise<*>}
|
|
26
|
-
*/
|
|
27
18
|
getItem(key) {
|
|
28
19
|
return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({ rows }) => {
|
|
29
|
-
if (rows.length === 0) {
|
|
20
|
+
if (!rows || (rows === null || rows === void 0 ? void 0 : rows.length) === 0) {
|
|
30
21
|
return null;
|
|
31
22
|
}
|
|
32
|
-
const result = rows.item(0);
|
|
23
|
+
const result = rows === null || rows === void 0 ? void 0 : rows.item(0);
|
|
33
24
|
return JSON.parse(result.valueJSON);
|
|
34
25
|
});
|
|
35
26
|
},
|
|
36
|
-
/**
|
|
37
|
-
* Get multiple key-value pairs for the given array of keys in a batch
|
|
38
|
-
* @param {String[]} keys
|
|
39
|
-
* @return {Promise<Array<[key, value]>>}
|
|
40
|
-
*/
|
|
41
27
|
multiGet(keys) {
|
|
42
|
-
const placeholders =
|
|
28
|
+
const placeholders = keys.map(() => '?').join(',');
|
|
43
29
|
const command = `SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
|
|
44
30
|
return db.executeAsync(command, keys).then(({ rows }) => {
|
|
45
31
|
// eslint-disable-next-line no-underscore-dangle
|
|
46
|
-
const result =
|
|
47
|
-
return result;
|
|
32
|
+
const result = rows === null || rows === void 0 ? void 0 : rows._array.map((row) => [row.record_key, JSON.parse(row.valueJSON)]);
|
|
33
|
+
return (result !== null && result !== void 0 ? result : []);
|
|
48
34
|
});
|
|
49
35
|
},
|
|
50
|
-
/**
|
|
51
|
-
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
|
|
52
|
-
* @param {String} key
|
|
53
|
-
* @param {*} value
|
|
54
|
-
* @return {Promise<void>}
|
|
55
|
-
*/
|
|
56
36
|
setItem(key, value) {
|
|
57
37
|
return db.executeAsync('REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, ?);', [key, JSON.stringify(value)]);
|
|
58
38
|
},
|
|
59
|
-
/**
|
|
60
|
-
* Stores multiple key-value pairs in a batch
|
|
61
|
-
* @param {Array<[key, value]>} pairs
|
|
62
|
-
* @return {Promise<void>}
|
|
63
|
-
*/
|
|
64
39
|
multiSet(pairs) {
|
|
65
|
-
const stringifiedPairs =
|
|
66
|
-
if (
|
|
40
|
+
const stringifiedPairs = pairs.map((pair) => [pair[0], JSON.stringify(pair[1] === undefined ? null : pair[1])]);
|
|
41
|
+
if (utils_1.default.isEmptyObject(stringifiedPairs)) {
|
|
67
42
|
return Promise.resolve();
|
|
68
43
|
}
|
|
69
44
|
return db.executeBatchAsync([['REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));', stringifiedPairs]]);
|
|
70
45
|
},
|
|
71
|
-
/**
|
|
72
|
-
* Multiple merging of existing and new values in a batch
|
|
73
|
-
* @param {Array<[key, value]>} pairs
|
|
74
|
-
* @return {Promise<void>}
|
|
75
|
-
*/
|
|
76
46
|
multiMerge(pairs) {
|
|
77
47
|
// Note: We use `ON CONFLICT DO UPDATE` here instead of `INSERT OR REPLACE INTO`
|
|
78
48
|
// so the new JSON value is merged into the old one if there's an existing value
|
|
@@ -81,74 +51,42 @@ const provider = {
|
|
|
81
51
|
ON CONFLICT DO UPDATE
|
|
82
52
|
SET valueJSON = JSON_PATCH(valueJSON, JSON(:value));
|
|
83
53
|
`;
|
|
84
|
-
const nonNullishPairs =
|
|
85
|
-
const queryArguments =
|
|
54
|
+
const nonNullishPairs = pairs.filter((pair) => pair[1] !== undefined);
|
|
55
|
+
const queryArguments = nonNullishPairs.map((pair) => {
|
|
86
56
|
const value = JSON.stringify(pair[1]);
|
|
87
57
|
return [pair[0], value];
|
|
88
58
|
});
|
|
89
59
|
return db.executeBatchAsync([[query, queryArguments]]);
|
|
90
60
|
},
|
|
91
|
-
/**
|
|
92
|
-
* Merges an existing value with a new one by leveraging JSON_PATCH
|
|
93
|
-
* @param {String} key
|
|
94
|
-
* @param {*} changes - the delta for a specific key
|
|
95
|
-
* @return {Promise<void>}
|
|
96
|
-
*/
|
|
97
61
|
mergeItem(key, changes) {
|
|
98
62
|
return this.multiMerge([[key, changes]]);
|
|
99
63
|
},
|
|
100
|
-
/**
|
|
101
|
-
* Returns all keys available in storage
|
|
102
|
-
* @returns {Promise<String[]>}
|
|
103
|
-
*/
|
|
104
64
|
getAllKeys: () => db.executeAsync('SELECT record_key FROM keyvaluepairs;').then(({ rows }) => {
|
|
105
65
|
// eslint-disable-next-line no-underscore-dangle
|
|
106
|
-
const result =
|
|
107
|
-
return result;
|
|
66
|
+
const result = rows === null || rows === void 0 ? void 0 : rows._array.map((row) => row.record_key);
|
|
67
|
+
return (result !== null && result !== void 0 ? result : []);
|
|
108
68
|
}),
|
|
109
|
-
/**
|
|
110
|
-
* Removes given key and it's value from storage
|
|
111
|
-
* @param {String} key
|
|
112
|
-
* @returns {Promise<void>}
|
|
113
|
-
*/
|
|
114
69
|
removeItem: (key) => db.executeAsync('DELETE FROM keyvaluepairs WHERE record_key = ?;', [key]),
|
|
115
|
-
/**
|
|
116
|
-
* Removes given keys and their values from storage
|
|
117
|
-
*
|
|
118
|
-
* @param {Array<String>} keys
|
|
119
|
-
* @returns {Promise<void>}
|
|
120
|
-
*/
|
|
121
70
|
removeItems: (keys) => {
|
|
122
|
-
const placeholders =
|
|
71
|
+
const placeholders = keys.map(() => '?').join(',');
|
|
123
72
|
const query = `DELETE FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
|
|
124
73
|
return db.executeAsync(query, keys);
|
|
125
74
|
},
|
|
126
|
-
/**
|
|
127
|
-
* Clears absolutely everything from storage
|
|
128
|
-
* @returns {Promise<void>}
|
|
129
|
-
*/
|
|
130
75
|
clear: () => db.executeAsync('DELETE FROM keyvaluepairs;', []),
|
|
131
|
-
/**
|
|
132
|
-
* Noop on mobile for now.
|
|
133
|
-
*/
|
|
134
|
-
setMemoryOnlyKeys: () => { },
|
|
135
|
-
/**
|
|
136
|
-
* Gets the total bytes of the database file
|
|
137
|
-
* @returns {Promise}
|
|
138
|
-
*/
|
|
139
76
|
getDatabaseSize() {
|
|
140
77
|
return Promise.all([db.executeAsync('PRAGMA page_size;'), db.executeAsync('PRAGMA page_count;'), (0, react_native_device_info_1.getFreeDiskStorage)()]).then(([pageSizeResult, pageCountResult, bytesRemaining]) => {
|
|
141
|
-
|
|
142
|
-
const
|
|
78
|
+
var _a, _b;
|
|
79
|
+
const pageSize = (_a = pageSizeResult.rows) === null || _a === void 0 ? void 0 : _a.item(0).page_size;
|
|
80
|
+
const pageCount = (_b = pageCountResult.rows) === null || _b === void 0 ? void 0 : _b.item(0).page_count;
|
|
143
81
|
return {
|
|
144
82
|
bytesUsed: pageSize * pageCount,
|
|
145
83
|
bytesRemaining,
|
|
146
84
|
};
|
|
147
85
|
});
|
|
148
86
|
},
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
88
|
+
setMemoryOnlyKeys: () => { },
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
152
90
|
keepInstancesSync: () => { },
|
|
153
91
|
};
|
|
154
92
|
exports.default = provider;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { BatchQueryResult, QueryResult } from 'react-native-quick-sqlite';
|
|
2
|
+
type Key = string;
|
|
3
|
+
type Value = IDBValidKey;
|
|
4
|
+
type KeyValuePair = [Key, Value];
|
|
5
|
+
type KeyList = Key[];
|
|
6
|
+
type KeyValuePairList = KeyValuePair[];
|
|
7
|
+
type OnStorageKeyChanged = (key: Key, value: Value | null) => void;
|
|
8
|
+
type StorageProvider = {
|
|
9
|
+
/**
|
|
10
|
+
* Gets the value of a given key or return `null` if it's not available in storage
|
|
11
|
+
*/
|
|
12
|
+
getItem: (key: Key) => Promise<Value | null>;
|
|
13
|
+
/**
|
|
14
|
+
* Get multiple key-value pairs for the given array of keys in a batch
|
|
15
|
+
*/
|
|
16
|
+
multiGet: (keys: KeyList) => Promise<KeyValuePairList>;
|
|
17
|
+
/**
|
|
18
|
+
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
|
|
19
|
+
*/
|
|
20
|
+
setItem: (key: Key, value: Value) => Promise<QueryResult | void>;
|
|
21
|
+
/**
|
|
22
|
+
* Stores multiple key-value pairs in a batch
|
|
23
|
+
*/
|
|
24
|
+
multiSet: (pairs: KeyValuePairList) => Promise<BatchQueryResult | void>;
|
|
25
|
+
/**
|
|
26
|
+
* Multiple merging of existing and new values in a batch
|
|
27
|
+
*/
|
|
28
|
+
multiMerge: (pairs: KeyValuePairList) => Promise<BatchQueryResult | IDBValidKey[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Merges an existing value with a new one by leveraging JSON_PATCH
|
|
31
|
+
* @param changes - the delta for a specific key
|
|
32
|
+
* @param modifiedData - the pre-merged data from `Onyx.applyMerge`
|
|
33
|
+
*/
|
|
34
|
+
mergeItem: (key: Key, changes: Value, modifiedData: Value) => Promise<BatchQueryResult | void>;
|
|
35
|
+
/**
|
|
36
|
+
* Returns all keys available in storage
|
|
37
|
+
*/
|
|
38
|
+
getAllKeys: () => Promise<KeyList>;
|
|
39
|
+
/**
|
|
40
|
+
* Removes given key and its value from storage
|
|
41
|
+
*/
|
|
42
|
+
removeItem: (key: Key) => Promise<QueryResult | void>;
|
|
43
|
+
/**
|
|
44
|
+
* Removes given keys and their values from storage
|
|
45
|
+
*/
|
|
46
|
+
removeItems: (keys: KeyList) => Promise<QueryResult | void>;
|
|
47
|
+
/**
|
|
48
|
+
* Clears absolutely everything from storage
|
|
49
|
+
*/
|
|
50
|
+
clear: () => Promise<QueryResult | void>;
|
|
51
|
+
/**
|
|
52
|
+
* Sets memory only keys
|
|
53
|
+
*/
|
|
54
|
+
setMemoryOnlyKeys: () => void;
|
|
55
|
+
/**
|
|
56
|
+
* Gets the total bytes of the database file
|
|
57
|
+
*/
|
|
58
|
+
getDatabaseSize: () => Promise<{
|
|
59
|
+
bytesUsed: number;
|
|
60
|
+
bytesRemaining: number;
|
|
61
|
+
}>;
|
|
62
|
+
/**
|
|
63
|
+
* @param onStorageKeyChanged Storage synchronization mechanism keeping all opened tabs in sync
|
|
64
|
+
*/
|
|
65
|
+
keepInstancesSync?: (onStorageKeyChanged: OnStorageKeyChanged) => void;
|
|
66
|
+
};
|
|
67
|
+
export default StorageProvider;
|
|
68
|
+
export type { Value, Key, KeyList, KeyValuePairList };
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import {OnyxKey} from './types';
|
|
2
|
-
|
|
1
|
+
import type { OnyxKey } from './types';
|
|
2
|
+
type EmptyObject = Record<string, never>;
|
|
3
|
+
type EmptyValue = EmptyObject | null | undefined;
|
|
4
|
+
/** Checks whether the given object is an object and not null/undefined. */
|
|
5
|
+
declare function isEmptyObject<T>(obj: T | EmptyValue): obj is EmptyValue;
|
|
3
6
|
/**
|
|
4
7
|
* Merges two objects and removes null values if "shouldRemoveNullObjectValues" is set to true
|
|
5
8
|
*
|
|
@@ -7,11 +10,15 @@ import {OnyxKey} from './types';
|
|
|
7
10
|
* On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
|
|
8
11
|
* To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
|
|
9
12
|
*/
|
|
10
|
-
declare function fastMerge<
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*/
|
|
13
|
+
declare function fastMerge<TTarget extends unknown[] | Record<string, unknown>>(target: TTarget, source: TTarget, shouldRemoveNullObjectValues?: boolean): TTarget;
|
|
14
|
+
/** Deep removes the nested null values from the given value. */
|
|
15
|
+
declare function removeNestedNullValues(value: unknown[] | Record<string, unknown>): Record<string, unknown> | unknown[];
|
|
16
|
+
/** Formats the action name by uppercasing and adding the key if provided. */
|
|
15
17
|
declare function formatActionName(method: string, key?: OnyxKey): string;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
declare const _default: {
|
|
19
|
+
isEmptyObject: typeof isEmptyObject;
|
|
20
|
+
fastMerge: typeof fastMerge;
|
|
21
|
+
formatActionName: typeof formatActionName;
|
|
22
|
+
removeNestedNullValues: typeof removeNestedNullValues;
|
|
23
|
+
};
|
|
24
|
+
export default _default;
|
package/dist/utils.js
CHANGED
|
@@ -1,66 +1,58 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
|
|
7
|
-
function
|
|
8
|
-
return typeof
|
|
3
|
+
/** Checks whether the given object is an object and not null/undefined. */
|
|
4
|
+
function isEmptyObject(obj) {
|
|
5
|
+
return typeof obj === 'object' && Object.keys(obj || {}).length === 0;
|
|
9
6
|
}
|
|
10
7
|
// Mostly copied from https://medium.com/@lubaka.a/how-to-remove-lodash-performance-improvement-b306669ad0e1
|
|
11
8
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @returns {boolean}
|
|
9
|
+
* Checks whether the given value can be merged. It has to be an object, but not an array, RegExp or Date.
|
|
14
10
|
*/
|
|
15
|
-
function isMergeableObject(
|
|
16
|
-
const nonNullObject =
|
|
17
|
-
return
|
|
18
|
-
Object.prototype.toString.call(val) !== '[object RegExp]' &&
|
|
19
|
-
Object.prototype.toString.call(val) !== '[object Date]' &&
|
|
20
|
-
// eslint-disable-next-line rulesdir/prefer-underscore-method
|
|
21
|
-
!Array.isArray(val));
|
|
11
|
+
function isMergeableObject(value) {
|
|
12
|
+
const nonNullObject = value != null ? typeof value === 'object' : false;
|
|
13
|
+
return nonNullObject && Object.prototype.toString.call(value) !== '[object RegExp]' && Object.prototype.toString.call(value) !== '[object Date]' && !Array.isArray(value);
|
|
22
14
|
}
|
|
23
15
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param
|
|
26
|
-
* @param
|
|
27
|
-
* @
|
|
16
|
+
* Merges the source object into the target object.
|
|
17
|
+
* @param target - The target object.
|
|
18
|
+
* @param source - The source object.
|
|
19
|
+
* @param shouldRemoveNullObjectValues - If true, null object values will be removed.
|
|
20
|
+
* @returns - The merged object.
|
|
28
21
|
*/
|
|
29
22
|
function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
|
|
30
23
|
const destination = {};
|
|
31
24
|
if (isMergeableObject(target)) {
|
|
32
25
|
// lodash adds a small overhead so we don't use it here
|
|
33
|
-
// eslint-disable-next-line rulesdir/prefer-underscore-method
|
|
34
26
|
const targetKeys = Object.keys(target);
|
|
35
27
|
for (let i = 0; i < targetKeys.length; ++i) {
|
|
36
28
|
const key = targetKeys[i];
|
|
29
|
+
const sourceValue = source === null || source === void 0 ? void 0 : source[key];
|
|
30
|
+
const targetValue = target === null || target === void 0 ? void 0 : target[key];
|
|
37
31
|
// If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
|
|
38
|
-
const isSourceOrTargetNull =
|
|
32
|
+
const isSourceOrTargetNull = targetValue === null || sourceValue === null;
|
|
39
33
|
const shouldOmitSourceKey = shouldRemoveNullObjectValues && isSourceOrTargetNull;
|
|
40
34
|
if (!shouldOmitSourceKey) {
|
|
41
|
-
destination[key] =
|
|
35
|
+
destination[key] = targetValue;
|
|
42
36
|
}
|
|
43
37
|
}
|
|
44
38
|
}
|
|
45
|
-
// lodash adds a small overhead so we don't use it here
|
|
46
|
-
// eslint-disable-next-line rulesdir/prefer-underscore-method
|
|
47
39
|
const sourceKeys = Object.keys(source);
|
|
48
40
|
for (let i = 0; i < sourceKeys.length; ++i) {
|
|
49
41
|
const key = sourceKeys[i];
|
|
42
|
+
const sourceValue = source === null || source === void 0 ? void 0 : source[key];
|
|
43
|
+
const targetValue = target === null || target === void 0 ? void 0 : target[key];
|
|
50
44
|
// If shouldRemoveNullObjectValues is true, we want to remove null values from the merged object
|
|
51
|
-
const shouldOmitSourceKey = shouldRemoveNullObjectValues &&
|
|
45
|
+
const shouldOmitSourceKey = shouldRemoveNullObjectValues && sourceValue === null;
|
|
52
46
|
// If we pass undefined as the updated value for a key, we want to generally ignore it
|
|
53
|
-
const isSourceKeyUndefined =
|
|
47
|
+
const isSourceKeyUndefined = sourceValue === undefined;
|
|
54
48
|
if (!isSourceKeyUndefined && !shouldOmitSourceKey) {
|
|
55
|
-
const isSourceKeyMergable = isMergeableObject(
|
|
56
|
-
if (isSourceKeyMergable &&
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
destination[key] = fastMerge(target[key], source[key], shouldRemoveNullObjectValues);
|
|
60
|
-
}
|
|
49
|
+
const isSourceKeyMergable = isMergeableObject(sourceValue);
|
|
50
|
+
if (isSourceKeyMergable && targetValue) {
|
|
51
|
+
// eslint-disable-next-line no-use-before-define
|
|
52
|
+
destination[key] = fastMerge(targetValue, sourceValue, shouldRemoveNullObjectValues);
|
|
61
53
|
}
|
|
62
|
-
else if (!shouldRemoveNullObjectValues ||
|
|
63
|
-
destination[key] =
|
|
54
|
+
else if (!shouldRemoveNullObjectValues || sourceValue !== null) {
|
|
55
|
+
destination[key] = sourceValue;
|
|
64
56
|
}
|
|
65
57
|
}
|
|
66
58
|
}
|
|
@@ -72,28 +64,25 @@ function mergeObject(target, source, shouldRemoveNullObjectValues = true) {
|
|
|
72
64
|
* We generally want to remove null values from objects written to disk and cache, because it decreases the amount of data stored in memory and on disk.
|
|
73
65
|
* On native, when merging an existing value with new changes, SQLite will use JSON_PATCH, which removes top-level nullish values.
|
|
74
66
|
* To be consistent with the behaviour for merge, we'll also want to remove null values for "set" operations.
|
|
75
|
-
*
|
|
76
|
-
* @param {Object|Array} target
|
|
77
|
-
* @param {Object|Array} source
|
|
78
|
-
* @param {Boolean} shouldRemoveNullObjectValues
|
|
79
|
-
* @returns {Object|Array}
|
|
80
67
|
*/
|
|
81
68
|
function fastMerge(target, source, shouldRemoveNullObjectValues = true) {
|
|
82
69
|
// We have to ignore arrays and nullish values here,
|
|
83
70
|
// otherwise "mergeObject" will throw an error,
|
|
84
71
|
// because it expects an object as "source"
|
|
85
|
-
if (
|
|
72
|
+
if (Array.isArray(source) || source === null || source === undefined) {
|
|
86
73
|
return source;
|
|
87
74
|
}
|
|
88
75
|
return mergeObject(target, source, shouldRemoveNullObjectValues);
|
|
89
76
|
}
|
|
77
|
+
/** Deep removes the nested null values from the given value. */
|
|
90
78
|
function removeNestedNullValues(value) {
|
|
91
|
-
if (typeof value === 'object' && !
|
|
79
|
+
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
92
80
|
return fastMerge(value, value);
|
|
93
81
|
}
|
|
94
82
|
return value;
|
|
95
83
|
}
|
|
84
|
+
/** Formats the action name by uppercasing and adding the key if provided. */
|
|
96
85
|
function formatActionName(method, key) {
|
|
97
86
|
return key ? `${method.toUpperCase()}/${key}` : method.toUpperCase();
|
|
98
87
|
}
|
|
99
|
-
exports.default = {
|
|
88
|
+
exports.default = { isEmptyObject, fastMerge, formatActionName, removeNestedNullValues };
|
package/dist/withOnyx.js
CHANGED
|
@@ -189,7 +189,7 @@ function default_1(mapOnyxToState, shouldDelayUpdates = false) {
|
|
|
189
189
|
// This simply bypasses the loading check if the tempState is gone and the update can be safely queued with a normal setStateProxy.
|
|
190
190
|
if (!this.state.loading || !this.tempState) {
|
|
191
191
|
// Performance optimization, do not trigger update with same values
|
|
192
|
-
if (prevValue === val || utils_1.default.
|
|
192
|
+
if (prevValue === val || (utils_1.default.isEmptyObject(prevValue) && utils_1.default.isEmptyObject(val))) {
|
|
193
193
|
return;
|
|
194
194
|
}
|
|
195
195
|
this.setStateProxy({ [statePropertyName]: val });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-onyx",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"author": "Expensify, Inc.",
|
|
5
5
|
"homepage": "https://expensify.com",
|
|
6
6
|
"description": "State management for React Native",
|
|
@@ -49,10 +49,12 @@
|
|
|
49
49
|
"@testing-library/jest-native": "^3.4.2",
|
|
50
50
|
"@testing-library/react-native": "^7.0.2",
|
|
51
51
|
"@types/jest": "^28.1.8",
|
|
52
|
+
"@types/lodash": "^4.14.202",
|
|
52
53
|
"@types/node": "^20.11.5",
|
|
53
54
|
"@types/react": "^18.2.14",
|
|
54
55
|
"@types/react-dom": "^18.2.18",
|
|
55
56
|
"@types/react-native": "^0.70.0",
|
|
57
|
+
"@types/underscore": "^1.11.15",
|
|
56
58
|
"@typescript-eslint/eslint-plugin": "^6.19.0",
|
|
57
59
|
"@typescript-eslint/parser": "^6.19.0",
|
|
58
60
|
"eslint": "^8.56.0",
|