cross-tab-worker-databus 0.20.59 → 0.20.60

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 CHANGED
@@ -1,4 +1,12 @@
1
1
  # Changelog
2
+ ## [0.20.60] - 2026-09-04
3
+
4
+ ### Added
5
+ - `publishBatch(topic, items)` on both `CrossTabDataBus` and `WorkerClusterRuntime` packs multiple items into a single BroadcastChannel postMessage so the receiving owner can dispatch them in one tick instead of one channel post per item.
6
+ - Per-item `messageId` / `timestamp` is preserved across the batched wire frame, so dedup / replay / ordering still apply per item in the original order.
7
+ - Empty batch is a no-op; single-item batch delegates to `publish()` so callers do not have to special-case the boundary.
8
+ - Added a `data bus hot paths` bench case (`publishBatch / 1000 messages / 10 per call`) to give an upper-bound reference for the burst path.
9
+
2
10
  ## [0.20.59] - 2026-09-04
3
11
 
4
12
  ### Added
@@ -2,7 +2,7 @@ import {
2
2
  CrossTabDataBus,
3
3
  parseDataBusPublication,
4
4
  selectWorkerBackend
5
- } from "./chunk-RAUX7RZC.js";
5
+ } from "./chunk-KLHPSFVU.js";
6
6
 
7
7
  // src/centrifuge-session.ts
8
8
  import { Centrifuge } from "centrifuge";
@@ -572,6 +572,67 @@ var WorkerClusterRuntime = class {
572
572
  }
573
573
  this.wildcardPublishCache.set(topic, null);
574
574
  }
575
+ return this.sendControl(this.resolvePublishTarget(topic, topicKey), "PUBLISH", topic, topicKey, data, metadata);
576
+ }
577
+ /**
578
+ * Burst-friendly variant of `publish()`: packs up to N items into a single
579
+ * BroadcastChannel postMessage so the receiving owner dispatches them all in
580
+ * one tick. Per-item dedup / replay / dispatch ordering is preserved; items
581
+ * may carry their own messageId/timestamp. Empty batch is a no-op,
582
+ * single-item batch delegates to `publish()`.
583
+ */
584
+ publishBatch(topic, items) {
585
+ if (items.length === 0) return true;
586
+ if (items.length === 1) {
587
+ const single = items[0];
588
+ const metadata = single.messageId !== void 0 || single.timestamp !== void 0 ? {
589
+ ...single.messageId !== void 0 ? { messageId: single.messageId } : {},
590
+ ...single.timestamp !== void 0 ? { timestamp: single.timestamp } : {}
591
+ } : void 0;
592
+ return this.publish(topic, single.data, metadata);
593
+ }
594
+ const topicKey = this.rememberTopic(topic);
595
+ if (this.assignedTopics.has(topicKey)) {
596
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
597
+ return true;
598
+ }
599
+ const cachedPattern = this.wildcardPublishCache.get(topic);
600
+ if (cachedPattern !== void 0 && cachedPattern !== null && this.assignedTopics.has(cachedPattern)) {
601
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
602
+ return true;
603
+ }
604
+ if (cachedPattern === void 0) {
605
+ for (const pattern of this.assignedTopics.values()) {
606
+ if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
607
+ this.wildcardPublishCache.set(topic, pattern);
608
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
609
+ return true;
610
+ }
611
+ }
612
+ this.wildcardPublishCache.set(topic, null);
613
+ }
614
+ const target = this.resolvePublishTarget(topic, topicKey);
615
+ if (target === this.workerId) {
616
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
617
+ return true;
618
+ }
619
+ return this.send({
620
+ type: "CONTROL",
621
+ sourceWorkerId: this.workerId,
622
+ targetWorkerId: target,
623
+ action: "PUBLISH",
624
+ topic,
625
+ topicKey,
626
+ items: items.map((item) => ({
627
+ data: item.data,
628
+ ...item.messageId !== void 0 ? { messageId: item.messageId } : {},
629
+ ...item.timestamp !== void 0 ? { timestamp: item.timestamp } : {}
630
+ }))
631
+ });
632
+ }
633
+ /** Resolve which worker should receive a PUBLISH for `topic`. Centralises the
634
+ * route-owner cache lookup so `publish()` and `publishBatch()` share one path. */
635
+ resolvePublishTarget(topic, topicKey) {
575
636
  const workers = this.readWorkers();
576
637
  const route = this.readRoute(topicKey);
577
638
  const cached = this.routeOwnerCache.get(topicKey);
@@ -579,9 +640,19 @@ var WorkerClusterRuntime = class {
579
640
  if (cachedLive) this.routeOwnerCacheHits += 1;
580
641
  else this.routeOwnerCacheMisses += 1;
581
642
  const target = cachedLive ? cached.workerId : this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
582
- if (route && target === route.workerId) this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });
583
- else this.routeOwnerCache.delete(topicKey);
584
- return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
643
+ if (route && target === route.workerId) {
644
+ this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });
645
+ } else {
646
+ this.routeOwnerCache.delete(topicKey);
647
+ }
648
+ return target;
649
+ }
650
+ /** Fan out a single batched item to the local onControl path. */
651
+ dispatchLocalPublish(topic, topicKey, data, messageId, timestamp) {
652
+ void topicKey;
653
+ const meta = publicationMetadata(messageId, timestamp);
654
+ if (meta) this.handlers.onControl("PUBLISH", topic, data, meta.messageId, meta.timestamp);
655
+ else this.handlers.onControl("PUBLISH", topic, data);
585
656
  }
586
657
  /** True when `route` exists and its owner worker is among `workers`.
587
658
  * Shared by subscribe (skip re-assignment) and publish (route to owner).
@@ -701,6 +772,15 @@ var WorkerClusterRuntime = class {
701
772
  if (this.releaseHandoffOnUnsubscribe(message)) return;
702
773
  break;
703
774
  case "PUBLISH":
775
+ if (message.items && message.items.length > 0) {
776
+ for (const item of message.items) {
777
+ const itemMeta = publicationMetadata(item.messageId, item.timestamp);
778
+ if (itemMeta) this.handlers.onControl("PUBLISH", message.topic, item.data, itemMeta.messageId, itemMeta.timestamp);
779
+ else this.handlers.onControl("PUBLISH", message.topic, item.data);
780
+ }
781
+ return;
782
+ }
783
+ break;
704
784
  default:
705
785
  break;
706
786
  }
@@ -1670,6 +1750,32 @@ var CrossTabDataBus = class {
1670
1750
  );
1671
1751
  }
1672
1752
  }
1753
+ /**
1754
+ * Burst-friendly variant of `publish()`: delivers `items` in a single
1755
+ * BroadcastChannel postMessage so the receiving owner can dispatch them all
1756
+ * in one tick. Per-item dedup / replay / ordering is preserved; each item
1757
+ * may carry its own `messageId` / `timestamp` via `options`. Empty array is
1758
+ * a no-op; single-item array delegates to `publish()`.
1759
+ */
1760
+ publishBatch(topic, items) {
1761
+ this.ensureStarted();
1762
+ if (items.length === 0) return;
1763
+ if (items.length === 1) {
1764
+ const first = items[0];
1765
+ this.publish(topic, first.data, first.options);
1766
+ return;
1767
+ }
1768
+ const mapped = items.map((item) => ({
1769
+ data: item.data,
1770
+ ...item.options?.messageId !== void 0 ? { messageId: item.options.messageId } : {},
1771
+ ...item.options?.timestamp !== void 0 ? { timestamp: item.options.timestamp } : {}
1772
+ }));
1773
+ if (!this.cluster.publishBatch(topic, mapped)) {
1774
+ this.reportError(
1775
+ new Error("Failed to send the batched publish control message to the owning worker.")
1776
+ );
1777
+ }
1778
+ }
1673
1779
  /** Register a handler that fires on every transport status change. Immediately invoked with the current status. */
1674
1780
  onStatus(handler) {
1675
1781
  this.statusHandlers.add(handler);
@@ -2182,4 +2288,4 @@ export {
2182
2288
  parseDataBusPublication,
2183
2289
  selectWorkerBackend
2184
2290
  };
2185
- //# sourceMappingURL=chunk-RAUX7RZC.js.map
2291
+ //# sourceMappingURL=chunk-KLHPSFVU.js.map