cross-tab-worker-databus 0.20.57 → 0.20.59
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 +17 -2
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-WAQJT3EE.js → chunk-RAUX7RZC.js} +54 -15
- package/dist/chunk-RAUX7RZC.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +53 -14
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +53 -14
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +13 -0
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +13 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/roadmap.md +19 -1
- package/docs/zh/roadmap.md +19 -1
- package/package.json +1 -1
- package/dist/chunk-WAQJT3EE.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
|
+
## [0.20.59] - 2026-09-04
|
|
3
|
+
|
|
4
|
+
### Added
|
|
5
|
+
- Extended `CrossTabDataBus.getRecoveryStats()` with `generation` (monotonic counter incremented on every successful transport open) and `lastSuccessAt` (timestamp of the most recent successful open, or `null` until the transport reaches `ready`).
|
|
6
|
+
- Added a unit test asserting generation/lastSuccessAt advance on the initial start and after a recovery, and stay stable across failed recovery attempts.
|
|
7
|
+
|
|
8
|
+
## [0.20.58] - 2026-09-04
|
|
9
|
+
|
|
10
|
+
### Changed
|
|
11
|
+
- Bounded the publish route-owner cache to a configurable LRU cap (default 256), evicting the oldest entry on overflow.
|
|
12
|
+
- Surfaced route-owner cache diagnostics (`size`, `max`, `hits`, `misses`) on `WorkerClusterRuntime.getSnapshot()` so callers can observe warm vs. cold route resolution.
|
|
13
|
+
- Fixed a correctness bug where `wildcardPublishCache`'s `null` entry short-circuited the remote-owner publish path; topics with no local wildcard subscription now still consult the route-owner cache and forward to the remote owner.
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- Added `routeOwnerCacheMax` to `WorkerClusterOptions` for tuning the route-owner cache size per workload.
|
|
17
|
+
- Added unit tests for LRU eviction, TTL-based cache invalidation, owner migration, and remote-owner cache hits.
|
|
18
|
+
|
|
2
19
|
## [0.20.57] - 2026-09-04
|
|
3
20
|
|
|
4
21
|
### Added
|
|
@@ -26,8 +43,6 @@
|
|
|
26
43
|
|
|
27
44
|
- Extended `getRecoveryStats()` with a safe `errorMessage` summary for diagnostics without exposing the raw error object.
|
|
28
45
|
|
|
29
|
-
## [Unreleased]
|
|
30
|
-
|
|
31
46
|
## [0.20.52] - 2026-09-04
|
|
32
47
|
|
|
33
48
|
### Added
|
package/dist/centrifuge.js
CHANGED
|
@@ -337,6 +337,18 @@ var WorkerClusterRuntime = class {
|
|
|
337
337
|
// (or local self-subscribe), never via the reverse cache.
|
|
338
338
|
assignedTopics = /* @__PURE__ */ new Map();
|
|
339
339
|
routeOwnerCache = /* @__PURE__ */ new Map();
|
|
340
|
+
routeOwnerCacheMax;
|
|
341
|
+
routeOwnerCacheHits = 0;
|
|
342
|
+
routeOwnerCacheMisses = 0;
|
|
343
|
+
touchRouteOwnerCache(topicKey, value) {
|
|
344
|
+
if (this.routeOwnerCache.has(topicKey)) this.routeOwnerCache.delete(topicKey);
|
|
345
|
+
this.routeOwnerCache.set(topicKey, value);
|
|
346
|
+
while (this.routeOwnerCache.size > this.routeOwnerCacheMax) {
|
|
347
|
+
const oldest = this.routeOwnerCache.keys().next().value;
|
|
348
|
+
if (oldest === void 0) break;
|
|
349
|
+
this.routeOwnerCache.delete(oldest);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
340
352
|
wildcardPublishCache = /* @__PURE__ */ new Map();
|
|
341
353
|
// Reverse mapping: opaque topicKey → plaintext topic. A bounded cache with
|
|
342
354
|
// FIFO eviction — NOT authoritative. It can hold a topicKey that is also in
|
|
@@ -355,6 +367,7 @@ var WorkerClusterRuntime = class {
|
|
|
355
367
|
this.handlers = options.handlers;
|
|
356
368
|
this.maxActiveWorkers = options.maxActiveWorkers ?? DEFAULT_MAX_ACTIVE_WORKERS;
|
|
357
369
|
this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
|
|
370
|
+
this.routeOwnerCacheMax = options.routeOwnerCacheMax ?? 256;
|
|
358
371
|
this.workerTtlMs = options.workerTtlMs ?? DEFAULT_WORKER_TTL_MS;
|
|
359
372
|
const clusterHash = createOpaqueKey(options.clusterKey || "__default__");
|
|
360
373
|
const prefix = options.storagePrefix ?? DEFAULT_STORAGE_PREFIX;
|
|
@@ -547,25 +560,26 @@ var WorkerClusterRuntime = class {
|
|
|
547
560
|
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
548
561
|
}
|
|
549
562
|
const cachedPattern = this.wildcardPublishCache.get(topic);
|
|
550
|
-
if (cachedPattern !== void 0) {
|
|
551
|
-
|
|
552
|
-
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
553
|
-
}
|
|
554
|
-
this.wildcardPublishCache.delete(topic);
|
|
563
|
+
if (cachedPattern !== void 0 && cachedPattern !== null && this.assignedTopics.has(cachedPattern)) {
|
|
564
|
+
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
555
565
|
}
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
566
|
+
if (cachedPattern === void 0) {
|
|
567
|
+
for (const pattern of this.assignedTopics.values()) {
|
|
568
|
+
if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
|
|
569
|
+
this.wildcardPublishCache.set(topic, pattern);
|
|
570
|
+
return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
|
|
571
|
+
}
|
|
560
572
|
}
|
|
573
|
+
this.wildcardPublishCache.set(topic, null);
|
|
561
574
|
}
|
|
562
|
-
this.wildcardPublishCache.set(topic, null);
|
|
563
575
|
const workers = this.readWorkers();
|
|
564
576
|
const route = this.readRoute(topicKey);
|
|
565
577
|
const cached = this.routeOwnerCache.get(topicKey);
|
|
566
578
|
const cachedLive = cached && route && route.generation === cached.generation && route.workerId === cached.workerId && workers.some((worker) => worker.workerId === cached.workerId);
|
|
579
|
+
if (cachedLive) this.routeOwnerCacheHits += 1;
|
|
580
|
+
else this.routeOwnerCacheMisses += 1;
|
|
567
581
|
const target = cachedLive ? cached.workerId : this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
|
|
568
|
-
if (route && target === route.workerId) this.
|
|
582
|
+
if (route && target === route.workerId) this.touchRouteOwnerCache(topicKey, { workerId: route.workerId, generation: route.generation });
|
|
569
583
|
else this.routeOwnerCache.delete(topicKey);
|
|
570
584
|
return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
|
|
571
585
|
}
|
|
@@ -622,7 +636,8 @@ var WorkerClusterRuntime = class {
|
|
|
622
636
|
routes,
|
|
623
637
|
subscribedTopics: Array.from(this.subscribedTopics),
|
|
624
638
|
assignedTopics: Array.from(this.assignedTopics.values()),
|
|
625
|
-
knownTopics: Array.from(this.knownTopics.entries(), ([topicKey, topic]) => ({ topicKey, topic }))
|
|
639
|
+
knownTopics: Array.from(this.knownTopics.entries(), ([topicKey, topic]) => ({ topicKey, topic })),
|
|
640
|
+
routeOwnerCache: { size: this.routeOwnerCache.size, max: this.routeOwnerCacheMax, hits: this.routeOwnerCacheHits, misses: this.routeOwnerCacheMisses }
|
|
626
641
|
};
|
|
627
642
|
}
|
|
628
643
|
handlePageHide = () => this.pause();
|
|
@@ -1301,6 +1316,13 @@ var CrossTabDataBus = class {
|
|
|
1301
1316
|
// a transport reopen succeeds so traces can correlate repeated failures.
|
|
1302
1317
|
recoveryAttempt = 0;
|
|
1303
1318
|
recoveryExhausted = false;
|
|
1319
|
+
/** Monotonic generation incremented on every successful transport open.
|
|
1320
|
+
* Stays in lockstep with `lastSuccessAt` so callers can detect that the
|
|
1321
|
+
* transport has been reopened even if the timestamp window is short. */
|
|
1322
|
+
recoveryGeneration = 0;
|
|
1323
|
+
/** Timestamp of the most recent successful transport open. Null until the
|
|
1324
|
+
* transport has reached the `ready` state at least once. */
|
|
1325
|
+
lastSuccessAt = null;
|
|
1304
1326
|
// True while the tab is hidden so an in-flight transport start does not mark
|
|
1305
1327
|
// the transport ready after suspendTransport() has stopped it.
|
|
1306
1328
|
suspended = false;
|
|
@@ -1494,7 +1516,11 @@ var CrossTabDataBus = class {
|
|
|
1494
1516
|
if (this.status === "error") {
|
|
1495
1517
|
throw new Error("Transport failed during startup.");
|
|
1496
1518
|
}
|
|
1497
|
-
if (!this.suspended && !this.stopping)
|
|
1519
|
+
if (!this.suspended && !this.stopping) {
|
|
1520
|
+
this.recoveryGeneration += 1;
|
|
1521
|
+
this.lastSuccessAt = this.now();
|
|
1522
|
+
this.transportReady = true;
|
|
1523
|
+
}
|
|
1498
1524
|
});
|
|
1499
1525
|
}).catch((error) => {
|
|
1500
1526
|
if (stopClusterOnFailure) this.started = false;
|
|
@@ -1664,9 +1690,22 @@ var CrossTabDataBus = class {
|
|
|
1664
1690
|
return this.status;
|
|
1665
1691
|
}
|
|
1666
1692
|
/** Return the current automatic transport recovery state. `hasError` means a transport error is currently retained. */
|
|
1693
|
+
/** Return the current automatic transport recovery state plus diagnostics.
|
|
1694
|
+
* `generation` increments on every successful transport open (initial start
|
|
1695
|
+
* and every recovery); `lastSuccessAt` is the timestamp of the most recent
|
|
1696
|
+
* successful open, or `null` until the transport reaches `ready`. */
|
|
1667
1697
|
getRecoveryStats() {
|
|
1668
1698
|
const errorMessage = this.lastError instanceof Error ? this.lastError.message : this.lastError === null ? null : String(this.lastError);
|
|
1669
|
-
return {
|
|
1699
|
+
return {
|
|
1700
|
+
attempt: this.recoveryAttempt,
|
|
1701
|
+
exhausted: this.recoveryExhausted,
|
|
1702
|
+
maxAttempts: this.recoveryMaxAttempts,
|
|
1703
|
+
hasError: this.lastError !== null,
|
|
1704
|
+
errorMessage,
|
|
1705
|
+
errorAt: this.lastErrorAt,
|
|
1706
|
+
generation: this.recoveryGeneration,
|
|
1707
|
+
lastSuccessAt: this.lastSuccessAt
|
|
1708
|
+
};
|
|
1670
1709
|
}
|
|
1671
1710
|
/** Snapshot of the cluster state (workers, routes, assignments).
|
|
1672
1711
|
* For diagnostics only — the returned object is a shallow copy but
|
|
@@ -2143,4 +2182,4 @@ export {
|
|
|
2143
2182
|
parseDataBusPublication,
|
|
2144
2183
|
selectWorkerBackend
|
|
2145
2184
|
};
|
|
2146
|
-
//# sourceMappingURL=chunk-
|
|
2185
|
+
//# sourceMappingURL=chunk-RAUX7RZC.js.map
|