cross-tab-worker-databus 0.21.10 → 0.21.11

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.
@@ -54,7 +54,7 @@ const bus = new CrossTabDataBus({
54
54
 
55
55
  `replay.retentionSweepMs` 可选地按周期触发同一清理逻辑。它适合安静 topic 的 durable 旧记录也需要过期的场景;需要同时配置 `retentionMs` 和实现 `clearBefore()` 的持久化适配器。定时器遵循页面可见性和生命周期切换,默认关闭。
56
56
 
57
- `replay.persistenceRetry` 可选地控制瞬时持久化失败的恢复。`maxAttempts` 是总尝试次数(默认 `1`),`backoffMs` 是首次重试前的延迟(默认 `50`);延迟会指数增长并封顶。最终失败仍沿用现有 `onError` 和 reliability 行为。bus 挂起或停止时仍排队在微任务中的批量 flush 会随其生命周期代际一起丢弃,不会在 teardown 后启动 durable append。
57
+ `replay.persistenceRetry` 可选地控制瞬时持久化失败的恢复。`maxAttempts` 是总尝试次数(默认 `1`),`backoffMs` 是首次重试前的延迟(默认 `50`);封顶只作用在增长上:每次尝试失败后延迟翻倍,翻倍后的值封顶在 1600 ms;`backoffMs` 作为首次延迟按配置值原样等待,所以 `backoffMs: 5000` 会先等 5000 ms、之后才回落到 1600 ms,而不是被压到封顶值。最终失败仍沿用现有 `onError` 和 reliability 行为。bus 挂起或停止时仍排队在微任务中的批量 flush 会随其生命周期代际一起丢弃,不会在 teardown 后启动 durable append。
58
58
 
59
59
  ### Replay 选项
60
60
 
@@ -65,7 +65,7 @@ const bus = new CrossTabDataBus({
65
65
  | `retentionMs` | `number` | — | 生产者时间戳保留窗口;早于 cutoff 的历史通过适配器的 `clearBefore` 清理 |
66
66
  | `pruneStrategy` | `'count' \| 'age' \| 'both'` | `'count'` | `count` 按 `maxPerTopic` 截断;`age` 按 `retentionMs` 清理带时间戳历史,并以 `maxPerTopic` 限制无时间戳的 legacy 条目;`both` 两者都应用。`age` 未配置 `retentionMs` 时无 age 可依,回退为数量上限 |
67
67
  | `retentionSweepMs` | `number` | — | 面向安静 topic 的周期性 durable retention sweep;需要 `retentionMs` 与实现 `clearBefore` 的适配器 |
68
- | `persistenceRetry` | `{ maxAttempts, backoffMs }` | `1` / `50` | 瞬时持久化失败的有界重试;延迟指数增长并封顶 |
68
+ | `persistenceRetry` | `{ maxAttempts, backoffMs }` | `1` / `50` | 瞬时持久化失败的有界重试;每次失败后延迟翻倍,翻倍值封顶 1600 ms,`backoffMs` 首次按原值等待 |
69
69
 
70
70
  ### 去重选项
71
71
 
@@ -14,7 +14,8 @@ pnpm add cross-tab-worker-databus
14
14
  - `cross-tab-worker-databus/centrifuge`:内置 Centrifuge Worker transport
15
15
  - `cross-tab-worker-databus/centrifuge.worker`:Dedicated Worker 构建产物,默认由内置 factory 加载,通常无需直接引用
16
16
  - `cross-tab-worker-databus/centrifuge.shared.worker`:SharedWorker 构建产物,默认由内置 factory 加载,通常无需直接引用
17
- - `cross-tab-worker-databus/hooks`:可选的 React hooks 适配层(`useCrossTabDataBus`、`useCrossTabSubscription`、`useCrossTabStatus`);React(>= 18)为可选 peer 依赖
17
+ - `cross-tab-worker-databus/hooks`:可选的 React hooks 适配层(`useCrossTabDataBus`、`useCrossTabSubscription`、`useCrossTabStatus`、`useCrossTabHealth`);React(>= 18)为可选 peer 依赖
18
+ - `cross-tab-worker-databus/vue`:可选的 Vue 组合式 API,四个名字相同(`useCrossTabDataBus`、`useCrossTabSubscription`、`useCrossTabStatus`、`useCrossTabHealth`);Vue(>= 3.3)为可选 peer 依赖
18
19
  - `cross-tab-worker-databus` 同时导出零依赖的原生 `WebSocketTransport` / `createWebSocketDataBus`,适用于本身使用 WebSocket 的服务器
19
20
 
20
21
  `cross-tab-worker-databus/centrifuge` 入口依赖可选 peer dependency `centrifuge`(^5.5.3)。使用内置 Centrifuge transport 时请一并安装:`pnpm add centrifuge`。
@@ -46,7 +47,7 @@ export const dataBus = createCentrifugeDataBus<ResourceEvent>({
46
47
 
47
48
  `clusterKey` 默认由连接地址派生。它只用于集群隔离,进入 localStorage 和 BroadcastChannel 通道名称前会转换为不透明 key。请注意,通过 BroadcastChannel 协调通道发送的 Topic 名称和事件类型以明文传输;仅 localStorage 元数据通过哈希进行混淆。
48
49
 
49
- 默认使用 Dedicated Worker,每个 Tab 一个 Worker。希望同源 Tab 复用同一个连接时,设置 `workerMode: 'shared'` 或 `'auto'`:
50
+ 默认使用 Dedicated Worker,每个 Tab 一个 Worker。`workerMode: 'shared'`(或先尝试 shared 的 `'auto'`)会把同源 Tab 放进**同一个** SharedWorker 进程——但不会合并它们的连接:每个连接的 port 都有自己的 `CentrifugeSession`,因此也有自己的 WebSocket,这正是关闭或刷新一个 Tab 不影响其余 Tab 的原因——N 个 Tab 仍是 N 条服务端连接、N 个 channel 订阅。shared 模式省下的是 Worker 进程,不是 socket:
50
51
 
51
52
  ```ts
52
53
  export const dataBus = createCentrifugeDataBus<ResourceEvent>({
@@ -144,7 +145,7 @@ pnpm examples
144
145
 
145
146
  打开 `http://localhost:4173/examples/demo/`,在多个浏览器标签页中同时打开即可观察跨 Tab 数据流转。页面支持:
146
147
 
147
- - 默认连接公共 Centrifugo 演示地址 `wss://faye.centrifugal.dev/connection/websocket`
148
+ - 默认连接演示页自带的本地 Centrifugo 端点(`<scheme>://<host>/centrifuge/demo/connection/websocket`,页面加载时即写入地址框);公共地址 `wss://faye.centrifugal.dev/connection/websocket` 只是可选预设之一,不是默认值
148
149
  - 在页面内修改 WSS 地址、`workerMode`、Topic 和 `transferable` 配置
149
150
  - 切换到"本地广播"模式,不依赖外部服务器,仅用 BroadcastChannel 演示多标签协同
150
151
  - 数据流动画、事件流、分发延迟指标和集群 Worker 路由状态
@@ -1,6 +1,29 @@
1
1
  # 路线图
2
2
 
3
- 0.21.10 已于 2026 年 9 月 23 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
3
+ 0.21.11 已于 2026 年 9 月 23 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
4
+
5
+ ## 0.21.11 已完成范围
6
+
7
+ - 一轮针对此前从未被扫过的四个文档文件(`architecture.md`、`capabilities.md`、`getting-started.md`、
8
+ `transports.md`,以及它们共同依赖的 `api.md`)的文案审计,找出约十五句代码并不支持的表述,中英文一并修正。
9
+ 最要紧的都是"从不持久化"这一类。`architecture.md` 用六句话承诺协调流量绝不写入 localStorage,而
10
+ `channelFallback: 'storage-event'` 在构造上就与之相反:信封本身就是那一帧,因此明文 topic 与 publication
11
+ payload 会留在 channel key 下直到通道关闭,页面崩溃后更是无限期。能力矩阵声称 SDK 不持久化业务 payload,
12
+ 而它自己导出的 `createIndexedDbReplayPersistence` 就是反例(对象仓以明文 topic 为 key);矩阵与入门文档还把
13
+ `workerMode: 'shared'` 说成"复用同一条连接",实际上每个 port 都有自己的 `CentrifugeSession` 和自己的
14
+ WebSocket——shared 模式省的是进程,从来不是 socket。
15
+ - 两条写给 transport 实现者的指引指错了回调、也指错了守卫。自动恢复由 `onStatus('error')` 启动、按恢复冷却窗口
16
+ 节流;`onError` 只做记录与通知,因此只通过它上报死连接的 backend 永远不会被重开。generation 计数器只有异步凭证桥
17
+ 会比较它——被取代的 Worker 根本到不了 transport,因为监听器早已移除。同一文件的接口块、Worker 联合与 wire 草图
18
+ 各自漏掉了确实发布的成员(`publish` 的元数据参数、可选的 `publishBatch`、两个 diagnostics 标签、`TOKEN_*` 与
19
+ `SESSION_REAPED` 类型、批量帧)。
20
+ - 有一处过度绝对的说法同时出现在三个地方,其中包括 `cluster.ts` 的一条注释:路由校验丢弃的是指向*另一个* Worker 的
21
+ `CONTROL/SUBSCRIBE`,读不到路由的 topic 会被接受。这个容忍是有界的——没有路由可盖章时 `confirmRoute` 什么都不写,
22
+ 下一次 reconcile 会收回 assignment——并由新增测试固化。实测:按旧文案描述采用更严格的规则,会让这条新测试和两条本就依赖
23
+ 该容忍的负载加权测试一起失败。
24
+ - 同一次"把说法拿去测量、而不是重读一遍"的改动也在此发布:`configuration.md` 的重试封顶语句现在说明了 1600 ms
25
+ 上限究竟约束哪一半,两条新测试固化完整的翻倍序列与原样生效的首次等待,两条 `PortReaper` 注释改为描述其真实的保护作用。
26
+ - 无行为、wire 格式、存储布局或集群协议变更。全树覆盖率保持 99.01 / 96.90 / 99.26 / 99.69,共 **895** 个测试。
4
27
 
5
28
  ## 0.21.10 已完成范围
6
29
 
@@ -14,10 +14,19 @@ WebSocket、socket.io、SSE 等)。
14
14
 
15
15
  ```ts
16
16
  interface DataBusTransport<TConfig = unknown, TData = unknown> {
17
+ /** 可选的身份/后端标签,经 diagnostics 暴露。 */
18
+ readonly diagnosticsName?: string;
19
+ readonly diagnosticsBackend?: string;
17
20
  start(config: TConfig, handlers: DataBusTransportHandlers<TData>): MaybePromise<void>;
18
21
  subscribe(topic: string): MaybePromise<void>;
19
22
  unsubscribe(topic: string): MaybePromise<void>;
20
- publish(topic: string, data: unknown): MaybePromise<void>;
23
+ /** `options` 带上调用方的 `{ messageId, timestamp }`;后端若悄悄丢掉它,
24
+ * 对端就同时失去了去重身份与 producer 时间戳。 */
25
+ publish(topic: string, data: unknown, options?: DataBusPublishOptions): MaybePromise<void>;
26
+ /** 可选:突发时用一帧发多条。没有它,DataBus 会退化成逐条 `publish`。
27
+ * 每条是 `{ data, messageId?, timestamp? }`,即 `publish` 那份元数据的逐条版本。
28
+ * (这个条目类型是内部的:要么按内联结构写,要么按你的后端放宽 `ReadonlyArray`。) */
29
+ publishBatch?(topic: string, items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>): MaybePromise<void>;
21
30
  stop(): MaybePromise<void>;
22
31
  }
23
32
 
@@ -30,7 +39,7 @@ interface DataBusTransportHandlers<TData = unknown> {
30
39
 
31
40
  `start()` 接收用户提供的连接配置(无类型 `TConfig`——后端自行定义其形状)和
32
41
  三个回调。连接状态变化时调 `onStatus`;收到 publication 时调 `onMessage`;
33
- 非致命错误调 `onError`(DataBus 有恢复冷却窗口,避免抖动连接死循环重试)。
42
+ 非致命错误调 `onError`。只有 `onStatus('error')` 会启动自动恢复——DataBus 用恢复冷却窗口给重开节流,并用 `recovery.maxAttempts` 限定次数,因此抖动连接不会死循环重试。`onError` 只是把失败记入 `getHealthSummary().lastFailure` 并通知订阅者,除此之外不做任何事:只用 `onError` 上报死掉的 socket 的后端,永远不会被重开。
34
43
 
35
44
  `start()` MUST 在后端真正连接后才 settle 返回的 Promise;尝试失败时必须 reject。
36
45
  DataBus 把这个 settlement 当作就绪与恢复边界:处于 `CONNECTING` 的 socket 不算
@@ -59,8 +68,9 @@ DataBus 层负责跨 Tab 协调(BroadcastChannel 控制面、localStorage 路
59
68
 
60
69
  参照 Centrifuge 后端的 `centrifuge-protocol.ts`:一个主线程发给 Worker 的
61
70
  判别联合(`INIT` / `SUBSCRIBE` / `UNSUBSCRIBE` / `PUBLISH` / `PUBLISH_BIN` /
62
- `PING` / `STOP`)和一个 Worker 回传的联合(`STATUS` / `MESSAGE` / `MESSAGE_BIN` /
63
- `ERROR`)。保持结构化克隆安全
71
+ `PING` / `STOP`,外加回应凭证请求的 `TOKEN_RESPONSE` / `TOKEN_ERROR`)和一个
72
+ Worker 回传的联合(`STATUS` / `MESSAGE` / `MESSAGE_BIN` / `ERROR`,外加凭证桥用的
73
+ `TOKEN_REQUEST`,以及 SharedWorker 端口被回收时的 `SESSION_REAPED`)。保持结构化克隆安全
64
74
  (无函数、无类实例——`Error` 必须序列化)。
65
75
 
66
76
  ### 2. 实现 session
@@ -82,8 +92,7 @@ transport 选择后端(SharedWorker / Dedicated Worker / 本地),向它发
82
92
 
83
93
  - **后端选举**:复用 `worker-mode.ts` 的 `selectWorkerBackend`,使你的后端与
84
94
  SDK 其余部分降级行为一致。
85
- - **generation 守卫**:创建后端时递增单调计数器;错误处理检查它,使被取代的
86
- Worker 的迟到错误不会污染新 session。
95
+ - **generation 守卫**:创建后端或停止 transport 时递增单调计数器。参考实现里只有异步凭证桥会比较它,因为 provider 可能在它所应答的 Worker 已经消失之后才 settle。Worker 的 error 处理并不读这个计数器——`stop()` 与 `onWorkerFailed()` 在 generation 前移之前就移除了这些监听器,被取代的后端根本到不了这个对象。只有当你的后端会让某个监听器跨过后端替换继续存活时,才需要自己加这层检查。
87
96
  - **SharedWorker 心跳**:若用 SharedWorker,定期发 PING,让 `PortReaper` 能
88
97
  回收死 tab 的 session,并处理回收器在关闭端口前发出的 `SESSION_REAPED`
89
98
  消息。对被饿死但仍存活的 tab 来说,那是唯一能收到的信号(`MessagePort` 没有
@@ -137,6 +146,7 @@ const bus = createWebSocketDataBus({
137
146
  线协议(JSON 文本帧):
138
147
 
139
148
  - client → server:`{"op":"subscribe"|"unsubscribe"|"publish","topic":"...","data":...,"messageId"?:"...","timestamp"?:123}`
149
+ - client → server(批量):`{"op":"publishBatch","topic":"...","items":[{data,...,"messageId"?,"timestamp"?}]}`——一帧携带多条,由服务端拆成一条条 publication 重新扇出。二进制 payload 以字节数组内嵌其中。只有一条的 batch 会改走普通 `publish` 帧,因此只实现 `publish` 的服务端仍能看到旧的单条形状;空批次在发出前就被丢弃。socket 未打开时收到的批次会通过 `handlers.onError` 上报并丢弃。
140
150
  - server → client:标准 publication 为 `{"op":"publication","publication":{"topic":"...","data":...,"messageId"?:"...","timestamp"?:123}}`;旧的扁平 `{"topic":"...","data":...}` 帧仍然兼容。没有字符串 topic 的帧会被忽略;非法 JSON 通过 `handlers.onError` 上报而不会抛出。
141
151
  - 自带字符串 `topic` 的 publication 会按**该值**寻址,而不是按它到达的 channel——这正是 server 通过通配 channel(`chat.*`)投递时指明具体 topic 的方式。因此顶层恰好含 `topic` 字段的负载会被重新寻址,若集群中没有任何 Tab 拥有重定向后的 topic,就会被丢弃。Centrifuge 的 channel 通常由客户端库在带外给出,这也是必须在此说明该规则的原因:只有在这条链路上,"topic 本来就写在帧里"这件事才可见。
142
152
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-tab-worker-databus",
3
- "version": "0.21.10",
3
+ "version": "0.21.11",
4
4
  "description": "Framework-agnostic cross-tab data bus with Dedicated/Shared Worker clustering and Centrifuge support.",
5
5
  "type": "module",
6
6
  "license": "MIT",