cross-tab-worker-databus 0.21.10 → 0.21.12
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 +24 -0
- package/README.md +2 -2
- package/README.zh.md +3 -1
- package/dist/centrifuge.js +1 -1
- package/dist/centrifuge.shared.worker.js +14 -5
- package/dist/centrifuge.shared.worker.js.map +2 -2
- package/dist/{chunk-L5P66THO.js → chunk-TSTV3SDC.js} +2 -2
- package/dist/chunk-TSTV3SDC.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +1 -1
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/workers/port-reaper.d.ts +14 -5
- package/dist/workers/port-reaper.d.ts.map +1 -1
- package/docs/api.md +1 -1
- package/docs/architecture.md +28 -8
- package/docs/capabilities.md +5 -4
- package/docs/configuration.md +2 -2
- package/docs/getting-started.md +4 -3
- package/docs/release-checklist.md +4 -4
- package/docs/roadmap.md +59 -1
- package/docs/transports.md +30 -8
- package/docs/zh/api.md +1 -1
- package/docs/zh/architecture.md +13 -8
- package/docs/zh/capabilities.md +4 -3
- package/docs/zh/configuration.md +2 -2
- package/docs/zh/getting-started.md +4 -3
- package/docs/zh/release-checklist.md +4 -4
- package/docs/zh/roadmap.md +43 -1
- package/docs/zh/transports.md +16 -6
- package/package.json +1 -1
- package/dist/chunk-L5P66THO.js.map +0 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../../src/core/cluster.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"cluster.d.ts","sourceRoot":"","sources":["../../src/core/cluster.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -39,9 +39,13 @@ export declare class PortReaper {
|
|
|
39
39
|
* setTimeout() overrides it. register() always calls schedule() so the
|
|
40
40
|
* reaper interval starts as soon as the first port connects. */
|
|
41
41
|
register(port: MessagePort, target: ReapTarget): void;
|
|
42
|
-
/** Record activity on a port (any incoming message).
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
/** Record activity on a port (any incoming message). The early return is what
|
|
43
|
+
* keeps a late PING from a removed port out of `lastSeenAt`: `reap()` iterates
|
|
44
|
+
* `targets`, so a row here would be unreachable rather than dangerous — measured,
|
|
45
|
+
* deleting the guard passes every test in the suite, because the only difference is
|
|
46
|
+
* one stale map entry per dead port. It stays as the cheap invariant that the three
|
|
47
|
+
* maps hold exactly the tracked ports, which is what `reap()`'s `??` fallbacks and
|
|
48
|
+
* this file's tracking contract both lean on. */
|
|
45
49
|
touch(port: MessagePort): void;
|
|
46
50
|
/** Override a port's session timeout from its INIT heartbeat config.
|
|
47
51
|
* `Infinity` is the documented way to disable the PING heartbeat, and it means
|
|
@@ -71,8 +75,13 @@ export declare class PortReaper {
|
|
|
71
75
|
* be unit-tested in isolation from the timer plumbing. */
|
|
72
76
|
private computeMinHeartbeat;
|
|
73
77
|
/** Close sessions whose port has been silent longer than its timeout.
|
|
74
|
-
* Iterates a snapshot
|
|
75
|
-
*
|
|
78
|
+
* Iterates a snapshot. That is defensive rather than load-bearing: `Map`
|
|
79
|
+
* iteration in JS is deletion-safe, so removing the current entry cannot skip the
|
|
80
|
+
* next one, and the only case a snapshot changes is a re-entrant `register()` from
|
|
81
|
+
* inside a target's own teardown — which a fresh port would survive anyway, because
|
|
82
|
+
* `register()` stamps `lastSeenAt` with the current time and a zero-age port is
|
|
83
|
+
* never past its timeout. Measured: iterating the live map instead passes every
|
|
84
|
+
* test, so do not read this line as pinned. */
|
|
76
85
|
private reap;
|
|
77
86
|
}
|
|
78
87
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"port-reaper.d.ts","sourceRoot":"","sources":["../../src/workers/port-reaper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH;;+DAE+D;AAC/D,MAAM,WAAW,UAAU;IACzB,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;CACd;AAED,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;AAC7D,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAE3C,qBAAa,UAAU;IAQnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkC;IACnE,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAiC;gBAGhC,GAAG,GAAE,MAAM,MAAiB,EAC7C,QAAQ,GAAE,QAA2E,EACrF,UAAU,GAAE,UAA4C;IAM1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IAExC;;;oEAGgE;IAChE,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAOrD
|
|
1
|
+
{"version":3,"file":"port-reaper.d.ts","sourceRoot":"","sources":["../../src/workers/port-reaper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAQH;;+DAE+D;AAC/D,MAAM,WAAW,UAAU;IACzB,MAAM,IAAI,IAAI,CAAC;IACf,KAAK,IAAI,IAAI,CAAC;IACd,IAAI,IAAI,IAAI,CAAC;CACd;AAED,KAAK,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;AAC7D,KAAK,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAE3C,qBAAa,UAAU;IAQnB,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;IAC9D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAkC;IAC7D,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAkC;IACnE,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,UAAU,CAAiC;gBAGhC,GAAG,GAAE,MAAM,MAAiB,EAC7C,QAAQ,GAAE,QAA2E,EACrF,UAAU,GAAE,UAA4C;IAM1D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAW;IACpC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IAExC;;;oEAGgE;IAChE,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAOrD;;;;;;qDAMiD;IACjD,KAAK,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAK9B;;;;;;;;;gDAS4C;IAC5C,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,mBAAmB,EAAE,MAAM,GAAG,IAAI;IAWhE;;4EAEwE;IACxE,MAAM,CAAC,IAAI,EAAE,WAAW,GAAG,IAAI;IAQ/B;2EACuE;IACvE,OAAO,IAAI,IAAI;IAgBf;;;yEAGqE;IACrE,OAAO,CAAC,QAAQ;IAahB;;8DAE0D;IAC1D,OAAO,CAAC,mBAAmB;IAQ3B;;;;;;;mDAO+C;IAC/C,OAAO,CAAC,IAAI;CAoDb"}
|
package/docs/api.md
CHANGED
|
@@ -376,7 +376,7 @@ createStorageEventChannel(options: {
|
|
|
376
376
|
}): ClusterChannel | null
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
Creates a `ClusterChannel` backed by localStorage `storage` events — the coordination fallback for environments without BroadcastChannel. Returns `null` when storage or a storage-event source is unavailable. Delivery semantics mirror BroadcastChannel (no echo to the sender, JSON-serializable messages, closed channels refuse further posts); a monotonic sequence
|
|
379
|
+
Creates a `ClusterChannel` backed by localStorage `storage` events — the coordination fallback for environments without BroadcastChannel. Returns `null` when storage or a storage-event source is unavailable. Delivery semantics mirror BroadcastChannel (no echo to the sender, JSON-serializable messages, closed channels refuse further posts); the payload envelope carries a per-channel sender nonce **and** a monotonic sequence, which is what keeps consecutive identical messages deliverable — including two tabs whose first frame would otherwise store the same `seq=1` value and be silently suppressed. Opt in via `createBrowserEnvironment({ channelFallback: 'storage-event' })`; see the security note in [configuration.md](./configuration.md#coordination-channel-fallback-broadcastchannel-unavailable).
|
|
380
380
|
|
|
381
381
|
## WebSocket Transport Backend
|
|
382
382
|
|
package/docs/architecture.md
CHANGED
|
@@ -85,7 +85,7 @@ Terms are explained in plain language; the code and the rest of this document us
|
|
|
85
85
|
| Term | Short name in code | Plain-language meaning |
|
|
86
86
|
|---|---|---|
|
|
87
87
|
| **Topic** | `topic` | A named channel (e.g. `price.feed`) that applications subscribe to or publish on. |
|
|
88
|
-
| **Topic key** | `topicKey` | An opaque 128-bit hash of the Topic name. The Topic name
|
|
88
|
+
| **Topic key** | `topicKey` | An opaque 128-bit hash of the Topic name. The Topic name never appears in the worker, route or subscriber records; the two opt-in exceptions that do write plaintext are `replay.persistence` (IndexedDB) and `channelFallback: 'storage-event'`. |
|
|
89
89
|
| **Tab** | `tabId` | One browser page instance. `tabId` survives refresh so a tab keeps its identity across the page lifecycle. |
|
|
90
90
|
| **Worker** | `workerId` | One runtime instance inside a Tab. Each Worker publishes its own heartbeat and can own Topics. A Tab can briefly run two Workers during a restart/handoff. |
|
|
91
91
|
| **Topic owner** | — | The Worker responsible for the real transport subscription of a Topic. "Owner" is a hat a Worker wears, not a permanent role: it receives the Topic's publications from the server and fans them out to other Tabs. |
|
|
@@ -117,9 +117,13 @@ Unlike the old single-JSON route table, subscribers use per-Tab independent keys
|
|
|
117
117
|
|
|
118
118
|
```ts
|
|
119
119
|
interface WorkerRecord {
|
|
120
|
+
/** Cluster protocol version advertised by this worker; absent from a legacy peer. */
|
|
121
|
+
protocolVersion?: number;
|
|
120
122
|
workerId: string;
|
|
121
123
|
tabId: string;
|
|
122
124
|
load: number;
|
|
125
|
+
/** Rolling traffic sample, present only when `loadWeighting` is enabled. */
|
|
126
|
+
throughput?: WorkerThroughputSample;
|
|
123
127
|
role: 'active' | 'standby';
|
|
124
128
|
status: 'connecting' | 'connected' | 'disconnected' | 'error';
|
|
125
129
|
visibilityState: 'visible' | 'hidden';
|
|
@@ -146,7 +150,7 @@ interface WorkerRoute {
|
|
|
146
150
|
|
|
147
151
|
`generation` increments on every re-assignment and must match across the handoff handshake; `handoffFromWorkerId` records the previous owner during a graceful handoff. The interface above matches the current protocol — see [Failover](#failover) for how these two fields drive takeover.
|
|
148
152
|
|
|
149
|
-
Routes do not store the original topic string or payload. When the actual owner receives `CONTROL/SUBSCRIBE`, the
|
|
153
|
+
Routes do not store the original topic string or payload. When the actual owner receives `CONTROL/SUBSCRIBE`, the topic plaintext reached it only through the coordination channel (in-memory on BroadcastChannel; part of the stored frame under `channelFallback: 'storage-event'`). The receiver drops that control frame when the durable route names a *different* worker, so a delayed frame from an earlier assignment round cannot make a non-owner subscribe, and a route awaiting `ROUTE_RELEASED` can be confirmed only by the matching handoff ACK. A topic whose route cannot be read is accepted rather than dropped — `confirmRoute` writes nothing without a route to stamp, so the frame mints no durable ownership, and the next reconcile withdraws the assignment; the tolerance exists so coordination still works when storage is absent or the record has expired. `confirmedAt` is stamped as the frame is accepted, **before** the control action is handed to the transport, so it records "this Worker took the assignment", not "the server subscription is live" — which is exactly why an unconfirmed route is re-sent: it is how "a route without a real subscription" is recovered from BroadcastChannel message loss.
|
|
150
154
|
|
|
151
155
|
### Receiver-side frame validation
|
|
152
156
|
|
|
@@ -166,7 +170,10 @@ The second row is what makes ownership authorization meaningful. `topicKey` is a
|
|
|
166
170
|
substitution, not a variant — and because ownership is authorized by the route stored under `topicKey`
|
|
167
171
|
while the transport subscription is named by `topic`, an unchecked mismatch lets a frame borrow one
|
|
168
172
|
channel's authorization to name another. The durable route record stores only `topicKey`, never the
|
|
169
|
-
plaintext, so the key → name mapping exists only in memory and cannot be injected through
|
|
173
|
+
plaintext, so the key → name mapping exists only in memory and cannot be injected through the
|
|
174
|
+
coordination records. The storage-event fallback channel is the one path where a frame — and with it
|
|
175
|
+
a key/plaintext pair — does arrive through localStorage; the pairing rule is what makes a frame
|
|
176
|
+
written by any other tenant of that storage just as invalid as one posted into the channel.
|
|
170
177
|
|
|
171
178
|
The third row exists because the batch path iterates its payload: a value with a `length` but no
|
|
172
179
|
iterator threw out of the message listener, and an iterable non-array (a string) walked the batch as
|
|
@@ -195,6 +202,7 @@ These identifiers represent different layers:
|
|
|
195
202
|
| `tabId` | Stable identity of a browser Tab | Identifies which Tab subscribes to a `topicKey` | Yes, in subscriber keys |
|
|
196
203
|
| `workerId` | Identity of the current Runtime/Worker instance | Identifies the Worker that owns the transport subscription | Yes, in worker/route records |
|
|
197
204
|
| `BroadcastChannel` | Same-origin, in-memory real-time channel | Carries control actions, publication events, and reconciliation signals | No |
|
|
205
|
+
| storage-event channel | The same channel interface over localStorage `storage` events, used only when `channelFallback: 'storage-event'` is set and BroadcastChannel is unavailable | Carries exactly the frames above | **Yes** — each frame is written whole under `cross-tab-worker-databus:channel:*`, so plaintext topics and `PUBLISH` payloads persist until the channel closes (indefinitely if the tab dies first) |
|
|
198
206
|
|
|
199
207
|
```text
|
|
200
208
|
topic
|
|
@@ -211,7 +219,7 @@ BroadcastChannel CONTROL
|
|
|
211
219
|
|
|
212
220
|
### In-memory topic key cache (`knownTopics`)
|
|
213
221
|
|
|
214
|
-
Each Runtime maintains a `Map<topicKey, topic>` called `knownTopics` that serves as the reverse-lookup cache from opaque key to plaintext topic. It is populated by `rememberTopic()`, which is called on every `subscribe`, `publish`, `unsubscribe`, and inbound `CONTROL` message.
|
|
222
|
+
Each Runtime maintains a `Map<topicKey, topic>` called `knownTopics` that serves as the reverse-lookup cache from opaque key to plaintext topic. It is populated by `rememberTopic()`, which is called on every `subscribe`, `publish`, and `unsubscribe`, and on every inbound `CONTROL` message that survives the `topicKey` pairing check — a frame dropped for disagreeing about that pair is dropped before its plaintext is ever read, so invalid pairs cannot grow this map. Legitimate calls still can, through any API a same-origin script may reach on the page's own bus, which is what the cap below bounds.
|
|
215
223
|
|
|
216
224
|
The cache exists for two reasons:
|
|
217
225
|
|
|
@@ -302,7 +310,10 @@ This decentralized structure is a trade-off for concurrency correctness, not for
|
|
|
302
310
|
| Single large JSON for Worker/route/subscriber | High | Only whole read/write | Multiple Tabs doing read-modify-write concurrently can easily overwrite each other, losing subscribers |
|
|
303
311
|
| Independent key per entity | Low | Can clean up per Worker, per route, per Topic+Tab precisely | More keys, requires TTL-based garbage collection |
|
|
304
312
|
|
|
305
|
-
|
|
313
|
+
By default the SDK does not rely on `storage` events to drive coordination; control notifications use
|
|
314
|
+
BroadcastChannel. That is a default, not a guarantee: `channelFallback: 'storage-event'` selects a
|
|
315
|
+
channel implemented entirely on `storage` events, which is the one configuration where coordination
|
|
316
|
+
notifications do arrive that way. Although Worker heartbeats update their own independent key, this does not trigger repeated business callbacks or message dispatch within the SDK. The core benefit of separate keys is that different Tabs write different records, avoiding overwrite contention on a shared large object.
|
|
306
317
|
|
|
307
318
|
Under normal conditions, the number of keys is approximately: `Number of Workers + Number of Topic routes + Number of Topic/Tab subscription relationships`. The Runtime cleans up timed-out Workers, orphaned subscribers without active Tabs, and orphaned routes that have exceeded the Worker TTL and no longer have subscribers. Other naming structures left over from older versions do not belong to the current SDK protocol and do not participate in current route resolution.
|
|
308
319
|
|
|
@@ -348,7 +359,11 @@ The two `topicKey → topic` maps (`assignedTopics`, `knownTopics`) deliberately
|
|
|
348
359
|
|
|
349
360
|
## BroadcastChannel Protocol
|
|
350
361
|
|
|
351
|
-
All real-time coordination flows through one
|
|
362
|
+
All real-time coordination flows through one channel per cluster, whose name is derived from
|
|
363
|
+
`clusterKey`. BroadcastChannel — the implementation unless `channelFallback` is set — keeps its
|
|
364
|
+
messages in memory only; they never touch localStorage. The storage-event fallback is the exception
|
|
365
|
+
by construction: it *is* localStorage, so every frame on it persists as long as the key does. Neither
|
|
366
|
+
implementation passes through the transport server. Four message types are exchanged:
|
|
352
367
|
|
|
353
368
|
| Type | Direction | Purpose |
|
|
354
369
|
|---|---|---|
|
|
@@ -513,7 +528,12 @@ sequenceDiagram
|
|
|
513
528
|
|
|
514
529
|
BroadcastChannel does not echo to its sender, so the owner receives no `EVENT` back for its own broadcast — its local dispatch is the only local delivery.
|
|
515
530
|
|
|
516
|
-
Publications are not written to localStorage
|
|
531
|
+
Publications are not written to localStorage by the coordination layer or the transport; message data
|
|
532
|
+
and publication metadata exist in the BroadcastChannel in-memory event and in the transport, and batch
|
|
533
|
+
writes cover coordination metadata only. Two opt-ins do put payloads in browser storage, and each is
|
|
534
|
+
named where it is documented: the `channelFallback: 'storage-event'` channel writes every frame it
|
|
535
|
+
carries — including `PUBLISH` payloads — under its channel key, and `replay.persistence` stores the
|
|
536
|
+
publications themselves in IndexedDB, keyed by the plaintext topic.
|
|
517
537
|
|
|
518
538
|
### Service Worker boundary
|
|
519
539
|
|
|
@@ -588,7 +608,7 @@ These invariants are pinned by regression tests (see `tests/stability.test.ts` a
|
|
|
588
608
|
- **Last-subscriber release.** An owning Worker learns a topic is unserved from the last subscriber's `CONTROL/UNSUBSCRIBE`: it drops the assignment *and* dispatches the release to its transport. Consuming the message inside the handoff short-circuit alone would leave the server subscription — and its inbound fan-out — alive after every tab has left.
|
|
589
609
|
- **Assignment drift repair.** Every reconcile tick drops any topic this Worker holds in `assignedTopics` once the durable route no longer names it, releasing the transport subscription as it goes. Ownership thus follows the storage record rather than whichever message arrived last: a stale `CONTROL/SUBSCRIBE`, a lost route write, or a tab that claimed a route it does not own all converge back to exactly one owner within a heartbeat. `tests/coordination-invariants.test.ts` fuzzes three tabs through those corruptions and asserts the quiescent result (pinned by regression: emptying this sweep makes the fuzzed end state fail).
|
|
590
610
|
- **Stranded-handoff recovery.** If the previous owner is gone and its `ROUTE_RELEASED` never arrives (dropped channel message under load, or a crash between the route write and the ACK), the reconcile loop re-elects a live owner once the unconfirmed handoff has been stuck longer than a worker TTL (10 s default): the route is rewritten with a fresh generation and the handoff marker cleared, so the normal confirmation path completes (pinned by regression). The age gate matters — a fresh unconfirmed route may simply be waiting out its confirmation flush — and while the previous owner is still alive the new owner keeps waiting, so the strict handoff keeps its no-overlap guarantee.
|
|
591
|
-
- **Loss and recovery matrix.** Each coordination message has a bounded recovery path: a lost `CONTROL/SUBSCRIBE` is re-sent by the heartbeat reconcile for any route still lacking `confirmedAt`; a lost `REGISTRY` nudge costs at most one heartbeat interval (3 s default) because every tick reconciles anyway; a lost `ROUTE_RELEASED` is recovered by reconcile re-election once the previous owner is gone and the handoff has been stuck longer than a worker TTL (see the stranded-handoff invariant above, pinned by regression); publications dropped during a transport disconnect window are the one documented unrecoverable loss (transport contract). The storage-event fallback channel
|
|
611
|
+
- **Loss and recovery matrix.** Each coordination message has a bounded recovery path: a lost `CONTROL/SUBSCRIBE` is re-sent by the heartbeat reconcile for any route still lacking `confirmedAt`; a lost `REGISTRY` nudge costs at most one heartbeat interval (3 s default) because every tick reconciles anyway; a lost `ROUTE_RELEASED` is recovered by reconcile re-election once the previous owner is gone and the handoff has been stuck longer than a worker TTL (see the stranded-handoff invariant above, pinned by regression); publications dropped during a transport disconnect window are the one documented unrecoverable loss (transport contract). The storage-event fallback channel makes every write a distinct value — a per-channel sender nonce *and* a monotonic sequence in the envelope — because a browser suppresses the `storage` event when `setItem` leaves the stored value unchanged, and the nonce is what separates two tabs whose first identical frame would each be `seq=1`; a dropped dispatch still recovers through the same reconcile loop.
|
|
592
612
|
- **Recovery diagnostics.** `getHealthSummary()` derives a single readiness verdict from the lifecycle flags (`stopped` / `starting` / `healthy` / `recovering` / `suspended` / `degraded`); the unified `lastFailure` ledger and persistence counters reset on every explicit `start()`.
|
|
593
613
|
|
|
594
614
|
## Transport Reconnection
|
package/docs/capabilities.md
CHANGED
|
@@ -22,7 +22,7 @@ Status Legend: `✅ Implemented` means the current version has code and test cov
|
|
|
22
22
|
| Degradation | Runs locally when localStorage or BroadcastChannel is unavailable | ✅ Implemented | Preserves the current Tab's connection and subscription capabilities |
|
|
23
23
|
| Degradation | Opt-in localStorage storage-event coordination channel when BroadcastChannel is unavailable | ✅ Implemented | `createBrowserEnvironment({ channelFallback: 'storage-event' })`; coordination payloads (plaintext topic names) persist to localStorage — documented trade-off |
|
|
24
24
|
| Centrifuge | Built-in Dedicated / Shared Worker transport | ✅ Implemented | Supports subscribe, unsubscribe, publish, connection status, and error reporting; `auto` degrades from SharedWorker → Dedicated Worker → main-thread WebSocket |
|
|
25
|
-
| Security Boundary | localStorage uses opaque keys derived from connection and Topic; BroadcastChannel coordination messages carry plaintext topic names | ✅ Implemented |
|
|
25
|
+
| Security Boundary | localStorage uses opaque keys derived from connection and Topic; BroadcastChannel coordination messages carry plaintext topic names | ✅ Implemented | The coordination records themselves persist no URLs, credentials, raw Topic names, or publication payloads, and BroadcastChannel coordination messages are in-memory only (they carry plaintext topic names but are never written). Two opt-ins persist what the default path does not, and both are documented trade-offs: `replay.persistence` (IndexedDB) keys its object store by the plaintext topic and stores publication payloads in it, and `channelFallback: 'storage-event'` writes every coordination frame — plaintext Topic names, and payloads on `PUBLISH` frames — to localStorage under the channel key until that channel closes. |
|
|
26
26
|
| Diagnostics | Aggregates lifecycle events, throughput, delivery latency, dedup outcomes, recovery retries, route acknowledgments, and migrations | ✅ Implemented | Disabled by default; metrics are emitted every 5 seconds by default, with bounded reliability events for recovery and route coordination. On-demand `getMetrics()` / `getDiagnostics().metrics` expose the current window synchronously without a sink |
|
|
27
27
|
| Diagnostics | `getHealthSummary()` single-object readiness verdict and unified failure ledger | ✅ Implemented | `healthy`/`state` covers stopped, starting, recovering, BFCache-suspended, and recovery-exhausted degraded; `lastFailure` unifies transport/persistence/dispatch sources and resets on `start()` |
|
|
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 |
|
|
@@ -35,7 +35,8 @@ Status Legend: `✅ Implemented` means the current version has code and test cov
|
|
|
35
35
|
| Observability | Metrics/events for owner acknowledgments, migrations, and recovery attempts | ✅ Implemented | `DataBusReliabilityTraceEvent` reports bounded route ack/migration and transport recovery events; exact server-side ack remains transport-specific |
|
|
36
36
|
| Runtime Model | SharedWorker / Dedicated Worker transport | ✅ Implemented | `workerMode` supports `dedicated`, `shared`, and `auto`, defaulting to `dedicated` |
|
|
37
37
|
| Runtime Model | Service Worker transport | Not Implemented | Deliberately deferred; lifetime and long-lived connection constraints are documented in `docs/architecture.md` |
|
|
38
|
-
| Durable Messages |
|
|
38
|
+
| Durable Messages | Replaying published payloads to a tab that loads later | ✅ Implemented | Opt-in: `createIndexedDbReplayPersistence` stores the publications themselves — business payloads included, under the plaintext topic as the object store's key — and they are re-hydrated on the next load, so history survives a reload or a page close. This is inbound replay for subscribers, not a delivery guarantee |
|
|
39
|
+
| Durable Messages | Re-sending outbound publish commands after a page closes | Not Implemented | No publish command is persisted; a publication that was already dropped (a transport disconnect window, or the local-mode degradation) is the one documented unrecoverable loss |
|
|
39
40
|
|
|
40
41
|
## Verified Guarantees (pinned by the regression suite)
|
|
41
42
|
|
|
@@ -46,10 +47,10 @@ and regression-locked:
|
|
|
46
47
|
|
|
47
48
|
| Invariant | Area | Regressed guarantee |
|
|
48
49
|
|---|---|---|
|
|
49
|
-
| SUBSCRIBE route binding | coordination | An inbound `CONTROL/SUBSCRIBE` is
|
|
50
|
+
| SUBSCRIBE route binding | coordination | An inbound `CONTROL/SUBSCRIBE` is dropped when the durable route names a different worker, and a route awaiting `ROUTE_RELEASED` is confirmed only by the matching handoff ACK — so delayed frames from earlier assignment rounds cannot create lasting ownership, subscribe the transport, or confirm a pending handoff. A topic with no readable route is accepted rather than dropped, and that tolerance is bounded: `confirmRoute` writes nothing without a route to stamp, and the next reconcile takes the assignment back. It is what keeps coordination working when storage is unavailable or the record has expired |
|
|
50
51
|
| Handoff ACK validity | coordination | `ROUTE_RELEASED` is honored only when the route still points to the receiver, the release matches `handoffFromWorkerId`, and the ACK generation exactly matches the stored route — delayed ACKs from another a↔b ping-pong round are dropped |
|
|
51
52
|
| Replay persistence cleanup ordering | durability | Queued batch flushes are filtered against the winning cleanup (`unsubscribe`/`clearReplayTopic` drop the topic's pending entries, `clearReplayBefore` drops entries older than the cutoff, and `suspend()`/`stop()` discard the queued batch); cleared or stopped-session history is never re-appended by an in-flight flush |
|
|
52
|
-
| Storage write recovery | coordination | Coalesced writes retry with exponential backoff (50 ms → 1.6 s cap); a structurally failing key is dropped after 5 attempts (with `console.warn`)
|
|
53
|
+
| Storage write recovery | coordination | Coalesced writes retry with exponential backoff (50 ms → 1.6 s cap); a structurally failing key is dropped after 5 attempts (with `console.warn`); the pass stops at the first failure, so keys queued behind it wait for that key's retry (four backoff passes, 50+100+200+400 ms) and then go through the same pass that drops the poisoned key — delayed, never permanently blocked; backoff resets once the queue drains or `clear()` cancels the retries |
|
|
53
54
|
| Transport recovery budget | lifecycle | Auto-recovery is paced by a cooldown, bounded by `recovery.maxAttempts`, and reports `exhausted` when the budget is spent; a successful reopen resets attempt + exhausted, and an explicit `subscribe` on a down transport can still recover manually |
|
|
54
55
|
| Recovery waiter lifetime | lifecycle | A recovery operation parked behind the transport gate captures the recovery cancellation generation; `stop()` / page-hide invalidates it, so an immediate explicit `start()` cannot be followed by a stale duplicate operation after the replacement transport re-establishes subscriptions. By contrast, an explicit `start()` that supersedes the automatic recovery timer preserves the gate and its parked operations if the manual open also fails, allowing a later automatic or demand-driven reopen to flush them |
|
|
55
56
|
| BFCache suspension | lifecycle | Hiding the tab stops the transport and silently cancels in-flight persistence retries; pageshow reopens the transport and re-establishes subscriptions exactly once per cycle |
|
package/docs/configuration.md
CHANGED
|
@@ -54,7 +54,7 @@ When `replay.retentionMs` is enabled, automatic durable cleanup is coalesced dur
|
|
|
54
54
|
|
|
55
55
|
`replay.retentionSweepMs` optionally schedules the same cleanup on a periodic interval. It is useful for quiet topics whose old durable records should still expire; it requires `retentionMs` and a persistence adapter with `clearBefore()`. The timer follows visibility and lifecycle transitions and is disabled by default.
|
|
56
56
|
|
|
57
|
-
`replay.persistenceRetry` optionally controls transient persistence recovery. `maxAttempts` is the total number of attempts (default `1`), and `backoffMs` is the initial delay before retry (default `50`). Delays grow
|
|
57
|
+
`replay.persistenceRetry` optionally controls transient persistence recovery. `maxAttempts` is the total number of attempts (default `1`), and `backoffMs` is the initial delay before retry (default `50`). Delays are capped only where they grow: after a failed attempt the delay doubles and the doubled value is capped at 1600 ms, while `backoffMs` itself is waited verbatim as the first delay — so `backoffMs: 5000` waits 5000 ms, then 1600 ms, rather than being clamped to the ceiling; final failures retain the existing `onError` and reliability behavior. A batch flush still queued in a microtask when the bus suspends or stops is discarded with its lifecycle generation rather than starting a durable append after teardown.
|
|
58
58
|
|
|
59
59
|
### Replay Options
|
|
60
60
|
|
|
@@ -65,7 +65,7 @@ When `replay.retentionMs` is enabled, automatic durable cleanup is coalesced dur
|
|
|
65
65
|
| `retentionMs` | `number` | — | Producer-timestamp retention window; history older than the cutoff is pruned through the adapter's `clearBefore` |
|
|
66
66
|
| `pruneStrategy` | `'count' \| 'age' \| 'both'` | `'count'` | `count` caps each topic at `maxPerTopic`; `age` prunes timestamped history by `retentionMs` and caps timestamp-less legacy entries by `maxPerTopic`; `both` applies both. `age` without `retentionMs` has nothing to prune by and falls back to the count cap |
|
|
67
67
|
| `retentionSweepMs` | `number` | — | Periodic durable-retention sweep for quiet topics; requires `retentionMs` and a `clearBefore` adapter |
|
|
68
|
-
| `persistenceRetry` | `{ maxAttempts, backoffMs }` | `1` / `50` | Bounded retry for transient persistence failures;
|
|
68
|
+
| `persistenceRetry` | `{ maxAttempts, backoffMs }` | `1` / `50` | Bounded retry for transient persistence failures; the delay doubles after each attempt and the doubled value is capped at 1600 ms, `backoffMs` being waited verbatim first |
|
|
69
69
|
|
|
70
70
|
### Deduplication Options
|
|
71
71
|
|
package/docs/getting-started.md
CHANGED
|
@@ -14,7 +14,8 @@ The package provides the following entry points:
|
|
|
14
14
|
- `cross-tab-worker-databus/centrifuge`: the built-in Centrifuge Worker transport
|
|
15
15
|
- `cross-tab-worker-databus/centrifuge.worker`: the Dedicated Worker build artifact, loaded by default by the built-in factory; typically no need to reference it directly
|
|
16
16
|
- `cross-tab-worker-databus/centrifuge.shared.worker`: the SharedWorker build artifact, loaded by default by the built-in factory; typically no need to reference it directly
|
|
17
|
-
- `cross-tab-worker-databus/hooks`: optional React hooks adapter (`useCrossTabDataBus`, `useCrossTabSubscription`, `useCrossTabStatus`); React (>= 18) is an optional peer dependency
|
|
17
|
+
- `cross-tab-worker-databus/hooks`: optional React hooks adapter (`useCrossTabDataBus`, `useCrossTabSubscription`, `useCrossTabStatus`, `useCrossTabHealth`); React (>= 18) is an optional peer dependency
|
|
18
|
+
- `cross-tab-worker-databus/vue`: optional Vue composables with the same four names (`useCrossTabDataBus`, `useCrossTabSubscription`, `useCrossTabStatus`, `useCrossTabHealth`); Vue (>= 3.3) is an optional peer dependency
|
|
18
19
|
- `cross-tab-worker-databus` also exports a zero-dependency native `WebSocketTransport` / `createWebSocketDataBus` for servers that speak plain WebSockets
|
|
19
20
|
|
|
20
21
|
The `cross-tab-worker-databus/centrifuge` entry point relies on the optional peer dependency `centrifuge` (^5.5.3). Install it alongside this package when using the built-in Centrifuge transport: `pnpm add centrifuge`.
|
|
@@ -46,7 +47,7 @@ export const dataBus = createCentrifugeDataBus<ResourceEvent>({
|
|
|
46
47
|
|
|
47
48
|
`clusterKey` is derived from the connection URL by default. It is only used for cluster isolation and is converted to an opaque key before entering localStorage and as the BroadcastChannel channel name. Note that topic names and event types sent over the BroadcastChannel coordination channel are transmitted in plaintext; only localStorage metadata is obfuscated via hashing.
|
|
48
49
|
|
|
49
|
-
By default, a Dedicated Worker is used, one Worker per Tab.
|
|
50
|
+
By default, a Dedicated Worker is used, one Worker per Tab. `workerMode: 'shared'` (or `'auto'`, which tries shared first) puts same-origin Tabs into **one** SharedWorker process — but it does not merge their connections. Each connecting port gets its own `CentrifugeSession` and so its own WebSocket, which is why closing or refreshing one tab leaves the others subscribed: N tabs still mean N server-side connections and N channel subscriptions. Shared mode saves the Worker process, not the socket:
|
|
50
51
|
|
|
51
52
|
```ts
|
|
52
53
|
export const dataBus = createCentrifugeDataBus<ResourceEvent>({
|
|
@@ -144,7 +145,7 @@ pnpm examples
|
|
|
144
145
|
|
|
145
146
|
Open `http://localhost:4173/examples/demo/` and open it in multiple browser Tabs at the same time to observe cross-Tab data flow. The page supports:
|
|
146
147
|
|
|
147
|
-
- Connecting to the
|
|
148
|
+
- Connecting to the demo's own local Centrifugo endpoint by default (`<scheme>://<host>/centrifuge/demo/connection/websocket`, seeded into the address box on load); the public `wss://faye.centrifugal.dev/connection/websocket` is one of the selectable presets, not the default
|
|
148
149
|
- Modifying the WSS address, `workerMode`, Topic, and `transferable` configuration in the page
|
|
149
150
|
- Switching to "Local Broadcast" mode, which does not depend on an external server and demonstrates multi-Tab collaboration using only BroadcastChannel
|
|
150
151
|
- Data flow animations, event stream, distribution latency metrics, and cluster Worker routing status
|
|
@@ -11,9 +11,9 @@ Use this checklist for each pre-1.0 release. Pushing the version tag *is* the pu
|
|
|
11
11
|
|
|
12
12
|
## Automated gates (CI)
|
|
13
13
|
|
|
14
|
-
The `CI` workflow's `verify` job runs `pnpm check`, `pnpm lint`, `pnpm test:coverage` (the `vitest.config.ts` floors: 98% statements / 96% branches / 98% functions / 99% lines), `pnpm verify:compat`, `pnpm verify:pack`, `pnpm bench`, and `pnpm audit` on
|
|
14
|
+
The `CI` workflow's `verify` job runs `pnpm check`, `pnpm lint`, `pnpm test:coverage` (the `vitest.config.ts` floors: 98% statements / 96% branches / 98% functions / 99% lines), `pnpm verify:compat`, `pnpm verify:pack`, `pnpm bench`, and `pnpm audit` on pushes and pull requests **that target `main`** — both triggers are filtered to `branches: [main]` in `ci.yml`, so pushing a topic branch runs nothing and CI first covers the work when its PR opens (a PR based on another branch is not covered either); the `browser` job runs the Playwright E2E suite. The `Release` workflow repeats `verify:compat` and `verify:pack` before publishing, then runs the blocking `verify:published` gate. The checkout in both workflows uses `fetch-depth: 0` + `fetch-tags: true` because `verify:compat` resolves its baseline from the most recent release tag.
|
|
15
15
|
|
|
16
|
-
Only `pnpm bench:browser` / `pnpm bench:compare` stay local-only: shared-runner timing noise makes numeric CI gates unreliable.
|
|
16
|
+
Only `pnpm bench:browser` / `pnpm bench:compare` / `pnpm bench:trend` stay local-only: shared-runner timing noise makes numeric CI gates unreliable, and `bench:trend` rewrites `docs/benchmarks.md` from the local archive, which CI must not do.
|
|
17
17
|
|
|
18
18
|
## Before tagging
|
|
19
19
|
|
|
@@ -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
|
|
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 at a budget **twelve times the manual default** — the workflow sets `PUBLISHED_VERIFY_ATTEMPTS=48` and `PUBLISHED_VERIFY_DELAY_MS=7500` (a ~6-minute ceiling) in its own env, while `scripts/verify-published-consumer.mjs` defaults to 6 × 5000 ms = 30 s when run by hand. A release whose published package cannot be imported by a clean consumer fails the workflow — treat every `verify:published` failure as a failed release. For registry propagation or infrastructure failures, rerun the workflow against the unchanged tag; for artifact defects, ship a new patch version. Never move or reuse a published tag. 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. 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.
|
|
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. Note the asymmetry when repeating by hand: `verify-published-consumer.mjs` defaults to `6` attempts × `5_000` ms — 30 seconds, not 6 minutes — because the tag run passes the larger pair as env rather than the script owning it. So a manual repeat can report `ETARGET` on a version whose CI run was green, which reads like a failed publish and is not one; reproduce the CI budget explicitly with `PUBLISHED_VERSION=<version> PUBLISHED_VERIFY_ATTEMPTS=48 PUBLISHED_VERIFY_DELAY_MS=7500 pnpm verify:published`, or judge a manual failure against the 30 s budget you actually ran. Expect ~39 retries of 48 in a *green* tag run: registry propagation, not a symptom.
|
package/docs/roadmap.md
CHANGED
|
@@ -1,6 +1,64 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
0.21.
|
|
3
|
+
0.21.12 was released on September 23, 2026. The project is intentionally continuing through reliability-focused releases before a 1.0.0 stability freeze.
|
|
4
|
+
|
|
5
|
+
## 0.21.12 delivered scope
|
|
6
|
+
|
|
7
|
+
- The prose sweep finished where it started: the README. `README.md` and `README.zh.md` promised
|
|
8
|
+
"the persistence layer does not store connection addresses, raw Topic text, or message content"
|
|
9
|
+
two bullets under the line advertising the durable replay persistence that does precisely that —
|
|
10
|
+
its IndexedDB store is created with `keyPath: 'topic'` and its rows carry the publication payloads,
|
|
11
|
+
and the factory is a public export. The claim is now scoped to the coordination records it is true
|
|
12
|
+
of, and both opt-ins that persist real content are named beside it. The Chinese list also gained
|
|
13
|
+
the two feature bullets it never had, so the promise no longer stands without its disclosure.
|
|
14
|
+
- `docs/release-checklist.md`, which ships in the tarball, held three traps. The published-consumer
|
|
15
|
+
gate was said to run "with the same budget as a manual run" — the workflow passes 48 × 7500 ms as
|
|
16
|
+
its own env while the script defaults to 6 × 5000 ms, so a hand-run repeat hits `ETARGET` after 30
|
|
17
|
+
seconds on a tag whose CI run was green. Measured on `v0.21.11`: 39 retries of 48 in a successful
|
|
18
|
+
run, and the offline repeat passed first try. Both budgets are now stated, with the command that
|
|
19
|
+
reproduces the CI one. CI was described as running on "every push and pull request" although both
|
|
20
|
+
triggers are filtered to `branches: [main]`, and `bench:trend` was missing from the local-only list
|
|
21
|
+
despite rewriting `docs/benchmarks.md` from the local archive.
|
|
22
|
+
- The `ci.yml` comment naming the coverage floors still said 96/92/96/97 after 0.21.7 raised them to
|
|
23
|
+
98/96/98/99; the config, the shipped checklist and `AGENTS.md` already agreed, so only the comment
|
|
24
|
+
had drifted. Corrected, and `AGENTS.md` now records how the sweep concluded: an audit of "the option
|
|
25
|
+
surface" covers the tables, while the paragraph under the same heading and the README's summary
|
|
26
|
+
bullets are separate claims — and the sentence shape worth hunting first is an absolute about
|
|
27
|
+
storage, because one opt-in is enough to break it.
|
|
28
|
+
- No behaviour, wire-format, storage-layout or cluster-protocol change, and no `src/` or `tests/`
|
|
29
|
+
file was touched. Coverage stays at 99.01 / 96.90 / 99.26 / 99.69 over **895** tests.
|
|
30
|
+
## 0.21.11 delivered scope
|
|
31
|
+
|
|
32
|
+
- A prose audit of the four docs files no earlier sweep had covered — `architecture.md`,
|
|
33
|
+
`capabilities.md`, `getting-started.md`, `transports.md`, plus the `api.md` entry they lean on —
|
|
34
|
+
produced roughly fifteen sentences the code does not support, corrected in English and Chinese. The
|
|
35
|
+
load-bearing ones are all of the "never persists" family. `architecture.md` promised in six sentences
|
|
36
|
+
that coordination traffic never touches localStorage, which `channelFallback: 'storage-event'`
|
|
37
|
+
contradicts by construction: the envelope *is* the frame, so plaintext topics and publication
|
|
38
|
+
payloads live under the channel key until the channel closes and indefinitely after a tab crash. The
|
|
39
|
+
matrix said the SDK does not persist business payloads, which its own exported
|
|
40
|
+
`createIndexedDbReplayPersistence` disproves (its object store is keyed by the plaintext topic), and
|
|
41
|
+
it sold `workerMode: 'shared'` as reusing one connection when every port gets its own
|
|
42
|
+
`CentrifugeSession` and its own WebSocket — shared mode saves the process, never the socket.
|
|
43
|
+
- Two instructions written for people implementing a transport pointed at the wrong callback and the
|
|
44
|
+
wrong guard. Auto-recovery is started by `onStatus('error')` and paced by the recovery cooldown;
|
|
45
|
+
`onError` records and notifies and does nothing else, so a backend that reports a dead socket only
|
|
46
|
+
there is never reopened. The generation counter is compared only by the asynchronous credential
|
|
47
|
+
bridge — a superseded Worker cannot reach the transport, because its listeners are removed first.
|
|
48
|
+
The same file's interface block, Worker unions and wire sketch each omitted members that ship
|
|
49
|
+
(`publish`'s metadata argument, optional `publishBatch`, the diagnostics labels, the `TOKEN_*` and
|
|
50
|
+
`SESSION_REAPED` types, the batch frame).
|
|
51
|
+
- One over-absolute claim sat in three places, a `cluster.ts` comment among them: the route check
|
|
52
|
+
drops a `CONTROL/SUBSCRIBE` naming a *different* worker, and a topic whose route cannot be read is
|
|
53
|
+
accepted. The tolerance is bounded — `confirmRoute` writes nothing without a route to stamp and the
|
|
54
|
+
next reconcile withdraws the assignment — and is now pinned by a test. Measured, the stricter rule
|
|
55
|
+
the prose described fails that test *and* two load-weighting tests that already relied on it.
|
|
56
|
+
- Also in this release, from the same "measure the claim rather than re-read it" pass: the
|
|
57
|
+
persistence-retry sentence in `configuration.md` now says which half the 1600 ms ceiling bounds, two
|
|
58
|
+
tests carry the full doubling schedule and the verbatim first wait, and two `PortReaper` comments
|
|
59
|
+
describe what their legs actually protect.
|
|
60
|
+
- No behaviour, wire-format, storage-layout or cluster-protocol change. Whole-tree coverage is
|
|
61
|
+
unchanged at 99.01 / 96.90 / 99.26 / 99.69 over **895** tests.
|
|
4
62
|
|
|
5
63
|
## 0.21.10 delivered scope
|
|
6
64
|
|
package/docs/transports.md
CHANGED
|
@@ -15,10 +15,20 @@ idempotent — the DataBus may call them repeatedly and replays them on reconnec
|
|
|
15
15
|
|
|
16
16
|
```ts
|
|
17
17
|
interface DataBusTransport<TConfig = unknown, TData = unknown> {
|
|
18
|
+
/** Optional identity/backend labels surfaced through diagnostics. */
|
|
19
|
+
readonly diagnosticsName?: string;
|
|
20
|
+
readonly diagnosticsBackend?: string;
|
|
18
21
|
start(config: TConfig, handlers: DataBusTransportHandlers<TData>): MaybePromise<void>;
|
|
19
22
|
subscribe(topic: string): MaybePromise<void>;
|
|
20
23
|
unsubscribe(topic: string): MaybePromise<void>;
|
|
21
|
-
|
|
24
|
+
/** `options` carries the caller's `{ messageId, timestamp }`; a backend that
|
|
25
|
+
* drops it silently strips dedup identity and producer timestamps from peers. */
|
|
26
|
+
publish(topic: string, data: unknown, options?: DataBusPublishOptions): MaybePromise<void>;
|
|
27
|
+
/** Optional: one frame for a burst. Without it the DataBus loops `publish`.
|
|
28
|
+
* Each item is `{ data, messageId?, timestamp? }` — the same metadata `publish`
|
|
29
|
+
* takes, per entry. (That entry type is internal: name it inline, or widen the
|
|
30
|
+
* `ReadonlyArray` to whatever your backend accepts.) */
|
|
31
|
+
publishBatch?(topic: string, items: ReadonlyArray<{ data: unknown; messageId?: string; timestamp?: number }>): MaybePromise<void>;
|
|
22
32
|
stop(): MaybePromise<void>;
|
|
23
33
|
}
|
|
24
34
|
|
|
@@ -32,8 +42,12 @@ interface DataBusTransportHandlers<TData = unknown> {
|
|
|
32
42
|
`start()` receives the user-supplied connection config (untyped `TConfig` — the
|
|
33
43
|
backend owns its shape) and the three callbacks. Call `onStatus` whenever the
|
|
34
44
|
connection state changes; call `onMessage` for each inbound publication; call
|
|
35
|
-
`onError` for non-fatal errors (
|
|
36
|
-
|
|
45
|
+
`onError` for non-fatal errors. Only `onStatus('error')` starts auto-recovery —
|
|
46
|
+
the DataBus paces that reopen with a recovery cooldown and bounds it with
|
|
47
|
+
`recovery.maxAttempts`, so a flapping connection does not retry-loop. `onError`
|
|
48
|
+
records the failure for `getHealthSummary().lastFailure` and notifies subscribers,
|
|
49
|
+
and nothing else: a backend that reports a dead socket through `onError` alone
|
|
50
|
+
is never reopened.
|
|
37
51
|
|
|
38
52
|
`start()` MUST settle its returned promise only once the backend is connected,
|
|
39
53
|
and reject it when the attempt fails. The DataBus uses that settlement as its
|
|
@@ -65,8 +79,11 @@ transport only owns the I/O path: connect, subscribe, publish, disconnect.
|
|
|
65
79
|
|
|
66
80
|
Mirror the Centrifuge backend's `centrifuge-protocol.ts`: a discriminated union
|
|
67
81
|
of messages the main thread sends to the Worker (`INIT` / `SUBSCRIBE` /
|
|
68
|
-
`UNSUBSCRIBE` / `PUBLISH` / `PUBLISH_BIN` / `PING` / `STOP
|
|
69
|
-
|
|
82
|
+
`UNSUBSCRIBE` / `PUBLISH` / `PUBLISH_BIN` / `PING` / `STOP`, plus
|
|
83
|
+
`TOKEN_RESPONSE` / `TOKEN_ERROR` answering a credential request) and a union the
|
|
84
|
+
Worker posts back (`STATUS` / `MESSAGE` / `MESSAGE_BIN` / `ERROR`, plus
|
|
85
|
+
`TOKEN_REQUEST` for the credential bridge and `SESSION_REAPED` when a SharedWorker
|
|
86
|
+
port is reclaimed). Keep it
|
|
70
87
|
structured-cloneable (no functions,
|
|
71
88
|
no class instances — `Error` must be serialised).
|
|
72
89
|
|
|
@@ -92,9 +109,13 @@ reference shape, including:
|
|
|
92
109
|
|
|
93
110
|
- **Backend selection**: reuse `selectWorkerBackend` from `worker-mode.ts` so
|
|
94
111
|
your backend degrades consistently with the rest of the SDK.
|
|
95
|
-
- **Generation guard**: bump a monotonic counter when a backend is created
|
|
96
|
-
|
|
97
|
-
|
|
112
|
+
- **Generation guard**: bump a monotonic counter when a backend is created or the
|
|
113
|
+
transport stops. Only the asynchronous credential bridge compares it, because a
|
|
114
|
+
provider can settle after the Worker it was answering is gone. Worker error
|
|
115
|
+
handlers read no such counter in the reference implementation — `stop()` and
|
|
116
|
+
`onWorkerFailed()` remove those listeners before the generation moves on, so a
|
|
117
|
+
superseded backend cannot reach the transport at all. If your backend keeps a
|
|
118
|
+
listener alive across a swap, that is when you need the check yourself.
|
|
98
119
|
- **SharedWorker heartbeat**: if you use a SharedWorker, send periodic PINGs
|
|
99
120
|
so a `PortReaper` can reclaim dead-tab sessions, and handle the
|
|
100
121
|
`SESSION_REAPED` message the reaper posts before it closes a port. That
|
|
@@ -153,6 +174,7 @@ const bus = createWebSocketDataBus({
|
|
|
153
174
|
Wire protocol (JSON text frames):
|
|
154
175
|
|
|
155
176
|
- client → server: `{"op":"subscribe"|"unsubscribe"|"publish","topic":"...","data":...,"messageId"?:"...","timestamp"?:123}`
|
|
177
|
+
- client → server (batched): `{"op":"publishBatch","topic":"...","items":[{data,...,"messageId"?,"timestamp"?}]}` — one frame whose entries the server re-fans out as individual publications. Binary payloads ride inside it as byte arrays. A one-item batch is sent as a plain `publish` frame instead, so a server that implements only `publish` still sees the legacy shape; an empty batch is dropped before it reaches the socket. A batch frame on a closed socket is reported through `handlers.onError` and dropped.
|
|
156
178
|
- server → client: the canonical publication is `{"op":"publication","publication":{"topic":"...","data":...,"messageId"?:"...","timestamp"?:123}}`. The legacy flat `{"topic":"...","data":...}` frame remains accepted. Frames without a string topic are ignored; malformed JSON is reported through `handlers.onError` without throwing.
|
|
157
179
|
- A publication that carries its own string `topic` is addressed by **that** value rather than by the channel it arrived on — that is how a server delivering through a wildcard channel (`chat.*`) names the concrete topic. A payload whose top level happens to contain a `topic` string is therefore re-addressed, and dropped when no tab owns the resulting topic. On Centrifuge the channel normally arrives out of band, which is why the rule needs stating here: it is the one transport where "the topic was already in the frame" is not otherwise visible.
|
|
158
180
|
|
package/docs/zh/api.md
CHANGED
|
@@ -375,7 +375,7 @@ createStorageEventChannel(options: {
|
|
|
375
375
|
}): ClusterChannel | null
|
|
376
376
|
```
|
|
377
377
|
|
|
378
|
-
创建以 localStorage `storage` 事件为载体的 `ClusterChannel`——面向无 BroadcastChannel 环境的协调降级通道。storage 或 storage-event 来源缺失时返回 `null`。投递语义与 BroadcastChannel 一致(不回显给发送方、消息可 JSON
|
|
378
|
+
创建以 localStorage `storage` 事件为载体的 `ClusterChannel`——面向无 BroadcastChannel 环境的协调降级通道。storage 或 storage-event 来源缺失时返回 `null`。投递语义与 BroadcastChannel 一致(不回显给发送方、消息可 JSON 序列化、关闭后拒绝再写入);载荷信封里同时带一个**每通道 sender nonce** 和一个单调序列号,正是这两者共同保证连续相同的消息仍可投递——包括两个 Tab 各自的第一帧都会写入相同的 `seq=1`、否则会被静默抑制的情况。通过 `createBrowserEnvironment({ channelFallback: 'storage-event' })` 启用;安全权衡见 [configuration.md](./configuration.md#协调通道降级broadcastchannel-不可用)。
|
|
379
379
|
|
|
380
380
|
## WebSocket 传输后端
|
|
381
381
|
|