cross-tab-worker-databus 0.20.89 → 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 +10 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-77BQELW4.js → chunk-L24ETFVK.js} +2 -2
- package/dist/cjs/centrifuge.cjs +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/docs/api.md +1 -1
- package/docs/architecture.md +5 -3
- package/docs/benchmarks.md +8 -8
- package/docs/capabilities.md +1 -1
- package/docs/release-checklist.md +2 -2
- package/docs/roadmap.md +10 -2
- package/docs/zh/api.md +2 -0
- package/docs/zh/architecture.md +5 -3
- package/docs/zh/benchmarks.md +8 -8
- package/docs/zh/capabilities.md +1 -1
- package/docs/zh/release-checklist.md +2 -2
- package/docs/zh/roadmap.md +10 -2
- package/package.json +1 -1
- /package/dist/{chunk-77BQELW4.js.map → chunk-L24ETFVK.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.20.90] - 2026-09-16
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- The `Release` workflow's blocking published-consumer gate no longer fails on ordinary npm CDN propagation lag. The 0.20.89 tag run published successfully and then still failed the gate because `npm pack cross-tab-worker-databus@0.20.89` kept returning `ETARGET` for the whole 24 x 5 s (2 min) budget; the budget is now 48 x 7.5 s (6 min). A genuinely missing or unimportable published package still exhausts the budget and fails the gate.
|
|
7
|
+
- The coverage gate no longer false-fails when V8 instrumentation pushes the 1,500-seed lifecycle fuzzer past Vitest's 5-second default. The fuzzer keeps every seed and now carries an explicit 30-second test budget.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- Delivery semantics are now documented consistently: one accepted transport publication is fanned out once and dispatches at most once per matching local handler, but transport/server redelivery or loss and the bounded per-bus opt-in `messageId` dedup window still mean the SDK does not provide end-to-end at-least-once or exactly-once delivery. The architecture docs no longer claim exactly-once dispatch per subscriber, and the Chinese API reference now includes the deduplication boundary already documented in English.
|
|
11
|
+
- The browser benchmark trend docs were refreshed through the latest two-run release gate (23 archived reports). The final comparison found no metric regression above the 50% ceiling; full round-trip publish latency improved in both worker modes.
|
|
12
|
+
|
|
3
13
|
## [0.20.89] - 2026-09-16
|
|
4
14
|
|
|
5
15
|
### Fixed
|
package/dist/centrifuge.js
CHANGED
|
@@ -2210,7 +2210,7 @@ var DedupManager = class {
|
|
|
2210
2210
|
};
|
|
2211
2211
|
|
|
2212
2212
|
// src/core/version.ts
|
|
2213
|
-
var SDK_VERSION = true ? "0.20.
|
|
2213
|
+
var SDK_VERSION = true ? "0.20.90" : "";
|
|
2214
2214
|
|
|
2215
2215
|
// src/core/data-bus.ts
|
|
2216
2216
|
var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
|
|
@@ -3342,4 +3342,4 @@ export {
|
|
|
3342
3342
|
parseDataBusPublication,
|
|
3343
3343
|
selectWorkerBackend
|
|
3344
3344
|
};
|
|
3345
|
-
//# sourceMappingURL=chunk-
|
|
3345
|
+
//# sourceMappingURL=chunk-L24ETFVK.js.map
|
package/dist/cjs/centrifuge.cjs
CHANGED
package/dist/cjs/index.cjs
CHANGED
package/dist/index.js
CHANGED
package/docs/api.md
CHANGED
|
@@ -131,7 +131,7 @@ Calling `publish()` while `stop()` is still settling reports through `onError` a
|
|
|
131
131
|
|
|
132
132
|
A publication issued after a runtime transport `error` is held behind the recovery gate and sent once the transport is ready again, so it is never written to the connection that just failed. If the recovery budget is exhausted or the wait is superseded by `stop()` / page hide, the publication is dropped rather than deferred indefinitely (page suspension keeps its documented drop-without-defer semantics). A clean `disconnected` status does not schedule a background DataBus reopen, but it no longer swallows later operations either: a `subscribe()` / `publish()` issued after the close demands one on-demand reopen, is held until the replacement connects, and then flushes. Call `start()` (or send an operation) to reopen explicitly.
|
|
133
133
|
|
|
134
|
-
Incoming messages may include a caller/server supplied `messageId`. Enable bounded duplicate suppression with `dedup: { maxEntries, ttlMs }`; repeated IDs within the window are ignored. This is disabled by default and does not provide an exactly-once server guarantee. Tests and hosts with a custom time source may provide `dedup.now`. A full `stop()` clears the remembered ID window; a later `start()` begins a fresh dedup session.
|
|
134
|
+
Incoming messages may include a caller/server supplied `messageId`. Enable bounded duplicate suppression with `dedup: { maxEntries, ttlMs }`; repeated IDs within the bounded per-bus window are ignored. This is disabled by default and is best-effort: it does not provide an end-to-end at-least-once or exactly-once server guarantee. One accepted transport publication is fanned out once and dispatches at most once per matching local handler, while the transport/server may still redeliver or lose publications and a disconnected or suspended tab can miss the cross-tab event. Tests and hosts with a custom time source may provide `dedup.now`. A full `stop()` clears the remembered ID window; a later `start()` begins a fresh dedup session.
|
|
135
135
|
|
|
136
136
|
When supplied, `options.messageId` and `options.timestamp` are propagated through cross-tab routing, Worker boundaries, and supported transports. The server must echo or otherwise preserve them for inbound deduplication and replay retention.
|
|
137
137
|
|
package/docs/architecture.md
CHANGED
|
@@ -483,7 +483,7 @@ The SDK intentionally does not host a real-time transport in a Service Worker. S
|
|
|
483
483
|
|
|
484
484
|
### Dispatch flow: three gates
|
|
485
485
|
|
|
486
|
-
|
|
486
|
+
After the optional `messageId` deduplication gate, every accepted publication from the transport goes through three checks before reaching the application handler:
|
|
487
487
|
|
|
488
488
|
1. **`isAssigned(topic)`** — called on the owning Worker when a transport message arrives (`handleTransportMessage`). If the topic is no longer assigned to this worker (e.g. a stale message from a previous ownership window), the message is dropped immediately. This is the outer gate: it prevents a non-owner from broadcasting.
|
|
489
489
|
|
|
@@ -491,11 +491,13 @@ Every publication from the transport goes through three checks before reaching t
|
|
|
491
491
|
|
|
492
492
|
3. **`hasLocalSubscriber(topic)`** — called on each tab receiving the `EVENT`. Only tabs that have a local subscriber record for this topic invoke the registered handler. Tabs without a local subscription drop the message silently.
|
|
493
493
|
|
|
494
|
-
These three checks
|
|
494
|
+
These three checks provide **at-most-once fan-out per accepted transport publication**:
|
|
495
495
|
- The outer gate (`isAssigned`) prevents duplicate broadcasts from a stale owner.
|
|
496
496
|
- The inner gate (`hasLocalSubscriber`) prevents a tab from dispatching a topic it never subscribed to.
|
|
497
497
|
- BroadcastChannel never echoes to its sender, so the owner does not receive its own `EVENT` — its local dispatch is the only local delivery.
|
|
498
498
|
|
|
499
|
+
This is a local fan-out guarantee, not an end-to-end delivery guarantee. A transport or server can redeliver a publication, a disconnected or suspended tab can miss the `EVENT`, and BroadcastChannel fan-out has no application-level acknowledgment. Opt-in `dedup` can suppress repeated `messageId` values within its bounded per-bus window, but it is best-effort, per-instance, and reset by `stop()`. The SDK therefore does not provide end-to-end at-least-once or exactly-once delivery; applications that cannot tolerate duplicates or gaps must use idempotent handlers and the transport/server guarantees appropriate for their workload.
|
|
500
|
+
|
|
499
501
|
```text
|
|
500
502
|
Transport message → isAssigned(topic)? → Yes → broadcastEvent(EVENT)
|
|
501
503
|
↓
|
|
@@ -529,7 +531,7 @@ When an abnormal exit cannot execute the `pagehide` cleanup, other Runtimes scan
|
|
|
529
531
|
|
|
530
532
|
If a Tab still subscribed to a Topic finds that the owner Worker has departed or expired, it selects a new owner and increments the route `generation`. Normal `pagehide` handoff is strict: the new route records `handoffFromWorkerId`, the old owner unsubscribes from transport first, then sends `ROUTE_RELEASED(generation)`, and only the matching new owner ACK handler sends `SUBSCRIBE`. If the old Worker has already disappeared, the new owner takes over immediately. A refreshed Tab that rejoins afterward records itself as a subscriber and reuses the replacement owner instead of taking the route back.
|
|
531
533
|
|
|
532
|
-
This process prevents overlap during graceful owner handoff while retaining availability during failure recovery; it does not guarantee exactly-once delivery.
|
|
534
|
+
This process prevents overlap during graceful owner handoff while retaining availability during failure recovery; it does not turn the local fan-out guarantee into end-to-end at-least-once or exactly-once delivery.
|
|
533
535
|
|
|
534
536
|
## Stability Invariants
|
|
535
537
|
|
package/docs/benchmarks.md
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
# Browser Benchmark Trend
|
|
4
4
|
|
|
5
|
-
> Data through 2026-09-
|
|
5
|
+
> Data through 2026-09-15, from the 23 archived `bench-results/browser-*.json` reports (run `pnpm bench:browser` to add one; regenerate this doc with `node scripts/bench-trend.mjs`).
|
|
6
6
|
|
|
7
7
|
The comparison baseline for release gating is `pnpm bench:compare --fail-above-pct 50` between the two most recent reports (50% ceiling absorbs shared-runner noise). This doc records the long-run picture: values are per-metric latencies where lower is better, and the all-time best marks the healthiest observed run on this machine.
|
|
8
8
|
|
|
9
9
|
<!-- BENCH-TREND:BEGIN (machine-generated table) -->
|
|
10
10
|
| Metric | Previous (ms) | Latest (ms) | Δ | All-time best (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
|
Notes:
|
package/docs/capabilities.md
CHANGED
|
@@ -28,7 +28,7 @@ Status Legend: `✅ Implemented` means the current version has code and test cov
|
|
|
28
28
|
| Performance | Batched writes of coordination metadata with backoff retry | ✅ Implemented | Heartbeat, route, and subscriber writes are merged and flushed in a microtask; failures use exponential backoff; `pagehide` / `stop()` flush synchronously |
|
|
29
29
|
| Performance | Optional ArrayBuffer Transferable transport | ✅ Implemented | With `transferable: true`, binary publish / receive bypasses structured clone copying; the object message API is unchanged |
|
|
30
30
|
| Performance | Optional `DataBusTransport.publishBatch` one-frame burst publishing | ✅ Implemented | The bundled WebSocket transport sends multi-item batches in one wire frame (`publishBatch` op, demo server supported); transports without batch support fall back to per-item `publish` |
|
|
31
|
-
| Message Semantics | exactly-once delivery | Not Implemented |
|
|
31
|
+
| Message Semantics | End-to-end at-least-once or exactly-once delivery | Not Implemented | The bus fans out one accepted transport publication once and dispatches at most once per matching local handler. Transport/server redelivery or loss, cross-tab event loss during disconnect/suspend, and bounded opt-in `messageId` dedup still prevent an end-to-end at-least-once or exactly-once guarantee. |
|
|
32
32
|
| Message Semantics | Pluggable publication deduplication | ✅ Implemented | Opt-in bounded inbound suppression by `DataBusMessage.messageId`; default is disabled and transport/server IDs remain caller-controlled |
|
|
33
33
|
| Authentication | Async credential refresh bridge inside the Worker | ✅ Implemented | Opt-in `credentialProvider` ({ getToken, getChannelToken }) on `createCentrifugeDataBus`. Worker config stays structured-cloneable; the Worker asks the main thread for each fresh token over a TOKEN_REQUEST / TOKEN_RESPONSE exchange, and the provider supplies it from application context |
|
|
34
34
|
| Load Policy | Adaptive weighting by message rate, byte count, or scheduling lag | ✅ Implemented | Opt-in `loadWeighting` ({ messageRateWeight, byteRateWeight, scheduleLagWeight }): workers sample their own fan-out traffic and heartbeat scheduling overrun per window and publish it with the worker record; new-route owner selection adds the normalized per-second rates and lag ratio to the topic count. Default (unset) keeps pure topic-count routing; existing routes stay sticky. |
|
|
@@ -30,7 +30,7 @@ The repository runs CodeQL (`javascript-typescript`; on push, on pull request, a
|
|
|
30
30
|
|
|
31
31
|
## Tagged-release workflow
|
|
32
32
|
|
|
33
|
-
Pushing a version tag triggers the `Release` GitHub Action: it runs `pnpm check` and `pnpm lint` (a tag can point at a commit that never passed CI's lint step), runs `verify:compat` and `verify:pack`, opens the GitHub release from the `CHANGELOG` section, publishes to npm when the `NPM_TOKEN` secret is set, and then runs the **blocking** published-consumer verification with the same budget as a manual run (`PUBLISHED_VERIFY_ATTEMPTS=
|
|
33
|
+
Pushing a version tag triggers the `Release` GitHub Action: it runs `pnpm check` and `pnpm lint` (a tag can point at a commit that never passed CI's lint step), runs `verify:compat` and `verify:pack`, opens the GitHub release from the `CHANGELOG` section, publishes to npm when the `NPM_TOKEN` secret is set, and then runs the **blocking** published-consumer verification with the same budget as a manual run (`PUBLISHED_VERIFY_ATTEMPTS=48`, `PUBLISHED_VERIFY_DELAY_MS=7500`, a 6-minute ceiling). A release whose published package cannot be imported by a clean consumer fails the workflow — treat every `verify:published` failure as a failed release and republish the tag after fixing it. When no token is configured the publish step is skipped, but verification still passes against whatever version is already on npm (e.g. one published manually).
|
|
34
34
|
|
|
35
35
|
## Publishing
|
|
36
36
|
|
|
@@ -42,4 +42,4 @@ For a manual release (no `NPM_TOKEN` in the workflow), run `npm publish --access
|
|
|
42
42
|
2. Install the published tarball or version in a clean consumer and import the root plus every public subpath.
|
|
43
43
|
3. Record the result in the release notes. Do not advance to `1.0.0` until the public API and protocol deprecation policy are explicitly frozen.
|
|
44
44
|
|
|
45
|
-
The tagged-release workflow already ran the consumer verification above. Run `PUBLISHED_VERSION=<version> pnpm verify:published` by hand only when you need an offline repeat.
|
|
45
|
+
The tagged-release workflow already ran the consumer verification above. Run `PUBLISHED_VERSION=<version> pnpm verify:published` by hand only when you need an offline repeat. The workflow's 6-minute ceiling absorbs normal npm CDN propagation lag (the 0.20.89 tag run exhausted the older 2-minute budget after a successful publish); raise `PUBLISHED_VERIFY_ATTEMPTS` / `PUBLISHED_VERIFY_DELAY_MS` further only for unusually slow mirrors.
|
package/docs/roadmap.md
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
0.20.
|
|
3
|
+
0.20.90 is released. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
|
|
4
|
+
|
|
5
|
+
## 0.20.90 delivered scope
|
|
6
|
+
|
|
7
|
+
The line hardens the release pipeline uncovered by the 0.20.89 tag run and aligns the documented delivery semantics with the implementation.
|
|
8
|
+
|
|
9
|
+
- Release-gate propagation budget: the blocking published-consumer verification now waits 48 x 7.5 s (6 min) instead of 24 x 5 s (2 min). The 0.20.89 tag run published successfully and then still failed the gate because `npm pack` returned `ETARGET` for the whole old budget; a genuinely missing package still exhausts the budget.
|
|
10
|
+
- Delivery semantics: the API, architecture, and capabilities docs now consistently state the bounded local fan-out guarantee and the lack of end-to-end at-least-once or exactly-once delivery; the Chinese API reference documents the opt-in `messageId` deduplication window.
|
|
11
|
+
- Release performance evidence: the benchmark trend docs were refreshed from 23 archived reports; the latest two-run comparison stayed inside the 50% ceiling, with per-message publish latency improving in both worker modes.
|
|
4
12
|
|
|
5
13
|
## 0.20.89 delivered scope
|
|
6
14
|
|
|
@@ -506,7 +514,7 @@ The line continues the async-callback isolation audit: every fix below binds a c
|
|
|
506
514
|
## 0.13.0 candidates
|
|
507
515
|
|
|
508
516
|
1. Freeze the public export surface and transport-neutral publication envelope.
|
|
509
|
-
2. Document at-least-once delivery and deduplication guarantees precisely.
|
|
517
|
+
2. ~~Document at-least-once delivery and deduplication guarantees precisely.~~ Delivered: architecture/API/capability docs now distinguish at-most-once local fan-out per accepted transport publication from end-to-end delivery, document transport/server loss and redelivery, and describe bounded opt-in `messageId` dedup without claiming at-least-once or exactly-once.
|
|
510
518
|
3. Add long-running browser soak coverage for replay retention, reconnect, BFCache, and owner migration.
|
|
511
519
|
4. Publish a migration guide and deprecation policy for any pre-1.0 protocol aliases.
|
|
512
520
|
|
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` 暴露相同的可选元数据。
|
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,7 +501,7 @@ 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
|
|
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 |
|
|
@@ -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,14 @@
|
|
|
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 模式的单消息发布延迟均有改善。
|
|
4
12
|
|
|
5
13
|
## 0.20.89 已完成范围
|
|
6
14
|
|
|
@@ -506,7 +514,7 @@
|
|
|
506
514
|
## 0.13.0 候选
|
|
507
515
|
|
|
508
516
|
1. 冻结公共导出面与 transport 无关的 publication 信封。
|
|
509
|
-
2.
|
|
517
|
+
2. ~~精确记录 at-least-once 投递与去重保证。~~ 已交付:architecture/API/capability 文档现在明确区分「每条已接受 transport publication 的本地至多一次扇出」与端到端投递,记录 transport/服务端的丢失与重复投递,并说明有界、可选 `messageId` 去重的边界,不再宣称 at-least-once 或 exactly-once。
|
|
510
518
|
3. 增加长时浏览器浸泡覆盖:replay 留存、重连、BFCache 与 owner 迁移。
|
|
511
519
|
4. 为 1.0 前的协议别名发布迁移指南与弃用策略。
|
|
512
520
|
|
package/package.json
CHANGED
|
File without changes
|