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

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