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