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 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 生命周期、订阅和状态
@@ -2,7 +2,7 @@ import {
2
2
  CrossTabDataBus,
3
3
  parseDataBusPublication,
4
4
  selectWorkerBackend
5
- } from "./chunk-ZOPNTR4E.js";
5
+ } from "./chunk-NX76TAV3.js";
6
6
 
7
7
  // src/centrifuge-session.ts
8
8
  import { Centrifuge } from "centrifuge";
@@ -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-ZOPNTR4E.js.map
1949
+ //# sourceMappingURL=chunk-NX76TAV3.js.map