@electric-sql/client 1.5.1 → 1.5.3

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.
@@ -504,6 +504,7 @@ var EXPERIMENTAL_LIVE_SSE_QUERY_PARAM = `experimental_live_sse`;
504
504
  var LIVE_SSE_QUERY_PARAM = `live_sse`;
505
505
  var FORCE_DISCONNECT_AND_REFRESH = `force-disconnect-and-refresh`;
506
506
  var PAUSE_STREAM = `pause-stream`;
507
+ var SYSTEM_WAKE = `system-wake`;
507
508
  var LOG_MODE_QUERY_PARAM = `log`;
508
509
  var SUBSET_PARAM_WHERE = `subset__where`;
509
510
  var SUBSET_PARAM_LIMIT = `subset__limit`;
@@ -1129,6 +1130,544 @@ var SnapshotTracker = class {
1129
1130
  }
1130
1131
  };
1131
1132
 
1133
+ // src/shape-stream-state.ts
1134
+ var ShapeStreamState = class {
1135
+ // --- Derived booleans ---
1136
+ get isUpToDate() {
1137
+ return false;
1138
+ }
1139
+ // --- Per-state field defaults ---
1140
+ get staleCacheBuster() {
1141
+ return void 0;
1142
+ }
1143
+ get staleCacheRetryCount() {
1144
+ return 0;
1145
+ }
1146
+ get sseFallbackToLongPolling() {
1147
+ return false;
1148
+ }
1149
+ get consecutiveShortSseConnections() {
1150
+ return 0;
1151
+ }
1152
+ get replayCursor() {
1153
+ return void 0;
1154
+ }
1155
+ // --- Default no-op methods ---
1156
+ canEnterReplayMode() {
1157
+ return false;
1158
+ }
1159
+ enterReplayMode(_cursor) {
1160
+ return this;
1161
+ }
1162
+ shouldUseSse(_opts) {
1163
+ return false;
1164
+ }
1165
+ handleSseConnectionClosed(_input) {
1166
+ return {
1167
+ state: this,
1168
+ fellBackToLongPolling: false,
1169
+ wasShortConnection: false
1170
+ };
1171
+ }
1172
+ // --- URL param application ---
1173
+ /** Adds state-specific query parameters to the fetch URL. */
1174
+ applyUrlParams(_url, _context) {
1175
+ }
1176
+ // --- Default response/message handlers (Paused/Error never receive these) ---
1177
+ handleResponseMetadata(_input) {
1178
+ return { action: `ignored`, state: this };
1179
+ }
1180
+ handleMessageBatch(_input) {
1181
+ return { state: this, suppressBatch: false, becameUpToDate: false };
1182
+ }
1183
+ pause() {
1184
+ return new PausedState(this);
1185
+ }
1186
+ toErrorState(error) {
1187
+ return new ErrorState(this, error);
1188
+ }
1189
+ markMustRefetch(handle) {
1190
+ return new InitialState({
1191
+ handle,
1192
+ offset: `-1`,
1193
+ liveCacheBuster: ``,
1194
+ lastSyncedAt: this.lastSyncedAt,
1195
+ schema: void 0
1196
+ });
1197
+ }
1198
+ };
1199
+ var _shared;
1200
+ var ActiveState = class extends ShapeStreamState {
1201
+ constructor(shared) {
1202
+ super();
1203
+ __privateAdd(this, _shared);
1204
+ __privateSet(this, _shared, shared);
1205
+ }
1206
+ get handle() {
1207
+ return __privateGet(this, _shared).handle;
1208
+ }
1209
+ get offset() {
1210
+ return __privateGet(this, _shared).offset;
1211
+ }
1212
+ get schema() {
1213
+ return __privateGet(this, _shared).schema;
1214
+ }
1215
+ get liveCacheBuster() {
1216
+ return __privateGet(this, _shared).liveCacheBuster;
1217
+ }
1218
+ get lastSyncedAt() {
1219
+ return __privateGet(this, _shared).lastSyncedAt;
1220
+ }
1221
+ /** Expose shared fields to subclasses for spreading into new instances. */
1222
+ get currentFields() {
1223
+ return __privateGet(this, _shared);
1224
+ }
1225
+ // --- URL param application ---
1226
+ applyUrlParams(url, _context) {
1227
+ url.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _shared).offset);
1228
+ if (__privateGet(this, _shared).handle) {
1229
+ url.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shared).handle);
1230
+ }
1231
+ }
1232
+ // --- Helpers for subclass handleResponseMetadata implementations ---
1233
+ /** Extracts updated SharedStateFields from response headers. */
1234
+ parseResponseFields(input) {
1235
+ var _a, _b, _c;
1236
+ const responseHandle = input.responseHandle;
1237
+ const handle = responseHandle && responseHandle !== input.expiredHandle ? responseHandle : __privateGet(this, _shared).handle;
1238
+ const offset = (_a = input.responseOffset) != null ? _a : __privateGet(this, _shared).offset;
1239
+ const liveCacheBuster = (_b = input.responseCursor) != null ? _b : __privateGet(this, _shared).liveCacheBuster;
1240
+ const schema = (_c = __privateGet(this, _shared).schema) != null ? _c : input.responseSchema;
1241
+ const lastSyncedAt = input.status === 204 ? input.now : __privateGet(this, _shared).lastSyncedAt;
1242
+ return { handle, offset, schema, liveCacheBuster, lastSyncedAt };
1243
+ }
1244
+ /**
1245
+ * Stale detection. Returns a transition if the response is stale,
1246
+ * or null if it is not stale and the caller should proceed normally.
1247
+ */
1248
+ checkStaleResponse(input) {
1249
+ const responseHandle = input.responseHandle;
1250
+ const expiredHandle = input.expiredHandle;
1251
+ if (!responseHandle || responseHandle !== expiredHandle) {
1252
+ return null;
1253
+ }
1254
+ if (__privateGet(this, _shared).handle === void 0 || __privateGet(this, _shared).handle === expiredHandle) {
1255
+ const retryCount = this.staleCacheRetryCount + 1;
1256
+ return {
1257
+ action: `stale-retry`,
1258
+ state: new StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
1259
+ staleCacheBuster: input.createCacheBuster(),
1260
+ staleCacheRetryCount: retryCount
1261
+ })),
1262
+ exceededMaxRetries: retryCount > input.maxStaleCacheRetries
1263
+ };
1264
+ }
1265
+ return { action: `ignored`, state: this };
1266
+ }
1267
+ // --- handleMessageBatch: template method with onUpToDate override point ---
1268
+ handleMessageBatch(input) {
1269
+ if (!input.hasMessages || !input.hasUpToDateMessage) {
1270
+ return { state: this, suppressBatch: false, becameUpToDate: false };
1271
+ }
1272
+ let offset = __privateGet(this, _shared).offset;
1273
+ if (input.isSse && input.upToDateOffset) {
1274
+ offset = input.upToDateOffset;
1275
+ }
1276
+ const shared = {
1277
+ handle: __privateGet(this, _shared).handle,
1278
+ offset,
1279
+ schema: __privateGet(this, _shared).schema,
1280
+ liveCacheBuster: __privateGet(this, _shared).liveCacheBuster,
1281
+ lastSyncedAt: input.now
1282
+ };
1283
+ return this.onUpToDate(shared, input);
1284
+ }
1285
+ /** Override point for up-to-date handling. Default → LiveState. */
1286
+ onUpToDate(shared, _input) {
1287
+ return {
1288
+ state: new LiveState(shared),
1289
+ suppressBatch: false,
1290
+ becameUpToDate: true
1291
+ };
1292
+ }
1293
+ };
1294
+ _shared = new WeakMap();
1295
+ var FetchingState = class extends ActiveState {
1296
+ handleResponseMetadata(input) {
1297
+ const staleResult = this.checkStaleResponse(input);
1298
+ if (staleResult) return staleResult;
1299
+ const shared = this.parseResponseFields(input);
1300
+ return { action: `accepted`, state: new SyncingState(shared) };
1301
+ }
1302
+ canEnterReplayMode() {
1303
+ return true;
1304
+ }
1305
+ enterReplayMode(cursor) {
1306
+ return new ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
1307
+ replayCursor: cursor
1308
+ }));
1309
+ }
1310
+ };
1311
+ var InitialState = class _InitialState extends FetchingState {
1312
+ constructor(shared) {
1313
+ super(shared);
1314
+ this.kind = `initial`;
1315
+ }
1316
+ withHandle(handle) {
1317
+ return new _InitialState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
1318
+ }
1319
+ };
1320
+ var SyncingState = class _SyncingState extends FetchingState {
1321
+ constructor(shared) {
1322
+ super(shared);
1323
+ this.kind = `syncing`;
1324
+ }
1325
+ withHandle(handle) {
1326
+ return new _SyncingState(__spreadProps(__spreadValues({}, this.currentFields), { handle }));
1327
+ }
1328
+ };
1329
+ var _staleCacheBuster, _staleCacheRetryCount;
1330
+ var _StaleRetryState = class _StaleRetryState extends FetchingState {
1331
+ constructor(fields) {
1332
+ const _a = fields, { staleCacheBuster, staleCacheRetryCount } = _a, shared = __objRest(_a, ["staleCacheBuster", "staleCacheRetryCount"]);
1333
+ super(shared);
1334
+ this.kind = `stale-retry`;
1335
+ __privateAdd(this, _staleCacheBuster);
1336
+ __privateAdd(this, _staleCacheRetryCount);
1337
+ __privateSet(this, _staleCacheBuster, staleCacheBuster);
1338
+ __privateSet(this, _staleCacheRetryCount, staleCacheRetryCount);
1339
+ }
1340
+ get staleCacheBuster() {
1341
+ return __privateGet(this, _staleCacheBuster);
1342
+ }
1343
+ get staleCacheRetryCount() {
1344
+ return __privateGet(this, _staleCacheRetryCount);
1345
+ }
1346
+ // StaleRetryState must not enter replay mode — it would lose the retry count
1347
+ canEnterReplayMode() {
1348
+ return false;
1349
+ }
1350
+ withHandle(handle) {
1351
+ return new _StaleRetryState(__spreadProps(__spreadValues({}, this.currentFields), {
1352
+ handle,
1353
+ staleCacheBuster: __privateGet(this, _staleCacheBuster),
1354
+ staleCacheRetryCount: __privateGet(this, _staleCacheRetryCount)
1355
+ }));
1356
+ }
1357
+ applyUrlParams(url, context) {
1358
+ super.applyUrlParams(url, context);
1359
+ url.searchParams.set(CACHE_BUSTER_QUERY_PARAM, __privateGet(this, _staleCacheBuster));
1360
+ }
1361
+ };
1362
+ _staleCacheBuster = new WeakMap();
1363
+ _staleCacheRetryCount = new WeakMap();
1364
+ var StaleRetryState = _StaleRetryState;
1365
+ var _consecutiveShortSseConnections, _sseFallbackToLongPolling;
1366
+ var _LiveState = class _LiveState extends ActiveState {
1367
+ constructor(shared, sseState) {
1368
+ var _a, _b;
1369
+ super(shared);
1370
+ this.kind = `live`;
1371
+ __privateAdd(this, _consecutiveShortSseConnections);
1372
+ __privateAdd(this, _sseFallbackToLongPolling);
1373
+ __privateSet(this, _consecutiveShortSseConnections, (_a = sseState == null ? void 0 : sseState.consecutiveShortSseConnections) != null ? _a : 0);
1374
+ __privateSet(this, _sseFallbackToLongPolling, (_b = sseState == null ? void 0 : sseState.sseFallbackToLongPolling) != null ? _b : false);
1375
+ }
1376
+ get isUpToDate() {
1377
+ return true;
1378
+ }
1379
+ get consecutiveShortSseConnections() {
1380
+ return __privateGet(this, _consecutiveShortSseConnections);
1381
+ }
1382
+ get sseFallbackToLongPolling() {
1383
+ return __privateGet(this, _sseFallbackToLongPolling);
1384
+ }
1385
+ withHandle(handle) {
1386
+ return new _LiveState(__spreadProps(__spreadValues({}, this.currentFields), { handle }), this.sseState);
1387
+ }
1388
+ applyUrlParams(url, context) {
1389
+ super.applyUrlParams(url, context);
1390
+ if (!context.isSnapshotRequest) {
1391
+ url.searchParams.set(LIVE_CACHE_BUSTER_QUERY_PARAM, this.liveCacheBuster);
1392
+ if (context.canLongPoll) {
1393
+ url.searchParams.set(LIVE_QUERY_PARAM, `true`);
1394
+ }
1395
+ }
1396
+ }
1397
+ get sseState() {
1398
+ return {
1399
+ consecutiveShortSseConnections: __privateGet(this, _consecutiveShortSseConnections),
1400
+ sseFallbackToLongPolling: __privateGet(this, _sseFallbackToLongPolling)
1401
+ };
1402
+ }
1403
+ handleResponseMetadata(input) {
1404
+ const staleResult = this.checkStaleResponse(input);
1405
+ if (staleResult) return staleResult;
1406
+ const shared = this.parseResponseFields(input);
1407
+ return {
1408
+ action: `accepted`,
1409
+ state: new _LiveState(shared, this.sseState)
1410
+ };
1411
+ }
1412
+ onUpToDate(shared, _input) {
1413
+ return {
1414
+ state: new _LiveState(shared, this.sseState),
1415
+ suppressBatch: false,
1416
+ becameUpToDate: true
1417
+ };
1418
+ }
1419
+ shouldUseSse(opts) {
1420
+ return opts.liveSseEnabled && !opts.isRefreshing && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling);
1421
+ }
1422
+ handleSseConnectionClosed(input) {
1423
+ let nextConsecutiveShort = __privateGet(this, _consecutiveShortSseConnections);
1424
+ let nextFallback = __privateGet(this, _sseFallbackToLongPolling);
1425
+ let fellBackToLongPolling = false;
1426
+ let wasShortConnection = false;
1427
+ if (input.connectionDuration < input.minConnectionDuration && !input.wasAborted) {
1428
+ wasShortConnection = true;
1429
+ nextConsecutiveShort = nextConsecutiveShort + 1;
1430
+ if (nextConsecutiveShort >= input.maxShortConnections) {
1431
+ nextFallback = true;
1432
+ fellBackToLongPolling = true;
1433
+ }
1434
+ } else if (input.connectionDuration >= input.minConnectionDuration) {
1435
+ nextConsecutiveShort = 0;
1436
+ }
1437
+ return {
1438
+ state: new _LiveState(this.currentFields, {
1439
+ consecutiveShortSseConnections: nextConsecutiveShort,
1440
+ sseFallbackToLongPolling: nextFallback
1441
+ }),
1442
+ fellBackToLongPolling,
1443
+ wasShortConnection
1444
+ };
1445
+ }
1446
+ };
1447
+ _consecutiveShortSseConnections = new WeakMap();
1448
+ _sseFallbackToLongPolling = new WeakMap();
1449
+ var LiveState = _LiveState;
1450
+ var _replayCursor;
1451
+ var _ReplayingState = class _ReplayingState extends ActiveState {
1452
+ constructor(fields) {
1453
+ const _a = fields, { replayCursor } = _a, shared = __objRest(_a, ["replayCursor"]);
1454
+ super(shared);
1455
+ this.kind = `replaying`;
1456
+ __privateAdd(this, _replayCursor);
1457
+ __privateSet(this, _replayCursor, replayCursor);
1458
+ }
1459
+ get replayCursor() {
1460
+ return __privateGet(this, _replayCursor);
1461
+ }
1462
+ withHandle(handle) {
1463
+ return new _ReplayingState(__spreadProps(__spreadValues({}, this.currentFields), {
1464
+ handle,
1465
+ replayCursor: __privateGet(this, _replayCursor)
1466
+ }));
1467
+ }
1468
+ handleResponseMetadata(input) {
1469
+ const staleResult = this.checkStaleResponse(input);
1470
+ if (staleResult) return staleResult;
1471
+ const shared = this.parseResponseFields(input);
1472
+ return {
1473
+ action: `accepted`,
1474
+ state: new _ReplayingState(__spreadProps(__spreadValues({}, shared), {
1475
+ replayCursor: __privateGet(this, _replayCursor)
1476
+ }))
1477
+ };
1478
+ }
1479
+ onUpToDate(shared, input) {
1480
+ const suppressBatch = !input.isSse && __privateGet(this, _replayCursor) === input.currentCursor;
1481
+ return {
1482
+ state: new LiveState(shared),
1483
+ suppressBatch,
1484
+ becameUpToDate: true
1485
+ };
1486
+ }
1487
+ };
1488
+ _replayCursor = new WeakMap();
1489
+ var ReplayingState = _ReplayingState;
1490
+ var PausedState = class _PausedState extends ShapeStreamState {
1491
+ constructor(previousState) {
1492
+ super();
1493
+ this.kind = `paused`;
1494
+ this.previousState = previousState;
1495
+ }
1496
+ get handle() {
1497
+ return this.previousState.handle;
1498
+ }
1499
+ get offset() {
1500
+ return this.previousState.offset;
1501
+ }
1502
+ get schema() {
1503
+ return this.previousState.schema;
1504
+ }
1505
+ get liveCacheBuster() {
1506
+ return this.previousState.liveCacheBuster;
1507
+ }
1508
+ get lastSyncedAt() {
1509
+ return this.previousState.lastSyncedAt;
1510
+ }
1511
+ get isUpToDate() {
1512
+ return this.previousState.isUpToDate;
1513
+ }
1514
+ get staleCacheBuster() {
1515
+ return this.previousState.staleCacheBuster;
1516
+ }
1517
+ get staleCacheRetryCount() {
1518
+ return this.previousState.staleCacheRetryCount;
1519
+ }
1520
+ get sseFallbackToLongPolling() {
1521
+ return this.previousState.sseFallbackToLongPolling;
1522
+ }
1523
+ get consecutiveShortSseConnections() {
1524
+ return this.previousState.consecutiveShortSseConnections;
1525
+ }
1526
+ get replayCursor() {
1527
+ return this.previousState.replayCursor;
1528
+ }
1529
+ withHandle(handle) {
1530
+ return new _PausedState(this.previousState.withHandle(handle));
1531
+ }
1532
+ applyUrlParams(url, context) {
1533
+ this.previousState.applyUrlParams(url, context);
1534
+ }
1535
+ pause() {
1536
+ return this;
1537
+ }
1538
+ resume() {
1539
+ return this.previousState;
1540
+ }
1541
+ };
1542
+ var ErrorState = class _ErrorState extends ShapeStreamState {
1543
+ constructor(previousState, error) {
1544
+ super();
1545
+ this.kind = `error`;
1546
+ this.previousState = previousState;
1547
+ this.error = error;
1548
+ }
1549
+ get handle() {
1550
+ return this.previousState.handle;
1551
+ }
1552
+ get offset() {
1553
+ return this.previousState.offset;
1554
+ }
1555
+ get schema() {
1556
+ return this.previousState.schema;
1557
+ }
1558
+ get liveCacheBuster() {
1559
+ return this.previousState.liveCacheBuster;
1560
+ }
1561
+ get lastSyncedAt() {
1562
+ return this.previousState.lastSyncedAt;
1563
+ }
1564
+ get isUpToDate() {
1565
+ return this.previousState.isUpToDate;
1566
+ }
1567
+ withHandle(handle) {
1568
+ return new _ErrorState(this.previousState.withHandle(handle), this.error);
1569
+ }
1570
+ applyUrlParams(url, context) {
1571
+ this.previousState.applyUrlParams(url, context);
1572
+ }
1573
+ retry() {
1574
+ return this.previousState;
1575
+ }
1576
+ reset(handle) {
1577
+ return this.previousState.markMustRefetch(handle);
1578
+ }
1579
+ };
1580
+ function createInitialState(opts) {
1581
+ return new InitialState({
1582
+ handle: opts.handle,
1583
+ offset: opts.offset,
1584
+ liveCacheBuster: ``,
1585
+ lastSyncedAt: void 0,
1586
+ schema: void 0
1587
+ });
1588
+ }
1589
+
1590
+ // src/pause-lock.ts
1591
+ var _holders, _onAcquired, _onReleased;
1592
+ var PauseLock = class {
1593
+ constructor(callbacks) {
1594
+ __privateAdd(this, _holders, /* @__PURE__ */ new Set());
1595
+ __privateAdd(this, _onAcquired);
1596
+ __privateAdd(this, _onReleased);
1597
+ __privateSet(this, _onAcquired, callbacks.onAcquired);
1598
+ __privateSet(this, _onReleased, callbacks.onReleased);
1599
+ }
1600
+ /**
1601
+ * Acquire the lock for a given reason. Idempotent — acquiring the same
1602
+ * reason twice is a no-op (but logs a warning since it likely indicates
1603
+ * a caller bug).
1604
+ *
1605
+ * Fires `onAcquired` when the first reason is acquired (transition from
1606
+ * unlocked to locked).
1607
+ */
1608
+ acquire(reason) {
1609
+ if (__privateGet(this, _holders).has(reason)) {
1610
+ console.warn(
1611
+ `[Electric] PauseLock: "${reason}" already held \u2014 ignoring duplicate acquire`
1612
+ );
1613
+ return;
1614
+ }
1615
+ const wasUnlocked = __privateGet(this, _holders).size === 0;
1616
+ __privateGet(this, _holders).add(reason);
1617
+ if (wasUnlocked) {
1618
+ __privateGet(this, _onAcquired).call(this);
1619
+ }
1620
+ }
1621
+ /**
1622
+ * Release the lock for a given reason. Releasing a reason that isn't
1623
+ * held logs a warning (likely indicates an acquire/release mismatch).
1624
+ *
1625
+ * Fires `onReleased` when the last reason is released (transition from
1626
+ * locked to unlocked).
1627
+ */
1628
+ release(reason) {
1629
+ if (!__privateGet(this, _holders).delete(reason)) {
1630
+ console.warn(
1631
+ `[Electric] PauseLock: "${reason}" not held \u2014 ignoring release (possible acquire/release mismatch)`
1632
+ );
1633
+ return;
1634
+ }
1635
+ if (__privateGet(this, _holders).size === 0) {
1636
+ __privateGet(this, _onReleased).call(this);
1637
+ }
1638
+ }
1639
+ /**
1640
+ * Whether the lock is currently held by any reason.
1641
+ */
1642
+ get isPaused() {
1643
+ return __privateGet(this, _holders).size > 0;
1644
+ }
1645
+ /**
1646
+ * Check if a specific reason is holding the lock.
1647
+ */
1648
+ isHeldBy(reason) {
1649
+ return __privateGet(this, _holders).has(reason);
1650
+ }
1651
+ /**
1652
+ * Release all reasons matching a prefix. Does NOT fire `onReleased` —
1653
+ * this is for cleanup/reset paths where the stream state is being
1654
+ * managed separately.
1655
+ *
1656
+ * This preserves reasons with different prefixes (e.g., 'visibility'
1657
+ * is preserved when clearing 'snapshot-*' reasons).
1658
+ */
1659
+ releaseAllMatching(prefix) {
1660
+ for (const reason of __privateGet(this, _holders)) {
1661
+ if (reason.startsWith(prefix)) {
1662
+ __privateGet(this, _holders).delete(reason);
1663
+ }
1664
+ }
1665
+ }
1666
+ };
1667
+ _holders = new WeakMap();
1668
+ _onAcquired = new WeakMap();
1669
+ _onReleased = new WeakMap();
1670
+
1132
1671
  // src/client.ts
1133
1672
  var RESERVED_PARAMS = /* @__PURE__ */ new Set([
1134
1673
  LIVE_CACHE_BUSTER_QUERY_PARAM,
@@ -1177,7 +1716,7 @@ function canonicalShapeKey(url) {
1177
1716
  cleanUrl.searchParams.sort();
1178
1717
  return cleanUrl.toString();
1179
1718
  }
1180
- 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, _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, subscribeToVisibilityChanges_fn, reset_fn, buildSubsetBody_fn;
1719
+ 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;
1181
1720
  var ShapeStream = class {
1182
1721
  constructor(options) {
1183
1722
  __privateAdd(this, _ShapeStream_instances);
@@ -1187,57 +1726,57 @@ var ShapeStream = class {
1187
1726
  __privateAdd(this, _messageParser);
1188
1727
  __privateAdd(this, _subscribers, /* @__PURE__ */ new Map());
1189
1728
  __privateAdd(this, _started, false);
1190
- __privateAdd(this, _state, `active`);
1191
- __privateAdd(this, _lastOffset);
1192
- __privateAdd(this, _liveCacheBuster);
1193
- // Seconds since our Electric Epoch 😎
1194
- __privateAdd(this, _lastSyncedAt);
1195
- // unix time
1196
- __privateAdd(this, _isUpToDate, false);
1197
- __privateAdd(this, _isMidStream, true);
1729
+ __privateAdd(this, _syncState);
1198
1730
  __privateAdd(this, _connected, false);
1199
- __privateAdd(this, _shapeHandle);
1200
1731
  __privateAdd(this, _mode);
1201
- __privateAdd(this, _schema);
1202
1732
  __privateAdd(this, _onError);
1203
1733
  __privateAdd(this, _requestAbortController);
1204
- __privateAdd(this, _isRefreshing, false);
1734
+ __privateAdd(this, _refreshCount, 0);
1735
+ __privateAdd(this, _snapshotCounter, 0);
1205
1736
  __privateAdd(this, _tickPromise);
1206
1737
  __privateAdd(this, _tickPromiseResolver);
1207
1738
  __privateAdd(this, _tickPromiseRejecter);
1208
1739
  __privateAdd(this, _messageChain, Promise.resolve([]));
1209
1740
  // promise chain for incoming messages
1210
1741
  __privateAdd(this, _snapshotTracker, new SnapshotTracker());
1211
- __privateAdd(this, _activeSnapshotRequests, 0);
1212
- // counter for concurrent snapshot requests
1213
- __privateAdd(this, _midStreamPromise);
1214
- __privateAdd(this, _midStreamPromiseResolver);
1215
- __privateAdd(this, _lastSeenCursor);
1216
- // Last seen cursor from previous session (used to detect cached responses)
1742
+ __privateAdd(this, _pauseLock);
1217
1743
  __privateAdd(this, _currentFetchUrl);
1218
1744
  // Current fetch URL for computing shape key
1219
1745
  __privateAdd(this, _lastSseConnectionStartTime);
1220
1746
  __privateAdd(this, _minSseConnectionDuration, 1e3);
1221
1747
  // Minimum expected SSE connection duration (1 second)
1222
- __privateAdd(this, _consecutiveShortSseConnections, 0);
1223
1748
  __privateAdd(this, _maxShortSseConnections, 3);
1224
1749
  // Fall back to long polling after this many short connections
1225
- __privateAdd(this, _sseFallbackToLongPolling, false);
1226
1750
  __privateAdd(this, _sseBackoffBaseDelay, 100);
1227
1751
  // Base delay for exponential backoff (ms)
1228
1752
  __privateAdd(this, _sseBackoffMaxDelay, 5e3);
1229
1753
  // Maximum delay cap (ms)
1230
1754
  __privateAdd(this, _unsubscribeFromVisibilityChanges);
1231
- __privateAdd(this, _staleCacheBuster);
1232
- // Cache buster set when stale CDN response detected, used on retry requests to bypass cache
1233
- __privateAdd(this, _staleCacheRetryCount, 0);
1755
+ __privateAdd(this, _unsubscribeFromWakeDetection);
1234
1756
  __privateAdd(this, _maxStaleCacheRetries, 3);
1235
1757
  var _a, _b, _c, _d;
1236
1758
  this.options = __spreadValues({ subscribe: true }, options);
1237
1759
  validateOptions(this.options);
1238
- __privateSet(this, _lastOffset, (_a = this.options.offset) != null ? _a : `-1`);
1239
- __privateSet(this, _liveCacheBuster, ``);
1240
- __privateSet(this, _shapeHandle, this.options.handle);
1760
+ __privateSet(this, _syncState, createInitialState({
1761
+ offset: (_a = this.options.offset) != null ? _a : `-1`,
1762
+ handle: this.options.handle
1763
+ }));
1764
+ __privateSet(this, _pauseLock, new PauseLock({
1765
+ onAcquired: () => {
1766
+ var _a2;
1767
+ __privateSet(this, _syncState, __privateGet(this, _syncState).pause());
1768
+ if (__privateGet(this, _started)) {
1769
+ (_a2 = __privateGet(this, _requestAbortController)) == null ? void 0 : _a2.abort(PAUSE_STREAM);
1770
+ }
1771
+ },
1772
+ onReleased: () => {
1773
+ var _a2;
1774
+ if (!__privateGet(this, _started)) return;
1775
+ if ((_a2 = this.options.signal) == null ? void 0 : _a2.aborted) return;
1776
+ __privateMethod(this, _ShapeStream_instances, start_fn).call(this).catch(() => {
1777
+ });
1778
+ }
1779
+ }));
1241
1780
  let transformer;
1242
1781
  if (options.columnMapper) {
1243
1782
  const applyColumnMapper = (row) => {
@@ -1272,18 +1811,19 @@ var ShapeStream = class {
1272
1811
  ));
1273
1812
  __privateSet(this, _fetchClient2, createFetchWithConsumedMessages(__privateGet(this, _sseFetchClient)));
1274
1813
  __privateMethod(this, _ShapeStream_instances, subscribeToVisibilityChanges_fn).call(this);
1814
+ __privateMethod(this, _ShapeStream_instances, subscribeToWakeDetection_fn).call(this);
1275
1815
  }
1276
1816
  get shapeHandle() {
1277
- return __privateGet(this, _shapeHandle);
1817
+ return __privateGet(this, _syncState).handle;
1278
1818
  }
1279
1819
  get error() {
1280
1820
  return __privateGet(this, _error);
1281
1821
  }
1282
1822
  get isUpToDate() {
1283
- return __privateGet(this, _isUpToDate);
1823
+ return __privateGet(this, _syncState).isUpToDate;
1284
1824
  }
1285
1825
  get lastOffset() {
1286
- return __privateGet(this, _lastOffset);
1826
+ return __privateGet(this, _syncState).offset;
1287
1827
  }
1288
1828
  get mode() {
1289
1829
  return __privateGet(this, _mode);
@@ -1298,32 +1838,33 @@ var ShapeStream = class {
1298
1838
  };
1299
1839
  }
1300
1840
  unsubscribeAll() {
1301
- var _a;
1841
+ var _a, _b;
1302
1842
  __privateGet(this, _subscribers).clear();
1303
1843
  (_a = __privateGet(this, _unsubscribeFromVisibilityChanges)) == null ? void 0 : _a.call(this);
1844
+ (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1304
1845
  }
1305
- /** Unix time at which we last synced. Undefined when `isLoading` is true. */
1846
+ /** Unix time at which we last synced. Undefined until first successful up-to-date. */
1306
1847
  lastSyncedAt() {
1307
- return __privateGet(this, _lastSyncedAt);
1848
+ return __privateGet(this, _syncState).lastSyncedAt;
1308
1849
  }
1309
1850
  /** Time elapsed since last sync (in ms). Infinity if we did not yet sync. */
1310
1851
  lastSynced() {
1311
- if (__privateGet(this, _lastSyncedAt) === void 0) return Infinity;
1312
- return Date.now() - __privateGet(this, _lastSyncedAt);
1852
+ if (__privateGet(this, _syncState).lastSyncedAt === void 0) return Infinity;
1853
+ return Date.now() - __privateGet(this, _syncState).lastSyncedAt;
1313
1854
  }
1314
1855
  /** Indicates if we are connected to the Electric sync service. */
1315
1856
  isConnected() {
1316
1857
  return __privateGet(this, _connected);
1317
1858
  }
1318
- /** True during initial fetch. False afterwise. */
1859
+ /** True during initial fetch. False afterwards. */
1319
1860
  isLoading() {
1320
- return !__privateGet(this, _isUpToDate);
1861
+ return !__privateGet(this, _syncState).isUpToDate;
1321
1862
  }
1322
1863
  hasStarted() {
1323
1864
  return __privateGet(this, _started);
1324
1865
  }
1325
1866
  isPaused() {
1326
- return __privateGet(this, _state) === `paused`;
1867
+ return __privateGet(this, _pauseLock).isPaused;
1327
1868
  }
1328
1869
  /**
1329
1870
  * Refreshes the shape stream.
@@ -1333,12 +1874,15 @@ var ShapeStream = class {
1333
1874
  */
1334
1875
  async forceDisconnectAndRefresh() {
1335
1876
  var _a, _b;
1336
- __privateSet(this, _isRefreshing, true);
1337
- if (__privateGet(this, _isUpToDate) && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
1338
- (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
1877
+ __privateWrapper(this, _refreshCount)._++;
1878
+ try {
1879
+ if (__privateGet(this, _syncState).isUpToDate && !((_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.signal.aborted)) {
1880
+ (_b = __privateGet(this, _requestAbortController)) == null ? void 0 : _b.abort(FORCE_DISCONNECT_AND_REFRESH);
1881
+ }
1882
+ await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
1883
+ } finally {
1884
+ __privateWrapper(this, _refreshCount)._--;
1339
1885
  }
1340
- await __privateMethod(this, _ShapeStream_instances, nextTick_fn).call(this);
1341
- __privateSet(this, _isRefreshing, false);
1342
1886
  }
1343
1887
  /**
1344
1888
  * Request a snapshot for subset of data and inject it into the subscribed data stream.
@@ -1360,13 +1904,18 @@ var ShapeStream = class {
1360
1904
  `Snapshot requests are not supported in ${__privateGet(this, _mode)} mode, as the consumer is guaranteed to observe all data`
1361
1905
  );
1362
1906
  }
1363
- if (!__privateGet(this, _started)) await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1364
- await __privateMethod(this, _ShapeStream_instances, waitForStreamEnd_fn).call(this);
1365
- __privateWrapper(this, _activeSnapshotRequests)._++;
1907
+ if (!__privateGet(this, _started)) {
1908
+ __privateMethod(this, _ShapeStream_instances, start_fn).call(this).catch(() => {
1909
+ });
1910
+ }
1911
+ const snapshotReason = `snapshot-${++__privateWrapper(this, _snapshotCounter)._}`;
1912
+ __privateGet(this, _pauseLock).acquire(snapshotReason);
1913
+ const snapshotWarnTimer = setTimeout(() => {
1914
+ console.warn(
1915
+ `[Electric] Snapshot "${snapshotReason}" has held the pause lock for 30s \u2014 possible hung request or leaked lock. Current holders: ${[.../* @__PURE__ */ new Set([snapshotReason])].join(`, `)}`
1916
+ );
1917
+ }, 3e4);
1366
1918
  try {
1367
- if (__privateGet(this, _activeSnapshotRequests) === 1) {
1368
- __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
1369
- }
1370
1919
  const { metadata, data } = await this.fetchSnapshot(opts);
1371
1920
  const dataWithEndBoundary = data.concat([
1372
1921
  { headers: __spreadValues({ control: `snapshot-end` }, metadata) },
@@ -1382,10 +1931,8 @@ var ShapeStream = class {
1382
1931
  data
1383
1932
  };
1384
1933
  } finally {
1385
- __privateWrapper(this, _activeSnapshotRequests)._--;
1386
- if (__privateGet(this, _activeSnapshotRequests) === 0) {
1387
- __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
1388
- }
1934
+ clearTimeout(snapshotWarnTimer);
1935
+ __privateGet(this, _pauseLock).release(snapshotReason);
1389
1936
  }
1390
1937
  }
1391
1938
  /**
@@ -1420,7 +1967,7 @@ var ShapeStream = class {
1420
1967
  fetchUrl = result.fetchUrl;
1421
1968
  fetchOptions = { headers: result.requestHeaders };
1422
1969
  }
1423
- const usedHandle = __privateGet(this, _shapeHandle);
1970
+ const usedHandle = __privateGet(this, _syncState).handle;
1424
1971
  let response;
1425
1972
  try {
1426
1973
  response = await __privateGet(this, _fetchClient2).call(this, fetchUrl.toString(), fetchOptions);
@@ -1430,7 +1977,8 @@ var ShapeStream = class {
1430
1977
  const shapeKey = canonicalShapeKey(fetchUrl);
1431
1978
  expiredShapesCache.markExpired(shapeKey, usedHandle);
1432
1979
  }
1433
- __privateSet(this, _shapeHandle, e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`);
1980
+ const nextHandle = e.headers[SHAPE_HANDLE_HEADER] || `${usedHandle != null ? usedHandle : `handle`}-next`;
1981
+ __privateSet(this, _syncState, __privateGet(this, _syncState).withHandle(nextHandle));
1434
1982
  return this.fetchSnapshot(opts);
1435
1983
  }
1436
1984
  throw e;
@@ -1438,7 +1986,7 @@ var ShapeStream = class {
1438
1986
  if (!response.ok) {
1439
1987
  throw await FetchError.fromResponse(response, fetchUrl.toString());
1440
1988
  }
1441
- const schema = (_c = __privateGet(this, _schema)) != null ? _c : getSchemaFromHeaders(response.headers, {
1989
+ const schema = (_c = __privateGet(this, _syncState).schema) != null ? _c : getSchemaFromHeaders(response.headers, {
1442
1990
  required: true,
1443
1991
  url: fetchUrl.toString()
1444
1992
  });
@@ -1456,51 +2004,42 @@ _sseFetchClient = new WeakMap();
1456
2004
  _messageParser = new WeakMap();
1457
2005
  _subscribers = new WeakMap();
1458
2006
  _started = new WeakMap();
1459
- _state = new WeakMap();
1460
- _lastOffset = new WeakMap();
1461
- _liveCacheBuster = new WeakMap();
1462
- _lastSyncedAt = new WeakMap();
1463
- _isUpToDate = new WeakMap();
1464
- _isMidStream = new WeakMap();
2007
+ _syncState = new WeakMap();
1465
2008
  _connected = new WeakMap();
1466
- _shapeHandle = new WeakMap();
1467
2009
  _mode = new WeakMap();
1468
- _schema = new WeakMap();
1469
2010
  _onError = new WeakMap();
1470
2011
  _requestAbortController = new WeakMap();
1471
- _isRefreshing = new WeakMap();
2012
+ _refreshCount = new WeakMap();
2013
+ _snapshotCounter = new WeakMap();
2014
+ _ShapeStream_instances = new WeakSet();
2015
+ isRefreshing_get = function() {
2016
+ return __privateGet(this, _refreshCount) > 0;
2017
+ };
1472
2018
  _tickPromise = new WeakMap();
1473
2019
  _tickPromiseResolver = new WeakMap();
1474
2020
  _tickPromiseRejecter = new WeakMap();
1475
2021
  _messageChain = new WeakMap();
1476
2022
  _snapshotTracker = new WeakMap();
1477
- _activeSnapshotRequests = new WeakMap();
1478
- _midStreamPromise = new WeakMap();
1479
- _midStreamPromiseResolver = new WeakMap();
1480
- _lastSeenCursor = new WeakMap();
2023
+ _pauseLock = new WeakMap();
1481
2024
  _currentFetchUrl = new WeakMap();
1482
2025
  _lastSseConnectionStartTime = new WeakMap();
1483
2026
  _minSseConnectionDuration = new WeakMap();
1484
- _consecutiveShortSseConnections = new WeakMap();
1485
2027
  _maxShortSseConnections = new WeakMap();
1486
- _sseFallbackToLongPolling = new WeakMap();
1487
2028
  _sseBackoffBaseDelay = new WeakMap();
1488
2029
  _sseBackoffMaxDelay = new WeakMap();
1489
2030
  _unsubscribeFromVisibilityChanges = new WeakMap();
1490
- _staleCacheBuster = new WeakMap();
1491
- _staleCacheRetryCount = new WeakMap();
2031
+ _unsubscribeFromWakeDetection = new WeakMap();
1492
2032
  _maxStaleCacheRetries = new WeakMap();
1493
- _ShapeStream_instances = new WeakSet();
1494
- replayMode_get = function() {
1495
- return __privateGet(this, _lastSeenCursor) !== void 0;
1496
- };
1497
2033
  start_fn = async function() {
1498
- var _a, _b, _c, _d, _e;
2034
+ var _a, _b;
1499
2035
  __privateSet(this, _started, true);
1500
2036
  try {
1501
2037
  await __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1502
2038
  } catch (err) {
1503
2039
  __privateSet(this, _error, err);
2040
+ if (err instanceof Error) {
2041
+ __privateSet(this, _syncState, __privateGet(this, _syncState).toErrorState(err));
2042
+ }
1504
2043
  if (__privateGet(this, _onError)) {
1505
2044
  const retryOpts = await __privateGet(this, _onError).call(this, err);
1506
2045
  const isRetryable = !(err instanceof MissingHeadersError);
@@ -1512,6 +2051,9 @@ start_fn = async function() {
1512
2051
  this.options.headers = __spreadValues(__spreadValues({}, (_b = this.options.headers) != null ? _b : {}), retryOpts.headers);
1513
2052
  }
1514
2053
  __privateSet(this, _error, null);
2054
+ if (__privateGet(this, _syncState) instanceof ErrorState) {
2055
+ __privateSet(this, _syncState, __privateGet(this, _syncState).retry());
2056
+ }
1515
2057
  __privateSet(this, _started, false);
1516
2058
  await __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1517
2059
  return;
@@ -1519,35 +2061,45 @@ start_fn = async function() {
1519
2061
  if (err instanceof Error) {
1520
2062
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1521
2063
  }
1522
- __privateSet(this, _connected, false);
1523
- (_c = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _c.call(this);
2064
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
1524
2065
  return;
1525
2066
  }
1526
2067
  if (err instanceof Error) {
1527
2068
  __privateMethod(this, _ShapeStream_instances, sendErrorToSubscribers_fn).call(this, err);
1528
2069
  }
1529
- __privateSet(this, _connected, false);
1530
- (_d = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _d.call(this);
2070
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
1531
2071
  throw err;
1532
2072
  }
2073
+ __privateMethod(this, _ShapeStream_instances, teardown_fn).call(this);
2074
+ };
2075
+ teardown_fn = function() {
2076
+ var _a, _b;
1533
2077
  __privateSet(this, _connected, false);
1534
- (_e = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _e.call(this);
2078
+ (_a = __privateGet(this, _tickPromiseRejecter)) == null ? void 0 : _a.call(this);
2079
+ (_b = __privateGet(this, _unsubscribeFromWakeDetection)) == null ? void 0 : _b.call(this);
1535
2080
  };
1536
2081
  requestShape_fn = async function() {
1537
2082
  var _a, _b;
1538
- if (__privateGet(this, _state) === `pause-requested`) {
1539
- __privateSet(this, _state, `paused`);
2083
+ if (__privateGet(this, _pauseLock).isPaused) return;
2084
+ if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _syncState).isUpToDate)) {
1540
2085
  return;
1541
2086
  }
1542
- if (!this.options.subscribe && (((_a = this.options.signal) == null ? void 0 : _a.aborted) || __privateGet(this, _isUpToDate))) {
1543
- return;
2087
+ let resumingFromPause = false;
2088
+ if (__privateGet(this, _syncState) instanceof PausedState) {
2089
+ resumingFromPause = true;
2090
+ __privateSet(this, _syncState, __privateGet(this, _syncState).resume());
1544
2091
  }
1545
- const resumingFromPause = __privateGet(this, _state) === `paused`;
1546
- __privateSet(this, _state, `active`);
1547
2092
  const { url, signal } = this.options;
1548
2093
  const { fetchUrl, requestHeaders } = await __privateMethod(this, _ShapeStream_instances, constructUrl_fn).call(this, url, resumingFromPause);
1549
2094
  const abortListener = await __privateMethod(this, _ShapeStream_instances, createAbortListener_fn).call(this, signal);
1550
2095
  const requestAbortController = __privateGet(this, _requestAbortController);
2096
+ if (__privateGet(this, _pauseLock).isPaused) {
2097
+ if (abortListener && signal) {
2098
+ signal.removeEventListener(`abort`, abortListener);
2099
+ }
2100
+ __privateSet(this, _requestAbortController, void 0);
2101
+ return;
2102
+ }
1551
2103
  try {
1552
2104
  await __privateMethod(this, _ShapeStream_instances, fetchShape_fn).call(this, {
1553
2105
  fetchUrl,
@@ -1556,14 +2108,12 @@ requestShape_fn = async function() {
1556
2108
  resumingFromPause
1557
2109
  });
1558
2110
  } catch (e) {
1559
- if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && requestAbortController.signal.aborted && requestAbortController.signal.reason === FORCE_DISCONNECT_AND_REFRESH) {
2111
+ const abortReason = requestAbortController.signal.reason;
2112
+ const isRestartAbort = requestAbortController.signal.aborted && (abortReason === FORCE_DISCONNECT_AND_REFRESH || abortReason === SYSTEM_WAKE);
2113
+ if ((e instanceof FetchError || e instanceof FetchBackoffAbortError) && isRestartAbort) {
1560
2114
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
1561
2115
  }
1562
2116
  if (e instanceof FetchBackoffAbortError) {
1563
- const currentState = __privateGet(this, _state);
1564
- if (requestAbortController.signal.aborted && requestAbortController.signal.reason === PAUSE_STREAM && currentState === `pause-requested`) {
1565
- __privateSet(this, _state, `paused`);
1566
- }
1567
2117
  return;
1568
2118
  }
1569
2119
  if (e instanceof StaleCacheError) {
@@ -1571,11 +2121,11 @@ requestShape_fn = async function() {
1571
2121
  }
1572
2122
  if (!(e instanceof FetchError)) throw e;
1573
2123
  if (e.status == 409) {
1574
- if (__privateGet(this, _shapeHandle)) {
2124
+ if (__privateGet(this, _syncState).handle) {
1575
2125
  const shapeKey = canonicalShapeKey(fetchUrl);
1576
- expiredShapesCache.markExpired(shapeKey, __privateGet(this, _shapeHandle));
2126
+ expiredShapesCache.markExpired(shapeKey, __privateGet(this, _syncState).handle);
1577
2127
  }
1578
- const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _shapeHandle)}-next`;
2128
+ const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this, _syncState).handle}-next`;
1579
2129
  __privateMethod(this, _ShapeStream_instances, reset_fn).call(this, newShapeHandle);
1580
2130
  await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, Array.isArray(e.json) ? e.json : [e.json]);
1581
2131
  return __privateMethod(this, _ShapeStream_instances, requestShape_fn).call(this);
@@ -1681,32 +2231,18 @@ constructUrl_fn = async function(url, resumingFromPause, subsetParams) {
1681
2231
  setQueryParam(fetchUrl, SUBSET_PARAM_ORDER_BY, encodedOrderBy);
1682
2232
  }
1683
2233
  }
1684
- fetchUrl.searchParams.set(OFFSET_QUERY_PARAM, __privateGet(this, _lastOffset));
2234
+ __privateGet(this, _syncState).applyUrlParams(fetchUrl, {
2235
+ isSnapshotRequest: subsetParams !== void 0,
2236
+ // Don't long-poll when resuming from pause or refreshing — avoids
2237
+ // a 20s hold during which `isConnected` would be false
2238
+ canLongPoll: !__privateGet(this, _ShapeStream_instances, isRefreshing_get) && !resumingFromPause
2239
+ });
1685
2240
  fetchUrl.searchParams.set(LOG_MODE_QUERY_PARAM, __privateGet(this, _mode));
1686
- const isSnapshotRequest = subsetParams !== void 0;
1687
- if (__privateGet(this, _isUpToDate) && !isSnapshotRequest) {
1688
- if (!__privateGet(this, _isRefreshing) && !resumingFromPause) {
1689
- fetchUrl.searchParams.set(LIVE_QUERY_PARAM, `true`);
1690
- }
1691
- fetchUrl.searchParams.set(
1692
- LIVE_CACHE_BUSTER_QUERY_PARAM,
1693
- __privateGet(this, _liveCacheBuster)
1694
- );
1695
- }
1696
- if (__privateGet(this, _shapeHandle)) {
1697
- fetchUrl.searchParams.set(SHAPE_HANDLE_QUERY_PARAM, __privateGet(this, _shapeHandle));
1698
- }
1699
2241
  const shapeKey = canonicalShapeKey(fetchUrl);
1700
2242
  const expiredHandle = expiredShapesCache.getExpiredHandle(shapeKey);
1701
2243
  if (expiredHandle) {
1702
2244
  fetchUrl.searchParams.set(EXPIRED_HANDLE_QUERY_PARAM, expiredHandle);
1703
2245
  }
1704
- if (__privateGet(this, _staleCacheBuster)) {
1705
- fetchUrl.searchParams.set(
1706
- CACHE_BUSTER_QUERY_PARAM,
1707
- __privateGet(this, _staleCacheBuster)
1708
- );
1709
- }
1710
2246
  fetchUrl.searchParams.sort();
1711
2247
  return {
1712
2248
  fetchUrl,
@@ -1729,108 +2265,100 @@ createAbortListener_fn = async function(signal) {
1729
2265
  }
1730
2266
  };
1731
2267
  onInitialResponse_fn = async function(response) {
1732
- var _a, _b, _c, _d;
2268
+ var _a, _b, _c;
1733
2269
  const { headers, status } = response;
1734
2270
  const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
1735
- if (shapeHandle) {
1736
- const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
1737
- const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
1738
- if (shapeHandle !== expiredHandle) {
1739
- __privateSet(this, _shapeHandle, shapeHandle);
1740
- if (__privateGet(this, _staleCacheBuster)) {
1741
- __privateSet(this, _staleCacheBuster, void 0);
1742
- __privateSet(this, _staleCacheRetryCount, 0);
1743
- }
1744
- } else if (__privateGet(this, _shapeHandle) === void 0) {
1745
- __privateWrapper(this, _staleCacheRetryCount)._++;
1746
- await ((_a = response.body) == null ? void 0 : _a.cancel());
1747
- if (__privateGet(this, _staleCacheRetryCount) > __privateGet(this, _maxStaleCacheRetries)) {
1748
- throw new FetchError(
1749
- 502,
1750
- void 0,
1751
- void 0,
1752
- {},
1753
- (_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
1754
- `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`
1755
- );
1756
- }
1757
- console.warn(
1758
- `[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)}).`
1759
- );
1760
- __privateSet(this, _staleCacheBuster, `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`);
1761
- throw new StaleCacheError(
1762
- `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.`
1763
- );
1764
- } else {
1765
- console.warn(
1766
- `[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)}".`
2271
+ const shapeKey = __privateGet(this, _currentFetchUrl) ? canonicalShapeKey(__privateGet(this, _currentFetchUrl)) : null;
2272
+ const expiredHandle = shapeKey ? expiredShapesCache.getExpiredHandle(shapeKey) : null;
2273
+ const transition = __privateGet(this, _syncState).handleResponseMetadata({
2274
+ status,
2275
+ responseHandle: shapeHandle,
2276
+ responseOffset: headers.get(CHUNK_LAST_OFFSET_HEADER),
2277
+ responseCursor: headers.get(LIVE_CACHE_BUSTER_HEADER),
2278
+ responseSchema: getSchemaFromHeaders(headers),
2279
+ expiredHandle,
2280
+ now: Date.now(),
2281
+ maxStaleCacheRetries: __privateGet(this, _maxStaleCacheRetries),
2282
+ createCacheBuster: () => `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`
2283
+ });
2284
+ __privateSet(this, _syncState, transition.state);
2285
+ if (transition.action === `stale-retry`) {
2286
+ await ((_a = response.body) == null ? void 0 : _a.cancel());
2287
+ if (transition.exceededMaxRetries) {
2288
+ throw new FetchError(
2289
+ 502,
2290
+ void 0,
2291
+ void 0,
2292
+ {},
2293
+ (_c = (_b = __privateGet(this, _currentFetchUrl)) == null ? void 0 : _b.toString()) != null ? _c : ``,
2294
+ `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`
1767
2295
  );
1768
- return;
1769
2296
  }
2297
+ console.warn(
2298
+ `[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)}).`
2299
+ );
2300
+ throw new StaleCacheError(
2301
+ `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.`
2302
+ );
1770
2303
  }
1771
- const lastOffset = headers.get(CHUNK_LAST_OFFSET_HEADER);
1772
- if (lastOffset) {
1773
- __privateSet(this, _lastOffset, lastOffset);
1774
- }
1775
- const liveCacheBuster = headers.get(LIVE_CACHE_BUSTER_HEADER);
1776
- if (liveCacheBuster) {
1777
- __privateSet(this, _liveCacheBuster, liveCacheBuster);
1778
- }
1779
- __privateSet(this, _schema, (_d = __privateGet(this, _schema)) != null ? _d : getSchemaFromHeaders(headers));
1780
- if (status === 204) {
1781
- __privateSet(this, _lastSyncedAt, Date.now());
2304
+ if (transition.action === `ignored`) {
2305
+ console.warn(
2306
+ `[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}".`
2307
+ );
2308
+ return false;
1782
2309
  }
2310
+ return true;
1783
2311
  };
1784
2312
  onMessages_fn = async function(batch, isSseMessage = false) {
1785
- var _a;
1786
- if (batch.length > 0) {
1787
- __privateSet(this, _isMidStream, true);
1788
- const lastMessage = batch[batch.length - 1];
1789
- if (isUpToDateMessage(lastMessage)) {
1790
- if (isSseMessage) {
1791
- const offset = getOffset(lastMessage);
1792
- if (offset) {
1793
- __privateSet(this, _lastOffset, offset);
1794
- }
1795
- }
1796
- __privateSet(this, _lastSyncedAt, Date.now());
1797
- __privateSet(this, _isUpToDate, true);
1798
- __privateSet(this, _isMidStream, false);
1799
- (_a = __privateGet(this, _midStreamPromiseResolver)) == null ? void 0 : _a.call(this);
1800
- if (__privateGet(this, _ShapeStream_instances, replayMode_get) && !isSseMessage) {
1801
- const currentCursor = __privateGet(this, _liveCacheBuster);
1802
- if (currentCursor === __privateGet(this, _lastSeenCursor)) {
1803
- __privateSet(this, _lastSeenCursor, void 0);
1804
- return;
1805
- }
1806
- }
1807
- __privateSet(this, _lastSeenCursor, void 0);
1808
- if (__privateGet(this, _currentFetchUrl)) {
1809
- const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
1810
- upToDateTracker.recordUpToDate(shapeKey, __privateGet(this, _liveCacheBuster));
1811
- }
2313
+ if (batch.length === 0) return;
2314
+ const lastMessage = batch[batch.length - 1];
2315
+ const hasUpToDateMessage = isUpToDateMessage(lastMessage);
2316
+ const upToDateOffset = hasUpToDateMessage ? getOffset(lastMessage) : void 0;
2317
+ const transition = __privateGet(this, _syncState).handleMessageBatch({
2318
+ hasMessages: true,
2319
+ hasUpToDateMessage,
2320
+ isSse: isSseMessage,
2321
+ upToDateOffset,
2322
+ now: Date.now(),
2323
+ currentCursor: __privateGet(this, _syncState).liveCacheBuster
2324
+ });
2325
+ __privateSet(this, _syncState, transition.state);
2326
+ if (hasUpToDateMessage) {
2327
+ if (transition.suppressBatch) {
2328
+ return;
2329
+ }
2330
+ if (__privateGet(this, _currentFetchUrl)) {
2331
+ const shapeKey = canonicalShapeKey(__privateGet(this, _currentFetchUrl));
2332
+ upToDateTracker.recordUpToDate(
2333
+ shapeKey,
2334
+ __privateGet(this, _syncState).liveCacheBuster
2335
+ );
1812
2336
  }
1813
- const messagesToProcess = batch.filter((message) => {
1814
- if (isChangeMessage(message)) {
1815
- return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
1816
- }
1817
- return true;
1818
- });
1819
- await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
1820
2337
  }
2338
+ const messagesToProcess = batch.filter((message) => {
2339
+ if (isChangeMessage(message)) {
2340
+ return !__privateGet(this, _snapshotTracker).shouldRejectMessage(message);
2341
+ }
2342
+ return true;
2343
+ });
2344
+ await __privateMethod(this, _ShapeStream_instances, publish_fn).call(this, messagesToProcess);
1821
2345
  };
1822
2346
  fetchShape_fn = async function(opts) {
1823
2347
  var _a;
1824
2348
  __privateSet(this, _currentFetchUrl, opts.fetchUrl);
1825
- if (!__privateGet(this, _isUpToDate) && !__privateGet(this, _ShapeStream_instances, replayMode_get)) {
2349
+ if (!__privateGet(this, _syncState).isUpToDate && __privateGet(this, _syncState).canEnterReplayMode()) {
1826
2350
  const shapeKey = canonicalShapeKey(opts.fetchUrl);
1827
2351
  const lastSeenCursor = upToDateTracker.shouldEnterReplayMode(shapeKey);
1828
2352
  if (lastSeenCursor) {
1829
- __privateSet(this, _lastSeenCursor, lastSeenCursor);
2353
+ __privateSet(this, _syncState, __privateGet(this, _syncState).enterReplayMode(lastSeenCursor));
1830
2354
  }
1831
2355
  }
1832
2356
  const useSse = (_a = this.options.liveSse) != null ? _a : this.options.experimentalLiveSse;
1833
- if (__privateGet(this, _isUpToDate) && useSse && !__privateGet(this, _isRefreshing) && !opts.resumingFromPause && !__privateGet(this, _sseFallbackToLongPolling)) {
2357
+ if (__privateGet(this, _syncState).shouldUseSse({
2358
+ liveSseEnabled: !!useSse,
2359
+ isRefreshing: __privateGet(this, _ShapeStream_instances, isRefreshing_get),
2360
+ resumingFromPause: !!opts.resumingFromPause
2361
+ })) {
1834
2362
  opts.fetchUrl.searchParams.set(EXPERIMENTAL_LIVE_SSE_QUERY_PARAM, `true`);
1835
2363
  opts.fetchUrl.searchParams.set(LIVE_SSE_QUERY_PARAM, `true`);
1836
2364
  return __privateMethod(this, _ShapeStream_instances, requestShapeSSE_fn).call(this, opts);
@@ -1844,8 +2372,9 @@ requestShapeLongPoll_fn = async function(opts) {
1844
2372
  headers
1845
2373
  });
1846
2374
  __privateSet(this, _connected, true);
1847
- await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
1848
- const schema = __privateGet(this, _schema);
2375
+ const shouldProcessBody = await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2376
+ if (!shouldProcessBody) return;
2377
+ const schema = __privateGet(this, _syncState).schema;
1849
2378
  const res = await response.text();
1850
2379
  const messages = res || `[]`;
1851
2380
  const batch = __privateGet(this, _messageParser).parse(messages, schema);
@@ -1858,6 +2387,7 @@ requestShapeSSE_fn = async function(opts) {
1858
2387
  const sseHeaders = __spreadProps(__spreadValues({}, headers), {
1859
2388
  Accept: `text/event-stream`
1860
2389
  });
2390
+ let ignoredStaleResponse = false;
1861
2391
  try {
1862
2392
  let buffer = [];
1863
2393
  await (0, import_fetch_event_source.fetchEventSource)(fetchUrl.toString(), {
@@ -1865,11 +2395,15 @@ requestShapeSSE_fn = async function(opts) {
1865
2395
  fetch: fetch2,
1866
2396
  onopen: async (response) => {
1867
2397
  __privateSet(this, _connected, true);
1868
- await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2398
+ const shouldProcessBody = await __privateMethod(this, _ShapeStream_instances, onInitialResponse_fn).call(this, response);
2399
+ if (!shouldProcessBody) {
2400
+ ignoredStaleResponse = true;
2401
+ throw new Error(`stale response ignored`);
2402
+ }
1869
2403
  },
1870
2404
  onmessage: (event) => {
1871
2405
  if (event.data) {
1872
- const schema = __privateGet(this, _schema);
2406
+ const schema = __privateGet(this, _syncState).schema;
1873
2407
  const message = __privateGet(this, _messageParser).parse(
1874
2408
  event.data,
1875
2409
  schema
@@ -1887,6 +2421,9 @@ requestShapeSSE_fn = async function(opts) {
1887
2421
  signal: requestAbortController.signal
1888
2422
  });
1889
2423
  } catch (error) {
2424
+ if (ignoredStaleResponse) {
2425
+ return;
2426
+ }
1890
2427
  if (requestAbortController.signal.aborted) {
1891
2428
  throw new FetchBackoffAbortError();
1892
2429
  }
@@ -1894,46 +2431,33 @@ requestShapeSSE_fn = async function(opts) {
1894
2431
  } finally {
1895
2432
  const connectionDuration = Date.now() - __privateGet(this, _lastSseConnectionStartTime);
1896
2433
  const wasAborted = requestAbortController.signal.aborted;
1897
- if (connectionDuration < __privateGet(this, _minSseConnectionDuration) && !wasAborted) {
1898
- __privateWrapper(this, _consecutiveShortSseConnections)._++;
1899
- if (__privateGet(this, _consecutiveShortSseConnections) >= __privateGet(this, _maxShortSseConnections)) {
1900
- __privateSet(this, _sseFallbackToLongPolling, true);
1901
- console.warn(
1902
- `[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.`
1903
- );
1904
- } else {
1905
- const maxDelay = Math.min(
1906
- __privateGet(this, _sseBackoffMaxDelay),
1907
- __privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _consecutiveShortSseConnections))
1908
- );
1909
- const delayMs = Math.floor(Math.random() * maxDelay);
1910
- await new Promise((resolve) => setTimeout(resolve, delayMs));
1911
- }
1912
- } else if (connectionDuration >= __privateGet(this, _minSseConnectionDuration)) {
1913
- __privateSet(this, _consecutiveShortSseConnections, 0);
1914
- }
1915
- }
1916
- };
1917
- pause_fn = function() {
1918
- var _a;
1919
- if (__privateGet(this, _started) && __privateGet(this, _state) === `active`) {
1920
- __privateSet(this, _state, `pause-requested`);
1921
- (_a = __privateGet(this, _requestAbortController)) == null ? void 0 : _a.abort(PAUSE_STREAM);
1922
- }
1923
- };
1924
- resume_fn = function() {
1925
- var _a;
1926
- if (__privateGet(this, _started) && (__privateGet(this, _state) === `paused` || __privateGet(this, _state) === `pause-requested`)) {
1927
- if ((_a = this.options.signal) == null ? void 0 : _a.aborted) {
1928
- return;
1929
- }
1930
- if (__privateGet(this, _state) === `pause-requested`) {
1931
- __privateSet(this, _state, `active`);
2434
+ const transition = __privateGet(this, _syncState).handleSseConnectionClosed({
2435
+ connectionDuration,
2436
+ wasAborted,
2437
+ minConnectionDuration: __privateGet(this, _minSseConnectionDuration),
2438
+ maxShortConnections: __privateGet(this, _maxShortSseConnections)
2439
+ });
2440
+ __privateSet(this, _syncState, transition.state);
2441
+ if (transition.fellBackToLongPolling) {
2442
+ console.warn(
2443
+ `[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.`
2444
+ );
2445
+ } else if (transition.wasShortConnection) {
2446
+ const maxDelay = Math.min(
2447
+ __privateGet(this, _sseBackoffMaxDelay),
2448
+ __privateGet(this, _sseBackoffBaseDelay) * Math.pow(2, __privateGet(this, _syncState).consecutiveShortSseConnections)
2449
+ );
2450
+ const delayMs = Math.floor(Math.random() * maxDelay);
2451
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1932
2452
  }
1933
- __privateMethod(this, _ShapeStream_instances, start_fn).call(this);
1934
2453
  }
1935
2454
  };
1936
2455
  nextTick_fn = async function() {
2456
+ if (__privateGet(this, _pauseLock).isPaused) {
2457
+ throw new Error(
2458
+ `Cannot wait for next tick while PauseLock is held \u2014 this would deadlock because the request loop is paused`
2459
+ );
2460
+ }
1937
2461
  if (__privateGet(this, _tickPromise)) {
1938
2462
  return __privateGet(this, _tickPromise);
1939
2463
  }
@@ -1948,22 +2472,6 @@ nextTick_fn = async function() {
1948
2472
  });
1949
2473
  return __privateGet(this, _tickPromise);
1950
2474
  };
1951
- waitForStreamEnd_fn = async function() {
1952
- if (!__privateGet(this, _isMidStream)) {
1953
- return;
1954
- }
1955
- if (__privateGet(this, _midStreamPromise)) {
1956
- return __privateGet(this, _midStreamPromise);
1957
- }
1958
- __privateSet(this, _midStreamPromise, new Promise((resolve) => {
1959
- __privateSet(this, _midStreamPromiseResolver, resolve);
1960
- }));
1961
- __privateGet(this, _midStreamPromise).finally(() => {
1962
- __privateSet(this, _midStreamPromise, void 0);
1963
- __privateSet(this, _midStreamPromiseResolver, void 0);
1964
- });
1965
- return __privateGet(this, _midStreamPromise);
1966
- };
1967
2475
  publish_fn = async function(messages) {
1968
2476
  __privateSet(this, _messageChain, __privateGet(this, _messageChain).then(
1969
2477
  () => Promise.all(
@@ -1985,13 +2493,16 @@ sendErrorToSubscribers_fn = function(error) {
1985
2493
  errorFn == null ? void 0 : errorFn(error);
1986
2494
  });
1987
2495
  };
2496
+ hasBrowserVisibilityAPI_fn = function() {
2497
+ return typeof document === `object` && typeof document.hidden === `boolean` && typeof document.addEventListener === `function`;
2498
+ };
1988
2499
  subscribeToVisibilityChanges_fn = function() {
1989
- if (typeof document === `object` && typeof document.hidden === `boolean` && typeof document.addEventListener === `function`) {
2500
+ if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) {
1990
2501
  const visibilityHandler = () => {
1991
2502
  if (document.hidden) {
1992
- __privateMethod(this, _ShapeStream_instances, pause_fn).call(this);
2503
+ __privateGet(this, _pauseLock).acquire(`visibility`);
1993
2504
  } else {
1994
- __privateMethod(this, _ShapeStream_instances, resume_fn).call(this);
2505
+ __privateGet(this, _pauseLock).release(`visibility`);
1995
2506
  }
1996
2507
  };
1997
2508
  document.addEventListener(`visibilitychange`, visibilityHandler);
@@ -2000,23 +2511,52 @@ subscribeToVisibilityChanges_fn = function() {
2000
2511
  });
2001
2512
  }
2002
2513
  };
2514
+ /**
2515
+ * Detects system wake from sleep using timer gap detection.
2516
+ * When the system sleeps, setInterval timers are paused. On wake,
2517
+ * the elapsed wall-clock time since the last tick will be much larger
2518
+ * than the interval period, indicating the system was asleep.
2519
+ *
2520
+ * Only active in non-browser environments (Bun, Node.js) where
2521
+ * `document.visibilitychange` is not available. In browsers,
2522
+ * `#subscribeToVisibilityChanges` handles this instead. Without wake
2523
+ * detection, in-flight HTTP requests (long-poll or SSE) may hang until
2524
+ * the OS TCP timeout.
2525
+ */
2526
+ subscribeToWakeDetection_fn = function() {
2527
+ if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) return;
2528
+ const INTERVAL_MS = 2e3;
2529
+ const WAKE_THRESHOLD_MS = 4e3;
2530
+ let lastTickTime = Date.now();
2531
+ const timer = setInterval(() => {
2532
+ const now = Date.now();
2533
+ const elapsed = now - lastTickTime;
2534
+ lastTickTime = now;
2535
+ if (elapsed > INTERVAL_MS + WAKE_THRESHOLD_MS) {
2536
+ if (!__privateGet(this, _pauseLock).isPaused && __privateGet(this, _requestAbortController)) {
2537
+ __privateWrapper(this, _refreshCount)._++;
2538
+ __privateGet(this, _requestAbortController).abort(SYSTEM_WAKE);
2539
+ queueMicrotask(() => {
2540
+ __privateWrapper(this, _refreshCount)._--;
2541
+ });
2542
+ }
2543
+ }
2544
+ }, INTERVAL_MS);
2545
+ if (typeof timer === `object` && `unref` in timer) {
2546
+ timer.unref();
2547
+ }
2548
+ __privateSet(this, _unsubscribeFromWakeDetection, () => {
2549
+ clearInterval(timer);
2550
+ });
2551
+ };
2003
2552
  /**
2004
2553
  * Resets the state of the stream, optionally with a provided
2005
2554
  * shape handle
2006
2555
  */
2007
2556
  reset_fn = function(handle) {
2008
- __privateSet(this, _lastOffset, `-1`);
2009
- __privateSet(this, _liveCacheBuster, ``);
2010
- __privateSet(this, _shapeHandle, handle);
2011
- __privateSet(this, _isUpToDate, false);
2012
- __privateSet(this, _isMidStream, true);
2557
+ __privateSet(this, _syncState, __privateGet(this, _syncState).markMustRefetch(handle));
2013
2558
  __privateSet(this, _connected, false);
2014
- __privateSet(this, _schema, void 0);
2015
- __privateSet(this, _activeSnapshotRequests, 0);
2016
- __privateSet(this, _consecutiveShortSseConnections, 0);
2017
- __privateSet(this, _sseFallbackToLongPolling, false);
2018
- __privateSet(this, _staleCacheBuster, void 0);
2019
- __privateSet(this, _staleCacheRetryCount, 0);
2559
+ __privateGet(this, _pauseLock).releaseAllMatching(`snapshot`);
2020
2560
  };
2021
2561
  buildSubsetBody_fn = function(opts) {
2022
2562
  var _a, _b, _c, _d;