@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.
@@ -891,8 +891,202 @@ function compileOrderBy(clauses, columnMapper) {
891
891
  }).join(`, `);
892
892
  }
893
893
 
894
- // src/client.ts
895
- var import_fetch_event_source = require("@microsoft/fetch-event-source");
894
+ // ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/lib/esm/parse.js
895
+ async function getBytes(stream, onChunk) {
896
+ const reader = stream.getReader();
897
+ let result;
898
+ while (!(result = await reader.read()).done) {
899
+ onChunk(result.value);
900
+ }
901
+ }
902
+ function getLines(onLine) {
903
+ let buffer;
904
+ let position;
905
+ let fieldLength;
906
+ let discardTrailingNewline = false;
907
+ return function onChunk(arr) {
908
+ if (buffer === void 0) {
909
+ buffer = arr;
910
+ position = 0;
911
+ fieldLength = -1;
912
+ } else {
913
+ buffer = concat(buffer, arr);
914
+ }
915
+ const bufLength = buffer.length;
916
+ let lineStart = 0;
917
+ while (position < bufLength) {
918
+ if (discardTrailingNewline) {
919
+ if (buffer[position] === 10) {
920
+ lineStart = ++position;
921
+ }
922
+ discardTrailingNewline = false;
923
+ }
924
+ let lineEnd = -1;
925
+ for (; position < bufLength && lineEnd === -1; ++position) {
926
+ switch (buffer[position]) {
927
+ case 58:
928
+ if (fieldLength === -1) {
929
+ fieldLength = position - lineStart;
930
+ }
931
+ break;
932
+ case 13:
933
+ discardTrailingNewline = true;
934
+ case 10:
935
+ lineEnd = position;
936
+ break;
937
+ }
938
+ }
939
+ if (lineEnd === -1) {
940
+ break;
941
+ }
942
+ onLine(buffer.subarray(lineStart, lineEnd), fieldLength);
943
+ lineStart = position;
944
+ fieldLength = -1;
945
+ }
946
+ if (lineStart === bufLength) {
947
+ buffer = void 0;
948
+ } else if (lineStart !== 0) {
949
+ buffer = buffer.subarray(lineStart);
950
+ position -= lineStart;
951
+ }
952
+ };
953
+ }
954
+ function getMessages(onId, onRetry, onMessage) {
955
+ let message = newMessage();
956
+ const decoder = new TextDecoder();
957
+ return function onLine(line, fieldLength) {
958
+ if (line.length === 0) {
959
+ onMessage === null || onMessage === void 0 ? void 0 : onMessage(message);
960
+ message = newMessage();
961
+ } else if (fieldLength > 0) {
962
+ const field = decoder.decode(line.subarray(0, fieldLength));
963
+ const valueOffset = fieldLength + (line[fieldLength + 1] === 32 ? 2 : 1);
964
+ const value = decoder.decode(line.subarray(valueOffset));
965
+ switch (field) {
966
+ case "data":
967
+ message.data = message.data ? message.data + "\n" + value : value;
968
+ break;
969
+ case "event":
970
+ message.event = value;
971
+ break;
972
+ case "id":
973
+ onId(message.id = value);
974
+ break;
975
+ case "retry":
976
+ const retry = parseInt(value, 10);
977
+ if (!isNaN(retry)) {
978
+ onRetry(message.retry = retry);
979
+ }
980
+ break;
981
+ }
982
+ }
983
+ };
984
+ }
985
+ function concat(a, b) {
986
+ const res = new Uint8Array(a.length + b.length);
987
+ res.set(a);
988
+ res.set(b, a.length);
989
+ return res;
990
+ }
991
+ function newMessage() {
992
+ return {
993
+ data: "",
994
+ event: "",
995
+ id: "",
996
+ retry: void 0
997
+ };
998
+ }
999
+
1000
+ // ../../node_modules/.pnpm/@microsoft+fetch-event-source@2.0.1_patch_hash=46f4e76dd960e002a542732bb4323817a24fce1673cb71e2f458fe09776fa188/node_modules/@microsoft/fetch-event-source/lib/esm/fetch.js
1001
+ var __rest = function(s, e) {
1002
+ var t = {};
1003
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
1004
+ t[p] = s[p];
1005
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
1006
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
1007
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
1008
+ t[p[i]] = s[p[i]];
1009
+ }
1010
+ return t;
1011
+ };
1012
+ var EventStreamContentType = "text/event-stream";
1013
+ var DefaultRetryInterval = 1e3;
1014
+ var LastEventId = "last-event-id";
1015
+ function fetchEventSource(input, _a) {
1016
+ 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"]);
1017
+ return new Promise((resolve, reject) => {
1018
+ const headers = Object.assign({}, inputHeaders);
1019
+ if (!headers.accept) {
1020
+ headers.accept = EventStreamContentType;
1021
+ }
1022
+ let curRequestController;
1023
+ function onVisibilityChange() {
1024
+ curRequestController.abort();
1025
+ if (typeof document !== "undefined" && !document.hidden) {
1026
+ create();
1027
+ }
1028
+ }
1029
+ if (typeof document !== "undefined" && !openWhenHidden) {
1030
+ document.addEventListener("visibilitychange", onVisibilityChange);
1031
+ }
1032
+ let retryInterval = DefaultRetryInterval;
1033
+ let retryTimer = 0;
1034
+ function dispose() {
1035
+ if (typeof document !== "undefined") {
1036
+ document.removeEventListener("visibilitychange", onVisibilityChange);
1037
+ }
1038
+ clearTimeout(retryTimer);
1039
+ curRequestController.abort();
1040
+ }
1041
+ inputSignal === null || inputSignal === void 0 ? void 0 : inputSignal.addEventListener("abort", () => {
1042
+ dispose();
1043
+ });
1044
+ const fetch2 = inputFetch !== null && inputFetch !== void 0 ? inputFetch : window.fetch;
1045
+ const onopen = inputOnOpen !== null && inputOnOpen !== void 0 ? inputOnOpen : defaultOnOpen;
1046
+ async function create() {
1047
+ var _a2;
1048
+ curRequestController = new AbortController();
1049
+ const sig = inputSignal.aborted ? inputSignal : curRequestController.signal;
1050
+ try {
1051
+ const response = await fetch2(input, Object.assign(Object.assign({}, rest), { headers, signal: sig }));
1052
+ await onopen(response);
1053
+ await getBytes(response.body, getLines(getMessages((id) => {
1054
+ if (id) {
1055
+ headers[LastEventId] = id;
1056
+ } else {
1057
+ delete headers[LastEventId];
1058
+ }
1059
+ }, (retry) => {
1060
+ retryInterval = retry;
1061
+ }, onmessage)));
1062
+ onclose === null || onclose === void 0 ? void 0 : onclose();
1063
+ dispose();
1064
+ resolve();
1065
+ } catch (err) {
1066
+ if (sig.aborted) {
1067
+ dispose();
1068
+ reject(err);
1069
+ } else if (!curRequestController.signal.aborted) {
1070
+ try {
1071
+ const interval = (_a2 = onerror === null || onerror === void 0 ? void 0 : onerror(err)) !== null && _a2 !== void 0 ? _a2 : retryInterval;
1072
+ clearTimeout(retryTimer);
1073
+ retryTimer = setTimeout(create, interval);
1074
+ } catch (innerErr) {
1075
+ dispose();
1076
+ reject(innerErr);
1077
+ }
1078
+ }
1079
+ }
1080
+ }
1081
+ create();
1082
+ });
1083
+ }
1084
+ function defaultOnOpen(response) {
1085
+ const contentType = response.headers.get("content-type");
1086
+ if (!(contentType === null || contentType === void 0 ? void 0 : contentType.startsWith(EventStreamContentType))) {
1087
+ throw new Error(`Expected content-type to be ${EventStreamContentType}, Actual: ${contentType}`);
1088
+ }
1089
+ }
896
1090
 
897
1091
  // src/expired-shapes-cache.ts
898
1092
  var ExpiredShapesCache = class {
@@ -1130,6 +1324,544 @@ var SnapshotTracker = class {
1130
1324
  }
1131
1325
  };
1132
1326
 
1327
+ // src/shape-stream-state.ts
1328
+ var ShapeStreamState = class {
1329
+ // --- Derived booleans ---
1330
+ get isUpToDate() {
1331
+ return false;
1332
+ }
1333
+ // --- Per-state field defaults ---
1334
+ get staleCacheBuster() {
1335
+ return void 0;
1336
+ }
1337
+ get staleCacheRetryCount() {
1338
+ return 0;
1339
+ }
1340
+ get sseFallbackToLongPolling() {
1341
+ return false;
1342
+ }
1343
+ get consecutiveShortSseConnections() {
1344
+ return 0;
1345
+ }
1346
+ get replayCursor() {
1347
+ return void 0;
1348
+ }
1349
+ // --- Default no-op methods ---
1350
+ canEnterReplayMode() {
1351
+ return false;
1352
+ }
1353
+ enterReplayMode(_cursor) {
1354
+ return this;
1355
+ }
1356
+ shouldUseSse(_opts) {
1357
+ return false;
1358
+ }
1359
+ handleSseConnectionClosed(_input) {
1360
+ return {
1361
+ state: this,
1362
+ fellBackToLongPolling: false,
1363
+ wasShortConnection: false
1364
+ };
1365
+ }
1366
+ // --- URL param application ---
1367
+ /** Adds state-specific query parameters to the fetch URL. */
1368
+ applyUrlParams(_url, _context) {
1369
+ }
1370
+ // --- Default response/message handlers (Paused/Error never receive these) ---
1371
+ handleResponseMetadata(_input) {
1372
+ return { action: `ignored`, state: this };
1373
+ }
1374
+ handleMessageBatch(_input) {
1375
+ return { state: this, suppressBatch: false, becameUpToDate: false };
1376
+ }
1377
+ pause() {
1378
+ return new PausedState(this);
1379
+ }
1380
+ toErrorState(error) {
1381
+ return new ErrorState(this, error);
1382
+ }
1383
+ markMustRefetch(handle) {
1384
+ return new InitialState({
1385
+ handle,
1386
+ offset: `-1`,
1387
+ liveCacheBuster: ``,
1388
+ lastSyncedAt: this.lastSyncedAt,
1389
+ schema: void 0
1390
+ });
1391
+ }
1392
+ };
1393
+ var _shared;
1394
+ var ActiveState = class extends ShapeStreamState {
1395
+ constructor(shared) {
1396
+ super();
1397
+ __privateAdd(this, _shared);
1398
+ __privateSet(this, _shared, shared);
1399
+ }
1400
+ get handle() {
1401
+ return __privateGet(this, _shared).handle;
1402
+ }
1403
+ get offset() {
1404
+ return __privateGet(this, _shared).offset;
1405
+ }
1406
+ get schema() {
1407
+ return __privateGet(this, _shared).schema;
1408
+ }
1409
+ get liveCacheBuster() {
1410
+ return __privateGet(this, _shared).liveCacheBuster;
1411
+ }
1412
+ get lastSyncedAt() {
1413
+ return __privateGet(this, _shared).lastSyncedAt;
1414
+ }
1415
+ /** Expose shared fields to subclasses for spreading into new instances. */
1416
+ get currentFields() {
1417
+ return __privateGet(this, _shared);
1418
+ }
1419
+ // --- URL param application ---
1420
+ applyUrlParams(url, _context) {
1421
+ url.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _shared).offset);
1422
+ if (__privateGet(this, _shared).handle) {
1423
+ url.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shared).handle);
1424
+ }
1425
+ }
1426
+ // --- Helpers for subclass handleResponseMetadata implementations ---
1427
+ /** Extracts updated SharedStateFields from response headers. */
1428
+ parseResponseFields(input) {
1429
+ var _a, _b, _c;
1430
+ const responseHandle = input.responseHandle;
1431
+ const handle = responseHandle && responseHandle !== input.expiredHandle ? responseHandle : __privateGet(this, _shared).handle;
1432
+ const offset = (_a = input.responseOffset) != null ? _a : __privateGet(this, _shared).offset;
1433
+ const liveCacheBuster = (_b = input.responseCursor) != null ? _b : __privateGet(this, _shared).liveCacheBuster;
1434
+ const schema = (_c = __privateGet(this, _shared).schema) != null ? _c : input.responseSchema;
1435
+ const lastSyncedAt = input.status === 204 ? input.now : __privateGet(this, _shared).lastSyncedAt;
1436
+ return { handle, offset, schema, liveCacheBuster, lastSyncedAt };
1437
+ }
1438
+ /**
1439
+ * Stale detection. Returns a transition if the response is stale,
1440
+ * or null if it is not stale and the caller should proceed normally.
1441
+ */
1442
+ checkStaleResponse(input) {
1443
+ const responseHandle = input.responseHandle;
1444
+ const expiredHandle = input.expiredHandle;
1445
+ if (!responseHandle || responseHandle !== expiredHandle) {
1446
+ return null;
1447
+ }
1448
+ if (__privateGet(this, _shared).handle === void 0 || __privateGet(this, _shared).handle === expiredHandle) {
1449
+ const retryCount = this.staleCacheRetryCount + 1;
1450
+ return {
1451
+ action: `stale-retry`,
1452
+ state: new StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
1453
+ staleCacheBuster: input.createCacheBuster(),
1454
+ staleCacheRetryCount: retryCount
1455
+ })),
1456
+ exceededMaxRetries: retryCount > input.maxStaleCacheRetries
1457
+ };
1458
+ }
1459
+ return { action: `ignored`, state: this };
1460
+ }
1461
+ // --- handleMessageBatch: template method with onUpToDate override point ---
1462
+ handleMessageBatch(input) {
1463
+ if (!input.hasMessages || !input.hasUpToDateMessage) {
1464
+ return { state: this, suppressBatch: false, becameUpToDate: false };
1465
+ }
1466
+ let offset = __privateGet(this, _shared).offset;
1467
+ if (input.isSse && input.upToDateOffset) {
1468
+ offset = input.upToDateOffset;
1469
+ }
1470
+ const shared = {
1471
+ handle: __privateGet(this, _shared).handle,
1472
+ offset,
1473
+ schema: __privateGet(this, _shared).schema,
1474
+ liveCacheBuster: __privateGet(this, _shared).liveCacheBuster,
1475
+ lastSyncedAt: input.now
1476
+ };
1477
+ return this.onUpToDate(shared, input);
1478
+ }
1479
+ /** Override point for up-to-date handling. Default → LiveState. */
1480
+ onUpToDate(shared, _input) {
1481
+ return {
1482
+ state: new LiveState(shared),
1483
+ suppressBatch: false,
1484
+ becameUpToDate: true
1485
+ };
1486
+ }
1487
+ };
1488
+ _shared = new WeakMap();
1489
+ var FetchingState = class extends ActiveState {
1490
+ handleResponseMetadata(input) {
1491
+ const staleResult = this.checkStaleResponse(input);
1492
+ if (staleResult) return staleResult;
1493
+ const shared = this.parseResponseFields(input);
1494
+ return { action: `accepted`, state: new SyncingState(shared) };
1495
+ }
1496
+ canEnterReplayMode() {
1497
+ return true;
1498
+ }
1499
+ enterReplayMode(cursor) {
1500
+ return new ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
1501
+ replayCursor: cursor
1502
+ }));
1503
+ }
1504
+ };
1505
+ var InitialState = class _InitialState extends FetchingState {
1506
+ constructor(shared) {
1507
+ super(shared);
1508
+ this.kind = `initial`;
1509
+ }
1510
+ withHandle(handle) {
1511
+ return new _InitialState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
1512
+ }
1513
+ };
1514
+ var SyncingState = class _SyncingState extends FetchingState {
1515
+ constructor(shared) {
1516
+ super(shared);
1517
+ this.kind = `syncing`;
1518
+ }
1519
+ withHandle(handle) {
1520
+ return new _SyncingState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
1521
+ }
1522
+ };
1523
+ var _staleCacheBuster, _staleCacheRetryCount;
1524
+ var _StaleRetryState = class _StaleRetryState extends FetchingState {
1525
+ constructor(fields) {
1526
+ const _a = fields, { staleCacheBuster, staleCacheRetryCount } = _a, shared = __objRest(_a, ["staleCacheBuster", "staleCacheRetryCount"]);
1527
+ super(shared);
1528
+ this.kind = `stale-retry`;
1529
+ __privateAdd(this, _staleCacheBuster);
1530
+ __privateAdd(this, _staleCacheRetryCount);
1531
+ __privateSet(this, _staleCacheBuster, staleCacheBuster);
1532
+ __privateSet(this, _staleCacheRetryCount, staleCacheRetryCount);
1533
+ }
1534
+ get staleCacheBuster() {
1535
+ return __privateGet(this, _staleCacheBuster);
1536
+ }
1537
+ get staleCacheRetryCount() {
1538
+ return __privateGet(this, _staleCacheRetryCount);
1539
+ }
1540
+ // StaleRetryState must not enter replay mode — it would lose the retry count
1541
+ canEnterReplayMode() {
1542
+ return false;
1543
+ }
1544
+ withHandle(handle) {
1545
+ return new _StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
1546
+ handle,
1547
+ staleCacheBuster: __privateGet(this, _staleCacheBuster),
1548
+ staleCacheRetryCount: __privateGet(this, _staleCacheRetryCount)
1549
+ }));
1550
+ }
1551
+ applyUrlParams(url, context) {
1552
+ super.applyUrlParams(url, context);
1553
+ url.searchParams.set(CACHE_BUSTER_QUERY_PARAM, __privateGet(this, _staleCacheBuster));
1554
+ }
1555
+ };
1556
+ _staleCacheBuster = new WeakMap();
1557
+ _staleCacheRetryCount = new WeakMap();
1558
+ var StaleRetryState = _StaleRetryState;
1559
+ var _consecutiveShortSseConnections, _sseFallbackToLongPolling;
1560
+ var _LiveState = class _LiveState extends ActiveState {
1561
+ constructor(shared, sseState) {
1562
+ var _a, _b;
1563
+ super(shared);
1564
+ this.kind = `live`;
1565
+ __privateAdd(this, _consecutiveShortSseConnections);
1566
+ __privateAdd(this, _sseFallbackToLongPolling);
1567
+ __privateSet(this, _consecutiveShortSseConnections, (_a = sseState == null ? void 0 : sseState.consecutiveShortSseConnections) != null ? _a : 0);
1568
+ __privateSet(this, _sseFallbackToLongPolling, (_b = sseState == null ? void 0 : sseState.sseFallbackToLongPolling) != null ? _b : false);
1569
+ }
1570
+ get isUpToDate() {
1571
+ return true;
1572
+ }
1573
+ get consecutiveShortSseConnections() {
1574
+ return __privateGet(this, _consecutiveShortSseConnections);
1575
+ }
1576
+ get sseFallbackToLongPolling() {
1577
+ return __privateGet(this, _sseFallbackToLongPolling);
1578
+ }
1579
+ withHandle(handle) {
1580
+ return new _LiveState(__spreadProps(__spreadValues({}, this.currentFields), { handle }), this.sseState);
1581
+ }
1582
+ applyUrlParams(url, context) {
1583
+ super.applyUrlParams(url, context);
1584
+ if (!context.isSnapshotRequest) {
1585
+ url.searchParams.set(LIVE_CACHE_BUSTER_QUERY_PARAM, this.liveCacheBuster);
1586
+ if (context.canLongPoll) {
1587
+ url.searchParams.set(LIVE_QUERY_PARAM, `true`);
1588
+ }
1589
+ }
1590
+ }
1591
+ get sseState() {
1592
+ return {
1593
+ consecutiveShortSseConnections: __privateGet(this, _consecutiveShortSseConnections),
1594
+ sseFallbackToLongPolling: __privateGet(this, _sseFallbackToLongPolling)
1595
+ };
1596
+ }
1597
+ handleResponseMetadata(input) {
1598
+ const staleResult = this.checkStaleResponse(input);
1599
+ if (staleResult) return staleResult;
1600
+ const shared = this.parseResponseFields(input);
1601
+ return {
1602
+ action: `accepted`,
1603
+ state: new _LiveState(shared, this.sseState)
1604
+ };
1605
+ }
1606
+ onUpToDate(shared, _input) {
1607
+ return {
1608
+ state: new _LiveState(shared, this.sseState),
1609
+ suppressBatch: false,
1610
+ becameUpToDate: true
1611
+ };
1612
+ }
1613
+ shouldUseSse(opts) {
1614
+ return opts.liveSseEnabled && !opts.isRefreshing && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling);
1615
+ }
1616
+ handleSseConnectionClosed(input) {
1617
+ let nextConsecutiveShort = __privateGet(this, _consecutiveShortSseConnections);
1618
+ let nextFallback = __privateGet(this, _sseFallbackToLongPolling);
1619
+ let fellBackToLongPolling = false;
1620
+ let wasShortConnection = false;
1621
+ if (input.connectionDuration < input.minConnectionDuration && !input.wasAborted) {
1622
+ wasShortConnection = true;
1623
+ nextConsecutiveShort = nextConsecutiveShort + 1;
1624
+ if (nextConsecutiveShort >= input.maxShortConnections) {
1625
+ nextFallback = true;
1626
+ fellBackToLongPolling = true;
1627
+ }
1628
+ } else if (input.connectionDuration >= input.minConnectionDuration) {
1629
+ nextConsecutiveShort = 0;
1630
+ }
1631
+ return {
1632
+ state: new _LiveState(this.currentFields, {
1633
+ consecutiveShortSseConnections: nextConsecutiveShort,
1634
+ sseFallbackToLongPolling: nextFallback
1635
+ }),
1636
+ fellBackToLongPolling,
1637
+ wasShortConnection
1638
+ };
1639
+ }
1640
+ };
1641
+ _consecutiveShortSseConnections = new WeakMap();
1642
+ _sseFallbackToLongPolling = new WeakMap();
1643
+ var LiveState = _LiveState;
1644
+ var _replayCursor;
1645
+ var _ReplayingState = class _ReplayingState extends ActiveState {
1646
+ constructor(fields) {
1647
+ const _a = fields, { replayCursor } = _a, shared = __objRest(_a, ["replayCursor"]);
1648
+ super(shared);
1649
+ this.kind = `replaying`;
1650
+ __privateAdd(this, _replayCursor);
1651
+ __privateSet(this, _replayCursor, replayCursor);
1652
+ }
1653
+ get replayCursor() {
1654
+ return __privateGet(this, _replayCursor);
1655
+ }
1656
+ withHandle(handle) {
1657
+ return new _ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
1658
+ handle,
1659
+ replayCursor: __privateGet(this, _replayCursor)
1660
+ }));
1661
+ }
1662
+ handleResponseMetadata(input) {
1663
+ const staleResult = this.checkStaleResponse(input);
1664
+ if (staleResult) return staleResult;
1665
+ const shared = this.parseResponseFields(input);
1666
+ return {
1667
+ action: `accepted`,
1668
+ state: new _ReplayingState(__spreadProps(__spreadValues({}, shared), {
1669
+ replayCursor: __privateGet(this, _replayCursor)
1670
+ }))
1671
+ };
1672
+ }
1673
+ onUpToDate(shared, input) {
1674
+ const suppressBatch = !input.isSse && __privateGet(this, _replayCursor) === input.currentCursor;
1675
+ return {
1676
+ state: new LiveState(shared),
1677
+ suppressBatch,
1678
+ becameUpToDate: true
1679
+ };
1680
+ }
1681
+ };
1682
+ _replayCursor = new WeakMap();
1683
+ var ReplayingState = _ReplayingState;
1684
+ var PausedState = class _PausedState extends ShapeStreamState {
1685
+ constructor(previousState) {
1686
+ super();
1687
+ this.kind = `paused`;
1688
+ this.previousState = previousState;
1689
+ }
1690
+ get handle() {
1691
+ return this.previousState.handle;
1692
+ }
1693
+ get offset() {
1694
+ return this.previousState.offset;
1695
+ }
1696
+ get schema() {
1697
+ return this.previousState.schema;
1698
+ }
1699
+ get liveCacheBuster() {
1700
+ return this.previousState.liveCacheBuster;
1701
+ }
1702
+ get lastSyncedAt() {
1703
+ return this.previousState.lastSyncedAt;
1704
+ }
1705
+ get isUpToDate() {
1706
+ return this.previousState.isUpToDate;
1707
+ }
1708
+ get staleCacheBuster() {
1709
+ return this.previousState.staleCacheBuster;
1710
+ }
1711
+ get staleCacheRetryCount() {
1712
+ return this.previousState.staleCacheRetryCount;
1713
+ }
1714
+ get sseFallbackToLongPolling() {
1715
+ return this.previousState.sseFallbackToLongPolling;
1716
+ }
1717
+ get consecutiveShortSseConnections() {
1718
+ return this.previousState.consecutiveShortSseConnections;
1719
+ }
1720
+ get replayCursor() {
1721
+ return this.previousState.replayCursor;
1722
+ }
1723
+ withHandle(handle) {
1724
+ return new _PausedState(this.previousState.withHandle(handle));
1725
+ }
1726
+ applyUrlParams(url, context) {
1727
+ this.previousState.applyUrlParams(url, context);
1728
+ }
1729
+ pause() {
1730
+ return this;
1731
+ }
1732
+ resume() {
1733
+ return this.previousState;
1734
+ }
1735
+ };
1736
+ var ErrorState = class _ErrorState extends ShapeStreamState {
1737
+ constructor(previousState, error) {
1738
+ super();
1739
+ this.kind = `error`;
1740
+ this.previousState = previousState;
1741
+ this.error = error;
1742
+ }
1743
+ get handle() {
1744
+ return this.previousState.handle;
1745
+ }
1746
+ get offset() {
1747
+ return this.previousState.offset;
1748
+ }
1749
+ get schema() {
1750
+ return this.previousState.schema;
1751
+ }
1752
+ get liveCacheBuster() {
1753
+ return this.previousState.liveCacheBuster;
1754
+ }
1755
+ get lastSyncedAt() {
1756
+ return this.previousState.lastSyncedAt;
1757
+ }
1758
+ get isUpToDate() {
1759
+ return this.previousState.isUpToDate;
1760
+ }
1761
+ withHandle(handle) {
1762
+ return new _ErrorState(this.previousState.withHandle(handle), this.error);
1763
+ }
1764
+ applyUrlParams(url, context) {
1765
+ this.previousState.applyUrlParams(url, context);
1766
+ }
1767
+ retry() {
1768
+ return this.previousState;
1769
+ }
1770
+ reset(handle) {
1771
+ return this.previousState.markMustRefetch(handle);
1772
+ }
1773
+ };
1774
+ function createInitialState(opts) {
1775
+ return new InitialState({
1776
+ handle: opts.handle,
1777
+ offset: opts.offset,
1778
+ liveCacheBuster: ``,
1779
+ lastSyncedAt: void 0,
1780
+ schema: void 0
1781
+ });
1782
+ }
1783
+
1784
+ // src/pause-lock.ts
1785
+ var _holders, _onAcquired, _onReleased;
1786
+ var PauseLock = class {
1787
+ constructor(callbacks) {
1788
+ __privateAdd(this, _holders, /* @__PURE__ */ new Set());
1789
+ __privateAdd(this, _onAcquired);
1790
+ __privateAdd(this, _onReleased);
1791
+ __privateSet(this, _onAcquired, callbacks.onAcquired);
1792
+ __privateSet(this, _onReleased, callbacks.onReleased);
1793
+ }
1794
+ /**
1795
+ * Acquire the lock for a given reason. Idempotent — acquiring the same
1796
+ * reason twice is a no-op (but logs a warning since it likely indicates
1797
+ * a caller bug).
1798
+ *
1799
+ * Fires `onAcquired` when the first reason is acquired (transition from
1800
+ * unlocked to locked).
1801
+ */
1802
+ acquire(reason) {
1803
+ if (__privateGet(this, _holders).has(reason)) {
1804
+ console.warn(
1805
+ `[Electric] PauseLock: "${reason}" already held \u2014 ignoring duplicate acquire`
1806
+ );
1807
+ return;
1808
+ }
1809
+ const wasUnlocked = __privateGet(this, _holders).size === 0;
1810
+ __privateGet(this, _holders).add(reason);
1811
+ if (wasUnlocked) {
1812
+ __privateGet(this, _onAcquired).call(this);
1813
+ }
1814
+ }
1815
+ /**
1816
+ * Release the lock for a given reason. Releasing a reason that isn't
1817
+ * held logs a warning (likely indicates an acquire/release mismatch).
1818
+ *
1819
+ * Fires `onReleased` when the last reason is released (transition from
1820
+ * locked to unlocked).
1821
+ */
1822
+ release(reason) {
1823
+ if (!__privateGet(this, _holders).delete(reason)) {
1824
+ console.warn(
1825
+ `[Electric] PauseLock: "${reason}" not held \u2014 ignoring release (possible acquire/release mismatch)`
1826
+ );
1827
+ return;
1828
+ }
1829
+ if (__privateGet(this, _holders).size === 0) {
1830
+ __privateGet(this, _onReleased).call(this);
1831
+ }
1832
+ }
1833
+ /**
1834
+ * Whether the lock is currently held by any reason.
1835
+ */
1836
+ get isPaused() {
1837
+ return __privateGet(this, _holders).size > 0;
1838
+ }
1839
+ /**
1840
+ * Check if a specific reason is holding the lock.
1841
+ */
1842
+ isHeldBy(reason) {
1843
+ return __privateGet(this, _holders).has(reason);
1844
+ }
1845
+ /**
1846
+ * Release all reasons matching a prefix. Does NOT fire `onReleased` —
1847
+ * this is for cleanup/reset paths where the stream state is being
1848
+ * managed separately.
1849
+ *
1850
+ * This preserves reasons with different prefixes (e.g., 'visibility'
1851
+ * is preserved when clearing 'snapshot-*' reasons).
1852
+ */
1853
+ releaseAllMatching(prefix) {
1854
+ for (const reason of __privateGet(this, _holders)) {
1855
+ if (reason.startsWith(prefix)) {
1856
+ __privateGet(this, _holders).delete(reason);
1857
+ }
1858
+ }
1859
+ }
1860
+ };
1861
+ _holders = new WeakMap();
1862
+ _onAcquired = new WeakMap();
1863
+ _onReleased = new WeakMap();
1864
+
1133
1865
  // src/client.ts
1134
1866
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
1135
1867
  LIVE_CACHE_BUSTER_QUERY_PARAM,
@@ -1178,7 +1910,7 @@ function canonicalShapeKey(url) {
1178
1910
  cleanUrl.searchParams.sort();
1179
1911
  return cleanUrl.toString();
1180
1912
  }
1181
- 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;
1913
+ 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;
1182
1914
  var ShapeStream = class {
1183
1915
  constructor(options) {
1184
1916
  __privateAdd(this, _ShapeStream_instances);
@@ -1188,58 +1920,57 @@ var ShapeStream = class {
1188
1920
  __privateAdd(this, _messageParser);
1189
1921
  __privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
1190
1922
  __privateAdd(this, _started, false);
1191
- __privateAdd(this, _state, `active`);
1192
- __privateAdd(this, _lastOffset);
1193
- __privateAdd(this, _liveCacheBuster);
1194
- // Seconds since our Electric Epoch 😎
1195
- __privateAdd(this, _lastSyncedAt);
1196
- // unix time
1197
- __privateAdd(this, _isUpToDate, false);
1198
- __privateAdd(this, _isMidStream, true);
1923
+ __privateAdd(this, _syncState);
1199
1924
  __privateAdd(this, _connected, false);
1200
- __privateAdd(this, _shapeHandle);
1201
1925
  __privateAdd(this, _mode);
1202
- __privateAdd(this, _schema);
1203
1926
  __privateAdd(this, _onError);
1204
1927
  __privateAdd(this, _requestAbortController);
1205
- __privateAdd(this, _isRefreshing, false);
1928
+ __privateAdd(this, _refreshCount, 0);
1929
+ __privateAdd(this, _snapshotCounter, 0);
1206
1930
  __privateAdd(this, _tickPromise);
1207
1931
  __privateAdd(this, _tickPromiseResolver);
1208
1932
  __privateAdd(this, _tickPromiseRejecter);
1209
1933
  __privateAdd(this, _messageChain, Promise.resolve([]));
1210
1934
  // promise chain for incoming messages
1211
1935
  __privateAdd(this, _snapshotTracker, new SnapshotTracker());
1212
- __privateAdd(this, _activeSnapshotRequests, 0);
1213
- // counter for concurrent snapshot requests
1214
- __privateAdd(this, _midStreamPromise);
1215
- __privateAdd(this, _midStreamPromiseResolver);
1216
- __privateAdd(this, _lastSeenCursor);
1217
- // Last seen cursor from previous session (used to detect cached responses)
1936
+ __privateAdd(this, _pauseLock);
1218
1937
  __privateAdd(this, _currentFetchUrl);
1219
1938
  // Current fetch URL for computing shape key
1220
1939
  __privateAdd(this, _lastSseConnectionStartTime);
1221
1940
  __privateAdd(this, _minSseConnectionDuration, 1e3);
1222
1941
  // Minimum expected SSE connection duration (1 second)
1223
- __privateAdd(this, _consecutiveShortSseConnections, 0);
1224
1942
  __privateAdd(this, _maxShortSseConnections, 3);
1225
1943
  // Fall back to long polling after this many short connections
1226
- __privateAdd(this, _sseFallbackToLongPolling, false);
1227
1944
  __privateAdd(this, _sseBackoffBaseDelay, 100);
1228
1945
  // Base delay for exponential backoff (ms)
1229
1946
  __privateAdd(this, _sseBackoffMaxDelay, 5e3);
1230
1947
  // Maximum delay cap (ms)
1231
1948
  __privateAdd(this, _unsubscribeFromVisibilityChanges);
1232
1949
  __privateAdd(this, _unsubscribeFromWakeDetection);
1233
- __privateAdd(this, _staleCacheBuster);
1234
- // Cache buster set when stale CDN response detected, used on retry requests to bypass cache
1235
- __privateAdd(this, _staleCacheRetryCount, 0);
1236
1950
  __privateAdd(this, _maxStaleCacheRetries, 3);
1237
1951
  var _a, _b, _c, _d;
1238
1952
  this.options = __spreadValues({ subscribe: true }, options);
1239
1953
  validateOptions(this.options);
1240
- __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
1241
- __privateSet(this, _liveCacheBuster, ``);
1242
- __privateSet(this, _shapeHandle, this.options.handle);
1954
+ __privateSet(this, _syncState, createInitialState({
1955
+ offset: (_a = this.options.offset) != null ? _a : `-1`,
1956
+ handle: this.options.handle
1957
+ }));
1958
+ __privateSet(this, _pauseLock, new PauseLock({
1959
+ onAcquired: () => {
1960
+ var _a2;
1961
+ __privateSet(this, _syncState, __privateGet(this, _syncState).pause());
1962
+ if (__privateGet(this, _started)) {
1963
+ (_a2 = __privateGet(this, _requestAbortController)) == null ? void 0 : _a2.abort(PAUSE_STREAM);
1964
+ }
1965
+ },
1966
+ onReleased: () => {
1967
+ var _a2;
1968
+ if (!__privateGet(this, _started)) return;
1969
+ if ((_a2 = this.options.signal) == null ? void 0 : _a2.aborted) return;
1970
+ __privateMethod(this, _ShapeStream_instances, start_fn).call(this).catch(() => {
1971
+ });
1972
+ }
1973
+ }));
1243
1974
  let transformer;
1244
1975
  if (options.columnMapper) {
1245
1976
  const applyColumnMapper = (row) => {
@@ -1277,16 +2008,16 @@ var ShapeStream = class {
1277
2008
  __privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
1278
2009
  }
1279
2010
  get shapeHandle() {
1280
- return __privateGet(this, _shapeHandle);
2011
+ return __privateGet(this, _syncState).handle;
1281
2012
  }
1282
2013
  get error() {
1283
2014
  return __privateGet(this, _error);
1284
2015
  }
1285
2016
  get isUpToDate() {
1286
- return __privateGet(this, _isUpToDate);
2017
+ return __privateGet(this, _syncState).isUpToDate;
1287
2018
  }
1288
2019
  get lastOffset() {
1289
- return __privateGet(this, _lastOffset);
2020
+ return __privateGet(this, _syncState).offset;
1290
2021
  }
1291
2022
  get mode() {
1292
2023
  return __privateGet(this, _mode);
@@ -1306,28 +2037,28 @@ var ShapeStream = class {
1306
2037
  (_a = __privateGet(this, _unsubscribeFromVisibilityChanges)) == null ? void 0 : _a.call(this);
1307
2038
  (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1308
2039
  }
1309
- /** Unix time at which we last synced. Undefined when `isLoading` is true. */
2040
+ /** Unix time at which we last synced. Undefined until first successful up-to-date. */
1310
2041
  lastSyncedAt() {
1311
- return __privateGet(this, _lastSyncedAt);
2042
+ return __privateGet(this, _syncState).lastSyncedAt;
1312
2043
  }
1313
2044
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
1314
2045
  lastSynced() {
1315
- if (__privateGet(this, _lastSyncedAt) === void 0) return Infinity;
1316
- return Date.now() - __privateGet(this, _lastSyncedAt);
2046
+ if (__privateGet(this, _syncState).lastSyncedAt === void 0) return Infinity;
2047
+ return Date.now() - __privateGet(this, _syncState).lastSyncedAt;
1317
2048
  }
1318
2049
  /** Indicates if we are connected to the Electric sync service. */
1319
2050
  isConnected() {
1320
2051
  return __privateGet(this, _connected);
1321
2052
  }
1322
- /** True during initial fetch. False afterwise. */
2053
+ /** True during initial fetch. False afterwards. */
1323
2054
  isLoading() {
1324
- return !__privateGet(this, _isUpToDate);
2055
+ return !__privateGet(this, _syncState).isUpToDate;
1325
2056
  }
1326
2057
  hasStarted() {
1327
2058
  return __privateGet(this, _started);
1328
2059
  }
1329
2060
  isPaused() {
1330
- return __privateGet(this, _state) === `paused`;
2061
+ return __privateGet(this, _pauseLock).isPaused;
1331
2062
  }
1332
2063
  /**
1333
2064
  * Refreshes the shape stream.
@@ -1337,12 +2068,15 @@ var ShapeStream = class {
1337
2068
  */
1338
2069
  async forceDisconnectAndRefresh() {
1339
2070
  var _a, _b;
1340
- __privateSet(this, _isRefreshing, true);
1341
- if (__privateGet(this, _isUpToDate) && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
1342
- (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
2071
+ __privateWrapper(this, _refreshCount)._++;
2072
+ try {
2073
+ if (__privateGet(this, _syncState).isUpToDate && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
2074
+ (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
2075
+ }
2076
+ await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
2077
+ } finally {
2078
+ __privateWrapper(this, _refreshCount)._--;
1343
2079
  }
1344
- await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
1345
- __privateSet(this, _isRefreshing, false);
1346
2080
  }
1347
2081
  /**
1348
2082
  * Request a snapshot for subset of data and inject it into the subscribed data stream.
@@ -1364,13 +2098,18 @@ var ShapeStream = class {
1364
2098
  `Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
1365
2099
  );
1366
2100
  }
1367
- if (!__privateGet(this, _started)) await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1368
- await __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
1369
- __privateWrapper(this, _activeSnapshotRequests)._++;
2101
+ if (!__privateGet(this, _started)) {
2102
+ __privateMethod(this, _ShapeStream_instances, start_fn).call(this).catch(() => {
2103
+ });
2104
+ }
2105
+ const snapshotReason = `snapshot-${++__privateWrapper(this, _snapshotCounter)._}`;
2106
+ __privateGet(this, _pauseLock).acquire(snapshotReason);
2107
+ const snapshotWarnTimer = setTimeout(() => {
2108
+ console.warn(
2109
+ `[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}`
2110
+ );
2111
+ }, 3e4);
1370
2112
  try {
1371
- if (__privateGet(this, _activeSnapshotRequests) === 1) {
1372
- __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
1373
- }
1374
2113
  const { metadata, data } = await this.fetchSnapshot(opts);
1375
2114
  const dataWithEndBoundary = data.concat([
1376
2115
  { headers: __spreadValues({ control: `snapshot-end` }, metadata) },
@@ -1386,10 +2125,8 @@ var ShapeStream = class {
1386
2125
  data
1387
2126
  };
1388
2127
  } finally {
1389
- __privateWrapper(this, _activeSnapshotRequests)._--;
1390
- if (__privateGet(this, _activeSnapshotRequests) === 0) {
1391
- __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
1392
- }
2128
+ clearTimeout(snapshotWarnTimer);
2129
+ __privateGet(this, _pauseLock).release(snapshotReason);
1393
2130
  }
1394
2131
  }
1395
2132
  /**
@@ -1424,7 +2161,7 @@ var ShapeStream = class {
1424
2161
  fetchUrl = result.fetchUrl;
1425
2162
  fetchOptions = { headers: result.requestHeaders };
1426
2163
  }
1427
- const usedHandle = __privateGet(this, _shapeHandle);
2164
+ const usedHandle = __privateGet(this, _syncState).handle;
1428
2165
  let response;
1429
2166
  try {
1430
2167
  response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
@@ -1434,7 +2171,8 @@ var ShapeStream = class {
1434
2171
  const shapeKey = canonicalShapeKey(fetchUrl);
1435
2172
  expiredShapesCache.markExpired(shapeKey, usedHandle);
1436
2173
  }
1437
- __privateSet(this, _shapeHandle, e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`);
2174
+ const nextHandle = e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`;
2175
+ __privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
1438
2176
  return this.fetchSnapshot(opts);
1439
2177
  }
1440
2178
  throw e;
@@ -1442,7 +2180,7 @@ var ShapeStream = class {
1442
2180
  if (!response.ok) {
1443
2181
  throw await FetchError.fromResponse(response, fetchUrl.toString());
1444
2182
  }
1445
- const schema = (_c = __privateGet(this, _schema)) != null ? _c : getSchemaFromHeaders(response.headers, {
2183
+ const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
1446
2184
  required: true,
1447
2185
  url: fetchUrl.toString()
1448
2186
  });
@@ -1460,52 +2198,42 @@ _sseFetchClient = new WeakMap();
1460
2198
  _messageParser = new WeakMap();
1461
2199
  _subscribers = new WeakMap();
1462
2200
  _started = new WeakMap();
1463
- _state = new WeakMap();
1464
- _lastOffset = new WeakMap();
1465
- _liveCacheBuster = new WeakMap();
1466
- _lastSyncedAt = new WeakMap();
1467
- _isUpToDate = new WeakMap();
1468
- _isMidStream = new WeakMap();
2201
+ _syncState = new WeakMap();
1469
2202
  _connected = new WeakMap();
1470
- _shapeHandle = new WeakMap();
1471
2203
  _mode = new WeakMap();
1472
- _schema = new WeakMap();
1473
2204
  _onError = new WeakMap();
1474
2205
  _requestAbortController = new WeakMap();
1475
- _isRefreshing = new WeakMap();
2206
+ _refreshCount = new WeakMap();
2207
+ _snapshotCounter = new WeakMap();
2208
+ _ShapeStream_instances = new WeakSet();
2209
+ isRefreshing_get = function() {
2210
+ return __privateGet(this, _refreshCount) > 0;
2211
+ };
1476
2212
  _tickPromise = new WeakMap();
1477
2213
  _tickPromiseResolver = new WeakMap();
1478
2214
  _tickPromiseRejecter = new WeakMap();
1479
2215
  _messageChain = new WeakMap();
1480
2216
  _snapshotTracker = new WeakMap();
1481
- _activeSnapshotRequests = new WeakMap();
1482
- _midStreamPromise = new WeakMap();
1483
- _midStreamPromiseResolver = new WeakMap();
1484
- _lastSeenCursor = new WeakMap();
2217
+ _pauseLock = new WeakMap();
1485
2218
  _currentFetchUrl = new WeakMap();
1486
2219
  _lastSseConnectionStartTime = new WeakMap();
1487
2220
  _minSseConnectionDuration = new WeakMap();
1488
- _consecutiveShortSseConnections = new WeakMap();
1489
2221
  _maxShortSseConnections = new WeakMap();
1490
- _sseFallbackToLongPolling = new WeakMap();
1491
2222
  _sseBackoffBaseDelay = new WeakMap();
1492
2223
  _sseBackoffMaxDelay = new WeakMap();
1493
2224
  _unsubscribeFromVisibilityChanges = new WeakMap();
1494
2225
  _unsubscribeFromWakeDetection = new WeakMap();
1495
- _staleCacheBuster = new WeakMap();
1496
- _staleCacheRetryCount = new WeakMap();
1497
2226
  _maxStaleCacheRetries = new WeakMap();
1498
- _ShapeStream_instances = new WeakSet();
1499
- replayMode_get = function() {
1500
- return __privateGet(this, _lastSeenCursor) !== void 0;
1501
- };
1502
2227
  start_fn = async function() {
1503
- var _a, _b, _c, _d, _e, _f, _g, _h;
2228
+ var _a, _b;
1504
2229
  __privateSet(this, _started, true);
1505
2230
  try {
1506
2231
  await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1507
2232
  } catch (err) {
1508
2233
  __privateSet(this, _error, err);
2234
+ if (err instanceof Error) {
2235
+ __privateSet(this, _syncState, __privateGet(this, _syncState).toErrorState(err));
2236
+ }
1509
2237
  if (__privateGet(this, _onError)) {
1510
2238
  const retryOpts = await __privateGet(this, _onError).call(this, err);
1511
2239
  const isRetryable = !(err instanceof MissingHeadersError);
@@ -1517,6 +2245,9 @@ start_fn = async function() {
1517
2245
  this.options.headers = __spreadValues(__spreadValues({}, (_b = this.options.headers) != null ? _b : {}), retryOpts.headers);
1518
2246
  }
1519
2247
  __privateSet(this, _error, null);
2248
+ if (__privateGet(this, _syncState) instanceof ErrorState) {
2249
+ __privateSet(this, _syncState, __privateGet(this, _syncState).retry());
2250
+ }
1520
2251
  __privateSet(this, _started, false);
1521
2252
  await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1522
2253
  return;
@@ -1524,38 +2255,45 @@ start_fn = async function() {
1524
2255
  if (err instanceof Error) {
1525
2256
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1526
2257
  }
1527
- __privateSet(this, _connected, false);
1528
- (_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
1529
- (_d = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _d.call(this);
2258
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
1530
2259
  return;
1531
2260
  }
1532
2261
  if (err instanceof Error) {
1533
2262
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1534
2263
  }
1535
- __privateSet(this, _connected, false);
1536
- (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
1537
- (_f = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _f.call(this);
2264
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
1538
2265
  throw err;
1539
2266
  }
2267
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
2268
+ };
2269
+ teardown_fn = function() {
2270
+ var _a, _b;
1540
2271
  __privateSet(this, _connected, false);
1541
- (_g = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _g.call(this);
1542
- (_h = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _h.call(this);
2272
+ (_a = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _a.call(this);
2273
+ (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1543
2274
  };
1544
2275
  requestShape_fn = async function() {
1545
2276
  var _a, _b;
1546
- if (__privateGet(this, _state) === `pause-requested`) {
1547
- __privateSet(this, _state, `paused`);
2277
+ if (__privateGet(this, _pauseLock).isPaused) return;
2278
+ if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
1548
2279
  return;
1549
2280
  }
1550
- if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _isUpToDate))) {
1551
- return;
2281
+ let resumingFromPause = false;
2282
+ if (__privateGet(this, _syncState) instanceof PausedState) {
2283
+ resumingFromPause = true;
2284
+ __privateSet(this, _syncState, __privateGet(this, _syncState).resume());
1552
2285
  }
1553
- const resumingFromPause = __privateGet(this, _state) === `paused`;
1554
- __privateSet(this, _state, `active`);
1555
2286
  const { url, signal } = this.options;
1556
2287
  const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
1557
2288
  const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
1558
2289
  const requestAbortController = __privateGet(this, _requestAbortController);
2290
+ if (__privateGet(this, _pauseLock).isPaused) {
2291
+ if (abortListener && signal) {
2292
+ signal.removeEventListener(`abort`, abortListener);
2293
+ }
2294
+ __privateSet(this, _requestAbortController, void 0);
2295
+ return;
2296
+ }
1559
2297
  try {
1560
2298
  await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
1561
2299
  fetchUrl,
@@ -1570,10 +2308,6 @@ requestShape_fn = async function() {
1570
2308
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1571
2309
  }
1572
2310
  if (e instanceof FetchBackoffAbortError) {
1573
- const currentState = __privateGet(this, _state);
1574
- if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM && currentState === `pause-requested`) {
1575
- __privateSet(this, _state, `paused`);
1576
- }
1577
2311
  return;
1578
2312
  }
1579
2313
  if (e instanceof StaleCacheError) {
@@ -1581,11 +2315,11 @@ requestShape_fn = async function() {
1581
2315
  }
1582
2316
  if (!(e instanceof FetchError)) throw e;
1583
2317
  if (e.status == 409) {
1584
- if (__privateGet(this, _shapeHandle)) {
2318
+ if (__privateGet(this, _syncState).handle) {
1585
2319
  const shapeKey = canonicalShapeKey(fetchUrl);
1586
- expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
2320
+ expiredShapesCache.markExpired(shapeKey, __privateGet(this, _syncState).handle);
1587
2321
  }
1588
- const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
2322
+ const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _syncState).handle}-next`;
1589
2323
  __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
1590
2324
  await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, Array.isArray(e.json) ? e.json : [e.json]);
1591
2325
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
@@ -1691,32 +2425,18 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
1691
2425
  setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
1692
2426
  }
1693
2427
  }
1694
- fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
2428
+ __privateGet(this, _syncState).applyUrlParams(fetchUrl, {
2429
+ isSnapshotRequest: subsetParams !== void 0,
2430
+ // Don't long-poll when resuming from pause or refreshing — avoids
2431
+ // a 20s hold during which `isConnected` would be false
2432
+ canLongPoll: !__privateGet(this, _ShapeStream_instances, isRefreshing_get) && !resumingFromPause
2433
+ });
1695
2434
  fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
1696
- const isSnapshotRequest = subsetParams !== void 0;
1697
- if (__privateGet(this, _isUpToDate) && !isSnapshotRequest) {
1698
- if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
1699
- fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
1700
- }
1701
- fetchUrl.searchParams.set(
1702
- LIVE_CACHE_BUSTER_QUERY_PARAM,
1703
- __privateGet(this, _liveCacheBuster)
1704
- );
1705
- }
1706
- if (__privateGet(this, _shapeHandle)) {
1707
- fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
1708
- }
1709
2435
  const shapeKey = canonicalShapeKey(fetchUrl);
1710
2436
  const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
1711
2437
  if (expiredHandle) {
1712
2438
  fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
1713
2439
  }
1714
- if (__privateGet(this, _staleCacheBuster)) {
1715
- fetchUrl.searchParams.set(
1716
- CACHE_BUSTER_QUERY_PARAM,
1717
- __privateGet(this, _staleCacheBuster)
1718
- );
1719
- }
1720
2440
  fetchUrl.searchParams.sort();
1721
2441
  return {
1722
2442
  fetchUrl,
@@ -1739,108 +2459,100 @@ createAbortListener_fn = async function(signal) {
1739
2459
  }
1740
2460
  };
1741
2461
  onInitialResponse_fn = async function(response) {
1742
- var _a, _b, _c, _d;
2462
+ var _a, _b, _c;
1743
2463
  const { headers, status } = response;
1744
2464
  const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
1745
- if (shapeHandle) {
1746
- const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
1747
- const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
1748
- if (shapeHandle !== expiredHandle) {
1749
- __privateSet(this, _shapeHandle, shapeHandle);
1750
- if (__privateGet(this, _staleCacheBuster)) {
1751
- __privateSet(this, _staleCacheBuster, void 0);
1752
- __privateSet(this, _staleCacheRetryCount, 0);
1753
- }
1754
- } else if (__privateGet(this, _shapeHandle) === void 0) {
1755
- __privateWrapper(this, _staleCacheRetryCount)._++;
1756
- await ((_a = response.body) == null ? void 0 : _a.cancel());
1757
- if (__privateGet(this, _staleCacheRetryCount) > __privateGet(this, _maxStaleCacheRetries)) {
1758
- throw new FetchError(
1759
- 502,
1760
- void 0,
1761
- void 0,
1762
- {},
1763
- (_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
1764
- `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`
1765
- );
1766
- }
1767
- console.warn(
1768
- `[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)}).`
1769
- );
1770
- __privateSet(this, _staleCacheBuster, `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
1771
- throw new StaleCacheError(
1772
- `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.`
1773
- );
1774
- } else {
1775
- console.warn(
1776
- `[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)}".`
2465
+ const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
2466
+ const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
2467
+ const transition = __privateGet(this, _syncState).handleResponseMetadata({
2468
+ status,
2469
+ responseHandle: shapeHandle,
2470
+ responseOffset: headers.get(CHUNK_LAST_OFFSET_HEADER),
2471
+ responseCursor: headers.get(LIVE_CACHE_BUSTER_HEADER),
2472
+ responseSchema: getSchemaFromHeaders(headers),
2473
+ expiredHandle,
2474
+ now: Date.now(),
2475
+ maxStaleCacheRetries: __privateGet(this, _maxStaleCacheRetries),
2476
+ createCacheBuster: () => `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
2477
+ });
2478
+ __privateSet(this, _syncState, transition.state);
2479
+ if (transition.action === `stale-retry`) {
2480
+ await ((_a = response.body) == null ? void 0 : _a.cancel());
2481
+ if (transition.exceededMaxRetries) {
2482
+ throw new FetchError(
2483
+ 502,
2484
+ void 0,
2485
+ void 0,
2486
+ {},
2487
+ (_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
2488
+ `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`
1777
2489
  );
1778
- return;
1779
2490
  }
2491
+ console.warn(
2492
+ `[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)}).`
2493
+ );
2494
+ throw new StaleCacheError(
2495
+ `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.`
2496
+ );
1780
2497
  }
1781
- const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
1782
- if (lastOffset) {
1783
- __privateSet(this, _lastOffset, lastOffset);
1784
- }
1785
- const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
1786
- if (liveCacheBuster) {
1787
- __privateSet(this, _liveCacheBuster, liveCacheBuster);
1788
- }
1789
- __privateSet(this, _schema, (_d = __privateGet(this, _schema)) != null ? _d : getSchemaFromHeaders(headers));
1790
- if (status === 204) {
1791
- __privateSet(this, _lastSyncedAt, Date.now());
2498
+ if (transition.action === `ignored`) {
2499
+ console.warn(
2500
+ `[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}".`
2501
+ );
2502
+ return false;
1792
2503
  }
2504
+ return true;
1793
2505
  };
1794
2506
  onMessages_fn = async function(batch, isSseMessage = false) {
1795
- var _a;
1796
- if (batch.length > 0) {
1797
- __privateSet(this, _isMidStream, true);
1798
- const lastMessage = batch[batch.length - 1];
1799
- if (isUpToDateMessage(lastMessage)) {
1800
- if (isSseMessage) {
1801
- const offset = getOffset(lastMessage);
1802
- if (offset) {
1803
- __privateSet(this, _lastOffset, offset);
1804
- }
1805
- }
1806
- __privateSet(this, _lastSyncedAt, Date.now());
1807
- __privateSet(this, _isUpToDate, true);
1808
- __privateSet(this, _isMidStream, false);
1809
- (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
1810
- if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
1811
- const currentCursor = __privateGet(this, _liveCacheBuster);
1812
- if (currentCursor === __privateGet(this, _lastSeenCursor)) {
1813
- __privateSet(this, _lastSeenCursor, void 0);
1814
- return;
1815
- }
1816
- }
1817
- __privateSet(this, _lastSeenCursor, void 0);
1818
- if (__privateGet(this, _currentFetchUrl)) {
1819
- const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
1820
- upToDateTracker.recordUpToDate(shapeKey, __privateGet(this, _liveCacheBuster));
1821
- }
2507
+ if (batch.length === 0) return;
2508
+ const lastMessage = batch[batch.length - 1];
2509
+ const hasUpToDateMessage = isUpToDateMessage(lastMessage);
2510
+ const upToDateOffset = hasUpToDateMessage ? getOffset(lastMessage) : void 0;
2511
+ const transition = __privateGet(this, _syncState).handleMessageBatch({
2512
+ hasMessages: true,
2513
+ hasUpToDateMessage,
2514
+ isSse: isSseMessage,
2515
+ upToDateOffset,
2516
+ now: Date.now(),
2517
+ currentCursor: __privateGet(this, _syncState).liveCacheBuster
2518
+ });
2519
+ __privateSet(this, _syncState, transition.state);
2520
+ if (hasUpToDateMessage) {
2521
+ if (transition.suppressBatch) {
2522
+ return;
2523
+ }
2524
+ if (__privateGet(this, _currentFetchUrl)) {
2525
+ const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
2526
+ upToDateTracker.recordUpToDate(
2527
+ shapeKey,
2528
+ __privateGet(this, _syncState).liveCacheBuster
2529
+ );
1822
2530
  }
1823
- const messagesToProcess = batch.filter((message) => {
1824
- if (isChangeMessage(message)) {
1825
- return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
1826
- }
1827
- return true;
1828
- });
1829
- await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
1830
2531
  }
2532
+ const messagesToProcess = batch.filter((message) => {
2533
+ if (isChangeMessage(message)) {
2534
+ return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
2535
+ }
2536
+ return true;
2537
+ });
2538
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
1831
2539
  };
1832
2540
  fetchShape_fn = async function(opts) {
1833
2541
  var _a;
1834
2542
  __privateSet(this, _currentFetchUrl, opts.fetchUrl);
1835
- if (!__privateGet(this, _isUpToDate) && !__privateGet(this, _ShapeStream_instances, replayMode_get)) {
2543
+ if (!__privateGet(this, _syncState).isUpToDate && __privateGet(this, _syncState).canEnterReplayMode()) {
1836
2544
  const shapeKey = canonicalShapeKey(opts.fetchUrl);
1837
2545
  const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey);
1838
2546
  if (lastSeenCursor) {
1839
- __privateSet(this, _lastSeenCursor, lastSeenCursor);
2547
+ __privateSet(this, _syncState, __privateGet(this, _syncState).enterReplayMode(lastSeenCursor));
1840
2548
  }
1841
2549
  }
1842
2550
  const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
1843
- if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
2551
+ if (__privateGet(this, _syncState).shouldUseSse({
2552
+ liveSseEnabled: !!useSse,
2553
+ isRefreshing: __privateGet(this, _ShapeStream_instances, isRefreshing_get),
2554
+ resumingFromPause: !!opts.resumingFromPause
2555
+ })) {
1844
2556
  opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
1845
2557
  opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`);
1846
2558
  return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
@@ -1854,8 +2566,9 @@ requestShapeLongPoll_fn = async function(opts) {
1854
2566
  headers
1855
2567
  });
1856
2568
  __privateSet(this, _connected, true);
1857
- await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
1858
- const schema = __privateGet(this, _schema);
2569
+ const shouldProcessBody = await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2570
+ if (!shouldProcessBody) return;
2571
+ const schema = __privateGet(this, _syncState).schema;
1859
2572
  const res = await response.text();
1860
2573
  const messages = res || `[]`;
1861
2574
  const batch = __privateGet(this, _messageParser).parse(messages, schema);
@@ -1868,18 +2581,23 @@ requestShapeSSE_fn = async function(opts) {
1868
2581
  const sseHeaders = __spreadProps(__spreadValues({}, headers), {
1869
2582
  Accept: `text/event-stream`
1870
2583
  });
2584
+ let ignoredStaleResponse = false;
1871
2585
  try {
1872
2586
  let buffer = [];
1873
- await (0, import_fetch_event_source.fetchEventSource)(fetchUrl.toString(), {
2587
+ await fetchEventSource(fetchUrl.toString(), {
1874
2588
  headers: sseHeaders,
1875
2589
  fetch: fetch2,
1876
2590
  onopen: async (response) => {
1877
2591
  __privateSet(this, _connected, true);
1878
- await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2592
+ const shouldProcessBody = await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2593
+ if (!shouldProcessBody) {
2594
+ ignoredStaleResponse = true;
2595
+ throw new Error(`stale response ignored`);
2596
+ }
1879
2597
  },
1880
2598
  onmessage: (event) => {
1881
2599
  if (event.data) {
1882
- const schema = __privateGet(this, _schema);
2600
+ const schema = __privateGet(this, _syncState).schema;
1883
2601
  const message = __privateGet(this, _messageParser).parse(
1884
2602
  event.data,
1885
2603
  schema
@@ -1897,6 +2615,9 @@ requestShapeSSE_fn = async function(opts) {
1897
2615
  signal: requestAbortController.signal
1898
2616
  });
1899
2617
  } catch (error) {
2618
+ if (ignoredStaleResponse) {
2619
+ return;
2620
+ }
1900
2621
  if (requestAbortController.signal.aborted) {
1901
2622
  throw new FetchBackoffAbortError();
1902
2623
  }
@@ -1904,46 +2625,33 @@ requestShapeSSE_fn = async function(opts) {
1904
2625
  } finally {
1905
2626
  const connectionDuration = Date.now() - __privateGet(this, _lastSseConnectionStartTime);
1906
2627
  const wasAborted = requestAbortController.signal.aborted;
1907
- if (connectionDuration < __privateGet(this, _minSseConnectionDuration) && !wasAborted) {
1908
- __privateWrapper(this, _consecutiveShortSseConnections)._++;
1909
- if (__privateGet(this, _consecutiveShortSseConnections) >= __privateGet(this, _maxShortSseConnections)) {
1910
- __privateSet(this, _sseFallbackToLongPolling, true);
1911
- console.warn(
1912
- `[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.`
1913
- );
1914
- } else {
1915
- const maxDelay = Math.min(
1916
- __privateGet(this, _sseBackoffMaxDelay),
1917
- __privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _consecutiveShortSseConnections))
1918
- );
1919
- const delayMs = Math.floor(Math.random() * maxDelay);
1920
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1921
- }
1922
- } else if (connectionDuration >= __privateGet(this, _minSseConnectionDuration)) {
1923
- __privateSet(this, _consecutiveShortSseConnections, 0);
1924
- }
1925
- }
1926
- };
1927
- pause_fn = function() {
1928
- var _a;
1929
- if (__privateGet(this, _started) && __privateGet(this, _state) === `active`) {
1930
- __privateSet(this, _state, `pause-requested`);
1931
- (_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.abort(PAUSE_STREAM);
1932
- }
1933
- };
1934
- resume_fn = function() {
1935
- var _a;
1936
- if (__privateGet(this, _started) && (__privateGet(this, _state) === `paused` || __privateGet(this, _state) === `pause-requested`)) {
1937
- if ((_a = this.options.signal) == null ? void 0 : _a.aborted) {
1938
- return;
1939
- }
1940
- if (__privateGet(this, _state) === `pause-requested`) {
1941
- __privateSet(this, _state, `active`);
2628
+ const transition = __privateGet(this, _syncState).handleSseConnectionClosed({
2629
+ connectionDuration,
2630
+ wasAborted,
2631
+ minConnectionDuration: __privateGet(this, _minSseConnectionDuration),
2632
+ maxShortConnections: __privateGet(this, _maxShortSseConnections)
2633
+ });
2634
+ __privateSet(this, _syncState, transition.state);
2635
+ if (transition.fellBackToLongPolling) {
2636
+ console.warn(
2637
+ `[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.`
2638
+ );
2639
+ } else if (transition.wasShortConnection) {
2640
+ const maxDelay = Math.min(
2641
+ __privateGet(this, _sseBackoffMaxDelay),
2642
+ __privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _syncState).consecutiveShortSseConnections)
2643
+ );
2644
+ const delayMs = Math.floor(Math.random() * maxDelay);
2645
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1942
2646
  }
1943
- __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1944
2647
  }
1945
2648
  };
1946
2649
  nextTick_fn = async function() {
2650
+ if (__privateGet(this, _pauseLock).isPaused) {
2651
+ throw new Error(
2652
+ `Cannot wait for next tick while PauseLock is held \u2014 this would deadlock because the request loop is paused`
2653
+ );
2654
+ }
1947
2655
  if (__privateGet(this, _tickPromise)) {
1948
2656
  return __privateGet(this, _tickPromise);
1949
2657
  }
@@ -1958,22 +2666,6 @@ nextTick_fn = async function() {
1958
2666
  });
1959
2667
  return __privateGet(this, _tickPromise);
1960
2668
  };
1961
- waitForStreamEnd_fn = async function() {
1962
- if (!__privateGet(this, _isMidStream)) {
1963
- return;
1964
- }
1965
- if (__privateGet(this, _midStreamPromise)) {
1966
- return __privateGet(this, _midStreamPromise);
1967
- }
1968
- __privateSet(this, _midStreamPromise, new Promise((resolve) => {
1969
- __privateSet(this, _midStreamPromiseResolver, resolve);
1970
- }));
1971
- __privateGet(this, _midStreamPromise).finally(() => {
1972
- __privateSet(this, _midStreamPromise, void 0);
1973
- __privateSet(this, _midStreamPromiseResolver, void 0);
1974
- });
1975
- return __privateGet(this, _midStreamPromise);
1976
- };
1977
2669
  publish_fn = async function(messages) {
1978
2670
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
1979
2671
  () => Promise.all(
@@ -2002,9 +2694,9 @@ subscribeToVisibilityChanges_fn = function() {
2002
2694
  if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) {
2003
2695
  const visibilityHandler = () => {
2004
2696
  if (document.hidden) {
2005
- __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
2697
+ __privateGet(this, _pauseLock).acquire(`visibility`);
2006
2698
  } else {
2007
- __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
2699
+ __privateGet(this, _pauseLock).release(`visibility`);
2008
2700
  }
2009
2701
  };
2010
2702
  document.addEventListener(`visibilitychange`, visibilityHandler);
@@ -2035,11 +2727,11 @@ subscribeToWakeDetection_fn = function() {
2035
2727
  const elapsed = now - lastTickTime;
2036
2728
  lastTickTime = now;
2037
2729
  if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {
2038
- if (__privateGet(this, _state) === `active` && __privateGet(this, _requestAbortController)) {
2039
- __privateSet(this, _isRefreshing, true);
2730
+ if (!__privateGet(this, _pauseLock).isPaused && __privateGet(this, _requestAbortController)) {
2731
+ __privateWrapper(this, _refreshCount)._++;
2040
2732
  __privateGet(this, _requestAbortController).abort(SYSTEM_WAKE);
2041
2733
  queueMicrotask(() => {
2042
- __privateSet(this, _isRefreshing, false);
2734
+ __privateWrapper(this, _refreshCount)._--;
2043
2735
  });
2044
2736
  }
2045
2737
  }
@@ -2056,18 +2748,9 @@ subscribeToWakeDetection_fn = function() {
2056
2748
  * shape handle
2057
2749
  */
2058
2750
  reset_fn = function(handle) {
2059
- __privateSet(this, _lastOffset, `-1`);
2060
- __privateSet(this, _liveCacheBuster, ``);
2061
- __privateSet(this, _shapeHandle, handle);
2062
- __privateSet(this, _isUpToDate, false);
2063
- __privateSet(this, _isMidStream, true);
2751
+ __privateSet(this, _syncState, __privateGet(this, _syncState).markMustRefetch(handle));
2064
2752
  __privateSet(this, _connected, false);
2065
- __privateSet(this, _schema, void 0);
2066
- __privateSet(this, _activeSnapshotRequests, 0);
2067
- __privateSet(this, _consecutiveShortSseConnections, 0);
2068
- __privateSet(this, _sseFallbackToLongPolling, false);
2069
- __privateSet(this, _staleCacheBuster, void 0);
2070
- __privateSet(this, _staleCacheRetryCount, 0);
2753
+ __privateGet(this, _pauseLock).releaseAllMatching(`snapshot`);
2071
2754
  };
2072
2755
  buildSubsetBody_fn = function(opts) {
2073
2756
  var _a, _b, _c, _d;