@legendapp/state 3.0.0-beta.3 → 3.0.0-beta.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/.DS_Store +0 -0
  2. package/config/enableReactComponents.js +3 -1
  3. package/config/enableReactComponents.mjs +3 -1
  4. package/config/enableReactTracking.d.mts +2 -1
  5. package/config/enableReactTracking.d.ts +2 -1
  6. package/config/enableReactTracking.js +32 -13
  7. package/config/enableReactTracking.mjs +32 -13
  8. package/index.d.mts +33 -4
  9. package/index.d.ts +33 -4
  10. package/index.js +191 -29
  11. package/index.mjs +191 -29
  12. package/package.json +35 -1
  13. package/persist-plugins/async-storage.js +17 -9
  14. package/persist-plugins/async-storage.mjs +17 -9
  15. package/persist-plugins/expo-sqlite.d.mts +19 -0
  16. package/persist-plugins/expo-sqlite.d.ts +19 -0
  17. package/persist-plugins/expo-sqlite.js +72 -0
  18. package/persist-plugins/expo-sqlite.mjs +69 -0
  19. package/react-native.d.mts +4 -0
  20. package/react-native.d.ts +4 -0
  21. package/react-native.js +53 -0
  22. package/react-native.mjs +40 -0
  23. package/react-reactive/Components.d.mts +19 -0
  24. package/react-reactive/Components.d.ts +19 -0
  25. package/react-reactive/Components.js +53 -0
  26. package/react-reactive/Components.mjs +40 -0
  27. package/react-reactive/enableReactComponents.d.mts +3 -2
  28. package/react-reactive/enableReactComponents.d.ts +3 -2
  29. package/react-reactive/enableReactComponents.js +10 -3
  30. package/react-reactive/enableReactComponents.mjs +10 -3
  31. package/react-reactive/enableReactNativeComponents.d.mts +3 -20
  32. package/react-reactive/enableReactNativeComponents.d.ts +3 -20
  33. package/react-reactive/enableReactNativeComponents.js +8 -3
  34. package/react-reactive/enableReactNativeComponents.mjs +8 -3
  35. package/react-reactive/enableReactive.js +10 -3
  36. package/react-reactive/enableReactive.mjs +10 -3
  37. package/react-reactive/enableReactive.native.js +8 -3
  38. package/react-reactive/enableReactive.native.mjs +8 -3
  39. package/react-reactive/enableReactive.web.js +8 -3
  40. package/react-reactive/enableReactive.web.mjs +8 -3
  41. package/react-web.d.mts +6 -0
  42. package/react-web.d.ts +6 -0
  43. package/react-web.js +39 -0
  44. package/react-web.mjs +37 -0
  45. package/react.d.mts +41 -21
  46. package/react.d.ts +41 -21
  47. package/react.js +36 -23
  48. package/react.mjs +37 -25
  49. package/sync-plugins/crud.d.mts +24 -9
  50. package/sync-plugins/crud.d.ts +24 -9
  51. package/sync-plugins/crud.js +250 -116
  52. package/sync-plugins/crud.mjs +251 -117
  53. package/sync-plugins/firebase.d.mts +7 -3
  54. package/sync-plugins/firebase.d.ts +7 -3
  55. package/sync-plugins/firebase.js +4 -2
  56. package/sync-plugins/firebase.mjs +4 -2
  57. package/sync-plugins/keel.d.mts +12 -13
  58. package/sync-plugins/keel.d.ts +12 -13
  59. package/sync-plugins/keel.js +60 -52
  60. package/sync-plugins/keel.mjs +61 -48
  61. package/sync-plugins/supabase.d.mts +7 -3
  62. package/sync-plugins/supabase.d.ts +7 -3
  63. package/sync-plugins/supabase.js +90 -33
  64. package/sync-plugins/supabase.mjs +91 -34
  65. package/sync-plugins/tanstack-query.d.mts +3 -3
  66. package/sync-plugins/tanstack-query.d.ts +3 -3
  67. package/sync.d.mts +16 -8
  68. package/sync.d.ts +16 -8
  69. package/sync.js +324 -215
  70. package/sync.mjs +323 -215
  71. package/trace.js +5 -6
  72. package/trace.mjs +5 -6
  73. package/types/reactive-native.d.ts +19 -0
  74. 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,13 +170,13 @@ 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));
169
180
  }
170
181
  return new Promise((resolve, reject) => {
171
182
  const run = () => {
@@ -176,9 +187,6 @@ function runWithRetry(state, retryOptions, fn, onError) {
176
187
  if (timeoutRetry) {
177
188
  clearTimeout(timeoutRetry);
178
189
  }
179
- if (onError) {
180
- onError(error, state);
181
- }
182
190
  if (!state.cancelRetry) {
183
191
  const timeout = createRetryTimeout(retryOptions, state.retryNum, () => {
184
192
  value = fn(state);
@@ -188,7 +196,7 @@ function runWithRetry(state, retryOptions, fn, onError) {
188
196
  state.cancelRetry = true;
189
197
  reject(error);
190
198
  } else {
191
- mapRetryTimeouts.set(state.node, timeout);
199
+ mapRetryTimeouts.set(retryId, timeout);
192
200
  timeoutRetry = timeout;
193
201
  }
194
202
  }
@@ -208,9 +216,33 @@ async function waitForSet(waitForSet2, changes, value, params = {}) {
208
216
  await when(waitFn);
209
217
  }
210
218
  }
219
+ var { clone } = internal;
220
+ function createRevertChanges(obs$, changes) {
221
+ return () => {
222
+ const previous = applyChanges(
223
+ clone(obs$.peek()),
224
+ changes,
225
+ /*applyPrevious*/
226
+ true
227
+ );
228
+ onChangeRemote(() => {
229
+ obs$.set(previous);
230
+ });
231
+ };
232
+ }
211
233
 
212
234
  // src/sync/syncObservable.ts
213
- var { clone, deepMerge, getNode, getNodeValue, getValueAtPath, globalState, symbolLinked, createPreviousHandler } = internal;
235
+ var {
236
+ clone: clone2,
237
+ createPreviousHandler,
238
+ deepMerge,
239
+ getNode,
240
+ getNodeValue,
241
+ getValueAtPath,
242
+ globalState,
243
+ registerMiddleware,
244
+ symbolLinked
245
+ } = internal;
214
246
  var mapSyncPlugins = /* @__PURE__ */ new WeakMap();
215
247
  var allSyncStates = /* @__PURE__ */ new Map();
216
248
  var metadatas = /* @__PURE__ */ new WeakMap();
@@ -221,7 +253,7 @@ function parseLocalConfig(config) {
221
253
  function doInOrder(arg1, arg2) {
222
254
  return isPromise$1(arg1) ? arg1.then(arg2) : arg2(arg1);
223
255
  }
224
- function onChangeRemote(cb) {
256
+ function onChangeRemote2(cb) {
225
257
  endBatch(true);
226
258
  globalState.isLoadingRemote = true;
227
259
  beginBatch();
@@ -273,10 +305,10 @@ function updateMetadata(value$, localState, syncState2, syncOptions, newMetadata
273
305
  if (localState.timeoutSaveMetadata) {
274
306
  clearTimeout(localState.timeoutSaveMetadata);
275
307
  }
276
- localState.timeoutSaveMetadata = setTimeout(
277
- () => updateMetadataImmediate(value$, localState, syncState2, syncOptions, newMetadata),
278
- 0
279
- );
308
+ metadatas.set(value$, { ...metadatas.get(value$) || {}, ...newMetadata });
309
+ localState.timeoutSaveMetadata = setTimeout(() => {
310
+ updateMetadataImmediate(value$, localState, syncState2, syncOptions, metadatas.get(value$));
311
+ }, 0);
280
312
  }
281
313
  var _queuedChanges = [];
282
314
  var _queuedRemoteChanges = /* @__PURE__ */ new Map();
@@ -295,8 +327,25 @@ function mergeChanges(changes) {
295
327
  existing.valueAtPath = change.valueAtPath;
296
328
  }
297
329
  } else {
298
- changesByPath.set(pathStr, change);
299
- changesOut.push(change);
330
+ let found = false;
331
+ for (let u = 0; u < change.path.length; u++) {
332
+ const path = change.path.slice(0, u).join("/");
333
+ if (changesByPath.has(path)) {
334
+ const remaining = change.path.slice(u);
335
+ setAtPath(
336
+ changesByPath.get(path).valueAtPath,
337
+ remaining,
338
+ change.pathTypes.slice(u),
339
+ change.valueAtPath
340
+ );
341
+ found = true;
342
+ break;
343
+ }
344
+ }
345
+ if (!found) {
346
+ changesByPath.set(pathStr, change);
347
+ changesOut.push(change);
348
+ }
300
349
  }
301
350
  }
302
351
  return changesOut;
@@ -578,7 +627,7 @@ async function doChangeRemote(changeInfo) {
578
627
  if (waitForSetParam) {
579
628
  await waitForSet(waitForSetParam, changesRemote, obs$.peek());
580
629
  }
581
- let value = clone(obs$.peek());
630
+ let value = clone2(obs$.peek());
582
631
  const transformSave = (_a = syncOptions == null ? void 0 : syncOptions.transform) == null ? void 0 : _a.save;
583
632
  if (transformSave) {
584
633
  value = transformSave(value);
@@ -591,33 +640,41 @@ async function doChangeRemote(changeInfo) {
591
640
  onBeforeSet == null ? void 0 : onBeforeSet(beforeSetParams);
592
641
  if (!beforeSetParams.cancel) {
593
642
  let updateResult = void 0;
594
- let errorHandled = false;
595
- const onError = (error, retryParams) => {
643
+ let lastErrorHandled;
644
+ const onSetError = (error, params, noThrow) => {
596
645
  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
- });
646
+ if (lastErrorHandled !== error) {
647
+ if (!params) {
648
+ params = {
649
+ setParams,
650
+ source: "set",
651
+ type: "set",
652
+ input: value,
653
+ retry: setParams,
654
+ revert: createRevertChanges(setParams.value$, setParams.changes)
655
+ };
656
+ }
657
+ state$.error.set(error);
658
+ (_a2 = syncOptions.onError) == null ? void 0 : _a2.call(syncOptions, error, params);
659
+ lastErrorHandled = error;
660
+ if (!noThrow) {
661
+ throw error;
662
+ }
605
663
  }
606
- errorHandled = true;
607
664
  };
608
665
  const setParams = {
609
666
  node,
610
667
  value$: obs$,
611
668
  changes: changesRemote,
612
669
  value,
613
- onError,
670
+ onError: onSetError,
614
671
  update: (params) => {
615
672
  if (updateResult) {
616
- const { value: value2, lastSync, mode } = params;
673
+ const { value: value2, mode, changes } = params;
617
674
  updateResult = {
618
- lastSync: Math.max(updateResult.lastSync || 0, lastSync || 0),
619
675
  value: deepMerge(updateResult.value, value2),
620
- mode
676
+ mode,
677
+ changes: changes ? [...updateResult.changes || [], ...changes] : updateResult.changes
621
678
  };
622
679
  } else {
623
680
  updateResult = params;
@@ -627,26 +684,23 @@ async function doChangeRemote(changeInfo) {
627
684
  retryNum: 0,
628
685
  cancelRetry: false
629
686
  };
630
- const savedPromise = runWithRetry(
631
- setParams,
632
- syncOptions.retry,
633
- async () => {
634
- return syncOptions.set(setParams);
635
- },
636
- onError
637
- );
687
+ const savedPromise = runWithRetry(setParams, syncOptions.retry, node, async () => {
688
+ return syncOptions.set(setParams);
689
+ });
638
690
  let didError = false;
639
691
  if (isPromise$1(savedPromise)) {
640
692
  await savedPromise.catch((error) => {
641
693
  didError = true;
642
694
  if (!syncOptions.retry) {
643
- onError(error);
695
+ onSetError(error, void 0, true);
644
696
  }
645
697
  });
646
698
  }
647
- if (!didError) {
648
- const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
649
- const { value: changes, lastSync } = updateResult || {};
699
+ if (!didError || (updateResult == null ? void 0 : updateResult.changes)) {
700
+ const { value: updateValue, changes: updateChanges = changesRemote } = updateResult || {};
701
+ const pathStrs = Array.from(
702
+ new Set(updateChanges.map((change) => change.pathStr))
703
+ );
650
704
  if (pathStrs.length > 0) {
651
705
  let transformedChanges = void 0;
652
706
  const metadata = {};
@@ -663,18 +717,15 @@ async function doChangeRemote(changeInfo) {
663
717
  delete pending[pathStr];
664
718
  }
665
719
  }
666
- if (lastSync) {
667
- metadata.lastSync = lastSync;
668
- }
669
720
  }
670
- if (changes && !isEmpty(changes)) {
671
- transformedChanges = transformLoadData(changes, syncOptions, false, "set");
721
+ if (updateValue && !isEmpty(updateValue)) {
722
+ transformedChanges = transformLoadData(updateValue, syncOptions, false, "set");
672
723
  }
673
724
  if (transformedChanges !== void 0) {
674
725
  if (isPromise$1(transformedChanges)) {
675
726
  transformedChanges = await transformedChanges;
676
727
  }
677
- onChangeRemote(() => mergeIntoObservable(obs$, transformedChanges));
728
+ onChangeRemote2(() => mergeIntoObservable(obs$, transformedChanges));
678
729
  }
679
730
  if (saveLocal) {
680
731
  if (shouldSaveMetadata && !isEmpty(metadata)) {
@@ -741,9 +792,20 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
741
792
  await when(initialized$);
742
793
  }
743
794
  if (persistPlugin.loadTable) {
744
- const promise = persistPlugin.loadTable(table, config);
745
- if (promise) {
746
- await promise;
795
+ try {
796
+ const promise = persistPlugin.loadTable(table, config);
797
+ if (promise) {
798
+ await promise;
799
+ }
800
+ } catch (err) {
801
+ if (process.env.NODE_ENV === "development") {
802
+ console.error(
803
+ "[legend-state] Error loading local cache. This would be a crashing error in production.",
804
+ err
805
+ );
806
+ } else {
807
+ throw err;
808
+ }
747
809
  }
748
810
  }
749
811
  const prevValue = getNodeValue(node);
@@ -762,12 +824,14 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
762
824
  if (isPromise$1(value)) {
763
825
  value = await value;
764
826
  }
827
+ node.root.isLoadingLocal = true;
765
828
  internal.globalState.isLoadingLocal = true;
766
829
  if (value === null && (!prevValue || prevValue[symbolLinked])) {
767
830
  value$.set(value);
768
831
  } else {
769
832
  mergeIntoObservable(value$, value);
770
833
  }
834
+ node.root.isLoadingLocal = false;
771
835
  internal.globalState.isLoadingLocal = false;
772
836
  }
773
837
  syncStateValue.numPendingLocalLoads--;
@@ -779,6 +843,11 @@ async function loadLocal(value$, syncOptions, syncState$, localState) {
779
843
  ].filter(Boolean)
780
844
  );
781
845
  } else {
846
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && persist && persist.plugin && !Object.hasOwn(persist, "name")) {
847
+ console.warn(
848
+ "[legend-state] Trying to syncObservable without `name` defined. Please include a `name` property in the `persist` configuration."
849
+ );
850
+ }
782
851
  nodeValue.resetPersistence = () => prevResetPersistence == null ? void 0 : prevResetPersistence();
783
852
  }
784
853
  nodeValue.clearPersist = nodeValue.resetPersistence;
@@ -806,14 +875,24 @@ function syncObservable(obs$, syncOptionsOrSynced) {
806
875
  const syncStateValue = getNodeValue(getNode(syncState$));
807
876
  allSyncStates.set(syncState$, node);
808
877
  syncStateValue.getPendingChanges = () => localState.pendingChanges;
809
- let errorHandled = false;
810
- const onGetError = (error, params) => {
878
+ let lastErrorHandled;
879
+ const onGetError = (error, params, noThrow) => {
811
880
  var _a;
812
- syncState$.error.set(error);
813
- if (!errorHandled) {
814
- (_a = syncOptions.onError) == null ? void 0 : _a.call(syncOptions, error, { ...params, value$: obs$ });
881
+ if (lastErrorHandled !== error) {
882
+ if (!params) {
883
+ params = {
884
+ source: "get",
885
+ type: "get",
886
+ retry: params
887
+ };
888
+ }
889
+ syncState$.error.set(error);
890
+ (_a = syncOptions.onError) == null ? void 0 : _a.call(syncOptions, error, params);
891
+ lastErrorHandled = error;
892
+ if (!noThrow) {
893
+ throw error;
894
+ }
815
895
  }
816
- errorHandled = true;
817
896
  };
818
897
  loadLocal(obs$, syncOptions, syncState$, localState);
819
898
  let isWaitingForLoad = !!syncOptions.get;
@@ -823,30 +902,37 @@ function syncObservable(obs$, syncOptionsOrSynced) {
823
902
  syncState$.isLoaded.set(!syncState$.numPendingRemoteLoads.peek());
824
903
  let isSynced = false;
825
904
  let isSubscribed = false;
905
+ let isApplyingPendingAfterSync = false;
826
906
  let unsubscribe = void 0;
827
907
  const applyPending = (pending) => {
828
908
  if (pending && !isEmpty(pending)) {
829
- localState.isApplyingPending = true;
830
909
  const keys = Object.keys(pending);
910
+ const value = getNodeValue(node);
831
911
  const changes = [];
832
912
  for (let i = 0; i < keys.length; i++) {
833
913
  const key = keys[i];
834
914
  const path = key.split("/").filter((p2) => p2 !== "");
835
- const { p, v, t } = pending[key];
836
- changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
915
+ const { p, t, v } = pending[key];
916
+ const valueAtPath = getValueAtPath(value, path);
917
+ if (isApplyingPendingAfterSync || !deepEqual(valueAtPath, v)) {
918
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
919
+ }
920
+ }
921
+ if (changes.length > 0) {
922
+ localState.isApplyingPending = true;
923
+ onObsChange(obs$, syncState$, localState, syncOptions, {
924
+ value,
925
+ isFromPersist: false,
926
+ isFromSync: false,
927
+ getPrevious: createPreviousHandler(value, changes),
928
+ changes
929
+ });
930
+ localState.isApplyingPending = false;
837
931
  }
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
932
  }
848
933
  };
849
- if (syncOptions.get) {
934
+ const { get, subscribe } = syncOptions;
935
+ if (get || subscribe) {
850
936
  sync = async () => {
851
937
  var _a;
852
938
  if (isSynced && (!getNodeValue(getNode(syncState$)).isSyncEnabled || shouldIgnoreUnobserved(node, sync))) {
@@ -859,128 +945,154 @@ function syncObservable(obs$, syncOptionsOrSynced) {
859
945
  }
860
946
  const lastSync = (_a = metadatas.get(obs$)) == null ? void 0 : _a.lastSync;
861
947
  const pending = localState.pendingChanges;
862
- const get = syncOptions.get;
863
- if (get) {
864
- const { waitFor } = syncOptions;
865
- const runGet = () => {
866
- 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");
948
+ const { waitFor } = syncOptions;
949
+ const runGet = () => {
950
+ var _a2;
951
+ const onChange = async ({ value, mode, lastSync: lastSync2 }) => {
952
+ mode = mode || syncOptions.mode || "set";
953
+ if (value !== void 0) {
954
+ value = transformLoadData(value, syncOptions, true, "get");
955
+ if (isPromise$1(value)) {
956
+ value = await value;
957
+ }
958
+ const pending2 = localState.pendingChanges;
959
+ const currentValue = obs$.peek();
960
+ if (pending2) {
961
+ let didChangeMetadata = false;
962
+ Object.keys(pending2).forEach((key) => {
963
+ const p = key.split("/").filter((k) => k !== "");
964
+ const { v, t } = pending2[key];
965
+ if (t.length === 0 || !value) {
966
+ const oldValue = clone2(value);
967
+ pending2[key].p = key ? oldValue[key] : oldValue;
968
+ if (isObject(value) && isObject(v)) {
969
+ Object.assign(value, key ? { [key]: v } : v);
970
+ } else if (!key) {
971
+ value = v;
932
972
  }
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");
973
+ } else if (value[p[0]] !== void 0) {
974
+ const curValue = getValueAtPath(currentValue, p);
975
+ const newValue = getValueAtPath(value, p);
976
+ if (JSON.stringify(curValue) === JSON.stringify(newValue)) {
977
+ delete pending2[key];
978
+ didChangeMetadata = true;
979
+ } else {
980
+ const oldValue = clone2(value);
981
+ pending2[key].p = getValueAtPath(oldValue, p);
982
+ didChangeMetadata = true;
983
+ value = setAtPath(
984
+ value,
985
+ p,
986
+ t,
987
+ v,
988
+ "merge",
989
+ obs$.peek(),
990
+ (path, value2) => {
991
+ delete pending2[key];
992
+ pending2[path.join("/")] = {
993
+ p: null,
994
+ v: value2,
995
+ t: t.slice(0, path.length)
996
+ };
997
+ }
998
+ );
937
999
  }
938
- obs$.splice(0, 0, ...value);
939
- } else if (mode === "merge") {
940
- mergeIntoObservable(obs$, value);
941
- } else {
942
- obs$.set(value);
943
1000
  }
944
1001
  });
1002
+ if (didChangeMetadata && syncOptions.persist) {
1003
+ updateMetadataImmediate(obs$, localState, syncState$, syncOptions, {
1004
+ pending: pending2
1005
+ });
1006
+ }
945
1007
  }
946
- if (lastSync2 && syncOptions.persist) {
947
- updateMetadata(obs$, localState, syncState$, syncOptions, {
948
- lastSync: lastSync2
949
- });
950
- }
951
- };
952
- if (node.activationState) {
953
- node.activationState.onChange = onChange;
1008
+ onChangeRemote2(() => {
1009
+ if (isPlainObject(value)) {
1010
+ value = ObservableHint.plain(value);
1011
+ }
1012
+ if (mode === "assign") {
1013
+ obs$.assign(value);
1014
+ } else if (mode === "append") {
1015
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !isArray(value)) {
1016
+ console.error("[legend-state] mode:append expects the value to be an array");
1017
+ }
1018
+ obs$.push(...value);
1019
+ } else if (mode === "prepend") {
1020
+ if ((process.env.NODE_ENV === "development" || process.env.NODE_ENV === "test") && !isArray(value)) {
1021
+ console.error("[legend-state] mode:prepend expects the value to be an array");
1022
+ }
1023
+ obs$.splice(0, 0, ...value);
1024
+ } else if (mode === "merge") {
1025
+ mergeIntoObservable(obs$, value);
1026
+ } else {
1027
+ obs$.set(value);
1028
+ }
1029
+ });
1030
+ }
1031
+ if (lastSync2 && syncOptions.persist) {
1032
+ updateMetadata(obs$, localState, syncState$, syncOptions, {
1033
+ lastSync: lastSync2
1034
+ });
954
1035
  }
955
- if (!isSubscribed && syncOptions.subscribe) {
956
- const subscribe = syncOptions.subscribe;
1036
+ };
1037
+ if (node.activationState) {
1038
+ node.activationState.onChange = onChange;
1039
+ }
1040
+ if (!isSubscribed && syncOptions.subscribe) {
1041
+ const subscribe2 = syncOptions.subscribe;
1042
+ const doSubscribe = () => {
957
1043
  isSubscribed = true;
958
- const doSubscribe = () => {
959
- const subscribeParams = {
960
- node,
961
- value$: obs$,
962
- lastSync,
963
- update: (params) => {
964
- when(syncState$.isLoaded, () => {
1044
+ const subscribeParams = {
1045
+ node,
1046
+ value$: obs$,
1047
+ lastSync,
1048
+ update: (params) => {
1049
+ when(
1050
+ () => !get || syncState$.isLoaded.get(),
1051
+ () => {
965
1052
  when(waitFor || true, () => {
966
1053
  params.mode || (params.mode = syncOptions.mode || "merge");
967
1054
  onChange(params);
1055
+ if (!syncState$.isLoaded.peek()) {
1056
+ syncState$.assign({
1057
+ isLoaded: syncStateValue.numPendingRemoteLoads < 1,
1058
+ error: void 0,
1059
+ isGetting: syncStateValue.numPendingGets > 0
1060
+ });
1061
+ }
968
1062
  });
969
- });
970
- },
971
- refresh: () => when(syncState$.isLoaded, sync),
972
- onError: (error) => onGetError(error, { source: "subscribe", subscribeParams })
973
- };
974
- unsubscribe = subscribe(subscribeParams);
1063
+ }
1064
+ );
1065
+ },
1066
+ refresh: () => when(syncState$.isLoaded, sync),
1067
+ onError: (error) => onGetError(error, {
1068
+ source: "subscribe",
1069
+ subscribeParams,
1070
+ type: "get",
1071
+ retry: {}
1072
+ })
975
1073
  };
976
- if (waitFor) {
977
- whenReady(waitFor, doSubscribe);
978
- } else {
979
- doSubscribe();
980
- }
1074
+ unsubscribe = subscribe2(subscribeParams);
1075
+ registerMiddleware(node, "listeners-cleared", () => {
1076
+ if (unsubscribe) {
1077
+ isSubscribed = false;
1078
+ unsubscribe();
1079
+ unsubscribe = void 0;
1080
+ }
1081
+ });
1082
+ registerMiddleware(node, "listener-added", () => {
1083
+ if (!isSubscribed) {
1084
+ doSubscribe();
1085
+ }
1086
+ });
1087
+ };
1088
+ if (waitFor) {
1089
+ whenReady(waitFor, doSubscribe);
1090
+ } else {
1091
+ doSubscribe();
981
1092
  }
982
- const existingValue = getNodeValue(node);
983
- const onError = (error) => onGetError(error, { getParams, source: "get" });
1093
+ }
1094
+ const existingValue = getNodeValue(node);
1095
+ if (get) {
984
1096
  const getParams = {
985
1097
  node,
986
1098
  value$: obs$,
@@ -990,7 +1102,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
990
1102
  options: syncOptions,
991
1103
  lastSync,
992
1104
  updateLastSync: (lastSync2) => getParams.lastSync = lastSync2,
993
- onError,
1105
+ onError: onGetError,
994
1106
  retryNum: 0,
995
1107
  cancelRetry: false
996
1108
  };
@@ -1019,17 +1131,12 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1019
1131
  numPendingGets: (syncStateValue.numPendingGets || 0) + 1,
1020
1132
  isGetting: true
1021
1133
  });
1022
- const got = runWithRetry(
1023
- getParams,
1024
- syncOptions.retry,
1025
- (retryEvent) => {
1026
- const params = getParams;
1027
- params.cancelRetry = retryEvent.cancelRetry;
1028
- params.retryNum = retryEvent.retryNum;
1029
- return get(params);
1030
- },
1031
- onError
1032
- );
1134
+ const got = runWithRetry(getParams, syncOptions.retry, node, (retryEvent) => {
1135
+ const params = getParams;
1136
+ params.cancelRetry = retryEvent.cancelRetry;
1137
+ params.retryNum = retryEvent.retryNum;
1138
+ return get(params);
1139
+ });
1033
1140
  const numGets = node.numGets = (node.numGets || 0) + 1;
1034
1141
  const handle = (value) => {
1035
1142
  syncState$.numPendingGets.set((v) => v - 1);
@@ -1056,33 +1163,33 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1056
1163
  });
1057
1164
  };
1058
1165
  if (isPromise$1(got)) {
1059
- got.then(handle).catch(onError);
1166
+ got.then(handle).catch((error) => {
1167
+ onGetError(error, { getParams, source: "get", type: "get", retry: getParams }, true);
1168
+ });
1060
1169
  } else {
1061
1170
  handle(got);
1062
1171
  }
1063
1172
  }
1064
- };
1065
- if (waitFor) {
1066
- whenReady(waitFor, () => trackSelector(runGet, sync));
1067
- } else {
1068
- trackSelector(runGet, sync);
1069
1173
  }
1174
+ };
1175
+ if (waitFor) {
1176
+ whenReady(waitFor, () => trackSelector(runGet, sync));
1070
1177
  } else {
1071
- syncState$.assign({
1072
- isLoaded: true,
1073
- error: void 0
1074
- });
1178
+ trackSelector(runGet, sync);
1075
1179
  }
1076
1180
  if (!isSynced) {
1077
1181
  isSynced = true;
1078
- await when(syncState$.isLoaded);
1182
+ isApplyingPendingAfterSync = true;
1079
1183
  applyPending(pending);
1184
+ isApplyingPendingAfterSync = false;
1080
1185
  }
1081
1186
  };
1082
1187
  syncStateValue.sync = sync;
1083
1188
  } else {
1084
1189
  if (!isSynced) {
1190
+ isApplyingPendingAfterSync = true;
1085
1191
  applyPending(localState.pendingChanges);
1192
+ isApplyingPendingAfterSync = false;
1086
1193
  }
1087
1194
  }
1088
1195
  syncStateValue.reset = async () => {
@@ -1108,7 +1215,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1108
1215
  unsubscribe == null ? void 0 : unsubscribe();
1109
1216
  unsubscribe = void 0;
1110
1217
  const promise = syncStateValue.resetPersistence();
1111
- onChangeRemote(() => {
1218
+ onChangeRemote2(() => {
1112
1219
  var _a;
1113
1220
  obs$.set((_a = syncOptions.initial) != null ? _a : void 0);
1114
1221
  });
@@ -1130,7 +1237,7 @@ function syncObservable(obs$, syncOptionsOrSynced) {
1130
1237
  return true;
1131
1238
  };
1132
1239
  when(onAllPersistLoaded, function() {
1133
- if (syncOptions.get && syncOptions.syncMode === "auto") {
1240
+ if ((syncOptions.get || syncOptions.subscribe) && syncOptions.syncMode === "auto") {
1134
1241
  sync();
1135
1242
  }
1136
1243
  if ((syncOptions == null ? void 0 : syncOptions.set) || (syncOptions == null ? void 0 : syncOptions.persist)) {
@@ -1213,9 +1320,10 @@ function configureSynced(fnOrOrigOptions, origOptions) {
1213
1320
  }
1214
1321
 
1215
1322
  // sync.ts
1216
- var internal4 = {
1323
+ var internal5 = {
1217
1324
  observableSyncConfiguration,
1218
- waitForSet
1325
+ waitForSet,
1326
+ runWithRetry
1219
1327
  };
1220
1328
 
1221
- export { combineTransforms, configureObservableSync, configureSynced, deepEqual, diffObjects, internal4 as internal, mapSyncPlugins, onChangeRemote, removeNullUndefined, syncObservable, synced, transformStringifyDates, transformStringifyKeys };
1329
+ export { combineTransforms, configureObservableSync, configureSynced, createRevertChanges, deepEqual, diffObjects, internal5 as internal, mapSyncPlugins, onChangeRemote2 as onChangeRemote, removeNullUndefined, syncObservable, synced, transformStringifyDates, transformStringifyKeys };