cross-tab-worker-databus 0.21.13 → 0.21.15

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.
@@ -20,7 +20,7 @@
20
20
  1. 更新 `package.json`、`CHANGELOG.md` 和中英文 roadmap。
21
21
  2. 运行 `pnpm check`、`pnpm lint`、`pnpm test:coverage`、`pnpm bench`、`pnpm test:e2e`、`pnpm bench:browser`、`pnpm verify:pack`、`pnpm verify:compat`、`pnpm verify:types` 以及 `git diff --check`(`verify:compat` 断言 `COMPAT_BASE_TAG` 基线中的 package `exports` 子路径与类型字段仍然存在;`verify:pack` 从打包产物冒烟导入完整根公共面与全部子路径的 ESM/CJS;`verify:types` 用同一基线冻结每个公共入口声明层导出的名字,并断言没有任何公共签名引用了「任何入口都未导出」的类型——纯类型导出不会在运行时产生任何键,因此 `tests/dual-format.test.ts` 的 `Object.keys()` 冻结和 `verify:compat` 都看不到它的变化。`verify:compat` 与 `verify:types` 从最近的发布 tag 解析基线,因此浅克隆或缺少 tag 的克隆需先执行 `git fetch --tags`,否则会以 "no version tag found" 失败)。
22
22
  3. 依赖安全门禁:`pnpm audit --registry=https://registry.npmjs.org`(配置的镜像 registry 缺少 audit 端点;CI 在 verify job 中于公共 registry 运行)。任一已知漏洞公告即视为发布失败;`pnpm-workspace.yaml` overrides 钉住补丁版本。
23
- 4. 浏览器基准回归门禁:运行 `pnpm bench:browser`(至少两次;门禁将最新报告与之前最多五份归档报告中同一指标的中位数比较,**并且额外要求新值超过这些样本中的最大值**),随后执行 `pnpm bench:compare --fail-above-pct 50`。采用中位数基线是因为页内热路径指标在同一份代码上会在快/慢两档之间来回跳——`dedup1000Ms` 相邻两次实测分别为 12.7 ms 与 25.6 ms,只看"上一份报告"时,一次偶发噪声就能在无代码改动的情况下击穿既定上限。第二个条件则是因为:在两档交替的样本上,中位数只有当两档保持混合时才稳定——一旦连续几份报告落在同一档,中位数本身就会跳变,这正是同一份纯注释改动在一版基线前读出 +121%、几分钟后在另一版基线前读出 −56.7% 的原因。被该条件放过的指标会打印在 `within-baseline, not gated` 下,而不是静默通过——在当前归档上实测,`publish/dedicated/perMessageMs`、`publish/shared/perMessageMs` 与 `databus/publishBatch1000Ms` 三项都落在各自五份报告的区间内,因此应把这份清单读作该指标的真实分辨率,而不是一张合格证。`publish/dedicated/perMessageMs` 同样如此:同一份代码相邻两次实测为 49.9 ms 与 71.1 ms,令门禁以 +75.7% 失败,而同一份报告里 `dedup1000Ms` 与 `traceAndPublish1000Ms` 反而*变快*——"单指标上涨、其余下降"正是噪声的特征,第三次实测为 49.4 ms 并通过。因此在采信或处理失败前先复跑并观察离散度;若改动涉及热路径,就直接量化其开销(0.20.95 有界映射改写的微基准显示三种写法均为 80–86 ns/op,落在运行间波动之内)。基线迁移(例如某指标从空操作变为真实路径)属预期内的一次性失败。用 `pnpm bench:trend` 刷新长期趋势文档,表格变化时一并提交。
23
+ 4. 浏览器基准回归门禁:运行 `pnpm bench:browser`(至少两次;门禁将最新报告与之前最多五份归档报告中同一指标的中位数比较,**并且额外要求新值超过这些样本中的最大值**),随后执行 `pnpm bench:compare --fail-above-pct 50`。采用中位数基线是因为页内热路径指标在同一份代码上会在快/慢两档之间来回跳——`dedup1000Ms` 相邻两次实测分别为 12.7 ms 与 25.6 ms,只看"上一份报告"时,一次偶发噪声就能在无代码改动的情况下击穿既定上限。第二个条件则是因为:在两档交替的样本上,中位数只有当两档保持混合时才稳定——一旦连续几份报告落在同一档,中位数本身就会跳变,这正是同一份纯注释改动在一版基线前读出 +121%、几分钟后在另一版基线前读出 −56.7% 的原因。被该条件放过的指标会打印在 `within-baseline, not gated` 下,而不是静默通过——在当前归档上实测,`publish/dedicated/perMessageMs`、`publish/shared/perMessageMs` 与 `databus/publishBatch1000Ms` 三项都落在各自五份报告的区间内,因此应把这份清单读作该指标的真实分辨率,而不是一张合格证。`publish/dedicated/perMessageMs` 同样如此:同一份代码相邻两次实测为 49.9 ms 与 71.1 ms,令门禁以 +75.7% 失败,而同一份报告里 `dedup1000Ms` 与 `traceAndPublish1000Ms` 反而*变快*——"单指标上涨、其余下降"正是噪声的特征,第三次实测为 49.4 ms 并通过。因此在采信或处理失败前先复跑并观察离散度;若改动涉及热路径,就直接量化其开销(0.20.95 有界映射改写的微基准显示三种写法均为 80–86 ns/op,落在运行间波动之内)。基线迁移(例如某指标从空操作变为真实路径)属预期内的一次性失败。用 `pnpm bench:trend` 刷新长期趋势文档,表格变化时一并提交。**复跑失败可能亲手制造出让它通过的基线,这一点已被实测。** `bench:compare` 的基线(中位数与最大值两个条件)取自最近若干份归档报告,而每一次 `pnpm bench:browser` 都会往同一个归档里追加——于是排查失败时新增的样本,就变成了裁决下一次尝试的基线。2026-09-24:主机负载均值 7.7–15.1、待发布树的非注释 `src/` 差异为 0 行,前两次实测 `publish/dedicated 77.3 ms`、`publish/shared 67.7 ms`,相对约 38/34 ms 的快档在 50% 上限上失败;随后五次复跑报告 **OK**,因为基线中位数已经滑到 `70.6`。把这些样本移出归档后的一次干净实测显示所有指标同时上涨(`dedicated +92.5%`、`shared +99.2%`、`dedup +43.4%`、`trace +21.0%`),那是资源竞争的形态,与上文"单指标上涨、其余下降"的噪声形态相反。因此:复跑之前先看负载(`uptime`);在满载主机上遇到可疑失败,就把数字连同负载一起记为"本轮推迟",并将偏离常态的样本移出基线——也不要据它们重新生成 `docs/benchmarks.md`。
24
24
  5. 用 `npm pack --dry-run --json` 确认发布包只包含预期文件。
25
25
  6. 在功能分支提交并推送该分支,PR 验证通过后使用 squash 或 fast-forward 合入(不创建 merge commit)。获取合入后的精确提交并打 tag,只推送该版本 tag;禁止直接推送 `main`/`master` 或 force-push。工作流运行 `node scripts/verify-release-version.mjs`,要求 `RELEASE_TAG` 等于 `v` 加 package 版本,且 CHANGELOG 中恰好有一个非空的对应版本章节。
26
26
 
@@ -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,然后运行**阻塞式**消费者验证,其预算是手动默认值的十二倍——工作流在自己的 env 里设置 `PUBLISHED_VERIFY_ATTEMPTS=48`、`PUBLISHED_VERIFY_DELAY_MS=7500`(约 6 分钟上限),而手动运行 `scripts/verify-published-consumer.mjs` 时的默认值是 6 × 5000 ms = 30 秒。已发布包若无法被干净消费者导入,工作流即失败——任何 `verify:published` 失败都应视为发布失败。若为 registry 传播延迟或基础设施故障,针对不变的 tag 重跑工作流;若为产物缺陷,发布新的 patch 版本。禁止移动或重用已发布 tag。未配置 token 时跳过发布步骤,但验证仍会针对 npm 上已有的版本(例如手动发布的)通过。
33
+ 推送版本 tag 会触发 `Release` GitHub Action:先跑 `pnpm check` 与 `pnpm lint`(tag 可能指向从未通过 CI lint 步骤的提交),再跑 `verify:compat`、`verify:types` 与 `verify:pack`,从 `CHANGELOG` 对应章节生成 GitHub release,配置了 `NPM_TOKEN` 时自动发布到 npm,然后运行**阻塞式**消费者验证,其预算是手动默认值的十二倍——工作流在自己的 env 里设置 `PUBLISHED_VERIFY_ATTEMPTS=48`、`PUBLISHED_VERIFY_DELAY_MS=7500`(约 6 分钟上限),而手动运行 `scripts/verify-published-consumer.mjs` 时的默认值是 6 × 5000 ms = 30 秒。已发布包若无法被干净消费者导入,工作流即失败——任何 `verify:published` 失败都应视为发布失败。若为 registry 传播延迟或基础设施故障,针对不变的 tag 重跑工作流;若为产物缺陷,发布新的 patch 版本。禁止移动或重用已发布 tag。未配置 token 时跳过发布步骤,但验证仍会针对 npm 上已有的版本(例如手动发布的)通过。
34
34
 
35
35
  ## 发布(手动场景)
36
36
 
@@ -1,6 +1,58 @@
1
1
  # 路线图
2
2
 
3
- 0.21.13 已于 2026 年 9 月 24 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
3
+ 0.21.15 已于 2026 年 9 月 24 日发布。项目会先持续完成可靠性发布,再进入 1.0.0 稳定性冻结。
4
+
5
+ ## 0.21.15 已完成范围
6
+
7
+ - 两个点对点寻址守卫现在都被具名钉住了。`handleMessage` 只按 `type` 分发,而集群频道是广播的,所以
8
+ `targetWorkerId !== this.workerId` 这一行是唯一阻止某个 runtime 处理发给别人的帧的东西;逐行删除后测量,
9
+ 先后有 369 个和 358 个测试仍然通过,而协调模糊测试无法断言它,因为它的不变式是结束态检查,收敛得慢一点
10
+ 依然算通过。
11
+ - `ROUTE_RELEASED` 的那颗钉子需要一个它的陈旧性测试无法拒绝的构造:一条确实把本 runtime 写成 owner 的持久
12
+ 路由,配上匹配的 `generation` 与 `handoffFromWorkerId`,再把 ACK 投给第三个 worker。这也正是较早那个伪造
13
+ `topicKey` 的用例从来走不到这一行的原因。
14
+ - 协调模糊测试增加了第三层限制,位置选在环路两边都不拥有的地方。原有的两层都由时间构成,而把 `verify` 跑红
15
+ 的那个失败把时间花在微任务里,同线程上的任何截止时间都碰不到它;`ChannelHub` 现在会在单个 seed 超过 50,000
16
+ 次投递后停止投递,阈值取自实测分布(p50 = 28、p99 = 90、最大值 = 16,763)而不是卡死的大小,被切断的 seed
17
+ 会按名字打印并且不计入深度下限。
18
+ - 那条环路到底是什么,随后是被测量而不是被推断出来的,而它是测试装置而不是库:11,542 次接受对应 11,538 次
19
+ `confirmRoute` 跳过,而落到存储的写只有 6 次——因为 owner 的确认还躺在它自己的合批写入器里,对端读到的却是
20
+ 已刷出的记录;而这个循环之所以让不出微任务,是测试用的 hub 把每一帧都在调用栈上同步投递。把投递推迟一个
21
+ 微任务,风暴就彻底消失。本文件里此前把它写成"未修复的产品缺陷"的说法在此更正,而更正之后留下的六个注入帧
22
+ seed 作为下一个问题被点名,而不是被搪塞过去。
23
+ - `docs/release-checklist.md` 两种语言现在都写明:`pnpm bench:compare` 会被"调查它"这个动作本身关掉。它的
24
+ 基线取自同一个滚动归档,而每次 `bench:browser` 都在往那个归档追加,所以规程变成:先看主机负载,把可疑失败
25
+ 连同负载记为 deferred,而不是去追一个绿色。
26
+ - `docs/progress.md` 有了结构性门禁。同一个阶段的两个 PR 曾留下两个 `## Phase 142` 块,其中一个被挤到所有
27
+ 条目都追加在前的那个锚点之下;`tests/documentation.test.ts` 现在要求每个阶段编号既是新的、又晚于上一条、
28
+ 并且在常驻章节之上。
29
+
30
+ 没有行为、线格式、存储布局或集群协议变更:本版本 `src/` 未改动,发布义务来自随包发布的
31
+ `docs/release-checklist.md` 与 `CHANGELOG.md`。
32
+
33
+ ## 0.21.14 已完成范围
34
+
35
+ - 中文版公开文档第一次被逐段对照 `src/` 读过。`package.json` 的 `files` 会发布十篇 `docs/*.md` 加上
36
+ 它们的 `docs/zh/` 副本和两份 README,而此前的每一轮审计都是按"当下正在读哪一种语言"逐句修正的——
37
+ 于是中文段落保留了英文已经撤回的说法,英文也保留了只有它自己的表格修过的说法。这一轮共处理了
38
+ 22 类说法,两个方向都有。
39
+ - 协调模糊测试现在可以约束单个 seed 了。一次 `verify` 运行报出
40
+ `stopped at 1046/5000 seeds after 158476ms (slowest seed 151850ms)`,随后撞上 120 秒的单测上限:
41
+ 其余 1,045 个 seed 合计约每个 6.6 毫秒,而某一个交织吃掉了整个扫描 96% 的墙钟时间,位于 seed 之间的
42
+ 保险丝无法伸进它内部。现在每个会等待的步骤都与一个 2 秒期限赛跑;触发期限的 seed 会被计数、按其操作
43
+ 列表点名,并且**不再被断言**——它的终态从未到达静默,在那里断言只会制造假失败。
44
+ - 这个守卫不能做什么是被测量的,而不是被假设的:把期限压到 12 毫秒跑完整 5,000 seed,它触发**零**次,
45
+ 因为本地能复现的慢 seed 全部是第一个 tab 的 `stop()` 里的**同步**开销——同一线程上的期限无法抢占它。
46
+ 于是机制被直接钉住且双向断言,三个变异体各自死在不同断言上:永不超时的期限、过早超时的期限,以及把
47
+ 已完成的工作误报为超时的期限。
48
+ - `docs/architecture.md` 的 `knownTopics` 一节只列出了实际填充该缓存的六个进程内调用点中的三个;
49
+ `publishBatch` 和 `activate()` 在两种语言里都完全没有出现。
50
+ - 本周期在别处反复撤回的那类 storage 绝对说法,原来藏在**表格单元格里**:标识符表的 `topic` 行写着
51
+ "否;仅在 Runtime 内存和控制消息中出现",而同一张表靠后的行说明 storage-event 的每一帧(含 Topic
52
+ 明文)都会整体写入 `localStorage`。此前有四轮审计经过这个文件,其中一轮修正的正是那行相矛盾的内容。
53
+ `configuration.md` 的边界清单还把 tab-id 键归在 `localStorage` 记录里——它其实写在 `sessionStorage`。
54
+ - 无行为、线格式、存储布局或集群协议变更。唯一可执行的改动在 `tests/`(不随包发布);`src/` 中的差异
55
+ 仅为注释。
4
56
 
5
57
  ## 0.21.13 已完成范围
6
58
 
@@ -14,8 +66,12 @@
14
66
  是一条没有持久路由就被接受的 `CONTROL/SUBSCRIBE`,而这个状态正是 `tests/cluster.test.ts` 已经钉住的。
15
67
  - 关于*依赖*的说法现在会点出依赖本身。针对 `centrifuge@5.7.4`:客户端级的凭据钩子只有一个,包里根本不存在
16
68
  `getChannelToken`,因此那类请求从未在测试替身之外触发过;而 `CentrifugeSession.unsubscribe()` 会移除
17
- emitter 自己的 no-op `error` 监听,注释却声称"内部监听被保留"。第二项被记为一个**尚未定论的问题**,并写明
18
- 要构造出怎样的交错才能称其为缺陷——测试替身无法呈现该保护,因此现有测试无论正反都看不见它。
69
+ emitter 自己的 no-op `error` 监听,注释却声称"内部监听被保留"。第二项先被记为一个尚未定论的问题——测试
70
+ 替身无法呈现该保护,因此现有测试无论正反都看不见它——随后针对同一个钉住的版本被结案:`BaseSubscription`
71
+ 里十二处 `emit('error')` 除一处外全部位于 `_isSubscribing()`/`_isSubscribed()` 判断之后,而
72
+ `unsubscribe()` 之后还能回到那一处的三条路径,分别被触发时的状态复检、客户端只驱动 `Subscribing`
73
+ 的重连遍历,以及 `_clearSubscribedState()` 取消的 refresh 定时器封死。枚举写在注释里,也因此没有添加
74
+ 任何防御式的重新挂载。
19
75
  - `pnpm bench:compare --fail-above-pct 50` 补上了它的中位数基线无法提供的那条判据:新值还必须超过自身基线窗口
20
76
  中的最大样本,因为在中双峰指标上,中位数只有在两档保持混合时才稳定。被这条判据放过的指标会被显式打印出来而不是
21
77
  静默通过,其代价也写在旁边——停留在已观测区间内的持续性劣化从此对门禁不可见。两条判据都由变异测试归属:删掉新增
@@ -93,7 +93,7 @@ transport 选择后端(SharedWorker / Dedicated Worker / 本地),向它发
93
93
 
94
94
  - **后端选举**:复用 `worker-mode.ts` 的 `selectWorkerBackend`,使你的后端与
95
95
  SDK 其余部分降级行为一致。
96
- - **generation 守卫**:创建后端或停止 transport 时递增单调计数器。参考实现里只有异步凭证桥会比较它,因为 provider 可能在它所应答的 Worker 已经消失之后才 settle。Worker 的 error 处理并不读这个计数器——`stop()` 与 `onWorkerFailed()` 在 generation 前移之前就移除了这些监听器,被取代的后端根本到不了这个对象。只有当你的后端会让某个监听器跨过后端替换继续存活时,才需要自己加这层检查。
96
+ - **generation 守卫**:创建 *Worker* 后端或停止 transport 时递增单调计数器——参考实现里进程内的 local 后端安装时并不递增,这正是它的 `isCurrentBackend()` 还要同时比较 Worker/port/`localSession` 身份的原因。参考实现中只有异步凭证桥会比较它,因为 provider 可能在它所应答的 Worker 已经消失之后才 settle。Worker 的 error 处理并不读这个计数器——`stop()` 是先前移 generation、再移除这些监听器,而 `onWorkerFailed()` 根本不碰这个计数器,所以被取代的后端到不了这个对象。只有当你的后端会让某个监听器跨过后端替换继续存活时,才需要自己加这层检查。
97
97
  - **SharedWorker 心跳**:若用 SharedWorker,定期发 PING,让 `PortReaper` 能
98
98
  回收死 tab 的 session,并处理回收器在关闭端口前发出的 `SESSION_REAPED`
99
99
  消息。对被饿死但仍存活的 tab 来说,那是唯一能收到的信号(`MessagePort` 没有
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cross-tab-worker-databus",
3
- "version": "0.21.13",
3
+ "version": "0.21.15",
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",