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