cross-tab-worker-databus 0.21.15 → 0.21.17

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.
@@ -1413,6 +1413,7 @@ var WorkerClusterRuntime = class {
1413
1413
  reconcile() {
1414
1414
  if (!this.started) return;
1415
1415
  const workers = this.reconcileWorkers();
1416
+ this.reconcileStrandedSelfRoutes();
1416
1417
  const activeWorkers = selectActiveWorkers(workers, this.maxActiveWorkers);
1417
1418
  this.reconcileSubscriptions(workers, activeWorkers);
1418
1419
  this.reconcileAssignedTopics();
@@ -1477,6 +1478,52 @@ var WorkerClusterRuntime = class {
1477
1478
  }
1478
1479
  }
1479
1480
  }
1481
+ /**
1482
+ * Repair a confirmed route that names this Worker while its assignment is gone.
1483
+ *
1484
+ * Every write that settles ownership stamps the new owner's assignment in the
1485
+ * same task as the confirmation (`confirmRoute()` has exactly three call sites,
1486
+ * each directly after an `assignedTopics.set`), so a route carrying
1487
+ * `confirmedAt` while this map has no entry can only mean the assignment was
1488
+ * taken *after* the route settled. That is what `pause()` does: it clears the
1489
+ * map unconditionally but rewrites only the routes it can hand off, so a
1490
+ * handoff whose final flush is cut short by a storage failure — and the
1491
+ * `discardPending()` that ends the teardown throws the rest away — leaves a
1492
+ * durable route naming this worker. If the same worker id registers again (a
1493
+ * BFCache restore, or a runtime rebuilt with an explicit `workerId`), every
1494
+ * peer reads that route as a live, confirmed owner and stands down, while no
1495
+ * worker holds a transport subscription for the topic any more. Both reconcile
1496
+ * passes miss it by construction: one walks `subscribedTopics`, the other
1497
+ * `assignedTopics`, and the phantom is in neither.
1498
+ *
1499
+ * Acting here is this worker's alone — a peer that elected it is still waiting
1500
+ * for its own CONTROL/SUBSCRIBE to be accepted, and the single-writer rule
1501
+ * recorded in `reconcileSubscriptions()` forbids anyone else rewriting the
1502
+ * record. Reclaim when the local subscription is still live, which restores the
1503
+ * assignment and the transport subscription without touching the route, so the
1504
+ * owner and its generation stay exactly as peers recorded them; release
1505
+ * otherwise, since an owner that has no handler for the topic and never will is
1506
+ * a record nothing can act on.
1507
+ */
1508
+ reconcileStrandedSelfRoutes() {
1509
+ if (!this.storage) return;
1510
+ let released = false;
1511
+ for (const { key, value: route } of readAllByPrefix(this.storage, this.routePrefix)) {
1512
+ if (route.confirmedAt === void 0 || route.workerId !== this.workerId) continue;
1513
+ if (this.assignedTopics.has(route.topicKey)) continue;
1514
+ const topic = this.knownTopics.get(route.topicKey);
1515
+ if (topic !== void 0 && this.subscribedTopics.has(topic)) {
1516
+ this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, route.topicKey);
1517
+ continue;
1518
+ }
1519
+ this.removeStorage(key);
1520
+ released = true;
1521
+ }
1522
+ if (released) {
1523
+ this.flushStorage();
1524
+ this.notifyRegistry();
1525
+ }
1526
+ }
1480
1527
  /** Drop assignments where the route no longer points to this worker. */
1481
1528
  reconcileAssignedTopics() {
1482
1529
  for (const [topicKey, topic] of [...this.assignedTopics]) {
@@ -2539,7 +2586,7 @@ var DedupManager = class {
2539
2586
  };
2540
2587
 
2541
2588
  // src/core/version.ts
2542
- var SDK_VERSION = true ? "0.21.15" : "";
2589
+ var SDK_VERSION = true ? "0.21.17" : "";
2543
2590
 
2544
2591
  // src/core/data-bus.ts
2545
2592
  var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
@@ -3490,12 +3537,18 @@ var CrossTabDataBus = class {
3490
3537
  subscribeTransport(topic) {
3491
3538
  if (this.transportSubscribedTopics.has(topic)) return false;
3492
3539
  this.transportSubscribedTopics.add(topic);
3493
- this.runTransport(() => this.transport.subscribe(topic));
3540
+ this.runTransport(
3541
+ () => this.transport.subscribe(topic),
3542
+ () => this.transportSubscribedTopics.has(topic)
3543
+ );
3494
3544
  return true;
3495
3545
  }
3496
3546
  unsubscribeTransport(topic) {
3497
3547
  if (!this.transportSubscribedTopics.delete(topic)) return false;
3498
- this.runTransport(() => this.transport.unsubscribe(topic));
3548
+ this.runTransport(
3549
+ () => this.transport.unsubscribe(topic),
3550
+ () => !this.transportSubscribedTopics.has(topic)
3551
+ );
3499
3552
  return true;
3500
3553
  }
3501
3554
  /** Invoke `callback` for each item in `handlers`, isolating a throwing
@@ -3613,8 +3666,15 @@ var CrossTabDataBus = class {
3613
3666
  * Run a transport operation now if the transport is ready, otherwise queue
3614
3667
  * it behind the start promise. This ensures subscribe/unsubscribe calls made
3615
3668
  * during startup are not lost.
3669
+ *
3670
+ * `stillWanted` expresses the deferred work as the *current* desired state
3671
+ * rather than a captured call, and is checked at each place that resumes on a
3672
+ * later task — the recovery gate and the opening flush. The immediate branch
3673
+ * carries no check because every path into it evaluates the desired state in
3674
+ * the same task: a caller that just mutated `transportSubscribedTopics`, or
3675
+ * the gate re-entry one line above.
3616
3676
  */
3617
- runTransport(operation) {
3677
+ runTransport(operation, stillWanted) {
3618
3678
  if (this.suspended) return;
3619
3679
  if (this.recoveryGate && !this.stopping) {
3620
3680
  const gate = this.recoveryGate;
@@ -3624,7 +3684,8 @@ var CrossTabDataBus = class {
3624
3684
  void gate.then(() => {
3625
3685
  this.recoveryWaiters -= 1;
3626
3686
  if (this.stopping || this.suspended || cancellationToken !== this.recoveryCancellationToken) return;
3627
- this.runTransport(operation);
3687
+ if (stillWanted && !stillWanted()) return;
3688
+ this.runTransport(operation, stillWanted);
3628
3689
  });
3629
3690
  return;
3630
3691
  }
@@ -3645,6 +3706,7 @@ var CrossTabDataBus = class {
3645
3706
  void ready.then(
3646
3707
  () => {
3647
3708
  if (!this.started || this.stopping || this.suspended) return;
3709
+ if (stillWanted && !stillWanted()) return;
3648
3710
  return operation();
3649
3711
  },
3650
3712
  // The opening promise reports its own lifecycle failure through