cross-tab-worker-databus 0.7.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 +28 -1
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/centrifuge-protocol.d.ts +8 -0
- package/dist/centrifuge-protocol.d.ts.map +1 -1
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.d.ts +2 -1
- package/dist/centrifuge.d.ts.map +1 -1
- package/dist/centrifuge.js +31 -10
- package/dist/centrifuge.js.map +2 -2
- package/dist/centrifuge.shared.worker.js +38 -4
- package/dist/centrifuge.shared.worker.js.map +3 -3
- package/dist/centrifuge.worker.js +38 -4
- package/dist/centrifuge.worker.js.map +3 -3
- package/dist/{chunk-SI7N5KNJ.js → chunk-ZOPNTR4E.js} +128 -22
- package/dist/chunk-ZOPNTR4E.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +157 -30
- package/dist/cjs/centrifuge.cjs.map +4 -4
- package/dist/cjs/index.cjs +166 -28
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/core/cluster.d.ts +4 -3
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +20 -2
- 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/trace.d.ts +2 -11
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/core/types.d.ts +25 -6
- 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 +42 -8
- package/dist/index.js.map +2 -2
- package/dist/websocket.d.ts +2 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +35 -4
- package/docs/architecture.md +3 -1
- package/docs/transports.md +9 -5
- package/docs/zh/api.md +35 -4
- package/docs/zh/architecture.md +3 -1
- package/docs/zh/transports.md +8 -4
- package/package.json +1 -1
- package/dist/chunk-SI7N5KNJ.js.map +0 -7
package/dist/cjs/centrifuge.cjs
CHANGED
|
@@ -314,6 +314,13 @@ function writeJson(storage, key, value) {
|
|
|
314
314
|
} catch {
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
|
+
function publicationMetadata(messageId, timestamp) {
|
|
318
|
+
if (messageId === void 0 && timestamp === void 0) return void 0;
|
|
319
|
+
return {
|
|
320
|
+
...messageId === void 0 ? {} : { messageId },
|
|
321
|
+
...timestamp === void 0 ? {} : { timestamp }
|
|
322
|
+
};
|
|
323
|
+
}
|
|
317
324
|
function listKeys(storage, prefix) {
|
|
318
325
|
try {
|
|
319
326
|
return Array.from({ length: storage.length }, (_, index) => storage.key(index)).filter(
|
|
@@ -543,26 +550,21 @@ var WorkerClusterRuntime = class {
|
|
|
543
550
|
this.sendRouteReleased(owner.workerId, topic, topicKey, generation);
|
|
544
551
|
}
|
|
545
552
|
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
* no owner is found). Returns false when the control message could not be
|
|
549
|
-
* posted to a remote owner, so the caller can surface the failure instead of
|
|
550
|
-
* silently dropping the publication.
|
|
551
|
-
*/
|
|
552
|
-
publish(topic, data) {
|
|
553
|
+
publish(topic, data, metadataOrMessageId) {
|
|
554
|
+
const metadata = typeof metadataOrMessageId === "string" ? { messageId: metadataOrMessageId } : metadataOrMessageId;
|
|
553
555
|
const topicKey = this.rememberTopic(topic);
|
|
554
556
|
if (this.assignedTopics.has(topicKey)) {
|
|
555
|
-
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data);
|
|
557
|
+
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
556
558
|
}
|
|
557
559
|
for (const pattern of this.assignedTopics.values()) {
|
|
558
560
|
if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
|
|
559
|
-
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data);
|
|
561
|
+
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
560
562
|
}
|
|
561
563
|
}
|
|
562
564
|
const workers = this.readWorkers();
|
|
563
565
|
const route = this.readRoute(topicKey);
|
|
564
566
|
const target = this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
|
|
565
|
-
return this.sendControl(target, "PUBLISH", topic, topicKey, data);
|
|
567
|
+
return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
|
|
566
568
|
}
|
|
567
569
|
/** True when `route` exists and its owner worker is among `workers`.
|
|
568
570
|
* Shared by subscribe (skip re-assignment) and publish (route to owner).
|
|
@@ -684,7 +686,15 @@ var WorkerClusterRuntime = class {
|
|
|
684
686
|
default:
|
|
685
687
|
break;
|
|
686
688
|
}
|
|
687
|
-
|
|
689
|
+
const metadata = publicationMetadata(message.messageId, message.timestamp);
|
|
690
|
+
if (metadata) this.handlers.onControl(
|
|
691
|
+
message.action,
|
|
692
|
+
message.topic,
|
|
693
|
+
message.data,
|
|
694
|
+
metadata.messageId,
|
|
695
|
+
metadata.timestamp
|
|
696
|
+
);
|
|
697
|
+
else this.handlers.onControl(message.action, message.topic, message.data);
|
|
688
698
|
if (message.action !== "PUBLISH") this.updateLoad();
|
|
689
699
|
}
|
|
690
700
|
/**
|
|
@@ -801,7 +811,7 @@ var WorkerClusterRuntime = class {
|
|
|
801
811
|
* Local execution updates the assignment map and route synchronously, bypassing
|
|
802
812
|
* the BroadcastChannel latency.
|
|
803
813
|
*/
|
|
804
|
-
sendControl(targetWorkerId, action, topic, topicKey, data) {
|
|
814
|
+
sendControl(targetWorkerId, action, topic, topicKey, data, metadata) {
|
|
805
815
|
if (targetWorkerId === this.workerId) {
|
|
806
816
|
switch (action) {
|
|
807
817
|
case "SUBSCRIBE":
|
|
@@ -815,7 +825,14 @@ var WorkerClusterRuntime = class {
|
|
|
815
825
|
default:
|
|
816
826
|
break;
|
|
817
827
|
}
|
|
818
|
-
this.handlers.onControl(
|
|
828
|
+
if (metadata) this.handlers.onControl(
|
|
829
|
+
action,
|
|
830
|
+
topic,
|
|
831
|
+
data,
|
|
832
|
+
metadata.messageId,
|
|
833
|
+
metadata.timestamp
|
|
834
|
+
);
|
|
835
|
+
else this.handlers.onControl(action, topic, data);
|
|
819
836
|
if (action !== "PUBLISH") this.updateLoad();
|
|
820
837
|
return true;
|
|
821
838
|
}
|
|
@@ -826,7 +843,9 @@ var WorkerClusterRuntime = class {
|
|
|
826
843
|
action,
|
|
827
844
|
topic,
|
|
828
845
|
topicKey,
|
|
829
|
-
...data === void 0 ? {} : { data }
|
|
846
|
+
...data === void 0 ? {} : { data },
|
|
847
|
+
...metadata?.messageId === void 0 ? {} : { messageId: metadata.messageId },
|
|
848
|
+
...metadata?.timestamp === void 0 ? {} : { timestamp: metadata.timestamp }
|
|
830
849
|
});
|
|
831
850
|
}
|
|
832
851
|
/** Post a message on the BroadcastChannel. Returns false on postMessage failure. */
|
|
@@ -1221,6 +1240,8 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1221
1240
|
dedupTtlMs;
|
|
1222
1241
|
dedupEnabled;
|
|
1223
1242
|
seenMessageIds = /* @__PURE__ */ new Map();
|
|
1243
|
+
dedupSuppressed = 0;
|
|
1244
|
+
dedupAccepted = 0;
|
|
1224
1245
|
activeConfig;
|
|
1225
1246
|
status = "disconnected";
|
|
1226
1247
|
started = false;
|
|
@@ -1278,7 +1299,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1278
1299
|
handlers: {
|
|
1279
1300
|
// The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH
|
|
1280
1301
|
// control message — meaning the owning Worker has delegated the action to us.
|
|
1281
|
-
onControl: (action, topic, data) => {
|
|
1302
|
+
onControl: (action, topic, data, messageId, timestamp) => {
|
|
1282
1303
|
switch (action) {
|
|
1283
1304
|
case "SUBSCRIBE":
|
|
1284
1305
|
if (this.subscribeTransport(topic)) this.traceSubscription("subscribe", topic);
|
|
@@ -1287,7 +1308,14 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1287
1308
|
if (this.unsubscribeTransport(topic)) this.traceSubscription("unsubscribe", topic);
|
|
1288
1309
|
break;
|
|
1289
1310
|
case "PUBLISH":
|
|
1290
|
-
this.runTransport(() => this.transport.publish(
|
|
1311
|
+
this.runTransport(() => this.transport.publish(
|
|
1312
|
+
topic,
|
|
1313
|
+
data,
|
|
1314
|
+
messageId === void 0 && timestamp === void 0 ? void 0 : {
|
|
1315
|
+
...messageId === void 0 ? {} : { messageId },
|
|
1316
|
+
...timestamp === void 0 ? {} : { timestamp }
|
|
1317
|
+
}
|
|
1318
|
+
));
|
|
1291
1319
|
break;
|
|
1292
1320
|
default:
|
|
1293
1321
|
break;
|
|
@@ -1466,10 +1494,61 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1466
1494
|
}
|
|
1467
1495
|
this.cluster.unsubscribe(topic);
|
|
1468
1496
|
}
|
|
1497
|
+
/** Clear all in-memory replay buffers and, when supported, durable history. */
|
|
1498
|
+
async clearReplay() {
|
|
1499
|
+
this.replayBuffers?.clear();
|
|
1500
|
+
if (this.replayPersistence?.clear) {
|
|
1501
|
+
try {
|
|
1502
|
+
await this.replayPersistence.clear();
|
|
1503
|
+
} catch (error) {
|
|
1504
|
+
this.reportError(error);
|
|
1505
|
+
throw error;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
/** Clear replay history for one exact topic, including durable storage. */
|
|
1510
|
+
async clearReplayTopic(topic) {
|
|
1511
|
+
this.replayBuffers?.delete(topic);
|
|
1512
|
+
if (this.replayPersistence?.clearTopic) {
|
|
1513
|
+
try {
|
|
1514
|
+
await this.replayPersistence.clearTopic(topic);
|
|
1515
|
+
} catch (error) {
|
|
1516
|
+
this.reportError(error);
|
|
1517
|
+
throw error;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
/** Remove replay entries older than an epoch-millisecond cutoff. */
|
|
1522
|
+
async clearReplayBefore(timestamp) {
|
|
1523
|
+
if (!Number.isFinite(timestamp)) throw new TypeError("timestamp must be finite.");
|
|
1524
|
+
if (this.replayBuffers) {
|
|
1525
|
+
for (const [topic, messages] of this.replayBuffers) {
|
|
1526
|
+
const kept = messages.filter((message) => (message.timestamp ?? 0) >= timestamp);
|
|
1527
|
+
if (kept.length) this.replayBuffers.set(topic, kept);
|
|
1528
|
+
else this.replayBuffers.delete(topic);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
if (this.replayPersistence?.clearBefore) await this.replayPersistence.clearBefore(timestamp);
|
|
1532
|
+
}
|
|
1533
|
+
/** Return bounded deduplication counters for diagnostics and health checks. */
|
|
1534
|
+
getDedupStats() {
|
|
1535
|
+
return {
|
|
1536
|
+
enabled: this.dedupEnabled,
|
|
1537
|
+
tracked: this.seenMessageIds.size,
|
|
1538
|
+
suppressed: this.dedupSuppressed,
|
|
1539
|
+
accepted: this.dedupAccepted
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
/** Drop all remembered IDs and reset dedup counters. */
|
|
1543
|
+
resetDedup() {
|
|
1544
|
+
this.seenMessageIds.clear();
|
|
1545
|
+
this.dedupSuppressed = 0;
|
|
1546
|
+
this.dedupAccepted = 0;
|
|
1547
|
+
}
|
|
1469
1548
|
/** Publish a message to `topic`. The owning Worker delivers it to the transport. */
|
|
1470
|
-
publish(topic, data) {
|
|
1549
|
+
publish(topic, data, options) {
|
|
1471
1550
|
this.ensureStarted();
|
|
1472
|
-
if (!this.cluster.publish(topic, data)) {
|
|
1551
|
+
if (!this.cluster.publish(topic, data, options)) {
|
|
1473
1552
|
this.reportError(
|
|
1474
1553
|
new Error("Failed to send the publish control message to the owning worker.")
|
|
1475
1554
|
);
|
|
@@ -1556,8 +1635,13 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1556
1635
|
for (const [id, timestamp] of this.seenMessageIds) {
|
|
1557
1636
|
if (now - timestamp > this.dedupTtlMs) this.seenMessageIds.delete(id);
|
|
1558
1637
|
}
|
|
1559
|
-
if (this.seenMessageIds.has(message.messageId))
|
|
1638
|
+
if (this.seenMessageIds.has(message.messageId)) {
|
|
1639
|
+
this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
|
|
1640
|
+
this.dedupSuppressed += 1;
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1560
1643
|
this.seenMessageIds.set(message.messageId, now);
|
|
1644
|
+
this.dedupAccepted += 1;
|
|
1561
1645
|
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1562
1646
|
const oldest = this.seenMessageIds.keys().next().value;
|
|
1563
1647
|
if (oldest === void 0) break;
|
|
@@ -1587,10 +1671,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1587
1671
|
buffer = [];
|
|
1588
1672
|
this.replayBuffers.set(message.topic, buffer);
|
|
1589
1673
|
}
|
|
1590
|
-
|
|
1674
|
+
const storedMessage = message;
|
|
1675
|
+
buffer.push(storedMessage);
|
|
1591
1676
|
if (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
1592
1677
|
if (this.replayPersistence) {
|
|
1593
|
-
void this.replayPersistence.append(
|
|
1678
|
+
void this.replayPersistence.append(storedMessage).catch((error) => this.reportError(error));
|
|
1594
1679
|
}
|
|
1595
1680
|
}
|
|
1596
1681
|
async hydrateReplay() {
|
|
@@ -1807,6 +1892,28 @@ function formatRouteTrace(route) {
|
|
|
1807
1892
|
|
|
1808
1893
|
// src/centrifuge-session.ts
|
|
1809
1894
|
var import_centrifuge = require("centrifuge");
|
|
1895
|
+
|
|
1896
|
+
// src/core/publication.ts
|
|
1897
|
+
function parseDataBusPublication(value, fallbackTopic) {
|
|
1898
|
+
if (!value || typeof value !== "object") {
|
|
1899
|
+
return fallbackTopic ? { topic: fallbackTopic, data: value } : null;
|
|
1900
|
+
}
|
|
1901
|
+
const frame = value;
|
|
1902
|
+
const nested = frame.publication && typeof frame.publication === "object" ? frame.publication : null;
|
|
1903
|
+
const publication = nested ?? frame;
|
|
1904
|
+
const topic = typeof publication.topic === "string" ? publication.topic : fallbackTopic;
|
|
1905
|
+
if (!topic) return null;
|
|
1906
|
+
const hasMetadataEnvelope = fallbackTopic !== void 0 && Object.prototype.hasOwnProperty.call(publication, "data") && (typeof publication.messageId === "string" || typeof publication.timestamp === "number");
|
|
1907
|
+
const data = nested || fallbackTopic === void 0 || hasMetadataEnvelope ? publication.data : value;
|
|
1908
|
+
return {
|
|
1909
|
+
topic,
|
|
1910
|
+
data,
|
|
1911
|
+
...typeof publication.messageId === "string" ? { messageId: publication.messageId } : {},
|
|
1912
|
+
...typeof publication.timestamp === "number" ? { timestamp: publication.timestamp } : {}
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/centrifuge-session.ts
|
|
1810
1917
|
var CentrifugeSession = class {
|
|
1811
1918
|
constructor(sink) {
|
|
1812
1919
|
this.sink = sink;
|
|
@@ -1830,7 +1937,7 @@ var CentrifugeSession = class {
|
|
|
1830
1937
|
return;
|
|
1831
1938
|
case "PUBLISH":
|
|
1832
1939
|
case "PUBLISH_BIN":
|
|
1833
|
-
this.publish(message.topic, message.data);
|
|
1940
|
+
this.publish(message.topic, message.data, message.messageId, message.timestamp);
|
|
1834
1941
|
return;
|
|
1835
1942
|
case "STOP":
|
|
1836
1943
|
this.stop();
|
|
@@ -1895,9 +2002,15 @@ var CentrifugeSession = class {
|
|
|
1895
2002
|
subscription.unsubscribe();
|
|
1896
2003
|
}
|
|
1897
2004
|
/** Publish a message to the Centrifuge channel. */
|
|
1898
|
-
publish(topic, data) {
|
|
2005
|
+
publish(topic, data, messageId, timestamp) {
|
|
1899
2006
|
if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
|
|
1900
|
-
void
|
|
2007
|
+
const hasMetadata = messageId !== void 0 || timestamp !== void 0;
|
|
2008
|
+
const payload = hasMetadata ? {
|
|
2009
|
+
data,
|
|
2010
|
+
...messageId === void 0 ? {} : { messageId },
|
|
2011
|
+
...timestamp === void 0 ? {} : { timestamp }
|
|
2012
|
+
} : data;
|
|
2013
|
+
void this.client.publish(topic, payload).catch((error) => this.postError(error));
|
|
1901
2014
|
}
|
|
1902
2015
|
/** Forward a publication to the transport. Binary payloads take the
|
|
1903
2016
|
* zero-copy `MESSAGE_BIN` path when `transferable` is enabled; everything
|
|
@@ -1909,7 +2022,15 @@ var CentrifugeSession = class {
|
|
|
1909
2022
|
this.post({ type: "MESSAGE_BIN", topic, data }, [data]);
|
|
1910
2023
|
return;
|
|
1911
2024
|
}
|
|
1912
|
-
|
|
2025
|
+
const publication = parseDataBusPublication(data, topic);
|
|
2026
|
+
if (!publication) return;
|
|
2027
|
+
this.post({
|
|
2028
|
+
type: "MESSAGE",
|
|
2029
|
+
topic: publication.topic,
|
|
2030
|
+
data: publication.data,
|
|
2031
|
+
...publication.messageId === void 0 ? {} : { messageId: publication.messageId },
|
|
2032
|
+
...publication.timestamp === void 0 ? {} : { timestamp: publication.timestamp }
|
|
2033
|
+
});
|
|
1913
2034
|
}
|
|
1914
2035
|
/** Disconnect the client and clear all subscriptions. */
|
|
1915
2036
|
stop() {
|
|
@@ -2039,12 +2160,12 @@ var CentrifugeWorkerTransport = class {
|
|
|
2039
2160
|
* Publish data to `topic`. Binary data (ArrayBuffer) is sent via Transferable
|
|
2040
2161
|
* when `transferable` is enabled, avoiding a structured-clone cycle.
|
|
2041
2162
|
*/
|
|
2042
|
-
publish(topic, data) {
|
|
2163
|
+
publish(topic, data, options) {
|
|
2043
2164
|
if (this.transferable && data instanceof ArrayBuffer) {
|
|
2044
|
-
this.post({ type: "PUBLISH_BIN", topic, data }, [data]);
|
|
2165
|
+
this.post({ type: "PUBLISH_BIN", topic, data, ...publicationMetadata2(options) }, [data]);
|
|
2045
2166
|
return;
|
|
2046
2167
|
}
|
|
2047
|
-
this.post({ type: "PUBLISH", topic, data });
|
|
2168
|
+
this.post({ type: "PUBLISH", topic, data, ...publicationMetadata2(options) });
|
|
2048
2169
|
}
|
|
2049
2170
|
/**
|
|
2050
2171
|
* Gracefully stop the transport: send STOP, clean up event listeners, and
|
|
@@ -2115,8 +2236,8 @@ var CentrifugeWorkerTransport = class {
|
|
|
2115
2236
|
* and the local-session sink — all three feed into this single dispatcher. */
|
|
2116
2237
|
handleOutput(message) {
|
|
2117
2238
|
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 });
|
|
2239
|
+
if (message.type === "MESSAGE") this.handlers?.onMessage({ topic: message.topic, data: message.data, ...publicationMetadata2(message) });
|
|
2240
|
+
if (message.type === "MESSAGE_BIN") this.handlers?.onMessage({ topic: message.topic, data: message.data, ...publicationMetadata2(message) });
|
|
2120
2241
|
if (message.type === "ERROR") this.handlers?.onError(deserializeWorkerError(message.error));
|
|
2121
2242
|
}
|
|
2122
2243
|
/** Handle a Worker-level failure (crash, message decode error). Discards the
|
|
@@ -2281,4 +2402,10 @@ function deserializeWorkerError(error) {
|
|
|
2281
2402
|
if (error.context !== void 0) Object.assign(result, { context: error.context });
|
|
2282
2403
|
return result;
|
|
2283
2404
|
}
|
|
2405
|
+
function publicationMetadata2(metadata) {
|
|
2406
|
+
return {
|
|
2407
|
+
...metadata?.messageId === void 0 ? {} : { messageId: metadata.messageId },
|
|
2408
|
+
...metadata?.timestamp === void 0 ? {} : { timestamp: metadata.timestamp }
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2284
2411
|
//# sourceMappingURL=centrifuge.cjs.map
|