@electric-sql/client 1.5.1 → 1.5.2
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/dist/cjs/index.cjs +58 -7
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +2 -2
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.legacy-esm.js +58 -7
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +58 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +67 -7
- package/src/constants.ts +1 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@electric-sql/client",
|
|
3
3
|
"description": "Postgres everywhere - your data, in sync, wherever you need it.",
|
|
4
|
-
"version": "1.5.
|
|
4
|
+
"version": "1.5.2",
|
|
5
5
|
"author": "ElectricSQL team and contributors.",
|
|
6
6
|
"bugs": {
|
|
7
7
|
"url": "https://github.com/electric-sql/electric/issues"
|
package/src/client.ts
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
REPLICA_PARAM,
|
|
52
52
|
FORCE_DISCONNECT_AND_REFRESH,
|
|
53
53
|
PAUSE_STREAM,
|
|
54
|
+
SYSTEM_WAKE,
|
|
54
55
|
EXPERIMENTAL_LIVE_SSE_QUERY_PARAM,
|
|
55
56
|
LIVE_SSE_QUERY_PARAM,
|
|
56
57
|
ELECTRIC_PROTOCOL_QUERY_PARAMS,
|
|
@@ -598,6 +599,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
598
599
|
#sseBackoffBaseDelay = 100 // Base delay for exponential backoff (ms)
|
|
599
600
|
#sseBackoffMaxDelay = 5000 // Maximum delay cap (ms)
|
|
600
601
|
#unsubscribeFromVisibilityChanges?: () => void
|
|
602
|
+
#unsubscribeFromWakeDetection?: () => void
|
|
601
603
|
#staleCacheBuster?: string // Cache buster set when stale CDN response detected, used on retry requests to bypass cache
|
|
602
604
|
#staleCacheRetryCount = 0
|
|
603
605
|
#maxStaleCacheRetries = 3
|
|
@@ -666,6 +668,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
666
668
|
this.#fetchClient = createFetchWithConsumedMessages(this.#sseFetchClient)
|
|
667
669
|
|
|
668
670
|
this.#subscribeToVisibilityChanges()
|
|
671
|
+
this.#subscribeToWakeDetection()
|
|
669
672
|
}
|
|
670
673
|
|
|
671
674
|
get shapeHandle() {
|
|
@@ -735,6 +738,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
735
738
|
}
|
|
736
739
|
this.#connected = false
|
|
737
740
|
this.#tickPromiseRejecter?.()
|
|
741
|
+
this.#unsubscribeFromWakeDetection?.()
|
|
738
742
|
return
|
|
739
743
|
}
|
|
740
744
|
|
|
@@ -745,12 +749,14 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
745
749
|
}
|
|
746
750
|
this.#connected = false
|
|
747
751
|
this.#tickPromiseRejecter?.()
|
|
752
|
+
this.#unsubscribeFromWakeDetection?.()
|
|
748
753
|
throw err
|
|
749
754
|
}
|
|
750
755
|
|
|
751
756
|
// Normal completion, clean up
|
|
752
757
|
this.#connected = false
|
|
753
758
|
this.#tickPromiseRejecter?.()
|
|
759
|
+
this.#unsubscribeFromWakeDetection?.()
|
|
754
760
|
}
|
|
755
761
|
|
|
756
762
|
async #requestShape(): Promise<void> {
|
|
@@ -785,13 +791,16 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
785
791
|
resumingFromPause,
|
|
786
792
|
})
|
|
787
793
|
} catch (e) {
|
|
788
|
-
|
|
794
|
+
const abortReason = requestAbortController.signal.reason
|
|
795
|
+
const isRestartAbort =
|
|
796
|
+
requestAbortController.signal.aborted &&
|
|
797
|
+
(abortReason === FORCE_DISCONNECT_AND_REFRESH ||
|
|
798
|
+
abortReason === SYSTEM_WAKE)
|
|
799
|
+
|
|
789
800
|
if (
|
|
790
801
|
(e instanceof FetchError || e instanceof FetchBackoffAbortError) &&
|
|
791
|
-
|
|
792
|
-
requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH
|
|
802
|
+
isRestartAbort
|
|
793
803
|
) {
|
|
794
|
-
// Start a new request
|
|
795
804
|
return this.#requestShape()
|
|
796
805
|
}
|
|
797
806
|
|
|
@@ -1432,6 +1441,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1432
1441
|
unsubscribeAll(): void {
|
|
1433
1442
|
this.#subscribers.clear()
|
|
1434
1443
|
this.#unsubscribeFromVisibilityChanges?.()
|
|
1444
|
+
this.#unsubscribeFromWakeDetection?.()
|
|
1435
1445
|
}
|
|
1436
1446
|
|
|
1437
1447
|
/** Unix time at which we last synced. Undefined when `isLoading` is true. */
|
|
@@ -1543,12 +1553,16 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1543
1553
|
})
|
|
1544
1554
|
}
|
|
1545
1555
|
|
|
1546
|
-
#
|
|
1547
|
-
|
|
1556
|
+
#hasBrowserVisibilityAPI(): boolean {
|
|
1557
|
+
return (
|
|
1548
1558
|
typeof document === `object` &&
|
|
1549
1559
|
typeof document.hidden === `boolean` &&
|
|
1550
1560
|
typeof document.addEventListener === `function`
|
|
1551
|
-
)
|
|
1561
|
+
)
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
#subscribeToVisibilityChanges() {
|
|
1565
|
+
if (this.#hasBrowserVisibilityAPI()) {
|
|
1552
1566
|
const visibilityHandler = () => {
|
|
1553
1567
|
if (document.hidden) {
|
|
1554
1568
|
this.#pause()
|
|
@@ -1566,6 +1580,52 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1566
1580
|
}
|
|
1567
1581
|
}
|
|
1568
1582
|
|
|
1583
|
+
/**
|
|
1584
|
+
* Detects system wake from sleep using timer gap detection.
|
|
1585
|
+
* When the system sleeps, setInterval timers are paused. On wake,
|
|
1586
|
+
* the elapsed wall-clock time since the last tick will be much larger
|
|
1587
|
+
* than the interval period, indicating the system was asleep.
|
|
1588
|
+
*
|
|
1589
|
+
* Only active in non-browser environments (Bun, Node.js) where
|
|
1590
|
+
* `document.visibilitychange` is not available. In browsers,
|
|
1591
|
+
* `#subscribeToVisibilityChanges` handles this instead. Without wake
|
|
1592
|
+
* detection, in-flight HTTP requests (long-poll or SSE) may hang until
|
|
1593
|
+
* the OS TCP timeout.
|
|
1594
|
+
*/
|
|
1595
|
+
#subscribeToWakeDetection() {
|
|
1596
|
+
if (this.#hasBrowserVisibilityAPI()) return
|
|
1597
|
+
|
|
1598
|
+
const INTERVAL_MS = 2_000
|
|
1599
|
+
const WAKE_THRESHOLD_MS = 4_000
|
|
1600
|
+
|
|
1601
|
+
let lastTickTime = Date.now()
|
|
1602
|
+
|
|
1603
|
+
const timer = setInterval(() => {
|
|
1604
|
+
const now = Date.now()
|
|
1605
|
+
const elapsed = now - lastTickTime
|
|
1606
|
+
lastTickTime = now
|
|
1607
|
+
|
|
1608
|
+
if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {
|
|
1609
|
+
if (this.#state === `active` && this.#requestAbortController) {
|
|
1610
|
+
this.#isRefreshing = true
|
|
1611
|
+
this.#requestAbortController.abort(SYSTEM_WAKE)
|
|
1612
|
+
queueMicrotask(() => {
|
|
1613
|
+
this.#isRefreshing = false
|
|
1614
|
+
})
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
}, INTERVAL_MS)
|
|
1618
|
+
|
|
1619
|
+
// Ensure the timer doesn't prevent the process from exiting
|
|
1620
|
+
if (typeof timer === `object` && `unref` in timer) {
|
|
1621
|
+
timer.unref()
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
this.#unsubscribeFromWakeDetection = () => {
|
|
1625
|
+
clearInterval(timer)
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1569
1629
|
/**
|
|
1570
1630
|
* Resets the state of the stream, optionally with a provided
|
|
1571
1631
|
* shape handle
|
package/src/constants.ts
CHANGED
|
@@ -20,6 +20,7 @@ export const EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`
|
|
|
20
20
|
export const LIVE_SSE_QUERY_PARAM = `live_sse`
|
|
21
21
|
export const FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`
|
|
22
22
|
export const PAUSE_STREAM = `pause-stream`
|
|
23
|
+
export const SYSTEM_WAKE = `system-wake`
|
|
23
24
|
export const LOG_MODE_QUERY_PARAM = `log`
|
|
24
25
|
export const SUBSET_PARAM_WHERE = `subset__where`
|
|
25
26
|
export const SUBSET_PARAM_LIMIT = `subset__limit`
|