cross-tab-worker-databus 0.20.53 → 0.20.56

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.
@@ -377,6 +377,8 @@ var WorkerClusterRuntime = class {
377
377
  // membership drives isAssigned() and load. Grows only via CONTROL/SUBSCRIBE
378
378
  // (or local self-subscribe), never via the reverse cache.
379
379
  assignedTopics = /* @__PURE__ */ new Map();
380
+ routeOwnerCache = /* @__PURE__ */ new Map();
381
+ wildcardPublishCache = /* @__PURE__ */ new Map();
380
382
  // Reverse mapping: opaque topicKey → plaintext topic. A bounded cache with
381
383
  // FIFO eviction — NOT authoritative. It can hold a topicKey that is also in
382
384
  // assignedTopics (the owned guard prevents evicting those), because it is
@@ -438,6 +440,8 @@ var WorkerClusterRuntime = class {
438
440
  this.removeLifecycleListeners();
439
441
  this.subscribedTopics.clear();
440
442
  this.assignedTopics.clear();
443
+ this.routeOwnerCache.clear();
444
+ this.wildcardPublishCache.clear();
441
445
  this.knownTopics.clear();
442
446
  this.suspended = false;
443
447
  }
@@ -485,6 +489,8 @@ var WorkerClusterRuntime = class {
485
489
  for (const topic of this.subscribedTopics) this.releaseSubscription(topic, false);
486
490
  this.handoffAssignedTopics();
487
491
  this.assignedTopics.clear();
492
+ this.routeOwnerCache.clear();
493
+ this.wildcardPublishCache.clear();
488
494
  this.removeStorage(this.workerStorageKey(this.workerId));
489
495
  this.flushStorage();
490
496
  this.notifyRegistry();
@@ -581,14 +587,27 @@ var WorkerClusterRuntime = class {
581
587
  if (this.assignedTopics.has(topicKey)) {
582
588
  return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
583
589
  }
590
+ const cachedPattern = this.wildcardPublishCache.get(topic);
591
+ if (cachedPattern !== void 0) {
592
+ if (cachedPattern === null || this.assignedTopics.has(cachedPattern)) {
593
+ return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
594
+ }
595
+ this.wildcardPublishCache.delete(topic);
596
+ }
584
597
  for (const pattern of this.assignedTopics.values()) {
585
598
  if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
599
+ this.wildcardPublishCache.set(topic, pattern);
586
600
  return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
587
601
  }
588
602
  }
603
+ this.wildcardPublishCache.set(topic, null);
589
604
  const workers = this.readWorkers();
590
605
  const route = this.readRoute(topicKey);
591
- const target = this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
606
+ const cached = this.routeOwnerCache.get(topicKey);
607
+ const cachedLive = cached && route && route.generation === cached.generation && route.workerId === cached.workerId && workers.some((worker) => worker.workerId === cached.workerId);
608
+ const target = cachedLive ? cached.workerId : this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
609
+ if (route && target === route.workerId) this.routeOwnerCache.set(topicKey, { workerId: route.workerId, generation: route.generation });
610
+ else this.routeOwnerCache.delete(topicKey);
592
611
  return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
593
612
  }
594
613
  /** True when `route` exists and its owner worker is among `workers`.
@@ -1312,6 +1331,7 @@ var CrossTabDataBus = class {
1312
1331
  // Last transport failure, retained so ready() can surface it to callers who
1313
1332
  // never awaited start() directly. Cleared on the next successful start.
1314
1333
  lastError = null;
1334
+ lastErrorAt = null;
1315
1335
  // Gate that serialises start/stop/suspend/resume — only one lifecycle
1316
1336
  // transition at a time. Resets to null once the operation settles.
1317
1337
  startPromise = null;
@@ -1525,6 +1545,7 @@ var CrossTabDataBus = class {
1525
1545
  this.updateStatus("error");
1526
1546
  this.reportError(error);
1527
1547
  this.lastError = error;
1548
+ this.lastErrorAt = this.now();
1528
1549
  this.transportReady = false;
1529
1550
  if (stopClusterOnFailure) {
1530
1551
  this.stopping = true;
@@ -1686,7 +1707,7 @@ var CrossTabDataBus = class {
1686
1707
  /** Return the current automatic transport recovery state. `hasError` means a transport error is currently retained. */
1687
1708
  getRecoveryStats() {
1688
1709
  const errorMessage = this.lastError instanceof Error ? this.lastError.message : this.lastError === null ? null : String(this.lastError);
1689
- return { attempt: this.recoveryAttempt, exhausted: this.recoveryExhausted, maxAttempts: this.recoveryMaxAttempts, hasError: this.lastError !== null, errorMessage };
1710
+ return { attempt: this.recoveryAttempt, exhausted: this.recoveryExhausted, maxAttempts: this.recoveryMaxAttempts, hasError: this.lastError !== null, errorMessage, errorAt: this.lastErrorAt };
1690
1711
  }
1691
1712
  /** Snapshot of the cluster state (workers, routes, assignments).
1692
1713
  * For diagnostics only — the returned object is a shallow copy but
@@ -1724,6 +1745,7 @@ var CrossTabDataBus = class {
1724
1745
  this.startPromise = null;
1725
1746
  this.pendingStop = null;
1726
1747
  this.lastError = null;
1748
+ this.lastErrorAt = null;
1727
1749
  this.activeConfig = void 0;
1728
1750
  this.recoveryAttempt = 0;
1729
1751
  this.recoveryExhausted = false;