cross-tab-worker-databus 0.21.22 → 0.21.24
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 +49 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-ISYP7LVP.js → chunk-IFSC44DS.js} +10 -3
- package/dist/chunk-IFSC44DS.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +9 -2
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +9 -2
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/validation.d.ts.map +1 -1
- package/docs/api.md +2 -0
- package/docs/roadmap.md +17 -1
- package/docs/zh/api.md +2 -0
- package/docs/zh/roadmap.md +17 -1
- package/package.json +1 -1
- package/dist/chunk-ISYP7LVP.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
|
+
## [0.21.24] - 2026-09-25
|
|
2
|
+
|
|
3
|
+
Two lifecycle defects that the coverage ledger handed over as a by-product: a cluster runtime stopped during its own activation rebuilt everything its teardown had removed, and a `stop()` issued from one of its callbacks was dropped on the floor. A verdict now sits at every zero-count site the ledger had left bare.
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A teardown arriving during activation abandons the rest of it.** `activate()` sets `started = true` and only then reaches consumer code — the environment adapter's `createChannel`, and `handlers.onControl` for a self-addressed SUBSCRIBE when storage is unavailable — before it arms its heartbeat. A `stop()` or pagehide landing in that span had already run `pause()`, so the remaining lines rebuilt exactly what it removed: the runtime kept a live channel with its listener attached, was written back into storage as a worker record no peer would ever TTL-prune, armed an interval no later `stop()` could clear (every leaked tick re-registered the departed tab), and accepted a `CONTROL/SUBSCRIBE` frame addressed to it after the stop. All four were measured on the unmodified tree, and each half of the rollback now dies to its own named assertion.
|
|
8
|
+
- **A `stop()` issued from `handlers.onResume` takes effect.** `handlePageShow()` clears `suspended` on the statement before it calls that callback, and `started` is set only by the `activate()` its own generation bump then skips, so both of `stop()`'s entry flags read false while consumer code was running: the teardown was dropped, the lifecycle listeners stayed attached, and the next hide/show pair re-fired `onResume` and put `coordinated` back to true. The guard's third term is `lifecycleListening`, which is what "something is still attached" actually means here.
|
|
9
|
+
|
|
10
|
+
### Tests
|
|
11
|
+
|
|
12
|
+
- **Three cases, one per symptom, and a census to tell the rest from unreachability.** Two new `cluster.test.ts` tests construct the activation window from the two seams that open it (a channel adapter that stops the runtime, a control handler that stops the runtime) and assert the abandoned activation leaves no registration, no heartbeat and no live listener; the third stops the runtime from inside `onResume` and asserts a later visibility toggle fires nothing. For the arms that stayed zero-count the method from `0.21.23` was reused: a flag-vector census over one full suite run (four distinct `pause()` vectors, one each for `activate()`'s entry and the listener removal, two for `reconcile()` and `writeRoute()` — every one of them reading the flag false *somewhere*, which is the control on the rows that never do), plus a `tsc --noEmit` measurement for each leg whose deadness is a type question rather than a reach question: one error apiece for `resolvePublishTarget`'s `?? this.workerId`, for `writeRoute`'s storage guard (reported on the very next statement), and for each of the four projected-load reads, and five for the handoff's `if (!owner) continue` — the count the comment at that site has been claiming, re-measured against this tree rather than inherited.
|
|
13
|
+
- **The ledger moved for the first time in six phases: 47 \u2192 46 zero-count branch arms**, denominator 1958 \u2192 1963, aggregate 99.02 / 97.59 \u2192 **97.65** / 99.27 / 99.69, tests 930 \u2192 933. One arm (`pause()`'s clear-the-interval false leg) became reachable-and-covered because of the fix, and every new branch the fix introduced is exercised on both sides.
|
|
14
|
+
- **Two terms of `stop()`'s guard are dominated by the new one, and stay.** Deleting `!started` alone or `!suspended` alone leaves the suite green at its full count, because each implies `lifecycleListening`. They are kept on an asymmetry: a redundant term in a disjunction costs one boolean test, while a missing one drops a teardown silently — which is the bug this release fixes.
|
|
15
|
+
|
|
16
|
+
### Documentation
|
|
17
|
+
|
|
18
|
+
- **Thirteen verdicts written at their sites, and one shipped enumeration corrected.** Every remaining zero-count arm in `cluster.ts` now carries what decides it: the caller enumeration for `writeRoute()`'s storage guard (with the contrast that makes its twin `writeSubscriber()` covered — one unconditional call site), `routeOwnerIsLive()`'s own conjunct for the publish-target fallback, the Map seeding for the two handoff projected-load reads (the only difference from their covered twins in `reconcileSubscriptions()`), and the three-flag invariant stated once, at `stop()`, with the other two sites pointing at it. One of those sites described a `?? this.currentRecord` fallback in the method whose comment it was — that election has no such fallback; the three are `subscribe()`'s and the two in `reconcileSubscriptions()` — and the list is now named by owning method.
|
|
19
|
+
- **A consumer-visible contract was added to `docs/api.md` and its Chinese mirror**: `stop()` is effective from inside any synchronous callback of `WorkerClusterRuntime`, and an abandoned activation leaves nothing registered. One repository rule went into `AGENTS.md`: a verdict of "no caller can bring the premise" must be re-checked against the consumer code the enclosing method itself calls, because in both defects found this way the caller list was correct and the re-entry was inside the audited method.
|
|
20
|
+
|
|
21
|
+
### Compatibility
|
|
22
|
+
|
|
23
|
+
No frame, storage key, default, export or option moved. The behavior change is only observable to a consumer that stops a cluster runtime from inside one of its own callbacks or its adapter's `createChannel`, and it changes that case from "the stop is dropped and the tab revives" to "the stop takes effect" — mixed-version peers see a well-behaved peer either way. `verify:compat`, `verify:types` and `verify:pack` pass against `v0.21.23`.
|
|
24
|
+
|
|
25
|
+
## [0.21.23] - 2026-09-24
|
|
26
|
+
|
|
27
|
+
A sixteen-mutant sweep of the last multi-term guards in the repository, one behavior that no test had ever named, and a comment that existed to justify an otherwise dead operand replaced by a lint gate that refuses the edit for real.
|
|
28
|
+
|
|
29
|
+
### Tests
|
|
30
|
+
|
|
31
|
+
- **One live gap in sixteen operands.** Every four-term state chain in `data-bus.ts` was measured one deletion at a time against the whole suite: `stop()`'s "nothing to tear down" early return, the recovery-timer callback gate, `runTransport()`'s ready shortcut, and the demand reopen under it. Four of the sixteen were already pinned by a named test, eleven are closed by construction, and exactly one — `!this.stopping` on the ready shortcut — was live and unnamed. `sends no unsubscribe to a transport while the bus is stopping` pins it now, and dies to that mutant alone.
|
|
32
|
+
- **How the guard was found is the reusable part.** For a state guard there is no input vector to differential, so the operands were classified with a *flag-vector census*: instrument the guard, record the operand fields at every evaluation, run the suite once, and compare the observed rows against the single assignment each surviving operand needs to be the decider (7589 recorded `stop()` calls over seven vectors; 49 timer-callback entries over two). Where a premise row did occur but its deletion still passed, printing a short stack on first sight named the *production path* reaching the guard — `unsubscribeTransport()` ← a control frame ← `WorkerClusterRuntime.stop()`'s handoff — which is what turned a surviving mutant into a test. The construction needed a second tab: with no remaining subscriber the handoff removes the route instead of posting a frame, and the single-tab version of the test passed against its own mutation.
|
|
33
|
+
- **Two closure mechanisms that had not met this repository before.** A leg can be dominated by the *function it calls* rather than by a neighbouring operand: three of the demand reopen's four legs restate guards `reopenTransport()` performs first, so deleting one assigns the caller the same promise it would have received. And a leg can have a premise that genuinely exists for the length of every `start()` and still be unobservable, because no caller boundary falls inside it — `stop()`'s `!this.started` is that case, and the same invariant is already written down at `reopenTransport()`, in the one copy of two that had a comment.
|
|
34
|
+
- **Two tests were written, measured, and deleted.** A draft aimed at the `status !== ERROR` leg and an early single-tab version of the unsubscribe test both passed against the mutation they were written for: the first parked its operation behind the recovery gate, which pre-empts the guard a few lines earlier; the second never reached the guard at all. Neither is in the suite; the verdicts that replaced them say "no route found, held against this branch being relaxed" instead of claiming unreachability.
|
|
35
|
+
|
|
36
|
+
### Quality gates
|
|
37
|
+
|
|
38
|
+
- **`no-restricted-globals` now refuses `isFinite` and `isNaN`.** The three `typeof value !== 'number'` operands in `src/utils/validation.ts` cannot decide anything for any input, and the only reason recorded for keeping one was that it catches a specific edit — writing the coercing global `isFinite(value)` where `Number.isFinite(value)` was — which nothing refused. A leg justified by a forbidden edit should have a gate refusing the edit, so the rule was added, and then *proved to have teeth* by introducing the swap: `pnpm lint` reports it at the call. The clean tree has zero findings, because no bare `isFinite(`/`isNaN(` call exists anywhere in `src/`, `tests/` or `scripts/`.
|
|
39
|
+
- **Which meant the claim had to be hunted down.** Two live copies said the rule does not exist — the validator's own paragraph and the `AGENTS.md` bullet citing this measurement — and both now state that the operand is defended twice, and which removal each defence covers. Five historical `docs/progress.md` mentions were left as written, with the new entry as the pointer.
|
|
40
|
+
|
|
41
|
+
### Documentation
|
|
42
|
+
|
|
43
|
+
- **Three repository rules.** Measure a *state* guard with a flag-vector census plus the assignment enumeration, and use a stack on first sight of a premise row to find the path that reaches it; a cannot-decide leg whose justification is a forbidden edit should end up with a gate in front of the edit, not only a comment; and name which of "no test reaches it" and "it cannot be reached" a verdict actually establishes, because the first is a coverage statement and the second is a claim about the state machine.
|
|
44
|
+
- **Ledger:** **47** zero-count branch arms of **1958** at both ends of all three phases, aggregate **99.02 / 97.59 / 99.27 / 99.69**, tests 929 → 930. Every operand probed is evaluated on every call, so no coverage total could have pointed at the gap this release closed — the sixth consecutive phase to make that argument by measurement rather than by assertion.
|
|
45
|
+
|
|
46
|
+
### Compatibility
|
|
47
|
+
|
|
48
|
+
Mixed-version peers are unaffected: no frame, default, storage key, export or behavior moved. The pinned behavior is one the code already had and no test named; the lint rule is repository-local and reaches consumers only as comment text in the declarations and the bundles. `verify:compat`, `verify:types` and `verify:pack` pass against `v0.21.22`.
|
|
49
|
+
|
|
1
50
|
## [0.21.22] - 2026-09-24
|
|
2
51
|
|
|
3
52
|
Six phases of one method, applied to every multi-term guard the previous scan ranked: delete a single operand, run the whole suite, and ask which of three answers that mutant produced. Nine behaviors that no test named are now named, five operands are closed as incapable of deciding anything, and the coverage ledger did not move once — which is the finding, not a shortfall.
|
package/dist/centrifuge.js
CHANGED
|
@@ -742,7 +742,7 @@ var WorkerClusterRuntime = class {
|
|
|
742
742
|
*/
|
|
743
743
|
stop() {
|
|
744
744
|
this.lifecycleGeneration += 1;
|
|
745
|
-
if (!this.started && !this.suspended) return;
|
|
745
|
+
if (!this.started && !this.suspended && !this.lifecycleListening) return;
|
|
746
746
|
this.pause();
|
|
747
747
|
this.flushStorage();
|
|
748
748
|
this.removeLifecycleListeners();
|
|
@@ -770,6 +770,12 @@ var WorkerClusterRuntime = class {
|
|
|
770
770
|
this.channel = null;
|
|
771
771
|
}
|
|
772
772
|
if (!this.channel) this.storage = null;
|
|
773
|
+
if (!this.started) {
|
|
774
|
+
const channel = this.channel;
|
|
775
|
+
this.channel = null;
|
|
776
|
+
channel?.close();
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
773
779
|
this.channel?.addEventListener("message", this.handleMessage);
|
|
774
780
|
const now = this.environment.now();
|
|
775
781
|
this.currentRecord = {
|
|
@@ -785,6 +791,7 @@ var WorkerClusterRuntime = class {
|
|
|
785
791
|
if (!this.storage) this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);
|
|
786
792
|
else this.writeSubscriber(topicKey);
|
|
787
793
|
}
|
|
794
|
+
if (!this.started) return;
|
|
788
795
|
this.reconcile();
|
|
789
796
|
this.heartbeatHandle = this.environment.setInterval(() => {
|
|
790
797
|
this.writeRecord(false);
|
|
@@ -2484,7 +2491,7 @@ var SDK_VERSION = (
|
|
|
2484
2491
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2485
2492
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2486
2493
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2487
|
-
true ? "0.21.
|
|
2494
|
+
true ? "0.21.24" : ""
|
|
2488
2495
|
);
|
|
2489
2496
|
|
|
2490
2497
|
// src/core/data-bus.ts
|
|
@@ -3718,4 +3725,4 @@ export {
|
|
|
3718
3725
|
parseDataBusPublication,
|
|
3719
3726
|
selectWorkerBackend
|
|
3720
3727
|
};
|
|
3721
|
-
//# sourceMappingURL=chunk-
|
|
3728
|
+
//# sourceMappingURL=chunk-IFSC44DS.js.map
|