cross-tab-worker-databus 0.7.0 → 0.8.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.
@@ -574,20 +574,20 @@ var WorkerClusterRuntime = class {
574
574
  * posted to a remote owner, so the caller can surface the failure instead of
575
575
  * silently dropping the publication.
576
576
  */
577
- publish(topic, data) {
577
+ publish(topic, data, messageId) {
578
578
  const topicKey = this.rememberTopic(topic);
579
579
  if (this.assignedTopics.has(topicKey)) {
580
- return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data);
580
+ return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, messageId);
581
581
  }
582
582
  for (const pattern of this.assignedTopics.values()) {
583
583
  if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
584
- return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data);
584
+ return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, messageId);
585
585
  }
586
586
  }
587
587
  const workers = this.readWorkers();
588
588
  const route = this.readRoute(topicKey);
589
589
  const target = this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
590
- return this.sendControl(target, "PUBLISH", topic, topicKey, data);
590
+ return this.sendControl(target, "PUBLISH", topic, topicKey, data, messageId);
591
591
  }
592
592
  /** True when `route` exists and its owner worker is among `workers`.
593
593
  * Shared by subscribe (skip re-assignment) and publish (route to owner).
@@ -709,7 +709,11 @@ var WorkerClusterRuntime = class {
709
709
  default:
710
710
  break;
711
711
  }
712
- this.handlers.onControl(message.action, message.topic, message.data);
712
+ if (message.messageId === void 0) {
713
+ this.handlers.onControl(message.action, message.topic, message.data);
714
+ } else {
715
+ this.handlers.onControl(message.action, message.topic, message.data, message.messageId);
716
+ }
713
717
  if (message.action !== "PUBLISH") this.updateLoad();
714
718
  }
715
719
  /**
@@ -826,7 +830,7 @@ var WorkerClusterRuntime = class {
826
830
  * Local execution updates the assignment map and route synchronously, bypassing
827
831
  * the BroadcastChannel latency.
828
832
  */
829
- sendControl(targetWorkerId, action, topic, topicKey, data) {
833
+ sendControl(targetWorkerId, action, topic, topicKey, data, messageId) {
830
834
  if (targetWorkerId === this.workerId) {
831
835
  switch (action) {
832
836
  case "SUBSCRIBE":
@@ -840,7 +844,8 @@ var WorkerClusterRuntime = class {
840
844
  default:
841
845
  break;
842
846
  }
843
- this.handlers.onControl(action, topic, data);
847
+ if (messageId === void 0) this.handlers.onControl(action, topic, data);
848
+ else this.handlers.onControl(action, topic, data, messageId);
844
849
  if (action !== "PUBLISH") this.updateLoad();
845
850
  return true;
846
851
  }
@@ -851,7 +856,8 @@ var WorkerClusterRuntime = class {
851
856
  action,
852
857
  topic,
853
858
  topicKey,
854
- ...data === void 0 ? {} : { data }
859
+ ...data === void 0 ? {} : { data },
860
+ ...messageId === void 0 ? {} : { messageId }
855
861
  });
856
862
  }
857
863
  /** Post a message on the BroadcastChannel. Returns false on postMessage failure. */
@@ -1303,7 +1309,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1303
1309
  handlers: {
1304
1310
  // The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH
1305
1311
  // control message — meaning the owning Worker has delegated the action to us.
1306
- onControl: (action, topic, data) => {
1312
+ onControl: (action, topic, data, messageId) => {
1307
1313
  switch (action) {
1308
1314
  case "SUBSCRIBE":
1309
1315
  if (this.subscribeTransport(topic)) this.traceSubscription("subscribe", topic);
@@ -1312,7 +1318,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1312
1318
  if (this.unsubscribeTransport(topic)) this.traceSubscription("unsubscribe", topic);
1313
1319
  break;
1314
1320
  case "PUBLISH":
1315
- this.runTransport(() => this.transport.publish(topic, data));
1321
+ this.runTransport(() => this.transport.publish(topic, data, messageId ? { messageId } : void 0));
1316
1322
  break;
1317
1323
  default:
1318
1324
  break;
@@ -1491,10 +1497,22 @@ var CrossTabDataBus = class _CrossTabDataBus {
1491
1497
  }
1492
1498
  this.cluster.unsubscribe(topic);
1493
1499
  }
1500
+ /** Clear all in-memory replay buffers and, when supported, durable history. */
1501
+ async clearReplay() {
1502
+ this.replayBuffers?.clear();
1503
+ if (this.replayPersistence?.clear) {
1504
+ try {
1505
+ await this.replayPersistence.clear();
1506
+ } catch (error) {
1507
+ this.reportError(error);
1508
+ throw error;
1509
+ }
1510
+ }
1511
+ }
1494
1512
  /** Publish a message to `topic`. The owning Worker delivers it to the transport. */
1495
- publish(topic, data) {
1513
+ publish(topic, data, options) {
1496
1514
  this.ensureStarted();
1497
- if (!this.cluster.publish(topic, data)) {
1515
+ if (!this.cluster.publish(topic, data, options?.messageId)) {
1498
1516
  this.reportError(
1499
1517
  new Error("Failed to send the publish control message to the owning worker.")
1500
1518
  );
@@ -1581,7 +1599,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
1581
1599
  for (const [id, timestamp] of this.seenMessageIds) {
1582
1600
  if (now - timestamp > this.dedupTtlMs) this.seenMessageIds.delete(id);
1583
1601
  }
1584
- if (this.seenMessageIds.has(message.messageId)) return true;
1602
+ if (this.seenMessageIds.has(message.messageId)) {
1603
+ this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
1604
+ return true;
1605
+ }
1585
1606
  this.seenMessageIds.set(message.messageId, now);
1586
1607
  while (this.seenMessageIds.size > this.dedupMaxEntries) {
1587
1608
  const oldest = this.seenMessageIds.keys().next().value;
@@ -1943,12 +1964,12 @@ var WebSocketTransport = class {
1943
1964
  this.sendFrame({ op: "unsubscribe", topic });
1944
1965
  }
1945
1966
  /** Publish `data` to `topic` as a JSON frame. Requires an open socket. */
1946
- publish(topic, data) {
1967
+ publish(topic, data, options) {
1947
1968
  if (data instanceof ArrayBuffer) {
1948
- this.sendBinaryFrame(topic, data);
1969
+ this.sendBinaryFrame(topic, data, options?.messageId);
1949
1970
  return;
1950
1971
  }
1951
- this.sendFrame({ op: "publish", topic, data });
1972
+ this.sendFrame({ op: "publish", topic, data, ...options?.messageId ? { messageId: options.messageId } : {} });
1952
1973
  }
1953
1974
  /** Close the socket and drop all state. Safe to call multiple times. */
1954
1975
  stop() {
@@ -1968,7 +1989,11 @@ var WebSocketTransport = class {
1968
1989
  }
1969
1990
  this.socket.send(JSON.stringify(payload));
1970
1991
  }
1971
- sendBinaryFrame(topic, data) {
1992
+ sendBinaryFrame(topic, data, messageId) {
1993
+ if (messageId) {
1994
+ this.sendFrame({ op: "publish", topic, data: Array.from(new Uint8Array(data)), messageId });
1995
+ return;
1996
+ }
1972
1997
  if (this.socket?.readyState !== WS_OPEN) {
1973
1998
  this.handlers?.onError(new Error('WebSocket is not open; dropped "publish" frame.'));
1974
1999
  return;
@@ -2010,7 +2035,12 @@ var WebSocketTransport = class {
2010
2035
  if (!parsed || typeof parsed !== "object") return;
2011
2036
  const frame = parsed;
2012
2037
  if (typeof frame.topic !== "string") return;
2013
- this.handlers?.onMessage({ topic: frame.topic, data: frame.data });
2038
+ const message = {
2039
+ topic: frame.topic,
2040
+ data: frame.data,
2041
+ ...typeof frame.messageId === "string" ? { messageId: frame.messageId } : {}
2042
+ };
2043
+ this.handlers?.onMessage(message);
2014
2044
  }
2015
2045
  };
2016
2046
  function defaultWebSocketFactory(url, protocols) {