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