react-native-onyx 1.0.30 → 1.0.32
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 +30 -5
- package/dist/web.development.js +4331 -3017
- package/dist/web.development.js.map +1 -1
- package/dist/web.min.js +2 -1
- package/dist/web.min.js.LICENSE.txt +9 -0
- package/dist/web.min.js.map +1 -1
- package/lib/Onyx.js +52 -8
- package/lib/storage/NativeStorage.js +2 -2
- package/lib/storage/providers/SQLiteStorage.js +118 -0
- package/lib/storage/providers/__mocks__/SQLiteStorage.js +3 -0
- package/package.json +22 -10
package/lib/Onyx.js
CHANGED
|
@@ -778,6 +778,19 @@ function notifySubscribersOnNextTick(key, value, canUpdateSubscriber) {
|
|
|
778
778
|
Promise.resolve().then(() => keyChanged(key, value, canUpdateSubscriber));
|
|
779
779
|
}
|
|
780
780
|
|
|
781
|
+
/**
|
|
782
|
+
* This method is similar to notifySubscribersOnNextTick but it is built for working specifically with collections
|
|
783
|
+
* so that keysChanged() is triggered for the collection and not keyChanged(). If this was not done, then the
|
|
784
|
+
* subscriber callbacks receive the data in a different format than they normally expect and it breaks code.
|
|
785
|
+
*
|
|
786
|
+
* @param {String} key
|
|
787
|
+
* @param {*} value
|
|
788
|
+
*/
|
|
789
|
+
// eslint-disable-next-line rulesdir/no-negated-variables
|
|
790
|
+
function notifyCollectionSubscribersOnNextTick(key, value) {
|
|
791
|
+
Promise.resolve().then(() => keysChanged(key, value));
|
|
792
|
+
}
|
|
793
|
+
|
|
781
794
|
/**
|
|
782
795
|
* Remove a key from Onyx and update the subscribers
|
|
783
796
|
*
|
|
@@ -1038,24 +1051,55 @@ function clear(keysToPreserve = []) {
|
|
|
1038
1051
|
const defaultKeys = _.keys(defaultKeyStates);
|
|
1039
1052
|
|
|
1040
1053
|
// The only keys that should not be cleared are:
|
|
1041
|
-
// 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
|
|
1042
|
-
//
|
|
1054
|
+
// 1. Anything specifically passed in keysToPreserve (because some keys like language preferences, offline
|
|
1055
|
+
// status, or activeClients need to remain in Onyx even when signed out)
|
|
1056
|
+
// 2. Any keys with a default state (because they need to remain in Onyx as their default, and setting them
|
|
1057
|
+
// to null would cause unknown behavior)
|
|
1043
1058
|
const keysToClear = _.difference(keys, keysToPreserve, defaultKeys);
|
|
1044
1059
|
keyValuesToReset.push(..._.map(keysToClear, key => [key, null]));
|
|
1045
1060
|
|
|
1046
|
-
// Remove any keysToPreserve from the defaultKeyStates because if they are passed in it has been explicitly
|
|
1061
|
+
// Remove any keysToPreserve from the defaultKeyStates because if they are passed in it has been explicitly
|
|
1062
|
+
// called out to preserve those values instead of resetting them back
|
|
1047
1063
|
// to the default.
|
|
1048
1064
|
const defaultKeyValuePairs = _.pairs(_.omit(defaultKeyStates, ...keysToPreserve));
|
|
1049
1065
|
|
|
1050
|
-
// Add the default key value pairs to the keyValuesToReset so that they get set back to their default values
|
|
1066
|
+
// Add the default key value pairs to the keyValuesToReset so that they get set back to their default values
|
|
1067
|
+
// when we clear Onyx
|
|
1051
1068
|
keyValuesToReset.push(...defaultKeyValuePairs);
|
|
1052
1069
|
|
|
1070
|
+
// We now have all the key/values that need to be reset, but we're not done yet!
|
|
1071
|
+
// There will be two groups of key/values and they each need to be updated a little bit differently.
|
|
1072
|
+
// Collection keys need to be notified differently than non collection keys
|
|
1073
|
+
const keyValuesToResetAsCollection = {};
|
|
1074
|
+
const keyValuesToResetIndividually = {};
|
|
1075
|
+
|
|
1053
1076
|
// Make sure that we also reset the cache values before clearing the values from storage.
|
|
1054
|
-
// We do this before clearing Storage so that any call to clear() followed by merge() on a key with a
|
|
1055
|
-
//
|
|
1077
|
+
// We do this before clearing Storage so that any call to clear() followed by merge() on a key with a
|
|
1078
|
+
// default state results in the merged value getting saved, since the update from the merge() call would
|
|
1079
|
+
// happen on the tick after the update from this clear()
|
|
1056
1080
|
_.each(keyValuesToReset, (keyValue) => {
|
|
1057
|
-
|
|
1058
|
-
|
|
1081
|
+
const key = keyValue[0];
|
|
1082
|
+
const value = keyValue[1];
|
|
1083
|
+
cache.set(key, value);
|
|
1084
|
+
|
|
1085
|
+
const collectionKey = key.substring(0, key.indexOf('_') + 1);
|
|
1086
|
+
if (collectionKey) {
|
|
1087
|
+
if (!keyValuesToResetAsCollection[collectionKey]) {
|
|
1088
|
+
keyValuesToResetAsCollection[collectionKey] = {};
|
|
1089
|
+
}
|
|
1090
|
+
keyValuesToResetAsCollection[collectionKey][key] = value;
|
|
1091
|
+
return;
|
|
1092
|
+
}
|
|
1093
|
+
|
|
1094
|
+
keyValuesToResetIndividually[key] = value;
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
// Notify the subscribers for each key/value group so they can receive the new values
|
|
1098
|
+
_.each(keyValuesToResetIndividually, (value, key) => {
|
|
1099
|
+
notifySubscribersOnNextTick(key, value);
|
|
1100
|
+
});
|
|
1101
|
+
_.each(keyValuesToResetAsCollection, (value, key) => {
|
|
1102
|
+
notifyCollectionSubscribersOnNextTick(key, value);
|
|
1059
1103
|
});
|
|
1060
1104
|
|
|
1061
1105
|
return Storage.multiSet(keyValuesToReset);
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import
|
|
1
|
+
import SQLiteStorage from './providers/SQLiteStorage';
|
|
2
2
|
|
|
3
|
-
export default
|
|
3
|
+
export default SQLiteStorage;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The SQLiteStorage provider stores everything in a key/value store by
|
|
3
|
+
* converting the value to a JSON string
|
|
4
|
+
*/
|
|
5
|
+
import {open} from 'react-native-quick-sqlite';
|
|
6
|
+
import _ from 'underscore';
|
|
7
|
+
|
|
8
|
+
const DB_NAME = 'OnyxDB';
|
|
9
|
+
const db = open({name: DB_NAME});
|
|
10
|
+
|
|
11
|
+
db.execute('CREATE TABLE IF NOT EXISTS keyvaluepairs (record_key TEXT NOT NULL PRIMARY KEY , valueJSON JSON NOT NULL) WITHOUT ROWID;');
|
|
12
|
+
|
|
13
|
+
// All of the 3 pragmas below were suggested by SQLite team.
|
|
14
|
+
// You can find more info about them here: https://www.sqlite.org/pragma.html
|
|
15
|
+
db.execute('PRAGMA CACHE_SIZE=-20000;');
|
|
16
|
+
db.execute('PRAGMA synchronous=NORMAL;');
|
|
17
|
+
db.execute('PRAGMA journal_mode=WAL;');
|
|
18
|
+
|
|
19
|
+
const provider = {
|
|
20
|
+
/**
|
|
21
|
+
* Get the value of a given key or return `null` if it's not available in storage
|
|
22
|
+
* @param {String} key
|
|
23
|
+
* @return {Promise<*>}
|
|
24
|
+
*/
|
|
25
|
+
getItem(key) {
|
|
26
|
+
return db.executeAsync('SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key = ?;', [key]).then(({rows}) => {
|
|
27
|
+
if (rows.length === 0) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const result = rows.item(0);
|
|
31
|
+
return JSON.parse(result.valueJSON);
|
|
32
|
+
});
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get multiple key-value pairs for the given array of keys in a batch
|
|
37
|
+
* @param {String[]} keys
|
|
38
|
+
* @return {Promise<Array<[key, value]>>}
|
|
39
|
+
*/
|
|
40
|
+
multiGet(keys) {
|
|
41
|
+
const placeholders = _.map(keys, () => '?').join(',');
|
|
42
|
+
const command = `SELECT record_key, valueJSON FROM keyvaluepairs WHERE record_key IN (${placeholders});`;
|
|
43
|
+
return db.executeAsync(command, keys)
|
|
44
|
+
.then(({rows}) => {
|
|
45
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
46
|
+
const result = _.map(rows._array, row => [row.record_key, JSON.parse(row.valueJSON)]);
|
|
47
|
+
return result;
|
|
48
|
+
});
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sets the value for a given key. The only requirement is that the value should be serializable to JSON string
|
|
53
|
+
* @param {String} key
|
|
54
|
+
* @param {*} value
|
|
55
|
+
* @return {Promise<void>}
|
|
56
|
+
*/
|
|
57
|
+
setItem(key, value) {
|
|
58
|
+
return db.executeAsync('REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, ?);', [key, JSON.stringify(value)]);
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Stores multiple key-value pairs in a batch
|
|
63
|
+
* @param {Array<[key, value]>} pairs
|
|
64
|
+
* @return {Promise<void>}
|
|
65
|
+
*/
|
|
66
|
+
multiSet(pairs) {
|
|
67
|
+
const stringifiedPairs = _.map(pairs, pair => [
|
|
68
|
+
pair[0],
|
|
69
|
+
JSON.stringify(pair[1]),
|
|
70
|
+
]);
|
|
71
|
+
return db.executeBatchAsync([['REPLACE INTO keyvaluepairs (record_key, valueJSON) VALUES (?, json(?));', stringifiedPairs]]);
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Multiple merging of existing and new values in a batch
|
|
76
|
+
* @param {Array<[key, value]>} pairs
|
|
77
|
+
* @return {Promise<void>}
|
|
78
|
+
*/
|
|
79
|
+
multiMerge(pairs) {
|
|
80
|
+
// Note: We use `ON CONFLICT DO UPDATE` here instead of `INSERT OR REPLACE INTO`
|
|
81
|
+
// so the new JSON value is merged into the old one if there's an existing value
|
|
82
|
+
const query = `INSERT INTO keyvaluepairs (record_key, valueJSON)
|
|
83
|
+
VALUES (:key, JSON(:value))
|
|
84
|
+
ON CONFLICT DO UPDATE
|
|
85
|
+
SET valueJSON = JSON_PATCH(valueJSON, JSON(:value));
|
|
86
|
+
`;
|
|
87
|
+
const queryArguments = _.map(pairs, (pair) => {
|
|
88
|
+
const value = JSON.stringify(pair[1]);
|
|
89
|
+
return [pair[0], value];
|
|
90
|
+
});
|
|
91
|
+
return db.executeBatchAsync([[query, queryArguments]]);
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns all keys available in storage
|
|
96
|
+
* @returns {Promise<String[]>}
|
|
97
|
+
*/
|
|
98
|
+
getAllKeys: () => db.executeAsync('SELECT record_key FROM keyvaluepairs;').then(({rows}) => {
|
|
99
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
100
|
+
const result = _.map(rows._array, row => row.record_key);
|
|
101
|
+
return result;
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Removes given key and it's value from storage
|
|
106
|
+
* @param {String} key
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
*/
|
|
109
|
+
removeItem: key => db.executeAsync('DELETE FROM keyvaluepairs WHERE record_key = ?;', [key]),
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Clears absolutely everything from storage
|
|
113
|
+
* @returns {Promise<void>}
|
|
114
|
+
*/
|
|
115
|
+
clear: () => db.executeAsync('DELETE FROM keyvaluepairs;', []),
|
|
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.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"author": "Expensify, Inc.",
|
|
5
5
|
"homepage": "https://expensify.com",
|
|
6
6
|
"description": "State management for React Native",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"@babel/core": "^7.17.10",
|
|
49
49
|
"@babel/plugin-proposal-class-properties": "^7.16.7",
|
|
50
50
|
"@babel/runtime": "^7.11.2",
|
|
51
|
-
"@react-native-async-storage/async-storage": "^1.
|
|
51
|
+
"@react-native-async-storage/async-storage": "^1.17.11",
|
|
52
52
|
"@react-native-community/eslint-config": "^2.0.0",
|
|
53
53
|
"@testing-library/jest-native": "^3.4.2",
|
|
54
54
|
"@testing-library/react-native": "^7.0.2",
|
|
@@ -60,27 +60,31 @@
|
|
|
60
60
|
"babel-plugin-transform-class-properties": "^6.24.1",
|
|
61
61
|
"eslint": "^7.6.0",
|
|
62
62
|
"eslint-config-expensify": "^2.0.24",
|
|
63
|
+
"eslint-plugin-jsx-a11y": "^6.6.1",
|
|
64
|
+
"eslint-plugin-react": "^7.31.10",
|
|
63
65
|
"expensify-common": "git+https://github.com/Expensify/expensify-common.git#427295da130a4eacc184d38693664280d020dffd",
|
|
64
66
|
"jest": "^26.5.2",
|
|
65
67
|
"jest-cli": "^26.5.2",
|
|
66
68
|
"jsdoc-to-markdown": "^7.1.0",
|
|
67
69
|
"localforage": "^1.10.0",
|
|
68
|
-
"metro-react-native-babel-preset": "^0.
|
|
70
|
+
"metro-react-native-babel-preset": "^0.72.3",
|
|
69
71
|
"prop-types": "^15.7.2",
|
|
70
|
-
"react": "
|
|
71
|
-
"react-native": "0.
|
|
72
|
+
"react": "18.1.0",
|
|
73
|
+
"react-native": "0.70.0",
|
|
72
74
|
"react-native-performance": "^2.0.0",
|
|
73
|
-
"react-
|
|
75
|
+
"react-native-quick-sqlite": "^5.0.3",
|
|
76
|
+
"react-test-renderer": "18.1.0",
|
|
74
77
|
"webpack": "^5.72.1",
|
|
75
78
|
"webpack-cli": "^4.9.2",
|
|
76
79
|
"webpack-merge": "^5.8.0"
|
|
77
80
|
},
|
|
78
81
|
"peerDependencies": {
|
|
79
|
-
"@react-native-async-storage/async-storage": "^1.
|
|
82
|
+
"@react-native-async-storage/async-storage": "^1.17.11",
|
|
80
83
|
"expensify-common": ">=1",
|
|
81
84
|
"localforage": "^1.10.0",
|
|
82
|
-
"react": ">=
|
|
83
|
-
"react-native-performance": "^
|
|
85
|
+
"react": ">=18.1.0",
|
|
86
|
+
"react-native-performance": "^4.0.0",
|
|
87
|
+
"react-native-quick-sqlite": "^5.0.3"
|
|
84
88
|
},
|
|
85
89
|
"peerDependenciesMeta": {
|
|
86
90
|
"react-native-performance": {
|
|
@@ -89,10 +93,17 @@
|
|
|
89
93
|
"@react-native-async-storage/async-storage": {
|
|
90
94
|
"optional": true
|
|
91
95
|
},
|
|
96
|
+
"react-native-quick-sqlite": {
|
|
97
|
+
"optional": true
|
|
98
|
+
},
|
|
92
99
|
"localforage": {
|
|
93
100
|
"optional": true
|
|
94
101
|
}
|
|
95
102
|
},
|
|
103
|
+
"engines": {
|
|
104
|
+
"node": "16.15.1",
|
|
105
|
+
"npm": "8.11.0"
|
|
106
|
+
},
|
|
96
107
|
"jest": {
|
|
97
108
|
"preset": "react-native",
|
|
98
109
|
"transform": {
|
|
@@ -116,7 +127,8 @@
|
|
|
116
127
|
"timers": "fake",
|
|
117
128
|
"testEnvironment": "jsdom",
|
|
118
129
|
"setupFilesAfterEnv": [
|
|
119
|
-
"@testing-library/jest-native/extend-expect"
|
|
130
|
+
"@testing-library/jest-native/extend-expect",
|
|
131
|
+
"./jestSetup.js"
|
|
120
132
|
]
|
|
121
133
|
},
|
|
122
134
|
"sideEffects": false
|