@legendapp/state 3.0.0-alpha.2 → 3.0.0-alpha.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +831 -1
  2. package/LICENSE +21 -1
  3. package/README.md +141 -1
  4. package/babel.js +0 -2
  5. package/babel.mjs +0 -2
  6. package/helpers/trackHistory.js +2 -2
  7. package/helpers/trackHistory.mjs +2 -2
  8. package/index.d.mts +45 -32
  9. package/index.d.ts +45 -32
  10. package/index.js +234 -156
  11. package/index.mjs +234 -156
  12. package/package.json +6 -1
  13. package/persist-plugins/async-storage.d.mts +2 -2
  14. package/persist-plugins/async-storage.d.ts +2 -2
  15. package/persist-plugins/indexeddb.js +1 -1
  16. package/persist-plugins/indexeddb.mjs +1 -1
  17. package/react.d.mts +17 -14
  18. package/react.d.ts +17 -14
  19. package/react.js +55 -30
  20. package/react.mjs +56 -31
  21. package/sync-plugins/_transformObjectFields.d.mts +31 -0
  22. package/sync-plugins/_transformObjectFields.d.ts +31 -0
  23. package/sync-plugins/_transformObjectFields.js +114 -0
  24. package/sync-plugins/_transformObjectFields.mjs +110 -0
  25. package/sync-plugins/crud.d.mts +14 -23
  26. package/sync-plugins/crud.d.ts +14 -23
  27. package/sync-plugins/crud.js +205 -134
  28. package/sync-plugins/crud.mjs +206 -135
  29. package/sync-plugins/firebase.d.mts +26 -0
  30. package/sync-plugins/firebase.d.ts +26 -0
  31. package/sync-plugins/firebase.js +365 -0
  32. package/sync-plugins/firebase.mjs +360 -0
  33. package/sync-plugins/keel.d.mts +24 -8
  34. package/sync-plugins/keel.d.ts +24 -8
  35. package/sync-plugins/keel.js +32 -16
  36. package/sync-plugins/keel.mjs +32 -16
  37. package/sync-plugins/supabase.d.mts +1 -1
  38. package/sync-plugins/supabase.d.ts +1 -1
  39. package/sync-plugins/supabase.js +5 -4
  40. package/sync-plugins/supabase.mjs +6 -5
  41. package/sync-plugins/tanstack-query.d.mts +2 -2
  42. package/sync-plugins/tanstack-query.d.ts +2 -2
  43. package/sync-plugins/tanstack-query.js +3 -2
  44. package/sync-plugins/tanstack-query.mjs +3 -2
  45. package/sync-plugins/tanstack-react-query.d.mts +1 -1
  46. package/sync-plugins/tanstack-react-query.d.ts +1 -1
  47. package/sync.d.mts +38 -185
  48. package/sync.d.ts +38 -185
  49. package/sync.js +354 -296
  50. package/sync.mjs +356 -297
  51. package/types/babel.d.ts +12 -1
  52. package/.DS_Store +0 -0
  53. /package/config/{enable_GetSet.d.mts → enable$GetSet.d.mts} +0 -0
  54. /package/config/{enable_GetSet.d.ts → enable$GetSet.d.ts} +0 -0
package/sync.js CHANGED
@@ -2,8 +2,6 @@
2
2
 
3
3
  var state = require('@legendapp/state');
4
4
 
5
- // sync.ts
6
-
7
5
  // src/sync/configureObservableSync.ts
8
6
  var observableSyncConfiguration = {};
9
7
  function configureObservableSync(options) {
@@ -139,34 +137,19 @@ function transformStringifyDates(...args) {
139
137
  }
140
138
  };
141
139
  }
142
- function syncObservableAdapter({ get, set }) {
143
- const ret = {};
144
- if (get) {
145
- ret.get = async (params) => {
146
- try {
147
- let value = get(params);
148
- if (state.isPromise(value)) {
149
- value = await value;
150
- }
151
- params.onChange({
152
- value,
153
- lastSync: params.lastSync,
154
- mode: params.mode
155
- });
156
- params.onGet();
157
- } catch (e) {
158
- }
159
- };
160
- }
161
- if (set) {
162
- ret.set = set;
163
- }
164
- return ret;
165
- }
166
-
167
- // src/sync/syncObservable.ts
168
- var { createPreviousHandler, clone, getValueAtPath, globalState, symbolLinked, getNode, getNodeValue } = state.internal;
140
+ var {
141
+ clone,
142
+ deepMerge,
143
+ getNode,
144
+ getNodeValue,
145
+ getValueAtPath,
146
+ globalState,
147
+ runWithRetry,
148
+ symbolLinked,
149
+ createPreviousHandler
150
+ } = state.internal;
169
151
  var mapSyncPlugins = /* @__PURE__ */ new WeakMap();
152
+ var allSyncStates = /* @__PURE__ */ new Map();
170
153
  var metadatas = /* @__PURE__ */ new WeakMap();
171
154
  var promisesLocalSaves = /* @__PURE__ */ new Set();
172
155
  function parseLocalConfig(config) {
@@ -183,13 +166,19 @@ function onChangeRemote(cb) {
183
166
  globalState.isLoadingRemote = false;
184
167
  state.endBatch(true);
185
168
  }
186
- function transformSaveData(value, path, pathTypes, { transform }) {
169
+ async function transformSaveData(value, path, pathTypes, { transform }) {
187
170
  if (transform == null ? void 0 : transform.save) {
188
171
  const constructed = state.constructObjectWithPath(path, pathTypes, value);
189
- const saved = transform.save(constructed);
190
- value = state.deconstructObjectWithPath(path, pathTypes, saved);
172
+ const saved = await transform.save(constructed);
173
+ value = saved;
174
+ const outPath = [];
175
+ for (let i = 0; i < path.length; i++) {
176
+ outPath[i] = Object.keys(value)[0];
177
+ value = value[outPath[i]];
178
+ }
179
+ path = outPath;
191
180
  }
192
- return value;
181
+ return { value, path };
193
182
  }
194
183
  function transformLoadData(value, { transform }, doUserTransform, method) {
195
184
  if (doUserTransform && (transform == null ? void 0 : transform.load)) {
@@ -197,7 +186,7 @@ function transformLoadData(value, { transform }, doUserTransform, method) {
197
186
  }
198
187
  return value;
199
188
  }
200
- async function updateMetadataImmediate(value$, localState, syncState, syncOptions, newMetadata) {
189
+ async function updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata) {
201
190
  const saves = Array.from(promisesLocalSaves);
202
191
  if (saves.length > 0) {
203
192
  await Promise.all(saves);
@@ -205,27 +194,24 @@ async function updateMetadataImmediate(value$, localState, syncState, syncOption
205
194
  const { pluginPersist } = localState;
206
195
  const { table, config } = parseLocalConfig(syncOptions == null ? void 0 : syncOptions.persist);
207
196
  const oldMetadata = metadatas.get(value$);
208
- const { lastSync, pending } = newMetadata;
209
- const needsUpdate = pending || lastSync && (!oldMetadata || lastSync !== oldMetadata.lastSync);
210
- if (needsUpdate) {
211
- const metadata = Object.assign({}, oldMetadata, newMetadata);
212
- metadatas.set(value$, metadata);
213
- if (pluginPersist) {
214
- await pluginPersist.setMetadata(table, metadata, config);
215
- }
216
- if (lastSync) {
217
- syncState.assign({
218
- lastSync
219
- });
220
- }
197
+ const { lastSync } = newMetadata;
198
+ const metadata = Object.assign({}, oldMetadata, newMetadata);
199
+ metadatas.set(value$, metadata);
200
+ if (pluginPersist) {
201
+ await pluginPersist.setMetadata(table, metadata, config);
202
+ }
203
+ if (lastSync) {
204
+ syncState2.assign({
205
+ lastSync
206
+ });
221
207
  }
222
208
  }
223
- function updateMetadata(value$, localState, syncState, syncOptions, newMetadata) {
209
+ function updateMetadata(value$, localState, syncState2, syncOptions, newMetadata) {
224
210
  if (localState.timeoutSaveMetadata) {
225
211
  clearTimeout(localState.timeoutSaveMetadata);
226
212
  }
227
213
  localState.timeoutSaveMetadata = setTimeout(
228
- () => updateMetadataImmediate(value$, localState, syncState, syncOptions, newMetadata),
214
+ () => updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata),
229
215
  0
230
216
  );
231
217
  }
@@ -253,26 +239,25 @@ function mergeChanges(changes) {
253
239
  return changesOut;
254
240
  }
255
241
  function mergeQueuedChanges(allChanges) {
256
- const changesByObsRemote = /* @__PURE__ */ new Map();
257
- const changesByObsLocal = /* @__PURE__ */ new Map();
258
- const previousByObs = /* @__PURE__ */ new Map();
242
+ const changesByOptionsRemote = /* @__PURE__ */ new Map();
243
+ const changesByOptionsLocal = /* @__PURE__ */ new Map();
244
+ const previousByOptions = /* @__PURE__ */ new Map();
259
245
  const outRemote = /* @__PURE__ */ new Map();
260
246
  const outLocal = /* @__PURE__ */ new Map();
261
247
  for (let i = 0; i < allChanges.length; i++) {
262
248
  const value = allChanges[i];
263
- const { value$: obs, changes, inRemoteChange, getPrevious } = value;
249
+ const { changes, inRemoteChange, getPrevious, syncOptions } = value;
264
250
  const targetMap = inRemoteChange ? outRemote : outLocal;
265
- const changesMap = inRemoteChange ? changesByObsRemote : changesByObsLocal;
266
- const existing = changesMap.get(obs);
251
+ const changesMap = inRemoteChange ? changesByOptionsRemote : changesByOptionsLocal;
252
+ const existing = changesMap.get(syncOptions);
267
253
  const newChanges = existing ? [...existing, ...changes] : changes;
268
254
  const merged = mergeChanges(newChanges);
269
- changesMap.set(obs, merged);
255
+ changesMap.set(syncOptions, merged);
270
256
  value.changes = merged;
271
- if (!previousByObs.has(obs)) {
272
- previousByObs.set(obs, getPrevious());
257
+ if (!previousByOptions.has(syncOptions)) {
258
+ previousByOptions.set(syncOptions, getPrevious());
273
259
  }
274
- value.valuePrevious = previousByObs.get(obs);
275
- targetMap.set(obs, value);
260
+ targetMap.set(syncOptions, value);
276
261
  }
277
262
  return Array.from(outRemote.values()).concat(Array.from(outLocal.values()));
278
263
  }
@@ -318,15 +303,13 @@ async function processQueuedRemoteChanges(syncOptions) {
318
303
  }
319
304
  }
320
305
  async function prepChangeLocal(queuedChange) {
321
- const { syncState, changes, localState, syncOptions, inRemoteChange, isApplyingPending } = queuedChange;
306
+ const { syncState: syncState2, changes, syncOptions, inRemoteChange, isApplyingPending } = queuedChange;
322
307
  const persist = syncOptions.persist;
323
- const { pluginSync } = localState;
324
308
  const { config: configLocal } = parseLocalConfig(persist);
325
- const configRemote = syncOptions;
326
- const saveLocal = (persist == null ? void 0 : persist.name) && !configLocal.readonly && !isApplyingPending && syncState.isPersistEnabled.peek();
327
- const saveRemote = !!(!inRemoteChange && (pluginSync == null ? void 0 : pluginSync.set) && (configRemote == null ? void 0 : configRemote.enableSync) !== false && syncState.isSyncEnabled.peek());
309
+ const saveLocal = (persist == null ? void 0 : persist.name) && !configLocal.readonly && !isApplyingPending && syncState2.isPersistEnabled.peek();
310
+ const saveRemote = !!(!inRemoteChange && (syncOptions == null ? void 0 : syncOptions.set) && syncState2.isSyncEnabled.peek());
328
311
  if (saveLocal || saveRemote) {
329
- if (saveLocal && !syncState.isPersistLoaded.peek()) {
312
+ if (saveLocal && !syncState2.isPersistLoaded.peek()) {
330
313
  console.error(
331
314
  "[legend-state] WARNING: An observable was changed before being loaded from persist",
332
315
  persist
@@ -359,13 +342,13 @@ async function prepChangeLocal(queuedChange) {
359
342
  configLocal
360
343
  );
361
344
  promisesTransform.push(
362
- doInOrder(promiseTransformLocal, (valueTransformed) => {
345
+ doInOrder(promiseTransformLocal, ({ value: valueTransformed, path: pathTransformed }) => {
363
346
  changesLocal.push({
364
- path,
347
+ path: pathTransformed,
365
348
  pathTypes,
366
349
  prevAtPath,
367
350
  valueAtPath: valueTransformed,
368
- pathStr
351
+ pathStr: path === pathTransformed ? pathStr : pathTransformed.join("/")
369
352
  });
370
353
  })
371
354
  );
@@ -381,22 +364,19 @@ async function prepChangeLocal(queuedChange) {
381
364
  }
382
365
  async function prepChangeRemote(queuedChange) {
383
366
  const {
384
- syncState,
367
+ syncState: syncState2,
385
368
  changes,
386
369
  localState,
387
370
  syncOptions,
388
371
  inRemoteChange,
389
- isApplyingPending,
390
- valuePrevious
372
+ isApplyingPending
391
373
  } = queuedChange;
392
374
  const persist = syncOptions.persist;
393
- const { pluginSync } = localState;
394
375
  const { config: configLocal } = parseLocalConfig(persist);
395
- const configRemote = syncOptions;
396
- const saveLocal = persist && !configLocal.readonly && !isApplyingPending && syncState.isPersistEnabled.peek();
397
- const saveRemote = !inRemoteChange && (pluginSync == null ? void 0 : pluginSync.set) && (configRemote == null ? void 0 : configRemote.enableSync) !== false && syncState.isSyncEnabled.peek();
376
+ const saveLocal = persist && !configLocal.readonly && !isApplyingPending && syncState2.isPersistEnabled.peek();
377
+ const saveRemote = !inRemoteChange && (syncOptions == null ? void 0 : syncOptions.set) && syncState2.isSyncEnabled.peek();
398
378
  if (saveLocal || saveRemote) {
399
- if (saveLocal && !syncState.isPersistLoaded.peek()) {
379
+ if (saveLocal && !syncState2.isPersistLoaded.peek()) {
400
380
  console.error(
401
381
  "[legend-state] WARNING: An observable was changed before being loaded from persist",
402
382
  persist
@@ -426,20 +406,20 @@ async function prepChangeRemote(queuedChange) {
426
406
  valueAtPath,
427
407
  path,
428
408
  pathTypes,
429
- configRemote || {}
409
+ syncOptions || {}
430
410
  );
431
411
  promisesTransform.push(
432
- doInOrder(promiseTransformRemote, (valueTransformed) => {
412
+ doInOrder(promiseTransformRemote, ({ value: valueTransformed, path: pathTransformed }) => {
433
413
  var _a;
434
414
  if (!localState.pendingChanges) {
435
415
  localState.pendingChanges = {};
436
416
  }
437
417
  let found2 = false;
438
- for (let i2 = 0; !found2 && i2 < path.length - 1; i2++) {
439
- const pathParent = path.slice(0, i2 + 1).join("/");
418
+ for (let i2 = 0; !found2 && i2 < pathTransformed.length - 1; i2++) {
419
+ const pathParent = pathTransformed.slice(0, i2 + 1).join("/");
440
420
  if ((_a = localState.pendingChanges[pathParent]) == null ? void 0 : _a.v) {
441
421
  found2 = true;
442
- const pathChild = path.slice(i2 + 1);
422
+ const pathChild = pathTransformed.slice(i2 + 1);
443
423
  const pathTypesChild = pathTypes.slice(i2 + 1);
444
424
  state.setAtPath(
445
425
  localState.pendingChanges[pathParent].v,
@@ -461,12 +441,11 @@ async function prepChangeRemote(queuedChange) {
461
441
  localState.pendingChanges[pathStr].v = valueAtPath;
462
442
  }
463
443
  changesRemote.push({
464
- path,
444
+ path: pathTransformed,
465
445
  pathTypes,
466
446
  prevAtPath,
467
447
  valueAtPath: valueTransformed,
468
- pathStr,
469
- valuePrevious
448
+ pathStr
470
449
  });
471
450
  })
472
451
  );
@@ -484,24 +463,27 @@ async function doChangeLocal(changeInfo) {
484
463
  if (!changeInfo)
485
464
  return;
486
465
  const { queuedChange, changesLocal, saveRemote } = changeInfo;
487
- const { value$: obs, syncState, localState, syncOptions } = queuedChange;
466
+ const { value$: obs, syncState: syncState2, localState, syncOptions } = queuedChange;
488
467
  const { pluginPersist } = localState;
489
468
  const persist = syncOptions.persist;
490
- const { table, config: configLocal } = parseLocalConfig(persist);
491
- const shouldSaveMetadata = persist == null ? void 0 : persist.retrySync;
492
- if (saveRemote && shouldSaveMetadata) {
493
- await updateMetadataImmediate(obs, localState, syncState, syncOptions, {
494
- pending: localState.pendingChanges
495
- });
496
- }
497
- if (changesLocal.length > 0) {
498
- let promiseSet = pluginPersist.set(table, changesLocal, configLocal);
499
- if (promiseSet) {
500
- promiseSet = promiseSet.then(() => {
501
- promisesLocalSaves.delete(promiseSet);
469
+ const saveLocal = !!(persist == null ? void 0 : persist.name);
470
+ if (saveLocal) {
471
+ const { table, config: configLocal } = parseLocalConfig(persist);
472
+ const shouldSaveMetadata = persist == null ? void 0 : persist.retrySync;
473
+ if (saveRemote && shouldSaveMetadata) {
474
+ await updateMetadataImmediate(obs, localState, syncState2, syncOptions, {
475
+ pending: localState.pendingChanges
502
476
  });
503
- promisesLocalSaves.add(promiseSet);
504
- await promiseSet;
477
+ }
478
+ if (changesLocal.length > 0) {
479
+ let promiseSet = pluginPersist.set(table, changesLocal, configLocal);
480
+ if (promiseSet) {
481
+ promiseSet = promiseSet.then(() => {
482
+ promisesLocalSaves.delete(promiseSet);
483
+ });
484
+ promisesLocalSaves.add(promiseSet);
485
+ await promiseSet;
486
+ }
505
487
  }
506
488
  }
507
489
  }
@@ -510,47 +492,87 @@ async function doChangeRemote(changeInfo) {
510
492
  if (!changeInfo)
511
493
  return;
512
494
  const { queuedChange, changesRemote } = changeInfo;
513
- const { value$: obs, syncState, localState, syncOptions, valuePrevious: previous } = queuedChange;
514
- const { pluginPersist, pluginSync } = localState;
495
+ const { value$: obs$, syncState: syncState2, localState, syncOptions } = queuedChange;
496
+ const { pluginPersist } = localState;
497
+ const node = getNode(obs$);
498
+ const state$ = node.state;
515
499
  const persist = syncOptions.persist;
516
500
  const { table, config: configLocal } = parseLocalConfig(persist);
517
- const { allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
501
+ const { onBeforeSet, waitForSet, onAfterSet } = syncOptions || {};
518
502
  const shouldSaveMetadata = persist == null ? void 0 : persist.retrySync;
503
+ const saveLocal = !!(persist == null ? void 0 : persist.name);
519
504
  if (changesRemote.length > 0) {
520
- await state.when(() => syncState.isLoaded.get() || allowSetIfGetError && syncState.error.get());
505
+ if (!syncState2.isLoaded.peek()) {
506
+ await state.when(syncState2.isLoaded);
507
+ const pending = localState.pendingChanges;
508
+ if (pending) {
509
+ changesRemote.forEach((change) => {
510
+ const key = change.pathStr;
511
+ const pendingAtPath = pending[key];
512
+ if (!state.isNullOrUndefined(pendingAtPath)) {
513
+ const { p } = pendingAtPath;
514
+ change.prevAtPath = p;
515
+ }
516
+ });
517
+ }
518
+ }
521
519
  if (waitForSet) {
522
- const waitFor = state.isFunction(waitForSet) ? waitForSet({ changes: changesRemote, value: obs.peek() }) : waitForSet;
523
- if (waitFor) {
524
- await state.when(waitFor);
520
+ const waitFn = state.isFunction(waitForSet) ? waitForSet({ changes: changesRemote, value: obs$.peek() }) : waitForSet;
521
+ if (waitFn) {
522
+ await state.when(waitFn);
525
523
  }
526
524
  }
527
- let value = obs.peek();
525
+ let value = clone(obs$.peek());
528
526
  const transformSave = (_a = syncOptions == null ? void 0 : syncOptions.transform) == null ? void 0 : _a.save;
529
527
  if (transformSave) {
530
- value = transformSave(clone(value));
528
+ value = transformSave(value);
531
529
  }
530
+ state$.numPendingSets.set((v) => (v || 0) + 1);
531
+ state$.isSetting.set(true);
532
532
  onBeforeSet == null ? void 0 : onBeforeSet();
533
- localState.numSavesOutstanding = (localState.numSavesOutstanding || 0) + 1;
534
- let savedPromise = pluginSync.set({
535
- value$: obs,
536
- syncState,
537
- options: syncOptions,
533
+ let updateResult = void 0;
534
+ const onError = (error) => {
535
+ var _a2;
536
+ state$.error.set(error);
537
+ (_a2 = syncOptions.onSetError) == null ? void 0 : _a2.call(syncOptions, error, setParams);
538
+ };
539
+ const setParams = {
540
+ node,
541
+ value$: obs$,
538
542
  changes: changesRemote,
539
543
  value,
540
- valuePrevious: previous
544
+ onError,
545
+ update: (params) => {
546
+ if (updateResult) {
547
+ const { value: value2, lastSync, mode } = params;
548
+ updateResult = {
549
+ lastSync: Math.max(updateResult.lastSync || 0, lastSync || 0),
550
+ value: deepMerge(updateResult.value, value2),
551
+ mode
552
+ };
553
+ } else {
554
+ updateResult = params;
555
+ }
556
+ },
557
+ refresh: syncState2.sync
558
+ };
559
+ let savedPromise = runWithRetry({ retryNum: 0, retry: syncOptions.retry }, async (retryEvent) => {
560
+ const params = setParams;
561
+ params.cancelRetry = retryEvent.cancelRetry;
562
+ params.retryNum = retryEvent.retryNum;
563
+ return syncOptions.set(params);
541
564
  });
542
565
  if (state.isPromise(savedPromise)) {
543
- savedPromise = savedPromise.catch((err) => onSetError == null ? void 0 : onSetError(err));
566
+ savedPromise = savedPromise.catch(onError);
544
567
  }
545
- const saved = await savedPromise;
546
- localState.numSavesOutstanding--;
547
- if (saved !== void 0) {
568
+ await savedPromise;
569
+ if (!state$.error.peek()) {
548
570
  const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
549
- const { changes, lastSync } = saved;
571
+ const { value: changes, lastSync } = updateResult || {};
550
572
  if (pathStrs.length > 0) {
551
573
  let transformedChanges = void 0;
552
574
  const metadata = {};
553
- if (persist) {
575
+ if (saveLocal) {
554
576
  const pendingMetadata = (_b = pluginPersist.getMetadata(table, configLocal)) == null ? void 0 : _b.pending;
555
577
  const pending = localState.pendingChanges;
556
578
  for (let i = 0; i < pathStrs.length; i++) {
@@ -570,42 +592,31 @@ async function doChangeRemote(changeInfo) {
570
592
  if (changes && !state.isEmpty(changes)) {
571
593
  transformedChanges = transformLoadData(changes, syncOptions, false, "set");
572
594
  }
573
- if (localState.numSavesOutstanding > 0) {
574
- if (transformedChanges) {
575
- if (!localState.pendingSaveResults) {
576
- localState.pendingSaveResults = [];
577
- }
578
- localState.pendingSaveResults.push(transformedChanges);
595
+ if (transformedChanges !== void 0) {
596
+ if (state.isPromise(transformedChanges)) {
597
+ transformedChanges = await transformedChanges;
579
598
  }
580
- } else {
581
- let allChanges = [...localState.pendingSaveResults || [], transformedChanges].filter(
582
- (v) => v !== void 0
583
- );
584
- if (allChanges.length > 0) {
585
- if (allChanges.some((change) => state.isPromise(change))) {
586
- allChanges = await Promise.all(allChanges);
587
- }
588
- onChangeRemote(() => state.mergeIntoObservable(obs, ...allChanges));
589
- }
590
- if (persist) {
591
- if (shouldSaveMetadata && !state.isEmpty(metadata)) {
592
- updateMetadata(obs, localState, syncState, syncOptions, metadata);
593
- }
599
+ onChangeRemote(() => state.mergeIntoObservable(obs$, transformedChanges));
600
+ }
601
+ if (saveLocal) {
602
+ if (shouldSaveMetadata && !state.isEmpty(metadata)) {
603
+ updateMetadata(obs$, localState, syncState2, syncOptions, metadata);
594
604
  }
595
- localState.pendingSaveResults = [];
596
605
  }
597
- onAfterSet == null ? void 0 : onAfterSet();
598
606
  }
607
+ state$.numPendingSets.set((v) => v - 1);
608
+ state$.isSetting.set(state$.numPendingSets.peek() > 0);
609
+ onAfterSet == null ? void 0 : onAfterSet();
599
610
  }
600
611
  }
601
612
  }
602
- function onObsChange(value$, syncState, localState, syncOptions, { changes, loading, remote, getPrevious }) {
603
- if (!loading) {
604
- const inRemoteChange = remote;
613
+ function onObsChange(value$, syncState2, localState, syncOptions, { changes, isFromPersist, isFromSync, getPrevious }) {
614
+ if (!isFromPersist) {
615
+ const inRemoteChange = isFromSync;
605
616
  const isApplyingPending = localState.isApplyingPending;
606
617
  _queuedChanges.push({
607
618
  value$,
608
- syncState,
619
+ syncState: syncState2,
609
620
  localState,
610
621
  syncOptions,
611
622
  changes,
@@ -618,13 +629,17 @@ function onObsChange(value$, syncState, localState, syncOptions, { changes, load
618
629
  }
619
630
  }
620
631
  }
621
- async function loadLocal(value$, syncOptions, syncState, localState) {
632
+ async function loadLocal(value$, syncOptions, syncState$, localState) {
622
633
  var _a, _b, _c;
623
634
  const { persist } = syncOptions;
624
- if (persist) {
635
+ const node = getNode(value$);
636
+ const nodeValue = getNodeValue(getNode(node.state));
637
+ const syncStateValue = syncState$.peek();
638
+ const prevClearPersist = nodeValue.clearPersist;
639
+ if (persist == null ? void 0 : persist.name) {
625
640
  const PersistPlugin = persist.plugin || ((_a = observableSyncConfiguration.persist) == null ? void 0 : _a.plugin);
626
641
  const { table, config } = parseLocalConfig(persist);
627
- const node = getNode(value$);
642
+ syncStateValue.numPendingLocalLoads = (syncStateValue.numPendingLocalLoads || 0) + 1;
628
643
  if (!PersistPlugin) {
629
644
  throw new Error("Local persist is not configured");
630
645
  }
@@ -658,7 +673,7 @@ async function loadLocal(value$, syncOptions, syncState, localState) {
658
673
  if (metadata) {
659
674
  metadatas.set(value$, metadata);
660
675
  localState.pendingChanges = metadata.pending;
661
- syncState.assign({
676
+ syncState$.assign({
662
677
  lastSync: metadata.lastSync
663
678
  });
664
679
  }
@@ -676,12 +691,18 @@ async function loadLocal(value$, syncOptions, syncState, localState) {
676
691
  }
677
692
  state.internal.globalState.isLoadingLocal = false;
678
693
  }
679
- getNodeValue(getNode(node.state)).clearPersist = () => Promise.all([
680
- persistPlugin.deleteTable(table, config),
681
- persistPlugin.deleteMetadata(table, config)
682
- ]);
694
+ syncStateValue.numPendingLocalLoads--;
695
+ nodeValue.clearPersist = () => Promise.all(
696
+ [
697
+ prevClearPersist,
698
+ persistPlugin.deleteTable(table, config),
699
+ persistPlugin.deleteMetadata(table, config)
700
+ ].filter(Boolean)
701
+ );
702
+ } else {
703
+ nodeValue.clearPersist = () => prevClearPersist == null ? void 0 : prevClearPersist();
683
704
  }
684
- syncState.isPersistLoaded.set(true);
705
+ syncState$.isPersistLoaded.set(!(syncStateValue.numPendingLocalLoads > 0));
685
706
  }
686
707
  function syncObservable(obs$, syncOptionsOrSynced) {
687
708
  let syncOptions = syncOptionsOrSynced;
@@ -692,31 +713,59 @@ function syncObservable(obs$, syncOptionsOrSynced) {
692
713
  if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && (!obs$ || !node)) {
693
714
  throw new Error("[legend-state] syncObservable called with undefined observable");
694
715
  }
695
- syncOptions = {
696
- syncMode: "auto",
697
- ...observableSyncConfiguration,
698
- ...removeNullUndefined(syncOptions || {})
699
- };
716
+ syncOptions = deepMerge(
717
+ {
718
+ syncMode: "auto"
719
+ },
720
+ observableSyncConfiguration,
721
+ removeNullUndefined(syncOptions || {})
722
+ );
700
723
  const localState = {};
701
724
  let sync;
702
- const syncState = node.state = state.observable({
703
- isPersistLoaded: false,
704
- isLoaded: !syncOptions.get,
705
- isPersistEnabled: true,
706
- isSyncEnabled: true,
707
- clearPersist: void 0,
708
- sync: () => Promise.resolve(),
709
- getPendingChanges: () => localState.pendingChanges
710
- });
711
- loadLocal(obs$, syncOptions, syncState, localState);
712
- localState.pluginSync = syncObservableAdapter(syncOptions);
725
+ const syncState$ = state.syncState(obs$);
726
+ const syncStateValue = getNodeValue(getNode(syncState$));
727
+ allSyncStates.set(syncState$, node);
728
+ syncStateValue.getPendingChanges = () => localState.pendingChanges;
729
+ const onError = (error, getParams, source) => {
730
+ var _a;
731
+ syncState$.error.set(error);
732
+ (_a = syncOptions.onGetError) == null ? void 0 : _a.call(syncOptions, error, getParams, source);
733
+ };
734
+ loadLocal(obs$, syncOptions, syncState$, localState);
735
+ let isWaitingForLoad = !!syncOptions.get;
736
+ if (isWaitingForLoad) {
737
+ syncStateValue.numPendingRemoteLoads = (syncStateValue.numPendingRemoteLoads || 0) + 1;
738
+ }
739
+ syncState$.isLoaded.set(!syncState$.numPendingRemoteLoads.peek());
740
+ let isSynced = false;
741
+ const applyPending = (pending) => {
742
+ if (pending && !state.isEmpty(pending)) {
743
+ localState.isApplyingPending = true;
744
+ const keys = Object.keys(pending);
745
+ const changes = [];
746
+ for (let i = 0; i < keys.length; i++) {
747
+ const key = keys[i];
748
+ const path = key.split("/").filter((p2) => p2 !== "");
749
+ const { p, v, t } = pending[key];
750
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
751
+ }
752
+ const value = getNodeValue(node);
753
+ onObsChange(obs$, syncState$, localState, syncOptions, {
754
+ value,
755
+ isFromPersist: false,
756
+ isFromSync: false,
757
+ getPrevious: createPreviousHandler(value, changes),
758
+ changes
759
+ });
760
+ localState.isApplyingPending = false;
761
+ }
762
+ };
713
763
  if (syncOptions.get) {
714
- let isSynced = false;
715
764
  let isSubscribed = false;
716
765
  let unsubscribe = void 0;
717
766
  sync = async () => {
718
- var _a, _b;
719
- if (isSynced && state.shouldIgnoreUnobserved(node, sync)) {
767
+ var _a;
768
+ if (isSynced && (!getNodeValue(getNode(syncState$)).isSyncEnabled || state.shouldIgnoreUnobserved(node, sync))) {
720
769
  if (unsubscribe) {
721
770
  isSubscribed = false;
722
771
  unsubscribe();
@@ -726,9 +775,10 @@ function syncObservable(obs$, syncOptionsOrSynced) {
726
775
  }
727
776
  const lastSync = (_a = metadatas.get(obs$)) == null ? void 0 : _a.lastSync;
728
777
  const pending = localState.pendingChanges;
729
- const get = (_b = localState.pluginSync.get) == null ? void 0 : _b.bind(localState.pluginSync);
778
+ const get = syncOptions.get;
730
779
  if (get) {
731
780
  const runGet = () => {
781
+ var _a2;
732
782
  const onChange = async ({ value, mode, lastSync: lastSync2 }) => {
733
783
  mode = mode || syncOptions.mode || "set";
734
784
  if (value !== void 0) {
@@ -741,9 +791,11 @@ function syncObservable(obs$, syncOptionsOrSynced) {
741
791
  if (pending2) {
742
792
  let didChangeMetadata = false;
743
793
  Object.keys(pending2).forEach((key) => {
744
- const p = key.split("/").filter((p2) => p2 !== "");
794
+ const p = key.split("/").filter((k) => k !== "");
745
795
  const { v, t } = pending2[key];
746
796
  if (t.length === 0 || !value) {
797
+ const oldValue = clone(value);
798
+ pending2[key].p = oldValue;
747
799
  if (state.isObject(value) && state.isObject(v)) {
748
800
  Object.assign(value, v);
749
801
  } else {
@@ -756,6 +808,8 @@ function syncObservable(obs$, syncOptionsOrSynced) {
756
808
  delete pending2[key];
757
809
  didChangeMetadata = true;
758
810
  } else {
811
+ const oldValue = clone(value);
812
+ pending2[key].p = getValueAtPath(oldValue, p);
759
813
  value = state.setAtPath(
760
814
  value,
761
815
  p,
@@ -775,18 +829,24 @@ function syncObservable(obs$, syncOptionsOrSynced) {
775
829
  }
776
830
  }
777
831
  });
778
- if (didChangeMetadata) {
779
- updateMetadata(obs$, localState, syncState, syncOptions, {
832
+ if (didChangeMetadata && syncOptions.persist) {
833
+ updateMetadata(obs$, localState, syncState$, syncOptions, {
780
834
  pending: pending2
781
835
  });
782
836
  }
783
837
  }
784
838
  onChangeRemote(() => {
785
- if (mode === "assign" && state.isObject(value)) {
839
+ if (mode === "assign") {
786
840
  obs$.assign(value);
787
- } else if (mode === "append" && state.isArray(value)) {
841
+ } else if (mode === "append") {
842
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !state.isArray(value)) {
843
+ console.error("[legend-state] mode:append expects the value to be an array");
844
+ }
788
845
  obs$.push(...value);
789
- } else if (mode === "prepend" && state.isArray(value)) {
846
+ } else if (mode === "prepend") {
847
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !state.isArray(value)) {
848
+ console.error("[legend-state] mode:prepend expects the value to be an array");
849
+ }
790
850
  obs$.splice(0, 0, ...value);
791
851
  } else if (mode === "merge") {
792
852
  state.mergeIntoObservable(obs$, value);
@@ -796,77 +856,129 @@ function syncObservable(obs$, syncOptionsOrSynced) {
796
856
  });
797
857
  }
798
858
  if (lastSync2 && syncOptions.persist) {
799
- updateMetadata(obs$, localState, syncState, syncOptions, {
859
+ updateMetadata(obs$, localState, syncState$, syncOptions, {
800
860
  lastSync: lastSync2
801
861
  });
802
862
  }
803
863
  };
804
- get({
805
- state: syncState,
806
- value$: obs$,
807
- options: syncOptions,
808
- lastSync,
809
- dateModified: lastSync,
810
- onError: (error) => {
811
- var _a2;
812
- (_a2 = syncOptions.onGetError) == null ? void 0 : _a2.call(syncOptions, error);
813
- },
814
- onGet: () => {
815
- node.state.assign({
816
- isLoaded: true,
817
- error: void 0
818
- });
819
- },
820
- onChange
821
- });
864
+ if (node.activationState) {
865
+ node.activationState.onChange = onChange;
866
+ }
822
867
  if (!isSubscribed && syncOptions.subscribe) {
823
868
  isSubscribed = true;
824
869
  unsubscribe = syncOptions.subscribe({
825
870
  node,
826
871
  value$: obs$,
872
+ lastSync,
827
873
  update: (params) => {
828
- state.when(node.state.isLoaded, () => {
829
- params.mode || (params.mode = syncOptions.mode || "merge");
830
- onChange(params);
874
+ state.when(syncState$.isLoaded, () => {
875
+ state.when(waitFor || true, () => {
876
+ params.mode || (params.mode = syncOptions.mode || "merge");
877
+ onChange(params);
878
+ });
831
879
  });
832
880
  },
833
- refresh: () => state.when(node.state.isLoaded, sync)
881
+ refresh: () => state.when(syncState$.isLoaded, sync),
882
+ onError: (error) => onError(error, void 0, "subscribe")
834
883
  });
835
884
  }
885
+ const existingValue = getNodeValue(node);
886
+ const getParams = {
887
+ node,
888
+ value$: obs$,
889
+ value: state.isFunction(existingValue) || (existingValue == null ? void 0 : existingValue[symbolLinked]) ? void 0 : existingValue,
890
+ mode: syncOptions.mode,
891
+ refresh: sync,
892
+ options: syncOptions,
893
+ lastSync,
894
+ updateLastSync: (lastSync2) => getParams.lastSync = lastSync2,
895
+ onError: (error) => onError(error, getParams, "get")
896
+ };
897
+ let modeBeforeReset = void 0;
898
+ (_a2 = syncOptions.onBeforeGet) == null ? void 0 : _a2.call(syncOptions, {
899
+ value: getParams.value,
900
+ lastSync,
901
+ pendingChanges: pending && !state.isEmpty(pending) ? pending : void 0,
902
+ clearPendingChanges: async () => {
903
+ localState.pendingChanges = {};
904
+ await updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
905
+ pending: localState.pendingChanges
906
+ });
907
+ },
908
+ resetCache: () => {
909
+ var _a3;
910
+ modeBeforeReset = getParams.mode;
911
+ getParams.mode = "set";
912
+ return (_a3 = syncStateValue.clearPersist) == null ? void 0 : _a3.call(syncStateValue);
913
+ }
914
+ });
915
+ syncState$.assign({
916
+ numPendingGets: (syncStateValue.numPendingGets || 0) + 1,
917
+ isGetting: true
918
+ });
919
+ const got = runWithRetry({ retryNum: 0, retry: syncOptions.retry }, (retryEvent) => {
920
+ const params = getParams;
921
+ params.cancelRetry = retryEvent.cancelRetry;
922
+ params.retryNum = retryEvent.retryNum;
923
+ return get(params);
924
+ });
925
+ const numGets = node.numGets = (node.numGets || 0) + 1;
926
+ const handle = (value) => {
927
+ syncState$.numPendingGets.set((v) => v - 1);
928
+ if (isWaitingForLoad) {
929
+ isWaitingForLoad = false;
930
+ syncStateValue.numPendingRemoteLoads--;
931
+ }
932
+ if (numGets >= (node.getNumResolved || 0)) {
933
+ node.getNumResolved = node.numGets;
934
+ onChange({
935
+ value,
936
+ lastSync: getParams.lastSync,
937
+ mode: getParams.mode
938
+ });
939
+ }
940
+ if (modeBeforeReset) {
941
+ getParams.mode = modeBeforeReset;
942
+ modeBeforeReset = void 0;
943
+ }
944
+ syncState$.assign({
945
+ isLoaded: syncStateValue.numPendingRemoteLoads < 1,
946
+ error: void 0,
947
+ isGetting: syncStateValue.numPendingGets > 0
948
+ });
949
+ };
950
+ if (state.isPromise(got)) {
951
+ got.then(handle);
952
+ } else {
953
+ handle(got);
954
+ }
836
955
  };
837
- runGet();
956
+ const { waitFor } = syncOptions;
957
+ if (waitFor) {
958
+ if (node.activationState) {
959
+ node.activationState.waitFor = void 0;
960
+ }
961
+ state.whenReady(waitFor, () => state.trackSelector(runGet, sync));
962
+ } else {
963
+ state.trackSelector(runGet, sync);
964
+ }
838
965
  } else {
839
- node.state.assign({
966
+ syncState$.assign({
840
967
  isLoaded: true,
841
968
  error: void 0
842
969
  });
843
970
  }
844
971
  if (!isSynced) {
845
972
  isSynced = true;
846
- await state.when(() => syncState.isLoaded.get() || syncOptions.allowSetIfGetError && syncState.error.get());
847
- if (pending && !state.isEmpty(pending)) {
848
- localState.isApplyingPending = true;
849
- const keys = Object.keys(pending);
850
- const changes = [];
851
- for (let i = 0; i < keys.length; i++) {
852
- const key = keys[i];
853
- const path = key.split("/").filter((p2) => p2 !== "");
854
- const { p, v, t } = pending[key];
855
- changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
856
- }
857
- const value = getNodeValue(node);
858
- onObsChange(obs$, syncState, localState, syncOptions, {
859
- value,
860
- loading: false,
861
- remote: false,
862
- getPrevious: createPreviousHandler(value, changes),
863
- changes
864
- });
865
- localState.isApplyingPending = false;
866
- }
973
+ await state.when(syncState$.isLoaded);
974
+ applyPending(pending);
867
975
  }
868
976
  };
869
- syncState.assign({ sync });
977
+ syncStateValue.sync = sync;
978
+ } else {
979
+ if (!isSynced) {
980
+ applyPending(localState.pendingChanges);
981
+ }
870
982
  }
871
983
  const onAllPersistLoaded = () => {
872
984
  var _a, _b;
@@ -885,77 +997,27 @@ function syncObservable(obs$, syncOptionsOrSynced) {
885
997
  }
886
998
  if ((syncOptions == null ? void 0 : syncOptions.set) || (syncOptions == null ? void 0 : syncOptions.persist)) {
887
999
  obs$.onChange(
888
- onObsChange.bind(this, obs$, syncState, localState, syncOptions)
1000
+ onObsChange.bind(this, obs$, syncState$, localState, syncOptions)
889
1001
  );
890
1002
  }
891
1003
  });
892
- return syncState;
1004
+ return syncState$;
893
1005
  }
894
- var { getProxy, globalState: globalState2, runWithRetry, symbolLinked: symbolLinked2, setNodeValue, getNodeValue: getNodeValue2 } = state.internal;
1006
+ var { getProxy, globalState: globalState2, setNodeValue, getNodeValue: getNodeValue2 } = state.internal;
895
1007
  function enableActivateSyncedNode() {
896
1008
  globalState2.activateSyncedNode = function activateSyncedNode(node, newValue) {
897
1009
  const obs$ = getProxy(node);
898
1010
  if (node.activationState) {
899
- const { get, initial, set, retry } = node.activationState;
900
- let onChange = void 0;
901
- const pluginRemote = {};
1011
+ const {
1012
+ get: getOrig,
1013
+ initial,
1014
+ set,
1015
+ onChange
1016
+ } = node.activationState;
902
1017
  let promiseReturn = void 0;
903
- let syncState;
904
- const refresh = () => syncState == null ? void 0 : syncState.sync();
905
- if (get) {
906
- pluginRemote.get = (params) => {
907
- var _a;
908
- onChange = params.onChange;
909
- const updateLastSync = (lastSync) => params.lastSync = lastSync;
910
- const existingValue = getNodeValue2(node);
911
- const value = runWithRetry(node, { attemptNum: 0, retry: retry || ((_a = params.options) == null ? void 0 : _a.retry) }, () => {
912
- const paramsToGet = {
913
- value: state.isFunction(existingValue) || (existingValue == null ? void 0 : existingValue[symbolLinked2]) ? void 0 : existingValue,
914
- lastSync: params.lastSync,
915
- updateLastSync,
916
- mode: params.mode,
917
- refresh
918
- };
919
- const ret = get(paramsToGet);
920
- params.mode = paramsToGet.mode;
921
- return ret;
922
- });
923
- promiseReturn = value;
924
- return value;
925
- };
926
- }
927
- if (set) {
928
- pluginRemote.set = async (params) => {
929
- var _a, _b;
930
- if ((_a = node.state) == null ? void 0 : _a.isLoaded.get()) {
931
- const retryAttempts = { attemptNum: 0, retry: retry || ((_b = params.options) == null ? void 0 : _b.retry) };
932
- return runWithRetry(node, retryAttempts, async (retryEvent) => {
933
- let changes = {};
934
- let maxModified = 0;
935
- if (!node.state.isLoaded.peek()) {
936
- await state.whenReady(node.state.isLoaded);
937
- }
938
- const cancelRetry = () => {
939
- retryEvent.cancel = true;
940
- };
941
- await set({
942
- ...params,
943
- node,
944
- update: (params2) => {
945
- const { value, lastSync } = params2;
946
- maxModified = Math.max(lastSync || 0, maxModified);
947
- changes = state.mergeIntoObservable(changes, value);
948
- },
949
- retryNum: retryAttempts.attemptNum,
950
- cancelRetry,
951
- refresh,
952
- fromSubscribe: false
953
- });
954
- return { changes, lastSync: maxModified || void 0 };
955
- });
956
- }
957
- };
958
- }
1018
+ const get = getOrig ? (params) => {
1019
+ return promiseReturn = getOrig(params);
1020
+ } : void 0;
959
1021
  const nodeVal = getNodeValue2(node);
960
1022
  if (promiseReturn !== void 0) {
961
1023
  newValue = promiseReturn;
@@ -965,7 +1027,7 @@ function enableActivateSyncedNode() {
965
1027
  newValue = initial;
966
1028
  }
967
1029
  setNodeValue(node, promiseReturn ? void 0 : newValue);
968
- syncState = syncObservable(obs$, { ...node.activationState, ...pluginRemote });
1030
+ syncObservable(obs$, { ...node.activationState, get, set });
969
1031
  return { update: onChange, value: newValue };
970
1032
  } else {
971
1033
  let update = void 0;
@@ -1004,9 +1066,6 @@ function installPersistActivateNode() {
1004
1066
  }
1005
1067
 
1006
1068
  // sync.ts
1007
- function isInRemoteChange() {
1008
- return state.internal.globalState.isLoadingRemote;
1009
- }
1010
1069
  var internal3 = {
1011
1070
  observableSyncConfiguration
1012
1071
  };
@@ -1016,7 +1075,6 @@ exports.configureObservableSync = configureObservableSync;
1016
1075
  exports.deepEqual = deepEqual;
1017
1076
  exports.diffObjects = diffObjects;
1018
1077
  exports.internal = internal3;
1019
- exports.isInRemoteChange = isInRemoteChange;
1020
1078
  exports.mapSyncPlugins = mapSyncPlugins;
1021
1079
  exports.onChangeRemote = onChangeRemote;
1022
1080
  exports.removeNullUndefined = removeNullUndefined;