cross-tab-worker-databus 0.21.56 → 0.21.58
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 +41 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-VDCLCCMN.js → chunk-X33QQUTG.js} +117 -23
- package/dist/chunk-X33QQUTG.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +116 -22
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +116 -22
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +64 -3
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/environment.d.ts +23 -1
- package/dist/core/environment.d.ts.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/api.md +3 -1
- package/docs/architecture.md +18 -16
- package/docs/benchmarks.md +8 -8
- package/docs/configuration.md +2 -2
- package/docs/roadmap.md +1 -1
- package/docs/zh/api.md +3 -1
- package/docs/zh/architecture.md +15 -13
- package/docs/zh/benchmarks.md +8 -8
- package/docs/zh/configuration.md +2 -2
- package/docs/zh/roadmap.md +1 -1
- package/package.json +7 -7
- package/dist/chunk-VDCLCCMN.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,44 @@
|
|
|
1
|
+
## [0.21.58] - 2026-10-30
|
|
2
|
+
|
|
3
|
+
A small patch that clears the one debt `0.21.57` left behind, plus the dependency batch. No public API, export, default, frame or storage key changes; the only emitted-code delta is the removal of a guard term that could never decide anything, so behaviour is unchanged. The coverage denominators move by that one removed branch and nothing else.
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A guard term added by `0.21.57` could not decide anything.** The identity probe's `claimedByPeer` guard carried `!hasOpener` on top of the `if` immediately below, whose first disjunct is already `hasOpener` — so the term never changed the outcome, and deleting it left the whole suite green (measured: **1058 passed**). That is the "a redundant re-read is load-bearing in the wrong direction" shape `AGENTS.md` records: a term that cannot decide anything has no defensive reading to keep. Removed, with the measurement recorded beside the guard and the three remaining terms each pinned by a named case (`existing` → the create path, `!tabIdentityInitialized` → the probe landing once per document, and the probe itself → the duplicated-tab case). The opener half is still checked where it actually decides. No runtime behaviour changes; the branch denominator is one smaller (`1931/1977`).
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- **The comment explaining why the coordination fuzz's shape arm exists was wrong, and is corrected.** It said "three Tabs have independent identities, so the old shape delivers consistently"; a 20-line probe printing the ids showed `['tab-coord-a', 'tab-coord-a', 'tab-coord-a']` — `getOrCreateTabId()`'s module-level cache resolves three fresh `sessionStorage`s to the first document's id, so the harness actually drives the documented *one-Tab-many-Runtimes* shape (×3). The honest reason is stronger: with records keyed per Tab the first unsubscribe can drop the shared record and release the route, and the fuzz still passes because every behavioural arm is about the *quiesced* cluster and the next reconcile re-elects. The loss is momentary and the assertions only see the end state — which is also why the `0.21.57` behavioural cases had to be unit tests that assert *immediately after* unsubscribe. A new case pins the harness premise (one `tabId`, three `workerId`s) so a future "fix" that seeds distinct ids hits a test rather than silently invalidating the reason.
|
|
12
|
+
|
|
13
|
+
### Dependencies
|
|
14
|
+
|
|
15
|
+
- Dev-dependency batch merged as one PR (all touch the lockfile): `vite` 7.3.7 → **8.3.5** (a major, admitted because `vitest` 5's `peerDependencies.vite` is `^6.4.0 || ^7.0.0 || ^8.0.0`, and `vite` is a root devDependency with no `vite.config` and no direct import — examples build with esbuild), `react` + `react-dom` 19.3.0 → **19.3.1** (paired: dependabot's react-only bump failed `hooks.test.tsx` with "Incompatible React versions"), `vue` 3.5.43 → 3.5.44, Playwright Test 1.63.0 → 1.64.1, `fake-indexeddb` 6.2.5 → 6.2.6. Dev dependencies do not ship.
|
|
16
|
+
|
|
17
|
+
## [0.21.57] - 2026-10-30
|
|
18
|
+
|
|
19
|
+
Three coordination fixes found by asking what still depends on a Tab's identity being unique. One is the browser's own **Duplicate tab**, which the opener guard could not see; the other two were already reachable in the documented one-document-many-DataBuses pattern and in a stranded handoff. The public surface gains one *optional* argument on `getOrCreateTabId()` and no new export; the subscriber record's key changes shape, and both directions stay compatible with older peers.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **A browser-duplicated tab took the original's identity.** `getOrCreateTabId()` discarded an inherited `sessionStorage` id only when `window.opener` was present, which covers `window.open()` and not the browser's Duplicate tab — that copy carries the same session and no opener at all. `WorkerClusterRuntime` now answers an optional `isIdentityClaimedByPeer` probe from this cluster's live Worker records, which a duplicated tab cannot copy (they live in `localStorage` under a random `workerId`), and the copy mints and persists a fresh id. The probe runs on a document's first lookup only, so later Runtimes in the same document keep sharing it, and a refresh still keeps its id because `pause()` removes and flushes the departing record. After an abnormal exit that record survives for up to `workerTtlMs`, so a reload inside that window takes a fresh id — recorded at the probe and in both architecture references.
|
|
24
|
+
- **Two Runtimes in one Tab shared one subscriber record.** The documented pattern of several DataBuses in one document (market + notice) means several `workerId`s under one `tabId`, and a tab-keyed subscriber record belonged to all of them: whichever unsubscribed first deleted the others' intent, the route then looked orphaned, and the owner dropped its transport subscription — up to one heartbeat of genuinely lost publications, because the transport was no longer subscribed. Records are now keyed by `workerId` and carry it, and liveness is per Runtime, so a crashed sibling's record is pruned while its tab is still alive.
|
|
25
|
+
- **A stranded handoff could be stalled forever by a leftover record.** `reconcileSubscriptions()` stands down when the elected owner looks subscribed, because an owner with no local subscription never reconciles the topic itself. It asked whether the owner's *tab* had a subscriber record, so a leftover record from a live sibling Runtime in that tab — what a lost removal leaves — read as "the owner subscribes": the peer stood down, nobody ever wrote the route, and the unconfirmed handoff sat there for good. The decision now asks the Runtime set. A legacy tab-keyed record cannot name the Runtime that wrote it, so it does not answer the question at all: sending one redundant `CONTROL/SUBSCRIBE` to a peer that is about to confirm costs a frame, while standing down on that record can cost the topic.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Subscriber records are keyed per Runtime**: `{clusterHash}:subscriber:{topicKey}:{workerId}`, with the record carrying its `workerId`. A record written by an older version carries none; it is honoured by the finest identity it has (its tab) and pruned with it, so a mixed-version cluster keeps working in both directions — old readers enumerate by prefix and read `tabId`, old sweeps keep the new records because that field is still present. The `docs/configuration.md` boundary list and both architecture references state the new shape.
|
|
30
|
+
|
|
31
|
+
### Documentation
|
|
32
|
+
|
|
33
|
+
- The inner dispatch gate is described accurately in both languages: `hasLocalSubscriber()` reads the in-memory subscription set (wildcard patterns included), not the durable subscriber record, whose job is to tell peers the intent exists.
|
|
34
|
+
- The tab-identity paragraphs state what a shared `tabId` now costs — a false identity in the public snapshot and the diagnostics — instead of the record collision, which per-Runtime keys removed.
|
|
35
|
+
|
|
36
|
+
### Verification
|
|
37
|
+
|
|
38
|
+
- `pnpm check` — 40 files and **1057 tests**, plus the 5 perf gates; `pnpm lint`; `pnpm typecheck`; `pnpm test:coverage` **99.03 / 97.67 / 99.28 / 99.69**; `pnpm test:e2e` **39 passed**; `pnpm verify:pack` / `verify:compat` / `verify:types`; `pnpm bench` + `bench:compare --fail-above-pct 50`; `pnpm audit --registry=https://registry.npmjs.org` clean; `npm pack --dry-run` 109 files; `git diff --check` clean.
|
|
39
|
+
- The duplicate-tab path is exercised in a real browser: a second page whose init script seeds the first page's stored id before any page script runs takes its own identity, stays a live peer of the original, and a reload of the original keeps its id. Deleting the probe wiring and rebuilding fails exactly that assertion.
|
|
40
|
+
- Every fix was mutation-checked with the mutant's own failing case named, including the one that survived a vacuous assertion first (an expected value minted by the same `randomId` as the code under test) and the one classified rather than repaired (a `!this.storage` leg the compiler holds and `listKeys()`'s catch makes undecidable).
|
|
41
|
+
|
|
1
42
|
## [0.21.56] - 2026-10-25
|
|
2
43
|
|
|
3
44
|
A benchmark-trend release that also carries one shipped-comment correction. No behavior, API shape, export, default, frame or storage key changed, and the coverage floors and the zero-count arm ledger are `0.21.55`'s.
|
package/dist/centrifuge.js
CHANGED
|
@@ -144,12 +144,13 @@ function canUseStorage(storage, probeKey) {
|
|
|
144
144
|
return false;
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
|
-
function getOrCreateTabId(environment, key = TAB_ID_STORAGE_KEY) {
|
|
147
|
+
function getOrCreateTabId(environment, key = TAB_ID_STORAGE_KEY, options) {
|
|
148
148
|
const storage = environment.sessionStorage;
|
|
149
149
|
try {
|
|
150
150
|
const existing = storage?.getItem(key);
|
|
151
151
|
const hasOpener = typeof window !== "undefined" && Boolean(window.opener);
|
|
152
|
-
|
|
152
|
+
const claimedByPeer = existing !== null && existing !== void 0 && existing !== "" && !tabIdentityInitialized && options?.isIdentityClaimedByPeer?.(existing) === true;
|
|
153
|
+
if (existing && (!hasOpener || tabIdentityInitialized) && !claimedByPeer) {
|
|
153
154
|
tabIdentityInitialized = true;
|
|
154
155
|
cachedTabId = existing;
|
|
155
156
|
return existing;
|
|
@@ -716,7 +717,21 @@ var WorkerClusterRuntime = class {
|
|
|
716
717
|
this.subscriberPrefix = `${baseKey}:subscriber:`;
|
|
717
718
|
this.channelName = `${prefix}:bus:${clusterHash}`;
|
|
718
719
|
this.storage = canUseStorage(this.environment.storage, `${baseKey}:probe`) ? new BatchingStorageWriter(this.environment.storage) : null;
|
|
719
|
-
this.tabId = options.tabId ?? getOrCreateTabId(this.environment, `${prefix}:tab-id
|
|
720
|
+
this.tabId = options.tabId ?? getOrCreateTabId(this.environment, `${prefix}:tab-id`, {
|
|
721
|
+
// A browser-duplicated tab inherits sessionStorage without an opener, so
|
|
722
|
+
// `getOrCreateTabId()`'s opener guard cannot see it. A live Worker
|
|
723
|
+
// record carrying that id is the evidence the copy cannot carry: it
|
|
724
|
+
// lives in localStorage under a random `workerId`, and this probe runs
|
|
725
|
+
// in the constructor, before this Runtime has written a record of its
|
|
726
|
+
// own — so every match belongs to a different live document. A refresh
|
|
727
|
+
// removes its record in `pause()` and flushes that removal before the
|
|
728
|
+
// document goes away, so the refresh path keeps the stored id. The one
|
|
729
|
+
// case that does not is an abnormal exit, whose record survives for up
|
|
730
|
+
// to `workerTtlMs`; a reload inside that window takes a fresh identity,
|
|
731
|
+
// which costs one observer a changed `tabId` and nothing else — the
|
|
732
|
+
// stale record is pruned by the normal TTL path.
|
|
733
|
+
isIdentityClaimedByPeer: (candidate) => this.isTabIdLiveElsewhere(candidate)
|
|
734
|
+
});
|
|
720
735
|
this.workerId = options.workerId ?? `worker-${this.tabId}-${this.environment.randomId()}`;
|
|
721
736
|
const now = this.nowMs();
|
|
722
737
|
this.currentRecord = {
|
|
@@ -910,12 +925,12 @@ var WorkerClusterRuntime = class {
|
|
|
910
925
|
const topicKey = this.releaseSubscription(topic);
|
|
911
926
|
if (topicKey && !this.assignedTopics.has(topicKey)) this.knownTopics.delete(topicKey);
|
|
912
927
|
}
|
|
913
|
-
/** Remove this
|
|
928
|
+
/** Remove this Runtime's subscriber record and, when it was the last one, delete
|
|
914
929
|
* the route. Returns the topicKey (so callers like `unsubscribe` can reuse
|
|
915
930
|
* it instead of re-hashing the topic to evict the reverse cache). */
|
|
916
931
|
releaseSubscription(topic, notifyOwner = true) {
|
|
917
932
|
const topicKey = this.rememberTopic(topic);
|
|
918
|
-
this.removeStorage(this.subscriberStorageKey(topicKey, this.
|
|
933
|
+
this.removeStorage(this.subscriberStorageKey(topicKey, this.workerId));
|
|
919
934
|
const route = this.readRoute(topicKey);
|
|
920
935
|
if (!route) return topicKey;
|
|
921
936
|
const subscribers = this.readSubscriberTabIds(topicKey, this.readWorkers());
|
|
@@ -1357,11 +1372,7 @@ var WorkerClusterRuntime = class {
|
|
|
1357
1372
|
) ?? this.currentRecord;
|
|
1358
1373
|
recoveryProjectedLoads.set(owner.workerId, (recoveryProjectedLoads.get(owner.workerId) ?? owner.load) + 1);
|
|
1359
1374
|
if (owner.workerId !== this.workerId) {
|
|
1360
|
-
|
|
1361
|
-
const ownerSubscribed = workers.some(
|
|
1362
|
-
(worker) => worker.workerId === owner.workerId && subscriberTabIds.has(worker.tabId)
|
|
1363
|
-
);
|
|
1364
|
-
if (ownerSubscribed) continue;
|
|
1375
|
+
if (this.readSubscriberIds(topicKey, workers).workerIds.has(owner.workerId)) continue;
|
|
1365
1376
|
}
|
|
1366
1377
|
this.writeRoute(topicKey, owner, void 0, route.generation + 1);
|
|
1367
1378
|
this.sendControl(owner.workerId, CONTROL_ACTION.SUBSCRIBE, topic, topicKey);
|
|
@@ -1482,6 +1493,38 @@ var WorkerClusterRuntime = class {
|
|
|
1482
1493
|
return false;
|
|
1483
1494
|
}
|
|
1484
1495
|
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Whether any other live Worker record already carries `tabId`.
|
|
1498
|
+
*
|
|
1499
|
+
* Used once per document, from the constructor, to tell a browser-duplicated
|
|
1500
|
+
* tab from this tab's own refresh: the copy inherits `sessionStorage` but not
|
|
1501
|
+
* the opener, so the identity stored there is the only shared state and the
|
|
1502
|
+
* Worker records are the only evidence that another live document holds it.
|
|
1503
|
+
* Liveness is the same heartbeat window `readWorkers()` prunes by, so a record
|
|
1504
|
+
* left behind by an abnormal exit is reported as live until it expires — the
|
|
1505
|
+
* only false positive, and it re-mints one identity instead of letting two
|
|
1506
|
+
* live tabs share one.
|
|
1507
|
+
*
|
|
1508
|
+
* The `!this.storage` leg is the degraded mode, and it earns its line for two
|
|
1509
|
+
* reasons rather than one. It is what narrows the field for the scan below
|
|
1510
|
+
* (`StorageLike | null` is rejected by `readAllByPrefix`), so the compiler
|
|
1511
|
+
* holds it; and it is *not* what decides the verdict, because `listKeys()`
|
|
1512
|
+
* catches its own failure and returns no records either way — measured by
|
|
1513
|
+
* emptying this consequent, which leaves the suite green. What the inverted
|
|
1514
|
+
* return does prove is the behaviour, not the deletion: returning `true` here
|
|
1515
|
+
* fails 'keeps the inherited id when there is no storage to probe', since
|
|
1516
|
+
* without `localStorage` there is no second document to collide with.
|
|
1517
|
+
*/
|
|
1518
|
+
isTabIdLiveElsewhere(tabId) {
|
|
1519
|
+
if (!this.storage) return false;
|
|
1520
|
+
const now = this.nowMs();
|
|
1521
|
+
for (const { value: worker } of readAllByPrefix(this.storage, this.workerPrefix)) {
|
|
1522
|
+
if (worker.tabId !== tabId) continue;
|
|
1523
|
+
if (now - worker.heartbeatAt > this.workerTtlMs) continue;
|
|
1524
|
+
return true;
|
|
1525
|
+
}
|
|
1526
|
+
return false;
|
|
1527
|
+
}
|
|
1485
1528
|
/** Read all live worker records from storage, pruning stale entries past the TTL. */
|
|
1486
1529
|
readWorkers() {
|
|
1487
1530
|
if (!this.storage) return [this.currentRecord];
|
|
@@ -1499,23 +1542,61 @@ var WorkerClusterRuntime = class {
|
|
|
1499
1542
|
}
|
|
1500
1543
|
/** Enumerate all tab IDs that have a subscriber record for `topicKey`. */
|
|
1501
1544
|
readSubscriberTabIds(topicKey, workers) {
|
|
1545
|
+
return Array.from(this.readSubscriberIds(topicKey, workers).tabIds);
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* One scan of `topicKey`'s subscriber records, answering the two questions
|
|
1549
|
+
* callers ask: which Tabs hold a subscription (emptiness checks, orphan-route
|
|
1550
|
+
* pruning) and which **Runtimes** do (the stranded-handoff re-election).
|
|
1551
|
+
*
|
|
1552
|
+
* The second answer is the finer one and the reason this is one method rather
|
|
1553
|
+
* than two scans: a tab-level answer lets a leftover record from a sibling
|
|
1554
|
+
* Runtime speak for a Worker that never subscribed to the topic, and standing
|
|
1555
|
+
* down on that proof stalls the recovery forever (see the call site).
|
|
1556
|
+
*
|
|
1557
|
+
* A legacy record — written before records carried `workerId` — proves only
|
|
1558
|
+
* that *some* Runtime in its tab subscribes, so it contributes to the tab set
|
|
1559
|
+
* and **not** to the worker set. Treating it as the whole tab, which is what
|
|
1560
|
+
* the tab-level reader effectively did, is the conservative-looking choice
|
|
1561
|
+
* that can stall; the cost of the other direction is one redundant
|
|
1562
|
+
* `CONTROL/SUBSCRIBE` to a peer that is about to confirm anyway.
|
|
1563
|
+
*/
|
|
1564
|
+
readSubscriberIds(topicKey, workers) {
|
|
1565
|
+
const tabIds = /* @__PURE__ */ new Set();
|
|
1566
|
+
const workerIds = /* @__PURE__ */ new Set();
|
|
1502
1567
|
if (!this.storage) {
|
|
1503
1568
|
const topic = this.knownTopics.get(topicKey);
|
|
1504
|
-
|
|
1569
|
+
if (topic && this.subscribedTopics.has(topic)) {
|
|
1570
|
+
tabIds.add(this.tabId);
|
|
1571
|
+
workerIds.add(this.workerId);
|
|
1572
|
+
}
|
|
1573
|
+
return { tabIds, workerIds };
|
|
1505
1574
|
}
|
|
1506
1575
|
const activeTabIds = new Set(workers.map((worker) => worker.tabId));
|
|
1507
|
-
const
|
|
1576
|
+
const activeWorkerIds = new Set(workers.map((worker) => worker.workerId));
|
|
1508
1577
|
for (const { key, value: record } of readAllByPrefix(
|
|
1509
1578
|
this.storage,
|
|
1510
1579
|
`${this.subscriberPrefix}${topicKey}:`
|
|
1511
1580
|
)) {
|
|
1512
|
-
if (!
|
|
1581
|
+
if (!this.isSubscriberRecordLive(record, activeTabIds, activeWorkerIds)) {
|
|
1513
1582
|
this.removeStorage(key);
|
|
1514
1583
|
continue;
|
|
1515
1584
|
}
|
|
1516
|
-
|
|
1585
|
+
tabIds.add(record.tabId);
|
|
1586
|
+
if (record.workerId !== void 0) workerIds.add(record.workerId);
|
|
1517
1587
|
}
|
|
1518
|
-
return
|
|
1588
|
+
return { tabIds, workerIds };
|
|
1589
|
+
}
|
|
1590
|
+
/** Whether a subscriber record still belongs to a live Runtime.
|
|
1591
|
+
*
|
|
1592
|
+
* A record written by this version names its `workerId`, so liveness is the
|
|
1593
|
+
* Worker set the reconcile pass already prunes by — which is what keeps two
|
|
1594
|
+
* Runtimes in one tab independent. A record from an older version carries no
|
|
1595
|
+
* `workerId`; it is honoured while its *tab* lives, the finest identity it
|
|
1596
|
+
* has, and each Runtime replaces its own with a worker-keyed record on its
|
|
1597
|
+
* next write. */
|
|
1598
|
+
isSubscriberRecordLive(record, activeTabIds, activeWorkerIds) {
|
|
1599
|
+
return record.workerId === void 0 ? activeTabIds.has(record.tabId) : activeWorkerIds.has(record.workerId);
|
|
1519
1600
|
}
|
|
1520
1601
|
/** Read the current route for `topicKey`. With a storage layer this returns
|
|
1521
1602
|
* the durable route, or null when none is recorded. Without one (degraded
|
|
@@ -1581,19 +1662,31 @@ var WorkerClusterRuntime = class {
|
|
|
1581
1662
|
this.removeStorage(key);
|
|
1582
1663
|
}
|
|
1583
1664
|
}
|
|
1584
|
-
/** Remove subscriber records
|
|
1665
|
+
/** Remove subscriber records whose owning Runtime (or, for a legacy record,
|
|
1666
|
+
* whose tab) is no longer active. */
|
|
1585
1667
|
cleanupOrphanedSubscribers(workers) {
|
|
1586
1668
|
if (!this.storage) return;
|
|
1587
1669
|
const activeTabIds = new Set(workers.map((worker) => worker.tabId));
|
|
1670
|
+
const activeWorkerIds = new Set(workers.map((worker) => worker.workerId));
|
|
1588
1671
|
for (const { key, value: record } of readAllByPrefix(this.storage, this.subscriberPrefix)) {
|
|
1589
|
-
if (!
|
|
1672
|
+
if (!this.isSubscriberRecordLive(record, activeTabIds, activeWorkerIds)) this.removeStorage(key);
|
|
1590
1673
|
}
|
|
1591
1674
|
}
|
|
1592
|
-
/** Persist a subscriber record for this
|
|
1675
|
+
/** Persist a subscriber record for this Runtime on `topicKey`.
|
|
1676
|
+
*
|
|
1677
|
+
* Keyed by `workerId`, not `tabId`. A document may run several Runtimes that
|
|
1678
|
+
* share one `tabId` on purpose (the market + notice pattern), and a tab-keyed
|
|
1679
|
+
* record belonged to all of them at once: whichever unsubscribed first deleted
|
|
1680
|
+
* the others' intent, the route then looked orphaned, and the owner dropped a
|
|
1681
|
+
* transport subscription its surviving sibling still needed — a delivery gap
|
|
1682
|
+
* until that sibling's next reconcile tick re-elected an owner. Duplicated
|
|
1683
|
+
* tabs get the same protection even when the identity probe cannot see them,
|
|
1684
|
+
* which is why the fix is here as well as in `isTabIdLiveElsewhere()`. */
|
|
1593
1685
|
writeSubscriber(topicKey) {
|
|
1594
1686
|
if (!this.storage) return;
|
|
1595
|
-
writeJson(this.storage, this.subscriberStorageKey(topicKey, this.
|
|
1687
|
+
writeJson(this.storage, this.subscriberStorageKey(topicKey, this.workerId), {
|
|
1596
1688
|
tabId: this.tabId,
|
|
1689
|
+
workerId: this.workerId,
|
|
1597
1690
|
updatedAt: this.nowMs()
|
|
1598
1691
|
});
|
|
1599
1692
|
}
|
|
@@ -1667,8 +1760,9 @@ var WorkerClusterRuntime = class {
|
|
|
1667
1760
|
routeStorageKey(topicKey) {
|
|
1668
1761
|
return `${this.routePrefix}${topicKey}`;
|
|
1669
1762
|
}
|
|
1670
|
-
|
|
1671
|
-
|
|
1763
|
+
/** `ownerId` is the Runtime's `workerId`; older versions used the `tabId`. */
|
|
1764
|
+
subscriberStorageKey(topicKey, ownerId) {
|
|
1765
|
+
return `${this.subscriberPrefix}${topicKey}:${ownerId}`;
|
|
1672
1766
|
}
|
|
1673
1767
|
removeStorage(key) {
|
|
1674
1768
|
try {
|
|
@@ -2548,7 +2642,7 @@ var SDK_VERSION = (
|
|
|
2548
2642
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2549
2643
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2550
2644
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2551
|
-
true ? "0.21.
|
|
2645
|
+
true ? "0.21.58" : ""
|
|
2552
2646
|
);
|
|
2553
2647
|
|
|
2554
2648
|
// src/core/data-bus.ts
|
|
@@ -3801,4 +3895,4 @@ export {
|
|
|
3801
3895
|
parseDataBusPublication,
|
|
3802
3896
|
selectWorkerBackend
|
|
3803
3897
|
};
|
|
3804
|
-
//# sourceMappingURL=chunk-
|
|
3898
|
+
//# sourceMappingURL=chunk-X33QQUTG.js.map
|