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