@electric-sql/client 1.5.10 → 1.5.11
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 +31 -6
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/index.browser.mjs +4 -4
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.legacy-esm.js +31 -6
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +31 -6
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +39 -13
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.11",
|
|
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
|
@@ -96,6 +96,10 @@ const RESERVED_PARAMS: Set<ReservedParamKeys> = new Set([
|
|
|
96
96
|
|
|
97
97
|
const TROUBLESHOOTING_URL = `https://electric-sql.com/docs/guides/troubleshooting`
|
|
98
98
|
|
|
99
|
+
function createCacheBuster(): string {
|
|
100
|
+
return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
|
|
101
|
+
}
|
|
102
|
+
|
|
99
103
|
type Replica = `full` | `default`
|
|
100
104
|
export type LogMode = `changes_only` | `full`
|
|
101
105
|
|
|
@@ -617,6 +621,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
617
621
|
#fastLoopBackoffMaxMs = 5_000
|
|
618
622
|
#fastLoopConsecutiveCount = 0
|
|
619
623
|
#fastLoopMaxCount = 5
|
|
624
|
+
#refetchCacheBuster?: string
|
|
620
625
|
|
|
621
626
|
constructor(options: ShapeStreamOptions<GetExtensions<T>>) {
|
|
622
627
|
this.options = { subscribe: true, ...options }
|
|
@@ -875,10 +880,10 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
875
880
|
if (!(e instanceof FetchError)) throw e // should never happen
|
|
876
881
|
|
|
877
882
|
if (e.status == 409) {
|
|
878
|
-
// Upon receiving a 409,
|
|
879
|
-
//
|
|
880
|
-
//
|
|
881
|
-
//
|
|
883
|
+
// Upon receiving a 409, start from scratch with the newly
|
|
884
|
+
// provided shape handle. If the header is missing (e.g. proxy
|
|
885
|
+
// stripped it), reset without a handle and use a random
|
|
886
|
+
// cache-buster query param to ensure the retry URL is unique.
|
|
882
887
|
|
|
883
888
|
// Store the current shape URL as expired to avoid future 409s
|
|
884
889
|
if (this.#syncState.handle) {
|
|
@@ -886,8 +891,14 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
886
891
|
expiredShapesCache.markExpired(shapeKey, this.#syncState.handle)
|
|
887
892
|
}
|
|
888
893
|
|
|
889
|
-
const newShapeHandle =
|
|
890
|
-
|
|
894
|
+
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER]
|
|
895
|
+
if (!newShapeHandle) {
|
|
896
|
+
console.warn(
|
|
897
|
+
`[Electric] Received 409 response without a shape handle header. ` +
|
|
898
|
+
`This likely indicates a proxy or CDN stripping required headers.`
|
|
899
|
+
)
|
|
900
|
+
this.#refetchCacheBuster = createCacheBuster()
|
|
901
|
+
}
|
|
891
902
|
this.#reset(newShapeHandle)
|
|
892
903
|
|
|
893
904
|
// must refetch control message might be in a list or not depending
|
|
@@ -1144,6 +1155,16 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1144
1155
|
fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle)
|
|
1145
1156
|
}
|
|
1146
1157
|
|
|
1158
|
+
// Add one-shot cache buster when a 409 response lacked a handle header
|
|
1159
|
+
// (e.g. proxy stripped it). Ensures each retry has a unique URL.
|
|
1160
|
+
if (this.#refetchCacheBuster) {
|
|
1161
|
+
fetchUrl.searchParams.set(
|
|
1162
|
+
CACHE_BUSTER_QUERY_PARAM,
|
|
1163
|
+
this.#refetchCacheBuster
|
|
1164
|
+
)
|
|
1165
|
+
this.#refetchCacheBuster = undefined
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1147
1168
|
// sort query params in-place for stable URLs and improved cache hits
|
|
1148
1169
|
fetchUrl.searchParams.sort()
|
|
1149
1170
|
|
|
@@ -1199,8 +1220,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1199
1220
|
expiredHandle,
|
|
1200
1221
|
now: Date.now(),
|
|
1201
1222
|
maxStaleCacheRetries: this.#maxStaleCacheRetries,
|
|
1202
|
-
createCacheBuster
|
|
1203
|
-
`${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
1223
|
+
createCacheBuster,
|
|
1204
1224
|
})
|
|
1205
1225
|
|
|
1206
1226
|
this.#syncState = transition.state
|
|
@@ -1786,8 +1806,7 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1786
1806
|
expiredHandle: null,
|
|
1787
1807
|
now: Date.now(),
|
|
1788
1808
|
maxStaleCacheRetries: this.#maxStaleCacheRetries,
|
|
1789
|
-
createCacheBuster
|
|
1790
|
-
`${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
1809
|
+
createCacheBuster,
|
|
1791
1810
|
})
|
|
1792
1811
|
if (transition.action === `accepted`) {
|
|
1793
1812
|
this.#syncState = transition.state
|
|
@@ -1869,9 +1888,16 @@ export class ShapeStream<T extends Row<unknown> = Row>
|
|
|
1869
1888
|
|
|
1870
1889
|
// For snapshot 409s, only update the handle — don't reset offset/schema/etc.
|
|
1871
1890
|
// The main stream is paused and should not be disturbed.
|
|
1872
|
-
const nextHandle =
|
|
1873
|
-
|
|
1874
|
-
|
|
1891
|
+
const nextHandle = e.headers[SHAPE_HANDLE_HEADER]
|
|
1892
|
+
if (nextHandle) {
|
|
1893
|
+
this.#syncState = this.#syncState.withHandle(nextHandle)
|
|
1894
|
+
} else {
|
|
1895
|
+
console.warn(
|
|
1896
|
+
`[Electric] Received 409 response without a shape handle header. ` +
|
|
1897
|
+
`This likely indicates a proxy or CDN stripping required headers.`
|
|
1898
|
+
)
|
|
1899
|
+
this.#refetchCacheBuster = createCacheBuster()
|
|
1900
|
+
}
|
|
1875
1901
|
|
|
1876
1902
|
return this.fetchSnapshot(opts)
|
|
1877
1903
|
}
|