@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/cjs/index.cjs +808 -268
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +2 -2
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.legacy-esm.js +808 -268
- package/dist/index.legacy-esm.js.map +1 -1
- package/dist/index.mjs +808 -268
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +366 -377
- package/src/constants.ts +1 -0
- package/src/pause-lock.ts +112 -0
- package/src/shape-stream-state.ts +781 -0
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
1204
|
-
|
|
1205
|
-
|
|
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,
|
|
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,
|
|
1788
|
+
return __privateGet(this, _syncState).isUpToDate;
|
|
1249
1789
|
}
|
|
1250
1790
|
get lastOffset() {
|
|
1251
|
-
return __privateGet(this,
|
|
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
|
|
1811
|
+
/** Unix time at which we last synced. Undefined until first successful up-to-date. */
|
|
1271
1812
|
lastSyncedAt() {
|
|
1272
|
-
return __privateGet(this,
|
|
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,
|
|
1277
|
-
return Date.now() - __privateGet(this,
|
|
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
|
|
1824
|
+
/** True during initial fetch. False afterwards. */
|
|
1284
1825
|
isLoading() {
|
|
1285
|
-
return !__privateGet(this,
|
|
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,
|
|
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
|
-
|
|
1302
|
-
|
|
1303
|
-
(
|
|
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))
|
|
1329
|
-
|
|
1330
|
-
|
|
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
|
-
|
|
1351
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
(
|
|
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,
|
|
1504
|
-
|
|
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
|
-
|
|
1508
|
-
|
|
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
|
-
|
|
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,
|
|
2089
|
+
if (__privateGet(this, _syncState).handle) {
|
|
1540
2090
|
const shapeKey = canonicalShapeKey(fetchUrl);
|
|
1541
|
-
expiredShapesCache.markExpired(shapeKey, __privateGet(this,
|
|
2091
|
+
expiredShapesCache.markExpired(shapeKey, __privateGet(this, _syncState).handle);
|
|
1542
2092
|
}
|
|
1543
|
-
const newShapeHandle = e.headers[SHAPE_HANDLE_HEADER] || `${__privateGet(this,
|
|
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
|
-
|
|
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
|
|
2233
|
+
var _a, _b, _c;
|
|
1698
2234
|
const { headers, status } = response;
|
|
1699
2235
|
const shapeHandle = headers.get(SHAPE_HANDLE_HEADER);
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
`
|
|
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
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
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
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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 (
|
|
2465
|
+
if (__privateMethod(this, _ShapeStream_instances, hasBrowserVisibilityAPI_fn).call(this)) {
|
|
1955
2466
|
const visibilityHandler = () => {
|
|
1956
2467
|
if (document.hidden) {
|
|
1957
|
-
|
|
2468
|
+
__privateGet(this, _pauseLock).acquire(`visibility`);
|
|
1958
2469
|
} else {
|
|
1959
|
-
|
|
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,
|
|
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
|
-
|
|
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;
|