@legendapp/state 3.0.0-beta.4 → 3.0.0-beta.41

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 (79) hide show
  1. package/.DS_Store +0 -0
  2. package/README.md +2 -2
  3. package/config/enableReactComponents.js +3 -1
  4. package/config/enableReactComponents.mjs +3 -1
  5. package/config/enableReactTracking.d.mts +2 -1
  6. package/config/enableReactTracking.d.ts +2 -1
  7. package/config/enableReactTracking.js +32 -13
  8. package/config/enableReactTracking.mjs +32 -13
  9. package/index.d.mts +46 -8
  10. package/index.d.ts +46 -8
  11. package/index.js +267 -75
  12. package/index.mjs +267 -75
  13. package/package.json +35 -1
  14. package/persist-plugins/async-storage.js +17 -9
  15. package/persist-plugins/async-storage.mjs +17 -9
  16. package/persist-plugins/expo-sqlite.d.mts +19 -0
  17. package/persist-plugins/expo-sqlite.d.ts +19 -0
  18. package/persist-plugins/expo-sqlite.js +72 -0
  19. package/persist-plugins/expo-sqlite.mjs +69 -0
  20. package/persist-plugins/indexeddb.js +13 -3
  21. package/persist-plugins/indexeddb.mjs +13 -3
  22. package/react-native.d.mts +4 -0
  23. package/react-native.d.ts +4 -0
  24. package/react-native.js +53 -0
  25. package/react-native.mjs +40 -0
  26. package/react-reactive/Components.d.mts +19 -0
  27. package/react-reactive/Components.d.ts +19 -0
  28. package/react-reactive/Components.js +53 -0
  29. package/react-reactive/Components.mjs +40 -0
  30. package/react-reactive/enableReactComponents.d.mts +3 -2
  31. package/react-reactive/enableReactComponents.d.ts +3 -2
  32. package/react-reactive/enableReactComponents.js +10 -3
  33. package/react-reactive/enableReactComponents.mjs +10 -3
  34. package/react-reactive/enableReactNativeComponents.d.mts +3 -20
  35. package/react-reactive/enableReactNativeComponents.d.ts +3 -20
  36. package/react-reactive/enableReactNativeComponents.js +8 -3
  37. package/react-reactive/enableReactNativeComponents.mjs +8 -3
  38. package/react-reactive/enableReactive.js +10 -3
  39. package/react-reactive/enableReactive.mjs +10 -3
  40. package/react-reactive/enableReactive.native.js +8 -3
  41. package/react-reactive/enableReactive.native.mjs +8 -3
  42. package/react-reactive/enableReactive.web.js +8 -3
  43. package/react-reactive/enableReactive.web.mjs +8 -3
  44. package/react-web.d.mts +7 -0
  45. package/react-web.d.ts +7 -0
  46. package/react-web.js +39 -0
  47. package/react-web.mjs +37 -0
  48. package/react.d.mts +59 -26
  49. package/react.d.ts +59 -26
  50. package/react.js +136 -87
  51. package/react.mjs +135 -89
  52. package/sync-plugins/crud.d.mts +24 -9
  53. package/sync-plugins/crud.d.ts +24 -9
  54. package/sync-plugins/crud.js +267 -123
  55. package/sync-plugins/crud.mjs +268 -124
  56. package/sync-plugins/firebase.d.mts +7 -3
  57. package/sync-plugins/firebase.d.ts +7 -3
  58. package/sync-plugins/firebase.js +214 -64
  59. package/sync-plugins/firebase.mjs +215 -65
  60. package/sync-plugins/keel.d.mts +12 -13
  61. package/sync-plugins/keel.d.ts +12 -13
  62. package/sync-plugins/keel.js +60 -52
  63. package/sync-plugins/keel.mjs +61 -48
  64. package/sync-plugins/supabase.d.mts +10 -5
  65. package/sync-plugins/supabase.d.ts +10 -5
  66. package/sync-plugins/supabase.js +90 -33
  67. package/sync-plugins/supabase.mjs +91 -34
  68. package/sync-plugins/tanstack-query.d.mts +3 -3
  69. package/sync-plugins/tanstack-query.d.ts +3 -3
  70. package/sync-plugins/tanstack-query.js +1 -1
  71. package/sync-plugins/tanstack-query.mjs +1 -1
  72. package/sync.d.mts +17 -8
  73. package/sync.d.ts +17 -8
  74. package/sync.js +448 -307
  75. package/sync.mjs +446 -307
  76. package/trace.js +5 -6
  77. package/trace.mjs +5 -6
  78. package/types/reactive-native.d.ts +19 -0
  79. package/types/reactive-web.d.ts +7 -0
package/sync.js CHANGED
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var state = require('@legendapp/state');
4
+ var sync = require('@legendapp/state/sync');
4
5
 
5
6
  // src/sync/configureObservableSync.ts
6
7
  var observableSyncConfiguration = {};
@@ -35,12 +36,12 @@ function diffObjects(obj1, obj2, deep = false) {
35
36
  return diff;
36
37
  }
37
38
  function deepEqual(a, b, ignoreFields, nullVsUndefined) {
38
- if (a === b) {
39
+ if (a === b)
39
40
  return true;
40
- }
41
- if (state.isNullOrUndefined(a) !== state.isNullOrUndefined(b)) {
41
+ if (state.isNullOrUndefined(a) !== state.isNullOrUndefined(b))
42
42
  return false;
43
- }
43
+ if (!state.isObject(a) || !state.isObject(b))
44
+ return a === b;
44
45
  if (nullVsUndefined) {
45
46
  a = removeNullUndefined(
46
47
  a,
@@ -53,8 +54,18 @@ function deepEqual(a, b, ignoreFields, nullVsUndefined) {
53
54
  true
54
55
  );
55
56
  }
56
- const replacer = ignoreFields ? (key, value) => ignoreFields.includes(key) ? void 0 : value : void 0;
57
- return JSON.stringify(a, replacer) === JSON.stringify(b, replacer);
57
+ const keysA = Object.keys(a).filter((key) => !(ignoreFields == null ? void 0 : ignoreFields.includes(key)));
58
+ const keysB = Object.keys(b).filter((key) => !(ignoreFields == null ? void 0 : ignoreFields.includes(key)));
59
+ if (keysA.length !== keysB.length)
60
+ return false;
61
+ return keysA.every((key) => {
62
+ if (!Object.prototype.hasOwnProperty.call(b, key))
63
+ return false;
64
+ if (state.isDate(a[key]) && state.isDate(b[key])) {
65
+ return a[key].getTime() === b[key].getTime();
66
+ }
67
+ return deepEqual(a[key], b[key], ignoreFields, nullVsUndefined);
68
+ });
58
69
  }
59
70
  function combineTransforms(...transforms) {
60
71
  return {
@@ -161,38 +172,50 @@ function createRetryTimeout(retryOptions, retryNum, fn) {
161
172
  }
162
173
  }
163
174
  var mapRetryTimeouts = /* @__PURE__ */ new Map();
164
- function runWithRetry(state, retryOptions, fn, onError) {
175
+ function runWithRetry(state, retryOptions, retryId, fn) {
165
176
  try {
166
177
  let value = fn(state);
167
178
  if (isPromise(value) && retryOptions) {
168
179
  let timeoutRetry;
169
- if (mapRetryTimeouts.has(state.node)) {
170
- clearTimeout(mapRetryTimeouts.get(state.node));
180
+ if (mapRetryTimeouts.has(retryId)) {
181
+ clearTimeout(mapRetryTimeouts.get(retryId));
182
+ mapRetryTimeouts.delete(retryId);
171
183
  }
184
+ const clearRetryState = () => {
185
+ if (timeoutRetry !== void 0) {
186
+ clearTimeout(timeoutRetry);
187
+ timeoutRetry = void 0;
188
+ }
189
+ mapRetryTimeouts.delete(retryId);
190
+ };
172
191
  return new Promise((resolve, reject) => {
173
192
  const run = () => {
174
193
  value.then((val) => {
194
+ state.retryNum = 0;
195
+ clearRetryState();
175
196
  resolve(val);
176
197
  }).catch((error) => {
177
- state.retryNum++;
178
- if (timeoutRetry) {
198
+ if (timeoutRetry !== void 0) {
179
199
  clearTimeout(timeoutRetry);
200
+ timeoutRetry = void 0;
180
201
  }
181
- if (onError) {
182
- onError(error, state);
202
+ state.retryNum++;
203
+ if (state.cancelRetry) {
204
+ clearRetryState();
205
+ reject(error);
206
+ return;
183
207
  }
184
- if (!state.cancelRetry) {
185
- const timeout = createRetryTimeout(retryOptions, state.retryNum, () => {
186
- value = fn(state);
187
- run();
188
- });
189
- if (timeout === false) {
190
- state.cancelRetry = true;
191
- reject(error);
192
- } else {
193
- mapRetryTimeouts.set(state.node, timeout);
194
- timeoutRetry = timeout;
195
- }
208
+ const timeout = createRetryTimeout(retryOptions, state.retryNum, () => {
209
+ value = fn(state);
210
+ run();
211
+ });
212
+ if (timeout === false) {
213
+ state.cancelRetry = true;
214
+ clearRetryState();
215
+ reject(error);
216
+ } else {
217
+ timeoutRetry = timeout;
218
+ mapRetryTimeouts.set(retryId, timeout);
196
219
  }
197
220
  });
198
221
  };
@@ -201,6 +224,7 @@ function runWithRetry(state, retryOptions, fn, onError) {
201
224
  }
202
225
  return value;
203
226
  } catch (error) {
227
+ mapRetryTimeouts.delete(retryId);
204
228
  return Promise.reject(error);
205
229
  }
206
230
  }
@@ -210,9 +234,34 @@ async function waitForSet(waitForSet2, changes, value, params = {}) {
210
234
  await state.when(waitFn);
211
235
  }
212
236
  }
237
+ var { clone } = state.internal;
238
+ function createRevertChanges(obs$, changes) {
239
+ return () => {
240
+ const previous = state.applyChanges(
241
+ clone(obs$.peek()),
242
+ changes,
243
+ /*applyPrevious*/
244
+ true
245
+ );
246
+ sync.onChangeRemote(() => {
247
+ obs$.set(previous);
248
+ });
249
+ };
250
+ }
213
251
 
214
252
  // src/sync/syncObservable.ts
215
- var { clone, deepMerge, getNode, getNodeValue, getValueAtPath, globalState, symbolLinked, createPreviousHandler } = state.internal;
253
+ var {
254
+ clone: clone2,
255
+ createPreviousHandler,
256
+ deepMerge,
257
+ getNode,
258
+ getNodeValue,
259
+ getValueAtPath,
260
+ globalState,
261
+ registerMiddleware,
262
+ symbolLinked,
263
+ setNodeValue
264
+ } = state.internal;
216
265
  var mapSyncPlugins = /* @__PURE__ */ new WeakMap();
217
266
  var allSyncStates = /* @__PURE__ */ new Map();
218
267
  var metadatas = /* @__PURE__ */ new WeakMap();
@@ -223,7 +272,7 @@ function parseLocalConfig(config) {
223
272
  function doInOrder(arg1, arg2) {
224
273
  return state.isPromise(arg1) ? arg1.then(arg2) : arg2(arg1);
225
274
  }
226
- function onChangeRemote(cb) {
275
+ function onChangeRemote2(cb) {
227
276
  state.endBatch(true);
228
277
  globalState.isLoadingRemote = true;
229
278
  state.beginBatch();
@@ -251,7 +300,7 @@ function transformLoadData(value, { transform }, doUserTransform, method) {
251
300
  }
252
301
  return value;
253
302
  }
254
- async function updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata) {
303
+ async function updateMetadataImmediate(value$, localState, syncState$, syncOptions, newMetadata) {
255
304
  const saves = Array.from(promisesLocalSaves);
256
305
  if (saves.length > 0) {
257
306
  await Promise.all(saves);
@@ -266,7 +315,7 @@ async function updateMetadataImmediate(value$, localState, syncState2, syncOptio
266
315
  await pluginPersist.setMetadata(table, metadata, config);
267
316
  }
268
317
  if (lastSync) {
269
- syncState2.assign({
318
+ syncState$.assign({
270
319
  lastSync
271
320
  });
272
321
  }
@@ -275,10 +324,10 @@ function updateMetadata(value$, localState, syncState2, syncOptions, newMetadata
275
324
  if (localState.timeoutSaveMetadata) {
276
325
  clearTimeout(localState.timeoutSaveMetadata);
277
326
  }
278
- localState.timeoutSaveMetadata = setTimeout(
279
- () => updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata),
280
- 0
281
- );
327
+ metadatas.set(value$, { ...metadatas.get(value$) || {}, ...newMetadata });
328
+ localState.timeoutSaveMetadata = setTimeout(() => {
329
+ updateMetadataImmediate(value$, localState, syncState2, syncOptions, metadatas.get(value$));
330
+ }, 0);
282
331
  }
283
332
  var _queuedChanges = [];
284
333
  var _queuedRemoteChanges = /* @__PURE__ */ new Map();
@@ -292,13 +341,35 @@ function mergeChanges(changes) {
292
341
  const existing = changesByPath.get(pathStr);
293
342
  if (existing) {
294
343
  if (change.valueAtPath === existing.prevAtPath) {
295
- changesOut.splice(changesOut.indexOf(change), 1);
344
+ const idx = changesOut.indexOf(existing);
345
+ if (idx >= 0) {
346
+ changesOut.splice(idx, 1);
347
+ }
348
+ changesByPath.delete(pathStr);
296
349
  } else {
297
350
  existing.valueAtPath = change.valueAtPath;
351
+ existing.pathTypes = change.pathTypes;
298
352
  }
299
353
  } else {
300
- changesByPath.set(pathStr, change);
301
- changesOut.push(change);
354
+ let found = false;
355
+ for (let u = 0; u < change.path.length; u++) {
356
+ const path = change.path.slice(0, u).join("/");
357
+ if (changesByPath.has(path)) {
358
+ const remaining = change.path.slice(u);
359
+ state.setAtPath(
360
+ changesByPath.get(path).valueAtPath,
361
+ remaining,
362
+ change.pathTypes.slice(u),
363
+ change.valueAtPath
364
+ );
365
+ found = true;
366
+ break;
367
+ }
368
+ }
369
+ if (!found) {
370
+ changesByPath.set(pathStr, change);
371
+ changesOut.push(change);
372
+ }
302
373
  }
303
374
  }
304
375
  return changesOut;
@@ -580,7 +651,7 @@ async function doChangeRemote(changeInfo) {
580
651
  if (waitForSetParam) {
581
652
  await waitForSet(waitForSetParam, changesRemote, obs$.peek());
582
653
  }
583
- let value = clone(obs$.peek());
654
+ let value = clone2(obs$.peek());
584
655
  const transformSave = (_a = syncOptions == null ? void 0 : syncOptions.transform) == null ? void 0 : _a.save;
585
656
  if (transformSave) {
586
657
  value = transformSave(value);
@@ -593,33 +664,41 @@ async function doChangeRemote(changeInfo) {
593
664
  onBeforeSet == null ? void 0 : onBeforeSet(beforeSetParams);
594
665
  if (!beforeSetParams.cancel) {
595
666
  let updateResult = void 0;
596
- let errorHandled = false;
597
- const onError = (error, retryParams) => {
667
+ let lastErrorHandled;
668
+ const onSetError = (error, params, noThrow) => {
598
669
  var _a2;
599
- state$.error.set(error);
600
- if (!errorHandled) {
601
- (_a2 = syncOptions.onError) == null ? void 0 : _a2.call(syncOptions, error, {
602
- setParams,
603
- source: "set",
604
- value$: obs$,
605
- retryParams
606
- });
670
+ if (lastErrorHandled !== error) {
671
+ if (!params) {
672
+ params = {
673
+ setParams,
674
+ source: "set",
675
+ type: "set",
676
+ input: value,
677
+ retry: setParams,
678
+ revert: createRevertChanges(setParams.value$, setParams.changes)
679
+ };
680
+ }
681
+ state$.error.set(error);
682
+ (_a2 = syncOptions.onError) == null ? void 0 : _a2.call(syncOptions, error, params);
683
+ lastErrorHandled = error;
684
+ if (!noThrow) {
685
+ throw error;
686
+ }
607
687
  }
608
- errorHandled = true;
609
688
  };
610
689
  const setParams = {
611
690
  node,
612
691
  value$: obs$,
613
692
  changes: changesRemote,
614
693
  value,
615
- onError,
694
+ onError: onSetError,
616
695
  update: (params) => {
617
696
  if (updateResult) {
618
- const { value: value2, lastSync, mode } = params;
697
+ const { value: value2, mode, changes } = params;
619
698
  updateResult = {
620
- lastSync: Math.max(updateResult.lastSync || 0, lastSync || 0),
621
699
  value: deepMerge(updateResult.value, value2),
622
- mode
700
+ mode,
701
+ changes: changes ? [...updateResult.changes || [], ...changes] : updateResult.changes
623
702
  };
624
703
  } else {
625
704
  updateResult = params;
@@ -629,26 +708,23 @@ async function doChangeRemote(changeInfo) {
629
708
  retryNum: 0,
630
709
  cancelRetry: false
631
710
  };
632
- const savedPromise = runWithRetry(
633
- setParams,
634
- syncOptions.retry,
635
- async () => {
636
- return syncOptions.set(setParams);
637
- },
638
- onError
639
- );
711
+ const savedPromise = runWithRetry(setParams, syncOptions.retry, node, async () => {
712
+ return syncOptions.set(setParams);
713
+ });
640
714
  let didError = false;
641
715
  if (state.isPromise(savedPromise)) {
642
716
  await savedPromise.catch((error) => {
643
717
  didError = true;
644
718
  if (!syncOptions.retry) {
645
- onError(error);
719
+ onSetError(error, void 0, true);
646
720
  }
647
721
  });
648
722
  }
649
- if (!didError) {
650
- const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
651
- const { value: changes, lastSync } = updateResult || {};
723
+ if (!didError || (updateResult == null ? void 0 : updateResult.changes)) {
724
+ const { value: updateValue, changes: updateChanges = changesRemote } = updateResult || {};
725
+ const pathStrs = Array.from(
726
+ new Set(updateChanges.map((change) => change.pathStr))
727
+ );
652
728
  if (pathStrs.length > 0) {
653
729
  let transformedChanges = void 0;
654
730
  const metadata = {};
@@ -665,18 +741,15 @@ async function doChangeRemote(changeInfo) {
665
741
  delete pending[pathStr];
666
742
  }
667
743
  }
668
- if (lastSync) {
669
- metadata.lastSync = lastSync;
670
- }
671
744
  }
672
- if (changes && !state.isEmpty(changes)) {
673
- transformedChanges = transformLoadData(changes, syncOptions, false, "set");
745
+ if (updateValue && !state.isEmpty(updateValue)) {
746
+ transformedChanges = transformLoadData(updateValue, syncOptions, false, "set");
674
747
  }
675
748
  if (transformedChanges !== void 0) {
676
749
  if (state.isPromise(transformedChanges)) {
677
750
  transformedChanges = await transformedChanges;
678
751
  }
679
- onChangeRemote(() => state.mergeIntoObservable(obs$, transformedChanges));
752
+ onChangeRemote2(() => state.mergeIntoObservable(obs$, transformedChanges));
680
753
  }
681
754
  if (saveLocal) {
682
755
  if (shouldSaveMetadata && !state.isEmpty(metadata)) {
@@ -743,9 +816,20 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
743
816
  await state.when(initialized$);
744
817
  }
745
818
  if (persistPlugin.loadTable) {
746
- const promise = persistPlugin.loadTable(table, config);
747
- if (promise) {
748
- await promise;
819
+ try {
820
+ const promise = persistPlugin.loadTable(table, config);
821
+ if (promise) {
822
+ await promise;
823
+ }
824
+ } catch (err) {
825
+ if (process.env.NODE_ENV === "development") {
826
+ console.error(
827
+ "[legend-state] Error loading local cache. This would be a crashing error in production.",
828
+ err
829
+ );
830
+ } else {
831
+ throw err;
832
+ }
749
833
  }
750
834
  }
751
835
  const prevValue = getNodeValue(node);
@@ -764,12 +848,14 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
764
848
  if (state.isPromise(value)) {
765
849
  value = await value;
766
850
  }
851
+ node.root.isLoadingLocal = true;
767
852
  state.internal.globalState.isLoadingLocal = true;
768
853
  if (value === null && (!prevValue || prevValue[symbolLinked])) {
769
854
  value$.set(value);
770
855
  } else {
771
856
  state.mergeIntoObservable(value$, value);
772
857
  }
858
+ node.root.isLoadingLocal = false;
773
859
  state.internal.globalState.isLoadingLocal = false;
774
860
  }
775
861
  syncStateValue.numPendingLocalLoads--;
@@ -781,6 +867,11 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
781
867
  ].filter(Boolean)
782
868
  );
783
869
  } else {
870
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && persist && persist.plugin && !Object.hasOwn(persist, "name")) {
871
+ console.warn(
872
+ "[legend-state] Trying to syncObservable without `name` defined. Please include a `name` property in the `persist` configuration."
873
+ );
874
+ }
784
875
  nodeValue.resetPersistence = () => prevResetPersistence == null ? void 0 : prevResetPersistence();
785
876
  }
786
877
  nodeValue.clearPersist = nodeValue.resetPersistence;
@@ -808,14 +899,24 @@ function syncObservable(obs$, syncOptionsOrSynced) {
808
899
  const syncStateValue = getNodeValue(getNode(syncState$));
809
900
  allSyncStates.set(syncState$, node);
810
901
  syncStateValue.getPendingChanges = () => localState.pendingChanges;
811
- let errorHandled = false;
812
- const onGetError = (error, params) => {
902
+ let lastErrorHandled;
903
+ const onGetError = (error, params, noThrow) => {
813
904
  var _a;
814
- syncState$.error.set(error);
815
- if (!errorHandled) {
816
- (_a = syncOptions.onError) == null ? void 0 : _a.call(syncOptions, error, { ...params, value$: obs$ });
905
+ if (lastErrorHandled !== error) {
906
+ if (!params) {
907
+ params = {
908
+ source: "get",
909
+ type: "get",
910
+ retry: params
911
+ };
912
+ }
913
+ syncState$.error.set(error);
914
+ (_a = syncOptions.onError) == null ? void 0 : _a.call(syncOptions, error, params);
915
+ lastErrorHandled = error;
916
+ if (!noThrow) {
917
+ throw error;
918
+ }
817
919
  }
818
- errorHandled = true;
819
920
  };
820
921
  loadLocal(obs$, syncOptions, syncState$, localState);
821
922
  let isWaitingForLoad = !!syncOptions.get;
@@ -825,33 +926,39 @@ function syncObservable(obs$, syncOptionsOrSynced) {
825
926
  syncState$.isLoaded.set(!syncState$.numPendingRemoteLoads.peek());
826
927
  let isSynced = false;
827
928
  let isSubscribed = false;
929
+ let isApplyingPendingAfterSync = false;
828
930
  let unsubscribe = void 0;
829
931
  const applyPending = (pending) => {
830
932
  if (pending && !state.isEmpty(pending)) {
831
- localState.isApplyingPending = true;
832
933
  const keys = Object.keys(pending);
934
+ const value = getNodeValue(node);
833
935
  const changes = [];
834
936
  for (let i = 0; i < keys.length; i++) {
835
937
  const key = keys[i];
836
938
  const path = key.split("/").filter((p2) => p2 !== "");
837
- const { p, v, t } = pending[key];
838
- changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
939
+ const { p, t, v } = pending[key];
940
+ const valueAtPath = getValueAtPath(value, path);
941
+ if (isApplyingPendingAfterSync || !deepEqual(valueAtPath, v)) {
942
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
943
+ }
944
+ }
945
+ if (changes.length > 0) {
946
+ localState.isApplyingPending = true;
947
+ onObsChange(obs$, syncState$, localState, syncOptions, {
948
+ value,
949
+ isFromPersist: false,
950
+ isFromSync: false,
951
+ getPrevious: createPreviousHandler(value, changes),
952
+ changes
953
+ });
954
+ localState.isApplyingPending = false;
839
955
  }
840
- const value = getNodeValue(node);
841
- onObsChange(obs$, syncState$, localState, syncOptions, {
842
- value,
843
- isFromPersist: false,
844
- isFromSync: false,
845
- getPrevious: createPreviousHandler(value, changes),
846
- changes
847
- });
848
- localState.isApplyingPending = false;
849
956
  }
850
957
  };
851
958
  const { get, subscribe } = syncOptions;
852
959
  if (get || subscribe) {
853
- sync = async () => {
854
- var _a;
960
+ const callSync = () => sync();
961
+ sync = async (options) => {
855
962
  if (isSynced && (!getNodeValue(getNode(syncState$)).isSyncEnabled || state.shouldIgnoreUnobserved(node, sync))) {
856
963
  if (unsubscribe) {
857
964
  isSubscribed = false;
@@ -860,243 +967,262 @@ function syncObservable(obs$, syncOptionsOrSynced) {
860
967
  }
861
968
  return;
862
969
  }
863
- const lastSync = (_a = metadatas.get(obs$)) == null ? void 0 : _a.lastSync;
970
+ const metadata = metadatas.get(obs$);
971
+ if (metadata && (options == null ? void 0 : options.resetLastSync)) {
972
+ metadata.lastSync = void 0;
973
+ syncState$.lastSync.set(void 0);
974
+ }
975
+ const lastSync = metadata == null ? void 0 : metadata.lastSync;
864
976
  const pending = localState.pendingChanges;
865
- if (get || subscribe) {
866
- const { waitFor } = syncOptions;
867
- const runGet = () => {
977
+ const { waitFor } = syncOptions;
978
+ const runGet = () => {
979
+ var _a;
980
+ const onChange = async ({ value, mode, lastSync: lastSync2 }) => {
868
981
  var _a2;
869
- const onChange = async ({ value, mode, lastSync: lastSync2 }) => {
870
- mode = mode || syncOptions.mode || "set";
871
- if (value !== void 0) {
872
- value = transformLoadData(value, syncOptions, true, "get");
873
- if (state.isPromise(value)) {
874
- value = await value;
875
- }
876
- const pending2 = localState.pendingChanges;
877
- const currentValue = obs$.peek();
878
- if (pending2) {
879
- let didChangeMetadata = false;
880
- Object.keys(pending2).forEach((key) => {
881
- const p = key.split("/").filter((k) => k !== "");
882
- const { v, t } = pending2[key];
883
- if (t.length === 0 || !value) {
884
- const oldValue = clone(value);
885
- pending2[key].p = oldValue;
886
- if (state.isObject(value) && state.isObject(v)) {
887
- Object.assign(value, v);
888
- } else {
889
- value = v;
890
- }
891
- } else if (value[p[0]] !== void 0) {
892
- const curValue = getValueAtPath(currentValue, p);
893
- const newValue = getValueAtPath(value, p);
894
- if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
895
- delete pending2[key];
896
- didChangeMetadata = true;
897
- } else {
898
- const oldValue = clone(value);
899
- pending2[key].p = getValueAtPath(oldValue, p);
900
- value = state.setAtPath(
901
- value,
902
- p,
903
- t,
904
- v,
905
- "merge",
906
- obs$.peek(),
907
- (path, value2) => {
908
- delete pending2[key];
909
- pending2[path.join("/")] = {
910
- p: null,
911
- v: value2,
912
- t: t.slice(0, path.length)
913
- };
914
- }
915
- );
916
- }
917
- }
918
- });
919
- if (didChangeMetadata && syncOptions.persist) {
920
- updateMetadata(obs$, localState, syncState$, syncOptions, {
921
- pending: pending2
922
- });
923
- }
924
- }
925
- onChangeRemote(() => {
926
- if (state.isPlainObject(value)) {
927
- value = state.ObservableHint.plain(value);
928
- }
929
- if (mode === "assign") {
930
- obs$.assign(value);
931
- } else if (mode === "append") {
932
- if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !state.isArray(value)) {
933
- console.error("[legend-state] mode:append expects the value to be an array");
982
+ mode = mode || syncOptions.mode || "set";
983
+ if (value !== void 0) {
984
+ value = transformLoadData(value, syncOptions, true, "get");
985
+ if (state.isPromise(value)) {
986
+ value = await value;
987
+ }
988
+ const pending2 = localState.pendingChanges;
989
+ const currentValue = obs$.peek();
990
+ if (pending2) {
991
+ let didChangeMetadata = false;
992
+ Object.keys(pending2).forEach((key) => {
993
+ const p = key.split("/").filter((k) => k !== "");
994
+ const { v, t } = pending2[key];
995
+ if (t.length === 0 || !value) {
996
+ const oldValue = clone2(value);
997
+ pending2[key].p = key ? oldValue[key] : oldValue;
998
+ if (state.isObject(value) && state.isObject(v)) {
999
+ Object.assign(value, key ? { [key]: v } : v);
1000
+ } else if (!key) {
1001
+ value = v;
934
1002
  }
935
- obs$.push(...value);
936
- } else if (mode === "prepend") {
937
- if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !state.isArray(value)) {
938
- console.error("[legend-state] mode:prepend expects the value to be an array");
1003
+ } else if (value[p[0]] !== void 0) {
1004
+ const curValue = getValueAtPath(currentValue, p);
1005
+ const newValue = getValueAtPath(value, p);
1006
+ if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
1007
+ delete pending2[key];
1008
+ didChangeMetadata = true;
1009
+ } else {
1010
+ const oldValue = clone2(value);
1011
+ pending2[key].p = getValueAtPath(oldValue, p);
1012
+ didChangeMetadata = true;
1013
+ value = state.setAtPath(
1014
+ value,
1015
+ p,
1016
+ t,
1017
+ v,
1018
+ "merge",
1019
+ obs$.peek(),
1020
+ (path, value2) => {
1021
+ delete pending2[key];
1022
+ pending2[path.join("/")] = {
1023
+ p: null,
1024
+ v: value2,
1025
+ t: t.slice(0, path.length)
1026
+ };
1027
+ }
1028
+ );
939
1029
  }
940
- obs$.splice(0, 0, ...value);
941
- } else if (mode === "merge") {
942
- state.mergeIntoObservable(obs$, value);
943
- } else {
944
- obs$.set(value);
945
1030
  }
946
1031
  });
1032
+ if (didChangeMetadata && syncOptions.persist) {
1033
+ updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
1034
+ pending: pending2
1035
+ });
1036
+ }
947
1037
  }
948
- if (lastSync2 && syncOptions.persist) {
949
- updateMetadata(obs$, localState, syncState$, syncOptions, {
950
- lastSync: lastSync2
951
- });
1038
+ if (options == null ? void 0 : options.resetLastSync) {
1039
+ setNodeValue(node, (_a2 = syncOptions.initial) != null ? _a2 : void 0);
952
1040
  }
953
- };
954
- if (node.activationState) {
955
- node.activationState.onChange = onChange;
1041
+ onChangeRemote2(() => {
1042
+ if (state.isPlainObject(value)) {
1043
+ value = state.ObservableHint.plain(value);
1044
+ }
1045
+ if (mode === "assign") {
1046
+ obs$.assign(value);
1047
+ } else if (mode === "append") {
1048
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !state.isArray(value)) {
1049
+ console.error("[legend-state] mode:append expects the value to be an array");
1050
+ }
1051
+ obs$.push(...value);
1052
+ } else if (mode === "prepend") {
1053
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !state.isArray(value)) {
1054
+ console.error("[legend-state] mode:prepend expects the value to be an array");
1055
+ }
1056
+ obs$.splice(0, 0, ...value);
1057
+ } else if (mode === "merge") {
1058
+ state.mergeIntoObservable(obs$, value);
1059
+ } else {
1060
+ obs$.set(value);
1061
+ }
1062
+ });
956
1063
  }
957
- if (!isSubscribed && syncOptions.subscribe) {
958
- const subscribe2 = syncOptions.subscribe;
959
- isSubscribed = true;
960
- const doSubscribe = () => {
961
- const subscribeParams = {
962
- node,
963
- value$: obs$,
964
- lastSync,
965
- update: (params) => {
966
- state.when(
967
- () => !get || syncState$.isLoaded.get(),
968
- () => {
969
- state.when(waitFor || true, () => {
970
- params.mode || (params.mode = syncOptions.mode || "merge");
971
- onChange(params);
972
- if (!syncState$.isLoaded.peek()) {
973
- syncState$.assign({
974
- isLoaded: syncStateValue.numPendingRemoteLoads < 1,
975
- error: void 0,
976
- isGetting: syncStateValue.numPendingGets > 0
977
- });
978
- }
979
- });
980
- }
981
- );
982
- },
983
- refresh: () => state.when(syncState$.isLoaded, sync),
984
- onError: (error) => onGetError(error, { source: "subscribe", subscribeParams })
985
- };
986
- unsubscribe = subscribe2(subscribeParams);
987
- };
988
- if (waitFor) {
989
- state.whenReady(waitFor, doSubscribe);
990
- } else {
991
- doSubscribe();
992
- }
1064
+ if (lastSync2 && syncOptions.persist) {
1065
+ updateMetadata(obs$, localState, syncState$, syncOptions, {
1066
+ lastSync: lastSync2
1067
+ });
993
1068
  }
994
- const existingValue = getNodeValue(node);
995
- if (get) {
996
- const onError = (error) => onGetError(error, { getParams, source: "get" });
997
- const getParams = {
1069
+ };
1070
+ if (node.activationState) {
1071
+ node.activationState.onChange = onChange;
1072
+ }
1073
+ if (!isSubscribed && syncOptions.subscribe) {
1074
+ const subscribe2 = syncOptions.subscribe;
1075
+ const doSubscribe = () => {
1076
+ isSubscribed = true;
1077
+ const subscribeParams = {
998
1078
  node,
999
1079
  value$: obs$,
1000
- value: state.isFunction(existingValue) || (existingValue == null ? void 0 : existingValue[symbolLinked]) ? void 0 : existingValue,
1001
- mode: syncOptions.mode,
1002
- refresh: sync,
1003
- options: syncOptions,
1004
- lastSync,
1005
- updateLastSync: (lastSync2) => getParams.lastSync = lastSync2,
1006
- onError,
1007
- retryNum: 0,
1008
- cancelRetry: false
1009
- };
1010
- let modeBeforeReset = void 0;
1011
- const beforeGetParams = {
1012
- value: getParams.value,
1013
1080
  lastSync,
1014
- pendingChanges: pending && !state.isEmpty(pending) ? pending : void 0,
1015
- clearPendingChanges: async () => {
1016
- localState.pendingChanges = {};
1017
- await updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
1018
- pending: localState.pendingChanges
1019
- });
1020
- },
1021
- resetCache: () => {
1022
- var _a3;
1023
- modeBeforeReset = getParams.mode;
1024
- getParams.mode = "set";
1025
- return (_a3 = syncStateValue.resetPersistence) == null ? void 0 : _a3.call(syncStateValue);
1081
+ update: (params) => {
1082
+ state.when(
1083
+ () => !get || syncState$.isLoaded.get(),
1084
+ () => {
1085
+ state.when(waitFor || true, () => {
1086
+ params.mode || (params.mode = syncOptions.mode || "merge");
1087
+ onChange(params);
1088
+ if (!syncState$.isLoaded.peek()) {
1089
+ syncState$.assign({
1090
+ isLoaded: syncStateValue.numPendingRemoteLoads < 1,
1091
+ error: void 0,
1092
+ isGetting: syncStateValue.numPendingGets > 0
1093
+ });
1094
+ }
1095
+ });
1096
+ }
1097
+ );
1026
1098
  },
1027
- cancel: false
1099
+ refresh: () => state.when(syncState$.isLoaded, callSync),
1100
+ onError: (error) => onGetError(error, {
1101
+ source: "subscribe",
1102
+ subscribeParams,
1103
+ type: "get",
1104
+ retry: {}
1105
+ })
1028
1106
  };
1029
- (_a2 = syncOptions.onBeforeGet) == null ? void 0 : _a2.call(syncOptions, beforeGetParams);
1030
- if (!beforeGetParams.cancel) {
1031
- syncState$.assign({
1032
- numPendingGets: (syncStateValue.numPendingGets || 0) + 1,
1033
- isGetting: true
1107
+ unsubscribe = subscribe2(subscribeParams);
1108
+ registerMiddleware(node, "listeners-cleared", () => {
1109
+ if (unsubscribe) {
1110
+ isSubscribed = false;
1111
+ unsubscribe();
1112
+ unsubscribe = void 0;
1113
+ }
1114
+ });
1115
+ registerMiddleware(node, "listener-added", () => {
1116
+ if (!isSubscribed) {
1117
+ doSubscribe();
1118
+ }
1119
+ });
1120
+ };
1121
+ if (waitFor) {
1122
+ state.whenReady(waitFor, doSubscribe);
1123
+ } else {
1124
+ doSubscribe();
1125
+ }
1126
+ }
1127
+ const existingValue = getNodeValue(node);
1128
+ if (get) {
1129
+ const getParams = {
1130
+ node,
1131
+ value$: obs$,
1132
+ value: state.isFunction(existingValue) || (existingValue == null ? void 0 : existingValue[symbolLinked]) ? void 0 : existingValue,
1133
+ mode: syncOptions.mode,
1134
+ refresh: sync,
1135
+ options: syncOptions,
1136
+ lastSync,
1137
+ updateLastSync: (lastSync2) => getParams.lastSync = lastSync2,
1138
+ onError: onGetError,
1139
+ retryNum: 0,
1140
+ cancelRetry: false
1141
+ };
1142
+ let modeBeforeReset = void 0;
1143
+ const beforeGetParams = {
1144
+ value: getParams.value,
1145
+ lastSync,
1146
+ pendingChanges: pending && !state.isEmpty(pending) ? pending : void 0,
1147
+ clearPendingChanges: async () => {
1148
+ localState.pendingChanges = {};
1149
+ await updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
1150
+ pending: localState.pendingChanges
1034
1151
  });
1035
- const got = runWithRetry(
1036
- getParams,
1037
- syncOptions.retry,
1038
- (retryEvent) => {
1039
- const params = getParams;
1040
- params.cancelRetry = retryEvent.cancelRetry;
1041
- params.retryNum = retryEvent.retryNum;
1042
- return get(params);
1043
- },
1044
- onError
1045
- );
1046
- const numGets = node.numGets = (node.numGets || 0) + 1;
1047
- const handle = (value) => {
1048
- syncState$.numPendingGets.set((v) => v - 1);
1049
- if (isWaitingForLoad) {
1050
- isWaitingForLoad = false;
1051
- syncStateValue.numPendingRemoteLoads--;
1052
- }
1053
- if (numGets >= (node.getNumResolved || 0)) {
1054
- node.getNumResolved = node.numGets;
1055
- onChange({
1056
- value,
1057
- lastSync: getParams.lastSync,
1058
- mode: getParams.mode
1059
- });
1060
- }
1061
- if (modeBeforeReset) {
1062
- getParams.mode = modeBeforeReset;
1063
- modeBeforeReset = void 0;
1064
- }
1065
- syncState$.assign({
1066
- isLoaded: syncStateValue.numPendingRemoteLoads < 1,
1067
- error: void 0,
1068
- isGetting: syncStateValue.numPendingGets > 0
1152
+ },
1153
+ resetCache: () => {
1154
+ var _a2;
1155
+ modeBeforeReset = getParams.mode;
1156
+ getParams.mode = "set";
1157
+ return (_a2 = syncStateValue.resetPersistence) == null ? void 0 : _a2.call(syncStateValue);
1158
+ },
1159
+ cancel: false
1160
+ };
1161
+ (_a = syncOptions.onBeforeGet) == null ? void 0 : _a.call(syncOptions, beforeGetParams);
1162
+ if (!beforeGetParams.cancel) {
1163
+ syncState$.assign({
1164
+ numPendingGets: (syncStateValue.numPendingGets || 0) + 1,
1165
+ isGetting: true
1166
+ });
1167
+ const got = runWithRetry(getParams, syncOptions.retry, node, (retryEvent) => {
1168
+ const params = getParams;
1169
+ params.cancelRetry = retryEvent.cancelRetry;
1170
+ params.retryNum = retryEvent.retryNum;
1171
+ return get(params);
1172
+ });
1173
+ const numGets = node.numGets = (node.numGets || 0) + 1;
1174
+ const handle = (value) => {
1175
+ syncState$.numPendingGets.set((v) => v - 1);
1176
+ if (isWaitingForLoad) {
1177
+ isWaitingForLoad = false;
1178
+ syncStateValue.numPendingRemoteLoads--;
1179
+ }
1180
+ if (numGets >= (node.getNumResolved || 0)) {
1181
+ node.getNumResolved = node.numGets;
1182
+ onChange({
1183
+ value,
1184
+ lastSync: getParams.lastSync,
1185
+ mode: getParams.mode
1069
1186
  });
1070
- };
1071
- if (state.isPromise(got)) {
1072
- got.then(handle).catch(onError);
1073
- } else {
1074
- handle(got);
1075
1187
  }
1188
+ if (modeBeforeReset) {
1189
+ getParams.mode = modeBeforeReset;
1190
+ modeBeforeReset = void 0;
1191
+ }
1192
+ syncState$.assign({
1193
+ isLoaded: syncStateValue.numPendingRemoteLoads < 1,
1194
+ error: void 0,
1195
+ isGetting: syncStateValue.numPendingGets > 0
1196
+ });
1197
+ };
1198
+ if (state.isPromise(got)) {
1199
+ got.then(handle).catch((error) => {
1200
+ onGetError(error, { getParams, source: "get", type: "get", retry: getParams }, true);
1201
+ });
1202
+ } else {
1203
+ handle(got);
1076
1204
  }
1077
1205
  }
1078
- };
1079
- if (waitFor) {
1080
- state.whenReady(waitFor, () => state.trackSelector(runGet, sync));
1081
- } else {
1082
- state.trackSelector(runGet, sync);
1083
1206
  }
1207
+ };
1208
+ if (waitFor) {
1209
+ state.whenReady(waitFor, () => state.trackSelector(runGet, callSync));
1084
1210
  } else {
1085
- syncState$.assign({
1086
- isLoaded: true,
1087
- error: void 0
1088
- });
1211
+ state.trackSelector(runGet, callSync);
1089
1212
  }
1090
1213
  if (!isSynced) {
1091
1214
  isSynced = true;
1092
- await state.when(syncState$.isLoaded);
1215
+ isApplyingPendingAfterSync = true;
1093
1216
  applyPending(pending);
1217
+ isApplyingPendingAfterSync = false;
1094
1218
  }
1095
1219
  };
1096
1220
  syncStateValue.sync = sync;
1097
1221
  } else {
1098
1222
  if (!isSynced) {
1223
+ isApplyingPendingAfterSync = true;
1099
1224
  applyPending(localState.pendingChanges);
1225
+ isApplyingPendingAfterSync = false;
1100
1226
  }
1101
1227
  }
1102
1228
  syncStateValue.reset = async () => {
@@ -1106,7 +1232,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1106
1232
  if (metadata) {
1107
1233
  Object.assign(metadata, { lastSync: void 0, pending: void 0 });
1108
1234
  }
1109
- Object.assign(syncStateValue, {
1235
+ const newState = {
1110
1236
  isPersistEnabled: false,
1111
1237
  isSyncEnabled: false,
1112
1238
  lastSync: void 0,
@@ -1116,13 +1242,14 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1116
1242
  isSetting: false,
1117
1243
  numPendingSets: 0,
1118
1244
  syncCount: 0
1119
- });
1245
+ };
1246
+ Object.assign(syncStateValue, newState);
1120
1247
  isSynced = false;
1121
1248
  isSubscribed = false;
1122
1249
  unsubscribe == null ? void 0 : unsubscribe();
1123
1250
  unsubscribe = void 0;
1124
1251
  const promise = syncStateValue.resetPersistence();
1125
- onChangeRemote(() => {
1252
+ onChangeRemote2(() => {
1126
1253
  var _a;
1127
1254
  obs$.set((_a = syncOptions.initial) != null ? _a : void 0);
1128
1255
  });
@@ -1132,6 +1259,14 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1132
1259
  node.dirtyFn = sync;
1133
1260
  await promise;
1134
1261
  };
1262
+ syncState$.lastSync.onChange(({ value }) => {
1263
+ const metadata = metadatas.get(obs$);
1264
+ if (metadata && metadata.lastSync !== value) {
1265
+ updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
1266
+ lastSync: value
1267
+ });
1268
+ }
1269
+ });
1135
1270
  const onAllPersistLoaded = () => {
1136
1271
  var _a, _b;
1137
1272
  let parentNode = node;
@@ -1155,7 +1290,10 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1155
1290
  });
1156
1291
  return syncState$;
1157
1292
  }
1158
- var { getProxy, globalState: globalState2, setNodeValue, getNodeValue: getNodeValue2 } = state.internal;
1293
+ function getAllSyncStates() {
1294
+ return Array.from(allSyncStates.entries());
1295
+ }
1296
+ var { getProxy, globalState: globalState2, setNodeValue: setNodeValue2, getNodeValue: getNodeValue2 } = state.internal;
1159
1297
  function enableActivateSyncedNode() {
1160
1298
  globalState2.activateSyncedNode = function activateSyncedNode(node, newValue) {
1161
1299
  const obs$ = getProxy(node);
@@ -1178,7 +1316,7 @@ function enableActivateSyncedNode() {
1178
1316
  } else {
1179
1317
  newValue = initial;
1180
1318
  }
1181
- setNodeValue(node, promiseReturn ? void 0 : newValue);
1319
+ setNodeValue2(node, promiseReturn ? void 0 : newValue);
1182
1320
  syncObservable(obs$, { ...node.activationState, get, set });
1183
1321
  return { update: onChange, value: newValue };
1184
1322
  } else {
@@ -1227,19 +1365,22 @@ function configureSynced(fnOrOrigOptions, origOptions) {
1227
1365
  }
1228
1366
 
1229
1367
  // sync.ts
1230
- var internal4 = {
1368
+ var internal5 = {
1231
1369
  observableSyncConfiguration,
1232
- waitForSet
1370
+ waitForSet,
1371
+ runWithRetry
1233
1372
  };
1234
1373
 
1235
1374
  exports.combineTransforms = combineTransforms;
1236
1375
  exports.configureObservableSync = configureObservableSync;
1237
1376
  exports.configureSynced = configureSynced;
1377
+ exports.createRevertChanges = createRevertChanges;
1238
1378
  exports.deepEqual = deepEqual;
1239
1379
  exports.diffObjects = diffObjects;
1240
- exports.internal = internal4;
1380
+ exports.getAllSyncStates = getAllSyncStates;
1381
+ exports.internal = internal5;
1241
1382
  exports.mapSyncPlugins = mapSyncPlugins;
1242
- exports.onChangeRemote = onChangeRemote;
1383
+ exports.onChangeRemote = onChangeRemote2;
1243
1384
  exports.removeNullUndefined = removeNullUndefined;
1244
1385
  exports.syncObservable = syncObservable;
1245
1386
  exports.synced = synced;