cross-tab-worker-databus 0.20.59 → 0.20.61
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 +21 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-RAUX7RZC.js → chunk-VB4P6DS6.js} +123 -12
- package/dist/chunk-VB4P6DS6.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +122 -11
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +122 -11
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +26 -4
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +11 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/types.d.ts +22 -2
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/roadmap.md +12 -1
- package/docs/zh/roadmap.md +12 -1
- package/package.json +1 -1
- package/dist/chunk-RAUX7RZC.js.map +0 -7
package/dist/cjs/centrifuge.cjs
CHANGED
|
@@ -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)
|
|
599
|
-
|
|
600
|
-
|
|
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).
|
|
@@ -606,9 +677,12 @@ var WorkerClusterRuntime = class {
|
|
|
606
677
|
routeOwnerIsLive(route, workers) {
|
|
607
678
|
return Boolean(route && workers.some((worker) => worker.workerId === route.workerId));
|
|
608
679
|
}
|
|
609
|
-
/** Broadcast an event to every tab — used to fan out transport publications.
|
|
610
|
-
|
|
611
|
-
|
|
680
|
+
/** Broadcast an event to every tab — used to fan out transport publications.
|
|
681
|
+
* `originTabId` (when set) is propagated across the BroadcastChannel hop so
|
|
682
|
+
* listeners can attribute the event to its source tab even after fan-out. */
|
|
683
|
+
broadcastEvent(eventType, payload, originTabId) {
|
|
684
|
+
const effectiveOriginTabId = originTabId ?? this.tabId;
|
|
685
|
+
this.send({ type: "EVENT", sourceWorkerId: this.workerId, eventType, payload, originTabId: effectiveOriginTabId });
|
|
612
686
|
}
|
|
613
687
|
isAssigned(topic) {
|
|
614
688
|
const topicKey = createOpaqueKey(topic);
|
|
@@ -696,7 +770,7 @@ var WorkerClusterRuntime = class {
|
|
|
696
770
|
case "ROUTE_RELEASED":
|
|
697
771
|
return this.handleRouteReleasedMessage(message);
|
|
698
772
|
case "EVENT":
|
|
699
|
-
this.handlers.onEvent(message.eventType, message.payload, message.sourceWorkerId);
|
|
773
|
+
this.handlers.onEvent(message.eventType, message.payload, message.sourceWorkerId, message.originTabId);
|
|
700
774
|
return;
|
|
701
775
|
case "REGISTRY":
|
|
702
776
|
default:
|
|
@@ -717,6 +791,15 @@ var WorkerClusterRuntime = class {
|
|
|
717
791
|
if (this.releaseHandoffOnUnsubscribe(message)) return;
|
|
718
792
|
break;
|
|
719
793
|
case "PUBLISH":
|
|
794
|
+
if (message.items && message.items.length > 0) {
|
|
795
|
+
for (const item of message.items) {
|
|
796
|
+
const itemMeta = publicationMetadata(item.messageId, item.timestamp);
|
|
797
|
+
if (itemMeta) this.handlers.onControl("PUBLISH", message.topic, item.data, itemMeta.messageId, itemMeta.timestamp);
|
|
798
|
+
else this.handlers.onControl("PUBLISH", message.topic, item.data);
|
|
799
|
+
}
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
break;
|
|
720
803
|
default:
|
|
721
804
|
break;
|
|
722
805
|
}
|
|
@@ -1439,9 +1522,10 @@ var CrossTabDataBus = class {
|
|
|
1439
1522
|
// typed `unknown` at the cluster boundary (the cluster is transport-
|
|
1440
1523
|
// agnostic); here we narrow it to DataBusMessage — the sender is our
|
|
1441
1524
|
// own broadcastEvent call, which always posts a DataBusMessage.
|
|
1442
|
-
onEvent: (eventType, payload) => {
|
|
1525
|
+
onEvent: (eventType, payload, _sourceWorkerId, originTabId) => {
|
|
1443
1526
|
if (eventType !== PUBLICATION_EVENT) return;
|
|
1444
|
-
const
|
|
1527
|
+
const incoming = payload;
|
|
1528
|
+
const message = incoming.originTabId !== void 0 ? incoming : originTabId !== void 0 ? { ...incoming, originTabId } : incoming;
|
|
1445
1529
|
if (this.cluster.hasLocalSubscriber(message.topic)) this.dispatch(message);
|
|
1446
1530
|
},
|
|
1447
1531
|
onSuspend: () => {
|
|
@@ -1686,6 +1770,32 @@ var CrossTabDataBus = class {
|
|
|
1686
1770
|
);
|
|
1687
1771
|
}
|
|
1688
1772
|
}
|
|
1773
|
+
/**
|
|
1774
|
+
* Burst-friendly variant of `publish()`: delivers `items` in a single
|
|
1775
|
+
* BroadcastChannel postMessage so the receiving owner can dispatch them all
|
|
1776
|
+
* in one tick. Per-item dedup / replay / ordering is preserved; each item
|
|
1777
|
+
* may carry its own `messageId` / `timestamp` via `options`. Empty array is
|
|
1778
|
+
* a no-op; single-item array delegates to `publish()`.
|
|
1779
|
+
*/
|
|
1780
|
+
publishBatch(topic, items) {
|
|
1781
|
+
this.ensureStarted();
|
|
1782
|
+
if (items.length === 0) return;
|
|
1783
|
+
if (items.length === 1) {
|
|
1784
|
+
const first = items[0];
|
|
1785
|
+
this.publish(topic, first.data, first.options);
|
|
1786
|
+
return;
|
|
1787
|
+
}
|
|
1788
|
+
const mapped = items.map((item) => ({
|
|
1789
|
+
data: item.data,
|
|
1790
|
+
...item.options?.messageId !== void 0 ? { messageId: item.options.messageId } : {},
|
|
1791
|
+
...item.options?.timestamp !== void 0 ? { timestamp: item.options.timestamp } : {}
|
|
1792
|
+
}));
|
|
1793
|
+
if (!this.cluster.publishBatch(topic, mapped)) {
|
|
1794
|
+
this.reportError(
|
|
1795
|
+
new Error("Failed to send the batched publish control message to the owning worker.")
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1689
1799
|
/** Register a handler that fires on every transport status change. Immediately invoked with the current status. */
|
|
1690
1800
|
onStatus(handler) {
|
|
1691
1801
|
this.statusHandlers.add(handler);
|
|
@@ -1778,9 +1888,10 @@ var CrossTabDataBus = class {
|
|
|
1778
1888
|
this.trace.recordDiscarded(message.topic);
|
|
1779
1889
|
return;
|
|
1780
1890
|
}
|
|
1781
|
-
this.cluster.
|
|
1891
|
+
const stamped = message.originTabId === void 0 ? { ...message, originTabId: this.cluster.tabId } : message;
|
|
1892
|
+
this.cluster.broadcastEvent(PUBLICATION_EVENT, stamped, stamped.originTabId);
|
|
1782
1893
|
if (this.cluster.hasLocalSubscriber(message.topic)) {
|
|
1783
|
-
this.dispatch(
|
|
1894
|
+
this.dispatch(stamped);
|
|
1784
1895
|
return;
|
|
1785
1896
|
}
|
|
1786
1897
|
this.trace.recordDiscarded(message.topic);
|