@electric-sql/client 1.5.2 → 1.5.4

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.
@@ -854,10 +854,202 @@ function compileOrderBy(clauses, columnMapper) {
854
854
  }).join(`, `);
855
855
  }
856
856
 
857
- // src/client.ts
858
- import {
859
- fetchEventSource
860
- } from "@microsoft/fetch-event-source";
857
+ // ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/lib/esm/parse.js
858
+ async function getBytes(stream, onChunk) {
859
+ const reader = stream.getReader();
860
+ let result;
861
+ while (!(result = await reader.read()).done) {
862
+ onChunk(result.value);
863
+ }
864
+ }
865
+ function getLines(onLine) {
866
+ let buffer;
867
+ let position;
868
+ let fieldLength;
869
+ let discardTrailingNewline = false;
870
+ return function onChunk(arr) {
871
+ if (buffer === void 0) {
872
+ buffer = arr;
873
+ position = 0;
874
+ fieldLength = -1;
875
+ } else {
876
+ buffer = concat(buffer, arr);
877
+ }
878
+ const bufLength = buffer.length;
879
+ let lineStart = 0;
880
+ while (position < bufLength) {
881
+ if (discardTrailingNewline) {
882
+ if (buffer[position] === 10) {
883
+ lineStart = ++position;
884
+ }
885
+ discardTrailingNewline = false;
886
+ }
887
+ let lineEnd = -1;
888
+ for (; position < bufLength && lineEnd === -1; ++position) {
889
+ switch (buffer[position]) {
890
+ case 58:
891
+ if (fieldLength === -1) {
892
+ fieldLength = position - lineStart;
893
+ }
894
+ break;
895
+ case 13:
896
+ discardTrailingNewline = true;
897
+ case 10:
898
+ lineEnd = position;
899
+ break;
900
+ }
901
+ }
902
+ if (lineEnd === -1) {
903
+ break;
904
+ }
905
+ onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
906
+ lineStart = position;
907
+ fieldLength = -1;
908
+ }
909
+ if (lineStart === bufLength) {
910
+ buffer = void 0;
911
+ } else if (lineStart !== 0) {
912
+ buffer = buffer.subarray(lineStart);
913
+ position -= lineStart;
914
+ }
915
+ };
916
+ }
917
+ function getMessages(onId, onRetry, onMessage) {
918
+ let message = newMessage();
919
+ const decoder = new TextDecoder();
920
+ return function onLine(line, fieldLength) {
921
+ if (line.length === 0) {
922
+ onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
923
+ message = newMessage();
924
+ } else if (fieldLength > 0) {
925
+ const field = decoder.decode(line.subarray(0, fieldLength));
926
+ const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
927
+ const value = decoder.decode(line.subarray(valueOffset));
928
+ switch (field) {
929
+ case "data":
930
+ message.data = message.data ? message.data + "\n" + value : value;
931
+ break;
932
+ case "event":
933
+ message.event = value;
934
+ break;
935
+ case "id":
936
+ onId(message.id = value);
937
+ break;
938
+ case "retry":
939
+ const retry = parseInt(value, 10);
940
+ if (!isNaN(retry)) {
941
+ onRetry(message.retry = retry);
942
+ }
943
+ break;
944
+ }
945
+ }
946
+ };
947
+ }
948
+ function concat(a, b) {
949
+ const res = new Uint8Array(a.length + b.length);
950
+ res.set(a);
951
+ res.set(b, a.length);
952
+ return res;
953
+ }
954
+ function newMessage() {
955
+ return {
956
+ data: "",
957
+ event: "",
958
+ id: "",
959
+ retry: void 0
960
+ };
961
+ }
962
+
963
+ // ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/lib/esm/fetch.js
964
+ var __rest = function(s, e) {
965
+ var t = {};
966
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
967
+ t[p] = s[p];
968
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
969
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
970
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
971
+ t[p[i]] = s[p[i]];
972
+ }
973
+ return t;
974
+ };
975
+ var EventStreamContentType = "text/event-stream";
976
+ var DefaultRetryInterval = 1e3;
977
+ var LastEventId = "last-event-id";
978
+ function fetchEventSource(input, _a) {
979
+ var { signal: inputSignal, headers: inputHeaders, onopen: inputOnOpen, onmessage, onclose, onerror, openWhenHidden, fetch: inputFetch } = _a, rest = __rest(_a, ["signal", "headers", "onopen", "onmessage", "onclose", "onerror", "openWhenHidden", "fetch"]);
980
+ return new Promise((resolve, reject) => {
981
+ const headers = Object.assign({}, inputHeaders);
982
+ if (!headers.accept) {
983
+ headers.accept = EventStreamContentType;
984
+ }
985
+ let curRequestController;
986
+ function onVisibilityChange() {
987
+ curRequestController.abort();
988
+ if (typeof document !== "undefined" && !document.hidden) {
989
+ create();
990
+ }
991
+ }
992
+ if (typeof document !== "undefined" && !openWhenHidden) {
993
+ document.addEventListener("visibilitychange", onVisibilityChange);
994
+ }
995
+ let retryInterval = DefaultRetryInterval;
996
+ let retryTimer = 0;
997
+ function dispose() {
998
+ if (typeof document !== "undefined") {
999
+ document.removeEventListener("visibilitychange", onVisibilityChange);
1000
+ }
1001
+ clearTimeout(retryTimer);
1002
+ curRequestController.abort();
1003
+ }
1004
+ inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener("abort", () => {
1005
+ dispose();
1006
+ });
1007
+ const fetch2 = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
1008
+ const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
1009
+ async function create() {
1010
+ var _a2;
1011
+ curRequestController = new AbortController();
1012
+ const sig = inputSignal.aborted ? inputSignal : curRequestController.signal;
1013
+ try {
1014
+ const response = await fetch2(input, Object.assign(Object.assign({}, rest), { headers, signal: sig }));
1015
+ await onopen(response);
1016
+ await getBytes(response.body, getLines(getMessages((id) => {
1017
+ if (id) {
1018
+ headers[LastEventId] = id;
1019
+ } else {
1020
+ delete headers[LastEventId];
1021
+ }
1022
+ }, (retry) => {
1023
+ retryInterval = retry;
1024
+ }, onmessage)));
1025
+ onclose === null || onclose === void 0 ? void 0 : onclose();
1026
+ dispose();
1027
+ resolve();
1028
+ } catch (err) {
1029
+ if (sig.aborted) {
1030
+ dispose();
1031
+ reject(err);
1032
+ } else if (!curRequestController.signal.aborted) {
1033
+ try {
1034
+ const interval = (_a2 = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a2 !== void 0 ? _a2 : retryInterval;
1035
+ clearTimeout(retryTimer);
1036
+ retryTimer = setTimeout(create, interval);
1037
+ } catch (innerErr) {
1038
+ dispose();
1039
+ reject(innerErr);
1040
+ }
1041
+ }
1042
+ }
1043
+ }
1044
+ create();
1045
+ });
1046
+ }
1047
+ function defaultOnOpen(response) {
1048
+ const contentType = response.headers.get("content-type");
1049
+ if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(EventStreamContentType))) {
1050
+ throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
1051
+ }
1052
+ }
861
1053
 
862
1054
  // src/expired-shapes-cache.ts
863
1055
  var ExpiredShapesCache = class {
@@ -1095,6 +1287,544 @@ var SnapshotTracker = class {
1095
1287
  }
1096
1288
  };
1097
1289
 
1290
+ // src/shape-stream-state.ts
1291
+ var ShapeStreamState = class {
1292
+ // --- Derived booleans ---
1293
+ get isUpToDate() {
1294
+ return false;
1295
+ }
1296
+ // --- Per-state field defaults ---
1297
+ get staleCacheBuster() {
1298
+ return void 0;
1299
+ }
1300
+ get staleCacheRetryCount() {
1301
+ return 0;
1302
+ }
1303
+ get sseFallbackToLongPolling() {
1304
+ return false;
1305
+ }
1306
+ get consecutiveShortSseConnections() {
1307
+ return 0;
1308
+ }
1309
+ get replayCursor() {
1310
+ return void 0;
1311
+ }
1312
+ // --- Default no-op methods ---
1313
+ canEnterReplayMode() {
1314
+ return false;
1315
+ }
1316
+ enterReplayMode(_cursor) {
1317
+ return this;
1318
+ }
1319
+ shouldUseSse(_opts) {
1320
+ return false;
1321
+ }
1322
+ handleSseConnectionClosed(_input) {
1323
+ return {
1324
+ state: this,
1325
+ fellBackToLongPolling: false,
1326
+ wasShortConnection: false
1327
+ };
1328
+ }
1329
+ // --- URL param application ---
1330
+ /** Adds state-specific query parameters to the fetch URL. */
1331
+ applyUrlParams(_url, _context) {
1332
+ }
1333
+ // --- Default response/message handlers (Paused/Error never receive these) ---
1334
+ handleResponseMetadata(_input) {
1335
+ return { action: `ignored`, state: this };
1336
+ }
1337
+ handleMessageBatch(_input) {
1338
+ return { state: this, suppressBatch: false, becameUpToDate: false };
1339
+ }
1340
+ pause() {
1341
+ return new PausedState(this);
1342
+ }
1343
+ toErrorState(error) {
1344
+ return new ErrorState(this, error);
1345
+ }
1346
+ markMustRefetch(handle) {
1347
+ return new InitialState({
1348
+ handle,
1349
+ offset: `-1`,
1350
+ liveCacheBuster: ``,
1351
+ lastSyncedAt: this.lastSyncedAt,
1352
+ schema: void 0
1353
+ });
1354
+ }
1355
+ };
1356
+ var _shared;
1357
+ var ActiveState = class extends ShapeStreamState {
1358
+ constructor(shared) {
1359
+ super();
1360
+ __privateAdd(this, _shared);
1361
+ __privateSet(this, _shared, shared);
1362
+ }
1363
+ get handle() {
1364
+ return __privateGet(this, _shared).handle;
1365
+ }
1366
+ get offset() {
1367
+ return __privateGet(this, _shared).offset;
1368
+ }
1369
+ get schema() {
1370
+ return __privateGet(this, _shared).schema;
1371
+ }
1372
+ get liveCacheBuster() {
1373
+ return __privateGet(this, _shared).liveCacheBuster;
1374
+ }
1375
+ get lastSyncedAt() {
1376
+ return __privateGet(this, _shared).lastSyncedAt;
1377
+ }
1378
+ /** Expose shared fields to subclasses for spreading into new instances. */
1379
+ get currentFields() {
1380
+ return __privateGet(this, _shared);
1381
+ }
1382
+ // --- URL param application ---
1383
+ applyUrlParams(url, _context) {
1384
+ url.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _shared).offset);
1385
+ if (__privateGet(this, _shared).handle) {
1386
+ url.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shared).handle);
1387
+ }
1388
+ }
1389
+ // --- Helpers for subclass handleResponseMetadata implementations ---
1390
+ /** Extracts updated SharedStateFields from response headers. */
1391
+ parseResponseFields(input) {
1392
+ var _a, _b, _c;
1393
+ const responseHandle = input.responseHandle;
1394
+ const handle = responseHandle && responseHandle !== input.expiredHandle ? responseHandle : __privateGet(this, _shared).handle;
1395
+ const offset = (_a = input.responseOffset) != null ? _a : __privateGet(this, _shared).offset;
1396
+ const liveCacheBuster = (_b = input.responseCursor) != null ? _b : __privateGet(this, _shared).liveCacheBuster;
1397
+ const schema = (_c = __privateGet(this, _shared).schema) != null ? _c : input.responseSchema;
1398
+ const lastSyncedAt = input.status === 204 ? input.now : __privateGet(this, _shared).lastSyncedAt;
1399
+ return { handle, offset, schema, liveCacheBuster, lastSyncedAt };
1400
+ }
1401
+ /**
1402
+ * Stale detection. Returns a transition if the response is stale,
1403
+ * or null if it is not stale and the caller should proceed normally.
1404
+ */
1405
+ checkStaleResponse(input) {
1406
+ const responseHandle = input.responseHandle;
1407
+ const expiredHandle = input.expiredHandle;
1408
+ if (!responseHandle || responseHandle !== expiredHandle) {
1409
+ return null;
1410
+ }
1411
+ if (__privateGet(this, _shared).handle === void 0 || __privateGet(this, _shared).handle === expiredHandle) {
1412
+ const retryCount = this.staleCacheRetryCount + 1;
1413
+ return {
1414
+ action: `stale-retry`,
1415
+ state: new StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
1416
+ staleCacheBuster: input.createCacheBuster(),
1417
+ staleCacheRetryCount: retryCount
1418
+ })),
1419
+ exceededMaxRetries: retryCount > input.maxStaleCacheRetries
1420
+ };
1421
+ }
1422
+ return { action: `ignored`, state: this };
1423
+ }
1424
+ // --- handleMessageBatch: template method with onUpToDate override point ---
1425
+ handleMessageBatch(input) {
1426
+ if (!input.hasMessages || !input.hasUpToDateMessage) {
1427
+ return { state: this, suppressBatch: false, becameUpToDate: false };
1428
+ }
1429
+ let offset = __privateGet(this, _shared).offset;
1430
+ if (input.isSse && input.upToDateOffset) {
1431
+ offset = input.upToDateOffset;
1432
+ }
1433
+ const shared = {
1434
+ handle: __privateGet(this, _shared).handle,
1435
+ offset,
1436
+ schema: __privateGet(this, _shared).schema,
1437
+ liveCacheBuster: __privateGet(this, _shared).liveCacheBuster,
1438
+ lastSyncedAt: input.now
1439
+ };
1440
+ return this.onUpToDate(shared, input);
1441
+ }
1442
+ /** Override point for up-to-date handling. Default → LiveState. */
1443
+ onUpToDate(shared, _input) {
1444
+ return {
1445
+ state: new LiveState(shared),
1446
+ suppressBatch: false,
1447
+ becameUpToDate: true
1448
+ };
1449
+ }
1450
+ };
1451
+ _shared = new WeakMap();
1452
+ var FetchingState = class extends ActiveState {
1453
+ handleResponseMetadata(input) {
1454
+ const staleResult = this.checkStaleResponse(input);
1455
+ if (staleResult) return staleResult;
1456
+ const shared = this.parseResponseFields(input);
1457
+ return { action: `accepted`, state: new SyncingState(shared) };
1458
+ }
1459
+ canEnterReplayMode() {
1460
+ return true;
1461
+ }
1462
+ enterReplayMode(cursor) {
1463
+ return new ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
1464
+ replayCursor: cursor
1465
+ }));
1466
+ }
1467
+ };
1468
+ var InitialState = class _InitialState extends FetchingState {
1469
+ constructor(shared) {
1470
+ super(shared);
1471
+ this.kind = `initial`;
1472
+ }
1473
+ withHandle(handle) {
1474
+ return new _InitialState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
1475
+ }
1476
+ };
1477
+ var SyncingState = class _SyncingState extends FetchingState {
1478
+ constructor(shared) {
1479
+ super(shared);
1480
+ this.kind = `syncing`;
1481
+ }
1482
+ withHandle(handle) {
1483
+ return new _SyncingState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
1484
+ }
1485
+ };
1486
+ var _staleCacheBuster, _staleCacheRetryCount;
1487
+ var _StaleRetryState = class _StaleRetryState extends FetchingState {
1488
+ constructor(fields) {
1489
+ const _a = fields, { staleCacheBuster, staleCacheRetryCount } = _a, shared = __objRest(_a, ["staleCacheBuster", "staleCacheRetryCount"]);
1490
+ super(shared);
1491
+ this.kind = `stale-retry`;
1492
+ __privateAdd(this, _staleCacheBuster);
1493
+ __privateAdd(this, _staleCacheRetryCount);
1494
+ __privateSet(this, _staleCacheBuster, staleCacheBuster);
1495
+ __privateSet(this, _staleCacheRetryCount, staleCacheRetryCount);
1496
+ }
1497
+ get staleCacheBuster() {
1498
+ return __privateGet(this, _staleCacheBuster);
1499
+ }
1500
+ get staleCacheRetryCount() {
1501
+ return __privateGet(this, _staleCacheRetryCount);
1502
+ }
1503
+ // StaleRetryState must not enter replay mode — it would lose the retry count
1504
+ canEnterReplayMode() {
1505
+ return false;
1506
+ }
1507
+ withHandle(handle) {
1508
+ return new _StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
1509
+ handle,
1510
+ staleCacheBuster: __privateGet(this, _staleCacheBuster),
1511
+ staleCacheRetryCount: __privateGet(this, _staleCacheRetryCount)
1512
+ }));
1513
+ }
1514
+ applyUrlParams(url, context) {
1515
+ super.applyUrlParams(url, context);
1516
+ url.searchParams.set(CACHE_BUSTER_QUERY_PARAM, __privateGet(this, _staleCacheBuster));
1517
+ }
1518
+ };
1519
+ _staleCacheBuster = new WeakMap();
1520
+ _staleCacheRetryCount = new WeakMap();
1521
+ var StaleRetryState = _StaleRetryState;
1522
+ var _consecutiveShortSseConnections, _sseFallbackToLongPolling;
1523
+ var _LiveState = class _LiveState extends ActiveState {
1524
+ constructor(shared, sseState) {
1525
+ var _a, _b;
1526
+ super(shared);
1527
+ this.kind = `live`;
1528
+ __privateAdd(this, _consecutiveShortSseConnections);
1529
+ __privateAdd(this, _sseFallbackToLongPolling);
1530
+ __privateSet(this, _consecutiveShortSseConnections, (_a = sseState == null ? void 0 : sseState.consecutiveShortSseConnections) != null ? _a : 0);
1531
+ __privateSet(this, _sseFallbackToLongPolling, (_b = sseState == null ? void 0 : sseState.sseFallbackToLongPolling) != null ? _b : false);
1532
+ }
1533
+ get isUpToDate() {
1534
+ return true;
1535
+ }
1536
+ get consecutiveShortSseConnections() {
1537
+ return __privateGet(this, _consecutiveShortSseConnections);
1538
+ }
1539
+ get sseFallbackToLongPolling() {
1540
+ return __privateGet(this, _sseFallbackToLongPolling);
1541
+ }
1542
+ withHandle(handle) {
1543
+ return new _LiveState(__spreadProps(__spreadValues({}, this.currentFields), { handle }), this.sseState);
1544
+ }
1545
+ applyUrlParams(url, context) {
1546
+ super.applyUrlParams(url, context);
1547
+ if (!context.isSnapshotRequest) {
1548
+ url.searchParams.set(LIVE_CACHE_BUSTER_QUERY_PARAM, this.liveCacheBuster);
1549
+ if (context.canLongPoll) {
1550
+ url.searchParams.set(LIVE_QUERY_PARAM, `true`);
1551
+ }
1552
+ }
1553
+ }
1554
+ get sseState() {
1555
+ return {
1556
+ consecutiveShortSseConnections: __privateGet(this, _consecutiveShortSseConnections),
1557
+ sseFallbackToLongPolling: __privateGet(this, _sseFallbackToLongPolling)
1558
+ };
1559
+ }
1560
+ handleResponseMetadata(input) {
1561
+ const staleResult = this.checkStaleResponse(input);
1562
+ if (staleResult) return staleResult;
1563
+ const shared = this.parseResponseFields(input);
1564
+ return {
1565
+ action: `accepted`,
1566
+ state: new _LiveState(shared, this.sseState)
1567
+ };
1568
+ }
1569
+ onUpToDate(shared, _input) {
1570
+ return {
1571
+ state: new _LiveState(shared, this.sseState),
1572
+ suppressBatch: false,
1573
+ becameUpToDate: true
1574
+ };
1575
+ }
1576
+ shouldUseSse(opts) {
1577
+ return opts.liveSseEnabled && !opts.isRefreshing && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling);
1578
+ }
1579
+ handleSseConnectionClosed(input) {
1580
+ let nextConsecutiveShort = __privateGet(this, _consecutiveShortSseConnections);
1581
+ let nextFallback = __privateGet(this, _sseFallbackToLongPolling);
1582
+ let fellBackToLongPolling = false;
1583
+ let wasShortConnection = false;
1584
+ if (input.connectionDuration < input.minConnectionDuration && !input.wasAborted) {
1585
+ wasShortConnection = true;
1586
+ nextConsecutiveShort = nextConsecutiveShort + 1;
1587
+ if (nextConsecutiveShort >= input.maxShortConnections) {
1588
+ nextFallback = true;
1589
+ fellBackToLongPolling = true;
1590
+ }
1591
+ } else if (input.connectionDuration >= input.minConnectionDuration) {
1592
+ nextConsecutiveShort = 0;
1593
+ }
1594
+ return {
1595
+ state: new _LiveState(this.currentFields, {
1596
+ consecutiveShortSseConnections: nextConsecutiveShort,
1597
+ sseFallbackToLongPolling: nextFallback
1598
+ }),
1599
+ fellBackToLongPolling,
1600
+ wasShortConnection
1601
+ };
1602
+ }
1603
+ };
1604
+ _consecutiveShortSseConnections = new WeakMap();
1605
+ _sseFallbackToLongPolling = new WeakMap();
1606
+ var LiveState = _LiveState;
1607
+ var _replayCursor;
1608
+ var _ReplayingState = class _ReplayingState extends ActiveState {
1609
+ constructor(fields) {
1610
+ const _a = fields, { replayCursor } = _a, shared = __objRest(_a, ["replayCursor"]);
1611
+ super(shared);
1612
+ this.kind = `replaying`;
1613
+ __privateAdd(this, _replayCursor);
1614
+ __privateSet(this, _replayCursor, replayCursor);
1615
+ }
1616
+ get replayCursor() {
1617
+ return __privateGet(this, _replayCursor);
1618
+ }
1619
+ withHandle(handle) {
1620
+ return new _ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
1621
+ handle,
1622
+ replayCursor: __privateGet(this, _replayCursor)
1623
+ }));
1624
+ }
1625
+ handleResponseMetadata(input) {
1626
+ const staleResult = this.checkStaleResponse(input);
1627
+ if (staleResult) return staleResult;
1628
+ const shared = this.parseResponseFields(input);
1629
+ return {
1630
+ action: `accepted`,
1631
+ state: new _ReplayingState(__spreadProps(__spreadValues({}, shared), {
1632
+ replayCursor: __privateGet(this, _replayCursor)
1633
+ }))
1634
+ };
1635
+ }
1636
+ onUpToDate(shared, input) {
1637
+ const suppressBatch = !input.isSse && __privateGet(this, _replayCursor) === input.currentCursor;
1638
+ return {
1639
+ state: new LiveState(shared),
1640
+ suppressBatch,
1641
+ becameUpToDate: true
1642
+ };
1643
+ }
1644
+ };
1645
+ _replayCursor = new WeakMap();
1646
+ var ReplayingState = _ReplayingState;
1647
+ var PausedState = class _PausedState extends ShapeStreamState {
1648
+ constructor(previousState) {
1649
+ super();
1650
+ this.kind = `paused`;
1651
+ this.previousState = previousState;
1652
+ }
1653
+ get handle() {
1654
+ return this.previousState.handle;
1655
+ }
1656
+ get offset() {
1657
+ return this.previousState.offset;
1658
+ }
1659
+ get schema() {
1660
+ return this.previousState.schema;
1661
+ }
1662
+ get liveCacheBuster() {
1663
+ return this.previousState.liveCacheBuster;
1664
+ }
1665
+ get lastSyncedAt() {
1666
+ return this.previousState.lastSyncedAt;
1667
+ }
1668
+ get isUpToDate() {
1669
+ return this.previousState.isUpToDate;
1670
+ }
1671
+ get staleCacheBuster() {
1672
+ return this.previousState.staleCacheBuster;
1673
+ }
1674
+ get staleCacheRetryCount() {
1675
+ return this.previousState.staleCacheRetryCount;
1676
+ }
1677
+ get sseFallbackToLongPolling() {
1678
+ return this.previousState.sseFallbackToLongPolling;
1679
+ }
1680
+ get consecutiveShortSseConnections() {
1681
+ return this.previousState.consecutiveShortSseConnections;
1682
+ }
1683
+ get replayCursor() {
1684
+ return this.previousState.replayCursor;
1685
+ }
1686
+ withHandle(handle) {
1687
+ return new _PausedState(this.previousState.withHandle(handle));
1688
+ }
1689
+ applyUrlParams(url, context) {
1690
+ this.previousState.applyUrlParams(url, context);
1691
+ }
1692
+ pause() {
1693
+ return this;
1694
+ }
1695
+ resume() {
1696
+ return this.previousState;
1697
+ }
1698
+ };
1699
+ var ErrorState = class _ErrorState extends ShapeStreamState {
1700
+ constructor(previousState, error) {
1701
+ super();
1702
+ this.kind = `error`;
1703
+ this.previousState = previousState;
1704
+ this.error = error;
1705
+ }
1706
+ get handle() {
1707
+ return this.previousState.handle;
1708
+ }
1709
+ get offset() {
1710
+ return this.previousState.offset;
1711
+ }
1712
+ get schema() {
1713
+ return this.previousState.schema;
1714
+ }
1715
+ get liveCacheBuster() {
1716
+ return this.previousState.liveCacheBuster;
1717
+ }
1718
+ get lastSyncedAt() {
1719
+ return this.previousState.lastSyncedAt;
1720
+ }
1721
+ get isUpToDate() {
1722
+ return this.previousState.isUpToDate;
1723
+ }
1724
+ withHandle(handle) {
1725
+ return new _ErrorState(this.previousState.withHandle(handle), this.error);
1726
+ }
1727
+ applyUrlParams(url, context) {
1728
+ this.previousState.applyUrlParams(url, context);
1729
+ }
1730
+ retry() {
1731
+ return this.previousState;
1732
+ }
1733
+ reset(handle) {
1734
+ return this.previousState.markMustRefetch(handle);
1735
+ }
1736
+ };
1737
+ function createInitialState(opts) {
1738
+ return new InitialState({
1739
+ handle: opts.handle,
1740
+ offset: opts.offset,
1741
+ liveCacheBuster: ``,
1742
+ lastSyncedAt: void 0,
1743
+ schema: void 0
1744
+ });
1745
+ }
1746
+
1747
+ // src/pause-lock.ts
1748
+ var _holders, _onAcquired, _onReleased;
1749
+ var PauseLock = class {
1750
+ constructor(callbacks) {
1751
+ __privateAdd(this, _holders, /* @__PURE__ */ new Set());
1752
+ __privateAdd(this, _onAcquired);
1753
+ __privateAdd(this, _onReleased);
1754
+ __privateSet(this, _onAcquired, callbacks.onAcquired);
1755
+ __privateSet(this, _onReleased, callbacks.onReleased);
1756
+ }
1757
+ /**
1758
+ * Acquire the lock for a given reason. Idempotent — acquiring the same
1759
+ * reason twice is a no-op (but logs a warning since it likely indicates
1760
+ * a caller bug).
1761
+ *
1762
+ * Fires `onAcquired` when the first reason is acquired (transition from
1763
+ * unlocked to locked).
1764
+ */
1765
+ acquire(reason) {
1766
+ if (__privateGet(this, _holders).has(reason)) {
1767
+ console.warn(
1768
+ `[Electric] PauseLock: "${reason}" already held \u2014 ignoring duplicate acquire`
1769
+ );
1770
+ return;
1771
+ }
1772
+ const wasUnlocked = __privateGet(this, _holders).size === 0;
1773
+ __privateGet(this, _holders).add(reason);
1774
+ if (wasUnlocked) {
1775
+ __privateGet(this, _onAcquired).call(this);
1776
+ }
1777
+ }
1778
+ /**
1779
+ * Release the lock for a given reason. Releasing a reason that isn't
1780
+ * held logs a warning (likely indicates an acquire/release mismatch).
1781
+ *
1782
+ * Fires `onReleased` when the last reason is released (transition from
1783
+ * locked to unlocked).
1784
+ */
1785
+ release(reason) {
1786
+ if (!__privateGet(this, _holders).delete(reason)) {
1787
+ console.warn(
1788
+ `[Electric] PauseLock: "${reason}" not held \u2014 ignoring release (possible acquire/release mismatch)`
1789
+ );
1790
+ return;
1791
+ }
1792
+ if (__privateGet(this, _holders).size === 0) {
1793
+ __privateGet(this, _onReleased).call(this);
1794
+ }
1795
+ }
1796
+ /**
1797
+ * Whether the lock is currently held by any reason.
1798
+ */
1799
+ get isPaused() {
1800
+ return __privateGet(this, _holders).size > 0;
1801
+ }
1802
+ /**
1803
+ * Check if a specific reason is holding the lock.
1804
+ */
1805
+ isHeldBy(reason) {
1806
+ return __privateGet(this, _holders).has(reason);
1807
+ }
1808
+ /**
1809
+ * Release all reasons matching a prefix. Does NOT fire `onReleased` —
1810
+ * this is for cleanup/reset paths where the stream state is being
1811
+ * managed separately.
1812
+ *
1813
+ * This preserves reasons with different prefixes (e.g., 'visibility'
1814
+ * is preserved when clearing 'snapshot-*' reasons).
1815
+ */
1816
+ releaseAllMatching(prefix) {
1817
+ for (const reason of __privateGet(this, _holders)) {
1818
+ if (reason.startsWith(prefix)) {
1819
+ __privateGet(this, _holders).delete(reason);
1820
+ }
1821
+ }
1822
+ }
1823
+ };
1824
+ _holders = new WeakMap();
1825
+ _onAcquired = new WeakMap();
1826
+ _onReleased = new WeakMap();
1827
+
1098
1828
  // src/client.ts
1099
1829
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
1100
1830
  LIVE_CACHE_BUSTER_QUERY_PARAM,
@@ -1143,7 +1873,7 @@ function canonicalShapeKey(url) {
1143
1873
  cleanUrl.searchParams.sort();
1144
1874
  return cleanUrl.toString();
1145
1875
  }
1146
- var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _state, _lastOffset, _liveCacheBuster, _lastSyncedAt, _isUpToDate, _isMidStream, _connected, _shapeHandle, _mode, _schema, _onError, _requestAbortController, _isRefreshing, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _activeSnapshotRequests, _midStreamPromise, _midStreamPromiseResolver, _lastSeenCursor, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _consecutiveShortSseConnections, _maxShortSseConnections, _sseFallbackToLongPolling, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _unsubscribeFromWakeDetection, _staleCacheBuster, _staleCacheRetryCount, _maxStaleCacheRetries, _ShapeStream_instances, replayMode_get, start_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, pause_fn, resume_fn, nextTick_fn, waitForStreamEnd_fn, publish_fn, sendErrorToSubscribers_fn, hasBrowserVisibilityAPI_fn, subscribeToVisibilityChanges_fn, subscribeToWakeDetection_fn, reset_fn, buildSubsetBody_fn;
1876
+ var _error, _fetchClient2, _sseFetchClient, _messageParser, _subscribers, _started, _syncState, _connected, _mode, _onError, _requestAbortController, _refreshCount, _snapshotCounter, _ShapeStream_instances, isRefreshing_get, _tickPromise, _tickPromiseResolver, _tickPromiseRejecter, _messageChain, _snapshotTracker, _pauseLock, _currentFetchUrl, _lastSseConnectionStartTime, _minSseConnectionDuration, _maxShortSseConnections, _sseBackoffBaseDelay, _sseBackoffMaxDelay, _unsubscribeFromVisibilityChanges, _unsubscribeFromWakeDetection, _maxStaleCacheRetries, start_fn, teardown_fn, requestShape_fn, constructUrl_fn, createAbortListener_fn, onInitialResponse_fn, onMessages_fn, fetchShape_fn, requestShapeLongPoll_fn, requestShapeSSE_fn, nextTick_fn, publish_fn, sendErrorToSubscribers_fn, hasBrowserVisibilityAPI_fn, subscribeToVisibilityChanges_fn, subscribeToWakeDetection_fn, reset_fn, buildSubsetBody_fn;
1147
1877
  var ShapeStream = class {
1148
1878
  constructor(options) {
1149
1879
  __privateAdd(this, _ShapeStream_instances);
@@ -1153,58 +1883,57 @@ var ShapeStream = class {
1153
1883
  __privateAdd(this, _messageParser);
1154
1884
  __privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
1155
1885
  __privateAdd(this, _started, false);
1156
- __privateAdd(this, _state, `active`);
1157
- __privateAdd(this, _lastOffset);
1158
- __privateAdd(this, _liveCacheBuster);
1159
- // Seconds since our Electric Epoch 😎
1160
- __privateAdd(this, _lastSyncedAt);
1161
- // unix time
1162
- __privateAdd(this, _isUpToDate, false);
1163
- __privateAdd(this, _isMidStream, true);
1886
+ __privateAdd(this, _syncState);
1164
1887
  __privateAdd(this, _connected, false);
1165
- __privateAdd(this, _shapeHandle);
1166
1888
  __privateAdd(this, _mode);
1167
- __privateAdd(this, _schema);
1168
1889
  __privateAdd(this, _onError);
1169
1890
  __privateAdd(this, _requestAbortController);
1170
- __privateAdd(this, _isRefreshing, false);
1891
+ __privateAdd(this, _refreshCount, 0);
1892
+ __privateAdd(this, _snapshotCounter, 0);
1171
1893
  __privateAdd(this, _tickPromise);
1172
1894
  __privateAdd(this, _tickPromiseResolver);
1173
1895
  __privateAdd(this, _tickPromiseRejecter);
1174
1896
  __privateAdd(this, _messageChain, Promise.resolve([]));
1175
1897
  // promise chain for incoming messages
1176
1898
  __privateAdd(this, _snapshotTracker, new SnapshotTracker());
1177
- __privateAdd(this, _activeSnapshotRequests, 0);
1178
- // counter for concurrent snapshot requests
1179
- __privateAdd(this, _midStreamPromise);
1180
- __privateAdd(this, _midStreamPromiseResolver);
1181
- __privateAdd(this, _lastSeenCursor);
1182
- // Last seen cursor from previous session (used to detect cached responses)
1899
+ __privateAdd(this, _pauseLock);
1183
1900
  __privateAdd(this, _currentFetchUrl);
1184
1901
  // Current fetch URL for computing shape key
1185
1902
  __privateAdd(this, _lastSseConnectionStartTime);
1186
1903
  __privateAdd(this, _minSseConnectionDuration, 1e3);
1187
1904
  // Minimum expected SSE connection duration (1 second)
1188
- __privateAdd(this, _consecutiveShortSseConnections, 0);
1189
1905
  __privateAdd(this, _maxShortSseConnections, 3);
1190
1906
  // Fall back to long polling after this many short connections
1191
- __privateAdd(this, _sseFallbackToLongPolling, false);
1192
1907
  __privateAdd(this, _sseBackoffBaseDelay, 100);
1193
1908
  // Base delay for exponential backoff (ms)
1194
1909
  __privateAdd(this, _sseBackoffMaxDelay, 5e3);
1195
1910
  // Maximum delay cap (ms)
1196
1911
  __privateAdd(this, _unsubscribeFromVisibilityChanges);
1197
1912
  __privateAdd(this, _unsubscribeFromWakeDetection);
1198
- __privateAdd(this, _staleCacheBuster);
1199
- // Cache buster set when stale CDN response detected, used on retry requests to bypass cache
1200
- __privateAdd(this, _staleCacheRetryCount, 0);
1201
1913
  __privateAdd(this, _maxStaleCacheRetries, 3);
1202
1914
  var _a, _b, _c, _d;
1203
1915
  this.options = __spreadValues({ subscribe: true }, options);
1204
1916
  validateOptions(this.options);
1205
- __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
1206
- __privateSet(this, _liveCacheBuster, ``);
1207
- __privateSet(this, _shapeHandle, this.options.handle);
1917
+ __privateSet(this, _syncState, createInitialState({
1918
+ offset: (_a = this.options.offset) != null ? _a : `-1`,
1919
+ handle: this.options.handle
1920
+ }));
1921
+ __privateSet(this, _pauseLock, new PauseLock({
1922
+ onAcquired: () => {
1923
+ var _a2;
1924
+ __privateSet(this, _syncState, __privateGet(this, _syncState).pause());
1925
+ if (__privateGet(this, _started)) {
1926
+ (_a2 = __privateGet(this, _requestAbortController)) == null ? void 0 : _a2.abort(PAUSE_STREAM);
1927
+ }
1928
+ },
1929
+ onReleased: () => {
1930
+ var _a2;
1931
+ if (!__privateGet(this, _started)) return;
1932
+ if ((_a2 = this.options.signal) == null ? void 0 : _a2.aborted) return;
1933
+ __privateMethod(this, _ShapeStream_instances, start_fn).call(this).catch(() => {
1934
+ });
1935
+ }
1936
+ }));
1208
1937
  let transformer;
1209
1938
  if (options.columnMapper) {
1210
1939
  const applyColumnMapper = (row) => {
@@ -1242,16 +1971,16 @@ var ShapeStream = class {
1242
1971
  __privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
1243
1972
  }
1244
1973
  get shapeHandle() {
1245
- return __privateGet(this, _shapeHandle);
1974
+ return __privateGet(this, _syncState).handle;
1246
1975
  }
1247
1976
  get error() {
1248
1977
  return __privateGet(this, _error);
1249
1978
  }
1250
1979
  get isUpToDate() {
1251
- return __privateGet(this, _isUpToDate);
1980
+ return __privateGet(this, _syncState).isUpToDate;
1252
1981
  }
1253
1982
  get lastOffset() {
1254
- return __privateGet(this, _lastOffset);
1983
+ return __privateGet(this, _syncState).offset;
1255
1984
  }
1256
1985
  get mode() {
1257
1986
  return __privateGet(this, _mode);
@@ -1271,28 +2000,28 @@ var ShapeStream = class {
1271
2000
  (_a = __privateGet(this, _unsubscribeFromVisibilityChanges)) == null ? void 0 : _a.call(this);
1272
2001
  (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1273
2002
  }
1274
- /** Unix time at which we last synced. Undefined when `isLoading` is true. */
2003
+ /** Unix time at which we last synced. Undefined until first successful up-to-date. */
1275
2004
  lastSyncedAt() {
1276
- return __privateGet(this, _lastSyncedAt);
2005
+ return __privateGet(this, _syncState).lastSyncedAt;
1277
2006
  }
1278
2007
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
1279
2008
  lastSynced() {
1280
- if (__privateGet(this, _lastSyncedAt) === void 0) return Infinity;
1281
- return Date.now() - __privateGet(this, _lastSyncedAt);
2009
+ if (__privateGet(this, _syncState).lastSyncedAt === void 0) return Infinity;
2010
+ return Date.now() - __privateGet(this, _syncState).lastSyncedAt;
1282
2011
  }
1283
2012
  /** Indicates if we are connected to the Electric sync service. */
1284
2013
  isConnected() {
1285
2014
  return __privateGet(this, _connected);
1286
2015
  }
1287
- /** True during initial fetch. False afterwise. */
2016
+ /** True during initial fetch. False afterwards. */
1288
2017
  isLoading() {
1289
- return !__privateGet(this, _isUpToDate);
2018
+ return !__privateGet(this, _syncState).isUpToDate;
1290
2019
  }
1291
2020
  hasStarted() {
1292
2021
  return __privateGet(this, _started);
1293
2022
  }
1294
2023
  isPaused() {
1295
- return __privateGet(this, _state) === `paused`;
2024
+ return __privateGet(this, _pauseLock).isPaused;
1296
2025
  }
1297
2026
  /**
1298
2027
  * Refreshes the shape stream.
@@ -1302,12 +2031,15 @@ var ShapeStream = class {
1302
2031
  */
1303
2032
  async forceDisconnectAndRefresh() {
1304
2033
  var _a, _b;
1305
- __privateSet(this, _isRefreshing, true);
1306
- if (__privateGet(this, _isUpToDate) && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
1307
- (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
2034
+ __privateWrapper(this, _refreshCount)._++;
2035
+ try {
2036
+ if (__privateGet(this, _syncState).isUpToDate && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
2037
+ (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
2038
+ }
2039
+ await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
2040
+ } finally {
2041
+ __privateWrapper(this, _refreshCount)._--;
1308
2042
  }
1309
- await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
1310
- __privateSet(this, _isRefreshing, false);
1311
2043
  }
1312
2044
  /**
1313
2045
  * Request a snapshot for subset of data and inject it into the subscribed data stream.
@@ -1329,13 +2061,18 @@ var ShapeStream = class {
1329
2061
  `Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
1330
2062
  );
1331
2063
  }
1332
- if (!__privateGet(this, _started)) await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1333
- await __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
1334
- __privateWrapper(this, _activeSnapshotRequests)._++;
2064
+ if (!__privateGet(this, _started)) {
2065
+ __privateMethod(this, _ShapeStream_instances, start_fn).call(this).catch(() => {
2066
+ });
2067
+ }
2068
+ const snapshotReason = `snapshot-${++__privateWrapper(this, _snapshotCounter)._}`;
2069
+ __privateGet(this, _pauseLock).acquire(snapshotReason);
2070
+ const snapshotWarnTimer = setTimeout(() => {
2071
+ console.warn(
2072
+ `[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}`
2073
+ );
2074
+ }, 3e4);
1335
2075
  try {
1336
- if (__privateGet(this, _activeSnapshotRequests) === 1) {
1337
- __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
1338
- }
1339
2076
  const { metadata, data } = await this.fetchSnapshot(opts);
1340
2077
  const dataWithEndBoundary = data.concat([
1341
2078
  { headers: __spreadValues({ control: `snapshot-end` }, metadata) },
@@ -1351,10 +2088,8 @@ var ShapeStream = class {
1351
2088
  data
1352
2089
  };
1353
2090
  } finally {
1354
- __privateWrapper(this, _activeSnapshotRequests)._--;
1355
- if (__privateGet(this, _activeSnapshotRequests) === 0) {
1356
- __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
1357
- }
2091
+ clearTimeout(snapshotWarnTimer);
2092
+ __privateGet(this, _pauseLock).release(snapshotReason);
1358
2093
  }
1359
2094
  }
1360
2095
  /**
@@ -1389,7 +2124,7 @@ var ShapeStream = class {
1389
2124
  fetchUrl = result.fetchUrl;
1390
2125
  fetchOptions = { headers: result.requestHeaders };
1391
2126
  }
1392
- const usedHandle = __privateGet(this, _shapeHandle);
2127
+ const usedHandle = __privateGet(this, _syncState).handle;
1393
2128
  let response;
1394
2129
  try {
1395
2130
  response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
@@ -1399,7 +2134,8 @@ var ShapeStream = class {
1399
2134
  const shapeKey = canonicalShapeKey(fetchUrl);
1400
2135
  expiredShapesCache.markExpired(shapeKey, usedHandle);
1401
2136
  }
1402
- __privateSet(this, _shapeHandle, e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`);
2137
+ const nextHandle = e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`;
2138
+ __privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
1403
2139
  return this.fetchSnapshot(opts);
1404
2140
  }
1405
2141
  throw e;
@@ -1407,7 +2143,7 @@ var ShapeStream = class {
1407
2143
  if (!response.ok) {
1408
2144
  throw await FetchError.fromResponse(response, fetchUrl.toString());
1409
2145
  }
1410
- const schema = (_c = __privateGet(this, _schema)) != null ? _c : getSchemaFromHeaders(response.headers, {
2146
+ const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
1411
2147
  required: true,
1412
2148
  url: fetchUrl.toString()
1413
2149
  });
@@ -1425,52 +2161,42 @@ _sseFetchClient = new WeakMap();
1425
2161
  _messageParser = new WeakMap();
1426
2162
  _subscribers = new WeakMap();
1427
2163
  _started = new WeakMap();
1428
- _state = new WeakMap();
1429
- _lastOffset = new WeakMap();
1430
- _liveCacheBuster = new WeakMap();
1431
- _lastSyncedAt = new WeakMap();
1432
- _isUpToDate = new WeakMap();
1433
- _isMidStream = new WeakMap();
2164
+ _syncState = new WeakMap();
1434
2165
  _connected = new WeakMap();
1435
- _shapeHandle = new WeakMap();
1436
2166
  _mode = new WeakMap();
1437
- _schema = new WeakMap();
1438
2167
  _onError = new WeakMap();
1439
2168
  _requestAbortController = new WeakMap();
1440
- _isRefreshing = new WeakMap();
2169
+ _refreshCount = new WeakMap();
2170
+ _snapshotCounter = new WeakMap();
2171
+ _ShapeStream_instances = new WeakSet();
2172
+ isRefreshing_get = function() {
2173
+ return __privateGet(this, _refreshCount) > 0;
2174
+ };
1441
2175
  _tickPromise = new WeakMap();
1442
2176
  _tickPromiseResolver = new WeakMap();
1443
2177
  _tickPromiseRejecter = new WeakMap();
1444
2178
  _messageChain = new WeakMap();
1445
2179
  _snapshotTracker = new WeakMap();
1446
- _activeSnapshotRequests = new WeakMap();
1447
- _midStreamPromise = new WeakMap();
1448
- _midStreamPromiseResolver = new WeakMap();
1449
- _lastSeenCursor = new WeakMap();
2180
+ _pauseLock = new WeakMap();
1450
2181
  _currentFetchUrl = new WeakMap();
1451
2182
  _lastSseConnectionStartTime = new WeakMap();
1452
2183
  _minSseConnectionDuration = new WeakMap();
1453
- _consecutiveShortSseConnections = new WeakMap();
1454
2184
  _maxShortSseConnections = new WeakMap();
1455
- _sseFallbackToLongPolling = new WeakMap();
1456
2185
  _sseBackoffBaseDelay = new WeakMap();
1457
2186
  _sseBackoffMaxDelay = new WeakMap();
1458
2187
  _unsubscribeFromVisibilityChanges = new WeakMap();
1459
2188
  _unsubscribeFromWakeDetection = new WeakMap();
1460
- _staleCacheBuster = new WeakMap();
1461
- _staleCacheRetryCount = new WeakMap();
1462
2189
  _maxStaleCacheRetries = new WeakMap();
1463
- _ShapeStream_instances = new WeakSet();
1464
- replayMode_get = function() {
1465
- return __privateGet(this, _lastSeenCursor) !== void 0;
1466
- };
1467
2190
  start_fn = async function() {
1468
- var _a, _b, _c, _d, _e, _f, _g, _h;
2191
+ var _a, _b;
1469
2192
  __privateSet(this, _started, true);
1470
2193
  try {
1471
2194
  await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1472
2195
  } catch (err) {
1473
2196
  __privateSet(this, _error, err);
2197
+ if (err instanceof Error) {
2198
+ __privateSet(this, _syncState, __privateGet(this, _syncState).toErrorState(err));
2199
+ }
1474
2200
  if (__privateGet(this, _onError)) {
1475
2201
  const retryOpts = await __privateGet(this, _onError).call(this, err);
1476
2202
  const isRetryable = !(err instanceof MissingHeadersError);
@@ -1482,6 +2208,9 @@ start_fn = async function() {
1482
2208
  this.options.headers = __spreadValues(__spreadValues({}, (_b = this.options.headers) != null ? _b : {}), retryOpts.headers);
1483
2209
  }
1484
2210
  __privateSet(this, _error, null);
2211
+ if (__privateGet(this, _syncState) instanceof ErrorState) {
2212
+ __privateSet(this, _syncState, __privateGet(this, _syncState).retry());
2213
+ }
1485
2214
  __privateSet(this, _started, false);
1486
2215
  await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1487
2216
  return;
@@ -1489,38 +2218,45 @@ start_fn = async function() {
1489
2218
  if (err instanceof Error) {
1490
2219
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1491
2220
  }
1492
- __privateSet(this, _connected, false);
1493
- (_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
1494
- (_d = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _d.call(this);
2221
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
1495
2222
  return;
1496
2223
  }
1497
2224
  if (err instanceof Error) {
1498
2225
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1499
2226
  }
1500
- __privateSet(this, _connected, false);
1501
- (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
1502
- (_f = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _f.call(this);
2227
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
1503
2228
  throw err;
1504
2229
  }
2230
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
2231
+ };
2232
+ teardown_fn = function() {
2233
+ var _a, _b;
1505
2234
  __privateSet(this, _connected, false);
1506
- (_g = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _g.call(this);
1507
- (_h = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _h.call(this);
2235
+ (_a = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _a.call(this);
2236
+ (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1508
2237
  };
1509
2238
  requestShape_fn = async function() {
1510
2239
  var _a, _b;
1511
- if (__privateGet(this, _state) === `pause-requested`) {
1512
- __privateSet(this, _state, `paused`);
2240
+ if (__privateGet(this, _pauseLock).isPaused) return;
2241
+ if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
1513
2242
  return;
1514
2243
  }
1515
- if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _isUpToDate))) {
1516
- return;
2244
+ let resumingFromPause = false;
2245
+ if (__privateGet(this, _syncState) instanceof PausedState) {
2246
+ resumingFromPause = true;
2247
+ __privateSet(this, _syncState, __privateGet(this, _syncState).resume());
1517
2248
  }
1518
- const resumingFromPause = __privateGet(this, _state) === `paused`;
1519
- __privateSet(this, _state, `active`);
1520
2249
  const { url, signal } = this.options;
1521
2250
  const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
1522
2251
  const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
1523
2252
  const requestAbortController = __privateGet(this, _requestAbortController);
2253
+ if (__privateGet(this, _pauseLock).isPaused) {
2254
+ if (abortListener && signal) {
2255
+ signal.removeEventListener(`abort`, abortListener);
2256
+ }
2257
+ __privateSet(this, _requestAbortController, void 0);
2258
+ return;
2259
+ }
1524
2260
  try {
1525
2261
  await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
1526
2262
  fetchUrl,
@@ -1535,10 +2271,6 @@ requestShape_fn = async function() {
1535
2271
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1536
2272
  }
1537
2273
  if (e instanceof FetchBackoffAbortError) {
1538
- const currentState = __privateGet(this, _state);
1539
- if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM && currentState === `pause-requested`) {
1540
- __privateSet(this, _state, `paused`);
1541
- }
1542
2274
  return;
1543
2275
  }
1544
2276
  if (e instanceof StaleCacheError) {
@@ -1546,11 +2278,11 @@ requestShape_fn = async function() {
1546
2278
  }
1547
2279
  if (!(e instanceof FetchError)) throw e;
1548
2280
  if (e.status == 409) {
1549
- if (__privateGet(this, _shapeHandle)) {
2281
+ if (__privateGet(this, _syncState).handle) {
1550
2282
  const shapeKey = canonicalShapeKey(fetchUrl);
1551
- expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
2283
+ expiredShapesCache.markExpired(shapeKey, __privateGet(this, _syncState).handle);
1552
2284
  }
1553
- const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
2285
+ const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _syncState).handle}-next`;
1554
2286
  __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
1555
2287
  await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, Array.isArray(e.json) ? e.json : [e.json]);
1556
2288
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
@@ -1656,32 +2388,18 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
1656
2388
  setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
1657
2389
  }
1658
2390
  }
1659
- fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
2391
+ __privateGet(this, _syncState).applyUrlParams(fetchUrl, {
2392
+ isSnapshotRequest: subsetParams !== void 0,
2393
+ // Don't long-poll when resuming from pause or refreshing — avoids
2394
+ // a 20s hold during which `isConnected` would be false
2395
+ canLongPoll: !__privateGet(this, _ShapeStream_instances, isRefreshing_get) && !resumingFromPause
2396
+ });
1660
2397
  fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
1661
- const isSnapshotRequest = subsetParams !== void 0;
1662
- if (__privateGet(this, _isUpToDate) && !isSnapshotRequest) {
1663
- if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
1664
- fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
1665
- }
1666
- fetchUrl.searchParams.set(
1667
- LIVE_CACHE_BUSTER_QUERY_PARAM,
1668
- __privateGet(this, _liveCacheBuster)
1669
- );
1670
- }
1671
- if (__privateGet(this, _shapeHandle)) {
1672
- fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
1673
- }
1674
2398
  const shapeKey = canonicalShapeKey(fetchUrl);
1675
2399
  const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
1676
2400
  if (expiredHandle) {
1677
2401
  fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
1678
2402
  }
1679
- if (__privateGet(this, _staleCacheBuster)) {
1680
- fetchUrl.searchParams.set(
1681
- CACHE_BUSTER_QUERY_PARAM,
1682
- __privateGet(this, _staleCacheBuster)
1683
- );
1684
- }
1685
2403
  fetchUrl.searchParams.sort();
1686
2404
  return {
1687
2405
  fetchUrl,
@@ -1704,108 +2422,100 @@ createAbortListener_fn = async function(signal) {
1704
2422
  }
1705
2423
  };
1706
2424
  onInitialResponse_fn = async function(response) {
1707
- var _a, _b, _c, _d;
2425
+ var _a, _b, _c;
1708
2426
  const { headers, status } = response;
1709
2427
  const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
1710
- if (shapeHandle) {
1711
- const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
1712
- const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
1713
- if (shapeHandle !== expiredHandle) {
1714
- __privateSet(this, _shapeHandle, shapeHandle);
1715
- if (__privateGet(this, _staleCacheBuster)) {
1716
- __privateSet(this, _staleCacheBuster, void 0);
1717
- __privateSet(this, _staleCacheRetryCount, 0);
1718
- }
1719
- } else if (__privateGet(this, _shapeHandle) === void 0) {
1720
- __privateWrapper(this, _staleCacheRetryCount)._++;
1721
- await ((_a = response.body) == null ? void 0 : _a.cancel());
1722
- if (__privateGet(this, _staleCacheRetryCount) > __privateGet(this, _maxStaleCacheRetries)) {
1723
- throw new FetchError(
1724
- 502,
1725
- void 0,
1726
- void 0,
1727
- {},
1728
- (_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
1729
- `CDN continues serving stale cached responses after ${__privateGet(this, _maxStaleCacheRetries)} retry attempts. This indicates a severe proxy/CDN misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. For more information visit the troubleshooting guide: https://electric-sql.com/docs/guides/troubleshooting`
1730
- );
1731
- }
1732
- console.warn(
1733
- `[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. For more information visit the troubleshooting guide: https://electric-sql.com/docs/guides/troubleshooting Retrying with a random cache buster to bypass the stale cache (attempt ${__privateGet(this, _staleCacheRetryCount)}/${__privateGet(this, _maxStaleCacheRetries)}).`
1734
- );
1735
- __privateSet(this, _staleCacheBuster, `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
1736
- throw new StaleCacheError(
1737
- `Received stale cached response with expired handle "${shapeHandle}". This indicates a proxy/CDN caching misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key.`
1738
- );
1739
- } else {
1740
- console.warn(
1741
- `[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Ignoring the stale response and continuing with handle "${__privateGet(this, _shapeHandle)}".`
2428
+ const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
2429
+ const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
2430
+ const transition = __privateGet(this, _syncState).handleResponseMetadata({
2431
+ status,
2432
+ responseHandle: shapeHandle,
2433
+ responseOffset: headers.get(CHUNK_LAST_OFFSET_HEADER),
2434
+ responseCursor: headers.get(LIVE_CACHE_BUSTER_HEADER),
2435
+ responseSchema: getSchemaFromHeaders(headers),
2436
+ expiredHandle,
2437
+ now: Date.now(),
2438
+ maxStaleCacheRetries: __privateGet(this, _maxStaleCacheRetries),
2439
+ createCacheBuster: () => `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
2440
+ });
2441
+ __privateSet(this, _syncState, transition.state);
2442
+ if (transition.action === `stale-retry`) {
2443
+ await ((_a = response.body) == null ? void 0 : _a.cancel());
2444
+ if (transition.exceededMaxRetries) {
2445
+ throw new FetchError(
2446
+ 502,
2447
+ void 0,
2448
+ void 0,
2449
+ {},
2450
+ (_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
2451
+ `CDN continues serving stale cached responses after ${__privateGet(this, _maxStaleCacheRetries)} retry attempts. This indicates a severe proxy/CDN misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. For more information visit the troubleshooting guide: https://electric-sql.com/docs/guides/troubleshooting`
1742
2452
  );
1743
- return;
1744
2453
  }
2454
+ console.warn(
2455
+ `[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. For more information visit the troubleshooting guide: https://electric-sql.com/docs/guides/troubleshooting Retrying with a random cache buster to bypass the stale cache (attempt ${__privateGet(this, _syncState).staleCacheRetryCount}/${__privateGet(this, _maxStaleCacheRetries)}).`
2456
+ );
2457
+ throw new StaleCacheError(
2458
+ `Received stale cached response with expired handle "${shapeHandle}". This indicates a proxy/CDN caching misconfiguration. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key.`
2459
+ );
1745
2460
  }
1746
- const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
1747
- if (lastOffset) {
1748
- __privateSet(this, _lastOffset, lastOffset);
1749
- }
1750
- const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
1751
- if (liveCacheBuster) {
1752
- __privateSet(this, _liveCacheBuster, liveCacheBuster);
1753
- }
1754
- __privateSet(this, _schema, (_d = __privateGet(this, _schema)) != null ? _d : getSchemaFromHeaders(headers));
1755
- if (status === 204) {
1756
- __privateSet(this, _lastSyncedAt, Date.now());
2461
+ if (transition.action === `ignored`) {
2462
+ console.warn(
2463
+ `[Electric] Received stale cached response with expired shape handle. This should not happen and indicates a proxy/CDN caching misconfiguration. The response contained handle "${shapeHandle}" which was previously marked as expired. Check that your proxy includes all query parameters (especially 'handle' and 'offset') in its cache key. Ignoring the stale response and continuing with handle "${__privateGet(this, _syncState).handle}".`
2464
+ );
2465
+ return false;
1757
2466
  }
2467
+ return true;
1758
2468
  };
1759
2469
  onMessages_fn = async function(batch, isSseMessage = false) {
1760
- var _a;
1761
- if (batch.length > 0) {
1762
- __privateSet(this, _isMidStream, true);
1763
- const lastMessage = batch[batch.length - 1];
1764
- if (isUpToDateMessage(lastMessage)) {
1765
- if (isSseMessage) {
1766
- const offset = getOffset(lastMessage);
1767
- if (offset) {
1768
- __privateSet(this, _lastOffset, offset);
1769
- }
1770
- }
1771
- __privateSet(this, _lastSyncedAt, Date.now());
1772
- __privateSet(this, _isUpToDate, true);
1773
- __privateSet(this, _isMidStream, false);
1774
- (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
1775
- if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
1776
- const currentCursor = __privateGet(this, _liveCacheBuster);
1777
- if (currentCursor === __privateGet(this, _lastSeenCursor)) {
1778
- __privateSet(this, _lastSeenCursor, void 0);
1779
- return;
1780
- }
1781
- }
1782
- __privateSet(this, _lastSeenCursor, void 0);
1783
- if (__privateGet(this, _currentFetchUrl)) {
1784
- const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
1785
- upToDateTracker.recordUpToDate(shapeKey, __privateGet(this, _liveCacheBuster));
1786
- }
2470
+ if (batch.length === 0) return;
2471
+ const lastMessage = batch[batch.length - 1];
2472
+ const hasUpToDateMessage = isUpToDateMessage(lastMessage);
2473
+ const upToDateOffset = hasUpToDateMessage ? getOffset(lastMessage) : void 0;
2474
+ const transition = __privateGet(this, _syncState).handleMessageBatch({
2475
+ hasMessages: true,
2476
+ hasUpToDateMessage,
2477
+ isSse: isSseMessage,
2478
+ upToDateOffset,
2479
+ now: Date.now(),
2480
+ currentCursor: __privateGet(this, _syncState).liveCacheBuster
2481
+ });
2482
+ __privateSet(this, _syncState, transition.state);
2483
+ if (hasUpToDateMessage) {
2484
+ if (transition.suppressBatch) {
2485
+ return;
2486
+ }
2487
+ if (__privateGet(this, _currentFetchUrl)) {
2488
+ const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
2489
+ upToDateTracker.recordUpToDate(
2490
+ shapeKey,
2491
+ __privateGet(this, _syncState).liveCacheBuster
2492
+ );
1787
2493
  }
1788
- const messagesToProcess = batch.filter((message) => {
1789
- if (isChangeMessage(message)) {
1790
- return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
1791
- }
1792
- return true;
1793
- });
1794
- await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
1795
2494
  }
2495
+ const messagesToProcess = batch.filter((message) => {
2496
+ if (isChangeMessage(message)) {
2497
+ return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
2498
+ }
2499
+ return true;
2500
+ });
2501
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
1796
2502
  };
1797
2503
  fetchShape_fn = async function(opts) {
1798
2504
  var _a;
1799
2505
  __privateSet(this, _currentFetchUrl, opts.fetchUrl);
1800
- if (!__privateGet(this, _isUpToDate) && !__privateGet(this, _ShapeStream_instances, replayMode_get)) {
2506
+ if (!__privateGet(this, _syncState).isUpToDate && __privateGet(this, _syncState).canEnterReplayMode()) {
1801
2507
  const shapeKey = canonicalShapeKey(opts.fetchUrl);
1802
2508
  const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey);
1803
2509
  if (lastSeenCursor) {
1804
- __privateSet(this, _lastSeenCursor, lastSeenCursor);
2510
+ __privateSet(this, _syncState, __privateGet(this, _syncState).enterReplayMode(lastSeenCursor));
1805
2511
  }
1806
2512
  }
1807
2513
  const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
1808
- if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
2514
+ if (__privateGet(this, _syncState).shouldUseSse({
2515
+ liveSseEnabled: !!useSse,
2516
+ isRefreshing: __privateGet(this, _ShapeStream_instances, isRefreshing_get),
2517
+ resumingFromPause: !!opts.resumingFromPause
2518
+ })) {
1809
2519
  opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
1810
2520
  opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`);
1811
2521
  return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
@@ -1819,8 +2529,9 @@ requestShapeLongPoll_fn = async function(opts) {
1819
2529
  headers
1820
2530
  });
1821
2531
  __privateSet(this, _connected, true);
1822
- await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
1823
- const schema = __privateGet(this, _schema);
2532
+ const shouldProcessBody = await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2533
+ if (!shouldProcessBody) return;
2534
+ const schema = __privateGet(this, _syncState).schema;
1824
2535
  const res = await response.text();
1825
2536
  const messages = res || `[]`;
1826
2537
  const batch = __privateGet(this, _messageParser).parse(messages, schema);
@@ -1833,6 +2544,7 @@ requestShapeSSE_fn = async function(opts) {
1833
2544
  const sseHeaders = __spreadProps(__spreadValues({}, headers), {
1834
2545
  Accept: `text/event-stream`
1835
2546
  });
2547
+ let ignoredStaleResponse = false;
1836
2548
  try {
1837
2549
  let buffer = [];
1838
2550
  await fetchEventSource(fetchUrl.toString(), {
@@ -1840,11 +2552,15 @@ requestShapeSSE_fn = async function(opts) {
1840
2552
  fetch: fetch2,
1841
2553
  onopen: async (response) => {
1842
2554
  __privateSet(this, _connected, true);
1843
- await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2555
+ const shouldProcessBody = await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2556
+ if (!shouldProcessBody) {
2557
+ ignoredStaleResponse = true;
2558
+ throw new Error(`stale response ignored`);
2559
+ }
1844
2560
  },
1845
2561
  onmessage: (event) => {
1846
2562
  if (event.data) {
1847
- const schema = __privateGet(this, _schema);
2563
+ const schema = __privateGet(this, _syncState).schema;
1848
2564
  const message = __privateGet(this, _messageParser).parse(
1849
2565
  event.data,
1850
2566
  schema
@@ -1862,6 +2578,9 @@ requestShapeSSE_fn = async function(opts) {
1862
2578
  signal: requestAbortController.signal
1863
2579
  });
1864
2580
  } catch (error) {
2581
+ if (ignoredStaleResponse) {
2582
+ return;
2583
+ }
1865
2584
  if (requestAbortController.signal.aborted) {
1866
2585
  throw new FetchBackoffAbortError();
1867
2586
  }
@@ -1869,46 +2588,33 @@ requestShapeSSE_fn = async function(opts) {
1869
2588
  } finally {
1870
2589
  const connectionDuration = Date.now() - __privateGet(this, _lastSseConnectionStartTime);
1871
2590
  const wasAborted = requestAbortController.signal.aborted;
1872
- if (connectionDuration < __privateGet(this, _minSseConnectionDuration) && !wasAborted) {
1873
- __privateWrapper(this, _consecutiveShortSseConnections)._++;
1874
- if (__privateGet(this, _consecutiveShortSseConnections) >= __privateGet(this, _maxShortSseConnections)) {
1875
- __privateSet(this, _sseFallbackToLongPolling, true);
1876
- console.warn(
1877
- `[Electric] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy. Note: Do NOT disable caching entirely - Electric uses cache headers to enable request collapsing for efficiency.`
1878
- );
1879
- } else {
1880
- const maxDelay = Math.min(
1881
- __privateGet(this, _sseBackoffMaxDelay),
1882
- __privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _consecutiveShortSseConnections))
1883
- );
1884
- const delayMs = Math.floor(Math.random() * maxDelay);
1885
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1886
- }
1887
- } else if (connectionDuration >= __privateGet(this, _minSseConnectionDuration)) {
1888
- __privateSet(this, _consecutiveShortSseConnections, 0);
1889
- }
1890
- }
1891
- };
1892
- pause_fn = function() {
1893
- var _a;
1894
- if (__privateGet(this, _started) && __privateGet(this, _state) === `active`) {
1895
- __privateSet(this, _state, `pause-requested`);
1896
- (_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.abort(PAUSE_STREAM);
1897
- }
1898
- };
1899
- resume_fn = function() {
1900
- var _a;
1901
- if (__privateGet(this, _started) && (__privateGet(this, _state) === `paused` || __privateGet(this, _state) === `pause-requested`)) {
1902
- if ((_a = this.options.signal) == null ? void 0 : _a.aborted) {
1903
- return;
1904
- }
1905
- if (__privateGet(this, _state) === `pause-requested`) {
1906
- __privateSet(this, _state, `active`);
2591
+ const transition = __privateGet(this, _syncState).handleSseConnectionClosed({
2592
+ connectionDuration,
2593
+ wasAborted,
2594
+ minConnectionDuration: __privateGet(this, _minSseConnectionDuration),
2595
+ maxShortConnections: __privateGet(this, _maxShortSseConnections)
2596
+ });
2597
+ __privateSet(this, _syncState, transition.state);
2598
+ if (transition.fellBackToLongPolling) {
2599
+ console.warn(
2600
+ `[Electric] SSE connections are closing immediately (possibly due to proxy buffering or misconfiguration). Falling back to long polling. Your proxy must support streaming SSE responses (not buffer the complete response). Configuration: Nginx add 'X-Accel-Buffering: no', Caddy add 'flush_interval -1' to reverse_proxy. Note: Do NOT disable caching entirely - Electric uses cache headers to enable request collapsing for efficiency.`
2601
+ );
2602
+ } else if (transition.wasShortConnection) {
2603
+ const maxDelay = Math.min(
2604
+ __privateGet(this, _sseBackoffMaxDelay),
2605
+ __privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _syncState).consecutiveShortSseConnections)
2606
+ );
2607
+ const delayMs = Math.floor(Math.random() * maxDelay);
2608
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1907
2609
  }
1908
- __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1909
2610
  }
1910
2611
  };
1911
2612
  nextTick_fn = async function() {
2613
+ if (__privateGet(this, _pauseLock).isPaused) {
2614
+ throw new Error(
2615
+ `Cannot wait for next tick while PauseLock is held \u2014 this would deadlock because the request loop is paused`
2616
+ );
2617
+ }
1912
2618
  if (__privateGet(this, _tickPromise)) {
1913
2619
  return __privateGet(this, _tickPromise);
1914
2620
  }
@@ -1923,22 +2629,6 @@ nextTick_fn = async function() {
1923
2629
  });
1924
2630
  return __privateGet(this, _tickPromise);
1925
2631
  };
1926
- waitForStreamEnd_fn = async function() {
1927
- if (!__privateGet(this, _isMidStream)) {
1928
- return;
1929
- }
1930
- if (__privateGet(this, _midStreamPromise)) {
1931
- return __privateGet(this, _midStreamPromise);
1932
- }
1933
- __privateSet(this, _midStreamPromise, new Promise((resolve) => {
1934
- __privateSet(this, _midStreamPromiseResolver, resolve);
1935
- }));
1936
- __privateGet(this, _midStreamPromise).finally(() => {
1937
- __privateSet(this, _midStreamPromise, void 0);
1938
- __privateSet(this, _midStreamPromiseResolver, void 0);
1939
- });
1940
- return __privateGet(this, _midStreamPromise);
1941
- };
1942
2632
  publish_fn = async function(messages) {
1943
2633
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
1944
2634
  () => Promise.all(
@@ -1967,9 +2657,9 @@ subscribeToVisibilityChanges_fn = function() {
1967
2657
  if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) {
1968
2658
  const visibilityHandler = () => {
1969
2659
  if (document.hidden) {
1970
- __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
2660
+ __privateGet(this, _pauseLock).acquire(`visibility`);
1971
2661
  } else {
1972
- __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
2662
+ __privateGet(this, _pauseLock).release(`visibility`);
1973
2663
  }
1974
2664
  };
1975
2665
  document.addEventListener(`visibilitychange`, visibilityHandler);
@@ -2000,11 +2690,11 @@ subscribeToWakeDetection_fn = function() {
2000
2690
  const elapsed = now - lastTickTime;
2001
2691
  lastTickTime = now;
2002
2692
  if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {
2003
- if (__privateGet(this, _state) === `active` && __privateGet(this, _requestAbortController)) {
2004
- __privateSet(this, _isRefreshing, true);
2693
+ if (!__privateGet(this, _pauseLock).isPaused && __privateGet(this, _requestAbortController)) {
2694
+ __privateWrapper(this, _refreshCount)._++;
2005
2695
  __privateGet(this, _requestAbortController).abort(SYSTEM_WAKE);
2006
2696
  queueMicrotask(() => {
2007
- __privateSet(this, _isRefreshing, false);
2697
+ __privateWrapper(this, _refreshCount)._--;
2008
2698
  });
2009
2699
  }
2010
2700
  }
@@ -2021,18 +2711,9 @@ subscribeToWakeDetection_fn = function() {
2021
2711
  * shape handle
2022
2712
  */
2023
2713
  reset_fn = function(handle) {
2024
- __privateSet(this, _lastOffset, `-1`);
2025
- __privateSet(this, _liveCacheBuster, ``);
2026
- __privateSet(this, _shapeHandle, handle);
2027
- __privateSet(this, _isUpToDate, false);
2028
- __privateSet(this, _isMidStream, true);
2714
+ __privateSet(this, _syncState, __privateGet(this, _syncState).markMustRefetch(handle));
2029
2715
  __privateSet(this, _connected, false);
2030
- __privateSet(this, _schema, void 0);
2031
- __privateSet(this, _activeSnapshotRequests, 0);
2032
- __privateSet(this, _consecutiveShortSseConnections, 0);
2033
- __privateSet(this, _sseFallbackToLongPolling, false);
2034
- __privateSet(this, _staleCacheBuster, void 0);
2035
- __privateSet(this, _staleCacheRetryCount, 0);
2716
+ __privateGet(this, _pauseLock).releaseAllMatching(`snapshot`);
2036
2717
  };
2037
2718
  buildSubsetBody_fn = function(opts) {
2038
2719
  var _a, _b, _c, _d;