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.
@@ -549,20 +549,20 @@ var WorkerClusterRuntime = class {
549
549
  * posted to a remote owner, so the caller can surface the failure instead of
550
550
  * silently dropping the publication.
551
551
  */
552
- publish(topic, data) {
552
+ publish(topic, data, messageId) {
553
553
  const topicKey = this.rememberTopic(topic);
554
554
  if (this.assignedTopics.has(topicKey)) {
555
- return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data);
555
+ return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, messageId);
556
556
  }
557
557
  for (const pattern of this.assignedTopics.values()) {
558
558
  if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
559
- return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data);
559
+ return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, messageId);
560
560
  }
561
561
  }
562
562
  const workers = this.readWorkers();
563
563
  const route = this.readRoute(topicKey);
564
564
  const target = this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
565
- return this.sendControl(target, "PUBLISH", topic, topicKey, data);
565
+ return this.sendControl(target, "PUBLISH", topic, topicKey, data, messageId);
566
566
  }
567
567
  /** True when `route` exists and its owner worker is among `workers`.
568
568
  * Shared by subscribe (skip re-assignment) and publish (route to owner).
@@ -684,7 +684,11 @@ var WorkerClusterRuntime = class {
684
684
  default:
685
685
  break;
686
686
  }
687
- this.handlers.onControl(message.action, message.topic, message.data);
687
+ if (message.messageId === void 0) {
688
+ this.handlers.onControl(message.action, message.topic, message.data);
689
+ } else {
690
+ this.handlers.onControl(message.action, message.topic, message.data, message.messageId);
691
+ }
688
692
  if (message.action !== "PUBLISH") this.updateLoad();
689
693
  }
690
694
  /**
@@ -801,7 +805,7 @@ var WorkerClusterRuntime = class {
801
805
  * Local execution updates the assignment map and route synchronously, bypassing
802
806
  * the BroadcastChannel latency.
803
807
  */
804
- sendControl(targetWorkerId, action, topic, topicKey, data) {
808
+ sendControl(targetWorkerId, action, topic, topicKey, data, messageId) {
805
809
  if (targetWorkerId === this.workerId) {
806
810
  switch (action) {
807
811
  case "SUBSCRIBE":
@@ -815,7 +819,8 @@ var WorkerClusterRuntime = class {
815
819
  default:
816
820
  break;
817
821
  }
818
- this.handlers.onControl(action, topic, data);
822
+ if (messageId === void 0) this.handlers.onControl(action, topic, data);
823
+ else this.handlers.onControl(action, topic, data, messageId);
819
824
  if (action !== "PUBLISH") this.updateLoad();
820
825
  return true;
821
826
  }
@@ -826,7 +831,8 @@ var WorkerClusterRuntime = class {
826
831
  action,
827
832
  topic,
828
833
  topicKey,
829
- ...data === void 0 ? {} : { data }
834
+ ...data === void 0 ? {} : { data },
835
+ ...messageId === void 0 ? {} : { messageId }
830
836
  });
831
837
  }
832
838
  /** Post a message on the BroadcastChannel. Returns false on postMessage failure. */
@@ -1278,7 +1284,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1278
1284
  handlers: {
1279
1285
  // The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH
1280
1286
  // control message — meaning the owning Worker has delegated the action to us.
1281
- onControl: (action, topic, data) => {
1287
+ onControl: (action, topic, data, messageId) => {
1282
1288
  switch (action) {
1283
1289
  case "SUBSCRIBE":
1284
1290
  if (this.subscribeTransport(topic)) this.traceSubscription("subscribe", topic);
@@ -1287,7 +1293,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
1287
1293
  if (this.unsubscribeTransport(topic)) this.traceSubscription("unsubscribe", topic);
1288
1294
  break;
1289
1295
  case "PUBLISH":
1290
- this.runTransport(() => this.transport.publish(topic, data));
1296
+ this.runTransport(() => this.transport.publish(topic, data, messageId ? { messageId } : void 0));
1291
1297
  break;
1292
1298
  default:
1293
1299
  break;
@@ -1466,10 +1472,22 @@ var CrossTabDataBus = class _CrossTabDataBus {
1466
1472
  }
1467
1473
  this.cluster.unsubscribe(topic);
1468
1474
  }
1475
+ /** Clear all in-memory replay buffers and, when supported, durable history. */
1476
+ async clearReplay() {
1477
+ this.replayBuffers?.clear();
1478
+ if (this.replayPersistence?.clear) {
1479
+ try {
1480
+ await this.replayPersistence.clear();
1481
+ } catch (error) {
1482
+ this.reportError(error);
1483
+ throw error;
1484
+ }
1485
+ }
1486
+ }
1469
1487
  /** Publish a message to `topic`. The owning Worker delivers it to the transport. */
1470
- publish(topic, data) {
1488
+ publish(topic, data, options) {
1471
1489
  this.ensureStarted();
1472
- if (!this.cluster.publish(topic, data)) {
1490
+ if (!this.cluster.publish(topic, data, options?.messageId)) {
1473
1491
  this.reportError(
1474
1492
  new Error("Failed to send the publish control message to the owning worker.")
1475
1493
  );
@@ -1556,7 +1574,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
1556
1574
  for (const [id, timestamp] of this.seenMessageIds) {
1557
1575
  if (now - timestamp > this.dedupTtlMs) this.seenMessageIds.delete(id);
1558
1576
  }
1559
- if (this.seenMessageIds.has(message.messageId)) return true;
1577
+ if (this.seenMessageIds.has(message.messageId)) {
1578
+ this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
1579
+ return true;
1580
+ }
1560
1581
  this.seenMessageIds.set(message.messageId, now);
1561
1582
  while (this.seenMessageIds.size > this.dedupMaxEntries) {
1562
1583
  const oldest = this.seenMessageIds.keys().next().value;
@@ -1830,7 +1851,7 @@ var CentrifugeSession = class {
1830
1851
  return;
1831
1852
  case "PUBLISH":
1832
1853
  case "PUBLISH_BIN":
1833
- this.publish(message.topic, message.data);
1854
+ this.publish(message.topic, message.data, message.messageId);
1834
1855
  return;
1835
1856
  case "STOP":
1836
1857
  this.stop();
@@ -1895,9 +1916,10 @@ var CentrifugeSession = class {
1895
1916
  subscription.unsubscribe();
1896
1917
  }
1897
1918
  /** Publish a message to the Centrifuge channel. */
1898
- publish(topic, data) {
1919
+ publish(topic, data, messageId) {
1899
1920
  if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
1900
- void this.client.publish(topic, data).catch((error) => this.postError(error));
1921
+ const payload = messageId ? { data, messageId } : data;
1922
+ void this.client.publish(topic, payload).catch((error) => this.postError(error));
1901
1923
  }
1902
1924
  /** Forward a publication to the transport. Binary payloads take the
1903
1925
  * zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything
@@ -1909,7 +1931,9 @@ var CentrifugeSession = class {
1909
1931
  this.post({ type: "MESSAGE_BIN", topic, data }, [data]);
1910
1932
  return;
1911
1933
  }
1912
- this.post({ type: "MESSAGE", topic, data });
1934
+ const messageId = data && typeof data === "object" && typeof data.messageId === "string" ? data.messageId : void 0;
1935
+ const payload = messageId && Object.prototype.hasOwnProperty.call(data, "data") ? data.data : data;
1936
+ this.post({ type: "MESSAGE", topic, data: payload, ...messageId ? { messageId } : {} });
1913
1937
  }
1914
1938
  /** Disconnect the client and clear all subscriptions. */
1915
1939
  stop() {
@@ -2039,12 +2063,12 @@ var CentrifugeWorkerTransport = class {
2039
2063
  * Publish data to `topic`. Binary data (ArrayBuffer) is sent via Transferable
2040
2064
  * when `transferable` is enabled, avoiding a structured-clone cycle.
2041
2065
  */
2042
- publish(topic, data) {
2066
+ publish(topic, data, options) {
2043
2067
  if (this.transferable && data instanceof ArrayBuffer) {
2044
- this.post({ type: "PUBLISH_BIN", topic, data }, [data]);
2068
+ this.post({ type: "PUBLISH_BIN", topic, data, ...options?.messageId ? { messageId: options.messageId } : {} }, [data]);
2045
2069
  return;
2046
2070
  }
2047
- this.post({ type: "PUBLISH", topic, data });
2071
+ this.post({ type: "PUBLISH", topic, data, ...options?.messageId ? { messageId: options.messageId } : {} });
2048
2072
  }
2049
2073
  /**
2050
2074
  * Gracefully stop the transport: send STOP, clean up event listeners, and
@@ -2115,8 +2139,8 @@ var CentrifugeWorkerTransport = class {
2115
2139
  * and the local-session sink — all three feed into this single dispatcher. */
2116
2140
  handleOutput(message) {
2117
2141
  if (message.type === "STATUS") this.handlers?.onStatus(message.status);
2118
- if (message.type === "MESSAGE") this.handlers?.onMessage({ topic: message.topic, data: message.data });
2119
- if (message.type === "MESSAGE_BIN") this.handlers?.onMessage({ topic: message.topic, data: message.data });
2142
+ if (message.type === "MESSAGE") this.handlers?.onMessage({ topic: message.topic, data: message.data, ...message.messageId ? { messageId: message.messageId } : {} });
2143
+ if (message.type === "MESSAGE_BIN") this.handlers?.onMessage({ topic: message.topic, data: message.data, ...message.messageId ? { messageId: message.messageId } : {} });
2120
2144
  if (message.type === "ERROR") this.handlers?.onError(deserializeWorkerError(message.error));
2121
2145
  }
2122
2146
  /** Handle a Worker-level failure (crash, message decode error). Discards the