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 +70 -0
- package/dist/{chunk-4CRCARB3.js → chunk-TTENGDS3.js} +223 -35
- package/dist/chunk-TTENGDS3.js.map +1 -0
- package/dist/core.d.ts +53 -1
- package/dist/core.js +1 -1
- package/dist/index.d.ts +20 -2
- package/dist/index.js +11 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-4CRCARB3.js.map +0 -1
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))
|
|
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
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
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
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
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
|
|
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
|
-
|
|
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-
|
|
2954
|
-
//# sourceMappingURL=chunk-
|
|
3140
|
+
export { convertToPocketBaseFilter, convertToPocketBaseSort, createCollection, disconnectRealtime, getSyncStatus, newRecordId, resetLogger, resetRealtime, setLogger, subscribeSyncStatus };
|
|
3141
|
+
//# sourceMappingURL=chunk-TTENGDS3.js.map
|
|
3142
|
+
//# sourceMappingURL=chunk-TTENGDS3.js.map
|