cross-tab-worker-databus 0.10.0 → 0.11.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 +13 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-ZOPNTR4E.js → chunk-NX76TAV3.js} +28 -2
- package/dist/{chunk-ZOPNTR4E.js.map → chunk-NX76TAV3.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +27 -1
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +27 -1
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/data-bus.d.ts +3 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/trace.d.ts +7 -0
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/api.md +2 -2
- package/docs/architecture.md +4 -0
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +1 -1
- package/docs/roadmap.md +15 -1
- package/docs/zh/api.md +2 -2
- package/docs/zh/architecture.md +4 -0
- package/docs/zh/capabilities.md +2 -2
- package/docs/zh/configuration.md +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
本项目遵循 [Semantic Versioning](https://semver.org/);变更记录格式参考 [Keep a Changelog](https://keepachangelog.com/)。
|
|
4
4
|
|
|
5
|
+
## [0.11.0] - 2026-09-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Automatic durable replay retention via `replay.retentionMs` when a persistence adapter supports `clearBefore`.
|
|
10
|
+
- Deduplication accepted/suppressed counters in periodic `message_metrics` trace snapshots.
|
|
11
|
+
- Service Worker runtime boundary documented as intentionally deferred pending stable browser lifetime semantics.
|
|
12
|
+
|
|
13
|
+
### Compatibility
|
|
14
|
+
|
|
15
|
+
- Existing replay adapters remain valid; `retentionMs` is ignored when an adapter does not implement `clearBefore`.
|
|
16
|
+
- Existing trace consumers can continue reading the original metrics fields; dedup counters are additive.
|
|
17
|
+
|
|
5
18
|
## [0.10.0] - 2026-09-01
|
|
6
19
|
|
|
7
20
|
### Added
|
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ By default each tab holds its own Dedicated Worker; when configured with `worker
|
|
|
22
22
|
- New Topics are assigned to the least-loaded eligible Worker
|
|
23
23
|
- Wildcard subscriptions: `chat.*` and `*` patterns match concrete topics at dispatch
|
|
24
24
|
- Transport-neutral publication metadata (`messageId`, `timestamp`) with canonical WebSocket/Centrifuge envelopes and legacy frame compatibility
|
|
25
|
+
- Optional durable replay retention (`replay.retentionMs`) and deduplication outcome metrics in trace snapshots
|
|
25
26
|
- Built-in zero-dependency native WebSocket transport (`createWebSocketDataBus`) for plain-WebSocket servers
|
|
26
27
|
- Optional React hooks adapter (`cross-tab-worker-databus/hooks`): StrictMode-safe bus lifecycle, auto-cleanup subscriptions
|
|
27
28
|
- Optional Vue 3 composables adapter (`cross-tab-worker-databus/vue`): lifecycle-safe bus, subscription, and status composables
|
package/README.zh.md
CHANGED
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
- 新 Topic 分配给负载最低的候选 Worker
|
|
23
23
|
- 通配符订阅:`chat.*` 与 `*` pattern 在分发侧匹配具体 Topic
|
|
24
24
|
- 传输无关的 publication 元数据(`messageId`、`timestamp`),支持标准 WebSocket/Centrifuge envelope,并兼容旧帧格式
|
|
25
|
+
- 可选的 durable replay retention(`replay.retentionMs`),以及 trace snapshot 中的去重结果指标
|
|
25
26
|
- 内置零依赖的原生 WebSocket 传输(`createWebSocketDataBus`),适配普通 WebSocket 服务器
|
|
26
27
|
- 可选的 React hooks 适配层(`cross-tab-worker-databus/hooks`):StrictMode 安全的 bus 生命周期与自动清理订阅
|
|
27
28
|
- 可选的 Vue 3 composables 适配层(`cross-tab-worker-databus/vue`):安全管理 bus 生命周期、订阅和状态
|
package/dist/centrifuge.js
CHANGED
|
@@ -1050,6 +1050,8 @@ var DataBusTraceReporter = class {
|
|
|
1050
1050
|
// Bucketed histogram: bucket index = floor(delayMs / 50), capped at 19.
|
|
1051
1051
|
latencyBuckets = new Array(LATENCY_BUCKET_COUNT).fill(0);
|
|
1052
1052
|
latencySumMs = 0;
|
|
1053
|
+
dedupAccepted = 0;
|
|
1054
|
+
dedupSuppressed = 0;
|
|
1053
1055
|
constructor(options, now = Date.now) {
|
|
1054
1056
|
this.enabled = options?.enabled ?? false;
|
|
1055
1057
|
this.mode = options?.mode ?? "all";
|
|
@@ -1125,6 +1127,13 @@ var DataBusTraceReporter = class {
|
|
|
1125
1127
|
this.latencyBuckets[bucketIndex] = (this.latencyBuckets[bucketIndex] ?? 0) + 1;
|
|
1126
1128
|
this.latencySumMs += delayMs;
|
|
1127
1129
|
}
|
|
1130
|
+
/** Record deduplication outcomes for the next metrics window. */
|
|
1131
|
+
recordDedupAccepted() {
|
|
1132
|
+
if (this.metricsActive) this.dedupAccepted += 1;
|
|
1133
|
+
}
|
|
1134
|
+
recordDedupSuppressed() {
|
|
1135
|
+
if (this.metricsActive) this.dedupSuppressed += 1;
|
|
1136
|
+
}
|
|
1128
1137
|
/** True when metrics recording is active: enabled and mode includes metrics.
|
|
1129
1138
|
* Extracted so the four record / flush methods share one guard expression
|
|
1130
1139
|
* instead of repeating `!this.enabled || this.mode === 'events'` at each. */
|
|
@@ -1138,7 +1147,7 @@ var DataBusTraceReporter = class {
|
|
|
1138
1147
|
}
|
|
1139
1148
|
flushNow() {
|
|
1140
1149
|
const timestamp = this.now();
|
|
1141
|
-
if (this.received > 0 || this.dispatched > 0) {
|
|
1150
|
+
if (this.received > 0 || this.dispatched > 0 || this.dedupAccepted > 0 || this.dedupSuppressed > 0) {
|
|
1142
1151
|
const samples = this.latencySamples;
|
|
1143
1152
|
this.emit({
|
|
1144
1153
|
type: "message_metrics",
|
|
@@ -1152,6 +1161,8 @@ var DataBusTraceReporter = class {
|
|
|
1152
1161
|
dispatchP50Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.5)),
|
|
1153
1162
|
dispatchP95Ms: roundMs(percentileMs(this.latencyBuckets, samples, 0.95)),
|
|
1154
1163
|
dispatchMaxMs: roundMs(percentileMs(this.latencyBuckets, samples, 1)),
|
|
1164
|
+
dedupAccepted: this.dedupAccepted,
|
|
1165
|
+
dedupSuppressed: this.dedupSuppressed,
|
|
1155
1166
|
timestamp
|
|
1156
1167
|
});
|
|
1157
1168
|
this.resetMetrics();
|
|
@@ -1166,6 +1177,8 @@ var DataBusTraceReporter = class {
|
|
|
1166
1177
|
this.receivedAt.clear();
|
|
1167
1178
|
this.latencyBuckets.fill(0);
|
|
1168
1179
|
this.latencySumMs = 0;
|
|
1180
|
+
this.dedupAccepted = 0;
|
|
1181
|
+
this.dedupSuppressed = 0;
|
|
1169
1182
|
}
|
|
1170
1183
|
emit(event) {
|
|
1171
1184
|
try {
|
|
@@ -1216,6 +1229,7 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1216
1229
|
replayBuffers;
|
|
1217
1230
|
replayMaxPerTopic;
|
|
1218
1231
|
replayPersistence;
|
|
1232
|
+
replayRetentionMs;
|
|
1219
1233
|
replayHydration;
|
|
1220
1234
|
initialConfig;
|
|
1221
1235
|
hasInitialConfig;
|
|
@@ -1263,6 +1277,10 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1263
1277
|
this.replayMaxPerTopic = replay?.maxPerTopic ?? DEFAULT_REPLAY_MAX_PER_TOPIC;
|
|
1264
1278
|
this.replayBuffers = replay ? /* @__PURE__ */ new Map() : null;
|
|
1265
1279
|
this.replayPersistence = replay?.persistence ?? null;
|
|
1280
|
+
this.replayRetentionMs = replay?.retentionMs;
|
|
1281
|
+
if (this.replayRetentionMs !== void 0 && (!Number.isFinite(this.replayRetentionMs) || this.replayRetentionMs <= 0)) {
|
|
1282
|
+
throw new TypeError("replay.retentionMs must be a positive finite number.");
|
|
1283
|
+
}
|
|
1266
1284
|
this.replayHydration = this.hydrateReplay();
|
|
1267
1285
|
const { autoStart, initialConfig, trace, transport, dedup, ...clusterOptions } = options;
|
|
1268
1286
|
this.transport = transport;
|
|
@@ -1622,10 +1640,12 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1622
1640
|
if (this.seenMessageIds.has(message.messageId)) {
|
|
1623
1641
|
this.trace.event({ type: "reliability", operation: "dedup_suppressed", topic: message.topic });
|
|
1624
1642
|
this.dedupSuppressed += 1;
|
|
1643
|
+
this.trace.recordDedupSuppressed();
|
|
1625
1644
|
return true;
|
|
1626
1645
|
}
|
|
1627
1646
|
this.seenMessageIds.set(message.messageId, now);
|
|
1628
1647
|
this.dedupAccepted += 1;
|
|
1648
|
+
this.trace.recordDedupAccepted();
|
|
1629
1649
|
while (this.seenMessageIds.size > this.dedupMaxEntries) {
|
|
1630
1650
|
const oldest = this.seenMessageIds.keys().next().value;
|
|
1631
1651
|
if (oldest === void 0) break;
|
|
@@ -1660,6 +1680,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1660
1680
|
if (buffer.length > this.replayMaxPerTopic) buffer.shift();
|
|
1661
1681
|
if (this.replayPersistence) {
|
|
1662
1682
|
void this.replayPersistence.append(storedMessage).catch((error) => this.reportError(error));
|
|
1683
|
+
if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {
|
|
1684
|
+
void this.replayPersistence.clearBefore(Date.now() - this.replayRetentionMs).catch((error) => this.reportError(error));
|
|
1685
|
+
}
|
|
1663
1686
|
}
|
|
1664
1687
|
}
|
|
1665
1688
|
async hydrateReplay() {
|
|
@@ -1667,6 +1690,9 @@ var CrossTabDataBus = class _CrossTabDataBus {
|
|
|
1667
1690
|
return;
|
|
1668
1691
|
}
|
|
1669
1692
|
try {
|
|
1693
|
+
if (this.replayRetentionMs !== void 0 && this.replayPersistence.clearBefore) {
|
|
1694
|
+
await this.replayPersistence.clearBefore(Date.now() - this.replayRetentionMs);
|
|
1695
|
+
}
|
|
1670
1696
|
for (const message of await this.replayPersistence.load()) {
|
|
1671
1697
|
let buffer = this.replayBuffers.get(message.topic);
|
|
1672
1698
|
if (!buffer) {
|
|
@@ -1920,4 +1946,4 @@ export {
|
|
|
1920
1946
|
parseDataBusPublication,
|
|
1921
1947
|
selectWorkerBackend
|
|
1922
1948
|
};
|
|
1923
|
-
//# sourceMappingURL=chunk-
|
|
1949
|
+
//# sourceMappingURL=chunk-NX76TAV3.js.map
|