@legendapp/state 0.23.2 → 1.0.0-rc.2
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/CHANGELOG.md +25 -0
- package/README.md +4 -0
- package/helpers/pageHash.js +1 -1
- package/helpers/pageHash.js.map +1 -1
- package/helpers/pageHash.mjs +1 -1
- package/helpers/pageHash.mjs.map +1 -1
- package/helpers/pageHashParams.js +1 -1
- package/helpers/pageHashParams.js.map +1 -1
- package/helpers/pageHashParams.mjs +1 -1
- package/helpers/pageHashParams.mjs.map +1 -1
- package/history.js +3 -3
- package/history.js.map +1 -1
- package/history.mjs +4 -4
- package/history.mjs.map +1 -1
- package/index.d.ts +2 -2
- package/index.js +151 -81
- package/index.js.map +1 -1
- package/index.mjs +148 -78
- package/index.mjs.map +1 -1
- package/package.json +1 -1
- package/persist-plugins/indexeddb-preloader.js +16 -10
- package/persist-plugins/indexeddb-preloader.js.map +1 -1
- package/persist-plugins/indexeddb-preloader.mjs +16 -10
- package/persist-plugins/indexeddb-preloader.mjs.map +1 -1
- package/persist-plugins/indexeddb.d.ts +3 -2
- package/persist-plugins/indexeddb.js +92 -56
- package/persist-plugins/indexeddb.js.map +1 -1
- package/persist-plugins/indexeddb.mjs +93 -57
- package/persist-plugins/indexeddb.mjs.map +1 -1
- package/persist-plugins/local-storage.d.ts +4 -3
- package/persist-plugins/local-storage.js +18 -8
- package/persist-plugins/local-storage.js.map +1 -1
- package/persist-plugins/local-storage.mjs +18 -8
- package/persist-plugins/local-storage.mjs.map +1 -1
- package/persist-plugins/mmkv.d.ts +3 -2
- package/persist-plugins/mmkv.js +27 -18
- package/persist-plugins/mmkv.js.map +1 -1
- package/persist-plugins/mmkv.mjs +27 -18
- package/persist-plugins/mmkv.mjs.map +1 -1
- package/persist.d.ts +3 -2
- package/persist.js +292 -113
- package/persist.js.map +1 -1
- package/persist.mjs +292 -114
- package/persist.mjs.map +1 -1
- package/react-hooks/usePersistedObservable.js.map +1 -1
- package/react-hooks/usePersistedObservable.mjs.map +1 -1
- package/react.js +1 -1
- package/react.js.map +1 -1
- package/react.mjs +2 -2
- package/react.mjs.map +1 -1
- package/src/batching.d.ts +3 -10
- package/src/helpers.d.ts +5 -4
- package/src/notify.d.ts +1 -1
- package/src/observableInterfaces.d.ts +56 -17
- package/src/onChange.d.ts +4 -1
- package/src/persist/fieldTransformer.d.ts +8 -3
- package/src/persist/persistHelpers.d.ts +2 -0
- package/src/persist/persistObservable.d.ts +7 -3
- package/src/persist-plugins/indexeddb.d.ts +3 -2
- package/src/persist-plugins/local-storage.d.ts +4 -3
- package/src/persist-plugins/mmkv.d.ts +3 -2
- package/trace.js.map +1 -1
- package/trace.mjs.map +1 -1
package/persist.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { isObject,
|
|
1
|
+
import { symbolDelete, symbolDateModified, isObject, isString, constructObjectWithPath, dateModifiedKey, deconstructObjectWithPath, isArray, observable, tracking as tracking$1, beginBatch, endBatch, when, isSymbol, isPromise, isEmpty, batch, mergeIntoObservable, setInObservableAtPath } from '@legendapp/state';
|
|
2
2
|
|
|
3
3
|
const observablePersistConfiguration = {};
|
|
4
4
|
function configureObservablePersistence(options) {
|
|
5
5
|
Object.assign(observablePersistConfiguration, options);
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
let validateMap;
|
|
9
|
+
function transformPath(path, map, passThroughKeys) {
|
|
9
10
|
const data = {};
|
|
10
11
|
let d = data;
|
|
11
12
|
for (let i = 0; i < path.length; i++) {
|
|
12
13
|
d = d[path[i]] = i === path.length - 1 ? null : {};
|
|
13
14
|
}
|
|
14
|
-
let value = transformObject(data, map, passThroughKeys
|
|
15
|
+
let value = transformObject(data, map, passThroughKeys);
|
|
15
16
|
const pathOut = [];
|
|
16
17
|
for (let i = 0; i < path.length; i++) {
|
|
17
18
|
const key = Object.keys(value)[0];
|
|
@@ -21,11 +22,20 @@ function transformPath(path, map, passThroughKeys, ignoreKeys) {
|
|
|
21
22
|
return pathOut;
|
|
22
23
|
}
|
|
23
24
|
function transformObject(dataIn, map, passThroughKeys, ignoreKeys) {
|
|
25
|
+
if (process.env.NODE_ENV === 'development') {
|
|
26
|
+
validateMap(map);
|
|
27
|
+
}
|
|
24
28
|
// Note: If changing this, change it in IndexedDB preloader
|
|
25
29
|
let ret = dataIn;
|
|
26
30
|
if (dataIn) {
|
|
31
|
+
if (dataIn === symbolDelete)
|
|
32
|
+
return dataIn;
|
|
27
33
|
ret = {};
|
|
28
34
|
const dict = Object.keys(map).length === 1 && map['_dict'];
|
|
35
|
+
let dateModified = dataIn[symbolDateModified];
|
|
36
|
+
if (dateModified) {
|
|
37
|
+
ret[symbolDateModified] = dateModified;
|
|
38
|
+
}
|
|
29
39
|
Object.keys(dataIn).forEach((key) => {
|
|
30
40
|
if (ret[key] !== undefined || (ignoreKeys === null || ignoreKeys === void 0 ? void 0 : ignoreKeys.includes(key)))
|
|
31
41
|
return;
|
|
@@ -47,14 +57,24 @@ function transformObject(dataIn, map, passThroughKeys, ignoreKeys) {
|
|
|
47
57
|
}
|
|
48
58
|
else if (mapped !== null) {
|
|
49
59
|
if (v !== undefined && v !== null) {
|
|
50
|
-
if (map[key + '
|
|
60
|
+
if (map[key + '_val']) {
|
|
61
|
+
const valMap = map[key + '_val'];
|
|
62
|
+
v = valMap[key];
|
|
63
|
+
}
|
|
64
|
+
else if (map[key + '_obj']) {
|
|
51
65
|
v = transformObject(v, map[key + '_obj'], passThroughKeys, ignoreKeys);
|
|
52
66
|
}
|
|
53
67
|
else if (map[key + '_dict']) {
|
|
54
68
|
const mapChild = map[key + '_dict'];
|
|
69
|
+
let out = {};
|
|
70
|
+
let dateModifiedChild = dataIn[symbolDateModified];
|
|
71
|
+
if (dateModifiedChild) {
|
|
72
|
+
out[symbolDateModified] = dateModifiedChild;
|
|
73
|
+
}
|
|
55
74
|
Object.keys(v).forEach((keyChild) => {
|
|
56
|
-
|
|
75
|
+
out[keyChild] = transformObject(v[keyChild], mapChild, passThroughKeys, ignoreKeys);
|
|
57
76
|
});
|
|
77
|
+
v = out;
|
|
58
78
|
}
|
|
59
79
|
else if (map[key + '_arr']) {
|
|
60
80
|
const mapChild = map[key + '_arr'];
|
|
@@ -72,8 +92,14 @@ function transformObject(dataIn, map, passThroughKeys, ignoreKeys) {
|
|
|
72
92
|
debugger;
|
|
73
93
|
return ret;
|
|
74
94
|
}
|
|
95
|
+
function transformObjectWithPath(obj, path, pathTypes, fieldTransforms) {
|
|
96
|
+
let constructed = constructObjectWithPath(path, obj, pathTypes);
|
|
97
|
+
const transformed = transformObject(constructed, fieldTransforms, [dateModifiedKey]);
|
|
98
|
+
const transformedPath = transformPath(path, fieldTransforms, [dateModifiedKey]);
|
|
99
|
+
return { path: transformedPath, obj: deconstructObjectWithPath(transformedPath, transformed) };
|
|
100
|
+
}
|
|
75
101
|
const invertedMaps = new WeakMap();
|
|
76
|
-
function
|
|
102
|
+
function invertFieldMap(obj) {
|
|
77
103
|
// Note: If changing this, change it in IndexedDB preloader
|
|
78
104
|
const existing = invertedMaps.get(obj);
|
|
79
105
|
if (existing)
|
|
@@ -84,12 +110,12 @@ function invertMap(obj) {
|
|
|
84
110
|
if (process.env.NODE_ENV === 'development' && target[val])
|
|
85
111
|
debugger;
|
|
86
112
|
if (key === '_dict') {
|
|
87
|
-
target[key] =
|
|
113
|
+
target[key] = invertFieldMap(val);
|
|
88
114
|
}
|
|
89
115
|
else if (key.endsWith('_obj') || key.endsWith('_dict') || key.endsWith('_arr')) {
|
|
90
116
|
const keyMapped = obj[key.replace(/_obj|_dict|_arr$/, '')];
|
|
91
117
|
const suffix = key.match(/_obj|_dict|_arr$/)[0];
|
|
92
|
-
target[keyMapped + suffix] =
|
|
118
|
+
target[keyMapped + suffix] = invertFieldMap(val);
|
|
93
119
|
}
|
|
94
120
|
else if (typeof val === 'string') {
|
|
95
121
|
target[val] = key;
|
|
@@ -100,21 +126,25 @@ function invertMap(obj) {
|
|
|
100
126
|
invertedMaps.set(obj, target);
|
|
101
127
|
return target;
|
|
102
128
|
}
|
|
103
|
-
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
if (process.env.NODE_ENV === 'development') {
|
|
130
|
+
validateMap = function (record) {
|
|
131
|
+
const values = Object.values(record).filter((value) => {
|
|
132
|
+
if (isObject(value)) {
|
|
133
|
+
validateMap(value);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
return isString(value);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
const uniques = Array.from(new Set(values));
|
|
140
|
+
if (values.length !== uniques.length) {
|
|
141
|
+
console.error('Field transform map has duplicate values', record, values.length, uniques.length);
|
|
142
|
+
debugger;
|
|
114
143
|
}
|
|
115
|
-
|
|
116
|
-
|
|
144
|
+
return record;
|
|
145
|
+
};
|
|
117
146
|
}
|
|
147
|
+
|
|
118
148
|
function replaceKeyInObject(obj, keySource, keyTarget, clone) {
|
|
119
149
|
if (isObject(obj)) {
|
|
120
150
|
const target = clone ? {} : obj;
|
|
@@ -122,6 +152,9 @@ function replaceKeyInObject(obj, keySource, keyTarget, clone) {
|
|
|
122
152
|
target[keyTarget] = obj[keySource];
|
|
123
153
|
delete target[keySource];
|
|
124
154
|
}
|
|
155
|
+
if (keySource !== symbolDateModified && obj[symbolDateModified]) {
|
|
156
|
+
target[symbolDateModified] = obj[symbolDateModified];
|
|
157
|
+
}
|
|
125
158
|
Object.keys(obj).forEach((key) => {
|
|
126
159
|
if (key !== keySource) {
|
|
127
160
|
target[key] = replaceKeyInObject(obj[key], keySource, keyTarget, clone);
|
|
@@ -136,59 +169,139 @@ function replaceKeyInObject(obj, keySource, keyTarget, clone) {
|
|
|
136
169
|
function getDateModifiedKey(dateModifiedKey) {
|
|
137
170
|
return dateModifiedKey || observablePersistConfiguration.dateModifiedKey || '@';
|
|
138
171
|
}
|
|
172
|
+
function mergeDateModified(obs, source) {
|
|
173
|
+
const isArr = isArray(source);
|
|
174
|
+
const isObj = !isArr && isObject(source);
|
|
175
|
+
let dateModified = isObj && source[symbolDateModified];
|
|
176
|
+
if (dateModified) {
|
|
177
|
+
delete source[symbolDateModified];
|
|
178
|
+
}
|
|
179
|
+
if (isArr || isObj) {
|
|
180
|
+
const keys = isArr ? source : Object.keys(source);
|
|
181
|
+
for (let i = 0; i < keys.length; i++) {
|
|
182
|
+
const key = isArr ? i : keys[i];
|
|
183
|
+
dateModified = Math.max(dateModified || 0, mergeDateModified(obs[key], source[key]));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (dateModified) {
|
|
187
|
+
obs.peek()[symbolDateModified] = dateModified;
|
|
188
|
+
}
|
|
189
|
+
return dateModified || 0;
|
|
190
|
+
}
|
|
139
191
|
|
|
140
192
|
const mapPersistences = new WeakMap();
|
|
141
193
|
const persistState = observable({ inRemoteSync: false });
|
|
142
194
|
function parseLocalConfig(config) {
|
|
143
195
|
return isString(config) ? { table: config, config: { name: config } } : { table: config.name, config };
|
|
144
196
|
}
|
|
145
|
-
|
|
146
|
-
|
|
197
|
+
let isMergingLocalData = false;
|
|
198
|
+
function adjustSaveData(value, path, pathTypes, { adjustData, fieldTransforms, }, replaceKey) {
|
|
199
|
+
let cloned = replaceKey ? replaceKeyInObject(value, symbolDateModified, dateModifiedKey, /*clone*/ true) : value;
|
|
200
|
+
const transform = () => {
|
|
201
|
+
if (fieldTransforms) {
|
|
202
|
+
const { obj, path: pathTransformed } = transformObjectWithPath(cloned, path, pathTypes, fieldTransforms);
|
|
203
|
+
cloned = obj;
|
|
204
|
+
path = pathTransformed;
|
|
205
|
+
}
|
|
206
|
+
return { value: cloned, path };
|
|
207
|
+
};
|
|
208
|
+
let promise;
|
|
209
|
+
if (adjustData === null || adjustData === void 0 ? void 0 : adjustData.save) {
|
|
210
|
+
const constructed = constructObjectWithPath(path, cloned, pathTypes);
|
|
211
|
+
promise = adjustData.save(constructed);
|
|
212
|
+
}
|
|
213
|
+
return isPromise(promise)
|
|
214
|
+
? promise.then((adjusted) => {
|
|
215
|
+
cloned = deconstructObjectWithPath(path, adjusted);
|
|
216
|
+
return transform();
|
|
217
|
+
})
|
|
218
|
+
: transform();
|
|
219
|
+
}
|
|
220
|
+
function adjustLoadData(value, { adjustData, fieldTransforms, }, replaceKey) {
|
|
221
|
+
let cloned = replaceKey ? replaceKeyInObject(value, dateModifiedKey, symbolDateModified, /*clone*/ true) : value;
|
|
222
|
+
if (fieldTransforms) {
|
|
223
|
+
const inverted = invertFieldMap(fieldTransforms);
|
|
224
|
+
cloned = transformObject(cloned, inverted, [dateModifiedKey]);
|
|
225
|
+
}
|
|
226
|
+
if (adjustData === null || adjustData === void 0 ? void 0 : adjustData.load) {
|
|
227
|
+
cloned = adjustData.load(cloned);
|
|
228
|
+
}
|
|
229
|
+
return cloned;
|
|
230
|
+
}
|
|
231
|
+
async function onObsChange(obs, obsState, localState, persistOptions, { value, changes }) {
|
|
232
|
+
var _a;
|
|
233
|
+
const { persistenceLocal, persistenceRemote, isApplyingPending } = localState;
|
|
147
234
|
const local = persistOptions.local;
|
|
148
235
|
const { table, config } = parseLocalConfig(local);
|
|
236
|
+
const configRemote = persistOptions.remote;
|
|
149
237
|
const inRemoteChange = tracking$1.inRemoteChange;
|
|
150
|
-
const saveRemote = !
|
|
151
|
-
|
|
238
|
+
const saveRemote = !isMergingLocalData &&
|
|
239
|
+
!inRemoteChange &&
|
|
240
|
+
configRemote &&
|
|
241
|
+
!configRemote.readonly &&
|
|
242
|
+
obsState.isEnabledRemote.peek();
|
|
243
|
+
const isQueryingModified = !!((_a = configRemote === null || configRemote === void 0 ? void 0 : configRemote.firebase) === null || _a === void 0 ? void 0 : _a.queryByModified);
|
|
244
|
+
if (local && !config.readonly && !isApplyingPending && obsState.isEnabledLocal.peek()) {
|
|
152
245
|
if (!obsState.isLoadedLocal.peek()) {
|
|
153
246
|
console.error('[legend-state] WARNING: An observable was changed before being loaded from persistence', local);
|
|
154
247
|
return;
|
|
155
248
|
}
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const pathStr = path.join('/');
|
|
166
|
-
if (!localState.pendingChanges) {
|
|
167
|
-
localState.pendingChanges = {};
|
|
168
|
-
}
|
|
169
|
-
// The value saved in pending should be the previous state before changes,
|
|
170
|
-
// so don't overwrite it if it already exists
|
|
171
|
-
if (!localState.pendingChanges[pathStr]) {
|
|
172
|
-
localState.pendingChanges[pathStr] = { p: prevAtPath !== null && prevAtPath !== void 0 ? prevAtPath : null };
|
|
173
|
-
}
|
|
174
|
-
localState.pendingChanges[pathStr].v = valueAtPath;
|
|
249
|
+
// Prepare pending changes
|
|
250
|
+
if (saveRemote) {
|
|
251
|
+
for (let i = 0; i < changes.length; i++) {
|
|
252
|
+
const { path, valueAtPath, prevAtPath, pathTypes } = changes[i];
|
|
253
|
+
if (path[path.length - 1] === symbolDateModified)
|
|
254
|
+
continue;
|
|
255
|
+
const pathStr = path.join('/');
|
|
256
|
+
if (!localState.pendingChanges) {
|
|
257
|
+
localState.pendingChanges = {};
|
|
175
258
|
}
|
|
259
|
+
// The value saved in pending should be the previous state before changes,
|
|
260
|
+
// so don't overwrite it if it already exists
|
|
261
|
+
if (!localState.pendingChanges[pathStr]) {
|
|
262
|
+
localState.pendingChanges[pathStr] = { p: prevAtPath !== null && prevAtPath !== void 0 ? prevAtPath : null, t: pathTypes };
|
|
263
|
+
}
|
|
264
|
+
localState.pendingChanges[pathStr].v = valueAtPath;
|
|
176
265
|
}
|
|
177
266
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
187
|
-
}
|
|
267
|
+
// Save changes locally
|
|
268
|
+
const changesLocal = [];
|
|
269
|
+
const changesPaths = new Set();
|
|
270
|
+
const promises = [];
|
|
271
|
+
changes.forEach((_, i) => {
|
|
272
|
+
// Reverse order
|
|
273
|
+
let { path: pathOriginal, prevAtPath, valueAtPath, pathTypes } = changes[changes.length - 1 - i];
|
|
274
|
+
if (isSymbol(pathOriginal[pathOriginal.length - 1])) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (isQueryingModified) {
|
|
278
|
+
pathOriginal = pathOriginal.map((p) => (p === symbolDateModified ? dateModifiedKey : p));
|
|
279
|
+
}
|
|
280
|
+
const pathStr = pathOriginal.join('/');
|
|
281
|
+
// Optimization to only save the latest update at each path. We might have multiple changes at the same path
|
|
282
|
+
// and we only need the latest value, so it starts from the end of the array, skipping any earlier changes
|
|
283
|
+
// already processed.
|
|
284
|
+
if (!changesPaths.has(pathStr)) {
|
|
285
|
+
changesPaths.add(pathStr);
|
|
286
|
+
let promise = adjustSaveData(valueAtPath, pathOriginal, pathTypes, config, isQueryingModified);
|
|
287
|
+
const push = ({ path, value }) => {
|
|
288
|
+
changesLocal.push({ path, pathTypes, prevAtPath, valueAtPath: value });
|
|
289
|
+
};
|
|
290
|
+
if (isPromise(promise)) {
|
|
291
|
+
promises.push(promise.then(push));
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
push(promise);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
if (promises.length > 0) {
|
|
299
|
+
await Promise.all(promises);
|
|
188
300
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
301
|
+
if (changesLocal.length > 0) {
|
|
302
|
+
persistenceLocal.set(table, changesLocal, config);
|
|
303
|
+
}
|
|
304
|
+
// Save metadata
|
|
192
305
|
const metadata = {};
|
|
193
306
|
if (inRemoteChange) {
|
|
194
307
|
const dateModified = value[symbolDateModified];
|
|
@@ -204,49 +317,55 @@ async function onObsChange(obs, obsState, localState, persistOptions, value, get
|
|
|
204
317
|
}
|
|
205
318
|
}
|
|
206
319
|
if (saveRemote) {
|
|
207
|
-
await when(obsState.isLoadedRemote);
|
|
208
|
-
|
|
209
|
-
|
|
320
|
+
await when(() => obsState.isLoadedRemote.get() || (configRemote.allowSaveIfError && obsState.remoteError.get()));
|
|
321
|
+
const fieldTransforms = configRemote.fieldTransforms;
|
|
322
|
+
changes.forEach(async (change) => {
|
|
323
|
+
const { path, valueAtPath, prevAtPath, pathTypes } = change;
|
|
210
324
|
if (path[path.length - 1] === symbolDateModified)
|
|
211
|
-
|
|
325
|
+
return;
|
|
212
326
|
const pathStr = path.join('/');
|
|
327
|
+
const { path: pathSave, value: valueSave } = await adjustSaveData(valueAtPath, path, pathTypes, configRemote, isQueryingModified);
|
|
213
328
|
// Save to remote persistence and get the remote value from it. Some providers (like Firebase) will return a
|
|
214
|
-
// server value
|
|
215
|
-
persistenceRemote
|
|
329
|
+
// server value with server timestamps for dateModified.
|
|
330
|
+
persistenceRemote
|
|
331
|
+
.save({
|
|
332
|
+
obs,
|
|
333
|
+
state: obsState,
|
|
334
|
+
options: persistOptions,
|
|
335
|
+
path: pathSave,
|
|
336
|
+
pathTypes,
|
|
337
|
+
valueAtPath: valueSave,
|
|
338
|
+
prevAtPath,
|
|
339
|
+
})
|
|
340
|
+
.then((saved) => {
|
|
216
341
|
var _a;
|
|
217
342
|
if (local) {
|
|
218
|
-
let toSave = persistenceLocal.getTable(table, config);
|
|
219
343
|
const pending = (_a = persistenceLocal.getMetadata(table, config)) === null || _a === void 0 ? void 0 : _a.pending;
|
|
220
|
-
|
|
221
|
-
let didDelete = false;
|
|
344
|
+
// Clear pending for this path
|
|
222
345
|
if (pending === null || pending === void 0 ? void 0 : pending[pathStr]) {
|
|
223
|
-
didDelete = true;
|
|
224
346
|
// Remove pending from the saved object
|
|
225
347
|
delete pending[pathStr];
|
|
226
348
|
// Remove pending from local state
|
|
227
349
|
delete localState.pendingChanges[pathStr];
|
|
350
|
+
persistenceLocal.updateMetadata(table, { pending }, config);
|
|
228
351
|
}
|
|
229
352
|
// Only the latest save will return a value so that it saves back to local persistence once
|
|
230
|
-
|
|
353
|
+
// It needs to get the dateModified from the save and update that through the observable
|
|
354
|
+
// which will fire onObsChange and save it locally.
|
|
355
|
+
if (saved !== undefined && isQueryingModified) {
|
|
356
|
+
// Note: Don't need to adjust data because we're just merging dateModified
|
|
357
|
+
const invertedMap = fieldTransforms && invertFieldMap(fieldTransforms);
|
|
358
|
+
if (invertedMap) {
|
|
359
|
+
saved = transformObject(saved, invertedMap, [dateModifiedKey]);
|
|
360
|
+
}
|
|
231
361
|
onChangeRemote(() => {
|
|
232
|
-
|
|
362
|
+
mergeDateModified(obs, saved);
|
|
233
363
|
});
|
|
234
|
-
dateModified = saved[symbolDateModified];
|
|
235
|
-
// Replace the dateModifiedKey and remove null/undefined before saving
|
|
236
|
-
if (config.fieldTransforms) {
|
|
237
|
-
saved = transformObject(saved, config.fieldTransforms, [dateModifiedKey]);
|
|
238
|
-
}
|
|
239
|
-
saved = replaceKeyInObject(removeNullUndefined(saved), symbolDateModified, dateModifiedKey,
|
|
240
|
-
/*clone*/ false);
|
|
241
|
-
toSave = toSave ? mergeIntoObservable(toSave, saved) : saved;
|
|
242
|
-
}
|
|
243
|
-
if (saved !== undefined || didDelete) {
|
|
244
|
-
persistenceLocal.set(table, toSave, [changes[i]], config);
|
|
245
|
-
persistenceLocal.updateMetadata(table, { pending, modified: dateModified }, config);
|
|
246
364
|
}
|
|
247
365
|
}
|
|
366
|
+
localState.onSaveRemoteListeners.forEach((cb) => cb());
|
|
248
367
|
});
|
|
249
|
-
}
|
|
368
|
+
});
|
|
250
369
|
}
|
|
251
370
|
}
|
|
252
371
|
function onChangeRemote(cb) {
|
|
@@ -264,23 +383,33 @@ function onChangeRemote(cb) {
|
|
|
264
383
|
}
|
|
265
384
|
}
|
|
266
385
|
async function loadLocal(obs, persistOptions, obsState, localState) {
|
|
267
|
-
var _a;
|
|
386
|
+
var _a, _b, _c, _d;
|
|
268
387
|
const { local, remote } = persistOptions;
|
|
269
388
|
const localPersistence = persistOptions.persistLocal || observablePersistConfiguration.persistLocal;
|
|
270
389
|
if (local) {
|
|
271
390
|
const { table, config } = parseLocalConfig(local);
|
|
391
|
+
const isQueryingModified = !!((_b = (_a = persistOptions.remote) === null || _a === void 0 ? void 0 : _a.firebase) === null || _b === void 0 ? void 0 : _b.queryByModified);
|
|
272
392
|
if (!localPersistence) {
|
|
273
393
|
throw new Error('Local persistence is not configured');
|
|
274
394
|
}
|
|
275
395
|
// Ensure there's only one instance of the persistence plugin
|
|
276
396
|
if (!mapPersistences.has(localPersistence)) {
|
|
277
397
|
const persistenceLocal = new localPersistence();
|
|
398
|
+
const mapValue = { persist: persistenceLocal, initialized: observable(false) };
|
|
399
|
+
mapPersistences.set(localPersistence, mapValue);
|
|
278
400
|
if (persistenceLocal.initialize) {
|
|
279
|
-
|
|
401
|
+
const initializePromise = (_c = persistenceLocal.initialize) === null || _c === void 0 ? void 0 : _c.call(persistenceLocal, observablePersistConfiguration.persistLocalOptions);
|
|
402
|
+
if (isPromise(initializePromise)) {
|
|
403
|
+
await initializePromise;
|
|
404
|
+
}
|
|
280
405
|
}
|
|
281
|
-
|
|
406
|
+
mapValue.initialized.set(true);
|
|
407
|
+
}
|
|
408
|
+
const { persist: persistenceLocal, initialized } = mapPersistences.get(localPersistence);
|
|
409
|
+
localState.persistenceLocal = persistenceLocal;
|
|
410
|
+
if (!initialized.get()) {
|
|
411
|
+
await when(initialized);
|
|
282
412
|
}
|
|
283
|
-
const persistenceLocal = (localState.persistenceLocal = mapPersistences.get(localPersistence));
|
|
284
413
|
// If persistence has an asynchronous load, wait for it
|
|
285
414
|
if (persistenceLocal.loadTable) {
|
|
286
415
|
const promise = persistenceLocal.loadTable(table, config);
|
|
@@ -291,36 +420,46 @@ async function loadLocal(obs, persistOptions, obsState, localState) {
|
|
|
291
420
|
// Get the value from state
|
|
292
421
|
let value = persistenceLocal.getTable(table, config);
|
|
293
422
|
const metadata = persistenceLocal.getMetadata(table, config);
|
|
294
|
-
if (config.fieldTransforms) {
|
|
295
|
-
// Get preloaded translated if available
|
|
296
|
-
let valueLoaded = persistenceLocal.getTableTransformed(table, config);
|
|
297
|
-
if (valueLoaded) {
|
|
298
|
-
value = valueLoaded;
|
|
299
|
-
}
|
|
300
|
-
else {
|
|
301
|
-
const inverted = invertMap(config.fieldTransforms);
|
|
302
|
-
value = transformObject(value, inverted, [dateModifiedKey]);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
423
|
if (metadata) {
|
|
306
424
|
const pending = metadata.pending;
|
|
307
425
|
localState.pendingChanges = pending;
|
|
308
426
|
}
|
|
309
427
|
// Merge the data from local persistence into the default state
|
|
310
428
|
if (value !== null && value !== undefined) {
|
|
311
|
-
|
|
312
|
-
|
|
429
|
+
let { adjustData, fieldTransforms } = config;
|
|
430
|
+
if (fieldTransforms) {
|
|
431
|
+
let valueLoaded = (_d = persistenceLocal.getTableTransformed) === null || _d === void 0 ? void 0 : _d.call(persistenceLocal, table, config);
|
|
432
|
+
if (valueLoaded) {
|
|
433
|
+
value = valueLoaded;
|
|
434
|
+
fieldTransforms = undefined;
|
|
435
|
+
}
|
|
313
436
|
}
|
|
314
|
-
|
|
315
|
-
|
|
437
|
+
value = adjustLoadData(value, { adjustData, fieldTransforms }, !!remote && isQueryingModified);
|
|
438
|
+
if (isPromise(value)) {
|
|
439
|
+
value = await value;
|
|
316
440
|
}
|
|
317
|
-
batch(() =>
|
|
441
|
+
batch(() => {
|
|
442
|
+
// isMergingLocalData prevents saving remotely when two different persistences
|
|
443
|
+
// are set on the same observable
|
|
444
|
+
isMergingLocalData = true;
|
|
445
|
+
// We want to merge the local data on top of any initial state the object is created with
|
|
446
|
+
mergeIntoObservable(obs, value);
|
|
447
|
+
if (metadata === null || metadata === void 0 ? void 0 : metadata.modified) {
|
|
448
|
+
obs.peek()[symbolDateModified] = metadata.modified;
|
|
449
|
+
}
|
|
450
|
+
}, () => {
|
|
451
|
+
isMergingLocalData = false;
|
|
452
|
+
});
|
|
318
453
|
}
|
|
319
454
|
obsState.peek().clearLocal = () => persistenceLocal.deleteTable(table, config);
|
|
320
455
|
}
|
|
321
456
|
obsState.isLoadedLocal.set(true);
|
|
322
457
|
}
|
|
323
458
|
function persistObservable(obs, persistOptions) {
|
|
459
|
+
const { remote, local } = persistOptions;
|
|
460
|
+
const remotePersistence = persistOptions.persistRemote || (observablePersistConfiguration === null || observablePersistConfiguration === void 0 ? void 0 : observablePersistConfiguration.persistRemote);
|
|
461
|
+
const onSaveRemoteListeners = [];
|
|
462
|
+
const localState = { onSaveRemoteListeners };
|
|
324
463
|
const obsState = observable({
|
|
325
464
|
isLoadedLocal: false,
|
|
326
465
|
isLoadedRemote: false,
|
|
@@ -328,10 +467,8 @@ function persistObservable(obs, persistOptions) {
|
|
|
328
467
|
isEnabledRemote: true,
|
|
329
468
|
clearLocal: undefined,
|
|
330
469
|
sync: () => Promise.resolve(),
|
|
470
|
+
getPendingChanges: () => localState.pendingChanges,
|
|
331
471
|
});
|
|
332
|
-
const { remote, local } = persistOptions;
|
|
333
|
-
const remotePersistence = persistOptions.persistRemote || (observablePersistConfiguration === null || observablePersistConfiguration === void 0 ? void 0 : observablePersistConfiguration.persistRemote);
|
|
334
|
-
const localState = {};
|
|
335
472
|
if (local) {
|
|
336
473
|
loadLocal(obs, persistOptions, obsState, localState);
|
|
337
474
|
}
|
|
@@ -341,25 +478,66 @@ function persistObservable(obs, persistOptions) {
|
|
|
341
478
|
}
|
|
342
479
|
// Ensure there's only one instance of the persistence plugin
|
|
343
480
|
if (!mapPersistences.has(remotePersistence)) {
|
|
344
|
-
mapPersistences.set(remotePersistence, new remotePersistence());
|
|
481
|
+
mapPersistences.set(remotePersistence, { persist: new remotePersistence() });
|
|
345
482
|
}
|
|
346
|
-
localState.persistenceRemote = mapPersistences.get(remotePersistence);
|
|
483
|
+
localState.persistenceRemote = mapPersistences.get(remotePersistence).persist;
|
|
347
484
|
let isSynced = false;
|
|
348
485
|
const sync = async () => {
|
|
486
|
+
var _a;
|
|
349
487
|
if (!isSynced) {
|
|
350
488
|
isSynced = true;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
489
|
+
const onSaveRemote = (_a = persistOptions.remote) === null || _a === void 0 ? void 0 : _a.onSaveRemote;
|
|
490
|
+
if (onSaveRemote) {
|
|
491
|
+
onSaveRemoteListeners.push(onSaveRemote);
|
|
492
|
+
}
|
|
493
|
+
localState.persistenceRemote.listen({
|
|
494
|
+
state: obsState,
|
|
495
|
+
obs,
|
|
496
|
+
options: persistOptions,
|
|
497
|
+
onLoad: () => {
|
|
498
|
+
obsState.isLoadedRemote.set(true);
|
|
499
|
+
},
|
|
500
|
+
onChange: async ({ value, path, mode }) => {
|
|
501
|
+
if (value !== undefined) {
|
|
502
|
+
value = adjustLoadData(value, remote, true);
|
|
503
|
+
if (isPromise(value)) {
|
|
504
|
+
value = await value;
|
|
505
|
+
}
|
|
506
|
+
const pending = localState.pendingChanges;
|
|
507
|
+
if (pending) {
|
|
508
|
+
Object.keys(pending).forEach((key) => {
|
|
509
|
+
const p = key.split('/').filter((p) => p !== '');
|
|
510
|
+
const { v, t } = pending[key];
|
|
511
|
+
const constructed = constructObjectWithPath(p, v, t);
|
|
512
|
+
value = mergeIntoObservable(value, constructed);
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
const invertedMap = remote.fieldTransforms && invertFieldMap(remote.fieldTransforms);
|
|
516
|
+
if (path.length && invertedMap) {
|
|
517
|
+
path = transformPath(path, invertedMap);
|
|
518
|
+
}
|
|
519
|
+
onChangeRemote(() => {
|
|
520
|
+
setInObservableAtPath(obs, path, value, mode);
|
|
521
|
+
mergeDateModified(obs, value);
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
});
|
|
526
|
+
await when(() => obsState.isLoadedRemote.get() || (remote.allowSaveIfError && obsState.remoteError.get()));
|
|
355
527
|
const pending = localState.pendingChanges;
|
|
356
528
|
if (pending) {
|
|
529
|
+
localState.isApplyingPending = true;
|
|
357
530
|
Object.keys(pending).forEach((key) => {
|
|
358
531
|
const path = key.split('/').filter((p) => p !== '');
|
|
359
|
-
const { p, v } = pending[key];
|
|
532
|
+
const { p, v, t } = pending[key];
|
|
360
533
|
// TODO getPrevious if any remote persistence layers need it
|
|
361
|
-
onObsChange(obs, obsState, localState, persistOptions,
|
|
534
|
+
onObsChange(obs, obsState, localState, persistOptions, {
|
|
535
|
+
value: obs.peek(),
|
|
536
|
+
getPrevious: () => undefined,
|
|
537
|
+
changes: [{ path, valueAtPath: v, prevAtPath: p, pathTypes: t }],
|
|
538
|
+
});
|
|
362
539
|
});
|
|
540
|
+
localState.isApplyingPending = false;
|
|
363
541
|
}
|
|
364
542
|
}
|
|
365
543
|
};
|
|
@@ -385,5 +563,5 @@ function isInRemoteChange() {
|
|
|
385
563
|
return tracking.inRemoteChange;
|
|
386
564
|
}
|
|
387
565
|
|
|
388
|
-
export { configureObservablePersistence, getDateModifiedKey,
|
|
566
|
+
export { configureObservablePersistence, getDateModifiedKey, invertFieldMap, isInRemoteChange, mapPersistences, observablePersistConfiguration, onChangeRemote, persistObservable, persistState, transformObject, transformPath };
|
|
389
567
|
//# sourceMappingURL=persist.mjs.map
|