cross-tab-worker-databus 0.21.43 → 0.21.45

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.
@@ -774,6 +774,10 @@ var WorkerClusterRuntime = class {
774
774
  channelName;
775
775
  /** Adaptive load weighting options; undefined keeps legacy topic-count routing. */
776
776
  loadWeighting;
777
+ /** The most recent finite reading of the environment's clock, and the fallback for
778
+ * a broken one — see {@link nowMs}. Seeded to 0 because a clock that has never
779
+ * produced a finite reading leaves nothing to hold. */
780
+ lastFiniteNow = 0;
777
781
  /** Rolling traffic accumulator folded into the worker record on writeRecord. */
778
782
  throughputWindow = {
779
783
  startedAt: 0,
@@ -835,7 +839,7 @@ var WorkerClusterRuntime = class {
835
839
  this.storage = canUseStorage(this.environment.storage, `${baseKey}:probe`) ? new BatchingStorageWriter(this.environment.storage) : null;
836
840
  this.tabId = options.tabId ?? getOrCreateTabId(this.environment, `${prefix}:tab-id`);
837
841
  this.workerId = options.workerId ?? `worker-${this.tabId}-${this.environment.randomId()}`;
838
- const now = this.environment.now();
842
+ const now = this.nowMs();
839
843
  this.currentRecord = {
840
844
  protocolVersion: CLUSTER_PROTOCOL_VERSION,
841
845
  workerId: this.workerId,
@@ -848,6 +852,33 @@ var WorkerClusterRuntime = class {
848
852
  registeredAt: now
849
853
  };
850
854
  }
855
+ /**
856
+ * The environment's clock, normalized.
857
+ *
858
+ * `ClusterEnvironment.now` is a required field, and the documented hazard is a
859
+ * *backwards* step rather than a missing reading — the library's own subtraction
860
+ * sites tolerate one, since every `now - stamped > ttl` reads a backwards clock as
861
+ * "not yet expired". A non-finite reading is outside that contract, and it does not
862
+ * degrade one field: measured before this guard, a `NaN` clock was written straight
863
+ * into durable state — the worker, route and subscriber records all carried
864
+ * `"updatedAt": null`, because `JSON.stringify(NaN)` is `null` — and that is worse
865
+ * than a wrong number rather than merely different, because `null` coerces to `0`
866
+ * in the TTL arithmetic, so the first tick after the clock recovered would read
867
+ * every record as ancient and prune the whole cluster at once.
868
+ *
869
+ * Holding the last finite reading is the same treatment `data-bus.ts` applies to
870
+ * `dedup.now` and `DedupManager` applies to its own boundary: a paused clock, not a
871
+ * poisoned one. It is a helper rather than nine conditions because the broken input
872
+ * is the clock, not any particular comparison.
873
+ */
874
+ nowMs() {
875
+ const reading = this.environment.now();
876
+ if (Number.isFinite(reading)) {
877
+ this.lastFiniteNow = reading;
878
+ return reading;
879
+ }
880
+ return this.lastFiniteNow;
881
+ }
851
882
  /** Start the cluster: register, listen for lifecycle events, and begin heartbeats. */
852
883
  start() {
853
884
  if (this.started) return;
@@ -904,7 +935,7 @@ var WorkerClusterRuntime = class {
904
935
  return;
905
936
  }
906
937
  this.channel?.addEventListener("message", this.handleMessage);
907
- const now = this.environment.now();
938
+ const now = this.nowMs();
908
939
  this.currentRecord = {
909
940
  ...this.currentRecord,
910
941
  heartbeatAt: now,
@@ -1390,7 +1421,7 @@ var WorkerClusterRuntime = class {
1390
1421
  * will never complete — while a fresh unconfirmed route may simply be
1391
1422
  * waiting out its confirmation flush and must be left alone. */
1392
1423
  isStaleHandoff(route) {
1393
- return this.environment.now() - route.updatedAt > this.workerTtlMs;
1424
+ return this.nowMs() - route.updatedAt > this.workerTtlMs;
1394
1425
  }
1395
1426
  /** Full reconciliation cycle: workers, subscriptions, and assigned topics. */
1396
1427
  reconcile() {
@@ -1575,7 +1606,7 @@ var WorkerClusterRuntime = class {
1575
1606
  /** Read all live worker records from storage, pruning stale entries past the TTL. */
1576
1607
  readWorkers() {
1577
1608
  if (!this.storage) return [this.currentRecord];
1578
- const now = this.environment.now();
1609
+ const now = this.nowMs();
1579
1610
  const workers = [];
1580
1611
  for (const { key, value: worker } of readAllByPrefix(this.storage, this.workerPrefix)) {
1581
1612
  if (worker.workerId !== this.workerId && now - worker.heartbeatAt > this.workerTtlMs) {
@@ -1627,7 +1658,7 @@ var WorkerClusterRuntime = class {
1627
1658
  topicKey,
1628
1659
  workerId: this.workerId,
1629
1660
  tabId: this.tabId,
1630
- updatedAt: this.environment.now(),
1661
+ updatedAt: this.nowMs(),
1631
1662
  generation: 1
1632
1663
  };
1633
1664
  }
@@ -1644,7 +1675,7 @@ var WorkerClusterRuntime = class {
1644
1675
  topicKey,
1645
1676
  workerId: owner.workerId,
1646
1677
  tabId: owner.tabId,
1647
- updatedAt: this.environment.now(),
1678
+ updatedAt: this.nowMs(),
1648
1679
  generation,
1649
1680
  ...handoffFromWorkerId ? { handoffFromWorkerId } : {}
1650
1681
  };
@@ -1656,7 +1687,7 @@ var WorkerClusterRuntime = class {
1656
1687
  if (!route || route.workerId !== this.workerId || route.confirmedAt !== void 0) return;
1657
1688
  writeJson(this.storage, this.routeStorageKey(topicKey), {
1658
1689
  ...route,
1659
- confirmedAt: this.environment.now()
1690
+ confirmedAt: this.nowMs()
1660
1691
  });
1661
1692
  const topic = this.knownTopics.get(topicKey);
1662
1693
  if (topic) this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_ACK, topic });
@@ -1664,7 +1695,7 @@ var WorkerClusterRuntime = class {
1664
1695
  /** Remove routes whose topic has no subscribers and whose TTL has expired. */
1665
1696
  cleanupOrphanedRoutes(workers) {
1666
1697
  if (!this.storage) return;
1667
- const now = this.environment.now();
1698
+ const now = this.nowMs();
1668
1699
  for (const { key, value: route } of readAllByPrefix(this.storage, this.routePrefix)) {
1669
1700
  if (now - route.updatedAt <= this.workerTtlMs) continue;
1670
1701
  if (this.readSubscriberTabIds(route.topicKey, workers).length > 0) continue;
@@ -1684,7 +1715,7 @@ var WorkerClusterRuntime = class {
1684
1715
  if (!this.storage) return;
1685
1716
  writeJson(this.storage, this.subscriberStorageKey(topicKey, this.tabId), {
1686
1717
  tabId: this.tabId,
1687
- updatedAt: this.environment.now()
1718
+ updatedAt: this.nowMs()
1688
1719
  });
1689
1720
  }
1690
1721
  /** Persist the current worker record with an updated heartbeat timestamp.
@@ -1695,7 +1726,7 @@ var WorkerClusterRuntime = class {
1695
1726
  * status, visibility and load changes pass true; the heartbeat tick and a
1696
1727
  * role change pass false. */
1697
1728
  writeRecord(notify) {
1698
- const now = this.environment.now();
1729
+ const now = this.nowMs();
1699
1730
  const sample = this.loadWeighting !== void 0 ? this.sampleThroughput(now) : void 0;
1700
1731
  this.currentRecord = {
1701
1732
  ...this.currentRecord,
@@ -2638,7 +2669,7 @@ var SDK_VERSION = (
2638
2669
  // declaration's type is `string | undefined` and the export's is `string`
2639
2670
  // (dropping the conditional is TS2322, measured). Recorded here so the leg reads
2640
2671
  // as a shape the harness cannot reach rather than as a case nobody exercised.
2641
- true ? "0.21.43" : ""
2672
+ true ? "0.21.45" : ""
2642
2673
  );
2643
2674
 
2644
2675
  // src/core/data-bus.ts
@@ -2758,7 +2789,7 @@ var CrossTabDataBus = class {
2758
2789
  assertRecoveryOptions(recovery);
2759
2790
  this.recoveryCooldownMs = recovery?.cooldownMs ?? 1e3;
2760
2791
  this.recoveryMaxAttempts = recovery?.maxAttempts ?? Number.POSITIVE_INFINITY;
2761
- this.now = dedup?.now ?? Date.now;
2792
+ this.now = holdLastFinite(dedup?.now ?? Date.now);
2762
2793
  this.transport = transport;
2763
2794
  this.initialConfig = initialConfig;
2764
2795
  this.hasInitialConfig = "initialConfig" in options;
@@ -3817,6 +3848,19 @@ function formatWorkerTrace(worker) {
3817
3848
  function formatRouteTrace(route) {
3818
3849
  return `${route.topicKey}@${route.workerId}|confirmed=${route.confirmedAt !== void 0}`;
3819
3850
  }
3851
+ function holdLastFinite(source) {
3852
+ let last = 0;
3853
+ let primed = false;
3854
+ return () => {
3855
+ const reading = source();
3856
+ if (Number.isFinite(reading)) {
3857
+ last = reading;
3858
+ primed = true;
3859
+ return reading;
3860
+ }
3861
+ return primed ? last : 0;
3862
+ };
3863
+ }
3820
3864
 
3821
3865
  // src/centrifuge-session.ts
3822
3866
  var import_centrifuge = require("centrifuge");