@legendapp/state 2.2.0-next.7 → 2.2.0-next.71

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 (298) hide show
  1. package/README.md +4 -2
  2. package/babel.js.map +1 -1
  3. package/config/enable$get.d.ts +8 -0
  4. package/config/enable$get.js +24 -0
  5. package/config/enable$get.js.map +1 -0
  6. package/config/enable$get.mjs +21 -0
  7. package/config/enable$get.mjs.map +1 -0
  8. package/config/enableReactComponents.js.map +1 -1
  9. package/config/enableReactComponents.mjs.map +1 -1
  10. package/config/enableReactNativeComponents.js.map +1 -1
  11. package/config/enableReactNativeComponents.mjs.map +1 -1
  12. package/config/enableReactTracking.d.ts +0 -9
  13. package/config/enableReactTracking.js.map +1 -1
  14. package/config/enableReactTracking.mjs.map +1 -1
  15. package/config/enableReactUse.d.ts +1 -1
  16. package/config/enableReactUse.js +1 -0
  17. package/config/enableReactUse.js.map +1 -1
  18. package/config/enableReactUse.mjs +1 -0
  19. package/config/enableReactUse.mjs.map +1 -1
  20. package/config/enable_peek.d.ts +8 -0
  21. package/config/{enableDirectPeek.js → enable_peek.js} +6 -3
  22. package/config/enable_peek.js.map +1 -0
  23. package/config/{enableDirectPeek.mjs → enable_peek.mjs} +5 -3
  24. package/config/enable_peek.mjs.map +1 -0
  25. package/helpers/fetch.d.ts +4 -3
  26. package/helpers/fetch.js.map +1 -1
  27. package/helpers/fetch.mjs.map +1 -1
  28. package/helpers/pageHash.js.map +1 -1
  29. package/helpers/pageHash.mjs.map +1 -1
  30. package/helpers/pageHashParams.js.map +1 -1
  31. package/helpers/pageHashParams.mjs.map +1 -1
  32. package/helpers/time.d.ts +2 -2
  33. package/helpers/time.js.map +1 -1
  34. package/helpers/time.mjs.map +1 -1
  35. package/history.js +2 -2
  36. package/history.js.map +1 -1
  37. package/history.mjs +3 -3
  38. package/history.mjs.map +1 -1
  39. package/index.d.ts +30 -9
  40. package/index.js +877 -661
  41. package/index.js.map +1 -1
  42. package/index.mjs +874 -658
  43. package/index.mjs.map +1 -1
  44. package/package.json +22 -25
  45. package/persist-plugins/async-storage.d.ts +3 -3
  46. package/persist-plugins/async-storage.js +8 -7
  47. package/persist-plugins/async-storage.js.map +1 -1
  48. package/persist-plugins/async-storage.mjs +9 -8
  49. package/persist-plugins/async-storage.mjs.map +1 -1
  50. package/persist-plugins/fetch.js.map +1 -1
  51. package/persist-plugins/fetch.mjs.map +1 -1
  52. package/persist-plugins/firebase.d.ts +1 -1
  53. package/persist-plugins/firebase.js +12 -11
  54. package/persist-plugins/firebase.js.map +1 -1
  55. package/persist-plugins/firebase.mjs +13 -12
  56. package/persist-plugins/firebase.mjs.map +1 -1
  57. package/persist-plugins/indexeddb.d.ts +10 -10
  58. package/persist-plugins/indexeddb.js +2 -2
  59. package/persist-plugins/indexeddb.js.map +1 -1
  60. package/persist-plugins/indexeddb.mjs +2 -2
  61. package/persist-plugins/indexeddb.mjs.map +1 -1
  62. package/persist-plugins/local-storage.d.ts +3 -4
  63. package/persist-plugins/local-storage.js +19 -7
  64. package/persist-plugins/local-storage.js.map +1 -1
  65. package/persist-plugins/local-storage.mjs +20 -9
  66. package/persist-plugins/local-storage.mjs.map +1 -1
  67. package/persist-plugins/mmkv.d.ts +8 -8
  68. package/persist-plugins/mmkv.js +5 -4
  69. package/persist-plugins/mmkv.js.map +1 -1
  70. package/persist-plugins/mmkv.mjs +6 -5
  71. package/persist-plugins/mmkv.mjs.map +1 -1
  72. package/persist-plugins/query.js.map +1 -1
  73. package/persist-plugins/query.mjs.map +1 -1
  74. package/persist.d.ts +2 -14
  75. package/persist.js +1250 -268
  76. package/persist.js.map +1 -1
  77. package/persist.mjs +1250 -269
  78. package/persist.mjs.map +1 -1
  79. package/react-hooks/createObservableHook.js +1 -1
  80. package/react-hooks/createObservableHook.js.map +1 -1
  81. package/react-hooks/createObservableHook.mjs +1 -1
  82. package/react-hooks/createObservableHook.mjs.map +1 -1
  83. package/react-hooks/useFetch.d.ts +4 -3
  84. package/react-hooks/useFetch.js.map +1 -1
  85. package/react-hooks/useFetch.mjs.map +1 -1
  86. package/react-hooks/useHover.js.map +1 -1
  87. package/react-hooks/useHover.mjs.map +1 -1
  88. package/react-hooks/useMeasure.js.map +1 -1
  89. package/react-hooks/useMeasure.mjs.map +1 -1
  90. package/react-hooks/useObservableNextRouter.js.map +1 -1
  91. package/react-hooks/useObservableNextRouter.mjs.map +1 -1
  92. package/react-hooks/useObservableQuery.js.map +1 -1
  93. package/react-hooks/useObservableQuery.mjs.map +1 -1
  94. package/react-hooks/usePersistedObservable.d.ts +5 -3
  95. package/react-hooks/usePersistedObservable.js +5 -2
  96. package/react-hooks/usePersistedObservable.js.map +1 -1
  97. package/react-hooks/usePersistedObservable.mjs +5 -2
  98. package/react-hooks/usePersistedObservable.mjs.map +1 -1
  99. package/react.js +61 -75
  100. package/react.js.map +1 -1
  101. package/react.mjs +61 -75
  102. package/react.mjs.map +1 -1
  103. package/src/ObservableObject.ts +1184 -0
  104. package/src/ObservablePrimitive.ts +62 -0
  105. package/src/babel/index.ts +70 -0
  106. package/src/batching.ts +372 -0
  107. package/src/computed.ts +16 -0
  108. package/src/config/enable$get.ts +30 -0
  109. package/src/config/enableReactComponents.ts +26 -0
  110. package/src/config/enableReactNativeComponents.ts +102 -0
  111. package/src/config/enableReactTracking.ts +60 -0
  112. package/src/config/enableReactUse.ts +23 -0
  113. package/src/config/enable_peek.ts +31 -0
  114. package/src/config.ts +47 -0
  115. package/src/createObservable.ts +46 -0
  116. package/src/event.ts +26 -0
  117. package/src/globals.ts +224 -0
  118. package/src/helpers/fetch.ts +26 -0
  119. package/src/helpers/pageHash.ts +41 -0
  120. package/src/helpers/pageHashParams.ts +55 -0
  121. package/src/helpers/time.ts +30 -0
  122. package/src/helpers.ts +221 -0
  123. package/src/history/trackHistory.ts +29 -0
  124. package/src/is.ts +56 -0
  125. package/src/linked.ts +6 -0
  126. package/src/observable.ts +32 -0
  127. package/src/observableInterfaces.ts +165 -0
  128. package/src/observableTypes.ts +221 -0
  129. package/src/observe.ts +89 -0
  130. package/src/onChange.ts +136 -0
  131. package/src/persist/configureObservablePersistence.ts +7 -0
  132. package/src/persist/fieldTransformer.ts +149 -0
  133. package/src/persist/observablePersistRemoteFunctionsAdapter.ts +39 -0
  134. package/src/persist/persistObservable.ts +1029 -0
  135. package/src/persist-plugins/async-storage.ts +102 -0
  136. package/src/persist-plugins/fetch.ts +33 -0
  137. package/src/persist-plugins/firebase.ts +1050 -0
  138. package/src/persist-plugins/indexeddb.ts +433 -0
  139. package/src/persist-plugins/local-storage.ts +90 -0
  140. package/src/persist-plugins/mmkv.ts +90 -0
  141. package/src/persist-plugins/query.ts +133 -0
  142. package/src/persistTypes.ts +226 -0
  143. package/src/proxy.ts +28 -0
  144. package/src/react/Computed.tsx +7 -0
  145. package/src/react/For.tsx +116 -0
  146. package/src/react/Memo.tsx +4 -0
  147. package/src/react/Reactive.tsx +53 -0
  148. package/src/react/Show.tsx +33 -0
  149. package/src/react/Switch.tsx +43 -0
  150. package/src/react/react-globals.ts +3 -0
  151. package/src/react/{reactInterfaces.d.ts → reactInterfaces.ts} +15 -7
  152. package/src/react/reactive-observer.tsx +210 -0
  153. package/src/react/useComputed.ts +36 -0
  154. package/src/react/useEffectOnce.ts +41 -0
  155. package/src/react/useIsMounted.ts +16 -0
  156. package/src/react/useMount.ts +15 -0
  157. package/src/react/useObservable.ts +24 -0
  158. package/src/react/useObservableReducer.ts +52 -0
  159. package/src/react/useObservableState.ts +30 -0
  160. package/src/react/useObserve.ts +54 -0
  161. package/src/react/useObserveEffect.ts +40 -0
  162. package/src/react/usePauseProvider.tsx +13 -0
  163. package/src/react/useSelector.ts +167 -0
  164. package/src/react/useUnmount.ts +8 -0
  165. package/src/react/useWhen.ts +9 -0
  166. package/src/react-hooks/createObservableHook.ts +53 -0
  167. package/src/react-hooks/useFetch.ts +16 -0
  168. package/src/react-hooks/useHover.ts +40 -0
  169. package/src/react-hooks/useMeasure.ts +48 -0
  170. package/src/react-hooks/useObservableNextRouter.ts +137 -0
  171. package/src/react-hooks/useObservableQuery.ts +205 -0
  172. package/src/react-hooks/usePersistedObservable.ts +24 -0
  173. package/src/retry.ts +69 -0
  174. package/src/setupTracking.ts +26 -0
  175. package/src/sync/activateSyncedNode.ts +146 -0
  176. package/src/sync/configureObservableSync.ts +7 -0
  177. package/src/sync/syncHelpers.ts +15 -0
  178. package/src/sync/syncObservable.ts +989 -0
  179. package/src/sync/syncObservableAdapter.ts +30 -0
  180. package/src/sync/synced.ts +20 -0
  181. package/src/sync-plugins/fetch.ts +42 -0
  182. package/src/syncTypes.ts +163 -0
  183. package/src/trace/traceHelpers.ts +11 -0
  184. package/src/trace/useTraceListeners.ts +34 -0
  185. package/src/trace/useTraceUpdates.ts +24 -0
  186. package/src/trace/useVerifyNotTracking.ts +33 -0
  187. package/src/trace/useVerifyOneRender.ts +10 -0
  188. package/src/trackSelector.ts +52 -0
  189. package/src/tracking.ts +43 -0
  190. package/src/types/babel.d.ts +12 -0
  191. package/src/when.ts +70 -0
  192. package/sync-plugins/fetch.d.ts +11 -0
  193. package/sync-plugins/fetch.js +24 -0
  194. package/sync-plugins/fetch.js.map +1 -0
  195. package/sync-plugins/fetch.mjs +22 -0
  196. package/sync-plugins/fetch.mjs.map +1 -0
  197. package/sync.d.ts +8 -0
  198. package/sync.js +919 -0
  199. package/sync.js.map +1 -0
  200. package/sync.mjs +912 -0
  201. package/sync.mjs.map +1 -0
  202. package/trace.js +13 -10
  203. package/trace.js.map +1 -1
  204. package/trace.mjs +11 -8
  205. package/trace.mjs.map +1 -1
  206. package/types/babel.d.ts +3 -3
  207. package/config/enableDirectAccess.d.ts +0 -7
  208. package/config/enableDirectAccess.js +0 -25
  209. package/config/enableDirectAccess.js.map +0 -1
  210. package/config/enableDirectAccess.mjs +0 -23
  211. package/config/enableDirectAccess.mjs.map +0 -1
  212. package/config/enableDirectPeek.d.ts +0 -7
  213. package/config/enableDirectPeek.js.map +0 -1
  214. package/config/enableDirectPeek.mjs.map +0 -1
  215. package/config/enableReactDirectRender.d.ts +0 -2
  216. package/config/enableReactDirectRender.js +0 -78
  217. package/config/enableReactDirectRender.js.map +0 -1
  218. package/config/enableReactDirectRender.mjs +0 -75
  219. package/config/enableReactDirectRender.mjs.map +0 -1
  220. package/src/ObservableObject.d.ts +0 -14
  221. package/src/ObservablePrimitive.d.ts +0 -7
  222. package/src/babel/index.d.ts +0 -17
  223. package/src/batching.d.ts +0 -6
  224. package/src/computed.d.ts +0 -4
  225. package/src/config/enableDirectAccess.d.ts +0 -7
  226. package/src/config/enableDirectPeek.d.ts +0 -7
  227. package/src/config/enableReactComponents.d.ts +0 -7
  228. package/src/config/enableReactDirectRender.d.ts +0 -2
  229. package/src/config/enableReactNativeComponents.d.ts +0 -20
  230. package/src/config/enableReactTracking.d.ts +0 -15
  231. package/src/config/enableReactUse.d.ts +0 -7
  232. package/src/config.d.ts +0 -8
  233. package/src/createObservable.d.ts +0 -2
  234. package/src/event.d.ts +0 -2
  235. package/src/globals.d.ts +0 -32
  236. package/src/helpers/fetch.d.ts +0 -6
  237. package/src/helpers/pageHash.d.ts +0 -7
  238. package/src/helpers/pageHashParams.d.ts +0 -7
  239. package/src/helpers/time.d.ts +0 -3
  240. package/src/helpers.d.ts +0 -13
  241. package/src/history/trackHistory.d.ts +0 -4
  242. package/src/is.d.ts +0 -10
  243. package/src/observable.d.ts +0 -16
  244. package/src/observableInterfaces.d.ts +0 -456
  245. package/src/observe.d.ts +0 -6
  246. package/src/onChange.d.ts +0 -7
  247. package/src/persist/configureObservablePersistence.d.ts +0 -3
  248. package/src/persist/fieldTransformer.d.ts +0 -8
  249. package/src/persist/observablePersistRemoteFunctionsAdapter.d.ts +0 -2
  250. package/src/persist/persistActivateNode.d.ts +0 -1
  251. package/src/persist/persistHelpers.d.ts +0 -1
  252. package/src/persist/persistObservable.d.ts +0 -25
  253. package/src/persist-plugins/async-storage.d.ts +0 -14
  254. package/src/persist-plugins/fetch.d.ts +0 -10
  255. package/src/persist-plugins/firebase.d.ts +0 -51
  256. package/src/persist-plugins/indexeddb.d.ts +0 -25
  257. package/src/persist-plugins/local-storage.d.ts +0 -21
  258. package/src/persist-plugins/mmkv.d.ts +0 -14
  259. package/src/persist-plugins/query.d.ts +0 -18
  260. package/src/proxy.d.ts +0 -5
  261. package/src/react/Computed.d.ts +0 -5
  262. package/src/react/For.d.ts +0 -15
  263. package/src/react/Memo.d.ts +0 -3
  264. package/src/react/Reactive.d.ts +0 -9
  265. package/src/react/Show.d.ts +0 -18
  266. package/src/react/Switch.d.ts +0 -14
  267. package/src/react/react-globals.d.ts +0 -3
  268. package/src/react/reactive-observer.d.ts +0 -14
  269. package/src/react/useComputed.d.ts +0 -5
  270. package/src/react/useEffectOnce.d.ts +0 -1
  271. package/src/react/useIsMounted.d.ts +0 -2
  272. package/src/react/useMount.d.ts +0 -2
  273. package/src/react/useObservable.d.ts +0 -9
  274. package/src/react/useObservableReducer.d.ts +0 -7
  275. package/src/react/useObservableState.d.ts +0 -2
  276. package/src/react/useObserve.d.ts +0 -4
  277. package/src/react/useObserveEffect.d.ts +0 -4
  278. package/src/react/usePauseProvider.d.ts +0 -8
  279. package/src/react/useSelector.d.ts +0 -3
  280. package/src/react/useUnmount.d.ts +0 -2
  281. package/src/react/useWhen.d.ts +0 -3
  282. package/src/react-hooks/createObservableHook.d.ts +0 -2
  283. package/src/react-hooks/useFetch.d.ts +0 -6
  284. package/src/react-hooks/useHover.d.ts +0 -3
  285. package/src/react-hooks/useMeasure.d.ts +0 -6
  286. package/src/react-hooks/useObservableNextRouter.d.ts +0 -33
  287. package/src/react-hooks/useObservableQuery.d.ts +0 -6
  288. package/src/react-hooks/usePersistedObservable.d.ts +0 -11
  289. package/src/retry.d.ts +0 -9
  290. package/src/setupTracking.d.ts +0 -2
  291. package/src/trace/traceHelpers.d.ts +0 -2
  292. package/src/trace/useTraceListeners.d.ts +0 -1
  293. package/src/trace/useTraceUpdates.d.ts +0 -1
  294. package/src/trace/useVerifyNotTracking.d.ts +0 -1
  295. package/src/trace/useVerifyOneRender.d.ts +0 -1
  296. package/src/trackSelector.d.ts +0 -7
  297. package/src/tracking.d.ts +0 -13
  298. package/src/when.d.ts +0 -3
package/sync.js ADDED
@@ -0,0 +1,919 @@
1
+ 'use strict';
2
+
3
+ var state = require('@legendapp/state');
4
+
5
+ const observableSyncConfiguration = {};
6
+ function configureObservableSync(options) {
7
+ Object.assign(observableSyncConfiguration, options);
8
+ }
9
+
10
+ function removeNullUndefined(val) {
11
+ if (val) {
12
+ Object.keys(val).forEach((key) => {
13
+ const v = val[key];
14
+ if (v === null || v === undefined) {
15
+ delete val[key];
16
+ }
17
+ else if (state.isObject(v)) {
18
+ removeNullUndefined(v);
19
+ }
20
+ });
21
+ }
22
+ return val;
23
+ }
24
+
25
+ function syncObservableAdapter({ get, set }) {
26
+ const ret = {};
27
+ if (get) {
28
+ ret.get = (async (params) => {
29
+ try {
30
+ let value = get(params);
31
+ if (state.isPromise(value)) {
32
+ value = await value;
33
+ }
34
+ params.onChange({
35
+ value,
36
+ lastSync: params.lastSync,
37
+ mode: params.mode,
38
+ });
39
+ params.onGet();
40
+ // eslint-disable-next-line no-empty
41
+ }
42
+ catch (_a) { }
43
+ });
44
+ }
45
+ if (set) {
46
+ ret.set = set;
47
+ }
48
+ return ret;
49
+ }
50
+
51
+ const { createPreviousHandler, globalState: globalState$1, symbolLinked: symbolLinked$2, getNode, getNodeValue: getNodeValue$1 } = state.internal;
52
+ const mapSyncPlugins = new WeakMap();
53
+ const metadatas = new WeakMap();
54
+ const promisesLocalSaves = new Set();
55
+ function parseLocalConfig(config) {
56
+ return config
57
+ ? state.isString(config)
58
+ ? { table: config, config: { name: config } }
59
+ : { table: config.name, config }
60
+ : {};
61
+ }
62
+ function doInOrder(arg1, arg2) {
63
+ return state.isPromise(arg1) ? arg1.then(arg2) : arg2(arg1);
64
+ }
65
+ function onChangeRemote(cb) {
66
+ state.endBatch(true);
67
+ // Remote changes should only update local state
68
+ globalState$1.isLoadingRemote = true;
69
+ state.beginBatch();
70
+ cb();
71
+ // Reset isLoadingRemote before ending the batch so it doesn't
72
+ // apply to any side effects
73
+ globalState$1.isLoadingRemote = false;
74
+ state.endBatch(true);
75
+ }
76
+ function transformSaveData(value, path, pathTypes, { transform }) {
77
+ if (transform === null || transform === void 0 ? void 0 : transform.save) {
78
+ const constructed = state.constructObjectWithPath(path, pathTypes, value);
79
+ const saved = transform.save(constructed);
80
+ value = state.deconstructObjectWithPath(path, pathTypes, saved);
81
+ }
82
+ return value;
83
+ }
84
+ function transformLoadData(value, { transform }, doUserTransform) {
85
+ if (doUserTransform && (transform === null || transform === void 0 ? void 0 : transform.load)) {
86
+ value = transform.load(value);
87
+ }
88
+ return value;
89
+ }
90
+ async function updateMetadataImmediate(obs, localState, syncState, syncOptions, newMetadata) {
91
+ const saves = Array.from(promisesLocalSaves);
92
+ if (saves.length > 0) {
93
+ await Promise.all(saves);
94
+ }
95
+ const { pluginPersist } = localState;
96
+ const { table, config } = parseLocalConfig(syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.persist);
97
+ // Save metadata
98
+ const oldMetadata = metadatas.get(obs);
99
+ const { lastSync, pending } = newMetadata;
100
+ const needsUpdate = pending || (lastSync && (!oldMetadata || lastSync !== oldMetadata.lastSync));
101
+ if (needsUpdate) {
102
+ const metadata = Object.assign({}, oldMetadata, newMetadata);
103
+ metadatas.set(obs, metadata);
104
+ if (pluginPersist) {
105
+ await pluginPersist.setMetadata(table, metadata, config);
106
+ }
107
+ if (lastSync) {
108
+ syncState.assign({
109
+ lastSync: lastSync,
110
+ });
111
+ }
112
+ }
113
+ }
114
+ function updateMetadata(obs, localState, syncState, syncOptions, newMetadata) {
115
+ if (localState.timeoutSaveMetadata) {
116
+ clearTimeout(localState.timeoutSaveMetadata);
117
+ }
118
+ localState.timeoutSaveMetadata = setTimeout(() => updateMetadataImmediate(obs, localState, syncState, syncOptions, newMetadata), 0);
119
+ }
120
+ let _queuedChanges = [];
121
+ const _queuedRemoteChanges = new Map();
122
+ const _queuedRemoteChangesTimeouts = new Map();
123
+ function mergeChanges(changes) {
124
+ const changesByPath = new Map();
125
+ const changesOut = [];
126
+ // TODO: This could be even more robust by going deeper into paths like the firebase plugin's _updatePendingSave
127
+ for (let i = 0; i < changes.length; i++) {
128
+ const change = changes[i];
129
+ const pathStr = change.path.join('/');
130
+ const existing = changesByPath.get(pathStr);
131
+ if (existing) {
132
+ existing.valueAtPath = change.valueAtPath;
133
+ }
134
+ else {
135
+ changesByPath.set(pathStr, change);
136
+ changesOut.push(change);
137
+ }
138
+ }
139
+ return changesOut;
140
+ }
141
+ function mergeQueuedChanges(allChanges) {
142
+ const changesByObsRemote = new Map();
143
+ const changesByObsLocal = new Map();
144
+ const previousByObs = new Map();
145
+ const outRemote = new Map();
146
+ const outLocal = new Map();
147
+ for (let i = 0; i < allChanges.length; i++) {
148
+ const value = allChanges[i];
149
+ const { obs, changes, inRemoteChange, getPrevious } = value;
150
+ const changesMap = inRemoteChange ? changesByObsRemote : changesByObsLocal;
151
+ const existing = changesMap.get(obs);
152
+ const newChanges = existing ? [...existing, ...changes] : changes;
153
+ const merged = mergeChanges(newChanges);
154
+ changesMap.set(obs, merged);
155
+ value.changes = merged;
156
+ if (!previousByObs.has(obs)) {
157
+ previousByObs.set(obs, getPrevious());
158
+ }
159
+ value.valuePrevious = previousByObs.get(obs);
160
+ (inRemoteChange ? outRemote : outLocal).set(obs, value);
161
+ }
162
+ return Array.from(outRemote.values()).concat(Array.from(outLocal.values()));
163
+ }
164
+ async function processQueuedChanges() {
165
+ var _a;
166
+ // Get a local copy of the queued changes and clear the global queue
167
+ const queuedChanges = mergeQueuedChanges(_queuedChanges);
168
+ _queuedChanges = [];
169
+ const pendingSyncOptions = new Set();
170
+ for (let i = 0; i < queuedChanges.length; i++) {
171
+ const change = queuedChanges[i];
172
+ if (!change.inRemoteChange) {
173
+ if (!_queuedRemoteChanges.has(change.syncOptions)) {
174
+ _queuedRemoteChanges.set(change.syncOptions, []);
175
+ }
176
+ pendingSyncOptions.add(change.syncOptions);
177
+ _queuedRemoteChanges.get(change.syncOptions).push(change);
178
+ }
179
+ }
180
+ // Note: Summary of the order of operations these functions:
181
+ // 1. Prepare all changes for saving. This may involve waiting for promises if the user has asynchronous transform.
182
+ // We need to prepare all of the changes in the queue before saving so that the saves happen in the correct order,
183
+ // since some may take longer to transformSaveData than others.
184
+ // 2. Save pending to the metadata table first. If this is the only operation that succeeds, it would try to save
185
+ // the current value again on next load, which isn't too bad.
186
+ // 3. Save local changes to storage. If they never make it to remote, then on the next load they will be pending
187
+ // and attempted again.
188
+ // 4. Wait for remote load or error if allowed
189
+ // 5. Save to remote
190
+ // 6. On successful save, merge changes (if any) back into observable
191
+ // 7. Lastly, update metadata to clear pending and update lastSync. Doing this earlier could potentially cause
192
+ // sync inconsistences so it's very important that this is last.
193
+ const preppedChangesLocal = await Promise.all(queuedChanges.map(prepChangeLocal));
194
+ // TODO Clean this up: We only need to prep this now in order to save pending changes, don't need any of the other stuff. Should split that up?
195
+ await Promise.all(queuedChanges.map(prepChangeRemote));
196
+ await Promise.all(preppedChangesLocal.map(doChangeLocal));
197
+ for (const options of pendingSyncOptions) {
198
+ const timeout = (_a = options.debounceSet) !== null && _a !== void 0 ? _a : observableSyncConfiguration === null || observableSyncConfiguration === void 0 ? void 0 : observableSyncConfiguration.debounceSet;
199
+ const timeoutSaveRemote = _queuedRemoteChangesTimeouts.get(options);
200
+ const run = () => processQueuedRemoteChanges(options);
201
+ if (timeout) {
202
+ if (timeoutSaveRemote) {
203
+ clearTimeout(timeoutSaveRemote);
204
+ }
205
+ _queuedRemoteChangesTimeouts.set(options, setTimeout(run, timeout));
206
+ }
207
+ else {
208
+ run();
209
+ }
210
+ }
211
+ }
212
+ async function processQueuedRemoteChanges(syncOptions) {
213
+ const arr = _queuedRemoteChanges.get(syncOptions);
214
+ if (arr === null || arr === void 0 ? void 0 : arr.length) {
215
+ const queuedRemoteChanges = mergeQueuedChanges(arr);
216
+ _queuedRemoteChanges.set(syncOptions, []);
217
+ const preppedChangesRemote = await Promise.all(queuedRemoteChanges.map(prepChangeRemote));
218
+ preppedChangesRemote.forEach(doChangeRemote);
219
+ }
220
+ }
221
+ async function prepChangeLocal(queuedChange) {
222
+ const { syncState, changes, localState, syncOptions, inRemoteChange, isApplyingPending } = queuedChange;
223
+ const persist = syncOptions.persist;
224
+ const { pluginSync } = localState;
225
+ const { config: configLocal } = parseLocalConfig(persist);
226
+ const configRemote = syncOptions;
227
+ const saveLocal = (persist === null || persist === void 0 ? void 0 : persist.name) && !configLocal.readonly && !isApplyingPending && syncState.isPersistEnabled.peek();
228
+ const saveRemote = !!(!inRemoteChange &&
229
+ (pluginSync === null || pluginSync === void 0 ? void 0 : pluginSync.set) &&
230
+ (configRemote === null || configRemote === void 0 ? void 0 : configRemote.enableSync) !== false &&
231
+ syncState.isEnabledRemote.peek());
232
+ if (saveLocal || saveRemote) {
233
+ if (saveLocal && !syncState.isPersistLoaded.peek()) {
234
+ console.error('[legend-state] WARNING: An observable was changed before being loaded from persist', persist);
235
+ return undefined;
236
+ }
237
+ const changesLocal = [];
238
+ const changesPaths = new Set();
239
+ let promisesTransform = [];
240
+ // Reverse order
241
+ for (let i = changes.length - 1; i >= 0; i--) {
242
+ const { path } = changes[i];
243
+ let found = false;
244
+ // Optimization to only save the latest update at each path. We might have multiple changes at the same path
245
+ // and we only need the latest value, so it starts from the end of the array, skipping any earlier changes
246
+ // already processed. If a later change modifies a parent of an earlier change (which happens on delete()
247
+ // it should be ignored as it's superseded by the parent modification.
248
+ if (changesPaths.size > 0) {
249
+ for (let u = 0; u < path.length; u++) {
250
+ if (changesPaths.has((u === path.length - 1 ? path : path.slice(0, u + 1)).join('/'))) {
251
+ found = true;
252
+ break;
253
+ }
254
+ }
255
+ }
256
+ if (!found) {
257
+ const pathStr = path.join('/');
258
+ changesPaths.add(pathStr);
259
+ const { prevAtPath, valueAtPath, pathTypes } = changes[i];
260
+ if (saveLocal) {
261
+ const promiseTransformLocal = transformSaveData(valueAtPath, path, pathTypes, configLocal);
262
+ promisesTransform.push(doInOrder(promiseTransformLocal, (valueTransformed) => {
263
+ // Prepare the local change with the transformed path/value
264
+ changesLocal.push({
265
+ path,
266
+ pathTypes,
267
+ prevAtPath,
268
+ valueAtPath: valueTransformed,
269
+ pathStr,
270
+ });
271
+ }));
272
+ }
273
+ }
274
+ }
275
+ // If there's any transform promises, wait for them before saving
276
+ promisesTransform = promisesTransform.filter(Boolean);
277
+ if (promisesTransform.length > 0) {
278
+ await Promise.all(promisesTransform);
279
+ }
280
+ return { queuedChange, changesLocal, saveRemote };
281
+ }
282
+ }
283
+ async function prepChangeRemote(queuedChange) {
284
+ const { syncState, changes, localState, syncOptions: syncOptions, inRemoteChange, isApplyingPending, valuePrevious, } = queuedChange;
285
+ const persist = syncOptions.persist;
286
+ const { pluginSync } = localState;
287
+ const { config: configLocal } = parseLocalConfig(persist);
288
+ const configRemote = syncOptions;
289
+ const saveLocal = persist && !configLocal.readonly && !isApplyingPending && syncState.isPersistEnabled.peek();
290
+ const saveRemote = !inRemoteChange && (pluginSync === null || pluginSync === void 0 ? void 0 : pluginSync.set) && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.enableSync) !== false && syncState.isEnabledRemote.peek();
291
+ if (saveLocal || saveRemote) {
292
+ if (saveLocal && !syncState.isPersistLoaded.peek()) {
293
+ console.error('[legend-state] WARNING: An observable was changed before being loaded from persist', persist);
294
+ return undefined;
295
+ }
296
+ const changesRemote = [];
297
+ const changesPaths = new Set();
298
+ let promisesTransform = [];
299
+ // Reverse order
300
+ for (let i = changes.length - 1; i >= 0; i--) {
301
+ const { path } = changes[i];
302
+ let found = false;
303
+ // Optimization to only save the latest update at each path. We might have multiple changes at the same path
304
+ // and we only need the latest value, so it starts from the end of the array, skipping any earlier changes
305
+ // already processed. If a later change modifies a parent of an earlier change (which happens on delete()
306
+ // it should be ignored as it's superseded by the parent modification.
307
+ if (changesPaths.size > 0) {
308
+ for (let u = 0; u < path.length; u++) {
309
+ if (changesPaths.has((u === path.length - 1 ? path : path.slice(0, u + 1)).join('/'))) {
310
+ found = true;
311
+ break;
312
+ }
313
+ }
314
+ }
315
+ if (!found) {
316
+ const pathStr = path.join('/');
317
+ changesPaths.add(pathStr);
318
+ const { prevAtPath, valueAtPath, pathTypes } = changes[i];
319
+ if (saveRemote) {
320
+ const promiseTransformRemote = transformSaveData(valueAtPath, path, pathTypes, configRemote || {});
321
+ promisesTransform.push(doInOrder(promiseTransformRemote, (valueTransformed) => {
322
+ var _a;
323
+ // Prepare pending changes
324
+ if (!localState.pendingChanges) {
325
+ localState.pendingChanges = {};
326
+ }
327
+ // First look for existing pending changes at a higher level than this change
328
+ // If they exist then merge this change into it
329
+ let found = false;
330
+ for (let i = 0; !found && i < path.length - 1; i++) {
331
+ const pathParent = path.slice(0, i + 1).join('/');
332
+ if ((_a = localState.pendingChanges[pathParent]) === null || _a === void 0 ? void 0 : _a.v) {
333
+ found = true;
334
+ const pathChild = path.slice(i + 1);
335
+ const pathTypesChild = pathTypes.slice(i + 1);
336
+ state.setAtPath(localState.pendingChanges[pathParent].v, pathChild, pathTypesChild, valueAtPath);
337
+ }
338
+ }
339
+ if (!found) {
340
+ // If an existing pending change is deeper than this change, just delete it
341
+ // in favor of this wider change
342
+ for (const key in localState.pendingChanges) {
343
+ if (key !== pathStr && key.startsWith(pathStr)) {
344
+ delete localState.pendingChanges[key];
345
+ }
346
+ }
347
+ // The "p" saved in pending should be the previous state before changes,
348
+ // so don't overwrite it if it already exists
349
+ if (!localState.pendingChanges[pathStr]) {
350
+ localState.pendingChanges[pathStr] = { p: prevAtPath !== null && prevAtPath !== void 0 ? prevAtPath : null, t: pathTypes };
351
+ }
352
+ // Pending value is the untransformed value because it gets loaded without transformment
353
+ // and forwarded through to onObsChange where it gets transformed before save
354
+ localState.pendingChanges[pathStr].v = valueAtPath;
355
+ }
356
+ // Prepare the remote change with the transformed path/value
357
+ changesRemote.push({
358
+ path,
359
+ pathTypes,
360
+ prevAtPath,
361
+ valueAtPath: valueTransformed,
362
+ pathStr,
363
+ valuePrevious,
364
+ });
365
+ }));
366
+ }
367
+ }
368
+ }
369
+ // If there's any transform promises, wait for them before saving
370
+ promisesTransform = promisesTransform.filter(Boolean);
371
+ if (promisesTransform.length > 0) {
372
+ await Promise.all(promisesTransform);
373
+ }
374
+ return { queuedChange, changesRemote };
375
+ }
376
+ }
377
+ async function doChangeLocal(changeInfo) {
378
+ if (!changeInfo)
379
+ return;
380
+ const { queuedChange, changesLocal, saveRemote } = changeInfo;
381
+ const { obs, syncState, localState, syncOptions: syncOptions } = queuedChange;
382
+ const { pluginPersist } = localState;
383
+ const persist = syncOptions.persist;
384
+ const { table, config: configLocal } = parseLocalConfig(persist);
385
+ const configRemote = syncOptions;
386
+ const shouldSaveMetadata = persist && (configRemote === null || configRemote === void 0 ? void 0 : configRemote.offlineBehavior) === 'retry';
387
+ if (saveRemote && shouldSaveMetadata) {
388
+ // First save pending changes before saving local or remote
389
+ await updateMetadataImmediate(obs, localState, syncState, syncOptions, {
390
+ pending: localState.pendingChanges,
391
+ });
392
+ }
393
+ if (changesLocal.length > 0) {
394
+ // Save the changes to local cache before saving to remote. They are already marked as pending so
395
+ // if remote sync fails or the app is closed before remote sync, it will attempt to sync them on the next load.
396
+ let promiseSet = pluginPersist.set(table, changesLocal, configLocal);
397
+ if (promiseSet) {
398
+ promiseSet = promiseSet.then(() => {
399
+ promisesLocalSaves.delete(promiseSet);
400
+ });
401
+ // Keep track of local save promises so that updateMetadata runs only after everything is saved
402
+ promisesLocalSaves.add(promiseSet);
403
+ // await the local save before proceeding to save remotely
404
+ await promiseSet;
405
+ }
406
+ }
407
+ }
408
+ async function doChangeRemote(changeInfo) {
409
+ var _a, _b;
410
+ if (!changeInfo)
411
+ return;
412
+ const { queuedChange, changesRemote } = changeInfo;
413
+ const { obs, syncState, localState, syncOptions, valuePrevious: previous } = queuedChange;
414
+ const { pluginPersist, pluginSync } = localState;
415
+ const persist = syncOptions.persist;
416
+ const { table, config: configLocal } = parseLocalConfig(persist);
417
+ const { offlineBehavior, allowSetIfGetError, onBeforeSet, onSetError, waitForSet, onAfterSet } = syncOptions || {};
418
+ const shouldSaveMetadata = persist && offlineBehavior === 'retry';
419
+ if (changesRemote.length > 0) {
420
+ // Wait for remote to be ready before saving
421
+ await state.when(() => syncState.isLoaded.get() || (allowSetIfGetError && syncState.error.get()));
422
+ if (waitForSet) {
423
+ const waitFor = state.isFunction(waitForSet)
424
+ ? waitForSet({ changes: changesRemote, value: obs.peek() })
425
+ : waitForSet;
426
+ if (waitFor) {
427
+ await state.when(waitFor);
428
+ }
429
+ }
430
+ let value = obs.peek();
431
+ const transformSave = (_a = syncOptions === null || syncOptions === void 0 ? void 0 : syncOptions.transform) === null || _a === void 0 ? void 0 : _a.save;
432
+ if (transformSave) {
433
+ value = transformSave(value);
434
+ }
435
+ onBeforeSet === null || onBeforeSet === void 0 ? void 0 : onBeforeSet();
436
+ localState.numSavesOutstanding = (localState.numSavesOutstanding || 0) + 1;
437
+ let savedPromise = pluginSync.set({
438
+ obs,
439
+ syncState: syncState,
440
+ options: syncOptions,
441
+ changes: changesRemote,
442
+ value,
443
+ valuePrevious: previous,
444
+ });
445
+ if (state.isPromise(savedPromise)) {
446
+ savedPromise = savedPromise.catch((err) => onSetError === null || onSetError === void 0 ? void 0 : onSetError(err));
447
+ }
448
+ const saved = await savedPromise;
449
+ localState.numSavesOutstanding--;
450
+ // If this remote save changed anything then update cache and metadata
451
+ // Because save happens after a timeout and they're batched together, some calls to save will
452
+ // return saved data and others won't, so those can be ignored.
453
+ if (saved) {
454
+ const pathStrs = Array.from(new Set(changesRemote.map((change) => change.pathStr)));
455
+ const { changes, lastSync } = saved;
456
+ if (pathStrs.length > 0) {
457
+ if (persist) {
458
+ const metadata = {};
459
+ const pendingMetadata = (_b = pluginPersist.getMetadata(table, configLocal)) === null || _b === void 0 ? void 0 : _b.pending;
460
+ const pending = localState.pendingChanges;
461
+ let transformedChanges = undefined;
462
+ for (let i = 0; i < pathStrs.length; i++) {
463
+ const pathStr = pathStrs[i];
464
+ // Clear pending for this path
465
+ if (pending === null || pending === void 0 ? void 0 : pending[pathStr]) {
466
+ // Remove pending from local state
467
+ delete pending[pathStr];
468
+ }
469
+ // Clear pending for this path
470
+ if (pendingMetadata === null || pendingMetadata === void 0 ? void 0 : pendingMetadata[pathStr]) {
471
+ // Remove pending from persisted medata state
472
+ delete pendingMetadata[pathStr];
473
+ metadata.pending = pendingMetadata;
474
+ }
475
+ }
476
+ if (lastSync) {
477
+ metadata.lastSync = lastSync;
478
+ }
479
+ // Remote can optionally have data that needs to be merged back into the observable,
480
+ // for example Firebase may update dateModified with the server timestamp
481
+ if (changes && !state.isEmpty(changes)) {
482
+ transformedChanges = transformLoadData(changes, syncOptions, false);
483
+ }
484
+ if (localState.numSavesOutstanding > 0) {
485
+ if (transformedChanges) {
486
+ if (!localState.pendingSaveResults) {
487
+ localState.pendingSaveResults = [];
488
+ }
489
+ localState.pendingSaveResults.push(transformedChanges);
490
+ }
491
+ }
492
+ else {
493
+ let allChanges = [...(localState.pendingSaveResults || []), transformedChanges].filter((v) => v !== undefined);
494
+ if (allChanges.length > 0) {
495
+ if (allChanges.some((change) => state.isPromise(change))) {
496
+ allChanges = await Promise.all(allChanges);
497
+ }
498
+ onChangeRemote(() => state.mergeIntoObservable(obs, ...allChanges));
499
+ }
500
+ if (shouldSaveMetadata && !state.isEmpty(metadata)) {
501
+ updateMetadata(obs, localState, syncState, syncOptions, metadata);
502
+ }
503
+ localState.pendingSaveResults = [];
504
+ }
505
+ }
506
+ onAfterSet === null || onAfterSet === void 0 ? void 0 : onAfterSet();
507
+ }
508
+ }
509
+ }
510
+ }
511
+ function onObsChange(obs, syncState, localState, syncOptions, { changes, loading, remote, getPrevious }) {
512
+ if (!loading) {
513
+ const inRemoteChange = remote;
514
+ const isApplyingPending = localState.isApplyingPending;
515
+ // Queue changes in a microtask so that multiple changes within a frame get run together
516
+ _queuedChanges.push({
517
+ obs: obs,
518
+ syncState,
519
+ localState,
520
+ syncOptions,
521
+ changes,
522
+ inRemoteChange,
523
+ isApplyingPending: isApplyingPending,
524
+ getPrevious,
525
+ });
526
+ if (_queuedChanges.length === 1) {
527
+ queueMicrotask(processQueuedChanges);
528
+ }
529
+ }
530
+ }
531
+ async function loadLocal(obs, syncOptions, syncState, localState) {
532
+ var _a, _b;
533
+ const { persist } = syncOptions;
534
+ if (persist) {
535
+ const PersistPlugin = persist.plugin || ((_a = observableSyncConfiguration.persist) === null || _a === void 0 ? void 0 : _a.plugin);
536
+ const { table, config } = parseLocalConfig(persist);
537
+ const node = getNode(obs);
538
+ if (!PersistPlugin) {
539
+ throw new Error('Local persist is not configured');
540
+ }
541
+ // Ensure there's only one instance of the cache plugin
542
+ if (!mapSyncPlugins.has(PersistPlugin)) {
543
+ const persistPlugin = new PersistPlugin();
544
+ const mapValue = { plugin: persistPlugin, initialized: state.observable(false) };
545
+ mapSyncPlugins.set(PersistPlugin, mapValue);
546
+ if (persistPlugin.initialize) {
547
+ const initializePromise = (_b = persistPlugin.initialize) === null || _b === void 0 ? void 0 : _b.call(persistPlugin, (observableSyncConfiguration === null || observableSyncConfiguration === void 0 ? void 0 : observableSyncConfiguration.persist) || {});
548
+ if (state.isPromise(initializePromise)) {
549
+ await initializePromise;
550
+ }
551
+ }
552
+ mapValue.initialized.set(true);
553
+ }
554
+ const { plugin, initialized: initialized$ } = mapSyncPlugins.get(PersistPlugin);
555
+ const persistPlugin = plugin;
556
+ localState.pluginPersist = persistPlugin;
557
+ if (!initialized$.peek()) {
558
+ await state.when(initialized$);
559
+ }
560
+ // If cache has an asynchronous load, wait for it
561
+ if (persistPlugin.loadTable) {
562
+ const promise = persistPlugin.loadTable(table, config);
563
+ if (promise) {
564
+ await promise;
565
+ }
566
+ }
567
+ // Get current value for init
568
+ const prevValue = getNodeValue$1(node);
569
+ // Get the value from state
570
+ let value = persistPlugin.getTable(table, prevValue, config);
571
+ const metadata = persistPlugin.getMetadata(table, config);
572
+ if (metadata) {
573
+ metadatas.set(obs, metadata);
574
+ localState.pendingChanges = metadata.pending;
575
+ syncState.assign({
576
+ lastSync: metadata.lastSync,
577
+ });
578
+ }
579
+ // Merge the data from local cache into the default state
580
+ if (value !== undefined) {
581
+ const { transform } = config;
582
+ value = transformLoadData(value, { transform }, true);
583
+ if (state.isPromise(value)) {
584
+ value = await value;
585
+ }
586
+ // isLoadingLocal prevents saving remotely when two different caches
587
+ // are set on the same observable
588
+ state.internal.globalState.isLoadingLocal = true;
589
+ // We want to merge the local data on top of any initial state the object is created with
590
+ if (value === null && (!prevValue || prevValue[symbolLinked$2])) {
591
+ obs.set(value);
592
+ }
593
+ else {
594
+ state.mergeIntoObservable(obs, value);
595
+ }
596
+ state.internal.globalState.isLoadingLocal = false;
597
+ }
598
+ node.state.peek().clearPersist = () => Promise.all([
599
+ persistPlugin.deleteTable(table, config),
600
+ persistPlugin.deleteMetadata(table, config),
601
+ ]);
602
+ }
603
+ syncState.isPersistLoaded.set(true);
604
+ }
605
+ function syncObservable(obs$, syncOptions) {
606
+ const node = getNode(obs$);
607
+ // Merge remote sync options with global options
608
+ if (syncOptions) {
609
+ syncOptions = Object.assign({
610
+ syncMode: 'auto',
611
+ }, observableSyncConfiguration, removeNullUndefined(syncOptions));
612
+ }
613
+ const localState = {};
614
+ let sync;
615
+ const syncState = (node.state = state.observable({
616
+ isPersistLoaded: false,
617
+ isLoaded: !syncOptions.get,
618
+ isPersistEnabled: true,
619
+ isEnabledRemote: true,
620
+ clearPersist: undefined,
621
+ sync: () => Promise.resolve(),
622
+ getPendingChanges: () => localState.pendingChanges,
623
+ }));
624
+ loadLocal(obs$, syncOptions, syncState, localState);
625
+ localState.pluginSync = syncObservableAdapter(syncOptions);
626
+ if (syncOptions.get) {
627
+ let isSynced = false;
628
+ sync = async () => {
629
+ var _a, _b;
630
+ const lastSync = (_a = metadatas.get(obs$)) === null || _a === void 0 ? void 0 : _a.lastSync;
631
+ const pending = localState.pendingChanges;
632
+ const get = (_b = localState.pluginSync.get) === null || _b === void 0 ? void 0 : _b.bind(localState.pluginSync);
633
+ if (get) {
634
+ const runGet = () => {
635
+ get({
636
+ state: syncState,
637
+ obs: obs$,
638
+ options: syncOptions,
639
+ lastSync,
640
+ dateModified: lastSync,
641
+ onError: (error) => {
642
+ var _a;
643
+ (_a = syncOptions.onGetError) === null || _a === void 0 ? void 0 : _a.call(syncOptions, error);
644
+ },
645
+ onGet: () => {
646
+ node.state.assign({
647
+ isLoaded: true,
648
+ error: undefined,
649
+ });
650
+ },
651
+ onChange: async ({ value, mode, lastSync }) => {
652
+ mode = mode || syncOptions.mode || 'set';
653
+ if (value !== undefined) {
654
+ value = transformLoadData(value, syncOptions, true);
655
+ if (state.isPromise(value)) {
656
+ value = await value;
657
+ }
658
+ const pending = localState.pendingChanges;
659
+ if (pending) {
660
+ Object.keys(pending).forEach((key) => {
661
+ const p = key.split('/').filter((p) => p !== '');
662
+ const { v, t } = pending[key];
663
+ if (t.length === 0 || !value) {
664
+ if (state.isObject(value) && state.isObject(v)) {
665
+ Object.assign(value, v);
666
+ }
667
+ else {
668
+ value = v;
669
+ }
670
+ }
671
+ else if (value[p[0]] !== undefined) {
672
+ value = state.setAtPath(value, p, t, v, 'merge', obs$.peek(), (path, value) => {
673
+ delete pending[key];
674
+ pending[path.join('/')] = {
675
+ p: null,
676
+ v: value,
677
+ t: t.slice(0, path.length),
678
+ };
679
+ });
680
+ }
681
+ });
682
+ }
683
+ onChangeRemote(() => {
684
+ if (mode === 'assign' && state.isObject(value)) {
685
+ obs$.assign(value);
686
+ }
687
+ else if (mode === 'append' && state.isArray(value)) {
688
+ obs$.push(...value);
689
+ }
690
+ else if (mode === 'prepend' && state.isArray(value)) {
691
+ obs$.splice(0, 0, ...value);
692
+ }
693
+ else if (mode === 'merge') {
694
+ state.mergeIntoObservable(obs$, value);
695
+ }
696
+ else {
697
+ obs$.set(value);
698
+ }
699
+ });
700
+ }
701
+ if (lastSync && syncOptions.persist) {
702
+ updateMetadata(obs$, localState, syncState, syncOptions, {
703
+ lastSync,
704
+ });
705
+ }
706
+ },
707
+ });
708
+ };
709
+ runGet();
710
+ }
711
+ else {
712
+ node.state.assign({
713
+ isLoaded: true,
714
+ error: undefined,
715
+ });
716
+ }
717
+ if (!isSynced) {
718
+ isSynced = true;
719
+ // Wait for remote to be ready before saving pending
720
+ await state.when(() => syncState.isLoaded.get() || (syncOptions.allowSetIfGetError && syncState.error.get()));
721
+ if (pending && !state.isEmpty(pending)) {
722
+ localState.isApplyingPending = true;
723
+ const keys = Object.keys(pending);
724
+ // Bundle up all the changes from pending
725
+ const changes = [];
726
+ for (let i = 0; i < keys.length; i++) {
727
+ const key = keys[i];
728
+ const path = key.split('/').filter((p) => p !== '');
729
+ const { p, v, t } = pending[key];
730
+ changes.push({ path, valueAtPath: v, prevAtPath: p, pathTypes: t });
731
+ }
732
+ // Send the changes into onObsChange so that they get synced remotely
733
+ const value = getNodeValue$1(node);
734
+ onObsChange(obs$, syncState, localState, syncOptions, {
735
+ value,
736
+ loading: false,
737
+ remote: false,
738
+ getPrevious: createPreviousHandler(value, changes),
739
+ changes,
740
+ });
741
+ localState.isApplyingPending = false;
742
+ }
743
+ }
744
+ };
745
+ syncState.assign({ sync });
746
+ }
747
+ // Wait for this node and all parent nodes up the hierarchy to be loaded
748
+ const onAllPersistLoaded = () => {
749
+ var _a, _b;
750
+ let parentNode = node;
751
+ while (parentNode) {
752
+ if (((_b = (_a = parentNode.state) === null || _a === void 0 ? void 0 : _a.isPersistLoaded) === null || _b === void 0 ? void 0 : _b.get()) === false) {
753
+ return false;
754
+ }
755
+ parentNode = parentNode.parent;
756
+ }
757
+ return true;
758
+ };
759
+ // When all is loaded locally we can start syncing and listening for changes
760
+ state.when(onAllPersistLoaded, function () {
761
+ // If remote is not manual, then sync() is called automatically
762
+ if (syncOptions.get && syncOptions.syncMode === 'auto') {
763
+ sync();
764
+ }
765
+ obs$.onChange(onObsChange.bind(this, obs$, syncState, localState, syncOptions));
766
+ });
767
+ return syncState;
768
+ }
769
+
770
+ const { getProxy, globalState, runWithRetry, symbolLinked: symbolLinked$1, setNodeValue, getNodeValue } = state.internal;
771
+ function enableActivateSyncedNode() {
772
+ globalState.activateSyncedNode = function activateSyncedNode(node, newValue) {
773
+ const obs$ = getProxy(node);
774
+ if (node.activationState) {
775
+ // If it is a Synced
776
+ const { get, initial, set, subscribe } = node.activationState;
777
+ let onChange = undefined;
778
+ const pluginRemote = {};
779
+ let promiseReturn = undefined;
780
+ // Not sure why this disable is needed, but it's needed to make the linter happy
781
+ // eslint-disable-next-line prefer-const
782
+ let syncState;
783
+ const refresh = () => syncState === null || syncState === void 0 ? void 0 : syncState.sync();
784
+ if (get) {
785
+ pluginRemote.get = (params) => {
786
+ onChange = params.onChange;
787
+ const updateLastSync = (lastSync) => (params.lastSync = lastSync);
788
+ const setMode = (mode) => (params.mode = mode);
789
+ const existingValue = getNodeValue(node);
790
+ const value = runWithRetry(node, { attemptNum: 0 }, () => {
791
+ return get({
792
+ value: state.isFunction(existingValue) || (existingValue === null || existingValue === void 0 ? void 0 : existingValue[symbolLinked$1]) ? undefined : existingValue,
793
+ lastSync: params.lastSync,
794
+ updateLastSync,
795
+ setMode,
796
+ refresh,
797
+ });
798
+ });
799
+ promiseReturn = value;
800
+ return value;
801
+ };
802
+ }
803
+ if (set) {
804
+ // TODO: Work out these types better
805
+ pluginRemote.set = async (params) => {
806
+ var _a;
807
+ if ((_a = node.state) === null || _a === void 0 ? void 0 : _a.isLoaded.get()) {
808
+ const retryAttempts = { attemptNum: 0 };
809
+ return runWithRetry(node, retryAttempts, async (retryEvent) => {
810
+ let changes = {};
811
+ let maxModified = 0;
812
+ if (!node.state.isLoaded.peek()) {
813
+ await state.whenReady(node.state.isLoaded);
814
+ }
815
+ const cancelRetry = () => {
816
+ retryEvent.cancel = true;
817
+ };
818
+ await set({
819
+ ...params,
820
+ node,
821
+ update: (params) => {
822
+ const { value, lastSync } = params;
823
+ maxModified = Math.max(lastSync || 0, maxModified);
824
+ changes = state.mergeIntoObservable(changes, value);
825
+ },
826
+ retryNum: retryAttempts.attemptNum,
827
+ cancelRetry,
828
+ refresh,
829
+ fromSubscribe: false,
830
+ });
831
+ return { changes, lastSync: maxModified || undefined };
832
+ });
833
+ }
834
+ };
835
+ }
836
+ const nodeVal = getNodeValue(node);
837
+ if (promiseReturn !== undefined) {
838
+ newValue = promiseReturn;
839
+ }
840
+ else if (nodeVal !== undefined && !state.isFunction(nodeVal)) {
841
+ newValue = nodeVal;
842
+ }
843
+ else {
844
+ newValue = initial;
845
+ }
846
+ setNodeValue(node, promiseReturn ? undefined : newValue);
847
+ // @ts-expect-error TODO fix these types
848
+ syncState = syncObservable(obs$, { ...node.activationState, ...pluginRemote });
849
+ if (subscribe) {
850
+ state.when(promiseReturn || true, () => {
851
+ subscribe({
852
+ node,
853
+ update: (params) => {
854
+ if (!onChange) {
855
+ // TODO: Make this message better
856
+ console.log('[legend-state] Cannot update immediately before the first return');
857
+ }
858
+ else {
859
+ onChange(params);
860
+ }
861
+ },
862
+ refresh,
863
+ });
864
+ });
865
+ }
866
+ return { update: onChange, value: newValue };
867
+ }
868
+ else {
869
+ // If it is not a Synced
870
+ let update = undefined;
871
+ const get = async (params) => {
872
+ update = params.refresh;
873
+ if (state.isPromise(newValue)) {
874
+ try {
875
+ newValue = await newValue;
876
+ }
877
+ catch (_a) {
878
+ // TODO Once we have global retry settings this should retry
879
+ }
880
+ }
881
+ return newValue;
882
+ };
883
+ syncObservable(obs$, {
884
+ get,
885
+ });
886
+ return { update: update, value: newValue };
887
+ }
888
+ };
889
+ }
890
+
891
+ const { symbolLinked } = state.internal;
892
+ function synced(params) {
893
+ installPersistActivateNode();
894
+ return (() => ({
895
+ [symbolLinked]: { ...params, synced: true },
896
+ }));
897
+ }
898
+ let didInstall = false;
899
+ function installPersistActivateNode() {
900
+ if (!didInstall) {
901
+ enableActivateSyncedNode();
902
+ didInstall = true;
903
+ }
904
+ }
905
+
906
+ function isInRemoteChange() {
907
+ return state.internal.globalState.isLoadingRemote;
908
+ }
909
+ const internal = {
910
+ observableSyncConfiguration,
911
+ };
912
+
913
+ exports.configureObservableSync = configureObservableSync;
914
+ exports.internal = internal;
915
+ exports.isInRemoteChange = isInRemoteChange;
916
+ exports.mapSyncPlugins = mapSyncPlugins;
917
+ exports.syncObservable = syncObservable;
918
+ exports.synced = synced;
919
+ //# sourceMappingURL=sync.js.map