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
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, messageId) {
|
|
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,11 +686,15 @@ var WorkerClusterRuntime = class {
|
|
|
684
686
|
default:
|
|
685
687
|
break;
|
|
686
688
|
}
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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);
|
|
692
698
|
if (message.action !== "PUBLISH") this.updateLoad();
|
|
693
699
|
}
|
|
694
700
|
/**
|
|
@@ -805,7 +811,7 @@ var WorkerClusterRuntime = class {
|
|
|
805
811
|
* Local execution updates the assignment map and route synchronously, bypassing
|
|
806
812
|
* the BroadcastChannel latency.
|
|
807
813
|
*/
|
|
808
|
-
sendControl(targetWorkerId, action, topic, topicKey, data,
|
|
814
|
+
sendControl(targetWorkerId, action, topic, topicKey, data, metadata) {
|
|
809
815
|
if (targetWorkerId === this.workerId) {
|
|
810
816
|
switch (action) {
|
|
811
817
|
case "SUBSCRIBE":
|
|
@@ -819,8 +825,14 @@ var WorkerClusterRuntime = class {
|
|
|
819
825
|
default:
|
|
820
826
|
break;
|
|
821
827
|
}
|
|
822
|
-
if (
|
|
823
|
-
|
|
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);
|
|
824
836
|
if (action !== "PUBLISH") this.updateLoad();
|
|
825
837
|
return true;
|
|
826
838
|
}
|
|
@@ -832,7 +844,8 @@ var WorkerClusterRuntime = class {
|
|
|
832
844
|
topic,
|
|
833
845
|
topicKey,
|
|
834
846
|
...data === void 0 ? {} : { data },
|
|
835
|
-
...messageId === void 0 ? {} : { messageId }
|
|
847
|
+
...metadata?.messageId === void 0 ? {} : { messageId: metadata.messageId },
|
|
848
|
+
...metadata?.timestamp === void 0 ? {} : { timestamp: metadata.timestamp }
|
|
836
849
|
});
|
|
837
850
|
}
|
|
838
851
|
/** Post a message on the BroadcastChannel. Returns false on postMessage failure. */
|
|
@@ -1227,6 +1240,8 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1227
1240
|
dedupTtlMs;
|
|
1228
1241
|
dedupEnabled;
|
|
1229
1242
|
seenMessageIds = /* @__PURE__ */ new Map();
|
|
1243
|
+
dedupSuppressed = 0;
|
|
1244
|
+
dedupAccepted = 0;
|
|
1230
1245
|
activeConfig;
|
|
1231
1246
|
status = "disconnected";
|
|
1232
1247
|
started = false;
|
|
@@ -1284,7 +1299,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1284
1299
|
handlers: {
|
|
1285
1300
|
// The cluster calls `onControl` when it receives a SUBSCRIBE/UNSUBSCRIBE/PUBLISH
|
|
1286
1301
|
// control message — meaning the owning Worker has delegated the action to us.
|
|
1287
|
-
onControl: (action, topic, data, messageId) => {
|
|
1302
|
+
onControl: (action, topic, data, messageId, timestamp) => {
|
|
1288
1303
|
switch (action) {
|
|
1289
1304
|
case "SUBSCRIBE":
|
|
1290
1305
|
if (this.subscribeTransport(topic)) this.traceSubscription("subscribe", topic);
|
|
@@ -1293,7 +1308,14 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1293
1308
|
if (this.unsubscribeTransport(topic)) this.traceSubscription("unsubscribe", topic);
|
|
1294
1309
|
break;
|
|
1295
1310
|
case "PUBLISH":
|
|
1296
|
-
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
|
+
));
|
|
1297
1319
|
break;
|
|
1298
1320
|
default:
|
|
1299
1321
|
break;
|
|
@@ -1484,10 +1506,49 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1484
1506
|
}
|
|
1485
1507
|
}
|
|
1486
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
|
+
}
|
|
1487
1548
|
/** Publish a message to `topic`. The owning Worker delivers it to the transport. */
|
|
1488
1549
|
publish(topic, data, options) {
|
|
1489
1550
|
this.ensureStarted();
|
|
1490
|
-
if (!this.cluster.publish(topic, data, options
|
|
1551
|
+
if (!this.cluster.publish(topic, data, options)) {
|
|
1491
1552
|
this.reportError(
|
|
1492
1553
|
new Error("Failed to send the publish control message to the owning worker.")
|
|
1493
1554
|
);
|
|
@@ -1576,9 +1637,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1576
1637
|
}
|
|
1577
1638
|
if (this.seenMessageIds.has(message.messageId)) {
|
|
1578
1639
|
this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
|
|
1640
|
+
this.dedupSuppressed += 1;
|
|
1579
1641
|
return true;
|
|
1580
1642
|
}
|
|
1581
1643
|
this.seenMessageIds.set(message.messageId, now);
|
|
1644
|
+
this.dedupAccepted += 1;
|
|
1582
1645
|
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1583
1646
|
const oldest = this.seenMessageIds.keys().next().value;
|
|
1584
1647
|
if (oldest === void 0) break;
|
|
@@ -1608,10 +1671,11 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1608
1671
|
buffer = [];
|
|
1609
1672
|
this.replayBuffers.set(message.topic, buffer);
|
|
1610
1673
|
}
|
|
1611
|
-
|
|
1674
|
+
const storedMessage = message;
|
|
1675
|
+
buffer.push(storedMessage);
|
|
1612
1676
|
if (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
1613
1677
|
if (this.replayPersistence) {
|
|
1614
|
-
void this.replayPersistence.append(
|
|
1678
|
+
void this.replayPersistence.append(storedMessage).catch((error) => this.reportError(error));
|
|
1615
1679
|
}
|
|
1616
1680
|
}
|
|
1617
1681
|
async hydrateReplay() {
|
|
@@ -1828,6 +1892,28 @@ function formatRouteTrace(route) {
|
|
|
1828
1892
|
|
|
1829
1893
|
// src/centrifuge-session.ts
|
|
1830
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
|
|
1831
1917
|
var CentrifugeSession = class {
|
|
1832
1918
|
constructor(sink) {
|
|
1833
1919
|
this.sink = sink;
|
|
@@ -1851,7 +1937,7 @@ var CentrifugeSession = class {
|
|
|
1851
1937
|
return;
|
|
1852
1938
|
case "PUBLISH":
|
|
1853
1939
|
case "PUBLISH_BIN":
|
|
1854
|
-
this.publish(message.topic, message.data, message.messageId);
|
|
1940
|
+
this.publish(message.topic, message.data, message.messageId, message.timestamp);
|
|
1855
1941
|
return;
|
|
1856
1942
|
case "STOP":
|
|
1857
1943
|
this.stop();
|
|
@@ -1916,9 +2002,14 @@ var CentrifugeSession = class {
|
|
|
1916
2002
|
subscription.unsubscribe();
|
|
1917
2003
|
}
|
|
1918
2004
|
/** Publish a message to the Centrifuge channel. */
|
|
1919
|
-
publish(topic, data, messageId) {
|
|
2005
|
+
publish(topic, data, messageId, timestamp) {
|
|
1920
2006
|
if (!this.client) return this.postError(new Error("Centrifuge client is not initialized."));
|
|
1921
|
-
const
|
|
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;
|
|
1922
2013
|
void this.client.publish(topic, payload).catch((error) => this.postError(error));
|
|
1923
2014
|
}
|
|
1924
2015
|
/** Forward a publication to the transport. Binary payloads take the
|
|
@@ -1931,9 +2022,15 @@ var CentrifugeSession = class {
|
|
|
1931
2022
|
this.post({ type: "MESSAGE_BIN", topic, data }, [data]);
|
|
1932
2023
|
return;
|
|
1933
2024
|
}
|
|
1934
|
-
const
|
|
1935
|
-
|
|
1936
|
-
this.post({
|
|
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
|
+
});
|
|
1937
2034
|
}
|
|
1938
2035
|
/** Disconnect the client and clear all subscriptions. */
|
|
1939
2036
|
stop() {
|
|
@@ -2065,10 +2162,10 @@ var CentrifugeWorkerTransport = class {
|
|
|
2065
2162
|
*/
|
|
2066
2163
|
publish(topic, data, options) {
|
|
2067
2164
|
if (this.transferable && data instanceof ArrayBuffer) {
|
|
2068
|
-
this.post({ type: "PUBLISH_BIN", topic, data, ...options
|
|
2165
|
+
this.post({ type: "PUBLISH_BIN", topic, data, ...publicationMetadata2(options) }, [data]);
|
|
2069
2166
|
return;
|
|
2070
2167
|
}
|
|
2071
|
-
this.post({ type: "PUBLISH", topic, data, ...options
|
|
2168
|
+
this.post({ type: "PUBLISH", topic, data, ...publicationMetadata2(options) });
|
|
2072
2169
|
}
|
|
2073
2170
|
/**
|
|
2074
2171
|
* Gracefully stop the transport: send STOP, clean up event listeners, and
|
|
@@ -2139,8 +2236,8 @@ var CentrifugeWorkerTransport = class {
|
|
|
2139
2236
|
* and the local-session sink — all three feed into this single dispatcher. */
|
|
2140
2237
|
handleOutput(message) {
|
|
2141
2238
|
if (message.type === "STATUS") this.handlers?.onStatus(message.status);
|
|
2142
|
-
if (message.type === "MESSAGE") this.handlers?.onMessage({ topic: message.topic, data: message.data, ...message
|
|
2143
|
-
if (message.type === "MESSAGE_BIN") this.handlers?.onMessage({ topic: message.topic, data: message.data, ...message
|
|
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) });
|
|
2144
2241
|
if (message.type === "ERROR") this.handlers?.onError(deserializeWorkerError(message.error));
|
|
2145
2242
|
}
|
|
2146
2243
|
/** Handle a Worker-level failure (crash, message decode error). Discards the
|
|
@@ -2305,4 +2402,10 @@ function deserializeWorkerError(error) {
|
|
|
2305
2402
|
if (error.context !== void 0) Object.assign(result, { context: error.context });
|
|
2306
2403
|
return result;
|
|
2307
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
|
+
}
|
|
2308
2411
|
//# sourceMappingURL=centrifuge.cjs.map
|