@legendapp/state 2.2.0-next.8 → 2.2.0-next.80

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 (321) 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 +34 -32
  14. package/config/enableReactTracking.js.map +1 -1
  15. package/config/enableReactTracking.mjs +35 -33
  16. package/config/enableReactTracking.mjs.map +1 -1
  17. package/config/enableReactUse.d.ts +1 -1
  18. package/config/enableReactUse.js +9 -1
  19. package/config/enableReactUse.js.map +1 -1
  20. package/config/enableReactUse.mjs +9 -1
  21. package/config/enableReactUse.mjs.map +1 -1
  22. package/config/enable_peek.d.ts +8 -0
  23. package/config/{enableDirectPeek.js → enable_peek.js} +6 -3
  24. package/config/enable_peek.js.map +1 -0
  25. package/config/{enableDirectPeek.mjs → enable_peek.mjs} +5 -3
  26. package/config/enable_peek.mjs.map +1 -0
  27. package/helpers/fetch.d.ts +4 -3
  28. package/helpers/fetch.js.map +1 -1
  29. package/helpers/fetch.mjs.map +1 -1
  30. package/helpers/pageHash.js.map +1 -1
  31. package/helpers/pageHash.mjs.map +1 -1
  32. package/helpers/pageHashParams.js.map +1 -1
  33. package/helpers/pageHashParams.mjs.map +1 -1
  34. package/helpers/time.d.ts +2 -2
  35. package/helpers/time.js.map +1 -1
  36. package/helpers/time.mjs.map +1 -1
  37. package/history.js +2 -2
  38. package/history.js.map +1 -1
  39. package/history.mjs +3 -3
  40. package/history.mjs.map +1 -1
  41. package/index.d.ts +29 -9
  42. package/index.js +964 -671
  43. package/index.js.map +1 -1
  44. package/index.mjs +959 -668
  45. package/index.mjs.map +1 -1
  46. package/package.json +37 -25
  47. package/persist-plugins/async-storage.d.ts +4 -3
  48. package/persist-plugins/async-storage.js +8 -7
  49. package/persist-plugins/async-storage.js.map +1 -1
  50. package/persist-plugins/async-storage.mjs +9 -8
  51. package/persist-plugins/async-storage.mjs.map +1 -1
  52. package/persist-plugins/fetch.d.ts +1 -1
  53. package/persist-plugins/fetch.js.map +1 -1
  54. package/persist-plugins/fetch.mjs.map +1 -1
  55. package/persist-plugins/firebase.d.ts +2 -2
  56. package/persist-plugins/firebase.js +12 -11
  57. package/persist-plugins/firebase.js.map +1 -1
  58. package/persist-plugins/firebase.mjs +13 -12
  59. package/persist-plugins/firebase.mjs.map +1 -1
  60. package/persist-plugins/indexeddb.d.ts +11 -10
  61. package/persist-plugins/indexeddb.js +2 -2
  62. package/persist-plugins/indexeddb.js.map +1 -1
  63. package/persist-plugins/indexeddb.mjs +2 -2
  64. package/persist-plugins/indexeddb.mjs.map +1 -1
  65. package/persist-plugins/local-storage.d.ts +4 -4
  66. package/persist-plugins/local-storage.js +19 -7
  67. package/persist-plugins/local-storage.js.map +1 -1
  68. package/persist-plugins/local-storage.mjs +20 -9
  69. package/persist-plugins/local-storage.mjs.map +1 -1
  70. package/persist-plugins/mmkv.d.ts +9 -8
  71. package/persist-plugins/mmkv.js +5 -4
  72. package/persist-plugins/mmkv.js.map +1 -1
  73. package/persist-plugins/mmkv.mjs +6 -5
  74. package/persist-plugins/mmkv.mjs.map +1 -1
  75. package/persist-plugins/query.d.ts +1 -1
  76. package/persist-plugins/query.js.map +1 -1
  77. package/persist-plugins/query.mjs.map +1 -1
  78. package/persist.d.ts +2 -14
  79. package/persist.js +1270 -269
  80. package/persist.js.map +1 -1
  81. package/persist.mjs +1270 -270
  82. package/persist.mjs.map +1 -1
  83. package/react-hooks/createObservableHook.js +1 -1
  84. package/react-hooks/createObservableHook.js.map +1 -1
  85. package/react-hooks/createObservableHook.mjs +1 -1
  86. package/react-hooks/createObservableHook.mjs.map +1 -1
  87. package/react-hooks/useFetch.d.ts +4 -3
  88. package/react-hooks/useFetch.js.map +1 -1
  89. package/react-hooks/useFetch.mjs.map +1 -1
  90. package/react-hooks/useHover.js.map +1 -1
  91. package/react-hooks/useHover.mjs.map +1 -1
  92. package/react-hooks/useMeasure.js.map +1 -1
  93. package/react-hooks/useMeasure.mjs.map +1 -1
  94. package/react-hooks/useObservableNextRouter.js.map +1 -1
  95. package/react-hooks/useObservableNextRouter.mjs.map +1 -1
  96. package/react-hooks/useObservableQuery.js.map +1 -1
  97. package/react-hooks/useObservableQuery.mjs.map +1 -1
  98. package/react-hooks/usePersistedObservable.d.ts +6 -3
  99. package/react-hooks/usePersistedObservable.js +5 -2
  100. package/react-hooks/usePersistedObservable.js.map +1 -1
  101. package/react-hooks/usePersistedObservable.mjs +5 -2
  102. package/react-hooks/usePersistedObservable.mjs.map +1 -1
  103. package/react.js +73 -93
  104. package/react.js.map +1 -1
  105. package/react.mjs +73 -93
  106. package/react.mjs.map +1 -1
  107. package/src/ObservableObject.ts +1217 -0
  108. package/src/ObservablePrimitive.ts +62 -0
  109. package/src/babel/index.ts +70 -0
  110. package/src/batching.ts +378 -0
  111. package/src/computed.ts +18 -0
  112. package/src/config/enable$get.ts +30 -0
  113. package/src/config/enableReactComponents.ts +26 -0
  114. package/src/config/enableReactNativeComponents.ts +102 -0
  115. package/src/config/enableReactTracking.ts +62 -0
  116. package/src/config/enableReactUse.ts +32 -0
  117. package/src/config/enable_peek.ts +31 -0
  118. package/src/config.ts +47 -0
  119. package/src/createObservable.ts +46 -0
  120. package/src/event.ts +26 -0
  121. package/src/globals.ts +234 -0
  122. package/src/helpers/fetch.ts +26 -0
  123. package/src/helpers/pageHash.ts +41 -0
  124. package/src/helpers/pageHashParams.ts +55 -0
  125. package/src/helpers/time.ts +30 -0
  126. package/src/helpers.ts +224 -0
  127. package/src/history/trackHistory.ts +29 -0
  128. package/src/history/undoRedo.ts +111 -0
  129. package/src/is.ts +63 -0
  130. package/src/linked.ts +6 -0
  131. package/src/observable.ts +32 -0
  132. package/src/observableInterfaces.ts +148 -0
  133. package/src/observableTypes.ts +226 -0
  134. package/src/observe.ts +89 -0
  135. package/src/onChange.ts +136 -0
  136. package/src/persist/configureObservablePersistence.ts +7 -0
  137. package/src/persist/fieldTransformer.ts +149 -0
  138. package/src/persist/observablePersistRemoteFunctionsAdapter.ts +39 -0
  139. package/src/persist/persistObservable.ts +1031 -0
  140. package/src/persist-plugins/async-storage.ts +102 -0
  141. package/src/persist-plugins/fetch.ts +34 -0
  142. package/src/persist-plugins/firebase.ts +1052 -0
  143. package/src/persist-plugins/indexeddb.ts +432 -0
  144. package/src/persist-plugins/local-storage.ts +91 -0
  145. package/src/persist-plugins/mmkv.ts +91 -0
  146. package/src/persist-plugins/query.ts +129 -0
  147. package/src/proxy.ts +28 -0
  148. package/src/react/Computed.tsx +7 -0
  149. package/src/react/For.tsx +116 -0
  150. package/src/react/Memo.tsx +4 -0
  151. package/src/react/Reactive.tsx +53 -0
  152. package/src/react/Show.tsx +33 -0
  153. package/src/react/Switch.tsx +43 -0
  154. package/src/react/react-globals.ts +3 -0
  155. package/src/react/{reactInterfaces.d.ts → reactInterfaces.ts} +15 -7
  156. package/src/react/reactive-observer.tsx +210 -0
  157. package/src/react/useComputed.ts +36 -0
  158. package/src/react/useEffectOnce.ts +41 -0
  159. package/src/react/useIsMounted.ts +16 -0
  160. package/src/react/useMount.ts +15 -0
  161. package/src/react/useObservable.ts +24 -0
  162. package/src/react/useObservableReducer.ts +52 -0
  163. package/src/react/useObservableState.ts +30 -0
  164. package/src/react/useObserve.ts +54 -0
  165. package/src/react/useObserveEffect.ts +40 -0
  166. package/src/react/usePauseProvider.tsx +13 -0
  167. package/src/react/useSelector.ts +167 -0
  168. package/src/react/useUnmount.ts +8 -0
  169. package/src/react/useWhen.ts +9 -0
  170. package/src/react-hooks/createObservableHook.ts +53 -0
  171. package/src/react-hooks/useFetch.ts +16 -0
  172. package/src/react-hooks/useHover.ts +40 -0
  173. package/src/react-hooks/useMeasure.ts +48 -0
  174. package/src/react-hooks/useObservableNextRouter.ts +137 -0
  175. package/src/react-hooks/useObservableQuery.ts +205 -0
  176. package/src/react-hooks/usePersistedObservable.ts +25 -0
  177. package/src/retry.ts +71 -0
  178. package/src/setupTracking.ts +26 -0
  179. package/src/sync/activateSyncedNode.ts +128 -0
  180. package/src/sync/configureObservableSync.ts +7 -0
  181. package/src/sync/persistTypes.ts +226 -0
  182. package/src/sync/syncHelpers.ts +56 -0
  183. package/src/sync/syncObservable.ts +1040 -0
  184. package/src/sync/syncObservableAdapter.ts +31 -0
  185. package/src/sync/syncTypes.ts +188 -0
  186. package/src/sync/synced.ts +20 -0
  187. package/src/sync-plugins/crud.ts +404 -0
  188. package/src/sync-plugins/fetch.ts +72 -0
  189. package/src/sync-plugins/keel.ts +452 -0
  190. package/src/sync-plugins/supabase.ts +261 -0
  191. package/src/trace/traceHelpers.ts +11 -0
  192. package/src/trace/useTraceListeners.ts +34 -0
  193. package/src/trace/useTraceUpdates.ts +24 -0
  194. package/src/trace/useVerifyNotTracking.ts +33 -0
  195. package/src/trace/useVerifyOneRender.ts +10 -0
  196. package/src/trackSelector.ts +52 -0
  197. package/src/tracking.ts +43 -0
  198. package/src/types/babel.d.ts +12 -0
  199. package/src/when.ts +75 -0
  200. package/sync-plugins/crud.d.ts +41 -0
  201. package/sync-plugins/crud.js +290 -0
  202. package/sync-plugins/crud.js.map +1 -0
  203. package/sync-plugins/crud.mjs +286 -0
  204. package/sync-plugins/crud.mjs.map +1 -0
  205. package/sync-plugins/fetch.d.ts +13 -0
  206. package/sync-plugins/fetch.js +46 -0
  207. package/sync-plugins/fetch.js.map +1 -0
  208. package/sync-plugins/fetch.mjs +44 -0
  209. package/sync-plugins/fetch.mjs.map +1 -0
  210. package/sync-plugins/keel.d.ts +91 -0
  211. package/sync-plugins/keel.js +277 -0
  212. package/sync-plugins/keel.js.map +1 -0
  213. package/sync-plugins/keel.mjs +273 -0
  214. package/sync-plugins/keel.mjs.map +1 -0
  215. package/sync-plugins/supabase.d.ts +36 -0
  216. package/sync-plugins/supabase.js +152 -0
  217. package/sync-plugins/supabase.js.map +1 -0
  218. package/sync-plugins/supabase.mjs +149 -0
  219. package/sync-plugins/supabase.mjs.map +1 -0
  220. package/sync.d.ts +11 -0
  221. package/sync.js +976 -0
  222. package/sync.js.map +1 -0
  223. package/sync.mjs +966 -0
  224. package/sync.mjs.map +1 -0
  225. package/trace.js +13 -10
  226. package/trace.js.map +1 -1
  227. package/trace.mjs +11 -8
  228. package/trace.mjs.map +1 -1
  229. package/types/babel.d.ts +3 -3
  230. package/config/enableDirectAccess.d.ts +0 -7
  231. package/config/enableDirectAccess.js +0 -25
  232. package/config/enableDirectAccess.js.map +0 -1
  233. package/config/enableDirectAccess.mjs +0 -23
  234. package/config/enableDirectAccess.mjs.map +0 -1
  235. package/config/enableDirectPeek.d.ts +0 -7
  236. package/config/enableDirectPeek.js.map +0 -1
  237. package/config/enableDirectPeek.mjs.map +0 -1
  238. package/config/enableReactDirectRender.d.ts +0 -2
  239. package/config/enableReactDirectRender.js +0 -78
  240. package/config/enableReactDirectRender.js.map +0 -1
  241. package/config/enableReactDirectRender.mjs +0 -75
  242. package/config/enableReactDirectRender.mjs.map +0 -1
  243. package/src/ObservableObject.d.ts +0 -14
  244. package/src/ObservablePrimitive.d.ts +0 -7
  245. package/src/babel/index.d.ts +0 -17
  246. package/src/batching.d.ts +0 -6
  247. package/src/computed.d.ts +0 -4
  248. package/src/config/enableDirectAccess.d.ts +0 -7
  249. package/src/config/enableDirectPeek.d.ts +0 -7
  250. package/src/config/enableReactComponents.d.ts +0 -7
  251. package/src/config/enableReactDirectRender.d.ts +0 -2
  252. package/src/config/enableReactNativeComponents.d.ts +0 -20
  253. package/src/config/enableReactTracking.d.ts +0 -15
  254. package/src/config/enableReactUse.d.ts +0 -7
  255. package/src/config.d.ts +0 -8
  256. package/src/createObservable.d.ts +0 -2
  257. package/src/event.d.ts +0 -2
  258. package/src/globals.d.ts +0 -32
  259. package/src/helpers/fetch.d.ts +0 -6
  260. package/src/helpers/pageHash.d.ts +0 -7
  261. package/src/helpers/pageHashParams.d.ts +0 -7
  262. package/src/helpers/time.d.ts +0 -3
  263. package/src/helpers.d.ts +0 -13
  264. package/src/history/trackHistory.d.ts +0 -4
  265. package/src/is.d.ts +0 -10
  266. package/src/observable.d.ts +0 -16
  267. package/src/observableInterfaces.d.ts +0 -458
  268. package/src/observe.d.ts +0 -6
  269. package/src/onChange.d.ts +0 -7
  270. package/src/persist/configureObservablePersistence.d.ts +0 -3
  271. package/src/persist/fieldTransformer.d.ts +0 -8
  272. package/src/persist/observablePersistRemoteFunctionsAdapter.d.ts +0 -2
  273. package/src/persist/persistActivateNode.d.ts +0 -1
  274. package/src/persist/persistHelpers.d.ts +0 -1
  275. package/src/persist/persistObservable.d.ts +0 -25
  276. package/src/persist-plugins/async-storage.d.ts +0 -14
  277. package/src/persist-plugins/fetch.d.ts +0 -10
  278. package/src/persist-plugins/firebase.d.ts +0 -51
  279. package/src/persist-plugins/indexeddb.d.ts +0 -25
  280. package/src/persist-plugins/local-storage.d.ts +0 -21
  281. package/src/persist-plugins/mmkv.d.ts +0 -14
  282. package/src/persist-plugins/query.d.ts +0 -18
  283. package/src/proxy.d.ts +0 -5
  284. package/src/react/Computed.d.ts +0 -5
  285. package/src/react/For.d.ts +0 -15
  286. package/src/react/Memo.d.ts +0 -3
  287. package/src/react/Reactive.d.ts +0 -9
  288. package/src/react/Show.d.ts +0 -18
  289. package/src/react/Switch.d.ts +0 -14
  290. package/src/react/react-globals.d.ts +0 -3
  291. package/src/react/reactive-observer.d.ts +0 -14
  292. package/src/react/useComputed.d.ts +0 -5
  293. package/src/react/useEffectOnce.d.ts +0 -1
  294. package/src/react/useIsMounted.d.ts +0 -2
  295. package/src/react/useMount.d.ts +0 -2
  296. package/src/react/useObservable.d.ts +0 -9
  297. package/src/react/useObservableReducer.d.ts +0 -7
  298. package/src/react/useObservableState.d.ts +0 -2
  299. package/src/react/useObserve.d.ts +0 -4
  300. package/src/react/useObserveEffect.d.ts +0 -4
  301. package/src/react/usePauseProvider.d.ts +0 -8
  302. package/src/react/useSelector.d.ts +0 -3
  303. package/src/react/useUnmount.d.ts +0 -2
  304. package/src/react/useWhen.d.ts +0 -3
  305. package/src/react-hooks/createObservableHook.d.ts +0 -2
  306. package/src/react-hooks/useFetch.d.ts +0 -6
  307. package/src/react-hooks/useHover.d.ts +0 -3
  308. package/src/react-hooks/useMeasure.d.ts +0 -6
  309. package/src/react-hooks/useObservableNextRouter.d.ts +0 -33
  310. package/src/react-hooks/useObservableQuery.d.ts +0 -6
  311. package/src/react-hooks/usePersistedObservable.d.ts +0 -11
  312. package/src/retry.d.ts +0 -9
  313. package/src/setupTracking.d.ts +0 -2
  314. package/src/trace/traceHelpers.d.ts +0 -2
  315. package/src/trace/useTraceListeners.d.ts +0 -1
  316. package/src/trace/useTraceUpdates.d.ts +0 -1
  317. package/src/trace/useVerifyNotTracking.d.ts +0 -1
  318. package/src/trace/useVerifyOneRender.d.ts +0 -1
  319. package/src/trackSelector.d.ts +0 -7
  320. package/src/tracking.d.ts +0 -13
  321. package/src/when.d.ts +0 -3
@@ -0,0 +1,137 @@
1
+ import { isEmpty, observable, Observable, setSilently } from '@legendapp/state';
2
+ import Router, { NextRouter, useRouter } from 'next/router';
3
+
4
+ type ParsedUrlQuery = { [key: string]: string | string[] | undefined };
5
+
6
+ interface TransitionOptions {
7
+ shallow?: boolean;
8
+ locale?: string | false;
9
+ scroll?: boolean;
10
+ unstable_skipClientCache?: boolean;
11
+ }
12
+ export interface ObservableNextRouterState {
13
+ pathname: string;
14
+ hash: string;
15
+ query: ParsedUrlQuery;
16
+ }
17
+ type RouteInfo = Partial<ObservableNextRouterState>;
18
+ export interface ParamsUseObservableNextRouterBase {
19
+ transitionOptions?: TransitionOptions;
20
+ method?: 'push' | 'replace';
21
+ subscribe?: boolean;
22
+ }
23
+ export interface ParamsUseObservableNextRouter<T extends object> extends ParamsUseObservableNextRouterBase {
24
+ compute: (value: ObservableNextRouterState) => T;
25
+ set: (
26
+ value: T,
27
+ previous: T,
28
+ router: NextRouter,
29
+ ) => RouteInfo & {
30
+ transitionOptions?: TransitionOptions;
31
+ method?: 'push' | 'replace';
32
+ };
33
+ }
34
+
35
+ function isShallowEqual(query1: ParsedUrlQuery, query2: ParsedUrlQuery) {
36
+ if (!query1 !== !query2) {
37
+ return false;
38
+ }
39
+ const keys1 = Object.keys(query1);
40
+ const keys2 = Object.keys(query2);
41
+
42
+ if (keys1.length !== keys2.length) {
43
+ return false;
44
+ }
45
+
46
+ for (const key of keys1) {
47
+ if (query1[key] !== query2[key]) {
48
+ return false;
49
+ }
50
+ }
51
+
52
+ return true;
53
+ }
54
+
55
+ const routes$ = observable({});
56
+ let routeParams = {} as ParamsUseObservableNextRouter<any>;
57
+ let router: NextRouter;
58
+
59
+ routes$.onChange(({ value, getPrevious }) => {
60
+ // Only run this if being manually changed by the user
61
+ let setter = routeParams?.set;
62
+ if (!setter) {
63
+ if ((value as any).pathname) {
64
+ setter = () => value;
65
+ } else {
66
+ console.error('[legend-state]: Must provide a set method to useObservableNextRouter');
67
+ }
68
+ }
69
+ const setReturn = setter(value, getPrevious(), router);
70
+ const { pathname, hash, query } = setReturn;
71
+ let { transitionOptions, method } = setReturn;
72
+
73
+ method = method || routeParams?.method;
74
+ transitionOptions = transitionOptions || routeParams?.transitionOptions;
75
+
76
+ const prevHash = router.asPath.split('#')[1] || '';
77
+
78
+ const change: RouteInfo = {};
79
+ // Only include changes that were meant to be changed. For example the user may have
80
+ // only changed the hash so we don't need to push a pathname change.
81
+ if (pathname !== undefined && pathname !== router.pathname) {
82
+ change.pathname = pathname;
83
+ }
84
+ if (hash !== undefined && hash !== prevHash) {
85
+ change.hash = hash;
86
+ }
87
+ if (query !== undefined && !isShallowEqual(query, router.query)) {
88
+ change.query = query;
89
+ }
90
+ // Only push if there are changes
91
+ if (!isEmpty(change)) {
92
+ const fn = method === 'replace' ? 'replace' : 'push';
93
+ router[fn](change, undefined, transitionOptions).catch((e) => {
94
+ // workaround for https://github.com/vercel/next.js/issues/37362
95
+ if (!e.cancelled) throw e;
96
+ });
97
+ }
98
+ });
99
+
100
+ export function useObservableNextRouter(): Observable<ObservableNextRouterState>;
101
+ export function useObservableNextRouter<T extends object>(params: ParamsUseObservableNextRouter<T>): Observable<T>;
102
+ export function useObservableNextRouter(
103
+ params: ParamsUseObservableNextRouterBase,
104
+ ): Observable<ObservableNextRouterState>;
105
+ export function useObservableNextRouter<T extends object>(
106
+ params?: ParamsUseObservableNextRouter<T> | ParamsUseObservableNextRouterBase,
107
+ ): Observable<T> | Observable<ObservableNextRouterState> {
108
+ const { subscribe, compute } = (params as ParamsUseObservableNextRouter<T>) || {};
109
+
110
+ try {
111
+ // Use the useRouter hook if we're on the client side and want to subscribe to changes.
112
+ // Otherwise use the Router object so that this does not subscribe to router changes.
113
+ router = typeof window !== 'undefined' && !subscribe ? Router : useRouter();
114
+ } finally {
115
+ router = router || useRouter();
116
+ }
117
+
118
+ // Update the local state with the new functions and options. This can happen when being run
119
+ // on a new page or if the user just changes it on the current page.
120
+ // It's better for performance than creating new observables or hooks for every use, since there may be
121
+ // many uses of useObservableRouter in the lifecycle of a page.
122
+ routeParams = params as ParamsUseObservableNextRouter<T>;
123
+
124
+ // Get the pathname and hash
125
+ const { asPath, pathname, query } = router;
126
+ const hash = asPath.split('#')[1] || '';
127
+
128
+ // Run the compute function to get the value of the object
129
+ const computeParams = { pathname, hash, query };
130
+ const obj = compute ? compute(computeParams) : computeParams;
131
+
132
+ // Set the object without triggering router.push
133
+ setSilently(routes$, obj);
134
+
135
+ // Return the observable with the computed values
136
+ return routes$ as any;
137
+ }
@@ -0,0 +1,205 @@
1
+ // This is basically just React Query's useBaseQuery with a few changes for Legend-State:
2
+ // 1. Remove the useSyncExternalStore
3
+ // 2. Return an observable that subscribes to the query observer
4
+ // 3. If there is a mutator observe the observable for changes and call mutate
5
+
6
+ import { observable, observe, type ObservableObject } from '@legendapp/state';
7
+ import {
8
+ DefaultedQueryObserverOptions,
9
+ MutationObserver,
10
+ Query,
11
+ QueryClient,
12
+ QueryKey,
13
+ QueryObserver,
14
+ QueryObserverResult,
15
+ UseErrorBoundary,
16
+ notifyManager,
17
+ } from '@tanstack/query-core';
18
+ import {
19
+ UseBaseQueryOptions,
20
+ UseMutationOptions,
21
+ useIsRestoring,
22
+ useQueryClient,
23
+ useQueryErrorResetBoundary,
24
+ type UseBaseQueryResult,
25
+ } from '@tanstack/react-query';
26
+ import type { QueryErrorResetBoundaryValue } from '@tanstack/react-query/build/lib/QueryErrorResetBoundary';
27
+ import * as React from 'react';
28
+
29
+ const ensurePreventErrorBoundaryRetry = <TQueryFnData, TError, TData, TQueryData, TQueryKey extends QueryKey>(
30
+ options: DefaultedQueryObserverOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey>,
31
+ errorResetBoundary: QueryErrorResetBoundaryValue,
32
+ ) => {
33
+ if (options.suspense || options.useErrorBoundary) {
34
+ // Prevent retrying failed query if the error boundary has not been reset yet
35
+ if (!errorResetBoundary.isReset()) {
36
+ options.retryOnMount = false;
37
+ }
38
+ }
39
+ };
40
+
41
+ const useClearResetErrorBoundary = (errorResetBoundary: QueryErrorResetBoundaryValue) => {
42
+ React.useEffect(() => {
43
+ errorResetBoundary.clearReset();
44
+ }, [errorResetBoundary]);
45
+ };
46
+
47
+ function shouldThrowError<T extends (...args: any[]) => boolean>(
48
+ _useErrorBoundary: boolean | T | undefined,
49
+ params: Parameters<T>,
50
+ ): boolean {
51
+ // Allow useErrorBoundary function to override throwing behavior on a per-error basis
52
+ if (typeof _useErrorBoundary === 'function') {
53
+ return _useErrorBoundary(...params);
54
+ }
55
+
56
+ return !!_useErrorBoundary;
57
+ }
58
+
59
+ const getHasError = <TData, TError, TQueryFnData, TQueryData, TQueryKey extends QueryKey>({
60
+ result,
61
+ errorResetBoundary,
62
+ useErrorBoundary,
63
+ query,
64
+ }: {
65
+ result: QueryObserverResult<TData, TError>;
66
+ errorResetBoundary: QueryErrorResetBoundaryValue;
67
+ useErrorBoundary: UseErrorBoundary<TQueryFnData, TError, TQueryData, TQueryKey>;
68
+ query: Query<TQueryFnData, TError, TQueryData, TQueryKey>;
69
+ }) => {
70
+ return (
71
+ result.isError &&
72
+ !errorResetBoundary.isReset() &&
73
+ !result.isFetching &&
74
+ shouldThrowError(useErrorBoundary, [result.error, query])
75
+ );
76
+ };
77
+
78
+ export function useObservableQuery<
79
+ TQueryFnData,
80
+ TError,
81
+ TData = TQueryFnData,
82
+ TQueryData = TQueryFnData,
83
+ TQueryKey extends QueryKey = QueryKey,
84
+ TContext = unknown,
85
+ >(
86
+ options: UseBaseQueryOptions<TQueryFnData, TError, TData, TQueryData, TQueryKey> & { queryClient?: QueryClient },
87
+ mutationOptions?: UseMutationOptions<TData, TError, void, TContext>,
88
+ ): ObservableObject<UseBaseQueryResult<TData, TError>> {
89
+ const Observer = QueryObserver;
90
+ const queryClient = options?.queryClient || useQueryClient({ context: options.context });
91
+ const isRestoring = useIsRestoring();
92
+ const errorResetBoundary = useQueryErrorResetBoundary();
93
+ const defaultedOptions = queryClient.defaultQueryOptions(options);
94
+
95
+ // Make sure results are optimistically set in fetching state before subscribing or updating options
96
+ defaultedOptions._optimisticResults = isRestoring ? 'isRestoring' : 'optimistic';
97
+
98
+ // Include callbacks in batch renders
99
+ if (defaultedOptions.onError) {
100
+ defaultedOptions.onError = notifyManager.batchCalls(defaultedOptions.onError);
101
+ }
102
+
103
+ if (defaultedOptions.onSuccess) {
104
+ defaultedOptions.onSuccess = notifyManager.batchCalls(defaultedOptions.onSuccess);
105
+ }
106
+
107
+ if (defaultedOptions.onSettled) {
108
+ defaultedOptions.onSettled = notifyManager.batchCalls(defaultedOptions.onSettled);
109
+ }
110
+
111
+ if (defaultedOptions.suspense) {
112
+ // Always set stale time when using suspense to prevent
113
+ // fetching again when directly mounting after suspending
114
+ if (typeof defaultedOptions.staleTime !== 'number') {
115
+ defaultedOptions.staleTime = 1000;
116
+ }
117
+ }
118
+
119
+ ensurePreventErrorBoundaryRetry(defaultedOptions, errorResetBoundary);
120
+
121
+ useClearResetErrorBoundary(errorResetBoundary);
122
+
123
+ const [observer] = React.useState(
124
+ () => new Observer<TQueryFnData, TError, TData, TQueryData, TQueryKey>(queryClient, defaultedOptions),
125
+ );
126
+
127
+ const result = observer.getOptimisticResult(defaultedOptions);
128
+
129
+ // useSyncExternalStore was here in useBaseQuery but is removed for Legend-State.
130
+
131
+ React.useEffect(() => {
132
+ // Do not notify on updates because of changes in the options because
133
+ // these changes should already be reflected in the optimistic result.
134
+ observer.setOptions(defaultedOptions, { listeners: false });
135
+ }, [defaultedOptions, observer]);
136
+
137
+ // Handle suspense
138
+ if (defaultedOptions.suspense && result.isLoading && result.isFetching && !isRestoring) {
139
+ throw observer
140
+ .fetchOptimistic(defaultedOptions)
141
+ .then(({ data }) => {
142
+ defaultedOptions.onSuccess?.(data as TData);
143
+ defaultedOptions.onSettled?.(data, null);
144
+ })
145
+ .catch((error) => {
146
+ errorResetBoundary.clearReset();
147
+ defaultedOptions.onError?.(error);
148
+ defaultedOptions.onSettled?.(undefined, error);
149
+ });
150
+ }
151
+
152
+ // Handle error boundary
153
+ if (
154
+ getHasError({
155
+ result,
156
+ errorResetBoundary,
157
+ useErrorBoundary: defaultedOptions.useErrorBoundary,
158
+ query: observer.getCurrentQuery(),
159
+ })
160
+ ) {
161
+ throw result.error;
162
+ }
163
+
164
+ // Legend-State changes from here down
165
+ let mutator: MutationObserver<TData, TError, void, TContext>;
166
+ if (mutationOptions) {
167
+ [mutator] = React.useState(() => new MutationObserver(queryClient, mutationOptions));
168
+ }
169
+
170
+ const [obs] = React.useState<ObservableObject<UseBaseQueryResult<TData, TError>>>(() => {
171
+ const obs = observable<any>(observer.getCurrentResult());
172
+
173
+ let isSetting = false;
174
+
175
+ // If there is a mutator watch for changes as long as they don't come from the the query observer
176
+ if (mutationOptions) {
177
+ observe(() => {
178
+ const data = (obs as any).data.get();
179
+ // Don't want to call mutate if there's no data or this coming from the query changing
180
+ if (data && !isSetting) {
181
+ mutator.mutate(data);
182
+ }
183
+ });
184
+ }
185
+
186
+ // Note: Don't need to worry about unsubscribing because the query observer itself
187
+ // is scoped to this component
188
+ observer.subscribe((result) => {
189
+ isSetting = true;
190
+
191
+ try {
192
+ // Update the observable with the latest value
193
+ obs.set(result);
194
+ } finally {
195
+ // If set causes a crash for some reason we still need to reset isSetting
196
+ isSetting = false;
197
+ }
198
+ });
199
+
200
+ return obs as unknown as ObservableObject<UseBaseQueryResult<TData, TError>>;
201
+ });
202
+
203
+ // Return the observable
204
+ return obs;
205
+ }
@@ -0,0 +1,25 @@
1
+ import { Observable, ObservableParam, observable } from '@legendapp/state';
2
+ import { LegacyPersistOptions } from '@legendapp/state/sync';
3
+ import { persistObservable } from '@legendapp/state/persist';
4
+ import { useMemo } from 'react';
5
+
6
+ // TODO: Deprecate this
7
+
8
+ /**
9
+ * A React hook that creates a new observable and can optionally listen or persist its state.
10
+ *
11
+ * @param initialValue The initial value of the observable or a function that returns the initial value
12
+ * @param options Persistence options for the observable
13
+ *
14
+ * @see https://www.legendapp.com/dev/state/react/#useObservable
15
+ */
16
+ export function usePersistedObservable<T>(params: {
17
+ options: LegacyPersistOptions<T>;
18
+ initialValue?: T | (() => T) | (() => Promise<T>);
19
+ }): Observable<T> {
20
+ // Create the observable from the default value
21
+ return useMemo(() => {
22
+ const obs$ = observable<T>(params.initialValue as any);
23
+ persistObservable<T>(obs$ as ObservableParam<T>, params.options);
24
+ }, []) as any;
25
+ }
package/src/retry.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { whenReady } from './when';
2
+ import type { NodeValue } from './observableInterfaces';
3
+ import type { RetryOptions } from './sync/syncTypes';
4
+ import { isPromise } from './is';
5
+
6
+ function calculateRetryDelay(retryOptions: RetryOptions, attemptNum: number): number | null {
7
+ const { backoff, delay = 1000, infinite, times = 3, maxDelay = 30000 } = retryOptions;
8
+ if (infinite || attemptNum < times) {
9
+ const delayTime = Math.min(delay * (backoff === 'constant' ? 1 : 2 ** attemptNum), maxDelay);
10
+ return delayTime;
11
+ }
12
+ return null;
13
+ }
14
+
15
+ function createRetryTimeout(retryOptions: RetryOptions, attemptNum: number, fn: () => void) {
16
+ const delayTime = calculateRetryDelay(retryOptions, attemptNum);
17
+ if (delayTime) {
18
+ return setTimeout(fn, delayTime);
19
+ }
20
+ }
21
+
22
+ export function runWithRetry<T>(
23
+ node: NodeValue,
24
+ state: { attemptNum: number; retry: RetryOptions | undefined },
25
+ fn: (e: { cancel?: boolean }) => T | Promise<T>,
26
+ ): T | Promise<T> {
27
+ const { waitFor } = node.activationState!;
28
+ const { retry } = state;
29
+
30
+ const e = { cancel: false };
31
+ let value: any = undefined;
32
+ if (waitFor) {
33
+ value = whenReady(waitFor, () => {
34
+ node.activationState!.waitFor = undefined;
35
+ return fn(e);
36
+ });
37
+ } else {
38
+ value = fn(e);
39
+ }
40
+
41
+ if (isPromise(value) && retry) {
42
+ let timeoutRetry: any;
43
+ return new Promise<any>((resolve) => {
44
+ const run = () => {
45
+ (value as Promise<any>)
46
+ .then((val: any) => {
47
+ node.activationState!.persistedRetry = false;
48
+ resolve(val);
49
+ })
50
+ .catch(() => {
51
+ state.attemptNum++;
52
+ if (timeoutRetry) {
53
+ clearTimeout(timeoutRetry);
54
+ }
55
+ if (!e.cancel) {
56
+ timeoutRetry = createRetryTimeout(retry, state.attemptNum, () => {
57
+ value = fn(e);
58
+ run();
59
+ });
60
+ }
61
+ })
62
+ .finally(() => {
63
+ node.activationState!.persistedRetry = false;
64
+ });
65
+ };
66
+ run();
67
+ });
68
+ }
69
+
70
+ return value;
71
+ }
@@ -0,0 +1,26 @@
1
+ import type { ListenerFn, NodeValue, TrackingNode } from './observableInterfaces';
2
+ import { onChange } from './onChange';
3
+
4
+ export function setupTracking(
5
+ nodes: Map<NodeValue, TrackingNode> | undefined,
6
+ update: ListenerFn,
7
+ noArgs?: boolean,
8
+ immediate?: boolean,
9
+ ) {
10
+ let listeners: (() => void)[] | undefined = [];
11
+
12
+ // Listen to tracked nodes
13
+ nodes?.forEach((tracked) => {
14
+ const { node, track } = tracked;
15
+ listeners!.push(onChange(node, update, { trackingType: track, immediate, noArgs }));
16
+ });
17
+
18
+ return () => {
19
+ if (listeners) {
20
+ for (let i = 0; i < listeners.length; i++) {
21
+ listeners[i]();
22
+ }
23
+ listeners = undefined;
24
+ }
25
+ };
26
+ }
@@ -0,0 +1,128 @@
1
+ import type { NodeValue, Observable, UpdateFn } from '@legendapp/state';
2
+ import type {
3
+ ObservableSyncFunctions,
4
+ ObservableSyncGetParams,
5
+ ObservableSyncSetParams,
6
+ SyncedGetParams,
7
+ SyncedSetParams,
8
+ } from './syncTypes';
9
+ import type { ObservablePersistState } from './persistTypes';
10
+ import { internal, isFunction, isPromise, mergeIntoObservable, whenReady } from '@legendapp/state';
11
+ import { syncObservable } from './syncObservable';
12
+ const { getProxy, globalState, runWithRetry, symbolLinked, setNodeValue, getNodeValue } = internal;
13
+
14
+ export function enableActivateSyncedNode() {
15
+ globalState.activateSyncedNode = function activateSyncedNode(node: NodeValue, newValue: any) {
16
+ const obs$ = getProxy(node);
17
+ if (node.activationState) {
18
+ // If it is a Synced
19
+ const { get, initial, set, retry } = node.activationState!;
20
+
21
+ let onChange: UpdateFn | undefined = undefined;
22
+ const pluginRemote: ObservableSyncFunctions = {};
23
+ let promiseReturn: any = undefined;
24
+
25
+ // Not sure why this disable is needed, but it's needed to make the linter happy
26
+ // eslint-disable-next-line prefer-const
27
+ let syncState: Observable<ObservablePersistState>;
28
+ const refresh = () => syncState?.sync();
29
+
30
+ if (get) {
31
+ pluginRemote.get = (params: ObservableSyncGetParams<any>) => {
32
+ onChange = params.onChange;
33
+ const updateLastSync = (lastSync: number) => (params.lastSync = lastSync);
34
+
35
+ const existingValue = getNodeValue(node);
36
+ const value = runWithRetry(node, { attemptNum: 0, retry: retry || params.options?.retry }, () => {
37
+ const paramsToGet = {
38
+ value:
39
+ isFunction(existingValue) || existingValue?.[symbolLinked] ? undefined : existingValue,
40
+ lastSync: params.lastSync!,
41
+ updateLastSync,
42
+ mode: params.mode!,
43
+ refresh,
44
+ };
45
+
46
+ const ret = get!(paramsToGet);
47
+ params.mode = paramsToGet.mode;
48
+ return ret;
49
+ });
50
+
51
+ promiseReturn = value;
52
+
53
+ return value;
54
+ };
55
+ }
56
+ if (set) {
57
+ // TODO: Work out these types better
58
+ pluginRemote.set = async (params: ObservableSyncSetParams<any>) => {
59
+ if (node.state?.isLoaded.get()) {
60
+ const retryAttempts = { attemptNum: 0, retry: retry || params.options?.retry };
61
+ return runWithRetry(node, retryAttempts, async (retryEvent) => {
62
+ let changes = {};
63
+ let maxModified = 0;
64
+ if (!node.state!.isLoaded.peek()) {
65
+ await whenReady(node.state!.isLoaded);
66
+ }
67
+
68
+ const cancelRetry = () => {
69
+ retryEvent.cancel = true;
70
+ };
71
+
72
+ await set({
73
+ ...(params as unknown as SyncedSetParams<any>),
74
+ node,
75
+ update: (params) => {
76
+ const { value, lastSync } = params;
77
+ maxModified = Math.max(lastSync || 0, maxModified);
78
+ changes = mergeIntoObservable(changes, value);
79
+ },
80
+ retryNum: retryAttempts.attemptNum,
81
+ cancelRetry,
82
+ refresh,
83
+ fromSubscribe: false,
84
+ });
85
+ return { changes, lastSync: maxModified || undefined };
86
+ });
87
+ }
88
+ };
89
+ }
90
+
91
+ const nodeVal = getNodeValue(node);
92
+ if (promiseReturn !== undefined) {
93
+ newValue = promiseReturn;
94
+ } else if (nodeVal !== undefined && !isFunction(nodeVal)) {
95
+ newValue = nodeVal;
96
+ } else {
97
+ newValue = initial;
98
+ }
99
+ setNodeValue(node, promiseReturn ? undefined : newValue);
100
+
101
+ // @ts-expect-error TODO fix these types
102
+ syncState = syncObservable(obs$, { ...node.activationState, ...pluginRemote });
103
+
104
+ return { update: onChange!, value: newValue };
105
+ } else {
106
+ // If it is not a Synced
107
+
108
+ let update: UpdateFn | undefined = undefined;
109
+ const get = async (params: SyncedGetParams) => {
110
+ update = params.refresh;
111
+ if (isPromise(newValue)) {
112
+ try {
113
+ newValue = await newValue;
114
+ } catch {
115
+ // TODO Once we have global retry settings this should retry
116
+ }
117
+ }
118
+ return newValue;
119
+ };
120
+
121
+ syncObservable(obs$, {
122
+ get,
123
+ });
124
+
125
+ return { update: update!, value: newValue };
126
+ }
127
+ };
128
+ }
@@ -0,0 +1,7 @@
1
+ import type { SyncedOptionsGlobal } from '@legendapp/state/sync';
2
+
3
+ export const observableSyncConfiguration: SyncedOptionsGlobal = {};
4
+
5
+ export function configureObservableSync(options?: SyncedOptionsGlobal) {
6
+ Object.assign(observableSyncConfiguration, options);
7
+ }