cross-tab-worker-databus 0.21.31 → 0.21.32
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 +28 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-M4RO3MVX.js → chunk-PNEOQX4O.js} +2 -38
- package/dist/chunk-PNEOQX4O.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +1 -37
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +1 -37
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +0 -1
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/trace.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/architecture.md +1 -1
- package/docs/release-checklist.md +1 -1
- package/docs/roadmap.md +10 -2
- package/docs/zh/architecture.md +1 -1
- package/docs/zh/release-checklist.md +1 -1
- package/docs/zh/roadmap.md +10 -2
- package/package.json +1 -1
- package/dist/chunk-M4RO3MVX.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,31 @@
|
|
|
1
|
+
## [0.21.32] - 2026-09-25
|
|
2
|
+
|
|
3
|
+
A delivery bug, found while looking for something else. A topic's **first** publication from a tab that held a matching wildcard was dispatched locally and never forwarded, even while a live durable route named a different owner for it — and delivery is at-most-once, so that publication was gone for good. The fix is a deletion: the memo that made the decision decided nothing.
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Routing: a topic's first `publish()` / `publishBatch()` now reaches that topic's concrete owner.** Both methods carried a textually identical fast path: while `wildcardPublishCache` held no entry for a topic, it scanned the local wildcard patterns and, on a match, addressed the control frame to *this* worker without consulting the durable route — while every later publication for the same topic did consult it. Measured with one runtime holding `chat.*` and a peer concretely owning `chat.room.1` (both `isAssigned` true, the route record naming the peer): three publications from the wildcard holder landed `1,0,0` on the sender and `0,1,1` on the owner. The first never left the tab. The same shape reproduced for a two-item batch, whose frame the receiver unpacks per item.
|
|
8
|
+
- **This is `0.20.58` from the other side, which is why an eviction rule would not have fixed it.** There, a cached `null` short-circuited the owner lookup and the fix was to keep consulting the route; here a cache *miss* did the same thing. The stored value decided nothing either way — `pattern` and `null` were both read only as "have I scanned this topic" — so the memo is removed rather than bounded, and every non-owned publication now resolves its target through `resolvePublishTarget()`. Wildcard ownership is still honoured: with no live concrete route that call answers with this worker, and the receiving ownership gate matches the pattern. Removing the map also retires the unbounded growth documented in `0.21.31`; that growth was a side effect of this fast path, not a separate defect, so the eviction rule it was framed as needing is no longer needed at all.
|
|
9
|
+
|
|
10
|
+
- **Repository tooling: a scratch directory inside the linted tree made `pnpm lint` red without naming any product code.** A mutation-probe harness was written to `.gate-logs/` — in-tree on purpose, since `/tmp` had evicted a failing log mid-session and `pnpm test:coverage` deletes `coverage/`, where logs had also been written — and `eslint .` reported 9 errors from it (`no-undef` on `console`/`process`, `no-control-regex`) as a lint failure of the release tree. `.gate-logs/` is now ignored by both git and eslint, with the reason recorded beside the entry in `.gitignore`. `tests/regression.test.ts`'s "keeps scripts/ covered by ESLint" still passes, so the release tooling stays linted and only scratch is excluded.
|
|
11
|
+
|
|
12
|
+
### Tests
|
|
13
|
+
|
|
14
|
+
- **Two regressions, one per entry point, each asserted one publication at a time** so a regression names *which* publication was lost instead of reporting a count short by one: `forwards a topic's FIRST publish to its concrete remote owner, not to a matching local wildcard` and `forwards a batch's FIRST publish to the concrete remote owner as well`. Measured both ways — without the fix they fail at `first publish must reach the concrete owner: expected +0 to be 1` and `expected [] to deeply equal [1, 2]`; with it the file is green. Three pre-existing cases that named the removed cache in their titles were retitled to what they actually assert, and the single-runtime pair among them is now labelled as the characterization it is: a lone runtime always wins its own election, so it cannot distinguish the two paths.
|
|
15
|
+
- **`trace.ts`'s flush-activity guard was re-measured on the tree that shipped this fix**, not carried forward: deleting `received` alone reddens 3 tests, `dispatched` 2, `dedupAccepted` 1, `dedupSuppressed` 1 — each operand's own kill set, all four restorations byte-identical.
|
|
16
|
+
|
|
17
|
+
### Documentation
|
|
18
|
+
|
|
19
|
+
- **`trace.ts`: each kill now names the file it comes from.** The note recorded which test dies to each operand deletion but not where it lives, and the two `dispatched` kills sit in two different files (`tests/data-bus.test.ts` and `tests/trace.test.ts`).
|
|
20
|
+
- **The publication-lag samples were extended, and one of them had been undercounted.** `0.21.31`'s tag run is the first to exercise the raised 720 s published-consumer ceiling: green on attempt 1 at 308 s, with its registry record 127.2 s after the publish ack and the remaining ~181 s spent against the cache lifetime — so the 310 s maximum the ceiling is sized from still stands, and the ceiling does not need raising again. The other half of that sentence was wrong in the cheap direction: the checklist and `AGENTS.md` credited the 304–305 s regularity to "three consecutive green tag runs", where the five runs from 0.21.25 to 0.21.29 took 307, 304, 305, 304 and 304 s, six samples in a 5-second band once 0.21.31's 308 s is counted. The lag list, the run-by-run durations and the `gh api` lookup that re-derives both are now written out in `docs/release-checklist.md` (en + zh), the workflow's own comment, and `tests/workflows.test.ts`.
|
|
21
|
+
- **`docs/architecture.md` (en + zh): the wildcard publish memo is removed, not capped.** Both copies had described it as the per-topic map the documented `knownTopics` cap does not reach, with an open question about which of its two value shapes could be forgotten; neither half survives, so the paragraph now says what replaced it and records the 1,200-versus-500 measurement beside the delivery counts that made the mechanism visible.
|
|
22
|
+
- **`publish()`'s surviving comment described the deleted mechanism.** Above the `assignedTopics.has(topicKey)` early return it still said wildcard holders "use the same fast path" — which that lookup never did: `assignedTopics` is keyed by the plaintext it was handed, so a holder of `chat.*` carries the *pattern's* key and a concrete topic misses the `has()`, exactly the fall-through the fix relies on. The sentence now names which lookup answers for which assignment, checked against `isAssigned()`'s own `topicMatchesPattern` loop and against `publishBatch()`, which carried no such claim.
|
|
23
|
+
- **The upstream-blocked toolchain item was re-checked, and both channels still exclude TypeScript 7.** `typescript-eslint`'s latest (8.70.1) and its canary (8.70.2-alpha.7) both declare `typescript >=4.8.4 <6.1.0`, so TS 7.0.2 would break the lint gate before touching our own types. The roadmap entry now says how to look that up without being fooled: read `dist-tags.canary`, because sorting version strings puts `8.9.1-alpha.9` last — `9` sorts after `70` — while the line actually worth watching is 8.70.x. The English entry's date had also drifted from the Chinese one (2026-09-23 against 2026-09-22) for the same check; both now carry one date and the same two versions.
|
|
24
|
+
|
|
25
|
+
### Compatibility
|
|
26
|
+
|
|
27
|
+
One behavioural change: a first publication for a wildcard-covered topic that has a live concrete owner is now forwarded to that owner instead of being dispatched locally. No export, option, default, frame or storage key changed; `wildcardPublishCache` was a private field and is absent from the emitted `dist/core/cluster.d.ts`. `verify:compat`, `verify:types` and `verify:pack` run against `v0.21.31`.
|
|
28
|
+
|
|
1
29
|
## [0.21.31] - 2026-09-25
|
|
2
30
|
|
|
3
31
|
The `0.21.30` tag run failed its blocking published-consumer gate after a *successful* publish. Diagnosing that gave the retry budget a measured size, retired a floor in the gate whose job was to keep that failure out, and retracted a note this repository wrote about the same failure less than an hour before it was corrected. No library code changed.
|
package/dist/centrifuge.js
CHANGED
|
@@ -672,19 +672,6 @@ var WorkerClusterRuntime = class {
|
|
|
672
672
|
this.routeOwnerCache.delete(oldest);
|
|
673
673
|
}
|
|
674
674
|
}
|
|
675
|
-
// Concrete topic → the local wildcard pattern that matched it, or `null` for
|
|
676
|
-
// "scanned, nothing matched". UNLIKE the reverse cache below, this map has no cap:
|
|
677
|
-
// it grows one entry per distinct topic this worker publishes, and is cleared only
|
|
678
|
-
// on the two lifecycle teardowns whose `routeOwnerCache.clear()` sits beside each
|
|
679
|
-
// `wildcardPublishCache.clear()`. Measured: 1,200 `publish()` calls on distinct
|
|
680
|
-
// topic names leave `knownTopics` at its 500-entry cap and this map holding 1,200.
|
|
681
|
-
// A cap is not free here, because both halves decide something. Evicting a pattern
|
|
682
|
-
// re-runs the first local dispatch the entry exists to prevent. Evicting a `null`
|
|
683
|
-
// re-scans, and if a wildcard was assigned in the meantime that publication
|
|
684
|
-
// dispatches locally instead of routing by owner — the fan-out a first publication
|
|
685
|
-
// gets. So bounding this map needs a rule about which of the two may be forgotten,
|
|
686
|
-
// not a number; until one is chosen the growth is the honest cost of the memo.
|
|
687
|
-
wildcardPublishCache = /* @__PURE__ */ new Map();
|
|
688
675
|
// Reverse mapping: opaque topicKey → plaintext topic. A bounded cache with
|
|
689
676
|
// FIFO eviction — NOT authoritative. It can hold a topicKey that is also in
|
|
690
677
|
// assignedTopics (the owned guard prevents evicting those), because it is
|
|
@@ -761,7 +748,6 @@ var WorkerClusterRuntime = class {
|
|
|
761
748
|
this.subscribedTopics.clear();
|
|
762
749
|
this.assignedTopics.clear();
|
|
763
750
|
this.routeOwnerCache.clear();
|
|
764
|
-
this.wildcardPublishCache.clear();
|
|
765
751
|
this.knownTopics.clear();
|
|
766
752
|
this.suspended = false;
|
|
767
753
|
}
|
|
@@ -829,7 +815,6 @@ var WorkerClusterRuntime = class {
|
|
|
829
815
|
this.handoffAssignedTopics();
|
|
830
816
|
this.assignedTopics.clear();
|
|
831
817
|
this.routeOwnerCache.clear();
|
|
832
|
-
this.wildcardPublishCache.clear();
|
|
833
818
|
this.removeStorage(this.workerStorageKey(this.workerId));
|
|
834
819
|
this.flushStorage();
|
|
835
820
|
this.notifyRegistry();
|
|
@@ -936,16 +921,6 @@ var WorkerClusterRuntime = class {
|
|
|
936
921
|
if (this.assignedTopics.has(topicKey)) {
|
|
937
922
|
return this.sendControl(this.workerId, CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);
|
|
938
923
|
}
|
|
939
|
-
const cachedPattern = this.wildcardPublishCache.get(topic);
|
|
940
|
-
if (cachedPattern === void 0) {
|
|
941
|
-
for (const pattern of this.assignedTopics.values()) {
|
|
942
|
-
if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
|
|
943
|
-
this.wildcardPublishCache.set(topic, pattern);
|
|
944
|
-
return this.sendControl(this.workerId, CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
this.wildcardPublishCache.set(topic, null);
|
|
948
|
-
}
|
|
949
924
|
return this.sendControl(this.resolvePublishTarget(topic, topicKey), CONTROL_ACTION.PUBLISH, topic, topicKey, data, metadata);
|
|
950
925
|
}
|
|
951
926
|
/**
|
|
@@ -970,17 +945,6 @@ var WorkerClusterRuntime = class {
|
|
|
970
945
|
this.dispatchLocalPublishBatch(topic, topicKey, items);
|
|
971
946
|
return true;
|
|
972
947
|
}
|
|
973
|
-
const cachedPattern = this.wildcardPublishCache.get(topic);
|
|
974
|
-
if (cachedPattern === void 0) {
|
|
975
|
-
for (const pattern of this.assignedTopics.values()) {
|
|
976
|
-
if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
|
|
977
|
-
this.wildcardPublishCache.set(topic, pattern);
|
|
978
|
-
this.dispatchLocalPublishBatch(topic, topicKey, items);
|
|
979
|
-
return true;
|
|
980
|
-
}
|
|
981
|
-
}
|
|
982
|
-
this.wildcardPublishCache.set(topic, null);
|
|
983
|
-
}
|
|
984
948
|
const target = this.resolvePublishTarget(topic, topicKey);
|
|
985
949
|
if (target === this.workerId) {
|
|
986
950
|
this.dispatchLocalPublishBatch(topic, topicKey, items);
|
|
@@ -2511,7 +2475,7 @@ var SDK_VERSION = (
|
|
|
2511
2475
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2512
2476
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2513
2477
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2514
|
-
true ? "0.21.
|
|
2478
|
+
true ? "0.21.32" : ""
|
|
2515
2479
|
);
|
|
2516
2480
|
|
|
2517
2481
|
// src/core/data-bus.ts
|
|
@@ -3751,4 +3715,4 @@ export {
|
|
|
3751
3715
|
parseDataBusPublication,
|
|
3752
3716
|
selectWorkerBackend
|
|
3753
3717
|
};
|
|
3754
|
-
//# sourceMappingURL=chunk-
|
|
3718
|
+
//# sourceMappingURL=chunk-PNEOQX4O.js.map
|