@electric-sql/client 1.3.0 → 1.3.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/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.3.0",
4
+ "version": "1.3.1",
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
@@ -982,7 +982,26 @@ export class ShapeStream<T extends Row<unknown> = Row>
982
982
  const { headers, status } = response
983
983
  const shapeHandle = headers.get(SHAPE_HANDLE_HEADER)
984
984
  if (shapeHandle) {
985
- this.#shapeHandle = shapeHandle
985
+ // Don't accept a handle we know is expired - this can happen if a
986
+ // proxy serves a stale cached response despite the expired_handle
987
+ // cache buster parameter
988
+ const shapeKey = this.#currentFetchUrl
989
+ ? canonicalShapeKey(this.#currentFetchUrl)
990
+ : null
991
+ const expiredHandle = shapeKey
992
+ ? expiredShapesCache.getExpiredHandle(shapeKey)
993
+ : null
994
+ if (shapeHandle !== expiredHandle) {
995
+ this.#shapeHandle = shapeHandle
996
+ } else {
997
+ console.warn(
998
+ `[Electric] Received stale cached response with expired shape handle. ` +
999
+ `This should not happen and indicates a proxy/CDN caching misconfiguration. ` +
1000
+ `The response contained handle "${shapeHandle}" which was previously marked as expired. ` +
1001
+ `Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. ` +
1002
+ `Ignoring the stale handle and continuing with handle "${this.#shapeHandle}".`
1003
+ )
1004
+ }
986
1005
  }
987
1006
 
988
1007
  const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER)
package/src/fetch.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  CHUNK_LAST_OFFSET_HEADER,
3
3
  CHUNK_UP_TO_DATE_HEADER,
4
+ EXPIRED_HANDLE_QUERY_PARAM,
4
5
  LIVE_QUERY_PARAM,
5
6
  OFFSET_QUERY_PARAM,
6
7
  SHAPE_HANDLE_HEADER,
@@ -436,6 +437,21 @@ function getNextChunkUrl(url: string, res: Response): string | void {
436
437
  // potentially miss more recent data
437
438
  if (nextUrl.searchParams.has(LIVE_QUERY_PARAM)) return
438
439
 
440
+ // don't prefetch if the response handle is the expired handle from the request
441
+ // this can happen when a proxy serves a stale cached response despite the
442
+ // expired_handle cache buster parameter
443
+ const expiredHandle = nextUrl.searchParams.get(EXPIRED_HANDLE_QUERY_PARAM)
444
+ if (expiredHandle && shapeHandle === expiredHandle) {
445
+ console.warn(
446
+ `[Electric] Received stale cached response with expired shape handle. ` +
447
+ `This should not happen and indicates a proxy/CDN caching misconfiguration. ` +
448
+ `The response contained handle "${shapeHandle}" which was previously marked as expired. ` +
449
+ `Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. ` +
450
+ `Skipping prefetch to prevent infinite 409 loop.`
451
+ )
452
+ return
453
+ }
454
+
439
455
  nextUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, shapeHandle)
440
456
  nextUrl.searchParams.set(OFFSET_QUERY_PARAM, lastOffset)
441
457
  nextUrl.searchParams.sort()