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.
@@ -537,6 +537,7 @@ var WorkerClusterRuntime = class {
537
537
  projectedLoads.set(owner.workerId, (projectedLoads.get(owner.workerId) ?? owner.load) + 1);
538
538
  const generation = (previous?.generation ?? 0) + 1;
539
539
  this.writeRoute(topicKey, owner, previous?.workerId, generation);
540
+ this.handlers.onDiagnostic?.({ operation: "route_migration", topic });
540
541
  this.flushStorage();
541
542
  this.handlers.onControl("UNSUBSCRIBE", topic);
542
543
  this.sendRouteReleased(owner.workerId, topic, topicKey, generation);
@@ -921,6 +922,8 @@ var WorkerClusterRuntime = class {
921
922
  ...route,
922
923
  confirmedAt: this.environment.now()
923
924
  });
925
+ const topic = this.knownTopics.get(topicKey);
926
+ if (topic) this.handlers.onDiagnostic?.({ operation: "route_ack", topic });
924
927
  }
925
928
  /** Remove routes whose topic has no subscribers and whose TTL has expired. */
926
929
  cleanupOrphanedRoutes(workers) {
@@ -1214,6 +1217,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
1214
1217
  initialConfig;
1215
1218
  hasInitialConfig;
1216
1219
  trace;
1220
+ dedupMaxEntries;
1221
+ dedupTtlMs;
1222
+ dedupEnabled;
1223
+ seenMessageIds = /* @__PURE__ */ new Map();
1217
1224
  activeConfig;
1218
1225
  status = "disconnected";
1219
1226
  started = false;
@@ -1252,11 +1259,20 @@ var CrossTabDataBus = class _CrossTabDataBus {
1252
1259
  this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
1253
1260
  this.replayPersistence = replay?.persistence ?? null;
1254
1261
  this.replayHydration = this.hydrateReplay();
1255
- const { autoStart, initialConfig, trace, transport, ...clusterOptions } = options;
1262
+ const { autoStart, initialConfig, trace, transport, dedup, ...clusterOptions } = options;
1256
1263
  this.transport = transport;
1257
1264
  this.initialConfig = initialConfig;
1258
1265
  this.hasInitialConfig = "initialConfig" in options;
1259
1266
  this.trace = new DataBusTraceReporter(trace);
1267
+ this.dedupMaxEntries = dedup?.maxEntries ?? 1e3;
1268
+ this.dedupTtlMs = dedup?.ttlMs ?? 6e4;
1269
+ this.dedupEnabled = dedup !== void 0;
1270
+ if (!Number.isSafeInteger(this.dedupMaxEntries) || this.dedupMaxEntries <= 0) {
1271
+ throw new TypeError("dedup.maxEntries must be a positive safe integer.");
1272
+ }
1273
+ if (!Number.isFinite(this.dedupTtlMs) || this.dedupTtlMs <= 0) {
1274
+ throw new TypeError("dedup.ttlMs must be a positive finite number.");
1275
+ }
1260
1276
  this.cluster = new WorkerClusterRuntime({
1261
1277
  ...clusterOptions,
1262
1278
  handlers: {
@@ -1296,6 +1312,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
1296
1312
  this.trace.event({ type: "lifecycle", action: "resume" });
1297
1313
  this.trace.start();
1298
1314
  this.resumeTransport();
1315
+ },
1316
+ onDiagnostic: (event) => {
1317
+ this.trace.event({ type: "reliability", ...event });
1299
1318
  }
1300
1319
  }
1301
1320
  });
@@ -1442,6 +1461,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
1442
1461
  if (handlers.size > 0) return;
1443
1462
  this.topicHandlers.delete(topic);
1444
1463
  this.replayBuffers?.delete(topic);
1464
+ if (this.replayPersistence?.clearTopic) {
1465
+ void this.replayPersistence.clearTopic(topic).catch((error) => this.reportError(error));
1466
+ }
1445
1467
  this.cluster.unsubscribe(topic);
1446
1468
  }
1447
1469
  /** Publish a message to `topic`. The owning Worker delivers it to the transport. */
@@ -1497,6 +1519,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1497
1519
  else await this.transport.stop();
1498
1520
  } finally {
1499
1521
  this.transportSubscribedTopics.clear();
1522
+ this.seenMessageIds.clear();
1500
1523
  this.started = false;
1501
1524
  this.stopping = false;
1502
1525
  this.suspended = false;
@@ -1514,6 +1537,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1514
1537
  * and dispatches locally.
1515
1538
  */
1516
1539
  handleTransportMessage(message) {
1540
+ if (this.isDuplicate(message)) return;
1517
1541
  this.trace.recordReceived(message.topic);
1518
1542
  if (!this.cluster.isAssigned(message.topic)) {
1519
1543
  this.trace.recordDiscarded(message.topic);
@@ -1526,6 +1550,21 @@ var CrossTabDataBus = class _CrossTabDataBus {
1526
1550
  }
1527
1551
  this.trace.recordDiscarded(message.topic);
1528
1552
  }
1553
+ isDuplicate(message) {
1554
+ if (!this.dedupEnabled || !message.messageId) return false;
1555
+ const now = Date.now();
1556
+ for (const [id, timestamp] of this.seenMessageIds) {
1557
+ if (now - timestamp > this.dedupTtlMs) this.seenMessageIds.delete(id);
1558
+ }
1559
+ if (this.seenMessageIds.has(message.messageId)) return true;
1560
+ this.seenMessageIds.set(message.messageId, now);
1561
+ while (this.seenMessageIds.size > this.dedupMaxEntries) {
1562
+ const oldest = this.seenMessageIds.keys().next().value;
1563
+ if (oldest === void 0) break;
1564
+ this.seenMessageIds.delete(oldest);
1565
+ }
1566
+ return false;
1567
+ }
1529
1568
  /** Deliver a message to every local handler registered for its topic,
1530
1569
  * plus every handler registered with a wildcard subscription that matches
1531
1570
  * (e.g. a handler subscribed to "chat.*" receives "chat.room.1"). */
@@ -1610,6 +1649,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1610
1649
  const now = Date.now();
1611
1650
  if (now - this.lastRecoveryAt >= _CrossTabDataBus.RECOVERY_COOLDOWN_MS) {
1612
1651
  this.lastRecoveryAt = now;
1652
+ this.trace.event({ type: "reliability", operation: "transport_recovery", attempt: 1 });
1613
1653
  setTimeout(() => {
1614
1654
  if (this.stopping || !this.started || this.suspended) return;
1615
1655
  if (this.status !== "error") return;