cross-tab-worker-databus 0.10.0 → 0.20.6
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/CHANGELOG.md +222 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/centrifuge.js +1 -1
- package/dist/centrifuge.shared.worker.js +4 -2
- package/dist/centrifuge.shared.worker.js.map +2 -2
- package/dist/centrifuge.worker.js +4 -2
- package/dist/centrifuge.worker.js.map +2 -2
- package/dist/{chunk-ZOPNTR4E.js → chunk-LPS4XOK4.js} +177 -19
- package/dist/{chunk-ZOPNTR4E.js.map → chunk-LPS4XOK4.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +176 -18
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/hooks.cjs +4 -1
- package/dist/cjs/hooks.cjs.map +2 -2
- package/dist/cjs/index.cjs +326 -67
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/vue.cjs +7 -1
- package/dist/cjs/vue.cjs.map +2 -2
- package/dist/core/data-bus.d.ts +35 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/publication.d.ts.map +1 -1
- package/dist/core/replay-persistence.d.ts.map +1 -1
- package/dist/core/trace.d.ts +11 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +4 -1
- package/dist/hooks.js.map +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +151 -50
- package/dist/index.js.map +2 -2
- package/dist/vue.d.ts.map +1 -1
- package/dist/vue.js +7 -1
- package/dist/vue.js.map +2 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/README.md +1 -0
- package/docs/api.md +11 -4
- package/docs/architecture.md +4 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +21 -1
- package/docs/release-checklist.md +20 -0
- package/docs/roadmap.md +144 -2
- package/docs/zh/README.md +1 -0
- package/docs/zh/api.md +11 -4
- package/docs/zh/architecture.md +4 -0
- package/docs/zh/capabilities.md +2 -2
- package/docs/zh/configuration.md +21 -1
- package/docs/zh/release-checklist.md +20 -0
- package/docs/zh/roadmap.md +132 -2
- package/package.json +2 -1
|
@@ -1050,7 +1050,9 @@ var DataBusTraceReporter = class {
|
|
|
1050
1050
|
// Bucketed histogram: bucket index = floor(delayMs / 50), capped at 19.
|
|
1051
1051
|
latencyBuckets = new Array(LATENCY_BUCKET_COUNT).fill(0);
|
|
1052
1052
|
latencySumMs = 0;
|
|
1053
|
-
|
|
1053
|
+
dedupAccepted = 0;
|
|
1054
|
+
dedupSuppressed = 0;
|
|
1055
|
+
constructor(options, now = options?.now ?? Date.now) {
|
|
1054
1056
|
this.enabled = options?.enabled ?? false;
|
|
1055
1057
|
this.mode = options?.mode ?? "all";
|
|
1056
1058
|
this.metricsIntervalMs = normalizeInterval(options?.metricsIntervalMs);
|
|
@@ -1125,6 +1127,13 @@ var DataBusTraceReporter = class {
|
|
|
1125
1127
|
this.latencyBuckets[bucketIndex] = (this.latencyBuckets[bucketIndex] ?? 0) + 1;
|
|
1126
1128
|
this.latencySumMs += delayMs;
|
|
1127
1129
|
}
|
|
1130
|
+
/** Record deduplication outcomes for the next metrics window. */
|
|
1131
|
+
recordDedupAccepted() {
|
|
1132
|
+
if (this.metricsActive) this.dedupAccepted += 1;
|
|
1133
|
+
}
|
|
1134
|
+
recordDedupSuppressed() {
|
|
1135
|
+
if (this.metricsActive) this.dedupSuppressed += 1;
|
|
1136
|
+
}
|
|
1128
1137
|
/** True when metrics recording is active: enabled and mode includes metrics.
|
|
1129
1138
|
* Extracted so the four record / flush methods share one guard expression
|
|
1130
1139
|
* instead of repeating `!this.enabled || this.mode === 'events'` at each. */
|
|
@@ -1138,7 +1147,7 @@ var DataBusTraceReporter = class {
|
|
|
1138
1147
|
}
|
|
1139
1148
|
flushNow() {
|
|
1140
1149
|
const timestamp = this.now();
|
|
1141
|
-
if (this.received > 0 || this.dispatched > 0) {
|
|
1150
|
+
if (this.received > 0 || this.dispatched > 0 || this.dedupAccepted > 0 || this.dedupSuppressed > 0) {
|
|
1142
1151
|
const samples = this.latencySamples;
|
|
1143
1152
|
this.emit({
|
|
1144
1153
|
type: "message_metrics",
|
|
@@ -1152,6 +1161,8 @@ var DataBusTraceReporter = class {
|
|
|
1152
1161
|
dispatchP50Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.5)),
|
|
1153
1162
|
dispatchP95Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.95)),
|
|
1154
1163
|
dispatchMaxMs: roundMs(percentileMs(this.latencyBuckets, samples, 1)),
|
|
1164
|
+
dedupAccepted: this.dedupAccepted,
|
|
1165
|
+
dedupSuppressed: this.dedupSuppressed,
|
|
1155
1166
|
timestamp
|
|
1156
1167
|
});
|
|
1157
1168
|
this.resetMetrics();
|
|
@@ -1166,6 +1177,8 @@ var DataBusTraceReporter = class {
|
|
|
1166
1177
|
this.receivedAt.clear();
|
|
1167
1178
|
this.latencyBuckets.fill(0);
|
|
1168
1179
|
this.latencySumMs = 0;
|
|
1180
|
+
this.dedupAccepted = 0;
|
|
1181
|
+
this.dedupSuppressed = 0;
|
|
1169
1182
|
}
|
|
1170
1183
|
emit(event) {
|
|
1171
1184
|
try {
|
|
@@ -1201,6 +1214,12 @@ function roundMs(value) {
|
|
|
1201
1214
|
// src/core/data-bus.ts
|
|
1202
1215
|
var PUBLICATION_EVENT = "DATABUS_PUBLICATION";
|
|
1203
1216
|
var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
|
|
1217
|
+
var PersistenceRetryCancelledError = class extends Error {
|
|
1218
|
+
constructor() {
|
|
1219
|
+
super("Persistence retry cancelled by lifecycle transition.");
|
|
1220
|
+
this.name = "PersistenceRetryCancelledError";
|
|
1221
|
+
}
|
|
1222
|
+
};
|
|
1204
1223
|
var CrossTabDataBus = class _CrossTabDataBus {
|
|
1205
1224
|
transport;
|
|
1206
1225
|
cluster;
|
|
@@ -1216,13 +1235,26 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1216
1235
|
replayBuffers;
|
|
1217
1236
|
replayMaxPerTopic;
|
|
1218
1237
|
replayPersistence;
|
|
1238
|
+
replayRetentionMs;
|
|
1239
|
+
replayRetentionSweepMs;
|
|
1240
|
+
persistenceRetryMaxAttempts;
|
|
1241
|
+
persistenceRetryBackoffMs;
|
|
1242
|
+
persistenceRetryGeneration = 0;
|
|
1219
1243
|
replayHydration;
|
|
1244
|
+
// Retention cleanup is coalesced so a burst of publications does not issue
|
|
1245
|
+
// one IndexedDB read/write transaction per message. The newest cutoff wins.
|
|
1246
|
+
replayRetentionCleanup = null;
|
|
1247
|
+
replayRetentionCutoff = null;
|
|
1248
|
+
replayRetentionTimer = null;
|
|
1220
1249
|
initialConfig;
|
|
1221
1250
|
hasInitialConfig;
|
|
1222
1251
|
trace;
|
|
1223
1252
|
dedupMaxEntries;
|
|
1224
1253
|
dedupTtlMs;
|
|
1254
|
+
dedupSweepMs;
|
|
1255
|
+
dedupSweepTimer = null;
|
|
1225
1256
|
dedupEnabled;
|
|
1257
|
+
now;
|
|
1226
1258
|
seenMessageIds = /* @__PURE__ */ new Map();
|
|
1227
1259
|
dedupSuppressed = 0;
|
|
1228
1260
|
dedupAccepted = 0;
|
|
@@ -1263,14 +1295,32 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1263
1295
|
this.replayMaxPerTopic = replay?.maxPerTopic ?? DEFAULT_REPLAY_MAX_PER_TOPIC;
|
|
1264
1296
|
this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
|
|
1265
1297
|
this.replayPersistence = replay?.persistence ?? null;
|
|
1266
|
-
this.
|
|
1298
|
+
this.replayRetentionMs = replay?.retentionMs;
|
|
1299
|
+
if (this.replayRetentionMs !== void 0 && (!Number.isFinite(this.replayRetentionMs) || this.replayRetentionMs <= 0)) {
|
|
1300
|
+
throw new TypeError("replay.retentionMs must be a positive finite number.");
|
|
1301
|
+
}
|
|
1302
|
+
this.replayRetentionSweepMs = replay?.retentionSweepMs;
|
|
1303
|
+
if (this.replayRetentionSweepMs !== void 0 && (!Number.isFinite(this.replayRetentionSweepMs) || this.replayRetentionSweepMs <= 0)) {
|
|
1304
|
+
throw new TypeError("replay.retentionSweepMs must be a positive finite number.");
|
|
1305
|
+
}
|
|
1306
|
+
this.persistenceRetryMaxAttempts = replay?.persistenceRetry?.maxAttempts ?? 1;
|
|
1307
|
+
this.persistenceRetryBackoffMs = replay?.persistenceRetry?.backoffMs ?? 50;
|
|
1308
|
+
if (!Number.isSafeInteger(this.persistenceRetryMaxAttempts) || this.persistenceRetryMaxAttempts <= 0) {
|
|
1309
|
+
throw new TypeError("replay.persistenceRetry.maxAttempts must be a positive safe integer.");
|
|
1310
|
+
}
|
|
1311
|
+
if (!Number.isFinite(this.persistenceRetryBackoffMs) || this.persistenceRetryBackoffMs < 0) {
|
|
1312
|
+
throw new TypeError("replay.persistenceRetry.backoffMs must be a non-negative finite number.");
|
|
1313
|
+
}
|
|
1267
1314
|
const { autoStart, initialConfig, trace, transport, dedup, ...clusterOptions } = options;
|
|
1315
|
+
this.now = dedup?.now ?? Date.now;
|
|
1316
|
+
this.replayHydration = this.hydrateReplay();
|
|
1268
1317
|
this.transport = transport;
|
|
1269
1318
|
this.initialConfig = initialConfig;
|
|
1270
1319
|
this.hasInitialConfig = "initialConfig" in options;
|
|
1271
1320
|
this.trace = new DataBusTraceReporter(trace);
|
|
1272
1321
|
this.dedupMaxEntries = dedup?.maxEntries ?? 1e3;
|
|
1273
1322
|
this.dedupTtlMs = dedup?.ttlMs ?? 6e4;
|
|
1323
|
+
this.dedupSweepMs = dedup?.sweepMs;
|
|
1274
1324
|
this.dedupEnabled = dedup !== void 0;
|
|
1275
1325
|
if (!Number.isSafeInteger(this.dedupMaxEntries) || this.dedupMaxEntries <= 0) {
|
|
1276
1326
|
throw new TypeError("dedup.maxEntries must be a positive safe integer.");
|
|
@@ -1278,6 +1328,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1278
1328
|
if (!Number.isFinite(this.dedupTtlMs) || this.dedupTtlMs <= 0) {
|
|
1279
1329
|
throw new TypeError("dedup.ttlMs must be a positive finite number.");
|
|
1280
1330
|
}
|
|
1331
|
+
if (this.dedupSweepMs !== void 0 && (!Number.isFinite(this.dedupSweepMs) || this.dedupSweepMs <= 0)) {
|
|
1332
|
+
throw new TypeError("dedup.sweepMs must be a positive finite number.");
|
|
1333
|
+
}
|
|
1281
1334
|
this.cluster = new WorkerClusterRuntime({
|
|
1282
1335
|
...clusterOptions,
|
|
1283
1336
|
handlers: {
|
|
@@ -1318,11 +1371,16 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1318
1371
|
onSuspend: () => {
|
|
1319
1372
|
if (!this.stopping) this.trace.event({ type: "lifecycle", action: "suspend" });
|
|
1320
1373
|
this.trace.pause();
|
|
1374
|
+
this.persistenceRetryGeneration += 1;
|
|
1375
|
+
this.stopDedupSweep();
|
|
1376
|
+
this.stopReplayRetentionSweep();
|
|
1321
1377
|
this.suspendTransport();
|
|
1322
1378
|
},
|
|
1323
1379
|
onResume: () => {
|
|
1324
1380
|
this.trace.event({ type: "lifecycle", action: "resume" });
|
|
1325
1381
|
this.trace.start();
|
|
1382
|
+
this.startDedupSweep();
|
|
1383
|
+
this.startReplayRetentionSweep();
|
|
1326
1384
|
this.resumeTransport();
|
|
1327
1385
|
},
|
|
1328
1386
|
onDiagnostic: (event) => {
|
|
@@ -1350,6 +1408,8 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1350
1408
|
this.lastError = null;
|
|
1351
1409
|
this.trace.event({ type: "lifecycle", action: "start" });
|
|
1352
1410
|
this.trace.start();
|
|
1411
|
+
this.startDedupSweep();
|
|
1412
|
+
this.startReplayRetentionSweep();
|
|
1353
1413
|
this.updateStatus("connecting");
|
|
1354
1414
|
this.cluster.start();
|
|
1355
1415
|
const opening = this.openTransport(config, this.pendingStop ?? Promise.resolve(), true);
|
|
@@ -1474,7 +1534,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1474
1534
|
this.topicHandlers.delete(topic);
|
|
1475
1535
|
this.replayBuffers?.delete(topic);
|
|
1476
1536
|
if (this.replayPersistence?.clearTopic) {
|
|
1477
|
-
void this.replayPersistence.clearTopic(topic).catch((error) => this.
|
|
1537
|
+
void this.withPersistenceRetry("clearTopic", () => this.replayPersistence.clearTopic(topic)).catch((error) => this.reportPersistenceError(error));
|
|
1478
1538
|
}
|
|
1479
1539
|
this.cluster.unsubscribe(topic);
|
|
1480
1540
|
}
|
|
@@ -1483,9 +1543,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1483
1543
|
this.replayBuffers?.clear();
|
|
1484
1544
|
if (this.replayPersistence?.clear) {
|
|
1485
1545
|
try {
|
|
1486
|
-
await this.replayPersistence.clear();
|
|
1546
|
+
await this.withPersistenceRetry("clear", () => this.replayPersistence.clear());
|
|
1487
1547
|
} catch (error) {
|
|
1488
|
-
this.
|
|
1548
|
+
this.reportPersistenceError(error);
|
|
1489
1549
|
throw error;
|
|
1490
1550
|
}
|
|
1491
1551
|
}
|
|
@@ -1495,9 +1555,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1495
1555
|
this.replayBuffers?.delete(topic);
|
|
1496
1556
|
if (this.replayPersistence?.clearTopic) {
|
|
1497
1557
|
try {
|
|
1498
|
-
await this.replayPersistence.clearTopic(topic);
|
|
1558
|
+
await this.withPersistenceRetry("clearTopic", () => this.replayPersistence.clearTopic(topic));
|
|
1499
1559
|
} catch (error) {
|
|
1500
|
-
this.
|
|
1560
|
+
this.reportPersistenceError(error);
|
|
1501
1561
|
throw error;
|
|
1502
1562
|
}
|
|
1503
1563
|
}
|
|
@@ -1507,12 +1567,19 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1507
1567
|
if (!Number.isFinite(timestamp)) throw new TypeError("timestamp must be finite.");
|
|
1508
1568
|
if (this.replayBuffers) {
|
|
1509
1569
|
for (const [topic, messages] of this.replayBuffers) {
|
|
1510
|
-
const kept = messages.filter((message) =>
|
|
1570
|
+
const kept = messages.filter((message) => message.timestamp === void 0 || message.timestamp >= timestamp);
|
|
1511
1571
|
if (kept.length) this.replayBuffers.set(topic, kept);
|
|
1512
1572
|
else this.replayBuffers.delete(topic);
|
|
1513
1573
|
}
|
|
1514
1574
|
}
|
|
1515
|
-
if (this.replayPersistence?.clearBefore)
|
|
1575
|
+
if (this.replayPersistence?.clearBefore) {
|
|
1576
|
+
try {
|
|
1577
|
+
await this.withPersistenceRetry("clearBefore", () => this.replayPersistence.clearBefore(timestamp));
|
|
1578
|
+
} catch (error) {
|
|
1579
|
+
this.reportPersistenceError(error);
|
|
1580
|
+
throw error;
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1516
1583
|
}
|
|
1517
1584
|
/** Return bounded deduplication counters for diagnostics and health checks. */
|
|
1518
1585
|
getDedupStats() {
|
|
@@ -1570,8 +1637,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1570
1637
|
async stop() {
|
|
1571
1638
|
if (!this.started) return;
|
|
1572
1639
|
this.stopping = true;
|
|
1640
|
+
this.persistenceRetryGeneration += 1;
|
|
1573
1641
|
this.trace.event({ type: "lifecycle", action: "stop" });
|
|
1574
1642
|
this.trace.stop();
|
|
1643
|
+
this.stopDedupSweep();
|
|
1644
|
+
this.stopReplayRetentionSweep();
|
|
1575
1645
|
this.topicHandlers.clear();
|
|
1576
1646
|
this.replayBuffers?.clear();
|
|
1577
1647
|
this.cluster.stop();
|
|
@@ -1582,7 +1652,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1582
1652
|
else await this.transport.stop();
|
|
1583
1653
|
} finally {
|
|
1584
1654
|
this.transportSubscribedTopics.clear();
|
|
1585
|
-
this.
|
|
1655
|
+
this.resetDedup();
|
|
1586
1656
|
this.started = false;
|
|
1587
1657
|
this.stopping = false;
|
|
1588
1658
|
this.suspended = false;
|
|
@@ -1615,17 +1685,19 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1615
1685
|
}
|
|
1616
1686
|
isDuplicate(message) {
|
|
1617
1687
|
if (!this.dedupEnabled || !message.messageId) return false;
|
|
1618
|
-
const now =
|
|
1688
|
+
const now = this.now();
|
|
1619
1689
|
for (const [id, timestamp] of this.seenMessageIds) {
|
|
1620
1690
|
if (now - timestamp > this.dedupTtlMs) this.seenMessageIds.delete(id);
|
|
1621
1691
|
}
|
|
1622
1692
|
if (this.seenMessageIds.has(message.messageId)) {
|
|
1623
1693
|
this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
|
|
1624
1694
|
this.dedupSuppressed += 1;
|
|
1695
|
+
this.trace.recordDedupSuppressed();
|
|
1625
1696
|
return true;
|
|
1626
1697
|
}
|
|
1627
1698
|
this.seenMessageIds.set(message.messageId, now);
|
|
1628
1699
|
this.dedupAccepted += 1;
|
|
1700
|
+
this.trace.recordDedupAccepted();
|
|
1629
1701
|
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1630
1702
|
const oldest = this.seenMessageIds.keys().next().value;
|
|
1631
1703
|
if (oldest === void 0) break;
|
|
@@ -1633,6 +1705,20 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1633
1705
|
}
|
|
1634
1706
|
return false;
|
|
1635
1707
|
}
|
|
1708
|
+
startDedupSweep() {
|
|
1709
|
+
if (this.dedupSweepTimer || !this.dedupEnabled || !this.dedupSweepMs) return;
|
|
1710
|
+
this.dedupSweepTimer = setInterval(() => this.pruneExpiredDedup(), this.dedupSweepMs);
|
|
1711
|
+
}
|
|
1712
|
+
stopDedupSweep() {
|
|
1713
|
+
if (this.dedupSweepTimer) clearInterval(this.dedupSweepTimer);
|
|
1714
|
+
this.dedupSweepTimer = null;
|
|
1715
|
+
}
|
|
1716
|
+
pruneExpiredDedup() {
|
|
1717
|
+
const cutoff = this.now() - this.dedupTtlMs;
|
|
1718
|
+
for (const [id, timestamp] of this.seenMessageIds) {
|
|
1719
|
+
if (timestamp < cutoff) this.seenMessageIds.delete(id);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1636
1722
|
/** Deliver a message to every local handler registered for its topic,
|
|
1637
1723
|
* plus every handler registered with a wildcard subscription that matches
|
|
1638
1724
|
* (e.g. a handler subscribed to "chat.*" receives "chat.room.1"). */
|
|
@@ -1659,7 +1745,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1659
1745
|
buffer.push(storedMessage);
|
|
1660
1746
|
if (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
1661
1747
|
if (this.replayPersistence) {
|
|
1662
|
-
void this.replayPersistence.append(storedMessage).catch((error) => this.
|
|
1748
|
+
void this.withPersistenceRetry("append", () => this.replayPersistence.append(storedMessage)).catch((error) => this.reportPersistenceError(error));
|
|
1749
|
+
if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {
|
|
1750
|
+
this.scheduleReplayRetentionCleanup(this.now() - this.replayRetentionMs);
|
|
1751
|
+
}
|
|
1663
1752
|
}
|
|
1664
1753
|
}
|
|
1665
1754
|
async hydrateReplay() {
|
|
@@ -1667,7 +1756,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1667
1756
|
return;
|
|
1668
1757
|
}
|
|
1669
1758
|
try {
|
|
1670
|
-
|
|
1759
|
+
if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {
|
|
1760
|
+
await this.withPersistenceRetry("clearBefore", () => this.replayPersistence.clearBefore(this.now() - this.replayRetentionMs));
|
|
1761
|
+
}
|
|
1762
|
+
for (const message of await this.withPersistenceRetry("load", () => this.replayPersistence.load())) {
|
|
1671
1763
|
let buffer = this.replayBuffers.get(message.topic);
|
|
1672
1764
|
if (!buffer) {
|
|
1673
1765
|
buffer = [];
|
|
@@ -1677,7 +1769,66 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1677
1769
|
if (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
1678
1770
|
}
|
|
1679
1771
|
} catch (error) {
|
|
1680
|
-
this.
|
|
1772
|
+
this.reportPersistenceError(error);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
scheduleReplayRetentionCleanup(cutoff) {
|
|
1776
|
+
if (!this.replayPersistence?.clearBefore) return;
|
|
1777
|
+
if (this.replayRetentionCutoff === null || cutoff > this.replayRetentionCutoff) {
|
|
1778
|
+
this.replayRetentionCutoff = cutoff;
|
|
1779
|
+
}
|
|
1780
|
+
if (this.replayRetentionCleanup) return;
|
|
1781
|
+
this.replayRetentionCleanup = (async () => {
|
|
1782
|
+
while (this.replayRetentionCutoff !== null) {
|
|
1783
|
+
const nextCutoff = this.replayRetentionCutoff;
|
|
1784
|
+
this.replayRetentionCutoff = null;
|
|
1785
|
+
try {
|
|
1786
|
+
await this.replayPersistence.clearBefore(nextCutoff);
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
this.reportPersistenceError(error);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
})().finally(() => {
|
|
1792
|
+
this.replayRetentionCleanup = null;
|
|
1793
|
+
if (this.replayRetentionCutoff !== null) {
|
|
1794
|
+
this.scheduleReplayRetentionCleanup(this.replayRetentionCutoff);
|
|
1795
|
+
}
|
|
1796
|
+
});
|
|
1797
|
+
}
|
|
1798
|
+
startReplayRetentionSweep() {
|
|
1799
|
+
if (this.replayRetentionTimer || !this.replayRetentionMs || !this.replayRetentionSweepMs || !this.replayPersistence?.clearBefore) return;
|
|
1800
|
+
this.replayRetentionTimer = setInterval(() => {
|
|
1801
|
+
this.scheduleReplayRetentionCleanup(this.now() - this.replayRetentionMs);
|
|
1802
|
+
}, this.replayRetentionSweepMs);
|
|
1803
|
+
}
|
|
1804
|
+
stopReplayRetentionSweep() {
|
|
1805
|
+
if (this.replayRetentionTimer) clearInterval(this.replayRetentionTimer);
|
|
1806
|
+
this.replayRetentionTimer = null;
|
|
1807
|
+
}
|
|
1808
|
+
async withPersistenceRetry(persistenceOperation, operation) {
|
|
1809
|
+
const generation = this.persistenceRetryGeneration;
|
|
1810
|
+
let attempt = 0;
|
|
1811
|
+
let delay = this.persistenceRetryBackoffMs;
|
|
1812
|
+
while (true) {
|
|
1813
|
+
attempt += 1;
|
|
1814
|
+
try {
|
|
1815
|
+
if (generation !== this.persistenceRetryGeneration) throw new PersistenceRetryCancelledError();
|
|
1816
|
+
return await operation();
|
|
1817
|
+
} catch (error) {
|
|
1818
|
+
if (error instanceof PersistenceRetryCancelledError || generation !== this.persistenceRetryGeneration) {
|
|
1819
|
+
throw new PersistenceRetryCancelledError();
|
|
1820
|
+
}
|
|
1821
|
+
if (attempt >= this.persistenceRetryMaxAttempts) throw error;
|
|
1822
|
+
this.trace.event({
|
|
1823
|
+
type: "reliability",
|
|
1824
|
+
operation: "persistence_retry",
|
|
1825
|
+
persistenceOperation,
|
|
1826
|
+
attempt
|
|
1827
|
+
});
|
|
1828
|
+
if (delay > 0) await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1829
|
+
if (generation !== this.persistenceRetryGeneration) throw new PersistenceRetryCancelledError();
|
|
1830
|
+
delay = Math.min(delay * 2, 1600);
|
|
1831
|
+
}
|
|
1681
1832
|
}
|
|
1682
1833
|
}
|
|
1683
1834
|
/** Deliver buffered history to a newly-registered handler. For an exact
|
|
@@ -1715,7 +1866,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1715
1866
|
for (const topic of this.cluster.getSnapshot().assignedTopics) this.subscribeTransport(topic);
|
|
1716
1867
|
}
|
|
1717
1868
|
if (status === "error" && this.started && !this.stopping) {
|
|
1718
|
-
const now =
|
|
1869
|
+
const now = this.now();
|
|
1719
1870
|
if (now - this.lastRecoveryAt >= _CrossTabDataBus.RECOVERY_COOLDOWN_MS) {
|
|
1720
1871
|
this.lastRecoveryAt = now;
|
|
1721
1872
|
this.trace.event({ type: "reliability", operation: "transport_recovery", attempt: 1 });
|
|
@@ -1732,6 +1883,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1732
1883
|
this.trace.event({ type: "error", source: "transport" });
|
|
1733
1884
|
this.invokeHandlers(this.errorHandlers, (handler) => handler(error), "error handler");
|
|
1734
1885
|
}
|
|
1886
|
+
reportPersistenceError(error) {
|
|
1887
|
+
if (error instanceof PersistenceRetryCancelledError) return;
|
|
1888
|
+
this.trace.event({ type: "reliability", operation: "persistence_cleanup" });
|
|
1889
|
+
this.reportError(error);
|
|
1890
|
+
}
|
|
1735
1891
|
traceSubscription(action, topic) {
|
|
1736
1892
|
this.trace.event({
|
|
1737
1893
|
type: "subscription",
|
|
@@ -1896,11 +2052,13 @@ function parseDataBusPublication(value, fallbackTopic) {
|
|
|
1896
2052
|
if (!topic) return null;
|
|
1897
2053
|
const hasMetadataEnvelope = fallbackTopic !== void 0 && Object.prototype.hasOwnProperty.call(publication, "data") && (typeof publication.messageId === "string" || typeof publication.timestamp === "number");
|
|
1898
2054
|
const data = nested || fallbackTopic === void 0 || hasMetadataEnvelope ? publication.data : value;
|
|
2055
|
+
const messageId = typeof publication.messageId === "string" && publication.messageId.length > 0 ? publication.messageId : void 0;
|
|
2056
|
+
const timestamp = typeof publication.timestamp === "number" && Number.isFinite(publication.timestamp) ? publication.timestamp : void 0;
|
|
1899
2057
|
return {
|
|
1900
2058
|
topic,
|
|
1901
2059
|
data,
|
|
1902
|
-
...
|
|
1903
|
-
...
|
|
2060
|
+
...messageId === void 0 ? {} : { messageId },
|
|
2061
|
+
...timestamp === void 0 ? {} : { timestamp }
|
|
1904
2062
|
};
|
|
1905
2063
|
}
|
|
1906
2064
|
|
|
@@ -1920,4 +2078,4 @@ export {
|
|
|
1920
2078
|
parseDataBusPublication,
|
|
1921
2079
|
selectWorkerBackend
|
|
1922
2080
|
};
|
|
1923
|
-
//# sourceMappingURL=chunk-
|
|
2081
|
+
//# sourceMappingURL=chunk-LPS4XOK4.js.map
|