pbtsdb 2.0.0 → 2.0.1

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.
package/README.md CHANGED
@@ -21,6 +21,7 @@ A TypeScript library that seamlessly integrates [PocketBase](https://pocketbase.
21
21
  - [Related data](#related-data)
22
22
  - [React Integration](#react-integration)
23
23
  - [Subscriptions](#subscriptions)
24
+ - [Sync Status API](#sync-status-api)
24
25
  - [Utility Functions](#utility-functions)
25
26
  - [Usage Examples](#usage-examples)
26
27
  - [Includes (Nested Subqueries)](#includes-nested-subqueries)
@@ -293,6 +294,49 @@ resetRealtime(pb); // open it again; every ready collection reloads
293
294
 
294
295
  `disconnectRealtime(pb)` closes the connection and keeps it closed until `resetRealtime(pb)`. Collections keep working over REST and keep their subscriptions registered. Use it at logout, or at startup where realtime is not wanted, such as an embedded view.
295
296
 
297
+ ### Sync Status
298
+
299
+ Two problems are visible only to pbtsdb: the realtime stream dropping while REST still works, so lists look fine but stop updating; and a query sleeping in its load retry backoff, so a list sits in "loading" with no reason shown. `getSyncStatus(pb)` reports both, for every collection of one client:
300
+
301
+ ```typescript
302
+ import { getSyncStatus, subscribeSyncStatus } from 'pbtsdb';
303
+
304
+ const status = getSyncStatus(pb);
305
+ // {
306
+ // realtime:
307
+ // | { state: 'disabled' } // disconnectRealtime(pb), or no collection has subscribed yet
308
+ // | { state: 'connecting' } // the first connection of a session is opening
309
+ // | { state: 'connected' } // stream open, PB_CONNECT received
310
+ // | { state: 'reconnecting'; attempt: number; nextRetryAt: number; since: number },
311
+ // loads: {
312
+ // retrying: number, // live queries sleeping in loadRetryDelays backoff
313
+ // failingSince?: number, // when the oldest of them first failed, epoch ms
314
+ // failed: number, // live queries whose load ended in a 4xx that is not retried
315
+ // },
316
+ // }
317
+
318
+ const stop = subscribeSyncStatus(pb, status => console.log(status));
319
+ ```
320
+
321
+ `reconnecting` means the stream was up and dropped, or a retry never came up, and the next retry is scheduled; the first connection of a session reports `connecting` instead. `nextRetryAt` and `since` are epoch milliseconds, and `nextRetryAt` changes only when a new retry is scheduled. `disabled` is not a problem state: it is what `disconnectRealtime(pb)` asks for, and what an app sees before login.
322
+
323
+ `retrying` counts only demands a live query still holds; a parked subset or an unmounted query is never counted. `failed` is separate because a 403 or 404 is an answer, not an outage; the count drops when the query reloads or unmounts.
324
+
325
+ The snapshot is stable: `getSyncStatus(pb)` returns the same object, with the same nested objects, until a value changes. The React entry point wraps it in `useSyncStatus(pb)`:
326
+
327
+ ```tsx
328
+ import { useSyncStatus } from 'pbtsdb';
329
+
330
+ function SyncNotice() {
331
+ const { realtime, loads } = useSyncStatus(pb);
332
+ if (realtime.state === 'reconnecting') return <p>Live updates paused, reconnecting…</p>;
333
+ if (loads.retrying > 0) return <p>Retrying {loads.retrying} queries…</p>;
334
+ return null;
335
+ }
336
+ ```
337
+
338
+ The shape of `SyncStatus` is public and changes only in a major release.
339
+
296
340
  ### Subset Lifetime
297
341
 
298
342
  In on-demand mode a live query's subset is released when the last subscriber unmounts, after a grace window of `subsetGcTime` milliseconds (default 5000). A query with an equal request that mounts within the window reuses the rows with no request. Realtime keeps the parked rows fresh, and rows a parent filed through a relation stay for the same window, so a panel that mounts and unmounts quickly costs no refetch. `collection.reload()` releases every parked subset. An auth change releases every waiting subset and every accepted row at once and reloads every live subset under the new auth, whether or not realtime is connected, so a load right after a logout never reuses the previous user's rows and the order of `authStore.clear()` and `disconnectRealtime(pb)` does not matter. Set `subsetGcTime: 0` to release a subset as soon as it unloads; `0` also keeps accepted rows until a reload or idle instead of expiring them.
@@ -751,6 +795,32 @@ An `expand` you add here is yours: pbtsdb strips only the paths it requested
751
795
  through `alwaysFetchRelations` and `fetchRelations()`, so records expanded by
752
796
  this option stay on the echoed rows, untyped.
753
797
 
798
+ ### Sync Status API
799
+
800
+ #### getSyncStatus()
801
+
802
+ ```typescript
803
+ getSyncStatus(pb: PocketBase): SyncStatus
804
+ ```
805
+
806
+ The current [sync status](#sync-status) of `pb`'s collections. The returned object is stable until a value changes.
807
+
808
+ #### subscribeSyncStatus()
809
+
810
+ ```typescript
811
+ subscribeSyncStatus(pb: PocketBase, listener: (status: SyncStatus) => void): () => void
812
+ ```
813
+
814
+ Calls `listener` with each new snapshot. Returns the unsubscribe function.
815
+
816
+ #### useSyncStatus()
817
+
818
+ ```typescript
819
+ useSyncStatus(pb: PocketBase): SyncStatus
820
+ ```
821
+
822
+ React hook over the two functions above, built on `useSyncExternalStore`. Exported from `pbtsdb`, not from `pbtsdb/core`.
823
+
754
824
  ### Utility Functions
755
825
 
756
826
  #### newRecordId()
@@ -1530,7 +1530,9 @@ function createSyncAdapter(deps) {
1530
1530
  loading: void 0,
1531
1531
  parked: void 0,
1532
1532
  landed: false,
1533
- wake: void 0
1533
+ wake: void 0,
1534
+ retryingSince: void 0,
1535
+ failed: false
1534
1536
  };
1535
1537
  if (mode !== "query") return { ...base, filters: void 0, counted: false };
1536
1538
  const filters = realtimeFiltersFor(toRequest({ where: realtimeWhereFor(opts) }));
@@ -1652,11 +1654,36 @@ function createSyncAdapter(deps) {
1652
1654
  run.eagerWake?.();
1653
1655
  for (const demand of run.demands.values()) demand.wake?.();
1654
1656
  }
1657
+ const { onLoadStatusChange } = deps;
1658
+ function loadStatus() {
1659
+ const status = { retrying: 0, failed: 0 };
1660
+ const run = current;
1661
+ if (!run) return status;
1662
+ const count = (retryingSince, failed) => {
1663
+ if (failed) status.failed += 1;
1664
+ if (retryingSince === void 0) return;
1665
+ status.retrying += 1;
1666
+ if (status.failingSince === void 0 || retryingSince < status.failingSince) {
1667
+ status.failingSince = retryingSince;
1668
+ }
1669
+ };
1670
+ for (const demand of run.demands.values()) {
1671
+ if (demand.parked === void 0) count(demand.retryingSince, demand.failed);
1672
+ }
1673
+ count(run.eagerRetryingSince, run.eagerFailed);
1674
+ return status;
1675
+ }
1655
1676
  async function retryAllowed(run, opts, demand, seq, attempt, error) {
1656
1677
  if (superseded(run, opts, demand, seq)) return false;
1657
- if (loadRetryDelays.length === 0 || !isRetryable(error)) throw error;
1678
+ if (loadRetryDelays.length === 0 || !isRetryable(error)) {
1679
+ demand.failed = true;
1680
+ onLoadStatusChange();
1681
+ throw error;
1682
+ }
1658
1683
  const delay = retryDelay(attempt, error);
1659
1684
  logger.warn("Subset load failed; retrying", { collectionName, attempt, delay, error });
1685
+ demand.retryingSince ?? (demand.retryingSince = Date.now());
1686
+ onLoadStatusChange();
1660
1687
  await sleep(delay, (wake) => {
1661
1688
  demand.wake = wake;
1662
1689
  });
@@ -1664,17 +1691,35 @@ function createSyncAdapter(deps) {
1664
1691
  return !superseded(run, opts, demand, seq);
1665
1692
  }
1666
1693
  async function fetchWithRetry(run, opts, demand, seq, refetch) {
1667
- for (let attempt = 0; ; attempt++) {
1668
- try {
1669
- return await fetchRows(run, demand.request, opts.signal, refetch, demand.filters);
1670
- } catch (error) {
1671
- if (!await retryAllowed(run, opts, demand, seq, attempt, error)) return void 0;
1694
+ try {
1695
+ for (let attempt = 0; ; attempt++) {
1696
+ try {
1697
+ return await fetchRows(
1698
+ run,
1699
+ demand.request,
1700
+ opts.signal,
1701
+ refetch,
1702
+ demand.filters
1703
+ );
1704
+ } catch (error) {
1705
+ if (!await retryAllowed(run, opts, demand, seq, attempt, error)) {
1706
+ return void 0;
1707
+ }
1708
+ }
1709
+ }
1710
+ } finally {
1711
+ if (demand.seq === seq) {
1712
+ demand.retryingSince = void 0;
1713
+ onLoadStatusChange();
1672
1714
  }
1673
1715
  }
1674
1716
  }
1675
1717
  function load(run, opts, demand, wait, refetch) {
1676
1718
  demand.wake?.();
1677
1719
  const seq = ++demand.seq;
1720
+ demand.failed = false;
1721
+ demand.retryingSince = void 0;
1722
+ onLoadStatusChange();
1678
1723
  const before = uncoveredRows(demand);
1679
1724
  const loading = (async () => {
1680
1725
  const result = await fetchWithRetry(run, opts, demand, seq, refetch);
@@ -1697,6 +1742,7 @@ function createSyncAdapter(deps) {
1697
1742
  run.demands.delete(opts);
1698
1743
  if (demand.counted) realtime.releaseQueryFilters(demand.filters);
1699
1744
  membership2.drop([opts]);
1745
+ onLoadStatusChange();
1700
1746
  }
1701
1747
  function unloadSubset(run, opts) {
1702
1748
  try {
@@ -1709,6 +1755,7 @@ function createSyncAdapter(deps) {
1709
1755
  demand.parked = setTimeout(() => {
1710
1756
  if (current === run) expire(run, opts);
1711
1757
  }, subsetGcTime);
1758
+ onLoadStatusChange();
1712
1759
  } catch (error) {
1713
1760
  logger.error("unloadSubset failed", { collectionName, error });
1714
1761
  }
@@ -1724,6 +1771,7 @@ function createSyncAdapter(deps) {
1724
1771
  if (parked.parked === void 0 || keyOf(parked) !== key) continue;
1725
1772
  clearTimeout(parked.parked);
1726
1773
  run.demands.delete(opts);
1774
+ onLoadStatusChange();
1727
1775
  return opts;
1728
1776
  }
1729
1777
  return void 0;
@@ -1782,31 +1830,46 @@ function createSyncAdapter(deps) {
1782
1830
  return load(run, opts, demand, "visible", opts.refetch === true).then(() => void 0);
1783
1831
  };
1784
1832
  }
1833
+ async function eagerRetryAllowed(run, seq, attempt, error) {
1834
+ const stale = () => run.abort.signal.aborted || run.eagerSeq !== seq;
1835
+ if (stale()) throw error;
1836
+ if (loadRetryDelays.length === 0 || !isRetryable(error)) {
1837
+ run.eagerFailed = true;
1838
+ onLoadStatusChange();
1839
+ throw error;
1840
+ }
1841
+ const delay = retryDelay(attempt, error);
1842
+ logger.warn("Eager load failed; retrying", { collectionName, attempt, delay, error });
1843
+ run.eagerRetryingSince ?? (run.eagerRetryingSince = Date.now());
1844
+ onLoadStatusChange();
1845
+ await sleep(delay, (wake) => {
1846
+ run.eagerWake = wake;
1847
+ });
1848
+ run.eagerWake = void 0;
1849
+ if (stale()) throw error;
1850
+ }
1785
1851
  async function fetchEagerWithRetry(run, seq) {
1786
- for (let attempt = 0; ; attempt++) {
1787
- try {
1788
- return await fetchRows(run, {}, void 0, false, void 0);
1789
- } catch (error) {
1790
- const stale = () => run.abort.signal.aborted || run.eagerSeq !== seq;
1791
- if (loadRetryDelays.length === 0 || !isRetryable(error) || stale()) throw error;
1792
- const delay = retryDelay(attempt, error);
1793
- logger.warn("Eager load failed; retrying", {
1794
- collectionName,
1795
- attempt,
1796
- delay,
1797
- error
1798
- });
1799
- await sleep(delay, (wake) => {
1800
- run.eagerWake = wake;
1801
- });
1802
- run.eagerWake = void 0;
1803
- if (stale()) throw error;
1852
+ try {
1853
+ for (let attempt = 0; ; attempt++) {
1854
+ try {
1855
+ return await fetchRows(run, {}, void 0, false, void 0);
1856
+ } catch (error) {
1857
+ await eagerRetryAllowed(run, seq, attempt, error);
1858
+ }
1859
+ }
1860
+ } finally {
1861
+ if (run.eagerSeq === seq) {
1862
+ run.eagerRetryingSince = void 0;
1863
+ onLoadStatusChange();
1804
1864
  }
1805
1865
  }
1806
1866
  }
1807
1867
  function loadEagerRows(run) {
1808
1868
  run.eagerWake?.();
1809
1869
  const seq = ++run.eagerSeq;
1870
+ run.eagerFailed = false;
1871
+ run.eagerRetryingSince = void 0;
1872
+ onLoadStatusChange();
1810
1873
  const loading = (async () => {
1811
1874
  const { rows, filings } = await fetchEagerWithRetry(run, seq);
1812
1875
  if (run.abort.signal.aborted || run.eagerSeq !== seq) {
@@ -1894,7 +1957,9 @@ function createSyncAdapter(deps) {
1894
1957
  demands: /* @__PURE__ */ new Map(),
1895
1958
  eagerSeq: 0,
1896
1959
  eagerLoad: void 0,
1897
- eagerWake: void 0
1960
+ eagerWake: void 0,
1961
+ eagerRetryingSince: void 0,
1962
+ eagerFailed: false
1898
1963
  };
1899
1964
  current = run;
1900
1965
  store.attach(channel);
@@ -1912,6 +1977,7 @@ function createSyncAdapter(deps) {
1912
1977
  subsets.clear();
1913
1978
  if (syncMode === "on-demand") realtime.resetQueryFilters();
1914
1979
  deps.onCleanup();
1980
+ onLoadStatusChange();
1915
1981
  };
1916
1982
  if (syncMode === "eager") {
1917
1983
  void startEager(run, params.markReady, params.markError);
@@ -1944,7 +2010,8 @@ function createSyncAdapter(deps) {
1944
2010
  return [...current.demands].filter(
1945
2011
  ([opts, demand]) => demand.parked === void 0 && demand.landed && matcherFor(opts.where, true)(row)
1946
2012
  ).map(([opts]) => opts);
1947
- }
2013
+ },
2014
+ loadStatus
1948
2015
  };
1949
2016
  }
1950
2017
 
@@ -2012,12 +2079,25 @@ function createRealtimeClient(deps) {
2012
2079
  let resubmit = false;
2013
2080
  let attempts = 0;
2014
2081
  let reconnectTimer;
2082
+ let lostAt;
2083
+ let nextRetryAt = 0;
2015
2084
  let everConnected = false;
2016
2085
  let session = 0;
2017
2086
  let disabled = false;
2018
2087
  function topics() {
2019
2088
  return [...listeners.keys()];
2020
2089
  }
2090
+ function status() {
2091
+ if (disabled || !source && reconnectTimer === void 0) return { state: "disabled" };
2092
+ if (connected) return { state: "connected" };
2093
+ if (lostAt !== void 0) {
2094
+ return { state: "reconnecting", attempt: attempts, nextRetryAt, since: lostAt };
2095
+ }
2096
+ return { state: "connecting" };
2097
+ }
2098
+ function statusChanged() {
2099
+ deps.onStatusChange?.();
2100
+ }
2021
2101
  function dispatcherFor(topic) {
2022
2102
  let dispatch = dispatchers.get(topic);
2023
2103
  if (!dispatch) {
@@ -2048,6 +2128,7 @@ function createRealtimeClient(deps) {
2048
2128
  close(true);
2049
2129
  clientId = void 0;
2050
2130
  lastSeq = void 0;
2131
+ statusChanged();
2051
2132
  return;
2052
2133
  }
2053
2134
  if (sameList(list, confirmed)) return;
@@ -2071,6 +2152,7 @@ function createRealtimeClient(deps) {
2071
2152
  close(true);
2072
2153
  clientId = void 0;
2073
2154
  lastSeq = void 0;
2155
+ statusChanged();
2074
2156
  settleWaiters(pending);
2075
2157
  }
2076
2158
  async function postTopics(pending, startedSession) {
@@ -2140,6 +2222,7 @@ function createRealtimeClient(deps) {
2140
2222
  if (forget) {
2141
2223
  confirmed = [];
2142
2224
  everConnected = false;
2225
+ lostAt = void 0;
2143
2226
  session += 1;
2144
2227
  }
2145
2228
  }
@@ -2153,6 +2236,7 @@ function createRealtimeClient(deps) {
2153
2236
  clientId = void 0;
2154
2237
  lastSeq = void 0;
2155
2238
  attempts = 0;
2239
+ lostAt = void 0;
2156
2240
  session += 1;
2157
2241
  if (listeners.size > 0) {
2158
2242
  try {
@@ -2161,10 +2245,13 @@ function createRealtimeClient(deps) {
2161
2245
  logger.error("Failed to reconnect after reset", { error });
2162
2246
  }
2163
2247
  }
2248
+ statusChanged();
2164
2249
  }
2165
2250
  function scheduleReconnect() {
2166
2251
  const delay = backoff[Math.min(attempts, backoff.length - 1)] ?? 0;
2167
2252
  attempts += 1;
2253
+ lostAt ?? (lostAt = Date.now());
2254
+ nextRetryAt = Date.now() + delay;
2168
2255
  reconnectTimer = setTimeout(() => {
2169
2256
  reconnectTimer = void 0;
2170
2257
  if (listeners.size > 0) connect();
@@ -2175,12 +2262,14 @@ function createRealtimeClient(deps) {
2175
2262
  logger.debug("Realtime connection lost", { clientId });
2176
2263
  close(false);
2177
2264
  scheduleReconnect();
2265
+ statusChanged();
2178
2266
  }
2179
2267
  function connect() {
2180
2268
  if (source || disabled) return;
2181
2269
  const previousId = clientId;
2182
2270
  const target = openSource(connectUrl());
2183
2271
  source = target;
2272
+ statusChanged();
2184
2273
  target.addEventListener("error", () => handleConnectionLost(target));
2185
2274
  target.addEventListener("PB_CONNECT", (ev) => {
2186
2275
  if (source !== target) return;
@@ -2203,16 +2292,20 @@ function createRealtimeClient(deps) {
2203
2292
  attachAll(target);
2204
2293
  const isReconnect = everConnected;
2205
2294
  everConnected = true;
2295
+ statusChanged();
2206
2296
  submit().then(
2207
2297
  () => {
2208
2298
  if (source !== target) return;
2209
2299
  attempts = 0;
2300
+ lostAt = void 0;
2301
+ statusChanged();
2210
2302
  if (isReconnect) deps.onReconnect?.(resumed);
2211
2303
  },
2212
2304
  () => {
2213
2305
  if (source !== target || !isReconnect) return;
2214
2306
  close(false);
2215
2307
  scheduleReconnect();
2308
+ statusChanged();
2216
2309
  }
2217
2310
  );
2218
2311
  });
@@ -2268,6 +2361,7 @@ function createRealtimeClient(deps) {
2268
2361
  },
2269
2362
  topics,
2270
2363
  isConnected: () => connected,
2364
+ status,
2271
2365
  clientId: () => clientId,
2272
2366
  disconnect() {
2273
2367
  close(true);
@@ -2277,6 +2371,7 @@ function createRealtimeClient(deps) {
2277
2371
  waiters = [];
2278
2372
  listeners.clear();
2279
2373
  dispatchers.clear();
2374
+ statusChanged();
2280
2375
  settleWaiters(pending, new RealtimeDisconnectedError());
2281
2376
  },
2282
2377
  simulateDisconnect() {
@@ -2297,11 +2392,77 @@ function createRealtimeClient(deps) {
2297
2392
  if (!disabled) return;
2298
2393
  disabled = false;
2299
2394
  if (listeners.size > 0) connect();
2395
+ statusChanged();
2300
2396
  },
2301
2397
  isEnabled: () => !disabled
2302
2398
  };
2303
2399
  }
2304
2400
 
2401
+ // src/sync-status.ts
2402
+ function sameRealtime(a, b) {
2403
+ if (a.state !== b.state) return false;
2404
+ if (a.state !== "reconnecting" || b.state !== "reconnecting") return true;
2405
+ return a.attempt === b.attempt && a.nextRetryAt === b.nextRetryAt && a.since === b.since;
2406
+ }
2407
+ function sameLoads(a, b) {
2408
+ return a.retrying === b.retrying && a.failed === b.failed && a.failingSince === b.failingSince;
2409
+ }
2410
+ function mergeLoads(sources) {
2411
+ const merged = { retrying: 0, failed: 0 };
2412
+ for (const loads of sources) {
2413
+ merged.retrying += loads.retrying;
2414
+ merged.failed += loads.failed;
2415
+ if (loads.failingSince === void 0) continue;
2416
+ if (merged.failingSince === void 0 || loads.failingSince < merged.failingSince) {
2417
+ merged.failingSince = loads.failingSince;
2418
+ }
2419
+ }
2420
+ return merged;
2421
+ }
2422
+ function createSyncStatusStore(deps) {
2423
+ const listeners = /* @__PURE__ */ new Set();
2424
+ let snapshot = { realtime: deps.realtime(), loads: mergeLoads(deps.loads()) };
2425
+ function next() {
2426
+ const realtime = deps.realtime();
2427
+ const loads = mergeLoads(deps.loads());
2428
+ const realtimeChanged = !sameRealtime(snapshot.realtime, realtime);
2429
+ const loadsChanged = !sameLoads(snapshot.loads, loads);
2430
+ if (!realtimeChanged && !loadsChanged) return void 0;
2431
+ return {
2432
+ realtime: realtimeChanged ? realtime : snapshot.realtime,
2433
+ loads: loadsChanged ? loads : snapshot.loads
2434
+ };
2435
+ }
2436
+ let notified = snapshot;
2437
+ let scheduled = false;
2438
+ function get() {
2439
+ const changed = next();
2440
+ if (changed) snapshot = changed;
2441
+ return snapshot;
2442
+ }
2443
+ function notify() {
2444
+ scheduled = false;
2445
+ const current = get();
2446
+ if (current === notified) return;
2447
+ notified = current;
2448
+ for (const listener of listeners) listener(current);
2449
+ }
2450
+ return {
2451
+ get,
2452
+ subscribe(listener) {
2453
+ listeners.add(listener);
2454
+ return () => {
2455
+ listeners.delete(listener);
2456
+ };
2457
+ },
2458
+ refresh() {
2459
+ if (scheduled) return;
2460
+ scheduled = true;
2461
+ queueMicrotask(notify);
2462
+ }
2463
+ };
2464
+ }
2465
+
2305
2466
  // src/transport.ts
2306
2467
  var entries = /* @__PURE__ */ new WeakMap();
2307
2468
  function watchAuth(pb, entry) {
@@ -2317,14 +2478,23 @@ function entryFor(pb) {
2317
2478
  let entry = entries.get(pb);
2318
2479
  if (!entry) {
2319
2480
  const reconnectListeners = /* @__PURE__ */ new Set();
2481
+ const loadSources = /* @__PURE__ */ new Set();
2482
+ let status;
2320
2483
  const client = createRealtimeClient({
2321
- url: pb.buildURL("/api/realtime"),
2484
+ get url() {
2485
+ return pb.buildURL("/api/realtime");
2486
+ },
2322
2487
  send: (body) => pb.send("/api/realtime", { method: "POST", body, requestKey: null }),
2323
2488
  onReconnect: (resumed) => {
2324
2489
  for (const listener of reconnectListeners) listener(resumed);
2325
- }
2490
+ },
2491
+ onStatusChange: () => status?.refresh()
2326
2492
  });
2327
- entry = { client, reconnectListeners, authListeners: /* @__PURE__ */ new Set() };
2493
+ status = createSyncStatusStore({
2494
+ realtime: client.status,
2495
+ loads: () => [...loadSources].map((source) => source())
2496
+ });
2497
+ entry = { client, reconnectListeners, authListeners: /* @__PURE__ */ new Set(), status, loadSources };
2328
2498
  watchAuth(pb, entry);
2329
2499
  entries.set(pb, entry);
2330
2500
  }
@@ -2339,6 +2509,12 @@ function resetRealtime(pb) {
2339
2509
  function disconnectRealtime(pb) {
2340
2510
  entryFor(pb).client.disable();
2341
2511
  }
2512
+ function getSyncStatus(pb) {
2513
+ return entryFor(pb).status.get();
2514
+ }
2515
+ function subscribeSyncStatus(pb, listener) {
2516
+ return entryFor(pb).status.subscribe(listener);
2517
+ }
2342
2518
  function transportFor(pb) {
2343
2519
  const entry = entryFor(pb);
2344
2520
  return {
@@ -2358,7 +2534,16 @@ function transportFor(pb) {
2358
2534
  entry.authListeners.delete(listener);
2359
2535
  };
2360
2536
  },
2361
- reconnectListenerCount: () => entry.reconnectListeners.size
2537
+ reconnectListenerCount: () => entry.reconnectListeners.size,
2538
+ addLoadSource(source) {
2539
+ entry.loadSources.add(source);
2540
+ return () => {
2541
+ entry.loadSources.delete(source);
2542
+ entry.status.refresh();
2543
+ };
2544
+ },
2545
+ loadStatusChanged: entry.status.refresh,
2546
+ loadSourceCount: () => entry.loadSources.size
2362
2547
  };
2363
2548
  }
2364
2549
 
@@ -2571,7 +2756,8 @@ function buildCollection(input) {
2571
2756
  held.clearFiled();
2572
2757
  for (const timer of acceptedTimers) clearTimeout(timer);
2573
2758
  acceptedTimers.clear();
2574
- }
2759
+ },
2760
+ onLoadStatusChange: transport.loadStatusChanged
2575
2761
  });
2576
2762
  function resolveHandler(option, fallback) {
2577
2763
  return option === false ? void 0 : option ?? fallback;
@@ -2675,6 +2861,7 @@ function buildCollection(input) {
2675
2861
  collection.on("status:change", (event) => {
2676
2862
  if (event.status === "cleaned-up") subsets.clear();
2677
2863
  if (!removeTransportListeners && (event.status === "loading" || event.status === "ready")) {
2864
+ const removeLoadSource = transport.addLoadSource(adapter.loadStatus);
2678
2865
  const removeReconnect = transport.onReconnect((resumed) => {
2679
2866
  if (resumed || !realtime.isOpen()) return;
2680
2867
  if (!collection.isReady()) {
@@ -2691,6 +2878,7 @@ function buildCollection(input) {
2691
2878
  if (store.isAttached()) reloadFor("auth change");
2692
2879
  });
2693
2880
  removeTransportListeners = () => {
2881
+ removeLoadSource();
2694
2882
  removeReconnect();
2695
2883
  removeAuth();
2696
2884
  };
@@ -2949,6 +3137,6 @@ function newRecordId() {
2949
3137
  return result;
2950
3138
  }
2951
3139
 
2952
- export { convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, newRecordId, resetLogger, resetRealtime, setLogger };
2953
- //# sourceMappingURL=chunk-4CRCARB3.js.map
2954
- //# sourceMappingURL=chunk-4CRCARB3.js.map
3140
+ export { convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, getSyncStatus, newRecordId, resetLogger, resetRealtime, setLogger, subscribeSyncStatus };
3141
+ //# sourceMappingURL=chunk-TTENGDS3.js.map
3142
+ //# sourceMappingURL=chunk-TTENGDS3.js.map