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.
@@ -588,6 +588,67 @@ var WorkerClusterRuntime = class {
588
588
  }
589
589
  this.wildcardPublishCache.set(topic, null);
590
590
  }
591
+ return this.sendControl(this.resolvePublishTarget(topic, topicKey), "PUBLISH", topic, topicKey, data, metadata);
592
+ }
593
+ /**
594
+ * Burst-friendly variant of `publish()`: packs up to N items into a single
595
+ * BroadcastChannel postMessage so the receiving owner dispatches them all in
596
+ * one tick. Per-item dedup / replay / dispatch ordering is preserved; items
597
+ * may carry their own messageId/timestamp. Empty batch is a no-op,
598
+ * single-item batch delegates to `publish()`.
599
+ */
600
+ publishBatch(topic, items) {
601
+ if (items.length === 0) return true;
602
+ if (items.length === 1) {
603
+ const single = items[0];
604
+ const metadata = single.messageId !== void 0 || single.timestamp !== void 0 ? {
605
+ ...single.messageId !== void 0 ? { messageId: single.messageId } : {},
606
+ ...single.timestamp !== void 0 ? { timestamp: single.timestamp } : {}
607
+ } : void 0;
608
+ return this.publish(topic, single.data, metadata);
609
+ }
610
+ const topicKey = this.rememberTopic(topic);
611
+ if (this.assignedTopics.has(topicKey)) {
612
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
613
+ return true;
614
+ }
615
+ const cachedPattern = this.wildcardPublishCache.get(topic);
616
+ if (cachedPattern !== void 0 && cachedPattern !== null && this.assignedTopics.has(cachedPattern)) {
617
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
618
+ return true;
619
+ }
620
+ if (cachedPattern === void 0) {
621
+ for (const pattern of this.assignedTopics.values()) {
622
+ if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
623
+ this.wildcardPublishCache.set(topic, pattern);
624
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
625
+ return true;
626
+ }
627
+ }
628
+ this.wildcardPublishCache.set(topic, null);
629
+ }
630
+ const target = this.resolvePublishTarget(topic, topicKey);
631
+ if (target === this.workerId) {
632
+ for (const item of items) this.dispatchLocalPublish(topic, topicKey, item.data, item.messageId, item.timestamp);
633
+ return true;
634
+ }
635
+ return this.send({
636
+ type: "CONTROL",
637
+ sourceWorkerId: this.workerId,
638
+ targetWorkerId: target,
639
+ action: "PUBLISH",
640
+ topic,
641
+ topicKey,
642
+ items: items.map((item) => ({
643
+ data: item.data,
644
+ ...item.messageId !== void 0 ? { messageId: item.messageId } : {},
645
+ ...item.timestamp !== void 0 ? { timestamp: item.timestamp } : {}
646
+ }))
647
+ });
648
+ }
649
+ /** Resolve which worker should receive a PUBLISH for `topic`. Centralises the
650
+ * route-owner cache lookup so `publish()` and `publishBatch()` share one path. */
651
+ resolvePublishTarget(topic, topicKey) {
591
652
  const workers = this.readWorkers();
592
653
  const route = this.readRoute(topicKey);
593
654
  const cached = this.routeOwnerCache.get(topicKey);
@@ -595,9 +656,19 @@ var WorkerClusterRuntime = class {
595
656
  if (cachedLive) this.routeOwnerCacheHits += 1;
596
657
  else this.routeOwnerCacheMisses += 1;
597
658
  const target = cachedLive ? cached.workerId : this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
598
- if (route && target === route.workerId) this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });
599
- else this.routeOwnerCache.delete(topicKey);
600
- return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
659
+ if (route && target === route.workerId) {
660
+ this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });
661
+ } else {
662
+ this.routeOwnerCache.delete(topicKey);
663
+ }
664
+ return target;
665
+ }
666
+ /** Fan out a single batched item to the local onControl path. */
667
+ dispatchLocalPublish(topic, topicKey, data, messageId, timestamp) {
668
+ void topicKey;
669
+ const meta = publicationMetadata(messageId, timestamp);
670
+ if (meta) this.handlers.onControl("PUBLISH", topic, data, meta.messageId, meta.timestamp);
671
+ else this.handlers.onControl("PUBLISH", topic, data);
601
672
  }
602
673
  /** True when `route` exists and its owner worker is among `workers`.
603
674
  * Shared by subscribe (skip re-assignment) and publish (route to owner).
@@ -717,6 +788,15 @@ var WorkerClusterRuntime = class {
717
788
  if (this.releaseHandoffOnUnsubscribe(message)) return;
718
789
  break;
719
790
  case "PUBLISH":
791
+ if (message.items && message.items.length > 0) {
792
+ for (const item of message.items) {
793
+ const itemMeta = publicationMetadata(item.messageId, item.timestamp);
794
+ if (itemMeta) this.handlers.onControl("PUBLISH", message.topic, item.data, itemMeta.messageId, itemMeta.timestamp);
795
+ else this.handlers.onControl("PUBLISH", message.topic, item.data);
796
+ }
797
+ return;
798
+ }
799
+ break;
720
800
  default:
721
801
  break;
722
802
  }
@@ -1686,6 +1766,32 @@ var CrossTabDataBus = class {
1686
1766
  );
1687
1767
  }
1688
1768
  }
1769
+ /**
1770
+ * Burst-friendly variant of `publish()`: delivers `items` in a single
1771
+ * BroadcastChannel postMessage so the receiving owner can dispatch them all
1772
+ * in one tick. Per-item dedup / replay / ordering is preserved; each item
1773
+ * may carry its own `messageId` / `timestamp` via `options`. Empty array is
1774
+ * a no-op; single-item array delegates to `publish()`.
1775
+ */
1776
+ publishBatch(topic, items) {
1777
+ this.ensureStarted();
1778
+ if (items.length === 0) return;
1779
+ if (items.length === 1) {
1780
+ const first = items[0];
1781
+ this.publish(topic, first.data, first.options);
1782
+ return;
1783
+ }
1784
+ const mapped = items.map((item) => ({
1785
+ data: item.data,
1786
+ ...item.options?.messageId !== void 0 ? { messageId: item.options.messageId } : {},
1787
+ ...item.options?.timestamp !== void 0 ? { timestamp: item.options.timestamp } : {}
1788
+ }));
1789
+ if (!this.cluster.publishBatch(topic, mapped)) {
1790
+ this.reportError(
1791
+ new Error("Failed to send the batched publish control message to the owning worker.")
1792
+ );
1793
+ }
1794
+ }
1689
1795
  /** Register a handler that fires on every transport status change. Immediately invoked with the current status. */
1690
1796
  onStatus(handler) {
1691
1797
  this.statusHandlers.add(handler);