cross-tab-worker-databus 0.20.86 → 0.20.87
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 +17 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-SDOV3UHG.js → chunk-ZNHJ5OMY.js} +126 -17
- package/dist/{chunk-SDOV3UHG.js.map → chunk-ZNHJ5OMY.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +125 -16
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/hooks.cjs +2 -2
- package/dist/cjs/hooks.cjs.map +2 -2
- package/dist/cjs/index.cjs +232 -36
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/cjs/vue.cjs +1 -1
- package/dist/cjs/vue.cjs.map +2 -2
- package/dist/core/data-bus.d.ts +28 -5
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/hooks.d.ts +2 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +2 -2
- package/dist/hooks.js.map +2 -2
- package/dist/index.js +108 -21
- package/dist/index.js.map +2 -2
- package/dist/vue.d.ts +2 -1
- package/dist/vue.d.ts.map +1 -1
- package/dist/vue.js +1 -1
- package/dist/vue.js.map +2 -2
- package/dist/websocket.d.ts +24 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +14 -9
- package/docs/architecture.md +6 -2
- package/docs/roadmap.md +8 -1
- package/docs/transports.md +19 -2
- package/docs/zh/api.md +14 -9
- package/docs/zh/architecture.md +6 -2
- package/docs/zh/roadmap.md +8 -1
- package/docs/zh/transports.md +14 -2
- package/package.json +3 -3
package/docs/zh/api.md
CHANGED
|
@@ -70,11 +70,13 @@ ready(): Promise<void>
|
|
|
70
70
|
|
|
71
71
|
若后续 `stop()` 取消了该排队重启,排队 `start()` Promise 仍按既定语义 resolve 且不会打开 transport,但 `ready()` 会以生命周期错误 reject,而不会把已停止的 bus 报告为 ready。
|
|
72
72
|
|
|
73
|
+
Tab 处于 BFCache 挂起态时(`pagehide` 之后、`pageshow` 之前),`ready()` 会以挂起态错误 reject。挂起路径会把 `startPromise` 复用为异步 `transport.stop()` 的 gate,若直接返回它,就会针对一个被有意停止的 transport 报告 ready。`pageshow` 或显式 `start()` 会清除挂起标记并安装真正的重开 Promise,此后 `ready()` 会在 transport 就绪后正常 resolve。
|
|
74
|
+
|
|
73
75
|
若该排队重启在 transport 启动阶段失败,即使未传入 `initialConfig`,`ready()` 也会以底层启动错误 reject。该失败会保留给显式恢复,而不会被通用的「缺少配置」错误掩盖。
|
|
74
76
|
|
|
75
77
|
未传入 `initialConfig` 且未显式调用 `start(config)` 时,`ready()` 返回 rejected Promise 而不是同步抛出,调用方可以统一通过 `.catch` 处理并决定是否显式启动。
|
|
76
78
|
|
|
77
|
-
`ready()`
|
|
79
|
+
`ready()` 会在当前 transport 满足其 `start()` 契约时 resolve;它不保证远端服务端已能处理应用流量。内置 WebSocket 后端的 `start()` 会等待 socket 握手:握手前发生 `error`、`close`,或超过 `connectTimeoutMs` 时都会 reject,因此 `ready()` 不会在 socket 仍处于 `CONNECTING` 时报告就绪。协议连接状态仍通过 `onStatus` 获取。
|
|
78
80
|
|
|
79
81
|
### `subscribe(topic, handler)`
|
|
80
82
|
|
|
@@ -90,7 +92,7 @@ subscribe(
|
|
|
90
92
|
- 同一 Topic 的多个 handler 使用引用计数。
|
|
91
93
|
- 当前 Tab 第一个 handler 会登记集群订阅。
|
|
92
94
|
- 最后一个 handler 释放后,当前 Tab 才退出该 Topic。
|
|
93
|
-
- transport 尚未 ready
|
|
95
|
+
- transport 尚未 ready 时订阅自动排队;transport 恢复待定时同样如此:订阅会挂在恢复门之后,等重开成功才下发,而不会写入刚刚上报 `error` 的连接。
|
|
94
96
|
- 显式 `stop()` 尚未 settle 时发起的订阅不会登记:`subscribe()` 通过 `onError` 上报并返回 no-op 释放函数。调用方应等待 `stop()` settle,再调用 `start()` 后重新订阅。
|
|
95
97
|
- 通配符订阅:以 `.*` 结尾的 Topic(如 `chat.*`)匹配任意后缀,`*` 匹配全部。pattern 以字面量参与路由、归属与传输订阅;携带匹配的具体 topic(或 pattern 本身)的发布都会投递给通配 handler。匹配规则见下方 `topicMatchesPattern`。
|
|
96
98
|
- 重放(可选):构造 bus 时传 `replay: { maxPerTopic }` 开启缓冲,`maxPerTopic` 必须是正安全整数;`subscribe()` 第三个参数传 `{ replay: true | n }` 后,新 handler 会立即收到缓冲历史(最多 `n` 条,受 `maxPerTopic` 上限约束,默认 100),消息带 `message.replayed: true` 标记——晚加入的 handler 不会错过更早的发布。只有被分发过的消息才入缓冲(无本地订阅者的 topic 会被 owner 丢弃);缓冲仅存内存,该 topic 最后一个 handler 退订时清空。通配订阅会对所有匹配 pattern 的已缓冲 topic 做回放。需要跨 reload/BFCache 持久化时,可传入 `createIndexedDbReplayPersistence({ maxPerTopic })` 创建的 `persistence`;持久化为异步操作,失败会通过 `onError` 报告,不影响实时投递。设置 `retentionMs` 后会清理内存中过期的 producer-timestamped 历史,并通过实现 `clearBefore` 的 adapter 在 hydrate 和追加后清理 durable 历史。设置 `persistenceRetry: { maxAttempts, backoffMs }` 可重试瞬时持久化失败;默认仍保持单次尝试。设置 `pruneStrategy` 为 `'count'`(默认)、`'age'` 或 `'both'`,分别表示按 `maxPerTopic` 截断、按 `retentionMs` 清理带时间戳历史,或两者都应用。`age` 下无时间戳的 legacy 条目会保留,但受 `maxPerTopic` 限制;带时间戳条目由 retention 窗口约束。
|
|
@@ -127,6 +129,8 @@ publish(
|
|
|
127
129
|
|
|
128
130
|
在 `stop()` 尚未 settle 时调用 `publish()` 会通过 `onError` 上报且不路由任何消息;消息不会延迟到之后的 start。更早发出、仍排队等待 transport open 的发布会被 stop 取消。
|
|
129
131
|
|
|
132
|
+
运行期 transport 上报 `error` 后发起的发布同样会挂在恢复门之后,等 transport 重新 ready 再发送,因此不会被写进刚刚失败的连接。若恢复预算耗尽,或等待被 `stop()` / 页面隐藏取代,该发布会按文档丢弃而不是无限期延迟(页面挂起仍保持「不延迟、直接丢弃」语义)。干净的 `disconnected` 不会触发后台 DataBus 自动重开,但也不会再吞掉后续操作:干净关闭后发起的 `subscribe()` / `publish()` 会触发一次按需重开,先挂起等待替代连接就绪,随后再 flush。可显式调用 `start()`(或直接发起操作)来重开。
|
|
133
|
+
|
|
130
134
|
传入 `options.messageId` 和 `options.timestamp` 后,元数据会穿过跨 Tab 路由、Worker 边界和支持的 transport。服务端必须回显或以其他方式保留它们,入站去重和 replay retention 才能使用。
|
|
131
135
|
|
|
132
136
|
`DataBusMessage` 与 `DataBusPublication` 暴露相同的可选元数据。
|
|
@@ -204,7 +208,7 @@ getHealthSummary(): DataBusHealthSummary
|
|
|
204
208
|
|
|
205
209
|
```ts
|
|
206
210
|
interface DataBusHealthSummary {
|
|
207
|
-
healthy: boolean; // 已启动、未挂起、transport
|
|
211
|
+
healthy: boolean; // 已启动、未挂起、transport 实时状态为 connected
|
|
208
212
|
state: 'stopped' | 'starting' | 'healthy' | 'recovering' | 'suspended' | 'degraded';
|
|
209
213
|
status: WorkerStatus;
|
|
210
214
|
sdkVersion: string;
|
|
@@ -219,7 +223,7 @@ interface DataBusHealthSummary {
|
|
|
219
223
|
}
|
|
220
224
|
```
|
|
221
225
|
|
|
222
|
-
`state` 语义:`stopped`(未启动)、`starting`(首次连接进行中)、`recovering`(transport 自动恢复进行中)、`suspended`(Tab 隐藏,pageshow 后自动恢复)、`degraded`(自动恢复已耗尽,需要手动 `start()` 或重新 subscribe 触发恢复)、`healthy`。处于 degraded 时再次调用 `start()` 会保留 cluster、订阅和 replay 缓冲区,重置失败/恢复账本后重新打开 transport;subscribe 与 publish 也走同一恢复路径。`lastFailure` 是覆盖全部失败来源的统一账本,每次显式 `start()`
|
|
226
|
+
`state` 语义:`stopped`(未启动)、`starting`(首次连接进行中)、`recovering`(transport 自动恢复进行中)、`suspended`(Tab 隐藏,pageshow 后自动恢复)、`degraded`(自动恢复已耗尽,需要手动 `start()` 或重新 subscribe 触发恢复)、`healthy`。处于 degraded 时再次调用 `start()` 会保留 cluster、订阅和 replay 缓冲区,重置失败/恢复账本后重新打开 transport;subscribe 与 publish 也走同一恢复路径。`lastFailure` 是覆盖全部失败来源的统一账本,每次显式 `start()` 后重置。`healthy` 依据 transport 的实时状态判定;`transport.ready` 是诊断字段,在 transport 已报告 `connected`、但其 `start()` Promise 尚未 settle 的短暂窗口内可能仍为 `false`,此时操作会排队等待该在途 start,而不会丢失。
|
|
223
227
|
|
|
224
228
|
### `getMetrics()`
|
|
225
229
|
|
|
@@ -238,7 +242,7 @@ getRecoveryStats(): { attempt; exhausted; maxAttempts; hasError; errorMessage; e
|
|
|
238
242
|
getPersistenceStats(): { failures; lastFailureAt; lastErrorMessage }
|
|
239
243
|
```
|
|
240
244
|
|
|
241
|
-
`recovery.generation` 在每次 transport 成功打开时递增(首次启动与每次恢复);`lastSuccessAt` 是该次成功的时间戳(首次成功前为 `null
|
|
245
|
+
`recovery.generation` 在每次 transport 成功打开时递增(首次启动与每次恢复);`lastSuccessAt` 是该次成功的时间戳(首次成功前为 `null`)。`recovery.hasError` / `errorMessage` / `errorAt` 描述最近一次被保留的 **transport** 失败——无论是 transport 打开失败还是运行期 `onError`——并与统一的 `lastFailure` 账本具有相同的生命周期:恢复成功后最后一次失败仍然可见,只有显式 `start()` 会清除它。非 transport 失败(`persistence`、`dispatch`)不会改动 recovery 账本,仍可通过 `lastFailure`(以及 replay 后端的 `getPersistenceStats()`)观察。同一次 transport 失败只取一次时间戳,因此 `recovery.errorAt` 与该次失败对应的 `lastFailure.at` 相等。持久化计数仅覆盖可选的 replay 持久化后端。
|
|
242
246
|
|
|
243
247
|
### `getDiagnostics()`
|
|
244
248
|
|
|
@@ -299,7 +303,7 @@ trace: {
|
|
|
299
303
|
stop(): Promise<void>
|
|
300
304
|
```
|
|
301
305
|
|
|
302
|
-
永久销毁当前实例:清理 handler、集群注册、路由、Worker 和 transport。若 transport open/reopen 仍在收敛,`stop()` 会等待它结束并使该结果失效,确保它不会在 stop 后变为 ready。普通页面隐藏和恢复不需要调用。
|
|
306
|
+
永久销毁当前实例:清理 handler、集群注册、路由、Worker 和 transport。若 transport open/reopen 仍在收敛,`stop()` 会等待它结束并使该结果失效,确保它不会在 stop 后变为 ready。普通页面隐藏和恢复不需要调用。teardown 对故障容错:即使 transport 自身的 `stop()` reject(或同步抛错),`stop()` 仍会在实例销毁完成后 resolve,并通过 `onError` 与统一的 `lastFailure` 记录上报该失败,而不是让 `stop()` 变成 rejected;因此 React / Vue adapter 中 fire-and-forget 的卸载路径不会产生 unhandled rejection。实例之后仍可重新 start。
|
|
303
307
|
|
|
304
308
|
## `DataBusTransport<TConfig, TData>`
|
|
305
309
|
|
|
@@ -395,13 +399,14 @@ const bus = createWebSocketDataBus({
|
|
|
395
399
|
new WebSocketTransport<TData>(connection: WebSocketDataBusConfig)
|
|
396
400
|
```
|
|
397
401
|
|
|
398
|
-
实现 `DataBusTransport
|
|
402
|
+
实现 `DataBusTransport`。`start()` 只在 socket 握手完成后 settle:`open` 时 resolve;握手前发生 `error`、`close`,或超过 `connectTimeoutMs` 时 reject。连接生命周期直接映射 DataBus 状态:socket `open` → `connected`,`close` → `disconnected`,`error` → `error`(触发 DataBus 自动恢复)。socket 原地重连时会自动重发订阅;当 bus 在 socket 失败后重新打开时(`error` 触发自动恢复,或 `close` 后显式 `start()` / 页面恢复 / 后续操作),`start()` 会创建替代 socket,并忽略被取代 socket 的迟到生命周期与消息回调(包括超时尝试之后迟到的 `open`);socket 未打开期间被丢弃的帧通过 `handlers.onError` 上报,替代 socket 打开后自动补发订阅帧。
|
|
399
403
|
|
|
400
404
|
`WebSocketDataBusConfig` 字段:
|
|
401
405
|
|
|
402
406
|
- `url` — WebSocket 端点。
|
|
403
407
|
- `protocols` — 可选的握手子协议。
|
|
404
408
|
- `webSocketFactory` — 可选工厂 `(url, protocols) => WebSocketLike`,用于测试与非浏览器运行时(默认使用全局 `WebSocket`)。
|
|
409
|
+
- `connectTimeoutMs` — 可选握手预算(毫秒),默认 `30000`;传 `0` 或 `Infinity` 表示无限等待。超时会通过 `error` 上报并 reject `start()`(也就是 `ready()`),随后关闭半开 socket。
|
|
405
410
|
|
|
406
411
|
### 线协议
|
|
407
412
|
|
|
@@ -435,7 +440,7 @@ React(>= 18)是可选 peer 依赖;独立入口保证非 React 消费者不
|
|
|
435
440
|
|
|
436
441
|
### `useCrossTabHealth(bus, options?)`
|
|
437
442
|
|
|
438
|
-
将 `bus.getHealthSummary()` 镜像为 React 状态(`DataBusHealthSummary | null`)。由于健康摘要是快照而非事件流,该 hook 按间隔轮询(默认 1000 ms;传 `{ intervalMs: 0 }`
|
|
443
|
+
将 `bus.getHealthSummary()` 镜像为 React 状态(`DataBusHealthSummary | null`)。由于健康摘要是快照而非事件流,该 hook 按间隔轮询(默认 1000 ms;传 `{ intervalMs: 0 }` 可仅依赖事件驱动刷新),并在状态变化与错误发生时立即刷新。修改 `intervalMs` 会替换轮询定时器,但不会重建 bus。bus 创建前返回 `null`。
|
|
439
444
|
|
|
440
445
|
## Vue Composables(`cross-tab-worker-databus/vue`)
|
|
441
446
|
|
|
@@ -451,7 +456,7 @@ useVueCrossTabSubscription(bus, 'chat.*', message => console.log(message.data));
|
|
|
451
456
|
|
|
452
457
|
### `useVueCrossTabHealth(bus, options?)`
|
|
453
458
|
|
|
454
|
-
`useCrossTabHealth` 的 Vue 绑定:将 `bus.getHealthSummary()` 镜像为 Vue `Ref<DataBusHealthSummary | null>`。健康摘要是快照而非事件流,因此该组合式函数按间隔轮询(默认 1000 ms;传 `{ intervalMs: 0 }`
|
|
459
|
+
`useCrossTabHealth` 的 Vue 绑定:将 `bus.getHealthSummary()` 镜像为 Vue `Ref<DataBusHealthSummary | null>`。健康摘要是快照而非事件流,因此该组合式函数按间隔轮询(默认 1000 ms;传 `{ intervalMs: 0 }` 可仅依赖事件驱动刷新),并在状态变化与错误发生时立即刷新。响应式修改 `intervalMs` 会替换轮询定时器,但不会重建 bus。bus 创建前返回 `null`。
|
|
455
460
|
|
|
456
461
|
## `WorkerClusterRuntime`
|
|
457
462
|
|
package/docs/zh/architecture.md
CHANGED
|
@@ -508,8 +508,9 @@ Transport 消息 → isAssigned(topic)? → 是 → broadcastEvent(EVENT)
|
|
|
508
508
|
- **Handoff ACK 有效性。** `ROUTE_RELEASED` 只有在 route 仍指向接收方、释放来自记录的 `handoffFromWorkerId`、且 ACK generation 不小于存储 route 的 generation 时才被接受。来自更早交接轮次的重复 ACK(如 a↔b 反复交接)携带更旧的 generation,会被丢弃。
|
|
509
509
|
- **Replay 持久化清理顺序。** 排队在当前任务之后的批量持久化 flush 会与竞速的清理操作对账:`unsubscribe` 与 `clearReplayTopic` 丢弃该 topic 的待写条目,`clearReplayBefore` 丢弃早于截止时间的条目。已清理的历史不会被在途 flush 复活。
|
|
510
510
|
- **存储写失败恢复。** 合并写入按指数退避重试(50 ms → 1.6 s 封顶)。结构性失败的关键在 5 次尝试后被丢弃(伴随 `console.warn`),且不会永久阻塞其他排队 key;队列完全清空或 `clear()` 取消重试后,退避延迟重置。
|
|
511
|
-
- **Transport 恢复预算。** 自动恢复由冷却时间限速、由 `recovery.maxAttempts` 限量,预算耗尽后标记 `exhausted`。成功的重开会重置尝试计数与 exhausted 标记;transport 宕机时显式 `subscribe`
|
|
511
|
+
- **Transport 恢复预算。** 自动恢复由冷却时间限速、由 `recovery.maxAttempts` 限量,预算耗尽后标记 `exhausted`。成功的重开会重置尝试计数与 exhausted 标记;transport 宕机时显式 `subscribe` 仍可手动恢复。自动调度本身不会重开连接:后端必须先释放失效连接,重试才能创建或重开 socket。`WebSocketTransport` 只在 socket `open` 后 resolve `start()`;握手前的 `error`/`close` 或 `connectTimeoutMs` 超时都会 reject。它仅在 socket 有效期间将其标记为 active;`error`/`close` 会立即失效,下一次 `start()` 在调用工厂前清除旧引用,因此被取代 socket 的迟到回调会被忽略。
|
|
512
512
|
- **BFCache 挂起。** Tab 隐藏时停止 transport、递增持久化重试 generation(取消在途重试且不对外报错)并门控分发;pageshow 时重开 transport,每轮循环只重建一次订阅。
|
|
513
|
+
- **挂起态就绪判定。** 挂起中的 bus 会把 `startPromise` 复用为 `pendingStop`(即 chained `transport.stop()` 的 gate),该 Promise 只能证明清理完成,不能证明可以承载数据。`ready()` 在 `stopping` 门之后检查 `suspended`,以挂起态错误 reject,而不是返回 stop gate;`pageshow`/`reopenTransport()` 与显式 `start()` 会清除标记并安装真正的重开 Promise,使 `ready()` 跟随最新生命周期意图。`getHealthSummary()` 原本就报告 `{ healthy: false, state: 'suspended' }`,reject 让 `ready()` 与该判定保持一致。
|
|
513
514
|
- **交接通道关闭顺序。** `pause()` 将物理 `channel.close()` 推迟一个任务。同步关闭会丢弃仍在排队等待投递的消息(包括交接的 `ROUTE_RELEASED`),使交接目标持有未确认路由。
|
|
514
515
|
- **悬挂交接恢复。** 若前任 owner 已消失而其 `ROUTE_RELEASED` 始终未到达(高负载下通道消息丢失,或 route 写入与 ACK 发送之间崩溃),reconcile 循环会在该未确认交接悬挂超过一个 worker TTL(默认 10 秒)后重新选举存活 owner:路由以全新 generation 重写并清除交接标记,使常规确认路径得以完成(已有回归固化)。年龄门限很关键——刚写入的未确认路由可能只是在等确认落盘,不能误判为悬挂;而只要前任 owner 仍然存活,新 owner 会继续等待,因此严格交接的无重叠保证不受影响。
|
|
515
516
|
- **丢失与恢复矩阵。** 每类协调消息都有有界恢复路径:丢失的 `CONTROL/SUBSCRIBE` 由心跳 reconcile 对未确认路由重发;丢失的 `REGISTRY` 通知最多损失一个心跳间隔(默认 3 秒),因为每次 tick 都会 reconcile;丢失的 `ROUTE_RELEASED` 由 reconcile 在前任 owner 消失且交接悬挂超过一个 worker TTL 后重新选举恢复(见上文悬挂交接不变量,已有回归固化);transport 断连窗口内被丢弃的 publication 是唯一文档化的不可恢复丢失(transport 契约)。storage-event 降级通道通过信封内的单调序列号保证变值投递,丢失的派发由同一 reconcile 循环恢复。
|
|
@@ -521,6 +522,8 @@ DataBus 将"业务订阅意图"与"transport 当前订阅状态"分离。transpo
|
|
|
521
522
|
|
|
522
523
|
内置 Centrifuge transport 也会保留自己的 Subscriptions 并做协议层重连。两层恢复都要求 `subscribe` / `unsubscribe` 幂等。
|
|
523
524
|
|
|
525
|
+
运行期 `error` 会有意保留 `transportReady`:该标记记录「本次会话中已安装的 transport 曾成功打开」,使 `ready()` 跟随 transport 而不是随协议连接抖动。transport 的*操作*由独立的恢复门(recovery gate)控制:当自动或按需重开尚未完成时,`runTransport()` 会把新的 `subscribe` / `publish` 挂在该门之后,而不是写入刚刚上报 `error` 的连接;门只在重开成功(或 transport 自愈回到 `connected`)后释放,此时所有挂起的操作才在可用 transport 上执行。自动尝试失败后门保持关闭,但下一次显式操作可以立即触发按需重开,而不必再等一个冷却周期;当 `recovery.maxAttempts` 耗尽,或被 `stop()` / 页面隐藏取代时,门会被释放,使文档化的显式重试路径与挂起丢弃语义继续成立。`disconnected` 是干净关闭而非可恢复失败:它不会调度后台 DataBus 重开,只有显式 `start()`、页面恢复、transport 自身的重连,或后续的 transport 操作才会回到 `connected`。最后一条路径很关键:transport 一旦真正到达过 `connected` 后再上报 `disconnected`,ready 快速路径就会被拒绝;这类干净关闭后到达的 `subscribe()` / `publish()` 会被挂在同一个恢复门之后并触发一次按需重开,随后在替换连接上 flush,而不再写入已关闭的连接。对于在首次 `connected` 之前就 resolve `start()` 的 transport(worker 型后端异步上报连接状态),操作仍会直接交给它,因为此时的 `disconnected` 表示「尚未连接」,而不是「已建立的连接断开」。
|
|
526
|
+
|
|
524
527
|
## 生命周期状态机
|
|
525
528
|
|
|
526
529
|
`CrossTabDataBus` 使用多个布尔标志和 Promise gate 来串行化生命周期转换。它们之间的交互是 DataBus 层最复杂的部分。
|
|
@@ -532,7 +535,7 @@ DataBus 将"业务订阅意图"与"transport 当前订阅状态"分离。transpo
|
|
|
532
535
|
| `started` | `boolean` | `start()` 已被调用,且之后没有 `stop()` 完成 |
|
|
533
536
|
| `stopping` | `boolean` | `stop()` 正在执行中;阻止新操作 |
|
|
534
537
|
| `suspended` | `boolean` | Tab 已隐藏;transport 被有意暂停 |
|
|
535
|
-
| `transportReady` | `boolean` | transport
|
|
538
|
+
| `transportReady` | `boolean` | 本次会话中 transport 已成功打开;运行期 `error` 后会保留,使 `ready()` 继续跟随已安装的 transport(待执行操作由恢复门而非该标记控制) |
|
|
536
539
|
| `startPromise` | `Promise \| null` | 并发 `start()` 调用的 gate;操作完成后清除 |
|
|
537
540
|
| `stopPromise` | `Promise \| null` | 显式 `stop()` 及其后排队的 restart 共享的 gate |
|
|
538
541
|
| `queuedStart` | `Promise \| null` | 等待进行中的显式 stop 完成后执行的一次全新 start |
|
|
@@ -578,6 +581,7 @@ DataBus 将"业务订阅意图"与"transport 当前订阅状态"分离。transpo
|
|
|
578
581
|
- **启动期间隐藏**:`pagehide` 在 `openTransport` 飞行中触发时,`suspendTransport()` 设置 `suspended = true`,并在飞行中的 start 之后链式执行 `transport.stop()`。`openTransport` 的 catch 路径检测到 `suspended` 后放弃本次 open,不视为失败。
|
|
579
582
|
- **被取代 open 失效**:每次全新 start、reopen、suspend 和 stop 都会推进 `lifecycleEpoch`。open 会捕获自己的 epoch;一旦更新的转换接管生命周期,旧 open 的 status/message/error 回调会被忽略,也不会再把 transport 标记为 ready 或执行失败清理。因此 `stop()` 会等待未完成的 open/reopen,并阻止被取代的 open 在 stop 完成后变为 ready。
|
|
580
583
|
- **恢复冷却**:transport 上报 `error` 且 `started` 为 true、`stopping` 为 false 时,`updateStatus` 在 `RECOVERY_COOLDOWN_MS`(1000 ms)后调度自动 `reopenTransport()`。冷却窗口内的第二次错误被抑制,防止紧循环重试。
|
|
584
|
+
- **传输恢复门**:调度重开的同时会抬起恢复门,使冷却期间发起的 `subscribe` / `publish` 无法到达失效连接,等重开成功后才释放。自动尝试失败后门刻意保持关闭:下一次显式操作会立即触发按需重开,而不是等待下一个限速尝试,挂起的操作则在该次成功后一并 flush。`recovery.maxAttempts` 耗尽,或被 `stop()` / `suspendTransport()` 取代时释放门,从而保留显式重试路径与挂起丢弃语义。运行期 `error` 不会清除 `transportReady`——清除它会让调用方流量绕过冷却重开,并在连接尚未承载数据时报告 ready。
|
|
581
585
|
- **暂停期间停止**:`stop()` 设置 `stopping = true`,阻止 `suspendTransport()` 执行。清理过程会 await `startPromise` 和 `pendingStop`,确保任何飞行中的 open 或 stop 完成后才执行最终的 `transport.stop()`。
|
|
582
586
|
|
|
583
587
|
## 降级
|
package/docs/zh/roadmap.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# 路线图
|
|
2
2
|
|
|
3
|
-
0.20.
|
|
3
|
+
0.20.87 正在推进。项目会先持续完成可靠性与协议兼容性的中版本迭代,再进入 1.0.0 稳定性冻结。
|
|
4
|
+
|
|
5
|
+
## 0.20.87 已完成范围
|
|
6
|
+
|
|
7
|
+
- transport 恢复与就绪加固:原生 WebSocket 后端现在遵守 `DataBusTransport.start()` 契约(仅在 `open` 后 resolve,握手失败或超过 `connectTimeoutMs` 时 reject),自动恢复会真正创建替代 socket,`getHealthSummary()` 跟随 live transport 状态而不是 `transportReady` 诊断标记。
|
|
8
|
+
- 不再把操作写进已经消失的连接:恢复门会把 `subscribe()` / `publish()` 停放在自动与按需重开之后(包括自动尝试失败但仍保留预算的情况);在真实连接之后出现的干净 `disconnected` 现在会触发一次按需重开,而不是交给已关闭的 socket;而异步上报连接状态的 worker 型后端仍保有其「尚未连接」窗口,不会被动重开。
|
|
9
|
+
- 生命周期与 `ready()` 边界修复:BFCache 挂起期间 `ready()` 会 reject;transport 自身 `stop()` reject 或抛错时 `stop()` 仍能 resolve;一次打开失败在两个恢复账本中只打一次时间戳;运行期 transport 错误会进入恢复账本;被取代的异步打开不再拆除更新的 suspend/resume 转换。
|
|
10
|
+
- 适配器与工具链:React/Vue `useCrossTabHealth` 在 `intervalMs` 变化时无需重建 bus 即可生效;`vitest` 及其 coverage-v8 provider 升级到 5.0.1 补丁版。
|
|
4
11
|
|
|
5
12
|
## 0.20.86 已完成范围
|
|
6
13
|
|
package/docs/zh/transports.md
CHANGED
|
@@ -32,6 +32,10 @@ interface DataBusTransportHandlers<TData = unknown> {
|
|
|
32
32
|
三个回调。连接状态变化时调 `onStatus`;收到 publication 时调 `onMessage`;
|
|
33
33
|
非致命错误调 `onError`(DataBus 有恢复冷却窗口,避免抖动连接死循环重试)。
|
|
34
34
|
|
|
35
|
+
`start()` MUST 在后端真正连接后才 settle 返回的 Promise;尝试失败时必须 reject。
|
|
36
|
+
DataBus 把这个 settlement 当作就绪与恢复边界:处于 `CONNECTING` 的 socket 不算
|
|
37
|
+
就绪,握手成功前不能释放排队操作。可能长期卡住的后端应自行设置握手超时并 reject。
|
|
38
|
+
|
|
35
39
|
## 架构分层
|
|
36
40
|
|
|
37
41
|
```
|
|
@@ -141,9 +145,17 @@ Centrifuge session 遵循同一个传输无关契约:不带 metadata 的 paylo
|
|
|
141
145
|
形状;带 metadata 的 publish 使用 `{ data, messageId?, timestamp? }`,入站还接受
|
|
142
146
|
标准嵌套 `DataBusPublicationEnvelope`。
|
|
143
147
|
|
|
148
|
+
`start()` 只在 `open` 后 resolve;握手前发生 error、close,或超过
|
|
149
|
+
`connectTimeoutMs` 时 reject(默认 `30000` ms;`0` / `Infinity` 表示无限等待)。
|
|
150
|
+
超时的 socket 会被关闭,该尝试之后迟到的 `open` 会被忽略。
|
|
151
|
+
|
|
144
152
|
生命周期映射:`open` → `connected`,`close` → `disconnected`,`error` → `error`
|
|
145
|
-
(触发 DataBus 自动恢复)。socket
|
|
146
|
-
|
|
153
|
+
(触发 DataBus 自动恢复)。socket 原地重连时自动重发订阅帧。成功的重开既可以
|
|
154
|
+
复用同一个 socket 对象,也可以通过工厂创建替代 socket;被取代 socket 的迟到
|
|
155
|
+
回调会被忽略,因此失败连接的 close 或 message 不会污染恢复后的连接。干净的
|
|
156
|
+
`disconnected` 不会调度后台自动恢复,但下一次 `subscribe()` / `publish()` 会触发
|
|
157
|
+
一次按需重开并在其后 flush,因此关闭后的操作不会被写进已关闭的 socket。支持
|
|
158
|
+
pattern 的服务器可以以具体 topic 标注发布——见 [api.md](../api.md) 中的通配符订阅。
|
|
147
159
|
|
|
148
160
|
## 工厂入口
|
|
149
161
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cross-tab-worker-databus",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.87",
|
|
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",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"@testing-library/react": "^16.3.3",
|
|
129
129
|
"@types/node": "^26.5.1",
|
|
130
130
|
"@types/react": "^19.3.0",
|
|
131
|
-
"@vitest/coverage-v8": "^5.0.
|
|
131
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
132
132
|
"esbuild": "^0.28.2",
|
|
133
133
|
"eslint": "^10.10.0",
|
|
134
134
|
"fake-indexeddb": "^6.2.5",
|
|
@@ -138,7 +138,7 @@
|
|
|
138
138
|
"react-dom": "^19.3.0",
|
|
139
139
|
"typescript": "^6.0.3",
|
|
140
140
|
"typescript-eslint": "^8.70.0",
|
|
141
|
-
"vitest": "^5.0.
|
|
141
|
+
"vitest": "^5.0.1",
|
|
142
142
|
"vue": "^3.5.42"
|
|
143
143
|
},
|
|
144
144
|
"engines": {
|