cross-tab-worker-databus 0.8.0 → 0.10.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.
- package/CHANGELOG.md +21 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/centrifuge-protocol.d.ts +4 -0
- package/dist/centrifuge-protocol.d.ts.map +1 -1
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.js +29 -11
- package/dist/centrifuge.js.map +2 -2
- package/dist/centrifuge.shared.worker.js +37 -6
- package/dist/centrifuge.shared.worker.js.map +3 -3
- package/dist/centrifuge.worker.js +37 -6
- package/dist/centrifuge.worker.js.map +3 -3
- package/dist/{chunk-WWUY2FV2.js → chunk-ZOPNTR4E.js} +110 -25
- package/dist/chunk-ZOPNTR4E.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +137 -34
- package/dist/cjs/centrifuge.cjs.map +4 -4
- package/dist/cjs/index.cjs +145 -37
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/core/cluster.d.ts +3 -2
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +16 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/publication.d.ts +10 -0
- package/dist/core/publication.d.ts.map +1 -0
- package/dist/core/replay-persistence.d.ts +2 -0
- package/dist/core/replay-persistence.d.ts.map +1 -1
- package/dist/core/types.d.ts +19 -7
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -14
- package/dist/index.js.map +2 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +25 -4
- package/docs/architecture.md +3 -1
- package/docs/transports.md +9 -5
- package/docs/zh/api.md +25 -4
- package/docs/zh/architecture.md +3 -1
- package/docs/zh/transports.md +8 -4
- package/package.json +1 -1
- package/dist/chunk-WWUY2FV2.js.map +0 -7
|
@@ -298,6 +298,13 @@ function writeJson(storage, key, value) {
|
|
|
298
298
|
} catch {
|
|
299
299
|
}
|
|
300
300
|
}
|
|
301
|
+
function publicationMetadata(messageId, timestamp) {
|
|
302
|
+
if (messageId === void 0 && timestamp === void 0) return void 0;
|
|
303
|
+
return {
|
|
304
|
+
...messageId === void 0 ? {} : { messageId },
|
|
305
|
+
...timestamp === void 0 ? {} : { timestamp }
|
|
306
|
+
};
|
|
307
|
+
}
|
|
301
308
|
function listKeys(storage, prefix) {
|
|
302
309
|
try {
|
|
303
310
|
return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter(
|
|
@@ -527,26 +534,21 @@ var WorkerClusterRuntime = class {
|
|
|
527
534
|
this.sendRouteReleased(owner.workerId, topic, topicKey, generation);
|
|
528
535
|
}
|
|
529
536
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
* no owner is found). Returns false when the control message could not be
|
|
533
|
-
* posted to a remote owner, so the caller can surface the failure instead of
|
|
534
|
-
* silently dropping the publication.
|
|
535
|
-
*/
|
|
536
|
-
publish(topic, data, messageId) {
|
|
537
|
+
publish(topic, data, metadataOrMessageId) {
|
|
538
|
+
const metadata = typeof metadataOrMessageId === "string" ? { messageId: metadataOrMessageId } : metadataOrMessageId;
|
|
537
539
|
const topicKey = this.rememberTopic(topic);
|
|
538
540
|
if (this.assignedTopics.has(topicKey)) {
|
|
539
|
-
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data,
|
|
541
|
+
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
540
542
|
}
|
|
541
543
|
for (const pattern of this.assignedTopics.values()) {
|
|
542
544
|
if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
|
|
543
|
-
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data,
|
|
545
|
+
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
544
546
|
}
|
|
545
547
|
}
|
|
546
548
|
const workers = this.readWorkers();
|
|
547
549
|
const route = this.readRoute(topicKey);
|
|
548
550
|
const target = this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
|
|
549
|
-
return this.sendControl(target, "PUBLISH", topic, topicKey, data,
|
|
551
|
+
return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
|
|
550
552
|
}
|
|
551
553
|
/** True when `route` exists and its owner worker is among `workers`.
|
|
552
554
|
* Shared by subscribe (skip re-assignment) and publish (route to owner).
|
|
@@ -668,11 +670,15 @@ var WorkerClusterRuntime = class {
|
|
|
668
670
|
default:
|
|
669
671
|
break;
|
|
670
672
|
}
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
673
|
+
const metadata = publicationMetadata(message.messageId, message.timestamp);
|
|
674
|
+
if (metadata) this.handlers.onControl(
|
|
675
|
+
message.action,
|
|
676
|
+
message.topic,
|
|
677
|
+
message.data,
|
|
678
|
+
metadata.messageId,
|
|
679
|
+
metadata.timestamp
|
|
680
|
+
);
|
|
681
|
+
else this.handlers.onControl(message.action, message.topic, message.data);
|
|
676
682
|
if (message.action !== "PUBLISH") this.updateLoad();
|
|
677
683
|
}
|
|
678
684
|
/**
|
|
@@ -789,7 +795,7 @@ var WorkerClusterRuntime = class {
|
|
|
789
795
|
* Local execution updates the assignment map and route synchronously, bypassing
|
|
790
796
|
* the BroadcastChannel latency.
|
|
791
797
|
*/
|
|
792
|
-
sendControl(targetWorkerId, action, topic, topicKey, data,
|
|
798
|
+
sendControl(targetWorkerId, action, topic, topicKey, data, metadata) {
|
|
793
799
|
if (targetWorkerId === this.workerId) {
|
|
794
800
|
switch (action) {
|
|
795
801
|
case "SUBSCRIBE":
|
|
@@ -803,8 +809,14 @@ var WorkerClusterRuntime = class {
|
|
|
803
809
|
default:
|
|
804
810
|
break;
|
|
805
811
|
}
|
|
806
|
-
if (
|
|
807
|
-
|
|
812
|
+
if (metadata) this.handlers.onControl(
|
|
813
|
+
action,
|
|
814
|
+
topic,
|
|
815
|
+
data,
|
|
816
|
+
metadata.messageId,
|
|
817
|
+
metadata.timestamp
|
|
818
|
+
);
|
|
819
|
+
else this.handlers.onControl(action, topic, data);
|
|
808
820
|
if (action !== "PUBLISH") this.updateLoad();
|
|
809
821
|
return true;
|
|
810
822
|
}
|
|
@@ -816,7 +828,8 @@ var WorkerClusterRuntime = class {
|
|
|
816
828
|
topic,
|
|
817
829
|
topicKey,
|
|
818
830
|
...data === void 0 ? {} : { data },
|
|
819
|
-
...messageId === void 0 ? {} : { messageId }
|
|
831
|
+
...metadata?.messageId === void 0 ? {} : { messageId: metadata.messageId },
|
|
832
|
+
...metadata?.timestamp === void 0 ? {} : { timestamp: metadata.timestamp }
|
|
820
833
|
});
|
|
821
834
|
}
|
|
822
835
|
/** Post a message on the BroadcastChannel. Returns false on postMessage failure. */
|
|
@@ -1211,6 +1224,8 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1211
1224
|
dedupTtlMs;
|
|
1212
1225
|
dedupEnabled;
|
|
1213
1226
|
seenMessageIds = /* @__PURE__ */ new Map();
|
|
1227
|
+
dedupSuppressed = 0;
|
|
1228
|
+
dedupAccepted = 0;
|
|
1214
1229
|
activeConfig;
|
|
1215
1230
|
status = "disconnected";
|
|
1216
1231
|
started = false;
|
|
@@ -1268,7 +1283,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1268
1283
|
handlers: {
|
|
1269
1284
|
// The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH
|
|
1270
1285
|
// control message — meaning the owning Worker has delegated the action to us.
|
|
1271
|
-
onControl: (action, topic, data, messageId) => {
|
|
1286
|
+
onControl: (action, topic, data, messageId, timestamp) => {
|
|
1272
1287
|
switch (action) {
|
|
1273
1288
|
case "SUBSCRIBE":
|
|
1274
1289
|
if (this.subscribeTransport(topic)) this.traceSubscription("subscribe", topic);
|
|
@@ -1277,7 +1292,14 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1277
1292
|
if (this.unsubscribeTransport(topic)) this.traceSubscription("unsubscribe", topic);
|
|
1278
1293
|
break;
|
|
1279
1294
|
case "PUBLISH":
|
|
1280
|
-
this.runTransport(() => this.transport.publish(
|
|
1295
|
+
this.runTransport(() => this.transport.publish(
|
|
1296
|
+
topic,
|
|
1297
|
+
data,
|
|
1298
|
+
messageId === void 0 && timestamp === void 0 ? void 0 : {
|
|
1299
|
+
...messageId === void 0 ? {} : { messageId },
|
|
1300
|
+
...timestamp === void 0 ? {} : { timestamp }
|
|
1301
|
+
}
|
|
1302
|
+
));
|
|
1281
1303
|
break;
|
|
1282
1304
|
default:
|
|
1283
1305
|
break;
|
|
@@ -1468,10 +1490,49 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1468
1490
|
}
|
|
1469
1491
|
}
|
|
1470
1492
|
}
|
|
1493
|
+
/** Clear replay history for one exact topic, including durable storage. */
|
|
1494
|
+
async clearReplayTopic(topic) {
|
|
1495
|
+
this.replayBuffers?.delete(topic);
|
|
1496
|
+
if (this.replayPersistence?.clearTopic) {
|
|
1497
|
+
try {
|
|
1498
|
+
await this.replayPersistence.clearTopic(topic);
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
this.reportError(error);
|
|
1501
|
+
throw error;
|
|
1502
|
+
}
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/** Remove replay entries older than an epoch-millisecond cutoff. */
|
|
1506
|
+
async clearReplayBefore(timestamp) {
|
|
1507
|
+
if (!Number.isFinite(timestamp)) throw new TypeError("timestamp must be finite.");
|
|
1508
|
+
if (this.replayBuffers) {
|
|
1509
|
+
for (const [topic, messages] of this.replayBuffers) {
|
|
1510
|
+
const kept = messages.filter((message) => (message.timestamp ?? 0) >= timestamp);
|
|
1511
|
+
if (kept.length) this.replayBuffers.set(topic, kept);
|
|
1512
|
+
else this.replayBuffers.delete(topic);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
if (this.replayPersistence?.clearBefore) await this.replayPersistence.clearBefore(timestamp);
|
|
1516
|
+
}
|
|
1517
|
+
/** Return bounded deduplication counters for diagnostics and health checks. */
|
|
1518
|
+
getDedupStats() {
|
|
1519
|
+
return {
|
|
1520
|
+
enabled: this.dedupEnabled,
|
|
1521
|
+
tracked: this.seenMessageIds.size,
|
|
1522
|
+
suppressed: this.dedupSuppressed,
|
|
1523
|
+
accepted: this.dedupAccepted
|
|
1524
|
+
};
|
|
1525
|
+
}
|
|
1526
|
+
/** Drop all remembered IDs and reset dedup counters. */
|
|
1527
|
+
resetDedup() {
|
|
1528
|
+
this.seenMessageIds.clear();
|
|
1529
|
+
this.dedupSuppressed = 0;
|
|
1530
|
+
this.dedupAccepted = 0;
|
|
1531
|
+
}
|
|
1471
1532
|
/** Publish a message to `topic`. The owning Worker delivers it to the transport. */
|
|
1472
1533
|
publish(topic, data, options) {
|
|
1473
1534
|
this.ensureStarted();
|
|
1474
|
-
if (!this.cluster.publish(topic, data, options
|
|
1535
|
+
if (!this.cluster.publish(topic, data, options)) {
|
|
1475
1536
|
this.reportError(
|
|
1476
1537
|
new Error("Failed to send the publish control message to the owning worker.")
|
|
1477
1538
|
);
|
|
@@ -1560,9 +1621,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1560
1621
|
}
|
|
1561
1622
|
if (this.seenMessageIds.has(message.messageId)) {
|
|
1562
1623
|
this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
|
|
1624
|
+
this.dedupSuppressed += 1;
|
|
1563
1625
|
return true;
|
|
1564
1626
|
}
|
|
1565
1627
|
this.seenMessageIds.set(message.messageId, now);
|
|
1628
|
+
this.dedupAccepted += 1;
|
|
1566
1629
|
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1567
1630
|
const oldest = this.seenMessageIds.keys().next().value;
|
|
1568
1631
|
if (oldest === void 0) break;
|
|
@@ -1592,10 +1655,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1592
1655
|
buffer = [];
|
|
1593
1656
|
this.replayBuffers.set(message.topic, buffer);
|
|
1594
1657
|
}
|
|
1595
|
-
|
|
1658
|
+
const storedMessage = message;
|
|
1659
|
+
buffer.push(storedMessage);
|
|
1596
1660
|
if (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
1597
1661
|
if (this.replayPersistence) {
|
|
1598
|
-
void this.replayPersistence.append(
|
|
1662
|
+
void this.replayPersistence.append(storedMessage).catch((error) => this.reportError(error));
|
|
1599
1663
|
}
|
|
1600
1664
|
}
|
|
1601
1665
|
async hydrateReplay() {
|
|
@@ -1820,6 +1884,26 @@ function selectWorkerBackend(mode, availability = {}) {
|
|
|
1820
1884
|
return hasDedicated ? "dedicated" : hasShared ? "shared" : "local";
|
|
1821
1885
|
}
|
|
1822
1886
|
|
|
1887
|
+
// src/core/publication.ts
|
|
1888
|
+
function parseDataBusPublication(value, fallbackTopic) {
|
|
1889
|
+
if (!value || typeof value !== "object") {
|
|
1890
|
+
return fallbackTopic ? { topic: fallbackTopic, data: value } : null;
|
|
1891
|
+
}
|
|
1892
|
+
const frame = value;
|
|
1893
|
+
const nested = frame.publication && typeof frame.publication === "object" ? frame.publication : null;
|
|
1894
|
+
const publication = nested ?? frame;
|
|
1895
|
+
const topic = typeof publication.topic === "string" ? publication.topic : fallbackTopic;
|
|
1896
|
+
if (!topic) return null;
|
|
1897
|
+
const hasMetadataEnvelope = fallbackTopic !== void 0 && Object.prototype.hasOwnProperty.call(publication, "data") && (typeof publication.messageId === "string" || typeof publication.timestamp === "number");
|
|
1898
|
+
const data = nested || fallbackTopic === void 0 || hasMetadataEnvelope ? publication.data : value;
|
|
1899
|
+
return {
|
|
1900
|
+
topic,
|
|
1901
|
+
data,
|
|
1902
|
+
...typeof publication.messageId === "string" ? { messageId: publication.messageId } : {},
|
|
1903
|
+
...typeof publication.timestamp === "number" ? { timestamp: publication.timestamp } : {}
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1823
1907
|
export {
|
|
1824
1908
|
createBrowserEnvironment,
|
|
1825
1909
|
getOrCreateTabId,
|
|
@@ -1833,6 +1917,7 @@ export {
|
|
|
1833
1917
|
topicMatchesPattern,
|
|
1834
1918
|
WorkerClusterRuntime,
|
|
1835
1919
|
CrossTabDataBus,
|
|
1920
|
+
parseDataBusPublication,
|
|
1836
1921
|
selectWorkerBackend
|
|
1837
1922
|
};
|
|
1838
|
-
//# sourceMappingURL=chunk-
|
|
1923
|
+
//# sourceMappingURL=chunk-ZOPNTR4E.js.map
|