@legendapp/state 0.23.2 → 1.0.0-rc.1
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 +12 -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 +99 -67
- package/index.js.map +1 -1
- package/index.mjs +97 -64
- package/index.mjs.map +1 -1
- package/package.json +1 -1
- package/persist-plugins/indexeddb-preloader.js +13 -11
- package/persist-plugins/indexeddb-preloader.js.map +1 -1
- package/persist-plugins/indexeddb-preloader.mjs +13 -11
- package/persist-plugins/indexeddb-preloader.mjs.map +1 -1
- package/persist-plugins/indexeddb.d.ts +3 -2
- package/persist-plugins/indexeddb.js +87 -57
- package/persist-plugins/indexeddb.js.map +1 -1
- package/persist-plugins/indexeddb.mjs +88 -58
- 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 +274 -112
- package/persist.js.map +1 -1
- package/persist.mjs +274 -113
- 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 +2 -9
- package/src/helpers.d.ts +4 -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, setAtPath } 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,134 @@ 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[symbolDateModified].set(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
|
-
|
|
197
|
+
function adjustSaveData(value, path, pathTypes, { adjustData, fieldTransforms, }, replaceKey) {
|
|
198
|
+
let cloned = replaceKey ? replaceKeyInObject(value, symbolDateModified, dateModifiedKey, /*clone*/ true) : value;
|
|
199
|
+
const transform = () => {
|
|
200
|
+
if (fieldTransforms) {
|
|
201
|
+
const { obj, path: pathTransformed } = transformObjectWithPath(cloned, path, pathTypes, fieldTransforms);
|
|
202
|
+
cloned = obj;
|
|
203
|
+
path = pathTransformed;
|
|
204
|
+
}
|
|
205
|
+
return { value: cloned, path };
|
|
206
|
+
};
|
|
207
|
+
let promise;
|
|
208
|
+
if (adjustData === null || adjustData === void 0 ? void 0 : adjustData.save) {
|
|
209
|
+
const constructed = constructObjectWithPath(path, cloned, pathTypes);
|
|
210
|
+
promise = adjustData.save(constructed);
|
|
211
|
+
}
|
|
212
|
+
return isPromise(promise)
|
|
213
|
+
? promise.then((adjusted) => {
|
|
214
|
+
cloned = deconstructObjectWithPath(path, adjusted);
|
|
215
|
+
return transform();
|
|
216
|
+
})
|
|
217
|
+
: transform();
|
|
218
|
+
}
|
|
219
|
+
function adjustLoadData(value, { adjustData, fieldTransforms, }, replaceKey) {
|
|
220
|
+
let cloned = replaceKey ? replaceKeyInObject(value, dateModifiedKey, symbolDateModified, /*clone*/ true) : value;
|
|
221
|
+
if (fieldTransforms) {
|
|
222
|
+
const inverted = invertFieldMap(fieldTransforms);
|
|
223
|
+
cloned = transformObject(cloned, inverted, [dateModifiedKey]);
|
|
224
|
+
}
|
|
225
|
+
if (adjustData === null || adjustData === void 0 ? void 0 : adjustData.load) {
|
|
226
|
+
cloned = adjustData.load(cloned);
|
|
227
|
+
}
|
|
228
|
+
return cloned;
|
|
229
|
+
}
|
|
230
|
+
async function onObsChange(obs, obsState, localState, persistOptions, { value, changes }) {
|
|
231
|
+
var _a;
|
|
146
232
|
const { persistenceLocal, persistenceRemote } = localState;
|
|
147
233
|
const local = persistOptions.local;
|
|
148
234
|
const { table, config } = parseLocalConfig(local);
|
|
235
|
+
const configRemote = persistOptions.remote;
|
|
149
236
|
const inRemoteChange = tracking$1.inRemoteChange;
|
|
150
|
-
const saveRemote = !inRemoteChange &&
|
|
151
|
-
|
|
237
|
+
const saveRemote = !inRemoteChange && configRemote && !configRemote.readonly && obsState.isEnabledRemote.peek();
|
|
238
|
+
const isQueryingModified = !!((_a = configRemote === null || configRemote === void 0 ? void 0 : configRemote.firebase) === null || _a === void 0 ? void 0 : _a.queryByModified);
|
|
239
|
+
if (local && !config.readonly && obsState.isEnabledLocal.peek()) {
|
|
152
240
|
if (!obsState.isLoadedLocal.peek()) {
|
|
153
241
|
console.error('[legend-state] WARNING: An observable was changed before being loaded from persistence', local);
|
|
154
242
|
return;
|
|
155
243
|
}
|
|
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;
|
|
244
|
+
// Prepare pending changes
|
|
245
|
+
if (saveRemote) {
|
|
246
|
+
for (let i = 0; i < changes.length; i++) {
|
|
247
|
+
const { path, valueAtPath, prevAtPath, pathTypes } = changes[i];
|
|
248
|
+
if (path[path.length - 1] === symbolDateModified)
|
|
249
|
+
continue;
|
|
250
|
+
const pathStr = path.join('/');
|
|
251
|
+
if (!localState.pendingChanges) {
|
|
252
|
+
localState.pendingChanges = {};
|
|
175
253
|
}
|
|
254
|
+
// The value saved in pending should be the previous state before changes,
|
|
255
|
+
// so don't overwrite it if it already exists
|
|
256
|
+
if (!localState.pendingChanges[pathStr]) {
|
|
257
|
+
localState.pendingChanges[pathStr] = { p: prevAtPath !== null && prevAtPath !== void 0 ? prevAtPath : null, t: pathTypes };
|
|
258
|
+
}
|
|
259
|
+
localState.pendingChanges[pathStr].v = valueAtPath;
|
|
176
260
|
}
|
|
177
261
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
return
|
|
187
|
-
}
|
|
262
|
+
// Save changes locally
|
|
263
|
+
const changesLocal = [];
|
|
264
|
+
const changesPaths = new Set();
|
|
265
|
+
const promises = [];
|
|
266
|
+
changes.forEach((_, i) => {
|
|
267
|
+
// Reverse order
|
|
268
|
+
let { path: pathOriginal, prevAtPath, valueAtPath, pathTypes } = changes[changes.length - 1 - i];
|
|
269
|
+
if (isSymbol(pathOriginal[pathOriginal.length - 1])) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (isQueryingModified) {
|
|
273
|
+
pathOriginal = pathOriginal.map((p) => (p === symbolDateModified ? dateModifiedKey : p));
|
|
274
|
+
}
|
|
275
|
+
const pathStr = pathOriginal.join('/');
|
|
276
|
+
// Optimization to only save the latest update at each path. We might have multiple changes at the same path
|
|
277
|
+
// and we only need the latest value, so it starts from the end of the array, skipping any earlier changes
|
|
278
|
+
// already processed.
|
|
279
|
+
if (!changesPaths.has(pathStr)) {
|
|
280
|
+
changesPaths.add(pathStr);
|
|
281
|
+
let promise = adjustSaveData(valueAtPath, pathOriginal, pathTypes, config, isQueryingModified);
|
|
282
|
+
const push = ({ path, value }) => {
|
|
283
|
+
changesLocal.push({ path, pathTypes, prevAtPath, valueAtPath: value });
|
|
284
|
+
};
|
|
285
|
+
if (isPromise(promise)) {
|
|
286
|
+
promises.push(promise.then(push));
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
push(promise);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
if (promises.length > 0) {
|
|
294
|
+
await Promise.all(promises);
|
|
188
295
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
296
|
+
if (changesLocal.length > 0) {
|
|
297
|
+
persistenceLocal.set(table, changesLocal, config);
|
|
298
|
+
}
|
|
299
|
+
// Save metadata
|
|
192
300
|
const metadata = {};
|
|
193
301
|
if (inRemoteChange) {
|
|
194
302
|
const dateModified = value[symbolDateModified];
|
|
@@ -204,49 +312,55 @@ async function onObsChange(obs, obsState, localState, persistOptions, value, get
|
|
|
204
312
|
}
|
|
205
313
|
}
|
|
206
314
|
if (saveRemote) {
|
|
207
|
-
await when(obsState.isLoadedRemote);
|
|
208
|
-
|
|
209
|
-
|
|
315
|
+
await when(() => obsState.isLoadedRemote.get() || (configRemote.allowSaveIfError && obsState.remoteError.get()));
|
|
316
|
+
const fieldTransforms = configRemote.fieldTransforms;
|
|
317
|
+
changes.forEach(async (change) => {
|
|
318
|
+
const { path, valueAtPath, prevAtPath, pathTypes } = change;
|
|
210
319
|
if (path[path.length - 1] === symbolDateModified)
|
|
211
|
-
|
|
320
|
+
return;
|
|
212
321
|
const pathStr = path.join('/');
|
|
322
|
+
const { path: pathSave, value: valueSave } = await adjustSaveData(valueAtPath, path, pathTypes, configRemote, isQueryingModified);
|
|
213
323
|
// Save to remote persistence and get the remote value from it. Some providers (like Firebase) will return a
|
|
214
|
-
// server value
|
|
215
|
-
persistenceRemote
|
|
324
|
+
// server value with server timestamps for dateModified.
|
|
325
|
+
persistenceRemote
|
|
326
|
+
.save({
|
|
327
|
+
obs,
|
|
328
|
+
state: obsState,
|
|
329
|
+
options: persistOptions,
|
|
330
|
+
path: pathSave,
|
|
331
|
+
pathTypes,
|
|
332
|
+
valueAtPath: valueSave,
|
|
333
|
+
prevAtPath,
|
|
334
|
+
})
|
|
335
|
+
.then((saved) => {
|
|
216
336
|
var _a;
|
|
217
337
|
if (local) {
|
|
218
|
-
let toSave = persistenceLocal.getTable(table, config);
|
|
219
338
|
const pending = (_a = persistenceLocal.getMetadata(table, config)) === null || _a === void 0 ? void 0 : _a.pending;
|
|
220
|
-
|
|
221
|
-
let didDelete = false;
|
|
339
|
+
// Clear pending for this path
|
|
222
340
|
if (pending === null || pending === void 0 ? void 0 : pending[pathStr]) {
|
|
223
|
-
didDelete = true;
|
|
224
341
|
// Remove pending from the saved object
|
|
225
342
|
delete pending[pathStr];
|
|
226
343
|
// Remove pending from local state
|
|
227
344
|
delete localState.pendingChanges[pathStr];
|
|
345
|
+
persistenceLocal.updateMetadata(table, { pending }, config);
|
|
228
346
|
}
|
|
229
347
|
// Only the latest save will return a value so that it saves back to local persistence once
|
|
230
|
-
|
|
348
|
+
// It needs to get the dateModified from the save and update that through the observable
|
|
349
|
+
// which will fire onObsChange and save it locally.
|
|
350
|
+
if (saved !== undefined && isQueryingModified) {
|
|
351
|
+
// Note: Don't need to adjust data because we're just merging dateModified
|
|
352
|
+
const invertedMap = fieldTransforms && invertFieldMap(fieldTransforms);
|
|
353
|
+
if (invertedMap) {
|
|
354
|
+
saved = transformObject(saved, invertedMap, [dateModifiedKey]);
|
|
355
|
+
}
|
|
231
356
|
onChangeRemote(() => {
|
|
232
|
-
|
|
357
|
+
mergeDateModified(obs, saved);
|
|
233
358
|
});
|
|
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
359
|
}
|
|
247
360
|
}
|
|
361
|
+
localState.onSaveRemoteListeners.forEach((cb) => cb());
|
|
248
362
|
});
|
|
249
|
-
}
|
|
363
|
+
});
|
|
250
364
|
}
|
|
251
365
|
}
|
|
252
366
|
function onChangeRemote(cb) {
|
|
@@ -264,23 +378,33 @@ function onChangeRemote(cb) {
|
|
|
264
378
|
}
|
|
265
379
|
}
|
|
266
380
|
async function loadLocal(obs, persistOptions, obsState, localState) {
|
|
267
|
-
var _a;
|
|
381
|
+
var _a, _b, _c, _d;
|
|
268
382
|
const { local, remote } = persistOptions;
|
|
269
383
|
const localPersistence = persistOptions.persistLocal || observablePersistConfiguration.persistLocal;
|
|
270
384
|
if (local) {
|
|
271
385
|
const { table, config } = parseLocalConfig(local);
|
|
386
|
+
const isQueryingModified = !!((_b = (_a = persistOptions.remote) === null || _a === void 0 ? void 0 : _a.firebase) === null || _b === void 0 ? void 0 : _b.queryByModified);
|
|
272
387
|
if (!localPersistence) {
|
|
273
388
|
throw new Error('Local persistence is not configured');
|
|
274
389
|
}
|
|
275
390
|
// Ensure there's only one instance of the persistence plugin
|
|
276
391
|
if (!mapPersistences.has(localPersistence)) {
|
|
277
392
|
const persistenceLocal = new localPersistence();
|
|
393
|
+
const mapValue = { persist: persistenceLocal, initialized: observable(false) };
|
|
394
|
+
mapPersistences.set(localPersistence, mapValue);
|
|
278
395
|
if (persistenceLocal.initialize) {
|
|
279
|
-
|
|
396
|
+
const initializePromise = (_c = persistenceLocal.initialize) === null || _c === void 0 ? void 0 : _c.call(persistenceLocal, observablePersistConfiguration.persistLocalOptions);
|
|
397
|
+
if (isPromise(initializePromise)) {
|
|
398
|
+
await initializePromise;
|
|
399
|
+
}
|
|
280
400
|
}
|
|
281
|
-
|
|
401
|
+
mapValue.initialized.set(true);
|
|
402
|
+
}
|
|
403
|
+
const { persist: persistenceLocal, initialized } = mapPersistences.get(localPersistence);
|
|
404
|
+
localState.persistenceLocal = persistenceLocal;
|
|
405
|
+
if (!initialized.get()) {
|
|
406
|
+
await when(initialized);
|
|
282
407
|
}
|
|
283
|
-
const persistenceLocal = (localState.persistenceLocal = mapPersistences.get(localPersistence));
|
|
284
408
|
// If persistence has an asynchronous load, wait for it
|
|
285
409
|
if (persistenceLocal.loadTable) {
|
|
286
410
|
const promise = persistenceLocal.loadTable(table, config);
|
|
@@ -291,36 +415,37 @@ async function loadLocal(obs, persistOptions, obsState, localState) {
|
|
|
291
415
|
// Get the value from state
|
|
292
416
|
let value = persistenceLocal.getTable(table, config);
|
|
293
417
|
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
418
|
if (metadata) {
|
|
306
419
|
const pending = metadata.pending;
|
|
307
420
|
localState.pendingChanges = pending;
|
|
308
421
|
}
|
|
309
422
|
// Merge the data from local persistence into the default state
|
|
310
423
|
if (value !== null && value !== undefined) {
|
|
311
|
-
|
|
312
|
-
|
|
424
|
+
let { adjustData, fieldTransforms } = config;
|
|
425
|
+
if (fieldTransforms) {
|
|
426
|
+
let valueLoaded = (_d = persistenceLocal.getTableTransformed) === null || _d === void 0 ? void 0 : _d.call(persistenceLocal, table, config);
|
|
427
|
+
if (valueLoaded) {
|
|
428
|
+
value = valueLoaded;
|
|
429
|
+
fieldTransforms = undefined;
|
|
430
|
+
}
|
|
313
431
|
}
|
|
314
|
-
|
|
315
|
-
|
|
432
|
+
value = adjustLoadData(value, { adjustData, fieldTransforms }, !!remote && isQueryingModified);
|
|
433
|
+
if (isPromise(value)) {
|
|
434
|
+
value = await value;
|
|
316
435
|
}
|
|
317
|
-
batch(() =>
|
|
436
|
+
batch(() => {
|
|
437
|
+
mergeIntoObservable(obs, value);
|
|
438
|
+
});
|
|
318
439
|
}
|
|
319
440
|
obsState.peek().clearLocal = () => persistenceLocal.deleteTable(table, config);
|
|
320
441
|
}
|
|
321
442
|
obsState.isLoadedLocal.set(true);
|
|
322
443
|
}
|
|
323
444
|
function persistObservable(obs, persistOptions) {
|
|
445
|
+
const { remote, local } = persistOptions;
|
|
446
|
+
const remotePersistence = persistOptions.persistRemote || (observablePersistConfiguration === null || observablePersistConfiguration === void 0 ? void 0 : observablePersistConfiguration.persistRemote);
|
|
447
|
+
const onSaveRemoteListeners = [];
|
|
448
|
+
const localState = { onSaveRemoteListeners };
|
|
324
449
|
const obsState = observable({
|
|
325
450
|
isLoadedLocal: false,
|
|
326
451
|
isLoadedRemote: false,
|
|
@@ -328,10 +453,8 @@ function persistObservable(obs, persistOptions) {
|
|
|
328
453
|
isEnabledRemote: true,
|
|
329
454
|
clearLocal: undefined,
|
|
330
455
|
sync: () => Promise.resolve(),
|
|
456
|
+
getPendingChanges: () => localState.pendingChanges,
|
|
331
457
|
});
|
|
332
|
-
const { remote, local } = persistOptions;
|
|
333
|
-
const remotePersistence = persistOptions.persistRemote || (observablePersistConfiguration === null || observablePersistConfiguration === void 0 ? void 0 : observablePersistConfiguration.persistRemote);
|
|
334
|
-
const localState = {};
|
|
335
458
|
if (local) {
|
|
336
459
|
loadLocal(obs, persistOptions, obsState, localState);
|
|
337
460
|
}
|
|
@@ -341,24 +464,62 @@ function persistObservable(obs, persistOptions) {
|
|
|
341
464
|
}
|
|
342
465
|
// Ensure there's only one instance of the persistence plugin
|
|
343
466
|
if (!mapPersistences.has(remotePersistence)) {
|
|
344
|
-
mapPersistences.set(remotePersistence, new remotePersistence());
|
|
467
|
+
mapPersistences.set(remotePersistence, { persist: new remotePersistence() });
|
|
345
468
|
}
|
|
346
|
-
localState.persistenceRemote = mapPersistences.get(remotePersistence);
|
|
469
|
+
localState.persistenceRemote = mapPersistences.get(remotePersistence).persist;
|
|
347
470
|
let isSynced = false;
|
|
348
471
|
const sync = async () => {
|
|
472
|
+
var _a;
|
|
349
473
|
if (!isSynced) {
|
|
350
474
|
isSynced = true;
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
475
|
+
const onSaveRemote = (_a = persistOptions.remote) === null || _a === void 0 ? void 0 : _a.onSaveRemote;
|
|
476
|
+
if (onSaveRemote) {
|
|
477
|
+
onSaveRemoteListeners.push(onSaveRemote);
|
|
478
|
+
}
|
|
479
|
+
localState.persistenceRemote.listen({
|
|
480
|
+
state: obsState,
|
|
481
|
+
obs,
|
|
482
|
+
options: persistOptions,
|
|
483
|
+
onLoad: () => {
|
|
484
|
+
obsState.isLoadedRemote.set(true);
|
|
485
|
+
},
|
|
486
|
+
onChange: async ({ value, path, mode }) => {
|
|
487
|
+
if (value !== undefined) {
|
|
488
|
+
value = adjustLoadData(value, remote, true);
|
|
489
|
+
if (isPromise(value)) {
|
|
490
|
+
value = await value;
|
|
491
|
+
}
|
|
492
|
+
const pending = localState.pendingChanges;
|
|
493
|
+
if (pending) {
|
|
494
|
+
Object.keys(pending).forEach((key) => {
|
|
495
|
+
const p = key.split('/').filter((p) => p !== '');
|
|
496
|
+
const { v, t } = pending[key];
|
|
497
|
+
const constructed = constructObjectWithPath(p, v, t);
|
|
498
|
+
value = mergeIntoObservable(value, constructed);
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
const invertedMap = remote.fieldTransforms && invertFieldMap(remote.fieldTransforms);
|
|
502
|
+
if (path.length && invertedMap) {
|
|
503
|
+
path = transformPath(path, invertedMap);
|
|
504
|
+
}
|
|
505
|
+
onChangeRemote(() => {
|
|
506
|
+
setAtPath(obs, path, value, mode);
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
await when(() => obsState.isLoadedRemote.get() || (remote.allowSaveIfError && obsState.remoteError.get()));
|
|
355
512
|
const pending = localState.pendingChanges;
|
|
356
513
|
if (pending) {
|
|
357
514
|
Object.keys(pending).forEach((key) => {
|
|
358
515
|
const path = key.split('/').filter((p) => p !== '');
|
|
359
|
-
const { p, v } = pending[key];
|
|
516
|
+
const { p, v, t } = pending[key];
|
|
360
517
|
// TODO getPrevious if any remote persistence layers need it
|
|
361
|
-
onObsChange(obs, obsState, localState, persistOptions,
|
|
518
|
+
onObsChange(obs, obsState, localState, persistOptions, {
|
|
519
|
+
value: obs.peek(),
|
|
520
|
+
getPrevious: () => undefined,
|
|
521
|
+
changes: [{ path, valueAtPath: v, prevAtPath: p, pathTypes: t }],
|
|
522
|
+
});
|
|
362
523
|
});
|
|
363
524
|
}
|
|
364
525
|
}
|
|
@@ -385,5 +546,5 @@ function isInRemoteChange() {
|
|
|
385
546
|
return tracking.inRemoteChange;
|
|
386
547
|
}
|
|
387
548
|
|
|
388
|
-
export { configureObservablePersistence, getDateModifiedKey,
|
|
549
|
+
export { configureObservablePersistence, getDateModifiedKey, invertFieldMap, isInRemoteChange, mapPersistences, observablePersistConfiguration, onChangeRemote, persistObservable, persistState, transformObject, transformPath };
|
|
389
550
|
//# sourceMappingURL=persist.mjs.map
|