@multiplatform.one/frappe 7.10.0 → 7.15.0

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 (239) hide show
  1. package/README.md +59 -1
  2. package/dist/cjs/auth.native.js.map +1 -1
  3. package/dist/cjs/call.native.js.map +1 -1
  4. package/dist/cjs/collection.native.js.map +1 -1
  5. package/dist/cjs/common/types.native.js.map +1 -1
  6. package/dist/cjs/core/http.native.js.map +1 -1
  7. package/dist/cjs/core/types.native.js.map +1 -1
  8. package/dist/cjs/core/wire.native.js.map +1 -1
  9. package/dist/cjs/db.native.js.map +1 -1
  10. package/dist/cjs/devtools/FrappeDevtoolsPanel.native.js.map +1 -1
  11. package/dist/cjs/devtools/index.native.js.map +1 -1
  12. package/dist/cjs/devtools/plugin.native.js.map +1 -1
  13. package/dist/cjs/devtools/theme.native.js.map +1 -1
  14. package/dist/cjs/doctype.cjs +25 -43
  15. package/dist/cjs/doctype.native.js +27 -43
  16. package/dist/cjs/doctype.native.js.map +1 -1
  17. package/dist/cjs/file.native.js.map +1 -1
  18. package/dist/cjs/fixtureDb.native.js.map +1 -1
  19. package/dist/cjs/fixtures.cjs +3 -0
  20. package/dist/cjs/fixtures.native.js +3 -0
  21. package/dist/cjs/fixtures.native.js.map +1 -1
  22. package/dist/cjs/frappeApp.native.js.map +1 -1
  23. package/dist/cjs/frappeReact.cjs +67 -31
  24. package/dist/cjs/frappeReact.native.js +87 -34
  25. package/dist/cjs/frappeReact.native.js.map +1 -1
  26. package/dist/cjs/index.cjs +7 -0
  27. package/dist/cjs/index.native.js +9 -2
  28. package/dist/cjs/index.native.js.map +1 -1
  29. package/dist/cjs/optimistic.native.js.map +1 -1
  30. package/dist/cjs/pacer.native.js.map +1 -1
  31. package/dist/cjs/plugin/index.native.js.map +1 -1
  32. package/dist/cjs/realtime.native.js.map +1 -1
  33. package/dist/cjs/serverLoaders.cjs +74 -0
  34. package/dist/cjs/serverLoaders.native.js +88 -0
  35. package/dist/cjs/serverLoaders.native.js.map +1 -0
  36. package/dist/cjs/sync/backfill.native.js.map +1 -1
  37. package/dist/cjs/sync/filters.native.js.map +1 -1
  38. package/dist/cjs/sync/index.cjs +99 -14
  39. package/dist/cjs/sync/index.native.js +129 -14
  40. package/dist/cjs/sync/index.native.js.map +1 -1
  41. package/dist/cjs/sync/merge.native.js.map +1 -1
  42. package/dist/cjs/sync/mutationEngine.native.js.map +1 -1
  43. package/dist/cjs/sync/mutationJournal.native.js.map +1 -1
  44. package/dist/cjs/sync/normalizedCache.native.js.map +1 -1
  45. package/dist/cjs/sync/persistence.native.js.map +1 -1
  46. package/dist/cjs/sync/realtime.native.js.map +1 -1
  47. package/dist/cjs/sync/rehydration.cjs +4 -0
  48. package/dist/cjs/sync/rehydration.native.js +6 -0
  49. package/dist/cjs/sync/rehydration.native.js.map +1 -1
  50. package/dist/cjs/sync/storage.native.js.map +1 -1
  51. package/dist/cjs/sync/subscriptionManager.native.js.map +1 -1
  52. package/dist/cjs/sync/types.native.js.map +1 -1
  53. package/dist/cjs/sync/virtualPageManager.native.js.map +1 -1
  54. package/dist/cjs/syncOptionsKey.cjs +59 -0
  55. package/dist/cjs/syncOptionsKey.native.js +82 -0
  56. package/dist/cjs/syncOptionsKey.native.js.map +1 -0
  57. package/dist/cjs/useLiveQuery.cjs +2 -20
  58. package/dist/cjs/useLiveQuery.native.js +1 -22
  59. package/dist/cjs/useLiveQuery.native.js.map +1 -1
  60. package/dist/cjs/util.native.js.map +1 -1
  61. package/dist/esm/auth.native.js.map +1 -1
  62. package/dist/esm/call.native.js.map +1 -1
  63. package/dist/esm/collection.native.js.map +1 -1
  64. package/dist/esm/common/types.native.js.map +1 -1
  65. package/dist/esm/core/http.native.js.map +1 -1
  66. package/dist/esm/core/types.native.js.map +1 -1
  67. package/dist/esm/core/wire.native.js.map +1 -1
  68. package/dist/esm/db.native.js.map +1 -1
  69. package/dist/esm/devtools/FrappeDevtoolsPanel.native.js.map +1 -1
  70. package/dist/esm/devtools/index.native.js.map +1 -1
  71. package/dist/esm/devtools/plugin.native.js.map +1 -1
  72. package/dist/esm/devtools/theme.native.js.map +1 -1
  73. package/dist/esm/doctype.mjs +23 -44
  74. package/dist/esm/doctype.mjs.map +1 -1
  75. package/dist/esm/doctype.native.js +25 -44
  76. package/dist/esm/doctype.native.js.map +1 -1
  77. package/dist/esm/file.native.js.map +1 -1
  78. package/dist/esm/fixtureDb.native.js.map +1 -1
  79. package/dist/esm/fixtures.mjs +3 -0
  80. package/dist/esm/fixtures.mjs.map +1 -1
  81. package/dist/esm/fixtures.native.js +3 -0
  82. package/dist/esm/fixtures.native.js.map +1 -1
  83. package/dist/esm/frappeApp.native.js.map +1 -1
  84. package/dist/esm/frappeReact.mjs +68 -33
  85. package/dist/esm/frappeReact.mjs.map +1 -1
  86. package/dist/esm/frappeReact.native.js +87 -35
  87. package/dist/esm/frappeReact.native.js.map +1 -1
  88. package/dist/esm/index.mjs +4 -3
  89. package/dist/esm/index.mjs.map +1 -1
  90. package/dist/esm/index.native.js +4 -3
  91. package/dist/esm/index.native.js.map +1 -1
  92. package/dist/esm/optimistic.native.js.map +1 -1
  93. package/dist/esm/pacer.native.js.map +1 -1
  94. package/dist/esm/plugin/index.native.js.map +1 -1
  95. package/dist/esm/realtime.native.js.map +1 -1
  96. package/dist/esm/serverLoaders.mjs +48 -0
  97. package/dist/esm/serverLoaders.mjs.map +1 -0
  98. package/dist/esm/serverLoaders.native.js +59 -0
  99. package/dist/esm/serverLoaders.native.js.map +1 -0
  100. package/dist/esm/sync/backfill.native.js.map +1 -1
  101. package/dist/esm/sync/filters.native.js.map +1 -1
  102. package/dist/esm/sync/index.mjs +99 -14
  103. package/dist/esm/sync/index.mjs.map +1 -1
  104. package/dist/esm/sync/index.native.js +129 -14
  105. package/dist/esm/sync/index.native.js.map +1 -1
  106. package/dist/esm/sync/merge.native.js.map +1 -1
  107. package/dist/esm/sync/mutationEngine.native.js.map +1 -1
  108. package/dist/esm/sync/mutationJournal.native.js.map +1 -1
  109. package/dist/esm/sync/normalizedCache.native.js.map +1 -1
  110. package/dist/esm/sync/persistence.native.js.map +1 -1
  111. package/dist/esm/sync/realtime.native.js.map +1 -1
  112. package/dist/esm/sync/rehydration.mjs +4 -0
  113. package/dist/esm/sync/rehydration.mjs.map +1 -1
  114. package/dist/esm/sync/rehydration.native.js +6 -0
  115. package/dist/esm/sync/rehydration.native.js.map +1 -1
  116. package/dist/esm/sync/storage.native.js.map +1 -1
  117. package/dist/esm/sync/subscriptionManager.native.js.map +1 -1
  118. package/dist/esm/sync/types.mjs.map +1 -1
  119. package/dist/esm/sync/types.native.js.map +1 -1
  120. package/dist/esm/sync/virtualPageManager.native.js.map +1 -1
  121. package/dist/esm/syncOptionsKey.mjs +34 -0
  122. package/dist/esm/syncOptionsKey.mjs.map +1 -0
  123. package/dist/esm/syncOptionsKey.native.js +54 -0
  124. package/dist/esm/syncOptionsKey.native.js.map +1 -0
  125. package/dist/esm/useLiveQuery.mjs +1 -19
  126. package/dist/esm/useLiveQuery.mjs.map +1 -1
  127. package/dist/esm/useLiveQuery.native.js +1 -22
  128. package/dist/esm/useLiveQuery.native.js.map +1 -1
  129. package/dist/esm/util.native.js.map +1 -1
  130. package/dist/jsx/auth.native.js.map +1 -1
  131. package/dist/jsx/call.native.js.map +1 -1
  132. package/dist/jsx/collection.native.js.map +1 -1
  133. package/dist/jsx/common/types.native.js.map +1 -1
  134. package/dist/jsx/core/http.native.js.map +1 -1
  135. package/dist/jsx/core/types.native.js.map +1 -1
  136. package/dist/jsx/core/wire.native.js.map +1 -1
  137. package/dist/jsx/db.native.js.map +1 -1
  138. package/dist/jsx/devtools/FrappeDevtoolsPanel.native.js.map +1 -1
  139. package/dist/jsx/devtools/index.native.js.map +1 -1
  140. package/dist/jsx/devtools/plugin.native.js.map +1 -1
  141. package/dist/jsx/devtools/theme.native.js.map +1 -1
  142. package/dist/jsx/doctype.mjs +23 -44
  143. package/dist/jsx/doctype.mjs.map +1 -1
  144. package/dist/jsx/doctype.native.js +27 -43
  145. package/dist/jsx/doctype.native.js.map +1 -1
  146. package/dist/jsx/file.native.js.map +1 -1
  147. package/dist/jsx/fixtureDb.native.js.map +1 -1
  148. package/dist/jsx/fixtures.mjs +3 -0
  149. package/dist/jsx/fixtures.mjs.map +1 -1
  150. package/dist/jsx/fixtures.native.js +3 -0
  151. package/dist/jsx/fixtures.native.js.map +1 -1
  152. package/dist/jsx/frappeApp.native.js.map +1 -1
  153. package/dist/jsx/frappeReact.mjs +68 -33
  154. package/dist/jsx/frappeReact.mjs.map +1 -1
  155. package/dist/jsx/frappeReact.native.js +87 -34
  156. package/dist/jsx/frappeReact.native.js.map +1 -1
  157. package/dist/jsx/index.js +4 -3
  158. package/dist/jsx/index.js.map +1 -1
  159. package/dist/jsx/index.mjs +4 -3
  160. package/dist/jsx/index.mjs.map +1 -1
  161. package/dist/jsx/index.native.js +9 -2
  162. package/dist/jsx/index.native.js.map +1 -1
  163. package/dist/jsx/optimistic.native.js.map +1 -1
  164. package/dist/jsx/pacer.native.js.map +1 -1
  165. package/dist/jsx/plugin/index.native.js.map +1 -1
  166. package/dist/jsx/realtime.native.js.map +1 -1
  167. package/dist/jsx/serverLoaders.mjs +48 -0
  168. package/dist/jsx/serverLoaders.mjs.map +1 -0
  169. package/dist/jsx/serverLoaders.native.js +88 -0
  170. package/dist/jsx/serverLoaders.native.js.map +1 -0
  171. package/dist/jsx/sync/backfill.native.js.map +1 -1
  172. package/dist/jsx/sync/filters.native.js.map +1 -1
  173. package/dist/jsx/sync/index.mjs +99 -14
  174. package/dist/jsx/sync/index.mjs.map +1 -1
  175. package/dist/jsx/sync/index.native.js +129 -14
  176. package/dist/jsx/sync/index.native.js.map +1 -1
  177. package/dist/jsx/sync/merge.native.js.map +1 -1
  178. package/dist/jsx/sync/mutationEngine.native.js.map +1 -1
  179. package/dist/jsx/sync/mutationJournal.native.js.map +1 -1
  180. package/dist/jsx/sync/normalizedCache.native.js.map +1 -1
  181. package/dist/jsx/sync/persistence.native.js.map +1 -1
  182. package/dist/jsx/sync/realtime.native.js.map +1 -1
  183. package/dist/jsx/sync/rehydration.mjs +4 -0
  184. package/dist/jsx/sync/rehydration.mjs.map +1 -1
  185. package/dist/jsx/sync/rehydration.native.js +6 -0
  186. package/dist/jsx/sync/rehydration.native.js.map +1 -1
  187. package/dist/jsx/sync/storage.native.js.map +1 -1
  188. package/dist/jsx/sync/subscriptionManager.native.js.map +1 -1
  189. package/dist/jsx/sync/types.mjs.map +1 -1
  190. package/dist/jsx/sync/types.native.js.map +1 -1
  191. package/dist/jsx/sync/virtualPageManager.native.js.map +1 -1
  192. package/dist/jsx/syncOptionsKey.mjs +34 -0
  193. package/dist/jsx/syncOptionsKey.mjs.map +1 -0
  194. package/dist/jsx/syncOptionsKey.native.js +82 -0
  195. package/dist/jsx/syncOptionsKey.native.js.map +1 -0
  196. package/dist/jsx/useLiveQuery.mjs +1 -19
  197. package/dist/jsx/useLiveQuery.mjs.map +1 -1
  198. package/dist/jsx/useLiveQuery.native.js +1 -22
  199. package/dist/jsx/useLiveQuery.native.js.map +1 -1
  200. package/dist/jsx/util.native.js.map +1 -1
  201. package/package.json +12 -12
  202. package/src/doctype.spec.ts +148 -5
  203. package/src/doctype.ts +202 -173
  204. package/src/fixtures.ts +1 -0
  205. package/src/frappeReact.catchUp.spec.tsx +105 -0
  206. package/src/frappeReact.hydration.spec.tsx +164 -0
  207. package/src/frappeReact.spec.ts +56 -0
  208. package/src/frappeReact.ts +93 -43
  209. package/src/index.ts +14 -0
  210. package/src/serverLoaders.ts +77 -0
  211. package/src/sync/catchUp.spec.ts +142 -0
  212. package/src/sync/index.ts +106 -9
  213. package/src/sync/rehydration.ts +5 -0
  214. package/src/sync/syncModule.spec.ts +20 -0
  215. package/src/sync/types.ts +9 -1
  216. package/src/syncOptionsKey.spec.ts +46 -0
  217. package/src/syncOptionsKey.ts +45 -0
  218. package/src/useLiveQuery.hydration.spec.tsx +129 -0
  219. package/src/useLiveQuery.spec.ts +3 -11
  220. package/src/useLiveQuery.ts +3 -57
  221. package/types/doctype.d.ts +117 -33
  222. package/types/doctype.d.ts.map +1 -1
  223. package/types/fixtures.d.ts.map +1 -1
  224. package/types/frappeReact.d.ts +9 -3
  225. package/types/frappeReact.d.ts.map +1 -1
  226. package/types/index.d.ts +6 -5
  227. package/types/index.d.ts.map +1 -1
  228. package/types/serverLoaders.d.ts +40 -0
  229. package/types/serverLoaders.d.ts.map +1 -0
  230. package/types/sync/index.d.ts +15 -1
  231. package/types/sync/index.d.ts.map +1 -1
  232. package/types/sync/rehydration.d.ts +2 -0
  233. package/types/sync/rehydration.d.ts.map +1 -1
  234. package/types/sync/types.d.ts +8 -1
  235. package/types/sync/types.d.ts.map +1 -1
  236. package/types/syncOptionsKey.d.ts +3 -0
  237. package/types/syncOptionsKey.d.ts.map +1 -0
  238. package/types/useLiveQuery.d.ts +3 -13
  239. package/types/useLiveQuery.d.ts.map +1 -1
@@ -24,6 +24,7 @@ var frappeReact_exports = {};
24
24
  __export(frappeReact_exports, {
25
25
  __getActiveSyncModule: () => __getActiveSyncModule,
26
26
  getFrappeSyncModule: () => getFrappeSyncModule,
27
+ useFrappeCatchUp: () => useFrappeCatchUp,
27
28
  useFrappeCollection: () => useFrappeCollection,
28
29
  useFrappeConnection: () => useFrappeConnection,
29
30
  useFrappeDoctypeEvents: () => useFrappeDoctypeEvents,
@@ -41,10 +42,17 @@ var import_fixtureDb = require("./fixtureDb.cjs");
41
42
  var import_realtime = require("./realtime.cjs");
42
43
  var import_sync = require("./sync/index.cjs");
43
44
  var import_types = require("./sync/types.cjs");
45
+ var import_syncOptionsKey = require("./syncOptionsKey.cjs");
44
46
  var import_util = require("./util.cjs");
45
47
  const syncModuleCache = /* @__PURE__ */new Map();
46
48
  const fixtureSyncModuleCache = /* @__PURE__ */new WeakMap();
49
+ let latestSyncModule;
50
+ function rememberLatestSyncModule(sync) {
51
+ if (typeof WeakRef === "function") latestSyncModule = new WeakRef(sync);
52
+ }
47
53
  function __getActiveSyncModule() {
54
+ const latest = latestSyncModule?.deref();
55
+ if (latest) return latest;
48
56
  for (const sync of syncModuleCache.values()) return sync;
49
57
  return void 0;
50
58
  }
@@ -54,20 +62,26 @@ function getFrappeSyncModule(config) {
54
62
  }
55
63
  function getSyncModule(config) {
56
64
  if (!(0, import_util.isBrowser)()) return void 0;
65
+ const optionsKey = (0, import_syncOptionsKey.syncOptionsKey)(config.syncOptions);
57
66
  if (config.fixtures) {
58
- let sync2 = fixtureSyncModuleCache.get(config.fixtures);
67
+ let cache = fixtureSyncModuleCache.get(config.fixtures);
68
+ if (!cache) {
69
+ cache = /* @__PURE__ */new Map();
70
+ fixtureSyncModuleCache.set(config.fixtures, cache);
71
+ }
72
+ let sync2 = cache.get(optionsKey);
59
73
  if (!sync2) {
60
74
  const db = new import_fixtureDb.FixtureDbModule(config.fixtures);
61
75
  sync2 = new import_sync.SyncModule(db, void 0, config.syncOptions);
62
- fixtureSyncModuleCache.set(config.fixtures, sync2);
76
+ cache.set(optionsKey, sync2);
77
+ rememberLatestSyncModule(sync2);
63
78
  }
64
79
  return sync2;
65
80
  }
66
81
  if (!config.baseURL) return void 0;
67
- const key = [config.baseURL, config.realtime === false ? "http" : "realtime", config.socketPort ?? "", config.socketBaseURL ?? "", config.siteName ?? ""].join("|");
82
+ const key = [config.baseURL, config.realtime === false ? "http" : "realtime", config.socketPort ?? "", config.socketBaseURL ?? "", config.siteName ?? "", optionsKey].join("|");
68
83
  let sync = syncModuleCache.get(key);
69
84
  if (!sync) {
70
- console.debug(`[frappe-sync] SyncModule created for ${key} (persistKey=${config.syncOptions?.persistKey ?? "none"}, cdcInterval=${config.syncOptions?.cdcInterval ?? "off"})`);
71
85
  const authConfig = config.auth ? {
72
86
  useToken: config.auth.useToken,
73
87
  token: config.auth.token,
@@ -92,6 +106,7 @@ function getSyncModule(config) {
92
106
  };
93
107
  sync = new import_sync.SyncModule(db, config.realtime === false ? void 0 : new import_realtime.RealtimeModule(realtimeConfig), config.syncOptions);
94
108
  syncModuleCache.set(key, sync);
109
+ rememberLatestSyncModule(sync);
95
110
  }
96
111
  return sync;
97
112
  }
@@ -214,7 +229,11 @@ function cleanupEntryCollectionWhenIdle(entry) {
214
229
  };
215
230
  entry.gcTimer = setTimeout(tick, SHARED_COLLECTION_GC_MS);
216
231
  }
232
+ const subscribeHydration = () => () => {};
233
+ const clientCollectionAvailable = () => true;
234
+ const serverCollectionAvailable = () => false;
217
235
  function useFrappeCollection(config, doctype, options) {
236
+ const collectionAvailable = (0, import_react.useSyncExternalStore)(subscribeHydration, clientCollectionAvailable, serverCollectionAvailable);
218
237
  const errorHandlerRef = (0, import_react.useRef)(options?.onSyncError);
219
238
  errorHandlerRef.current = options?.onSyncError;
220
239
  const stableErrorHandler = (0, import_react.useCallback)(error => {
@@ -224,7 +243,7 @@ function useFrappeCollection(config, doctype, options) {
224
243
  let cache;
225
244
  let key = "";
226
245
  let entry;
227
- if (config && doctype) {
246
+ if (collectionAvailable && config && doctype) {
228
247
  const syncModule = getSyncModule(config);
229
248
  if (syncModule) {
230
249
  key = collectionOptionsKey(doctype, options);
@@ -256,34 +275,48 @@ function useFrappeCollection(config, doctype, options) {
256
275
  }, [entry, stableErrorHandler]);
257
276
  return entry ? entry.collection : void 0;
258
277
  }
278
+ const idleCatchUpState = Object.freeze({
279
+ refreshing: false,
280
+ stale: false,
281
+ error: null
282
+ });
283
+ function useFrappeCatchUp(config) {
284
+ const sync = getFrappeSyncModule(config);
285
+ const subscribe = (0, import_react.useCallback)(listener => sync?.subscribeStore(listener) ?? (() => {}), [sync]);
286
+ const snapshot = (0, import_react.useCallback)(() => sync?.getCatchUpState() ?? idleCatchUpState, [sync]);
287
+ const state = (0, import_react.useSyncExternalStore)(subscribe, snapshot, () => idleCatchUpState);
288
+ const retry = (0, import_react.useCallback)(() => sync?.catchUp() ?? Promise.resolve(idleCatchUpState), [sync]);
289
+ return (0, import_react.useMemo)(() => ({
290
+ ...state,
291
+ retry
292
+ }), [state, retry]);
293
+ }
259
294
  const DISCONNECTED_STATE = Object.freeze({
260
295
  connected: false,
261
296
  everConnected: false
262
297
  });
263
298
  function useFrappeConnection(config) {
264
299
  const [state, setState] = (0, import_react.useState)(DISCONNECTED_STATE);
300
+ const syncModule = config && !config.fixtures && config.realtime !== false ? getSyncModule(config) : void 0;
265
301
  (0, import_react.useEffect)(() => {
266
- if (!config || config.fixtures || config.realtime === false || !(0, import_util.isBrowser)()) {
302
+ if (!syncModule) {
267
303
  setState(DISCONNECTED_STATE);
268
304
  return;
269
305
  }
270
- const syncModule = getSyncModule(config);
271
- if (!syncModule) return;
272
306
  const read = () => {
273
307
  const next = syncModule.getConnectionState();
274
308
  setState(prev => prev.connected === next.connected && prev.everConnected === next.everConnected ? prev : next);
275
309
  };
276
310
  read();
277
311
  return syncModule.subscribeStore(read);
278
- }, [config?.baseURL, config?.realtime, !!config?.fixtures]);
312
+ }, [syncModule]);
279
313
  return config?.realtime === false ? DISCONNECTED_STATE : state;
280
314
  }
281
315
  function useFrappeDoctypeEvents(config, doctype, onEvent, enabled = true) {
282
316
  const handlerRef = (0, import_react.useRef)(onEvent);
283
317
  handlerRef.current = onEvent;
318
+ const syncModule = enabled && config && !config.fixtures && config.realtime !== false && doctype ? getSyncModule(config) : void 0;
284
319
  (0, import_react.useEffect)(() => {
285
- if (!enabled || !config || config.fixtures || config.realtime === false || !doctype || !(0, import_util.isBrowser)()) return;
286
- const syncModule = getSyncModule(config);
287
320
  if (!syncModule) return;
288
321
  syncModule.watchDoctype(doctype);
289
322
  const unsubscribe = syncModule.subscribeRealtimeEvents(event => {
@@ -298,21 +331,20 @@ function useFrappeDoctypeEvents(config, doctype, onEvent, enabled = true) {
298
331
  unsubscribe();
299
332
  syncModule.unwatchDoctype(doctype);
300
333
  };
301
- }, [enabled, config?.baseURL, config?.realtime, !!config?.fixtures, doctype]);
334
+ }, [syncModule, doctype]);
302
335
  }
303
336
  const EMPTY_LEDGER = [];
304
337
  function useFrappePendingMutations(config, doctype) {
305
338
  const [entries, setEntries] = (0, import_react.useState)(EMPTY_LEDGER);
306
339
  const syncModuleRef = (0, import_react.useRef)(void 0);
340
+ const syncModule = getFrappeSyncModule(config);
307
341
  (0, import_react.useEffect)(() => {
308
- if (!config || !(0, import_util.isBrowser)()) {
342
+ if (!syncModule) {
309
343
  setEntries(EMPTY_LEDGER);
310
344
  return;
311
345
  }
312
- const syncModule = getSyncModule(config);
313
- if (!syncModule) return;
314
346
  syncModuleRef.current = syncModule;
315
- let lastSignature = "";
347
+ let lastSignature;
316
348
  const read = () => {
317
349
  const next = syncModule.getMutationEntries(doctype);
318
350
  const signature = next.map(e => `${e.id}:${e.status}:${e.retryCount}`).join("|");
@@ -321,8 +353,12 @@ function useFrappePendingMutations(config, doctype) {
321
353
  setEntries(next.length === 0 ? EMPTY_LEDGER : next);
322
354
  };
323
355
  read();
324
- return syncModule.subscribeStore(read);
325
- }, [config?.baseURL, config?.realtime, !!config?.fixtures, doctype]);
356
+ const unsubscribe = syncModule.subscribeStore(read);
357
+ return () => {
358
+ unsubscribe();
359
+ syncModuleRef.current = void 0;
360
+ };
361
+ }, [syncModule, doctype]);
326
362
  const retry = (0, import_react.useCallback)(async () => {
327
363
  await syncModuleRef.current?.replayPending();
328
364
  }, []);
@@ -437,9 +473,8 @@ function useFrappeInfiniteList(options) {
437
473
  const subscriptionIdRef = (0, import_react.useRef)(void 0);
438
474
  const syncModuleRef = (0, import_react.useRef)(void 0);
439
475
  const filtersKey = JSON.stringify(filters);
476
+ const syncModule = getFrappeSyncModule(config);
440
477
  (0, import_react.useEffect)(() => {
441
- if (!config || !(0, import_util.isBrowser)()) return;
442
- const syncModule = getSyncModule(config);
443
478
  if (!syncModule) return;
444
479
  syncModuleRef.current = syncModule;
445
480
  let cancelled = false;
@@ -489,19 +524,20 @@ function useFrappeInfiniteList(options) {
489
524
  syncModule.unsubscribe(subscriptionIdRef.current);
490
525
  subscriptionIdRef.current = void 0;
491
526
  }
527
+ syncModuleRef.current = void 0;
492
528
  };
493
- }, [config?.baseURL, config?.realtime, config?.auth?.token, doctype, filtersKey, pageSize]);
529
+ }, [syncModule, config?.auth?.token, doctype, filtersKey, pageSize]);
494
530
  const loadMore = (0, import_react.useCallback)(async () => {
495
- const syncModule = syncModuleRef.current;
531
+ const syncModule2 = syncModuleRef.current;
496
532
  const subId = subscriptionIdRef.current;
497
- if (!syncModule || !subId || !hasMore || isLoadingMore) return;
533
+ if (!syncModule2 || !subId || !hasMore || isLoadingMore) return;
498
534
  setIsLoadingMore(true);
499
535
  setError(null);
500
536
  try {
501
- const currentDocs = syncModule.getDocsForSubscription(subId);
537
+ const currentDocs = syncModule2.getDocsForSubscription(subId);
502
538
  const lastDoc = currentDocs[currentDocs.length - 1];
503
539
  if (!lastDoc) return;
504
- await syncModule.getNextBatch(subId, lastDoc.name, pageSize);
540
+ await syncModule2.getNextBatch(subId, lastDoc.name, pageSize);
505
541
  } catch (err) {
506
542
  setError(err instanceof Error ? err : new Error(String(err)));
507
543
  } finally {
@@ -509,16 +545,16 @@ function useFrappeInfiniteList(options) {
509
545
  }
510
546
  }, [hasMore, isLoadingMore, pageSize]);
511
547
  const refresh = (0, import_react.useCallback)(async () => {
512
- const syncModule = syncModuleRef.current;
513
- if (!syncModule || !subscriptionIdRef.current) return;
514
- syncModule.unsubscribe(subscriptionIdRef.current);
548
+ const syncModule2 = syncModuleRef.current;
549
+ if (!syncModule2 || !subscriptionIdRef.current) return;
550
+ syncModule2.unsubscribe(subscriptionIdRef.current);
515
551
  subscriptionIdRef.current = void 0;
516
552
  setItems([]);
517
553
  setHasMore(true);
518
554
  setIsLoading(true);
519
555
  setError(null);
520
556
  try {
521
- const result = await syncModule.subscribe({
557
+ const result = await syncModule2.subscribe({
522
558
  doctype,
523
559
  fields,
524
560
  filters,
@@ -527,8 +563,8 @@ function useFrappeInfiniteList(options) {
527
563
  progressive: true
528
564
  });
529
565
  subscriptionIdRef.current = result.subscriptionId;
530
- const docs = syncModule.getDocsForSubscription(result.subscriptionId);
531
- const pageInfo = syncModule.getPageInfo(result.subscriptionId);
566
+ const docs = syncModule2.getDocsForSubscription(result.subscriptionId);
567
+ const pageInfo = syncModule2.getPageInfo(result.subscriptionId);
532
568
  setItems(transform ? docs.map(transform) : docs);
533
569
  setHasMore(pageInfo.hasMore);
534
570
  } catch (err) {
@@ -26,6 +26,7 @@ var frappeReact_exports = {};
26
26
  __export(frappeReact_exports, {
27
27
  __getActiveSyncModule: () => __getActiveSyncModule,
28
28
  getFrappeSyncModule: () => getFrappeSyncModule,
29
+ useFrappeCatchUp: () => useFrappeCatchUp,
29
30
  useFrappeCollection: () => useFrappeCollection,
30
31
  useFrappeConnection: () => useFrappeConnection,
31
32
  useFrappeDoctypeEvents: () => useFrappeDoctypeEvents,
@@ -41,8 +42,9 @@ var import_http = require("./core/http.native.js");
41
42
  var import_db = require("./db.native.js");
42
43
  var import_fixtureDb = require("./fixtureDb.native.js");
43
44
  var import_realtime = require("./realtime.native.js");
44
- var import_sync = require("./sync/index.native.js");
45
+ var import_sync = require("./sync");
45
46
  var import_types = require("./sync/types.native.js");
47
+ var import_syncOptionsKey = require("./syncOptionsKey.native.js");
46
48
  var import_util = require("./util.native.js");
47
49
  function _instanceof(left, right) {
48
50
  "@swc/helpers - instanceof";
@@ -55,7 +57,13 @@ function _instanceof(left, right) {
55
57
  }
56
58
  var syncModuleCache = /* @__PURE__ */new Map();
57
59
  var fixtureSyncModuleCache = /* @__PURE__ */new WeakMap();
60
+ var latestSyncModule;
61
+ function rememberLatestSyncModule(sync) {
62
+ if (typeof WeakRef === "function") latestSyncModule = new WeakRef(sync);
63
+ }
58
64
  function __getActiveSyncModule() {
65
+ var latest = latestSyncModule === null || latestSyncModule === void 0 ? void 0 : latestSyncModule.deref();
66
+ if (latest) return latest;
59
67
  var _iteratorNormalCompletion = true,
60
68
  _didIteratorError = false,
61
69
  _iteratorError = void 0;
@@ -87,22 +95,26 @@ function getFrappeSyncModule(config) {
87
95
  function getSyncModule(config) {
88
96
  var _config_socketPort, _config_socketBaseURL, _config_siteName;
89
97
  if (!(0, import_util.isBrowser)()) return void 0;
98
+ var optionsKey = (0, import_syncOptionsKey.syncOptionsKey)(config.syncOptions);
90
99
  if (config.fixtures) {
91
- var sync = fixtureSyncModuleCache.get(config.fixtures);
100
+ var cache = fixtureSyncModuleCache.get(config.fixtures);
101
+ if (!cache) {
102
+ cache = /* @__PURE__ */new Map();
103
+ fixtureSyncModuleCache.set(config.fixtures, cache);
104
+ }
105
+ var sync = cache.get(optionsKey);
92
106
  if (!sync) {
93
107
  var db = new import_fixtureDb.FixtureDbModule(config.fixtures);
94
108
  sync = new import_sync.SyncModule(db, void 0, config.syncOptions);
95
- fixtureSyncModuleCache.set(config.fixtures, sync);
109
+ cache.set(optionsKey, sync);
110
+ rememberLatestSyncModule(sync);
96
111
  }
97
112
  return sync;
98
113
  }
99
114
  if (!config.baseURL) return void 0;
100
- var key = [config.baseURL, config.realtime === false ? "http" : "realtime", (_config_socketPort = config.socketPort) !== null && _config_socketPort !== void 0 ? _config_socketPort : "", (_config_socketBaseURL = config.socketBaseURL) !== null && _config_socketBaseURL !== void 0 ? _config_socketBaseURL : "", (_config_siteName = config.siteName) !== null && _config_siteName !== void 0 ? _config_siteName : ""].join("|");
115
+ var key = [config.baseURL, config.realtime === false ? "http" : "realtime", (_config_socketPort = config.socketPort) !== null && _config_socketPort !== void 0 ? _config_socketPort : "", (_config_socketBaseURL = config.socketBaseURL) !== null && _config_socketBaseURL !== void 0 ? _config_socketBaseURL : "", (_config_siteName = config.siteName) !== null && _config_siteName !== void 0 ? _config_siteName : "", optionsKey].join("|");
101
116
  var sync1 = syncModuleCache.get(key);
102
117
  if (!sync1) {
103
- var _ref, _ref1;
104
- var _config_syncOptions, _config_syncOptions1;
105
- console.debug(`[frappe-sync] SyncModule created for ${key} (persistKey=${(_ref = (_config_syncOptions = config.syncOptions) === null || _config_syncOptions === void 0 ? void 0 : _config_syncOptions.persistKey) !== null && _ref !== void 0 ? _ref : "none"}, cdcInterval=${(_ref1 = (_config_syncOptions1 = config.syncOptions) === null || _config_syncOptions1 === void 0 ? void 0 : _config_syncOptions1.cdcInterval) !== null && _ref1 !== void 0 ? _ref1 : "off"})`);
106
118
  var authConfig = config.auth ? {
107
119
  useToken: config.auth.useToken,
108
120
  token: config.auth.token,
@@ -127,6 +139,7 @@ function getSyncModule(config) {
127
139
  };
128
140
  sync1 = new import_sync.SyncModule(db1, config.realtime === false ? void 0 : new import_realtime.RealtimeModule(realtimeConfig), config.syncOptions);
129
141
  syncModuleCache.set(key, sync1);
142
+ rememberLatestSyncModule(sync1);
130
143
  }
131
144
  return sync1;
132
145
  }
@@ -275,7 +288,17 @@ function cleanupEntryCollectionWhenIdle(entry) {
275
288
  };
276
289
  entry.gcTimer = setTimeout(tick, SHARED_COLLECTION_GC_MS);
277
290
  }
291
+ var subscribeHydration = function subscribeHydration2() {
292
+ return function () {};
293
+ };
294
+ var clientCollectionAvailable = function clientCollectionAvailable2() {
295
+ return true;
296
+ };
297
+ var serverCollectionAvailable = function serverCollectionAvailable2() {
298
+ return false;
299
+ };
278
300
  function useFrappeCollection(config, doctype, options) {
301
+ var collectionAvailable = (0, import_react.useSyncExternalStore)(subscribeHydration, clientCollectionAvailable, serverCollectionAvailable);
279
302
  var errorHandlerRef = (0, import_react.useRef)(options === null || options === void 0 ? void 0 : options.onSyncError);
280
303
  errorHandlerRef.current = options === null || options === void 0 ? void 0 : options.onSyncError;
281
304
  var stableErrorHandler = (0, import_react.useCallback)(function (error) {
@@ -286,7 +309,7 @@ function useFrappeCollection(config, doctype, options) {
286
309
  var cache;
287
310
  var key = "";
288
311
  var entry;
289
- if (config && doctype) {
312
+ if (collectionAvailable && config && doctype) {
290
313
  var syncModule = getSyncModule(config);
291
314
  if (syncModule) {
292
315
  key = collectionOptionsKey(doctype, options);
@@ -325,19 +348,47 @@ function useFrappeCollection(config, doctype, options) {
325
348
  [entry, stableErrorHandler]);
326
349
  return entry ? entry.collection : void 0;
327
350
  }
351
+ var idleCatchUpState = Object.freeze({
352
+ refreshing: false,
353
+ stale: false,
354
+ error: null
355
+ });
356
+ function useFrappeCatchUp(config) {
357
+ var sync = getFrappeSyncModule(config);
358
+ var subscribe = (0, import_react.useCallback)(function (listener) {
359
+ var _ref;
360
+ return (_ref = sync === null || sync === void 0 ? void 0 : sync.subscribeStore(listener)) !== null && _ref !== void 0 ? _ref : function () {};
361
+ }, [sync]);
362
+ var snapshot = (0, import_react.useCallback)(function () {
363
+ var _ref;
364
+ return (_ref = sync === null || sync === void 0 ? void 0 : sync.getCatchUpState()) !== null && _ref !== void 0 ? _ref : idleCatchUpState;
365
+ }, [sync]);
366
+ var state = (0, import_react.useSyncExternalStore)(subscribe, snapshot, function () {
367
+ return idleCatchUpState;
368
+ });
369
+ var retry = (0, import_react.useCallback)(function () {
370
+ var _ref;
371
+ return (_ref = sync === null || sync === void 0 ? void 0 : sync.catchUp()) !== null && _ref !== void 0 ? _ref : Promise.resolve(idleCatchUpState);
372
+ }, [sync]);
373
+ return (0, import_react.useMemo)(function () {
374
+ return {
375
+ ...state,
376
+ retry
377
+ };
378
+ }, [state, retry]);
379
+ }
328
380
  var DISCONNECTED_STATE = Object.freeze({
329
381
  connected: false,
330
382
  everConnected: false
331
383
  });
332
384
  function useFrappeConnection(config) {
333
385
  var [state, setState] = (0, import_react.useState)(DISCONNECTED_STATE);
386
+ var syncModule = config && !config.fixtures && config.realtime !== false ? getSyncModule(config) : void 0;
334
387
  (0, import_react.useEffect)(function () {
335
- if (!config || config.fixtures || config.realtime === false || !(0, import_util.isBrowser)()) {
388
+ if (!syncModule) {
336
389
  setState(DISCONNECTED_STATE);
337
390
  return;
338
391
  }
339
- var syncModule = getSyncModule(config);
340
- if (!syncModule) return;
341
392
  var read = function read2() {
342
393
  var next = syncModule.getConnectionState();
343
394
  setState(function (prev) {
@@ -348,16 +399,15 @@ function useFrappeConnection(config) {
348
399
  return syncModule.subscribeStore(read);
349
400
  },
350
401
  // eslint-disable-next-line react-hooks/exhaustive-deps
351
- [config === null || config === void 0 ? void 0 : config.baseURL, config === null || config === void 0 ? void 0 : config.realtime, !!(config === null || config === void 0 ? void 0 : config.fixtures)]);
402
+ [syncModule]);
352
403
  return (config === null || config === void 0 ? void 0 : config.realtime) === false ? DISCONNECTED_STATE : state;
353
404
  }
354
405
  function useFrappeDoctypeEvents(config, doctype, onEvent) {
355
406
  var enabled = arguments.length > 3 && arguments[3] !== void 0 ? arguments[3] : true;
356
407
  var handlerRef = (0, import_react.useRef)(onEvent);
357
408
  handlerRef.current = onEvent;
409
+ var syncModule = enabled && config && !config.fixtures && config.realtime !== false && doctype ? getSyncModule(config) : void 0;
358
410
  (0, import_react.useEffect)(function () {
359
- if (!enabled || !config || config.fixtures || config.realtime === false || !doctype || !(0, import_util.isBrowser)()) return;
360
- var syncModule = getSyncModule(config);
361
411
  if (!syncModule) return;
362
412
  syncModule.watchDoctype(doctype);
363
413
  var unsubscribe = syncModule.subscribeRealtimeEvents(function (event) {
@@ -374,21 +424,20 @@ function useFrappeDoctypeEvents(config, doctype, onEvent) {
374
424
  };
375
425
  },
376
426
  // eslint-disable-next-line react-hooks/exhaustive-deps
377
- [enabled, config === null || config === void 0 ? void 0 : config.baseURL, config === null || config === void 0 ? void 0 : config.realtime, !!(config === null || config === void 0 ? void 0 : config.fixtures), doctype]);
427
+ [syncModule, doctype]);
378
428
  }
379
429
  var EMPTY_LEDGER = [];
380
430
  function useFrappePendingMutations(config, doctype) {
381
431
  var [entries, setEntries] = (0, import_react.useState)(EMPTY_LEDGER);
382
432
  var syncModuleRef = (0, import_react.useRef)(void 0);
433
+ var syncModule = getFrappeSyncModule(config);
383
434
  (0, import_react.useEffect)(function () {
384
- if (!config || !(0, import_util.isBrowser)()) {
435
+ if (!syncModule) {
385
436
  setEntries(EMPTY_LEDGER);
386
437
  return;
387
438
  }
388
- var syncModule = getSyncModule(config);
389
- if (!syncModule) return;
390
439
  syncModuleRef.current = syncModule;
391
- var lastSignature = "";
440
+ var lastSignature;
392
441
  var read = function read2() {
393
442
  var next = syncModule.getMutationEntries(doctype);
394
443
  var signature = next.map(function (e) {
@@ -399,10 +448,14 @@ function useFrappePendingMutations(config, doctype) {
399
448
  setEntries(next.length === 0 ? EMPTY_LEDGER : next);
400
449
  };
401
450
  read();
402
- return syncModule.subscribeStore(read);
451
+ var unsubscribe = syncModule.subscribeStore(read);
452
+ return function () {
453
+ unsubscribe();
454
+ syncModuleRef.current = void 0;
455
+ };
403
456
  },
404
457
  // eslint-disable-next-line react-hooks/exhaustive-deps
405
- [config === null || config === void 0 ? void 0 : config.baseURL, config === null || config === void 0 ? void 0 : config.realtime, !!(config === null || config === void 0 ? void 0 : config.fixtures), doctype]);
458
+ [syncModule, doctype]);
406
459
  var retry = (0, import_react.useCallback)(async function () {
407
460
  var _syncModuleRef_current;
408
461
  await ((_syncModuleRef_current = syncModuleRef.current) === null || _syncModuleRef_current === void 0 ? void 0 : _syncModuleRef_current.replayPending());
@@ -525,9 +578,8 @@ function useFrappeInfiniteList(options) {
525
578
  var subscriptionIdRef = (0, import_react.useRef)(void 0);
526
579
  var syncModuleRef = (0, import_react.useRef)(void 0);
527
580
  var filtersKey = JSON.stringify(filters);
581
+ var syncModule = getFrappeSyncModule(config);
528
582
  (0, import_react.useEffect)(function () {
529
- if (!config || !(0, import_util.isBrowser)()) return;
530
- var syncModule = getSyncModule(config);
531
583
  if (!syncModule) return;
532
584
  syncModuleRef.current = syncModule;
533
585
  var cancelled = false;
@@ -577,21 +629,22 @@ function useFrappeInfiniteList(options) {
577
629
  syncModule.unsubscribe(subscriptionIdRef.current);
578
630
  subscriptionIdRef.current = void 0;
579
631
  }
632
+ syncModuleRef.current = void 0;
580
633
  };
581
634
  },
582
635
  // eslint-disable-next-line react-hooks/exhaustive-deps
583
- [config === null || config === void 0 ? void 0 : config.baseURL, config === null || config === void 0 ? void 0 : config.realtime, config === null || config === void 0 ? void 0 : (_config_auth = config.auth) === null || _config_auth === void 0 ? void 0 : _config_auth.token, doctype, filtersKey, pageSize]);
636
+ [syncModule, config === null || config === void 0 ? void 0 : (_config_auth = config.auth) === null || _config_auth === void 0 ? void 0 : _config_auth.token, doctype, filtersKey, pageSize]);
584
637
  var loadMore = (0, import_react.useCallback)(async function () {
585
- var syncModule = syncModuleRef.current;
638
+ var syncModule2 = syncModuleRef.current;
586
639
  var subId = subscriptionIdRef.current;
587
- if (!syncModule || !subId || !hasMore || isLoadingMore) return;
640
+ if (!syncModule2 || !subId || !hasMore || isLoadingMore) return;
588
641
  setIsLoadingMore(true);
589
642
  setError(null);
590
643
  try {
591
- var currentDocs = syncModule.getDocsForSubscription(subId);
644
+ var currentDocs = syncModule2.getDocsForSubscription(subId);
592
645
  var lastDoc = currentDocs[currentDocs.length - 1];
593
646
  if (!lastDoc) return;
594
- await syncModule.getNextBatch(subId, lastDoc.name, pageSize);
647
+ await syncModule2.getNextBatch(subId, lastDoc.name, pageSize);
595
648
  } catch (err) {
596
649
  setError(_instanceof(err, Error) ? err : new Error(String(err)));
597
650
  } finally {
@@ -599,16 +652,16 @@ function useFrappeInfiniteList(options) {
599
652
  }
600
653
  }, [hasMore, isLoadingMore, pageSize]);
601
654
  var refresh = (0, import_react.useCallback)(async function () {
602
- var syncModule = syncModuleRef.current;
603
- if (!syncModule || !subscriptionIdRef.current) return;
604
- syncModule.unsubscribe(subscriptionIdRef.current);
655
+ var syncModule2 = syncModuleRef.current;
656
+ if (!syncModule2 || !subscriptionIdRef.current) return;
657
+ syncModule2.unsubscribe(subscriptionIdRef.current);
605
658
  subscriptionIdRef.current = void 0;
606
659
  setItems([]);
607
660
  setHasMore(true);
608
661
  setIsLoading(true);
609
662
  setError(null);
610
663
  try {
611
- var result = await syncModule.subscribe({
664
+ var result = await syncModule2.subscribe({
612
665
  doctype,
613
666
  fields,
614
667
  filters,
@@ -617,8 +670,8 @@ function useFrappeInfiniteList(options) {
617
670
  progressive: true
618
671
  });
619
672
  subscriptionIdRef.current = result.subscriptionId;
620
- var docs = syncModule.getDocsForSubscription(result.subscriptionId);
621
- var pageInfo = syncModule.getPageInfo(result.subscriptionId);
673
+ var docs = syncModule2.getDocsForSubscription(result.subscriptionId);
674
+ var pageInfo = syncModule2.getPageInfo(result.subscriptionId);
622
675
  setItems(transform ? docs.map(transform) : docs);
623
676
  setHasMore(pageInfo.hasMore);
624
677
  } catch (err) {