@legendapp/state 2.2.0-next.2 → 2.2.0-next.21
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/babel.js.map +1 -1
- package/config/enableDirectAccess.d.ts +1 -1
- package/config/enableDirectPeek.d.ts +1 -1
- package/config/enableReactDirectRender.js.map +1 -1
- package/config/enableReactDirectRender.mjs.map +1 -1
- package/config/enableReactTracking.d.ts +4 -3
- package/config/enableReactTracking.js.map +1 -1
- package/config/enableReactTracking.mjs.map +1 -1
- package/config/enableReactUse.d.ts +1 -1
- package/helpers/fetch.d.ts +4 -3
- package/helpers/fetch.js.map +1 -1
- package/helpers/fetch.mjs.map +1 -1
- package/helpers/pageHash.js.map +1 -1
- package/helpers/pageHash.mjs.map +1 -1
- package/helpers/pageHashParams.js.map +1 -1
- package/helpers/pageHashParams.mjs.map +1 -1
- package/helpers/time.d.ts +2 -2
- package/helpers/time.js.map +1 -1
- package/helpers/time.mjs.map +1 -1
- package/history.js.map +1 -1
- package/history.mjs.map +1 -1
- package/index.d.ts +14 -9
- package/index.js +522 -226
- package/index.js.map +1 -1
- package/index.mjs +521 -227
- package/index.mjs.map +1 -1
- package/package.json +2 -10
- package/persist-plugins/async-storage.js.map +1 -1
- package/persist-plugins/async-storage.mjs.map +1 -1
- package/persist-plugins/fetch.js.map +1 -1
- package/persist-plugins/fetch.mjs.map +1 -1
- package/persist-plugins/firebase.js +3 -3
- package/persist-plugins/firebase.js.map +1 -1
- package/persist-plugins/firebase.mjs +3 -3
- package/persist-plugins/firebase.mjs.map +1 -1
- package/persist-plugins/indexeddb.js.map +1 -1
- package/persist-plugins/indexeddb.mjs.map +1 -1
- package/persist-plugins/local-storage.js +1 -1
- package/persist-plugins/local-storage.js.map +1 -1
- package/persist-plugins/local-storage.mjs +1 -1
- package/persist-plugins/local-storage.mjs.map +1 -1
- package/persist-plugins/mmkv.js.map +1 -1
- package/persist-plugins/mmkv.mjs.map +1 -1
- package/persist-plugins/query.js.map +1 -1
- package/persist-plugins/query.mjs.map +1 -1
- package/persist.d.ts +16 -1
- package/persist.js +406 -138
- package/persist.js.map +1 -1
- package/persist.mjs +407 -139
- package/persist.mjs.map +1 -1
- package/react-hooks/createObservableHook.js +1 -1
- package/react-hooks/createObservableHook.js.map +1 -1
- package/react-hooks/createObservableHook.mjs +1 -1
- package/react-hooks/createObservableHook.mjs.map +1 -1
- package/react-hooks/useFetch.d.ts +4 -3
- package/react-hooks/useFetch.js.map +1 -1
- package/react-hooks/useFetch.mjs.map +1 -1
- package/react-hooks/useHover.js.map +1 -1
- package/react-hooks/useHover.mjs.map +1 -1
- package/react-hooks/useMeasure.js.map +1 -1
- package/react-hooks/useMeasure.mjs.map +1 -1
- package/react-hooks/useObservableNextRouter.js.map +1 -1
- package/react-hooks/useObservableNextRouter.mjs.map +1 -1
- package/react-hooks/useObservableQuery.js.map +1 -1
- package/react-hooks/useObservableQuery.mjs.map +1 -1
- package/react-hooks/usePersistedObservable.d.ts +2 -2
- package/react-hooks/usePersistedObservable.js +3 -3
- package/react-hooks/usePersistedObservable.js.map +1 -1
- package/react-hooks/usePersistedObservable.mjs +3 -3
- package/react-hooks/usePersistedObservable.mjs.map +1 -1
- package/react.js +13 -8
- package/react.js.map +1 -1
- package/react.mjs +14 -9
- package/react.mjs.map +1 -1
- package/src/ObservableObject.d.ts +8 -4
- package/src/ObservablePrimitive.d.ts +2 -1
- package/src/activated.d.ts +3 -0
- package/src/batching.d.ts +3 -1
- package/src/computed.d.ts +1 -1
- package/src/config/enableDirectAccess.d.ts +1 -1
- package/src/config/enableDirectPeek.d.ts +1 -1
- package/src/config/enableReactTracking.d.ts +4 -3
- package/src/config/enableReactUse.d.ts +1 -1
- package/src/createObservable.d.ts +2 -2
- package/src/globals.d.ts +10 -9
- package/src/helpers/fetch.d.ts +4 -3
- package/src/helpers/time.d.ts +2 -2
- package/src/helpers.d.ts +3 -2
- package/src/history/trackHistory.d.ts +1 -1
- package/src/observable.d.ts +6 -15
- package/src/observableInterfaces.d.ts +68 -315
- package/src/observableTypes.d.ts +93 -0
- package/src/persist/observablePersistRemoteFunctionsAdapter.d.ts +1 -1
- package/src/persist/persistActivateNode.d.ts +1 -0
- package/src/persist/persistHelpers.d.ts +1 -1
- package/src/persist/persistObservable.d.ts +3 -3
- package/src/persistTypes.d.ts +229 -0
- package/src/proxy.d.ts +2 -1
- package/src/react/Computed.d.ts +1 -1
- package/src/react/Switch.d.ts +3 -3
- package/src/react/reactInterfaces.d.ts +2 -1
- package/src/react/usePauseProvider.d.ts +3 -3
- package/src/react/useWhen.d.ts +2 -2
- package/src/react-hooks/useFetch.d.ts +4 -3
- package/src/react-hooks/usePersistedObservable.d.ts +2 -2
- package/src/retry.d.ts +4 -0
- package/src/trackSelector.d.ts +3 -2
- package/src/when.d.ts +6 -2
- package/trace.js.map +1 -1
- package/trace.mjs.map +1 -1
package/persist.js
CHANGED
|
@@ -139,9 +139,21 @@ function observablePersistRemoteFunctionsAdapter({ get, set, }) {
|
|
|
139
139
|
const ret = {};
|
|
140
140
|
if (get) {
|
|
141
141
|
ret.get = (async (params) => {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
try {
|
|
143
|
+
let value = get(params);
|
|
144
|
+
if (state.isPromise(value)) {
|
|
145
|
+
value = await value;
|
|
146
|
+
}
|
|
147
|
+
params.onChange({
|
|
148
|
+
value,
|
|
149
|
+
dateModified: params.dateModified,
|
|
150
|
+
lastSync: params.lastSync,
|
|
151
|
+
mode: params.mode,
|
|
152
|
+
});
|
|
153
|
+
params.onGet();
|
|
154
|
+
// eslint-disable-next-line no-empty
|
|
155
|
+
}
|
|
156
|
+
catch (_a) { }
|
|
145
157
|
});
|
|
146
158
|
}
|
|
147
159
|
if (set) {
|
|
@@ -150,7 +162,22 @@ function observablePersistRemoteFunctionsAdapter({ get, set, }) {
|
|
|
150
162
|
return ret;
|
|
151
163
|
}
|
|
152
164
|
|
|
153
|
-
|
|
165
|
+
function removeNullUndefined(val) {
|
|
166
|
+
if (val) {
|
|
167
|
+
Object.keys(val).forEach((key) => {
|
|
168
|
+
const v = val[key];
|
|
169
|
+
if (v === null || v === undefined) {
|
|
170
|
+
delete val[key];
|
|
171
|
+
}
|
|
172
|
+
else if (state.isObject(v)) {
|
|
173
|
+
removeNullUndefined(v);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return val;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const { globalState: globalState$1 } = state.internal;
|
|
154
181
|
const mapPersistences = new WeakMap();
|
|
155
182
|
const metadatas = new WeakMap();
|
|
156
183
|
const promisesLocalSaves = new Set();
|
|
@@ -165,11 +192,12 @@ function doInOrder(arg1, arg2) {
|
|
|
165
192
|
return state.isPromise(arg1) ? arg1.then(arg2) : arg2(arg1);
|
|
166
193
|
}
|
|
167
194
|
function onChangeRemote(cb) {
|
|
168
|
-
state.when(() => !globalState.isLoadingRemote$.get(), () => {
|
|
195
|
+
state.when(() => !globalState$1.isLoadingRemote$.get(), () => {
|
|
196
|
+
state.endBatch(true);
|
|
169
197
|
// Remote changes should only update local state
|
|
170
|
-
globalState.isLoadingRemote$.set(true);
|
|
198
|
+
globalState$1.isLoadingRemote$.set(true);
|
|
171
199
|
state.batch(cb, () => {
|
|
172
|
-
globalState.isLoadingRemote$.set(false);
|
|
200
|
+
globalState$1.isLoadingRemote$.set(false);
|
|
173
201
|
});
|
|
174
202
|
});
|
|
175
203
|
}
|
|
@@ -216,35 +244,78 @@ async function updateMetadataImmediate(obs, localState, syncState, persistOption
|
|
|
216
244
|
const { table, config } = parseLocalConfig(local);
|
|
217
245
|
// Save metadata
|
|
218
246
|
const oldMetadata = metadatas.get(obs);
|
|
219
|
-
const {
|
|
220
|
-
const needsUpdate = pending || (
|
|
247
|
+
const { lastSync, pending } = newMetadata;
|
|
248
|
+
const needsUpdate = pending || (lastSync && (!oldMetadata || lastSync !== oldMetadata.lastSync));
|
|
221
249
|
if (needsUpdate) {
|
|
222
250
|
const metadata = Object.assign({}, oldMetadata, newMetadata);
|
|
223
251
|
metadatas.set(obs, metadata);
|
|
224
252
|
if (persistenceLocal) {
|
|
225
253
|
await persistenceLocal.setMetadata(table, metadata, config);
|
|
226
254
|
}
|
|
227
|
-
if (
|
|
228
|
-
syncState.
|
|
255
|
+
if (lastSync) {
|
|
256
|
+
syncState.assign({
|
|
257
|
+
lastSync: lastSync,
|
|
258
|
+
dateModified: lastSync,
|
|
259
|
+
});
|
|
229
260
|
}
|
|
230
261
|
}
|
|
231
262
|
}
|
|
232
263
|
function updateMetadata(obs, localState, syncState, persistOptions, newMetadata) {
|
|
264
|
+
var _a;
|
|
233
265
|
if (localState.timeoutSaveMetadata) {
|
|
234
266
|
clearTimeout(localState.timeoutSaveMetadata);
|
|
235
267
|
}
|
|
236
|
-
localState.timeoutSaveMetadata = setTimeout(() => updateMetadataImmediate(obs, localState, syncState, persistOptions, newMetadata),
|
|
268
|
+
localState.timeoutSaveMetadata = setTimeout(() => updateMetadataImmediate(obs, localState, syncState, persistOptions, newMetadata), ((_a = persistOptions === null || persistOptions === void 0 ? void 0 : persistOptions.remote) === null || _a === void 0 ? void 0 : _a.metadataTimeout) || 0);
|
|
237
269
|
}
|
|
238
270
|
let _queuedChanges = [];
|
|
271
|
+
let _queuedRemoteChanges = [];
|
|
272
|
+
let timeoutSaveRemote = undefined;
|
|
273
|
+
function mergeChanges(changes) {
|
|
274
|
+
const changesByPath = new Map();
|
|
275
|
+
const changesOut = [];
|
|
276
|
+
// TODO: This could be even more robust by going deeper into paths like the firebase plugin's _updatePendingSave
|
|
277
|
+
for (let i = 0; i < changes.length; i++) {
|
|
278
|
+
const change = changes[i];
|
|
279
|
+
const pathStr = change.path.join('/');
|
|
280
|
+
const existing = changesByPath.get(pathStr);
|
|
281
|
+
if (existing) {
|
|
282
|
+
existing.valueAtPath = change.valueAtPath;
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
285
|
+
changesByPath.set(pathStr, change);
|
|
286
|
+
changesOut.push(change);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return changesOut;
|
|
290
|
+
}
|
|
291
|
+
function mergeQueuedChanges(allChanges) {
|
|
292
|
+
const changesByObs = new Map();
|
|
293
|
+
const out = [];
|
|
294
|
+
for (let i = 0; i < allChanges.length; i++) {
|
|
295
|
+
const value = allChanges[i];
|
|
296
|
+
const { obs, changes } = value;
|
|
297
|
+
const existing = changesByObs.get(obs);
|
|
298
|
+
const newChanges = existing ? [...existing, ...changes] : changes;
|
|
299
|
+
const merged = mergeChanges(newChanges);
|
|
300
|
+
changesByObs.set(obs, merged);
|
|
301
|
+
if (!existing) {
|
|
302
|
+
value.changes = merged;
|
|
303
|
+
out.push(value);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
239
308
|
async function processQueuedChanges() {
|
|
309
|
+
var _a;
|
|
240
310
|
// Get a local copy of the queued changes and clear the global queue
|
|
241
|
-
const queuedChanges = _queuedChanges;
|
|
311
|
+
const queuedChanges = mergeQueuedChanges(_queuedChanges);
|
|
242
312
|
_queuedChanges = [];
|
|
313
|
+
_queuedRemoteChanges.push(...queuedChanges.filter((c) => !c.inRemoteChange));
|
|
243
314
|
// Note: Summary of the order of operations these functions:
|
|
244
315
|
// 1. Prepare all changes for saving. This may involve waiting for promises if the user has asynchronous transform.
|
|
245
316
|
// We need to prepare all of the changes in the queue before saving so that the saves happen in the correct order,
|
|
246
317
|
// since some may take longer to transformSaveData than others.
|
|
247
|
-
const
|
|
318
|
+
const preppedChangesLocal = await Promise.all(queuedChanges.map(prepChangeLocal));
|
|
248
319
|
// 2. Save pending to the metadata table first. If this is the only operation that succeeds, it would try to save
|
|
249
320
|
// the current value again on next load, which isn't too bad.
|
|
250
321
|
// 3. Save local changes to storage. If they never make it to remote, then on the next load they will be pending
|
|
@@ -252,25 +323,43 @@ async function processQueuedChanges() {
|
|
|
252
323
|
// 4. Wait for remote load or error if allowed
|
|
253
324
|
// 5. Save to remote
|
|
254
325
|
// 6. On successful save, merge changes (if any) back into observable
|
|
255
|
-
// 7. Lastly, update metadata to clear pending and update
|
|
326
|
+
// 7. Lastly, update metadata to clear pending and update lastSync. Doing this earlier could potentially cause
|
|
256
327
|
// sync inconsistences so it's very important that this is last.
|
|
257
|
-
|
|
328
|
+
await Promise.all(preppedChangesLocal.map(doChangeLocal));
|
|
329
|
+
const timeout = (_a = observablePersistConfiguration === null || observablePersistConfiguration === void 0 ? void 0 : observablePersistConfiguration.remoteOptions) === null || _a === void 0 ? void 0 : _a.saveTimeout;
|
|
330
|
+
if (timeout) {
|
|
331
|
+
if (timeoutSaveRemote) {
|
|
332
|
+
clearTimeout(timeoutSaveRemote);
|
|
333
|
+
}
|
|
334
|
+
timeoutSaveRemote = setTimeout(processQueuedRemoteChanges, timeout);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
processQueuedRemoteChanges();
|
|
338
|
+
}
|
|
258
339
|
}
|
|
259
|
-
async function
|
|
340
|
+
async function processQueuedRemoteChanges() {
|
|
341
|
+
const queuedRemoteChanges = mergeQueuedChanges(_queuedRemoteChanges);
|
|
342
|
+
_queuedRemoteChanges = [];
|
|
343
|
+
const preppedChangesRemote = await Promise.all(queuedRemoteChanges.map(prepChangeRemote));
|
|
344
|
+
preppedChangesRemote.forEach(doChangeRemote);
|
|
345
|
+
}
|
|
346
|
+
async function prepChangeLocal(queuedChange) {
|
|
260
347
|
const { syncState, changes, localState, persistOptions, inRemoteChange, isApplyingPending } = queuedChange;
|
|
261
348
|
const local = persistOptions.local;
|
|
262
349
|
const { persistenceRemote } = localState;
|
|
263
350
|
const { config: configLocal } = parseLocalConfig(local);
|
|
264
351
|
const configRemote = persistOptions.remote;
|
|
265
352
|
const saveLocal = local && !configLocal.readonly && !isApplyingPending && syncState.isEnabledLocal.peek();
|
|
266
|
-
const saveRemote = !inRemoteChange &&
|
|
353
|
+
const saveRemote = !!(!inRemoteChange &&
|
|
354
|
+
(persistenceRemote === null || persistenceRemote === void 0 ? void 0 : persistenceRemote.set) &&
|
|
355
|
+
!(configRemote === null || configRemote === void 0 ? void 0 : configRemote.readonly) &&
|
|
356
|
+
syncState.isEnabledRemote.peek());
|
|
267
357
|
if (saveLocal || saveRemote) {
|
|
268
358
|
if (saveLocal && !syncState.isLoadedLocal.peek()) {
|
|
269
359
|
console.error('[legend-state] WARNING: An observable was changed before being loaded from persistence', local);
|
|
270
|
-
return;
|
|
360
|
+
return undefined;
|
|
271
361
|
}
|
|
272
362
|
const changesLocal = [];
|
|
273
|
-
const changesRemote = [];
|
|
274
363
|
const changesPaths = new Set();
|
|
275
364
|
let promisesTransform = [];
|
|
276
365
|
// Reverse order
|
|
@@ -309,6 +398,52 @@ async function prepChange(queuedChange) {
|
|
|
309
398
|
}
|
|
310
399
|
}));
|
|
311
400
|
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
// If there's any transform promises, wait for them before saving
|
|
404
|
+
promisesTransform = promisesTransform.filter(Boolean);
|
|
405
|
+
if (promisesTransform.length > 0) {
|
|
406
|
+
await Promise.all(promisesTransform);
|
|
407
|
+
}
|
|
408
|
+
return { queuedChange, changesLocal, saveRemote };
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
async function prepChangeRemote(queuedChange) {
|
|
412
|
+
const { syncState, changes, localState, persistOptions, inRemoteChange, isApplyingPending } = queuedChange;
|
|
413
|
+
const local = persistOptions.local;
|
|
414
|
+
const { persistenceRemote } = localState;
|
|
415
|
+
const { config: configLocal } = parseLocalConfig(local);
|
|
416
|
+
const configRemote = persistOptions.remote;
|
|
417
|
+
const saveLocal = local && !configLocal.readonly && !isApplyingPending && syncState.isEnabledLocal.peek();
|
|
418
|
+
const saveRemote = !inRemoteChange && (persistenceRemote === null || persistenceRemote === void 0 ? void 0 : persistenceRemote.set) && !(configRemote === null || configRemote === void 0 ? void 0 : configRemote.readonly) && syncState.isEnabledRemote.peek();
|
|
419
|
+
if (saveLocal || saveRemote) {
|
|
420
|
+
if (saveLocal && !syncState.isLoadedLocal.peek()) {
|
|
421
|
+
console.error('[legend-state] WARNING: An observable was changed before being loaded from persistence', local);
|
|
422
|
+
return undefined;
|
|
423
|
+
}
|
|
424
|
+
const changesRemote = [];
|
|
425
|
+
const changesPaths = new Set();
|
|
426
|
+
let promisesTransform = [];
|
|
427
|
+
// Reverse order
|
|
428
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
429
|
+
const { path } = changes[i];
|
|
430
|
+
let found = false;
|
|
431
|
+
// Optimization to only save the latest update at each path. We might have multiple changes at the same path
|
|
432
|
+
// and we only need the latest value, so it starts from the end of the array, skipping any earlier changes
|
|
433
|
+
// already processed. If a later change modifies a parent of an earlier change (which happens on delete()
|
|
434
|
+
// it should be ignored as it's superseded by the parent modification.
|
|
435
|
+
if (changesPaths.size > 0) {
|
|
436
|
+
for (let u = 0; u < path.length; u++) {
|
|
437
|
+
if (changesPaths.has((u === path.length - 1 ? path : path.slice(0, u + 1)).join('/'))) {
|
|
438
|
+
found = true;
|
|
439
|
+
break;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
if (!found) {
|
|
444
|
+
const pathStr = path.join('/');
|
|
445
|
+
changesPaths.add(pathStr);
|
|
446
|
+
const { prevAtPath, valueAtPath, pathTypes } = changes[i];
|
|
312
447
|
if (saveRemote) {
|
|
313
448
|
const promiseTransformRemote = transformOutData(valueAtPath, path, pathTypes, configRemote || {});
|
|
314
449
|
promisesTransform.push(doInOrder(promiseTransformRemote, ({ path: pathTransformed, value: valueTransformed }) => {
|
|
@@ -366,21 +501,20 @@ async function prepChange(queuedChange) {
|
|
|
366
501
|
if (promisesTransform.length > 0) {
|
|
367
502
|
await Promise.all(promisesTransform);
|
|
368
503
|
}
|
|
369
|
-
return { queuedChange,
|
|
504
|
+
return { queuedChange, changesRemote };
|
|
370
505
|
}
|
|
371
506
|
}
|
|
372
|
-
async function
|
|
373
|
-
var _a, _b, _c, _d;
|
|
507
|
+
async function doChangeLocal(changeInfo) {
|
|
374
508
|
if (!changeInfo)
|
|
375
509
|
return;
|
|
376
|
-
const { queuedChange, changesLocal,
|
|
510
|
+
const { queuedChange, changesLocal, saveRemote } = changeInfo;
|
|
377
511
|
const { obs, syncState, localState, persistOptions } = queuedChange;
|
|
378
|
-
const { persistenceLocal
|
|
512
|
+
const { persistenceLocal } = localState;
|
|
379
513
|
const local = persistOptions.local;
|
|
380
514
|
const { table, config: configLocal } = parseLocalConfig(local);
|
|
381
515
|
const configRemote = persistOptions.remote;
|
|
382
516
|
const shouldSaveMetadata = local && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.offlineBehavior) === 'retry';
|
|
383
|
-
if (
|
|
517
|
+
if (saveRemote && shouldSaveMetadata) {
|
|
384
518
|
// First save pending changes before saving local or remote
|
|
385
519
|
await updateMetadataImmediate(obs, localState, syncState, persistOptions, {
|
|
386
520
|
pending: localState.pendingChanges,
|
|
@@ -400,11 +534,24 @@ async function doChange(changeInfo) {
|
|
|
400
534
|
await promiseSet;
|
|
401
535
|
}
|
|
402
536
|
}
|
|
537
|
+
}
|
|
538
|
+
async function doChangeRemote(changeInfo) {
|
|
539
|
+
var _a, _b, _c, _d;
|
|
540
|
+
if (!changeInfo)
|
|
541
|
+
return;
|
|
542
|
+
const { queuedChange, changesRemote } = changeInfo;
|
|
543
|
+
const { obs, syncState, localState, persistOptions } = queuedChange;
|
|
544
|
+
const { persistenceLocal, persistenceRemote } = localState;
|
|
545
|
+
const local = persistOptions.local;
|
|
546
|
+
const { table, config: configLocal } = parseLocalConfig(local);
|
|
547
|
+
const configRemote = persistOptions.remote;
|
|
548
|
+
const shouldSaveMetadata = local && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.offlineBehavior) === 'retry';
|
|
403
549
|
if (changesRemote.length > 0) {
|
|
404
550
|
// Wait for remote to be ready before saving
|
|
405
551
|
await state.when(() => syncState.isLoaded.get() || ((configRemote === null || configRemote === void 0 ? void 0 : configRemote.allowSetIfError) && syncState.error.get()));
|
|
406
552
|
const value = obs.peek();
|
|
407
553
|
(_a = configRemote === null || configRemote === void 0 ? void 0 : configRemote.onBeforeSet) === null || _a === void 0 ? void 0 : _a.call(configRemote);
|
|
554
|
+
localState.numSavesOutstanding = (localState.numSavesOutstanding || 0) + 1;
|
|
408
555
|
const saved = await ((_b = persistenceRemote.set({
|
|
409
556
|
obs,
|
|
410
557
|
syncState: syncState,
|
|
@@ -412,17 +559,18 @@ async function doChange(changeInfo) {
|
|
|
412
559
|
changes: changesRemote,
|
|
413
560
|
value,
|
|
414
561
|
})) === null || _b === void 0 ? void 0 : _b.catch((err) => { var _a; return (_a = configRemote === null || configRemote === void 0 ? void 0 : configRemote.onSetError) === null || _a === void 0 ? void 0 : _a.call(configRemote, err); }));
|
|
562
|
+
localState.numSavesOutstanding--;
|
|
415
563
|
// If this remote save changed anything then update persistence and metadata
|
|
416
564
|
// Because save happens after a timeout and they're batched together, some calls to save will
|
|
417
565
|
// return saved data and others won't, so those can be ignored.
|
|
418
566
|
if (saved) {
|
|
419
567
|
const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
|
|
420
|
-
const { changes,
|
|
568
|
+
const { changes, lastSync } = saved;
|
|
421
569
|
if (pathStrs.length > 0) {
|
|
422
570
|
if (local) {
|
|
423
571
|
const metadata = {};
|
|
424
572
|
const pending = (_c = persistenceLocal.getMetadata(table, configLocal)) === null || _c === void 0 ? void 0 : _c.pending;
|
|
425
|
-
let transformedChanges =
|
|
573
|
+
let transformedChanges = undefined;
|
|
426
574
|
for (let i = 0; i < pathStrs.length; i++) {
|
|
427
575
|
const pathStr = pathStrs[i];
|
|
428
576
|
// Clear pending for this path
|
|
@@ -432,22 +580,34 @@ async function doChange(changeInfo) {
|
|
|
432
580
|
metadata.pending = pending;
|
|
433
581
|
}
|
|
434
582
|
}
|
|
435
|
-
if (
|
|
436
|
-
metadata.
|
|
583
|
+
if (lastSync) {
|
|
584
|
+
metadata.lastSync = lastSync;
|
|
437
585
|
}
|
|
438
586
|
// Remote can optionally have data that needs to be merged back into the observable,
|
|
439
587
|
// for example Firebase may update dateModified with the server timestamp
|
|
440
588
|
if (changes && !state.isEmpty(changes)) {
|
|
441
|
-
transformedChanges
|
|
589
|
+
transformedChanges = transformLoadData(changes, persistOptions.remote, false);
|
|
442
590
|
}
|
|
443
|
-
if (
|
|
444
|
-
if (transformedChanges
|
|
445
|
-
|
|
591
|
+
if (localState.numSavesOutstanding > 0) {
|
|
592
|
+
if (transformedChanges) {
|
|
593
|
+
if (!localState.pendingSaveResults) {
|
|
594
|
+
localState.pendingSaveResults = [];
|
|
595
|
+
}
|
|
596
|
+
localState.pendingSaveResults.push(transformedChanges);
|
|
446
597
|
}
|
|
447
|
-
onChangeRemote(() => state.mergeIntoObservable(obs, ...transformedChanges));
|
|
448
598
|
}
|
|
449
|
-
|
|
450
|
-
|
|
599
|
+
else {
|
|
600
|
+
let allChanges = [...(localState.pendingSaveResults || []), transformedChanges];
|
|
601
|
+
if (allChanges.length > 0) {
|
|
602
|
+
if (allChanges.some((change) => state.isPromise(change))) {
|
|
603
|
+
allChanges = await Promise.all(allChanges);
|
|
604
|
+
}
|
|
605
|
+
onChangeRemote(() => state.mergeIntoObservable(obs, ...allChanges));
|
|
606
|
+
}
|
|
607
|
+
if (shouldSaveMetadata && !state.isEmpty(metadata)) {
|
|
608
|
+
updateMetadata(obs, localState, syncState, persistOptions, metadata);
|
|
609
|
+
}
|
|
610
|
+
localState.pendingSaveResults = [];
|
|
451
611
|
}
|
|
452
612
|
}
|
|
453
613
|
(_d = configRemote === null || configRemote === void 0 ? void 0 : configRemote.onSet) === null || _d === void 0 ? void 0 : _d.call(configRemote);
|
|
@@ -498,7 +658,7 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
498
658
|
}
|
|
499
659
|
const { persist: persistenceLocal, initialized } = mapPersistences.get(localPersistence);
|
|
500
660
|
localState.persistenceLocal = persistenceLocal;
|
|
501
|
-
if (!initialized.
|
|
661
|
+
if (!initialized.peek()) {
|
|
502
662
|
await state.when(initialized);
|
|
503
663
|
}
|
|
504
664
|
// If persistence has an asynchronous load, wait for it
|
|
@@ -512,12 +672,21 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
512
672
|
let value = persistenceLocal.getTable(table, config);
|
|
513
673
|
const metadata = persistenceLocal.getMetadata(table, config);
|
|
514
674
|
if (metadata) {
|
|
675
|
+
// @ts-expect-error Migration from old version
|
|
676
|
+
if (!metadata.lastSync && metadata.modified) {
|
|
677
|
+
// @ts-expect-error Migration from old
|
|
678
|
+
metadata.lastSync = metadata.modified;
|
|
679
|
+
}
|
|
515
680
|
metadatas.set(obs, metadata);
|
|
516
681
|
localState.pendingChanges = metadata.pending;
|
|
517
|
-
|
|
682
|
+
// TODOV3 Remove dateModified
|
|
683
|
+
syncState.assign({
|
|
684
|
+
dateModified: metadata.lastSync,
|
|
685
|
+
lastSync: metadata.lastSync,
|
|
686
|
+
});
|
|
518
687
|
}
|
|
519
688
|
// Merge the data from local persistence into the default state
|
|
520
|
-
if (value !==
|
|
689
|
+
if (value !== undefined) {
|
|
521
690
|
const { transform, fieldTransforms } = config;
|
|
522
691
|
value = transformLoadData(value, { transform, fieldTransforms }, true);
|
|
523
692
|
if (state.isPromise(value)) {
|
|
@@ -528,7 +697,12 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
528
697
|
// are set on the same observable
|
|
529
698
|
state.internal.globalState.isLoadingLocal = true;
|
|
530
699
|
// We want to merge the local data on top of any initial state the object is created with
|
|
531
|
-
|
|
700
|
+
if (value === null && !obs.peek()) {
|
|
701
|
+
obs.set(value);
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
state.mergeIntoObservable(obs, value);
|
|
705
|
+
}
|
|
532
706
|
}, () => {
|
|
533
707
|
state.internal.globalState.isLoadingLocal = false;
|
|
534
708
|
});
|
|
@@ -541,18 +715,11 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
541
715
|
}
|
|
542
716
|
syncState.isLoadedLocal.set(true);
|
|
543
717
|
}
|
|
544
|
-
function persistObservable(
|
|
545
|
-
var _a;
|
|
546
|
-
const obs = (state.isObservable(initialOrObservable)
|
|
547
|
-
? initialOrObservable
|
|
548
|
-
: state.observable(state.isFunction(initialOrObservable) ? initialOrObservable() : initialOrObservable));
|
|
718
|
+
function persistObservable(obs, persistOptions) {
|
|
549
719
|
const node = state.getNode(obs);
|
|
550
|
-
if (process.env.NODE_ENV === 'development' && ((_a = obs === null || obs === void 0 ? void 0 : obs.peek()) === null || _a === void 0 ? void 0 : _a._state)) {
|
|
551
|
-
console.warn('[legend-state] WARNING: persistObservable creates a property named "_state" but your observable already has "state" in it');
|
|
552
|
-
}
|
|
553
720
|
// Merge remote persist options with clobal options
|
|
554
721
|
if (persistOptions.remote) {
|
|
555
|
-
persistOptions.remote = Object.assign({}, observablePersistConfiguration.remoteOptions, persistOptions.remote);
|
|
722
|
+
persistOptions.remote = Object.assign({}, observablePersistConfiguration.remoteOptions, removeNullUndefined(persistOptions.remote));
|
|
556
723
|
}
|
|
557
724
|
let { remote } = persistOptions;
|
|
558
725
|
const { local } = persistOptions;
|
|
@@ -564,6 +731,7 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
564
731
|
isLoaded: false,
|
|
565
732
|
isEnabledLocal: true,
|
|
566
733
|
isEnabledRemote: true,
|
|
734
|
+
refreshNum: 0,
|
|
567
735
|
clearLocal: undefined,
|
|
568
736
|
sync: () => Promise.resolve(),
|
|
569
737
|
getPendingChanges: () => localState.pendingChanges,
|
|
@@ -594,73 +762,93 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
594
762
|
var _a, _b;
|
|
595
763
|
if (!isSynced) {
|
|
596
764
|
isSynced = true;
|
|
597
|
-
const
|
|
765
|
+
const lastSync = (_a = metadatas.get(obs)) === null || _a === void 0 ? void 0 : _a.lastSync;
|
|
766
|
+
const pending = localState.pendingChanges;
|
|
598
767
|
const get = (_b = localState.persistenceRemote.get) === null || _b === void 0 ? void 0 : _b.bind(localState.persistenceRemote);
|
|
599
768
|
if (get) {
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
769
|
+
const runGet = () => {
|
|
770
|
+
get({
|
|
771
|
+
state: syncState,
|
|
772
|
+
obs,
|
|
773
|
+
options: persistOptions,
|
|
774
|
+
lastSync,
|
|
775
|
+
dateModified: lastSync,
|
|
776
|
+
onError: (error) => {
|
|
777
|
+
var _a;
|
|
778
|
+
(_a = remote.onGetError) === null || _a === void 0 ? void 0 : _a.call(remote, error);
|
|
779
|
+
},
|
|
780
|
+
onGet: () => {
|
|
781
|
+
node.state.assign({
|
|
782
|
+
isLoaded: true,
|
|
783
|
+
error: undefined,
|
|
784
|
+
});
|
|
785
|
+
},
|
|
786
|
+
onChange: async ({ value, path = [], pathTypes = [], mode = 'set', lastSync }) => {
|
|
787
|
+
// Note: value is the constructed value, path is used for setInObservableAtPath
|
|
788
|
+
// to start the set into the observable from the path
|
|
789
|
+
if (value !== undefined) {
|
|
790
|
+
value = transformLoadData(value, remote, true);
|
|
791
|
+
if (state.isPromise(value)) {
|
|
792
|
+
value = await value;
|
|
793
|
+
}
|
|
794
|
+
const invertedMap = remote.fieldTransforms && invertFieldMap(remote.fieldTransforms);
|
|
795
|
+
if (path.length && invertedMap) {
|
|
796
|
+
path = transformPath(path, pathTypes, invertedMap);
|
|
797
|
+
}
|
|
798
|
+
if (mode === 'lastSync' || mode === 'dateModified') {
|
|
799
|
+
if (lastSync && !state.isEmpty(value)) {
|
|
800
|
+
onChangeRemote(() => {
|
|
801
|
+
state.setInObservableAtPath(
|
|
802
|
+
// @ts-expect-error Fix this type
|
|
803
|
+
obs, path, pathTypes, value, 'assign');
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
else {
|
|
808
|
+
const pending = localState.pendingChanges;
|
|
809
|
+
if (pending) {
|
|
810
|
+
Object.keys(pending).forEach((key) => {
|
|
811
|
+
const p = key.split('/').filter((p) => p !== '');
|
|
812
|
+
const { v, t } = pending[key];
|
|
813
|
+
if (t.length === 0) {
|
|
814
|
+
value = v;
|
|
815
|
+
}
|
|
816
|
+
else if (value[p[0]] !== undefined) {
|
|
817
|
+
value = state.setAtPath(value, p, t, v, obs.peek(), (path, value) => {
|
|
818
|
+
delete pending[key];
|
|
819
|
+
pending[path.join('/')] = {
|
|
820
|
+
p: null,
|
|
821
|
+
v: value,
|
|
822
|
+
t: t.slice(0, path.length),
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
}
|
|
622
828
|
onChangeRemote(() => {
|
|
623
|
-
|
|
829
|
+
// @ts-expect-error Fix this type
|
|
830
|
+
state.setInObservableAtPath(obs, path, pathTypes, value, mode);
|
|
624
831
|
});
|
|
625
832
|
}
|
|
626
833
|
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
Object.keys(pending).forEach((key) => {
|
|
631
|
-
const p = key.split('/').filter((p) => p !== '');
|
|
632
|
-
const { v, t } = pending[key];
|
|
633
|
-
if (value[p[0]] !== undefined) {
|
|
634
|
-
value = state.setAtPath(value, p, t, v, obs.peek(), (path, value) => {
|
|
635
|
-
delete pending[key];
|
|
636
|
-
pending[path.join('/')] = {
|
|
637
|
-
p: null,
|
|
638
|
-
v: value,
|
|
639
|
-
t: t.slice(0, path.length),
|
|
640
|
-
};
|
|
641
|
-
});
|
|
642
|
-
}
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
onChangeRemote(() => {
|
|
646
|
-
state.setInObservableAtPath(obs, path, pathTypes, value, mode);
|
|
834
|
+
if (lastSync && local) {
|
|
835
|
+
updateMetadata(obs, localState, syncState, persistOptions, {
|
|
836
|
+
lastSync,
|
|
647
837
|
});
|
|
648
838
|
}
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
});
|
|
654
|
-
}
|
|
655
|
-
},
|
|
656
|
-
});
|
|
839
|
+
},
|
|
840
|
+
});
|
|
841
|
+
};
|
|
842
|
+
runGet();
|
|
657
843
|
}
|
|
658
844
|
else {
|
|
659
|
-
|
|
845
|
+
node.state.assign({
|
|
846
|
+
isLoaded: true,
|
|
847
|
+
error: undefined,
|
|
848
|
+
});
|
|
660
849
|
}
|
|
661
850
|
// Wait for remote to be ready before saving pending
|
|
662
851
|
await state.when(() => syncState.isLoaded.get() || (remote.allowSetIfError && syncState.error.get()));
|
|
663
|
-
const pending = localState.pendingChanges;
|
|
664
852
|
if (pending && !state.isEmpty(pending)) {
|
|
665
853
|
localState.isApplyingPending = true;
|
|
666
854
|
const keys = Object.keys(pending);
|
|
@@ -673,6 +861,7 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
673
861
|
changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
|
|
674
862
|
}
|
|
675
863
|
// Send the changes into onObsChange so that they get persisted remotely
|
|
864
|
+
// @ts-expect-error Fix this type
|
|
676
865
|
onObsChange(obs, syncState, localState, persistOptions, {
|
|
677
866
|
value: obs.peek(),
|
|
678
867
|
// TODO getPrevious if any remote persistence layers need it
|
|
@@ -708,44 +897,122 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
708
897
|
}
|
|
709
898
|
obs.onChange(onObsChange.bind(this, obs, syncState, localState, persistOptions));
|
|
710
899
|
});
|
|
711
|
-
return
|
|
900
|
+
return syncState;
|
|
712
901
|
}
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
902
|
+
|
|
903
|
+
const { getProxy, globalState, runWithRetry, symbolActivated } = state.internal;
|
|
904
|
+
function persistActivateNode() {
|
|
905
|
+
globalState.activateNode = function activateNodePersist(node, refresh, wasPromise, newValue) {
|
|
906
|
+
if (node.activationState) {
|
|
907
|
+
const { get, initial, onSet, subscribe, cache, retry, offlineBehavior } = node.activationState;
|
|
908
|
+
let onChange = undefined;
|
|
909
|
+
const pluginRemote = {};
|
|
910
|
+
if (get) {
|
|
911
|
+
pluginRemote.get = (params) => {
|
|
912
|
+
onChange = params.onChange;
|
|
913
|
+
const updateLastSync = (lastSync) => (params.lastSync = lastSync);
|
|
914
|
+
const setMode = (mode) => (params.mode = mode);
|
|
915
|
+
const nodeValue = state.getNodeValue(node);
|
|
916
|
+
const value = runWithRetry(node, { attemptNum: 0 }, () => {
|
|
917
|
+
return get({
|
|
918
|
+
value: state.isFunction(nodeValue) || (nodeValue === null || nodeValue === void 0 ? void 0 : nodeValue[symbolActivated]) ? undefined : nodeValue,
|
|
919
|
+
lastSync: params.lastSync,
|
|
920
|
+
updateLastSync,
|
|
921
|
+
setMode,
|
|
922
|
+
});
|
|
923
|
+
});
|
|
924
|
+
return value;
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
if (onSet) {
|
|
928
|
+
// TODO: Work out these types better
|
|
929
|
+
pluginRemote.set = async (params) => {
|
|
930
|
+
var _a;
|
|
931
|
+
if ((_a = node.state) === null || _a === void 0 ? void 0 : _a.isLoaded.get()) {
|
|
932
|
+
return runWithRetry(node, { attemptNum: 0 }, async () => {
|
|
933
|
+
let changes = {};
|
|
934
|
+
let maxModified = 0;
|
|
935
|
+
let didError = false;
|
|
936
|
+
if (!node.state.isLoaded.peek()) {
|
|
937
|
+
await state.whenReady(node.state.isLoaded);
|
|
938
|
+
}
|
|
939
|
+
await onSet({
|
|
940
|
+
...params,
|
|
941
|
+
node,
|
|
942
|
+
update: (params) => {
|
|
943
|
+
const { value, lastSync } = params;
|
|
944
|
+
maxModified = Math.max(lastSync || 0, maxModified);
|
|
945
|
+
changes = state.mergeIntoObservable(changes, value);
|
|
946
|
+
},
|
|
947
|
+
onError: () => {
|
|
948
|
+
// TODO Is this necessary?
|
|
949
|
+
didError = true;
|
|
950
|
+
throw new Error();
|
|
951
|
+
},
|
|
952
|
+
refresh,
|
|
953
|
+
fromSubscribe: false,
|
|
954
|
+
});
|
|
955
|
+
if (!didError) {
|
|
956
|
+
return { changes, lastSync: maxModified || undefined };
|
|
957
|
+
}
|
|
958
|
+
});
|
|
959
|
+
}
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
if (subscribe) {
|
|
963
|
+
subscribe({
|
|
964
|
+
node,
|
|
965
|
+
update: (params) => {
|
|
966
|
+
if (!onChange) {
|
|
967
|
+
// TODO: Make this message better
|
|
968
|
+
console.log('[legend-state] Cannot update immediately before the first return');
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
onChange(params);
|
|
972
|
+
}
|
|
973
|
+
},
|
|
974
|
+
refresh,
|
|
975
|
+
});
|
|
976
|
+
}
|
|
977
|
+
persistObservable(getProxy(node), {
|
|
978
|
+
pluginRemote,
|
|
979
|
+
...(cache || {}),
|
|
980
|
+
remote: {
|
|
981
|
+
retry: retry,
|
|
982
|
+
offlineBehavior,
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
const nodeVal = state.getNodeValue(node);
|
|
986
|
+
if (nodeVal !== undefined) {
|
|
987
|
+
newValue = nodeVal;
|
|
720
988
|
}
|
|
721
|
-
if (
|
|
722
|
-
|
|
989
|
+
else if (newValue === undefined) {
|
|
990
|
+
newValue = initial;
|
|
723
991
|
}
|
|
724
|
-
return newValue;
|
|
725
|
-
}
|
|
992
|
+
return { update: onChange, value: newValue };
|
|
993
|
+
}
|
|
994
|
+
else {
|
|
995
|
+
let onChange = undefined;
|
|
996
|
+
const pluginRemote = {
|
|
997
|
+
get: async (params) => {
|
|
998
|
+
onChange = params.onChange;
|
|
999
|
+
if (state.isPromise(newValue)) {
|
|
1000
|
+
try {
|
|
1001
|
+
newValue = await newValue;
|
|
1002
|
+
// eslint-disable-next-line no-empty
|
|
1003
|
+
}
|
|
1004
|
+
catch (_a) { }
|
|
1005
|
+
}
|
|
1006
|
+
return newValue;
|
|
1007
|
+
},
|
|
1008
|
+
};
|
|
1009
|
+
persistObservable(getProxy(node), {
|
|
1010
|
+
pluginRemote,
|
|
1011
|
+
});
|
|
1012
|
+
return { update: onChange, value: newValue };
|
|
1013
|
+
}
|
|
726
1014
|
};
|
|
727
|
-
|
|
728
|
-
pluginRemote.set = setter;
|
|
729
|
-
}
|
|
730
|
-
if (subscriber) {
|
|
731
|
-
subscriber({
|
|
732
|
-
update: (params) => {
|
|
733
|
-
if (!onChange) {
|
|
734
|
-
// TODO: Make this message better
|
|
735
|
-
console.log('[legend-state] Cannot update immediately before the first return');
|
|
736
|
-
}
|
|
737
|
-
else {
|
|
738
|
-
onChange(params);
|
|
739
|
-
}
|
|
740
|
-
},
|
|
741
|
-
});
|
|
742
|
-
}
|
|
743
|
-
persistObservable(getProxy(node), {
|
|
744
|
-
pluginRemote,
|
|
745
|
-
...(cacheOptions || {}),
|
|
746
|
-
});
|
|
747
|
-
return { update: onChange };
|
|
748
|
-
};
|
|
1015
|
+
}
|
|
749
1016
|
|
|
750
1017
|
function isInRemoteChange() {
|
|
751
1018
|
return state.internal.globalState.isLoadingRemote$.get();
|
|
@@ -753,6 +1020,7 @@ function isInRemoteChange() {
|
|
|
753
1020
|
const internal = {
|
|
754
1021
|
observablePersistConfiguration,
|
|
755
1022
|
};
|
|
1023
|
+
persistActivateNode();
|
|
756
1024
|
|
|
757
1025
|
exports.configureObservablePersistence = configureObservablePersistence;
|
|
758
1026
|
exports.internal = internal;
|