cross-tab-worker-databus 0.20.88 → 0.20.90
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 +23 -0
- package/dist/centrifuge-session.d.ts +1 -0
- package/dist/centrifuge-session.d.ts.map +1 -1
- package/dist/centrifuge.d.ts.map +1 -1
- package/dist/centrifuge.js +55 -8
- package/dist/centrifuge.js.map +2 -2
- package/dist/centrifuge.shared.worker.js +38 -6
- package/dist/centrifuge.shared.worker.js.map +2 -2
- package/dist/centrifuge.worker.js +38 -6
- package/dist/centrifuge.worker.js.map +2 -2
- package/dist/{chunk-4UOLOWOD.js → chunk-L24ETFVK.js} +20 -13
- package/dist/{chunk-4UOLOWOD.js.map → chunk-L24ETFVK.js.map} +2 -2
- package/dist/cjs/centrifuge.cjs +73 -19
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +27 -14
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/replay-manager.d.ts.map +1 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/index.js.map +2 -2
- package/dist/websocket.d.ts.map +1 -1
- package/docs/api.md +3 -3
- package/docs/architecture.md +9 -6
- package/docs/benchmarks.md +8 -8
- package/docs/capabilities.md +2 -2
- package/docs/configuration.md +1 -1
- package/docs/release-checklist.md +2 -2
- package/docs/roadmap.md +21 -2
- package/docs/zh/api.md +4 -2
- package/docs/zh/architecture.md +9 -6
- package/docs/zh/benchmarks.md +8 -8
- package/docs/zh/capabilities.md +2 -2
- package/docs/zh/configuration.md +1 -1
- package/docs/zh/release-checklist.md +2 -2
- package/docs/zh/roadmap.md +21 -2
- package/package.json +1 -1
package/docs/zh/api.md
CHANGED
|
@@ -131,6 +131,8 @@ publish(
|
|
|
131
131
|
|
|
132
132
|
运行期 transport 上报 `error` 后发起的发布同样会挂在恢复门之后,等 transport 重新 ready 再发送,因此不会被写进刚刚失败的连接。若恢复预算耗尽,或等待被 `stop()` / 页面隐藏取代,该发布会按文档丢弃而不是无限期延迟(页面挂起仍保持「不延迟、直接丢弃」语义)。干净的 `disconnected` 不会触发后台 DataBus 自动重开,但也不会再吞掉后续操作:干净关闭后发起的 `subscribe()` / `publish()` 会触发一次按需重开,先挂起等待替代连接就绪,随后再 flush。可显式调用 `start()`(或直接发起操作)来重开。
|
|
133
133
|
|
|
134
|
+
入站消息可携带调用方/服务端提供的 `messageId`。可通过 `dedup: { maxEntries, ttlMs }` 启用有界重复抑制;每个 bus 实例只会忽略其窗口内的重复 ID。该能力默认关闭且属于尽力而为:它不提供端到端的 at-least-once 或 exactly-once 服务端保证。每条被接受的 transport publication 只会扇出一次,每个匹配的本地 handler 至多分发一次;但 transport/服务端仍可能重复投递或丢失,断连或挂起中的 Tab 也可能错过跨 Tab 事件。测试和自定义时钟宿主可传入 `dedup.now`。完整 `stop()` 会清空已记住的 ID 窗口;之后的 `start()` 会开启全新的 dedup 会话。
|
|
135
|
+
|
|
134
136
|
传入 `options.messageId` 和 `options.timestamp` 后,元数据会穿过跨 Tab 路由、Worker 边界和支持的 transport。服务端必须回显或以其他方式保留它们,入站去重和 replay retention 才能使用。
|
|
135
137
|
|
|
136
138
|
`DataBusMessage` 与 `DataBusPublication` 暴露相同的可选元数据。
|
|
@@ -208,7 +210,7 @@ getHealthSummary(): DataBusHealthSummary
|
|
|
208
210
|
|
|
209
211
|
```ts
|
|
210
212
|
interface DataBusHealthSummary {
|
|
211
|
-
healthy: boolean; //
|
|
213
|
+
healthy: boolean; // 已启动、未在停止中、未挂起、transport 实时状态为 connected
|
|
212
214
|
state: 'stopped' | 'starting' | 'healthy' | 'recovering' | 'suspended' | 'degraded';
|
|
213
215
|
status: WorkerStatus;
|
|
214
216
|
sdkVersion: string;
|
|
@@ -223,7 +225,7 @@ interface DataBusHealthSummary {
|
|
|
223
225
|
}
|
|
224
226
|
```
|
|
225
227
|
|
|
226
|
-
`state` 语义:`stopped
|
|
228
|
+
`state` 语义:`stopped`(未启动,或显式 `stop()` 仍在 teardown)、`starting`(首次连接进行中)、`recovering`(transport 自动恢复进行中)、`suspended`(Tab 隐藏,pageshow 后自动恢复)、`degraded`(自动恢复已耗尽,需要手动 `start()` 或重新 subscribe 触发恢复)、`healthy`。处于 degraded 时再次调用 `start()` 会保留 cluster、订阅和 replay 缓冲区,重置失败/恢复账本后重新打开 transport;subscribe 与 publish 也走同一恢复路径。`lastFailure` 是覆盖全部失败来源的统一账本,每次显式 `start()` 后重置。`healthy` 依据 transport 的实时状态判定,但当显式 `stop()` 仍在进行时一律报告 `stopped`:teardown 期间其余生命周期 API(`publish()`、`subscribe()`、`ready()`)已经拒绝操作,健康判定不能与之矛盾。`transport.ready` 是诊断字段,在 transport 已报告 `connected`、但其 `start()` Promise 尚未 settle 的短暂窗口内可能仍为 `false`,此时操作会排队等待该在途 start,而不会丢失。
|
|
227
229
|
|
|
228
230
|
### `getMetrics()`
|
|
229
231
|
|
package/docs/zh/architecture.md
CHANGED
|
@@ -455,17 +455,19 @@ SDK 当前刻意不在 Service Worker 中承载实时 transport。Service Worker
|
|
|
455
455
|
|
|
456
456
|
### 分发流程:三道关卡
|
|
457
457
|
|
|
458
|
-
|
|
458
|
+
经过可选的 `messageId` 去重门之后,每条被接受的 transport publication 在到达应用 handler 之前还会经过三道关卡:
|
|
459
459
|
|
|
460
460
|
1. **`isAssigned(topic)`** — 在 owner Worker 上检查(`handleTransportMessage`)。如果该 topic 已不再分配给此 worker(比如前一个 ownership 窗口的过期消息),立即丢弃。这是外层关卡:防止非 owner 广播。
|
|
461
461
|
2. **`broadcastEvent('DATABUS_PUBLICATION', message)`** — 仅当 `isAssigned` 通过后调用。owner Worker 通过 BroadcastChannel `EVENT` 将消息扇出到所有 Tab。每个 Tab 收到事件但暂不分发——必须通过内层关卡。
|
|
462
462
|
3. **`hasLocalSubscriber(topic)`** — 在收到 `EVENT` 的每个 Tab 上检查。仅当该 Tab 有该 topic 的本地 subscriber 记录时才调用已注册的 handler。无本地订阅的 Tab 静默丢弃。
|
|
463
463
|
|
|
464
|
-
|
|
464
|
+
这三道关卡提供的是**每次已接受的 transport publication 至多扇出一次**:
|
|
465
465
|
- 外层关卡(`isAssigned`)防止过期 owner 重复广播。
|
|
466
466
|
- 内层关卡(`hasLocalSubscriber`)防止 Tab 分发自从未订阅过的 topic。
|
|
467
467
|
- BroadcastChannel 从不把消息回传给发送者,因此 owner 不会收到自己的 `EVENT`——本地分发是唯一一次本地投递。
|
|
468
468
|
|
|
469
|
+
这只是本地扇出保证,不是端到端投递保证。transport 或服务端可能重复投递,断连或挂起中的 Tab 可能错过 `EVENT`,BroadcastChannel 扇出也没有应用层确认。可选的 `dedup` 能在每个 bus 实例的有界窗口内抑制重复的 `messageId`,但它是尽力而为、按实例生效,并会被 `stop()` 重置。因此 SDK 不提供端到端的 at-least-once 或 exactly-once 保证;无法容忍重复或缺口的应用必须让 handler 幂等,并依赖其工作负载所需的 transport/服务端保证。
|
|
470
|
+
|
|
469
471
|
```text
|
|
470
472
|
Transport 消息 → isAssigned(topic)? → 是 → broadcastEvent(EVENT)
|
|
471
473
|
↓
|
|
@@ -499,14 +501,14 @@ Transport 消息 → isAssigned(topic)? → 是 → broadcastEvent(EVENT)
|
|
|
499
501
|
|
|
500
502
|
如果仍订阅某 Topic 的 Tab 发现 owner Worker 已退出或心跳过期,它会选择新 owner 并递增 route `generation`。正常 `pagehide` 迁移采用严格握手:新路由记录 `handoffFromWorkerId`,旧 owner 先退订 transport,再发送 `ROUTE_RELEASED(generation)`;只有 generation 匹配的新 owner 才发送 `SUBSCRIBE`。如果旧 Worker 已经消失,新 owner 立即接管。刷新后的旧 Tab 再次加入时只恢复 subscriber 记录并复用替代 owner,不会把 route 抢回。
|
|
501
503
|
|
|
502
|
-
该过程在正常 owner
|
|
504
|
+
该过程在正常 owner 交接时避免重复订阅,同时在故障恢复时保持可用;它不会把本地扇出保证变成端到端 at-least-once 或 exactly-once 投递。
|
|
503
505
|
|
|
504
506
|
## 稳定性不变量
|
|
505
507
|
|
|
506
508
|
以下不变量由回归测试固化(见 `tests/stability.test.ts` 与 `tests/replay-persistence.test.ts`),后续重构必须继续保持:
|
|
507
509
|
|
|
508
510
|
- **Handoff ACK 有效性。** `ROUTE_RELEASED` 只有在 route 仍指向接收方、释放来自记录的 `handoffFromWorkerId`、且 ACK generation 不小于存储 route 的 generation 时才被接受。来自更早交接轮次的重复 ACK(如 a↔b 反复交接)携带更旧的 generation,会被丢弃。
|
|
509
|
-
- **Replay 持久化清理顺序。** 排队在当前任务之后的批量持久化 flush 会与竞速的清理操作对账:`unsubscribe` 与 `clearReplayTopic` 丢弃该 topic 的待写条目,`clearReplayBefore`
|
|
511
|
+
- **Replay 持久化清理顺序。** 排队在当前任务之后的批量持久化 flush 会与竞速的清理操作对账:`unsubscribe` 与 `clearReplayTopic` 丢弃该 topic 的待写条目,`clearReplayBefore` 丢弃早于截止时间的条目,`suspend()`/`stop()` 则丢弃整个待写批次,避免它在新生命周期代际下启动。已清理或属于已停止会话的历史不会被在途 flush 复活。
|
|
510
512
|
- **存储写失败恢复。** 合并写入按指数退避重试(50 ms → 1.6 s 封顶)。结构性失败的关键在 5 次尝试后被丢弃(伴随 `console.warn`),且不会永久阻塞其他排队 key;队列完全清空或 `clear()` 取消重试后,退避延迟重置。
|
|
511
513
|
- **Transport 恢复预算。** 自动恢复由冷却时间限速、由 `recovery.maxAttempts` 限量,预算耗尽后标记 `exhausted`。成功的重开会重置尝试计数与 exhausted 标记;transport 宕机时显式 `subscribe` 仍可手动恢复。自动调度本身不会重开连接:后端必须先释放失效连接,重试才能创建或重开 socket。`WebSocketTransport` 只在 socket `open` 后 resolve `start()`;握手前的 `error`/`close` 或 `connectTimeoutMs` 超时都会 reject。它仅在 socket 有效期间将其标记为 active;`error`/`close` 会立即失效,下一次 `start()` 在调用工厂前清除旧引用,因此被取代 socket 的迟到回调会被忽略。
|
|
512
514
|
- **BFCache 挂起。** Tab 隐藏时停止 transport、递增持久化重试 generation(取消在途重试且不对外报错),同时暂停 trace metrics 与 dedup/replay 周期清理并门控分发;`pageshow` 或显式 `start()` 会重开 transport、恢复这些周期资源,并且每轮循环只重建一次订阅。
|
|
@@ -537,7 +539,7 @@ DataBus 将"业务订阅意图"与"transport 当前订阅状态"分离。transpo
|
|
|
537
539
|
| `suspended` | `boolean` | Tab 已隐藏;transport 被有意暂停 |
|
|
538
540
|
| `transportReady` | `boolean` | 本次会话中 transport 已成功打开;运行期 `error` 后会保留,使 `ready()` 继续跟随已安装的 transport(待执行操作由恢复门而非该标记控制) |
|
|
539
541
|
| `startPromise` | `Promise \| null` | 并发 `start()` 调用的 gate;操作完成后清除 |
|
|
540
|
-
| `stopPromise` | `Promise \| null` | 显式 `stop()` 及其后排队的 restart 共享的 gate |
|
|
542
|
+
| `stopPromise` | `Promise \| null` | 显式 `stop()` 及其后排队的 restart 共享的 gate;仅在 `stopping` 为 true 时复用,因为它会在 `performStop()` settle 后再过一个微任务才被清空 |
|
|
541
543
|
| `queuedStart` | `Promise \| null` | 等待进行中的显式 stop 完成后执行的一次全新 start |
|
|
542
544
|
| `queuedStartToken` | `number` | 每次排队 restart 获得的单调令牌,避免取消被误认为更晚的 restart |
|
|
543
545
|
| `canceledQueuedStartToken` | `number` | 被 `stop()` 取消的最高 queued-restart 令牌;令牌不高于它的续体只 resolve,不打开 transport |
|
|
@@ -574,10 +576,11 @@ DataBus 将"业务订阅意图"与"transport 当前订阅状态"分离。transpo
|
|
|
574
576
|
|
|
575
577
|
- **并发 start**:真实 transport open 在飞行中时,第二次调用 `start()` 返回同一个 promise,任何时候只有一个 transport open 在飞行中。pagehide 产生的 stop 也可能占用 `startPromise`;`start()` 会识别 `startPromise === pendingStop`,把 reopen 排在该 stop 之后,而不是把清理 promise 当作成功启动返回。
|
|
576
578
|
- **失败通知顺序**:transport 可能在 `start()` 仍在飞行时同步上报 `error`。`openTransport()` 会立即更新内部状态,但会延迟用户可见的 `onStatus('error')` 通知,直到它清除 `transportReady`、拆掉初始启动的 cluster、安装失败 transport 的 stop gate、记录失败并清除 `startPromise`;启动失败的 `onError` 通知也在这些清理之后发送。因此在任一回调中同步调用 `start()` 都会在 stop gate 之后开启全新尝试,而不是共享刚刚 reject 的 promise。重试会重置失败账本,被取代 opening 的 rejection 由其自身生命周期清理消费,不会在重试成功后再次写回。真实 open 仍在飞行时的普通并发 start 仍共享同一个 promise。
|
|
577
|
-
- **显式 stop 期间 start**:`stop()` 用共享的 `stopPromise` 服务并发调用者。若 `start()` 在该 stop settle 期间到达,只保存一个 `queuedStart`;stop 的 `finally` 清理生命周期状态后,排队的 start 使用新配置开启全新生命周期。此窗口内的重复调用共享 stop 和 queued-start promise
|
|
579
|
+
- **显式 stop 期间 start**:`stop()` 用共享的 `stopPromise` 服务并发调用者。若 `start()` 在该 stop settle 期间到达,只保存一个 `queuedStart`;stop 的 `finally` 清理生命周期状态后,排队的 start 使用新配置开启全新生命周期。此窗口内的重复调用共享 stop 和 queued-start promise。该共享 gate 只在 `stopping` 为 true 时复用:`performStop()` 在 `finally` 中把 `stopping` 翻回 false,而 `stopPromise` 要再过一个微任务才清空,因此落在这个缝隙里的 `start()`/`stop()` 必须 fall through 到一次全新 teardown,而不是对已 settle 的 gate resolve 并放任重启后的 bus 继续运行。
|
|
578
580
|
- **stop 取消排队 restart**:排队续体已经挂在 stop promise 上、无法撤销调度,因此在它执行前再次 `stop()` 会改为使其失效。每个排队 restart 携带单调令牌;`stop()` 记录当前令牌并释放唯一的队列槽位,续体发现自己的令牌不再是最新时只 resolve、不打开 transport。排队 `start()` Promise 保留这一「取消即 resolve」契约,而单独的 readiness 视图会让 `ready()` 对被取消的意图 reject。由于后到的 `start()` 会签发更高令牌,`stop → start → stop → start` 仍以运行态结束,而 `stop → start → stop` 以停止态结束且不会多打开一次 transport。
|
|
579
581
|
- **停止期间发布拒绝**:`stop()` 设置 `stopping` 后,新发起的 `publish()` 与非空 `publishBatch()` 无法到达 transport;它们通过 `onError` 上报错误,而不是让 `runTransport()` 静默返回;空 batch 仍为 no-op。已排队在飞行中 open 之后的发布会被 stop 取消(最新生命周期意图优先),而页面隐藏挂起仍保持文档所述的「不延迟、直接丢弃」语义。
|
|
580
582
|
- **停止期间生命周期操作拒绝**:`stopping` gate 同样覆盖 `subscribe()` 与 `ready()`。迟到的 `subscribe()` 会通过 `onError` 上报并返回 no-op 释放函数,避免 handler 被 `topicHandlers.clear()` 清掉,或订阅漂移进下一次 restart 却没有对应 handler。`ready()` 会 reject,而不是对正在停止的 transport 报告 ready。若 `start()` 已在该 stop 之后排队重启,`ready()` 仍返回 queued-start promise,因为这是最新生命周期意图。
|
|
583
|
+
- **停止期间健康判定**:`getHealthSummary()` 在整个 teardown 期间都报告 `state: 'stopped'` / `healthy: false`,而不只是在 `started` 翻回 false 之后。transport 的异步 `stop()` 尚未 settle 时可能仍上报 `connected`,仅依据实时状态推导健康会得到自相矛盾的快照:一边宣称 bus 可用,一边 `publish()`、`subscribe()`、`ready()` 已经全部拒绝。`started` 仍反映真实生命周期标志,`transport` 仍把实时 status/`ready` 作为诊断信息输出;排在 stop 之后的 restart 在真正接管生命周期后报告为 `starting`。
|
|
581
584
|
- **排队重启失败保留**:排队重启若在 transport 启动阶段失败,会清除 `started`,但为后续 `ready()` 调用保留真实错误。未提供 `initialConfig` 时,这些调用会以启动失败 reject,而不是返回通用的配置错误;显式 `start(config)` 仍以全新失败账本执行干净的手动重试。
|
|
582
585
|
- **启动期间隐藏**:`pagehide` 在 `openTransport` 飞行中触发时,`suspendTransport()` 设置 `suspended = true`,并在飞行中的 start 之后链式执行 `transport.stop()`。`openTransport` 的 catch 路径检测到 `suspended` 后放弃本次 open,不视为失败。当重复的 hide/show 让排队的 resume opening 与更早的 stop gate 交错时,挂起会安装新的串行 stop gate 并恢复 `startPromise === pendingStop` 不变量,使下一次 `pageshow` 真正重开,而不是复用已被淘汰的 opening 并永久停留在挂起状态。
|
|
583
586
|
- **被取代 open 失效**:每次全新 start、reopen、suspend 和 stop 都会推进 `lifecycleEpoch`。open 会捕获自己的 epoch;一旦更新的转换接管生命周期,旧 open 的 status/message/error 回调会被忽略,也不会再把 transport 标记为 ready 或执行失败清理。因此 `stop()` 会等待未完成的 open/reopen,并阻止被取代的 open 在 stop 完成后变为 ready。
|
package/docs/zh/benchmarks.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
# 浏览器基准趋势
|
|
4
4
|
|
|
5
|
-
> 数据截至 2026-09-
|
|
5
|
+
> 数据截至 2026-09-15,基于 23 份归档的 `bench-results/browser-*.json` 报告(运行 `pnpm bench:browser` 追加一份;用 `node scripts/bench-trend.mjs` 重新生成本文档)。
|
|
6
6
|
|
|
7
7
|
发布门禁的对比基线是最近两份报告之间的 `pnpm bench:compare --fail-above-pct 50`(50% 上限用于吸收共享 runner 的噪声)。本文记录长期趋势:数值为逐指标延迟,越低越好;历史最优为本机观察到的最健康一次运行。
|
|
8
8
|
|
|
9
9
|
<!-- BENCH-TREND:BEGIN (machine-generated table) -->
|
|
10
10
|
| 指标 | 上次 (ms) | 本次 (ms) | Δ | 历史最优 (ms) |
|
|
11
11
|
|---|---|---|---|---|
|
|
12
|
-
| publish per-message (ms, lower is better) — dedicated |
|
|
13
|
-
| publish per-message (ms, lower is better) — shared |
|
|
14
|
-
| wildcard dispatch ×1000 (ms, lower is better) |
|
|
15
|
-
| publishBatch ×1000 (ms, lower is better) | 4.
|
|
16
|
-
| dedup ×1000 (ms, lower is better) |
|
|
17
|
-
| trace + publish ×1000 (ms, lower is better) |
|
|
18
|
-
| first-packet cold dispatch (ms, lower is better) | 0
|
|
12
|
+
| publish per-message (ms, lower is better) — dedicated | 53.7635 | 42.335 | -11.43 | 35.3543 |
|
|
13
|
+
| publish per-message (ms, lower is better) — shared | 39.2276 | 35.806 | -3.42 | 33.6784 |
|
|
14
|
+
| wildcard dispatch ×1000 (ms, lower is better) | 7.4 | 7.2 | -0.20 | 0.1 |
|
|
15
|
+
| publishBatch ×1000 (ms, lower is better) | 4.3 | 4.9 | +0.60 | 0.4 |
|
|
16
|
+
| dedup ×1000 (ms, lower is better) | 17.4 | 21.5 | +4.10 | 0 |
|
|
17
|
+
| trace + publish ×1000 (ms, lower is better) | 6.7 | 5.3 | -1.40 | 4.8 |
|
|
18
|
+
| first-packet cold dispatch (ms, lower is better) | 0 | 0.1 | +0.10 | 0 |
|
|
19
19
|
<!-- BENCH-TREND:END -->
|
|
20
20
|
|
|
21
21
|
说明:
|
package/docs/zh/capabilities.md
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
| 性能 | 协调元数据批量写入 + 退避重试 | ✅ 已实现 | 心跳、路由和 subscriber 写入合并后在微任务中 flush;失败时指数退避;`pagehide` / `stop()` 同步 flush |
|
|
29
29
|
| 性能 | 可选 ArrayBuffer Transferable 传输 | ✅ 已实现 | 开启 `transferable: true` 后,二进制 publish/receive 跳过 structured clone 复制;对象消息 API 不变 |
|
|
30
30
|
| 性能 | 可选 `DataBusTransport.publishBatch` 单帧突发发布 | ✅ 已实现 | 内置 WebSocket transport 将多条消息合并为一帧(`publishBatch` op,demo server 已支持);无批量能力的 transport 回退逐条 `publish` |
|
|
31
|
-
| 消息语义 | exactly-once 投递 | 未实现 |
|
|
31
|
+
| 消息语义 | 端到端 at-least-once 或 exactly-once 投递 | 未实现 | 每条被接受的 transport publication 只会扇出一次,每个匹配的本地 handler 至多分发一次;但 transport/服务端仍可能重复或丢失,断连/挂起期间跨 Tab 事件也可能丢失,按实例有界的可选 `messageId` 去重仍不足以提供端到端 at-least-once 或 exactly-once 保证 |
|
|
32
32
|
| 消息语义 | 可插拔的 publication 去重 | ✅ 已实现 | 按 `DataBusMessage.messageId` 做可选有界入站抑制;默认关闭,ID 仍由调用方/服务端控制 |
|
|
33
33
|
| 认证 | Worker 内异步凭证刷新桥接 | ✅ 已实现 | `createCentrifugeDataBus` 的可选 `credentialProvider`(`getToken` / `getChannelToken`)。Worker 配置保持结构化可克隆;Worker 通过 TOKEN_REQUEST / TOKEN_RESPONSE 交换向主线程请求每个新凭证,由 provider 从应用上下文提供 |
|
|
34
34
|
| 负载策略 | 按消息速率、字节数或调度滞后自适应加权 | ✅ 已实现 | 可选的 `loadWeighting`(`messageRateWeight`、`byteRateWeight`、`scheduleLagWeight`):Workers 按窗口采样自身 fan-out 流量与心跳调度超时并随记录发布;新 route 的 owner 选择在 Topic 数之上加入归一化速率与滞后比率。默认(未设置)保持纯按 Topic 数;已有 route 保持 sticky |
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
| 不变量 | 领域 | 被回归固化的保证 |
|
|
45
45
|
|---|---|---|
|
|
46
46
|
| 交接 ACK 有效性 | 协调 | 仅当 route 仍指向接收方、释放方匹配 `handoffFromWorkerId`、且 ACK 代数 ≥ 存储 route 代数时才接受 `ROUTE_RELEASED`——更早交接轮次的过期 ACK(如 a↔b 乒乓)会被丢弃 |
|
|
47
|
-
| 回放持久化清理顺序 | 持久性 | 排队中的批量 flush 会按先到清理过滤(`unsubscribe`/`clearReplayTopic` 丢弃该 topic 的待写条目,`clearReplayBefore`
|
|
47
|
+
| 回放持久化清理顺序 | 持久性 | 排队中的批量 flush 会按先到清理过滤(`unsubscribe`/`clearReplayTopic` 丢弃该 topic 的待写条目,`clearReplayBefore` 丢弃早于截止时间的条目,`suspend()`/`stop()` 丢弃整个待写批次);已清或已停止会话的历史不会被进行中的 flush 重新追加 |
|
|
48
48
|
| 存储写入恢复 | 协调 | 合并写以指数退避重试(50 ms → 1.6 s 上限);结构性失败键在 5 次后丢弃(并 `console.warn`)而不阻塞其他排队键;队列清空或 `clear()` 取消后退避重置 |
|
|
49
49
|
| 传输恢复预算 | 生命周期 | 自动恢复由冷却间隔节流、以 `recovery.maxAttempts` 为界,预算耗尽时报 `exhausted`;成功重开后重置尝试计数与 exhausted,断线传输上的显式 `subscribe` 仍可手动恢复 |
|
|
50
50
|
| BFCache 挂起 | 生命周期 | 隐藏页面停掉传输并静默取消进行中的持久化重试;pageshow 重开传输并每个周期恰好一次重建订阅 |
|
package/docs/zh/configuration.md
CHANGED
|
@@ -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 行为。
|
|
57
|
+
`replay.persistenceRetry` 可选地控制瞬时持久化失败的恢复。`maxAttempts` 是总尝试次数(默认 `1`),`backoffMs` 是首次重试前的延迟(默认 `50`);延迟会指数增长并封顶。最终失败仍沿用现有 `onError` 和 reliability 行为。bus 挂起或停止时仍排队在微任务中的批量 flush 会随其生命周期代际一起丢弃,不会在 teardown 后启动 durable append。
|
|
58
58
|
|
|
59
59
|
### Replay 选项
|
|
60
60
|
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
|
|
31
31
|
## 打 tag 的发布工作流
|
|
32
32
|
|
|
33
|
-
推送版本 tag 会触发 `Release` GitHub Action:先跑 `pnpm check` 与 `pnpm lint`(tag 可能指向从未通过 CI lint 步骤的提交),再跑 `verify:compat` 与 `verify:pack`,从 `CHANGELOG` 对应章节生成 GitHub release,配置了 `NPM_TOKEN` 时自动发布到 npm,然后运行与手动执行相同预算的**阻塞式**消费者验证(`PUBLISHED_VERIFY_ATTEMPTS=
|
|
33
|
+
推送版本 tag 会触发 `Release` GitHub Action:先跑 `pnpm check` 与 `pnpm lint`(tag 可能指向从未通过 CI lint 步骤的提交),再跑 `verify:compat` 与 `verify:pack`,从 `CHANGELOG` 对应章节生成 GitHub release,配置了 `NPM_TOKEN` 时自动发布到 npm,然后运行与手动执行相同预算的**阻塞式**消费者验证(`PUBLISHED_VERIFY_ATTEMPTS=48`、`PUBLISHED_VERIFY_DELAY_MS=7500`,即 6 分钟上限)。已发布包若无法被干净消费者导入,工作流即失败——任何 `verify:published` 失败都应视为发布失败,修复后重新发布该 tag。未配置 token 时跳过发布步骤,但验证仍会针对 npm 上已有的版本(例如手动发布的)通过。
|
|
34
34
|
|
|
35
35
|
## 发布(手动场景)
|
|
36
36
|
|
|
@@ -42,4 +42,4 @@
|
|
|
42
42
|
2. 在干净消费者中安装已发布版本或 tarball,并导入主入口及所有公开子路径。
|
|
43
43
|
3. 将结果记录到发布说明。在公开 API 和协议弃用策略明确冻结前,不进入 `1.0.0`。
|
|
44
44
|
|
|
45
|
-
打 tag 的发布工作流已自动执行上述消费者验证;仅在需要离线复验时才手动运行 `PUBLISHED_VERSION=<version> pnpm verify:published
|
|
45
|
+
打 tag 的发布工作流已自动执行上述消费者验证;仅在需要离线复验时才手动运行 `PUBLISHED_VERSION=<version> pnpm verify:published`。工作流默认的 6 分钟上限足以吸收 npm CDN 的正常传播延迟(0.20.89 tag 首次运行在发布成功后排空了旧的 2 分钟预算);只有遇到异常慢的镜像才需要继续调大 `PUBLISHED_VERIFY_ATTEMPTS` 和 `PUBLISHED_VERIFY_DELAY_MS`。
|
package/docs/zh/roadmap.md
CHANGED
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# 路线图
|
|
2
2
|
|
|
3
|
-
0.20.
|
|
3
|
+
0.20.90 已发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
|
|
4
|
+
|
|
5
|
+
## 0.20.90 已完成范围
|
|
6
|
+
|
|
7
|
+
本开发线修复 0.20.89 tag 首发暴露的发布管线问题,并统一文档中的投递语义。
|
|
8
|
+
|
|
9
|
+
- 发布门禁传播预算:阻塞式已发布包消费者验证从 24 × 5 s(2 分钟)提升为 48 × 7.5 s(6 分钟)。0.20.89 的 tag 首发发布成功之后仍在该门禁失败,因为 `npm pack` 在旧预算内始终返回 `ETARGET`;真正缺失的包仍会耗尽预算而失败。
|
|
10
|
+
- 投递语义:API、架构与能力文档现在一致说明有界的本地 fan-out 保证,并明确 SDK 不提供端到端的 at-least-once 或 exactly-once 投递;中文 API 参考已补充按 bus 可选启用的 `messageId` 去重窗口。
|
|
11
|
+
- 发布性能证据:基准趋势文档已基于 23 份归档报告刷新;最近两次运行对比未超过 50% 上限,两种 Worker 模式的单消息发布延迟均有改善。
|
|
12
|
+
|
|
13
|
+
## 0.20.89 已完成范围
|
|
14
|
+
|
|
15
|
+
本开发线延续异步回调隔离审计:以下每一项修复都把回调、排队微任务或 Promise 续体绑定到创建它的生命周期 generation,使被取代的会话无法写入其替代者。
|
|
16
|
+
|
|
17
|
+
- 异步 teardown 与重启边界:已 settle 的 `stop()` gate 不再吞掉后续 teardown(`stop → start → stop` 现在以停止态结束);`getHealthSummary()` 对正在停止的 bus 报告 `state: 'stopped'`,不再与其已经发出的 `publish()` / `subscribe()` / `ready()` 拒绝语义自相矛盾。
|
|
18
|
+
- replay 持久化隔离:微任务排队的 batch flush 与被排队的 retention cleanup 会在 `suspend()` / `stop()` 取代其 generation 后被丢弃,已停止会话的历史无法再写入 durable store。
|
|
19
|
+
- durable hydration 取消:在 `suspend()` 或 `stop()` 之后才 resolve 的 `load()` 不再向 teardown 已清空的缓冲区追加数据,并按生命周期取消上报,而不是记为持久化失败。
|
|
20
|
+
- trace 会话隔离:已停止的 trace reporter 保持惰性——旧会话排队中的 `asyncSink` 事件被丢弃,显式重启会清除停止标记,使其重新发出生命周期 `start`。
|
|
21
|
+
- transport 与 Worker 回调隔离:Centrifuge credential-provider 结果绑定到发起请求的确切 Worker/port/session;`CentrifugeSession` 的异步 client/subscription 回调在 `STOP` 或重新初始化后被忽略;来自已替换 WebSocket 连接的 `Blob` 二进制帧不再被当作新连接的帧派发。
|
|
22
|
+
- 回归安全网:seeded lifecycle fuzzer 现在覆盖 1_500 种交织,并断言最后一次显式意图为 `stop()` 的序列会以 `state: 'stopped'` 且无 live transport 结束。
|
|
4
23
|
|
|
5
24
|
## 0.20.88 已完成范围
|
|
6
25
|
|
|
@@ -495,7 +514,7 @@
|
|
|
495
514
|
## 0.13.0 候选
|
|
496
515
|
|
|
497
516
|
1. 冻结公共导出面与 transport 无关的 publication 信封。
|
|
498
|
-
2.
|
|
517
|
+
2. ~~精确记录 at-least-once 投递与去重保证。~~ 已交付:architecture/API/capability 文档现在明确区分「每条已接受 transport publication 的本地至多一次扇出」与端到端投递,记录 transport/服务端的丢失与重复投递,并说明有界、可选 `messageId` 去重的边界,不再宣称 at-least-once 或 exactly-once。
|
|
499
518
|
3. 增加长时浏览器浸泡覆盖:replay 留存、重连、BFCache 与 owner 迁移。
|
|
500
519
|
4. 为 1.0 前的协议别名发布迁移指南与弃用策略。
|
|
501
520
|
|
package/package.json
CHANGED