@legendapp/state 2.2.0-next.3 → 2.2.0-next.31
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 +363 -116
- package/persist.js.map +1 -1
- package/persist.mjs +364 -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,51 @@ 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 = new Map();
|
|
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
|
+
value.changes = merged;
|
|
300
|
+
out.set(obs, value);
|
|
301
|
+
}
|
|
302
|
+
return Array.from(out.values());
|
|
303
|
+
}
|
|
238
304
|
async function processQueuedChanges() {
|
|
305
|
+
var _a;
|
|
239
306
|
// Get a local copy of the queued changes and clear the global queue
|
|
240
|
-
const queuedChanges = _queuedChanges;
|
|
307
|
+
const queuedChanges = mergeQueuedChanges(_queuedChanges);
|
|
241
308
|
_queuedChanges = [];
|
|
309
|
+
_queuedRemoteChanges.push(...queuedChanges.filter((c) => !c.inRemoteChange));
|
|
242
310
|
// Note: Summary of the order of operations these functions:
|
|
243
311
|
// 1. Prepare all changes for saving. This may involve waiting for promises if the user has asynchronous transform.
|
|
244
312
|
// We need to prepare all of the changes in the queue before saving so that the saves happen in the correct order,
|
|
245
313
|
// since some may take longer to transformSaveData than others.
|
|
246
|
-
const changes = await Promise.all(queuedChanges.map(prepChange));
|
|
247
314
|
// 2. Save pending to the metadata table first. If this is the only operation that succeeds, it would try to save
|
|
248
315
|
// the current value again on next load, which isn't too bad.
|
|
249
316
|
// 3. Save local changes to storage. If they never make it to remote, then on the next load they will be pending
|
|
@@ -251,25 +318,46 @@ async function processQueuedChanges() {
|
|
|
251
318
|
// 4. Wait for remote load or error if allowed
|
|
252
319
|
// 5. Save to remote
|
|
253
320
|
// 6. On successful save, merge changes (if any) back into observable
|
|
254
|
-
// 7. Lastly, update metadata to clear pending and update
|
|
321
|
+
// 7. Lastly, update metadata to clear pending and update lastSync. Doing this earlier could potentially cause
|
|
255
322
|
// sync inconsistences so it's very important that this is last.
|
|
256
|
-
|
|
323
|
+
const preppedChangesLocal = await Promise.all(queuedChanges.map(prepChangeLocal));
|
|
324
|
+
// 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?
|
|
325
|
+
await Promise.all(queuedChanges.map(prepChangeRemote));
|
|
326
|
+
await Promise.all(preppedChangesLocal.map(doChangeLocal));
|
|
327
|
+
const timeout = (_a = observablePersistConfiguration === null || observablePersistConfiguration === void 0 ? void 0 : observablePersistConfiguration.remoteOptions) === null || _a === void 0 ? void 0 : _a.saveTimeout;
|
|
328
|
+
if (timeout) {
|
|
329
|
+
if (timeoutSaveRemote) {
|
|
330
|
+
clearTimeout(timeoutSaveRemote);
|
|
331
|
+
}
|
|
332
|
+
timeoutSaveRemote = setTimeout(processQueuedRemoteChanges, timeout);
|
|
333
|
+
}
|
|
334
|
+
else {
|
|
335
|
+
processQueuedRemoteChanges();
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function processQueuedRemoteChanges() {
|
|
339
|
+
const queuedRemoteChanges = mergeQueuedChanges(_queuedRemoteChanges);
|
|
340
|
+
_queuedRemoteChanges = [];
|
|
341
|
+
const preppedChangesRemote = await Promise.all(queuedRemoteChanges.map(prepChangeRemote));
|
|
342
|
+
preppedChangesRemote.forEach(doChangeRemote);
|
|
257
343
|
}
|
|
258
|
-
async function
|
|
344
|
+
async function prepChangeLocal(queuedChange) {
|
|
259
345
|
const { syncState, changes, localState, persistOptions, inRemoteChange, isApplyingPending } = queuedChange;
|
|
260
346
|
const local = persistOptions.local;
|
|
261
347
|
const { persistenceRemote } = localState;
|
|
262
348
|
const { config: configLocal } = parseLocalConfig(local);
|
|
263
349
|
const configRemote = persistOptions.remote;
|
|
264
350
|
const saveLocal = local && !configLocal.readonly && !isApplyingPending && syncState.isEnabledLocal.peek();
|
|
265
|
-
const saveRemote = !inRemoteChange &&
|
|
351
|
+
const saveRemote = !!(!inRemoteChange &&
|
|
352
|
+
(persistenceRemote === null || persistenceRemote === void 0 ? void 0 : persistenceRemote.set) &&
|
|
353
|
+
!(configRemote === null || configRemote === void 0 ? void 0 : configRemote.readonly) &&
|
|
354
|
+
syncState.isEnabledRemote.peek());
|
|
266
355
|
if (saveLocal || saveRemote) {
|
|
267
356
|
if (saveLocal && !syncState.isLoadedLocal.peek()) {
|
|
268
357
|
console.error('[legend-state] WARNING: An observable was changed before being loaded from persistence', local);
|
|
269
|
-
return;
|
|
358
|
+
return undefined;
|
|
270
359
|
}
|
|
271
360
|
const changesLocal = [];
|
|
272
|
-
const changesRemote = [];
|
|
273
361
|
const changesPaths = new Set();
|
|
274
362
|
let promisesTransform = [];
|
|
275
363
|
// Reverse order
|
|
@@ -308,6 +396,52 @@ async function prepChange(queuedChange) {
|
|
|
308
396
|
}
|
|
309
397
|
}));
|
|
310
398
|
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// If there's any transform promises, wait for them before saving
|
|
402
|
+
promisesTransform = promisesTransform.filter(Boolean);
|
|
403
|
+
if (promisesTransform.length > 0) {
|
|
404
|
+
await Promise.all(promisesTransform);
|
|
405
|
+
}
|
|
406
|
+
return { queuedChange, changesLocal, saveRemote };
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async function prepChangeRemote(queuedChange) {
|
|
410
|
+
const { syncState, changes, localState, persistOptions, inRemoteChange, isApplyingPending } = queuedChange;
|
|
411
|
+
const local = persistOptions.local;
|
|
412
|
+
const { persistenceRemote } = localState;
|
|
413
|
+
const { config: configLocal } = parseLocalConfig(local);
|
|
414
|
+
const configRemote = persistOptions.remote;
|
|
415
|
+
const saveLocal = local && !configLocal.readonly && !isApplyingPending && syncState.isEnabledLocal.peek();
|
|
416
|
+
const saveRemote = !inRemoteChange && (persistenceRemote === null || persistenceRemote === void 0 ? void 0 : persistenceRemote.set) && !(configRemote === null || configRemote === void 0 ? void 0 : configRemote.readonly) && syncState.isEnabledRemote.peek();
|
|
417
|
+
if (saveLocal || saveRemote) {
|
|
418
|
+
if (saveLocal && !syncState.isLoadedLocal.peek()) {
|
|
419
|
+
console.error('[legend-state] WARNING: An observable was changed before being loaded from persistence', local);
|
|
420
|
+
return undefined;
|
|
421
|
+
}
|
|
422
|
+
const changesRemote = [];
|
|
423
|
+
const changesPaths = new Set();
|
|
424
|
+
let promisesTransform = [];
|
|
425
|
+
// Reverse order
|
|
426
|
+
for (let i = changes.length - 1; i >= 0; i--) {
|
|
427
|
+
const { path } = changes[i];
|
|
428
|
+
let found = false;
|
|
429
|
+
// Optimization to only save the latest update at each path. We might have multiple changes at the same path
|
|
430
|
+
// and we only need the latest value, so it starts from the end of the array, skipping any earlier changes
|
|
431
|
+
// already processed. If a later change modifies a parent of an earlier change (which happens on delete()
|
|
432
|
+
// it should be ignored as it's superseded by the parent modification.
|
|
433
|
+
if (changesPaths.size > 0) {
|
|
434
|
+
for (let u = 0; u < path.length; u++) {
|
|
435
|
+
if (changesPaths.has((u === path.length - 1 ? path : path.slice(0, u + 1)).join('/'))) {
|
|
436
|
+
found = true;
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (!found) {
|
|
442
|
+
const pathStr = path.join('/');
|
|
443
|
+
changesPaths.add(pathStr);
|
|
444
|
+
const { prevAtPath, valueAtPath, pathTypes } = changes[i];
|
|
311
445
|
if (saveRemote) {
|
|
312
446
|
const promiseTransformRemote = transformOutData(valueAtPath, path, pathTypes, configRemote || {});
|
|
313
447
|
promisesTransform.push(doInOrder(promiseTransformRemote, ({ path: pathTransformed, value: valueTransformed }) => {
|
|
@@ -365,21 +499,20 @@ async function prepChange(queuedChange) {
|
|
|
365
499
|
if (promisesTransform.length > 0) {
|
|
366
500
|
await Promise.all(promisesTransform);
|
|
367
501
|
}
|
|
368
|
-
return { queuedChange,
|
|
502
|
+
return { queuedChange, changesRemote };
|
|
369
503
|
}
|
|
370
504
|
}
|
|
371
|
-
async function
|
|
372
|
-
var _a, _b, _c, _d;
|
|
505
|
+
async function doChangeLocal(changeInfo) {
|
|
373
506
|
if (!changeInfo)
|
|
374
507
|
return;
|
|
375
|
-
const { queuedChange, changesLocal,
|
|
508
|
+
const { queuedChange, changesLocal, saveRemote } = changeInfo;
|
|
376
509
|
const { obs, syncState, localState, persistOptions } = queuedChange;
|
|
377
|
-
const { persistenceLocal
|
|
510
|
+
const { persistenceLocal } = localState;
|
|
378
511
|
const local = persistOptions.local;
|
|
379
512
|
const { table, config: configLocal } = parseLocalConfig(local);
|
|
380
513
|
const configRemote = persistOptions.remote;
|
|
381
514
|
const shouldSaveMetadata = local && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.offlineBehavior) === 'retry';
|
|
382
|
-
if (
|
|
515
|
+
if (saveRemote && shouldSaveMetadata) {
|
|
383
516
|
// First save pending changes before saving local or remote
|
|
384
517
|
await updateMetadataImmediate(obs, localState, syncState, persistOptions, {
|
|
385
518
|
pending: localState.pendingChanges,
|
|
@@ -399,29 +532,46 @@ async function doChange(changeInfo) {
|
|
|
399
532
|
await promiseSet;
|
|
400
533
|
}
|
|
401
534
|
}
|
|
535
|
+
}
|
|
536
|
+
async function doChangeRemote(changeInfo) {
|
|
537
|
+
var _a, _b;
|
|
538
|
+
if (!changeInfo)
|
|
539
|
+
return;
|
|
540
|
+
const { queuedChange, changesRemote } = changeInfo;
|
|
541
|
+
const { obs, syncState, localState, persistOptions } = queuedChange;
|
|
542
|
+
const { persistenceLocal, persistenceRemote } = localState;
|
|
543
|
+
const local = persistOptions.local;
|
|
544
|
+
const { table, config: configLocal } = parseLocalConfig(local);
|
|
545
|
+
const { offlineBehavior, allowSetIfError, onBeforeSet, onSetError, waitForSet, onSet } = persistOptions.remote || {};
|
|
546
|
+
const shouldSaveMetadata = local && offlineBehavior === 'retry';
|
|
402
547
|
if (changesRemote.length > 0) {
|
|
403
548
|
// Wait for remote to be ready before saving
|
|
404
|
-
await when(() => syncState.isLoaded.get() || (
|
|
549
|
+
await when(() => syncState.isLoaded.get() || (allowSetIfError && syncState.error.get()));
|
|
550
|
+
if (waitForSet) {
|
|
551
|
+
await when(isFunction(waitForSet) ? waitForSet(changesRemote) : waitForSet);
|
|
552
|
+
}
|
|
405
553
|
const value = obs.peek();
|
|
406
|
-
|
|
407
|
-
|
|
554
|
+
onBeforeSet === null || onBeforeSet === void 0 ? void 0 : onBeforeSet();
|
|
555
|
+
localState.numSavesOutstanding = (localState.numSavesOutstanding || 0) + 1;
|
|
556
|
+
const saved = await ((_a = persistenceRemote.set({
|
|
408
557
|
obs,
|
|
409
558
|
syncState: syncState,
|
|
410
559
|
options: persistOptions,
|
|
411
560
|
changes: changesRemote,
|
|
412
561
|
value,
|
|
413
|
-
})) === null ||
|
|
562
|
+
})) === null || _a === void 0 ? void 0 : _a.catch((err) => onSetError === null || onSetError === void 0 ? void 0 : onSetError(err)));
|
|
563
|
+
localState.numSavesOutstanding--;
|
|
414
564
|
// If this remote save changed anything then update persistence and metadata
|
|
415
565
|
// Because save happens after a timeout and they're batched together, some calls to save will
|
|
416
566
|
// return saved data and others won't, so those can be ignored.
|
|
417
567
|
if (saved) {
|
|
418
568
|
const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
|
|
419
|
-
const { changes,
|
|
569
|
+
const { changes, lastSync } = saved;
|
|
420
570
|
if (pathStrs.length > 0) {
|
|
421
571
|
if (local) {
|
|
422
572
|
const metadata = {};
|
|
423
|
-
const pending = (
|
|
424
|
-
let transformedChanges =
|
|
573
|
+
const pending = (_b = persistenceLocal.getMetadata(table, configLocal)) === null || _b === void 0 ? void 0 : _b.pending;
|
|
574
|
+
let transformedChanges = undefined;
|
|
425
575
|
for (let i = 0; i < pathStrs.length; i++) {
|
|
426
576
|
const pathStr = pathStrs[i];
|
|
427
577
|
// Clear pending for this path
|
|
@@ -431,25 +581,37 @@ async function doChange(changeInfo) {
|
|
|
431
581
|
metadata.pending = pending;
|
|
432
582
|
}
|
|
433
583
|
}
|
|
434
|
-
if (
|
|
435
|
-
metadata.
|
|
584
|
+
if (lastSync) {
|
|
585
|
+
metadata.lastSync = lastSync;
|
|
436
586
|
}
|
|
437
587
|
// Remote can optionally have data that needs to be merged back into the observable,
|
|
438
588
|
// for example Firebase may update dateModified with the server timestamp
|
|
439
589
|
if (changes && !isEmpty(changes)) {
|
|
440
|
-
transformedChanges
|
|
590
|
+
transformedChanges = transformLoadData(changes, persistOptions.remote, false);
|
|
441
591
|
}
|
|
442
|
-
if (
|
|
443
|
-
if (transformedChanges
|
|
444
|
-
|
|
592
|
+
if (localState.numSavesOutstanding > 0) {
|
|
593
|
+
if (transformedChanges) {
|
|
594
|
+
if (!localState.pendingSaveResults) {
|
|
595
|
+
localState.pendingSaveResults = [];
|
|
596
|
+
}
|
|
597
|
+
localState.pendingSaveResults.push(transformedChanges);
|
|
445
598
|
}
|
|
446
|
-
onChangeRemote(() => mergeIntoObservable(obs, ...transformedChanges));
|
|
447
599
|
}
|
|
448
|
-
|
|
449
|
-
|
|
600
|
+
else {
|
|
601
|
+
let allChanges = [...(localState.pendingSaveResults || []), transformedChanges];
|
|
602
|
+
if (allChanges.length > 0) {
|
|
603
|
+
if (allChanges.some((change) => isPromise(change))) {
|
|
604
|
+
allChanges = await Promise.all(allChanges);
|
|
605
|
+
}
|
|
606
|
+
onChangeRemote(() => mergeIntoObservable(obs, ...allChanges));
|
|
607
|
+
}
|
|
608
|
+
if (shouldSaveMetadata && !isEmpty(metadata)) {
|
|
609
|
+
updateMetadata(obs, localState, syncState, persistOptions, metadata);
|
|
610
|
+
}
|
|
611
|
+
localState.pendingSaveResults = [];
|
|
450
612
|
}
|
|
451
613
|
}
|
|
452
|
-
|
|
614
|
+
onSet === null || onSet === void 0 ? void 0 : onSet();
|
|
453
615
|
}
|
|
454
616
|
}
|
|
455
617
|
}
|
|
@@ -497,7 +659,7 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
497
659
|
}
|
|
498
660
|
const { persist: persistenceLocal, initialized } = mapPersistences.get(localPersistence);
|
|
499
661
|
localState.persistenceLocal = persistenceLocal;
|
|
500
|
-
if (!initialized.
|
|
662
|
+
if (!initialized.peek()) {
|
|
501
663
|
await when(initialized);
|
|
502
664
|
}
|
|
503
665
|
// If persistence has an asynchronous load, wait for it
|
|
@@ -511,12 +673,21 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
511
673
|
let value = persistenceLocal.getTable(table, config);
|
|
512
674
|
const metadata = persistenceLocal.getMetadata(table, config);
|
|
513
675
|
if (metadata) {
|
|
676
|
+
// @ts-expect-error Migration from old version
|
|
677
|
+
if (!metadata.lastSync && metadata.modified) {
|
|
678
|
+
// @ts-expect-error Migration from old
|
|
679
|
+
metadata.lastSync = metadata.modified;
|
|
680
|
+
}
|
|
514
681
|
metadatas.set(obs, metadata);
|
|
515
682
|
localState.pendingChanges = metadata.pending;
|
|
516
|
-
|
|
683
|
+
// TODOV3 Remove dateModified
|
|
684
|
+
syncState.assign({
|
|
685
|
+
dateModified: metadata.lastSync,
|
|
686
|
+
lastSync: metadata.lastSync,
|
|
687
|
+
});
|
|
517
688
|
}
|
|
518
689
|
// Merge the data from local persistence into the default state
|
|
519
|
-
if (value !==
|
|
690
|
+
if (value !== undefined) {
|
|
520
691
|
const { transform, fieldTransforms } = config;
|
|
521
692
|
value = transformLoadData(value, { transform, fieldTransforms }, true);
|
|
522
693
|
if (isPromise(value)) {
|
|
@@ -527,7 +698,12 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
527
698
|
// are set on the same observable
|
|
528
699
|
internal$1.globalState.isLoadingLocal = true;
|
|
529
700
|
// We want to merge the local data on top of any initial state the object is created with
|
|
530
|
-
|
|
701
|
+
if (value === null && !obs.peek()) {
|
|
702
|
+
obs.set(value);
|
|
703
|
+
}
|
|
704
|
+
else {
|
|
705
|
+
mergeIntoObservable(obs, value);
|
|
706
|
+
}
|
|
531
707
|
}, () => {
|
|
532
708
|
internal$1.globalState.isLoadingLocal = false;
|
|
533
709
|
});
|
|
@@ -540,18 +716,11 @@ async function loadLocal(obs, persistOptions, syncState, localState) {
|
|
|
540
716
|
}
|
|
541
717
|
syncState.isLoadedLocal.set(true);
|
|
542
718
|
}
|
|
543
|
-
function persistObservable(
|
|
544
|
-
var _a;
|
|
545
|
-
const obs = (isObservable(initialOrObservable)
|
|
546
|
-
? initialOrObservable
|
|
547
|
-
: observable(isFunction(initialOrObservable) ? initialOrObservable() : initialOrObservable));
|
|
719
|
+
function persistObservable(obs, persistOptions) {
|
|
548
720
|
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
721
|
// Merge remote persist options with clobal options
|
|
553
722
|
if (persistOptions.remote) {
|
|
554
|
-
persistOptions.remote = Object.assign({}, observablePersistConfiguration.remoteOptions, persistOptions.remote);
|
|
723
|
+
persistOptions.remote = Object.assign({}, observablePersistConfiguration.remoteOptions, removeNullUndefined(persistOptions.remote));
|
|
555
724
|
}
|
|
556
725
|
let { remote } = persistOptions;
|
|
557
726
|
const { local } = persistOptions;
|
|
@@ -594,33 +763,28 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
594
763
|
var _a, _b;
|
|
595
764
|
if (!isSynced) {
|
|
596
765
|
isSynced = true;
|
|
597
|
-
const
|
|
766
|
+
const lastSync = (_a = metadatas.get(obs)) === null || _a === void 0 ? void 0 : _a.lastSync;
|
|
767
|
+
const pending = localState.pendingChanges;
|
|
598
768
|
const get = (_b = localState.persistenceRemote.get) === null || _b === void 0 ? void 0 : _b.bind(localState.persistenceRemote);
|
|
599
769
|
if (get) {
|
|
600
|
-
let attemptNum = 0;
|
|
601
770
|
const runGet = () => {
|
|
602
771
|
get({
|
|
603
772
|
state: syncState,
|
|
604
773
|
obs,
|
|
605
774
|
options: persistOptions,
|
|
606
|
-
|
|
775
|
+
lastSync,
|
|
776
|
+
dateModified: lastSync,
|
|
607
777
|
onError: (error) => {
|
|
608
778
|
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
779
|
(_a = remote.onGetError) === null || _a === void 0 ? void 0 : _a.call(remote, error);
|
|
619
780
|
},
|
|
620
781
|
onGet: () => {
|
|
621
|
-
|
|
782
|
+
node.state.assign({
|
|
783
|
+
isLoaded: true,
|
|
784
|
+
error: undefined,
|
|
785
|
+
});
|
|
622
786
|
},
|
|
623
|
-
onChange: async ({ value, path = [], pathTypes = [], mode = 'set',
|
|
787
|
+
onChange: async ({ value, path = [], pathTypes = [], mode = 'set', lastSync }) => {
|
|
624
788
|
// Note: value is the constructed value, path is used for setInObservableAtPath
|
|
625
789
|
// to start the set into the observable from the path
|
|
626
790
|
if (value !== undefined) {
|
|
@@ -632,10 +796,12 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
632
796
|
if (path.length && invertedMap) {
|
|
633
797
|
path = transformPath(path, pathTypes, invertedMap);
|
|
634
798
|
}
|
|
635
|
-
if (mode === 'dateModified') {
|
|
636
|
-
if (
|
|
799
|
+
if (mode === 'lastSync' || mode === 'dateModified') {
|
|
800
|
+
if (lastSync && !isEmpty(value)) {
|
|
637
801
|
onChangeRemote(() => {
|
|
638
|
-
setInObservableAtPath(
|
|
802
|
+
setInObservableAtPath(
|
|
803
|
+
// @ts-expect-error Fix this type
|
|
804
|
+
obs, path, pathTypes, value, 'assign');
|
|
639
805
|
});
|
|
640
806
|
}
|
|
641
807
|
}
|
|
@@ -645,7 +811,10 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
645
811
|
Object.keys(pending).forEach((key) => {
|
|
646
812
|
const p = key.split('/').filter((p) => p !== '');
|
|
647
813
|
const { v, t } = pending[key];
|
|
648
|
-
if (
|
|
814
|
+
if (t.length === 0 || !value) {
|
|
815
|
+
value = v;
|
|
816
|
+
}
|
|
817
|
+
else if (value[p[0]] !== undefined) {
|
|
649
818
|
value = setAtPath(value, p, t, v, obs.peek(), (path, value) => {
|
|
650
819
|
delete pending[key];
|
|
651
820
|
pending[path.join('/')] = {
|
|
@@ -658,13 +827,14 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
658
827
|
});
|
|
659
828
|
}
|
|
660
829
|
onChangeRemote(() => {
|
|
830
|
+
// @ts-expect-error Fix this type
|
|
661
831
|
setInObservableAtPath(obs, path, pathTypes, value, mode);
|
|
662
832
|
});
|
|
663
833
|
}
|
|
664
834
|
}
|
|
665
|
-
if (
|
|
835
|
+
if (lastSync && local) {
|
|
666
836
|
updateMetadata(obs, localState, syncState, persistOptions, {
|
|
667
|
-
|
|
837
|
+
lastSync,
|
|
668
838
|
});
|
|
669
839
|
}
|
|
670
840
|
},
|
|
@@ -673,11 +843,13 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
673
843
|
runGet();
|
|
674
844
|
}
|
|
675
845
|
else {
|
|
676
|
-
|
|
846
|
+
node.state.assign({
|
|
847
|
+
isLoaded: true,
|
|
848
|
+
error: undefined,
|
|
849
|
+
});
|
|
677
850
|
}
|
|
678
851
|
// Wait for remote to be ready before saving pending
|
|
679
852
|
await when(() => syncState.isLoaded.get() || (remote.allowSetIfError && syncState.error.get()));
|
|
680
|
-
const pending = localState.pendingChanges;
|
|
681
853
|
if (pending && !isEmpty(pending)) {
|
|
682
854
|
localState.isApplyingPending = true;
|
|
683
855
|
const keys = Object.keys(pending);
|
|
@@ -690,6 +862,7 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
690
862
|
changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
|
|
691
863
|
}
|
|
692
864
|
// Send the changes into onObsChange so that they get persisted remotely
|
|
865
|
+
// @ts-expect-error Fix this type
|
|
693
866
|
onObsChange(obs, syncState, localState, persistOptions, {
|
|
694
867
|
value: obs.peek(),
|
|
695
868
|
// TODO getPrevious if any remote persistence layers need it
|
|
@@ -725,49 +898,122 @@ function persistObservable(initialOrObservable, persistOptions) {
|
|
|
725
898
|
}
|
|
726
899
|
obs.onChange(onObsChange.bind(this, obs, syncState, localState, persistOptions));
|
|
727
900
|
});
|
|
728
|
-
return
|
|
901
|
+
return syncState;
|
|
729
902
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
903
|
+
|
|
904
|
+
const { getProxy, globalState, runWithRetry, symbolActivated } = internal$1;
|
|
905
|
+
function persistActivateNode() {
|
|
906
|
+
globalState.activateNode = function activateNodePersist(node, refresh, wasPromise, newValue) {
|
|
907
|
+
if (node.activationState) {
|
|
908
|
+
const { get, initial, onSet, subscribe, cache, retry, offlineBehavior, waitForSet } = node.activationState;
|
|
909
|
+
let onChange = undefined;
|
|
910
|
+
const pluginRemote = {};
|
|
911
|
+
if (get) {
|
|
912
|
+
pluginRemote.get = (params) => {
|
|
913
|
+
onChange = params.onChange;
|
|
914
|
+
const updateLastSync = (lastSync) => (params.lastSync = lastSync);
|
|
915
|
+
const setMode = (mode) => (params.mode = mode);
|
|
916
|
+
const nodeValue = getNodeValue(node);
|
|
917
|
+
const value = runWithRetry(node, { attemptNum: 0 }, () => {
|
|
918
|
+
return get({
|
|
919
|
+
value: isFunction(nodeValue) || (nodeValue === null || nodeValue === void 0 ? void 0 : nodeValue[symbolActivated]) ? undefined : nodeValue,
|
|
920
|
+
lastSync: params.lastSync,
|
|
921
|
+
updateLastSync,
|
|
922
|
+
setMode,
|
|
923
|
+
refresh,
|
|
924
|
+
});
|
|
925
|
+
});
|
|
926
|
+
return value;
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (onSet) {
|
|
930
|
+
// TODO: Work out these types better
|
|
931
|
+
pluginRemote.set = async (params) => {
|
|
932
|
+
var _a;
|
|
933
|
+
if ((_a = node.state) === null || _a === void 0 ? void 0 : _a.isLoaded.get()) {
|
|
934
|
+
const retryAttempts = { attemptNum: 0 };
|
|
935
|
+
return runWithRetry(node, retryAttempts, async (retryEvent) => {
|
|
936
|
+
let changes = {};
|
|
937
|
+
let maxModified = 0;
|
|
938
|
+
if (!node.state.isLoaded.peek()) {
|
|
939
|
+
await whenReady(node.state.isLoaded);
|
|
940
|
+
}
|
|
941
|
+
const cancelRetry = () => {
|
|
942
|
+
retryEvent.cancel = true;
|
|
943
|
+
};
|
|
944
|
+
await onSet({
|
|
945
|
+
...params,
|
|
946
|
+
node,
|
|
947
|
+
update: (params) => {
|
|
948
|
+
const { value, lastSync } = params;
|
|
949
|
+
maxModified = Math.max(lastSync || 0, maxModified);
|
|
950
|
+
changes = mergeIntoObservable(changes, value);
|
|
951
|
+
},
|
|
952
|
+
retryNum: retryAttempts.attemptNum,
|
|
953
|
+
cancelRetry,
|
|
954
|
+
refresh,
|
|
955
|
+
fromSubscribe: false,
|
|
956
|
+
});
|
|
957
|
+
return { changes, lastSync: maxModified || undefined };
|
|
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
|
+
waitForSet,
|
|
984
|
+
},
|
|
985
|
+
});
|
|
986
|
+
const nodeVal = getNodeValue(node);
|
|
987
|
+
if (nodeVal !== undefined) {
|
|
988
|
+
newValue = nodeVal;
|
|
738
989
|
}
|
|
739
|
-
if (
|
|
740
|
-
|
|
990
|
+
else if (newValue === undefined) {
|
|
991
|
+
newValue = initial;
|
|
741
992
|
}
|
|
742
|
-
return newValue;
|
|
743
|
-
}
|
|
993
|
+
return { update: onChange, value: newValue };
|
|
994
|
+
}
|
|
995
|
+
else {
|
|
996
|
+
let onChange = undefined;
|
|
997
|
+
const pluginRemote = {
|
|
998
|
+
get: async (params) => {
|
|
999
|
+
onChange = params.onChange;
|
|
1000
|
+
if (isPromise(newValue)) {
|
|
1001
|
+
try {
|
|
1002
|
+
newValue = await newValue;
|
|
1003
|
+
// eslint-disable-next-line no-empty
|
|
1004
|
+
}
|
|
1005
|
+
catch (_a) { }
|
|
1006
|
+
}
|
|
1007
|
+
return newValue;
|
|
1008
|
+
},
|
|
1009
|
+
};
|
|
1010
|
+
persistObservable(getProxy(node), {
|
|
1011
|
+
pluginRemote,
|
|
1012
|
+
});
|
|
1013
|
+
return { update: onChange, value: newValue };
|
|
1014
|
+
}
|
|
744
1015
|
};
|
|
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
|
-
};
|
|
1016
|
+
}
|
|
771
1017
|
|
|
772
1018
|
function isInRemoteChange() {
|
|
773
1019
|
return internal$1.globalState.isLoadingRemote$.get();
|
|
@@ -775,6 +1021,7 @@ function isInRemoteChange() {
|
|
|
775
1021
|
const internal = {
|
|
776
1022
|
observablePersistConfiguration,
|
|
777
1023
|
};
|
|
1024
|
+
persistActivateNode();
|
|
778
1025
|
|
|
779
1026
|
export { configureObservablePersistence, internal, invertFieldMap, isInRemoteChange, mapPersistences, persistObservable, transformObject, transformPath };
|
|
780
1027
|
//# sourceMappingURL=persist.mjs.map
|