cross-tab-worker-databus 0.6.0 → 0.7.0
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 +15 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-YUSQSNCX.js → chunk-SI7N5KNJ.js} +42 -2
- package/dist/chunk-SI7N5KNJ.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +41 -1
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +59 -1
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +5 -0
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +12 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/replay-persistence.d.ts +4 -0
- package/dist/core/replay-persistence.d.ts.map +1 -1
- package/dist/core/trace.d.ts +19 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -1
- package/dist/index.js.map +2 -2
- package/docs/api.md +3 -0
- package/docs/capabilities.md +3 -3
- package/docs/roadmap.md +7 -7
- package/docs/zh/api.md +1 -0
- package/docs/zh/capabilities.md +3 -3
- package/docs/zh/roadmap.md +7 -7
- package/package.json +1 -1
- package/dist/chunk-YUSQSNCX.js.map +0 -7
package/dist/cjs/index.cjs
CHANGED
|
@@ -562,6 +562,7 @@ var WorkerClusterRuntime = class {
|
|
|
562
562
|
projectedLoads.set(owner.workerId, (projectedLoads.get(owner.workerId) ?? owner.load) + 1);
|
|
563
563
|
const generation = (previous?.generation ?? 0) + 1;
|
|
564
564
|
this.writeRoute(topicKey, owner, previous?.workerId, generation);
|
|
565
|
+
this.handlers.onDiagnostic?.({ operation: "route_migration", topic });
|
|
565
566
|
this.flushStorage();
|
|
566
567
|
this.handlers.onControl("UNSUBSCRIBE", topic);
|
|
567
568
|
this.sendRouteReleased(owner.workerId, topic, topicKey, generation);
|
|
@@ -946,6 +947,8 @@ var WorkerClusterRuntime = class {
|
|
|
946
947
|
...route,
|
|
947
948
|
confirmedAt: this.environment.now()
|
|
948
949
|
});
|
|
950
|
+
const topic = this.knownTopics.get(topicKey);
|
|
951
|
+
if (topic) this.handlers.onDiagnostic?.({ operation: "route_ack", topic });
|
|
949
952
|
}
|
|
950
953
|
/** Remove routes whose topic has no subscribers and whose TTL has expired. */
|
|
951
954
|
cleanupOrphanedRoutes(workers) {
|
|
@@ -1239,6 +1242,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1239
1242
|
initialConfig;
|
|
1240
1243
|
hasInitialConfig;
|
|
1241
1244
|
trace;
|
|
1245
|
+
dedupMaxEntries;
|
|
1246
|
+
dedupTtlMs;
|
|
1247
|
+
dedupEnabled;
|
|
1248
|
+
seenMessageIds = /* @__PURE__ */ new Map();
|
|
1242
1249
|
activeConfig;
|
|
1243
1250
|
status = "disconnected";
|
|
1244
1251
|
started = false;
|
|
@@ -1277,11 +1284,20 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1277
1284
|
this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
|
|
1278
1285
|
this.replayPersistence = replay?.persistence ?? null;
|
|
1279
1286
|
this.replayHydration = this.hydrateReplay();
|
|
1280
|
-
const { autoStart, initialConfig, trace, transport, ...clusterOptions } = options;
|
|
1287
|
+
const { autoStart, initialConfig, trace, transport, dedup, ...clusterOptions } = options;
|
|
1281
1288
|
this.transport = transport;
|
|
1282
1289
|
this.initialConfig = initialConfig;
|
|
1283
1290
|
this.hasInitialConfig = "initialConfig" in options;
|
|
1284
1291
|
this.trace = new DataBusTraceReporter(trace);
|
|
1292
|
+
this.dedupMaxEntries = dedup?.maxEntries ?? 1e3;
|
|
1293
|
+
this.dedupTtlMs = dedup?.ttlMs ?? 6e4;
|
|
1294
|
+
this.dedupEnabled = dedup !== void 0;
|
|
1295
|
+
if (!Number.isSafeInteger(this.dedupMaxEntries) || this.dedupMaxEntries <= 0) {
|
|
1296
|
+
throw new TypeError("dedup.maxEntries must be a positive safe integer.");
|
|
1297
|
+
}
|
|
1298
|
+
if (!Number.isFinite(this.dedupTtlMs) || this.dedupTtlMs <= 0) {
|
|
1299
|
+
throw new TypeError("dedup.ttlMs must be a positive finite number.");
|
|
1300
|
+
}
|
|
1285
1301
|
this.cluster = new WorkerClusterRuntime({
|
|
1286
1302
|
...clusterOptions,
|
|
1287
1303
|
handlers: {
|
|
@@ -1321,6 +1337,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1321
1337
|
this.trace.event({ type: "lifecycle", action: "resume" });
|
|
1322
1338
|
this.trace.start();
|
|
1323
1339
|
this.resumeTransport();
|
|
1340
|
+
},
|
|
1341
|
+
onDiagnostic: (event) => {
|
|
1342
|
+
this.trace.event({ type: "reliability", ...event });
|
|
1324
1343
|
}
|
|
1325
1344
|
}
|
|
1326
1345
|
});
|
|
@@ -1467,6 +1486,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1467
1486
|
if (handlers.size > 0) return;
|
|
1468
1487
|
this.topicHandlers.delete(topic);
|
|
1469
1488
|
this.replayBuffers?.delete(topic);
|
|
1489
|
+
if (this.replayPersistence?.clearTopic) {
|
|
1490
|
+
void this.replayPersistence.clearTopic(topic).catch((error) => this.reportError(error));
|
|
1491
|
+
}
|
|
1470
1492
|
this.cluster.unsubscribe(topic);
|
|
1471
1493
|
}
|
|
1472
1494
|
/** Publish a message to `topic`. The owning Worker delivers it to the transport. */
|
|
@@ -1522,6 +1544,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1522
1544
|
else await this.transport.stop();
|
|
1523
1545
|
} finally {
|
|
1524
1546
|
this.transportSubscribedTopics.clear();
|
|
1547
|
+
this.seenMessageIds.clear();
|
|
1525
1548
|
this.started = false;
|
|
1526
1549
|
this.stopping = false;
|
|
1527
1550
|
this.suspended = false;
|
|
@@ -1539,6 +1562,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1539
1562
|
* and dispatches locally.
|
|
1540
1563
|
*/
|
|
1541
1564
|
handleTransportMessage(message) {
|
|
1565
|
+
if (this.isDuplicate(message)) return;
|
|
1542
1566
|
this.trace.recordReceived(message.topic);
|
|
1543
1567
|
if (!this.cluster.isAssigned(message.topic)) {
|
|
1544
1568
|
this.trace.recordDiscarded(message.topic);
|
|
@@ -1551,6 +1575,21 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1551
1575
|
}
|
|
1552
1576
|
this.trace.recordDiscarded(message.topic);
|
|
1553
1577
|
}
|
|
1578
|
+
isDuplicate(message) {
|
|
1579
|
+
if (!this.dedupEnabled || !message.messageId) return false;
|
|
1580
|
+
const now = Date.now();
|
|
1581
|
+
for (const [id, timestamp] of this.seenMessageIds) {
|
|
1582
|
+
if (now - timestamp > this.dedupTtlMs) this.seenMessageIds.delete(id);
|
|
1583
|
+
}
|
|
1584
|
+
if (this.seenMessageIds.has(message.messageId)) return true;
|
|
1585
|
+
this.seenMessageIds.set(message.messageId, now);
|
|
1586
|
+
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1587
|
+
const oldest = this.seenMessageIds.keys().next().value;
|
|
1588
|
+
if (oldest === void 0) break;
|
|
1589
|
+
this.seenMessageIds.delete(oldest);
|
|
1590
|
+
}
|
|
1591
|
+
return false;
|
|
1592
|
+
}
|
|
1554
1593
|
/** Deliver a message to every local handler registered for its topic,
|
|
1555
1594
|
* plus every handler registered with a wildcard subscription that matches
|
|
1556
1595
|
* (e.g. a handler subscribed to "chat.*" receives "chat.room.1"). */
|
|
@@ -1635,6 +1674,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1635
1674
|
const now = Date.now();
|
|
1636
1675
|
if (now - this.lastRecoveryAt >= _CrossTabDataBus.RECOVERY_COOLDOWN_MS) {
|
|
1637
1676
|
this.lastRecoveryAt = now;
|
|
1677
|
+
this.trace.event({ type: "reliability", operation: "transport_recovery", attempt: 1 });
|
|
1638
1678
|
setTimeout(() => {
|
|
1639
1679
|
if (this.stopping || !this.started || this.suspended) return;
|
|
1640
1680
|
if (this.status !== "error") return;
|
|
@@ -1834,6 +1874,24 @@ function createIndexedDbReplayPersistence(options) {
|
|
|
1834
1874
|
transaction.oncomplete = () => resolve();
|
|
1835
1875
|
transaction.onerror = () => reject(transaction.error ?? new Error("Failed to persist replay history."));
|
|
1836
1876
|
});
|
|
1877
|
+
},
|
|
1878
|
+
async clear() {
|
|
1879
|
+
const db = await open();
|
|
1880
|
+
await new Promise((resolve, reject) => {
|
|
1881
|
+
const transaction = db.transaction(storeName, "readwrite");
|
|
1882
|
+
transaction.objectStore(storeName).clear();
|
|
1883
|
+
transaction.oncomplete = () => resolve();
|
|
1884
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("Failed to clear replay history."));
|
|
1885
|
+
});
|
|
1886
|
+
},
|
|
1887
|
+
async clearTopic(topic) {
|
|
1888
|
+
const db = await open();
|
|
1889
|
+
await new Promise((resolve, reject) => {
|
|
1890
|
+
const transaction = db.transaction(storeName, "readwrite");
|
|
1891
|
+
transaction.objectStore(storeName).delete(topic);
|
|
1892
|
+
transaction.oncomplete = () => resolve();
|
|
1893
|
+
transaction.onerror = () => reject(transaction.error ?? new Error("Failed to clear topic replay history."));
|
|
1894
|
+
});
|
|
1837
1895
|
}
|
|
1838
1896
|
};
|
|
1839
1897
|
}
|