cross-tab-worker-databus 0.20.53 → 0.20.57

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.
@@ -352,6 +352,8 @@ var WorkerClusterRuntime = class {
352
352
  // membership drives isAssigned() and load. Grows only via CONTROL/SUBSCRIBE
353
353
  // (or local self-subscribe), never via the reverse cache.
354
354
  assignedTopics = /* @__PURE__ */ new Map();
355
+ routeOwnerCache = /* @__PURE__ */ new Map();
356
+ wildcardPublishCache = /* @__PURE__ */ new Map();
355
357
  // Reverse mapping: opaque topicKey → plaintext topic. A bounded cache with
356
358
  // FIFO eviction — NOT authoritative. It can hold a topicKey that is also in
357
359
  // assignedTopics (the owned guard prevents evicting those), because it is
@@ -413,6 +415,8 @@ var WorkerClusterRuntime = class {
413
415
  this.removeLifecycleListeners();
414
416
  this.subscribedTopics.clear();
415
417
  this.assignedTopics.clear();
418
+ this.routeOwnerCache.clear();
419
+ this.wildcardPublishCache.clear();
416
420
  this.knownTopics.clear();
417
421
  this.suspended = false;
418
422
  }
@@ -460,6 +464,8 @@ var WorkerClusterRuntime = class {
460
464
  for (const topic of this.subscribedTopics) this.releaseSubscription(topic, false);
461
465
  this.handoffAssignedTopics();
462
466
  this.assignedTopics.clear();
467
+ this.routeOwnerCache.clear();
468
+ this.wildcardPublishCache.clear();
463
469
  this.removeStorage(this.workerStorageKey(this.workerId));
464
470
  this.flushStorage();
465
471
  this.notifyRegistry();
@@ -556,14 +562,27 @@ var WorkerClusterRuntime = class {
556
562
  if (this.assignedTopics.has(topicKey)) {
557
563
  return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
558
564
  }
565
+ const cachedPattern = this.wildcardPublishCache.get(topic);
566
+ if (cachedPattern !== void 0) {
567
+ if (cachedPattern === null || this.assignedTopics.has(cachedPattern)) {
568
+ return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
569
+ }
570
+ this.wildcardPublishCache.delete(topic);
571
+ }
559
572
  for (const pattern of this.assignedTopics.values()) {
560
573
  if (pattern !== topic && topicMatchesPattern(pattern, topic)) {
574
+ this.wildcardPublishCache.set(topic, pattern);
561
575
  return this.sendControl(this.workerId, "PUBLISH", topic, topicKey, data, metadata);
562
576
  }
563
577
  }
578
+ this.wildcardPublishCache.set(topic, null);
564
579
  const workers = this.readWorkers();
565
580
  const route = this.readRoute(topicKey);
566
- const target = this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
581
+ const cached = this.routeOwnerCache.get(topicKey);
582
+ const cachedLive = cached && route && route.generation === cached.generation && route.workerId === cached.workerId && workers.some((worker) => worker.workerId === cached.workerId);
583
+ const target = cachedLive ? cached.workerId : this.routeOwnerIsLive(route, workers) ? route?.workerId ?? this.workerId : this.workerId;
584
+ if (route && target === route.workerId) this.routeOwnerCache.set(topicKey, { workerId: route.workerId, generation: route.generation });
585
+ else this.routeOwnerCache.delete(topicKey);
567
586
  return this.sendControl(target, "PUBLISH", topic, topicKey, data, metadata);
568
587
  }
569
588
  /** True when `route` exists and its owner worker is among `workers`.
@@ -1287,6 +1306,7 @@ var CrossTabDataBus = class {
1287
1306
  // Last transport failure, retained so ready() can surface it to callers who
1288
1307
  // never awaited start() directly. Cleared on the next successful start.
1289
1308
  lastError = null;
1309
+ lastErrorAt = null;
1290
1310
  // Gate that serialises start/stop/suspend/resume — only one lifecycle
1291
1311
  // transition at a time. Resets to null once the operation settles.
1292
1312
  startPromise = null;
@@ -1500,6 +1520,7 @@ var CrossTabDataBus = class {
1500
1520
  this.updateStatus("error");
1501
1521
  this.reportError(error);
1502
1522
  this.lastError = error;
1523
+ this.lastErrorAt = this.now();
1503
1524
  this.transportReady = false;
1504
1525
  if (stopClusterOnFailure) {
1505
1526
  this.stopping = true;
@@ -1661,7 +1682,7 @@ var CrossTabDataBus = class {
1661
1682
  /** Return the current automatic transport recovery state. `hasError` means a transport error is currently retained. */
1662
1683
  getRecoveryStats() {
1663
1684
  const errorMessage = this.lastError instanceof Error ? this.lastError.message : this.lastError === null ? null : String(this.lastError);
1664
- return { attempt: this.recoveryAttempt, exhausted: this.recoveryExhausted, maxAttempts: this.recoveryMaxAttempts, hasError: this.lastError !== null, errorMessage };
1685
+ return { attempt: this.recoveryAttempt, exhausted: this.recoveryExhausted, maxAttempts: this.recoveryMaxAttempts, hasError: this.lastError !== null, errorMessage, errorAt: this.lastErrorAt };
1665
1686
  }
1666
1687
  /** Snapshot of the cluster state (workers, routes, assignments).
1667
1688
  * For diagnostics only — the returned object is a shallow copy but
@@ -1699,6 +1720,7 @@ var CrossTabDataBus = class {
1699
1720
  this.startPromise = null;
1700
1721
  this.pendingStop = null;
1701
1722
  this.lastError = null;
1723
+ this.lastErrorAt = null;
1702
1724
  this.activeConfig = void 0;
1703
1725
  this.recoveryAttempt = 0;
1704
1726
  this.recoveryExhausted = false;