cross-tab-worker-databus 0.20.94 → 0.20.96

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.
@@ -542,11 +542,11 @@ var BatchingStorageWriter = class {
542
542
  if (typeof queueMicrotask === "function") queueMicrotask(flush);
543
543
  else setTimeout(flush, 0);
544
544
  }
545
- // Schedule a single retry timer. The guard ensures only one retry is in
546
- // flight at a time; subsequent scheduleRetry calls during the wait are
547
- // no-ops because the first retry will re-flush all pending keys together.
545
+ // Arm the single backoff timer for the pass that just failed. Exactly one
546
+ // retry can be in flight because flush() the only caller — cancels any
547
+ // armed retry on entry and stops at the first failing key, so this always
548
+ // runs with `retryHandle` cleared.
548
549
  scheduleRetry() {
549
- if (this.retryHandle !== null) return;
550
550
  this.retryHandle = setTimeout(() => {
551
551
  this.retryHandle = null;
552
552
  this.flush();
@@ -747,9 +747,8 @@ var WorkerClusterRuntime = class {
747
747
  touchRouteOwnerCache(topicKey, value) {
748
748
  if (this.routeOwnerCache.has(topicKey)) this.routeOwnerCache.delete(topicKey);
749
749
  this.routeOwnerCache.set(topicKey, value);
750
- while (this.routeOwnerCache.size > this.routeOwnerCacheMax) {
751
- const oldest = this.routeOwnerCache.keys().next().value;
752
- if (oldest === void 0) break;
750
+ for (const oldest of this.routeOwnerCache.keys()) {
751
+ if (this.routeOwnerCache.size <= this.routeOwnerCacheMax) break;
753
752
  this.routeOwnerCache.delete(oldest);
754
753
  }
755
754
  }
@@ -2407,9 +2406,8 @@ var DedupManager = class {
2407
2406
  this.accepted += 1;
2408
2407
  this.windowAccepted += 1;
2409
2408
  this.trace.recordDedupAccepted();
2410
- while (this.seenMessageIds.size > this.maxEntries) {
2411
- const oldest = this.seenMessageIds.keys().next().value;
2412
- if (oldest === void 0) break;
2409
+ for (const oldest of this.seenMessageIds.keys()) {
2410
+ if (this.seenMessageIds.size <= this.maxEntries) break;
2413
2411
  this.seenMessageIds.delete(oldest);
2414
2412
  }
2415
2413
  return false;
@@ -2470,7 +2468,7 @@ var DedupManager = class {
2470
2468
  };
2471
2469
 
2472
2470
  // src/core/version.ts
2473
- var SDK_VERSION = true ? "0.20.94" : "";
2471
+ var SDK_VERSION = true ? "0.20.96" : "";
2474
2472
 
2475
2473
  // src/core/data-bus.ts
2476
2474
  var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
@@ -2561,6 +2559,10 @@ var CrossTabDataBus = class {
2561
2559
  // themselves demand: the failure path starts an on-demand reopen instead of
2562
2560
  // stranding them until some unrelated future operation arrives.
2563
2561
  recoveryWaiters = 0;
2562
+ // Latch for the empty-topic deprecation warning so a hot publish path cannot
2563
+ // fill the console. It is per-bus and never reset: the warning is about the
2564
+ // caller's code, not about a transient runtime condition.
2565
+ emptyTopicWarned = false;
2564
2566
  /** Monotonic generation incremented on every successful transport open.
2565
2567
  * Stays in lockstep with `lastSuccessAt` so callers can detect that the
2566
2568
  * transport has been reopened even if the timestamp window is short. */
@@ -2965,6 +2967,7 @@ var CrossTabDataBus = class {
2965
2967
  };
2966
2968
  }
2967
2969
  this.ensureStarted();
2970
+ if (topic === "") this.warnEmptyTopic("subscribe");
2968
2971
  const handlers = this.topicHandlers.get(topic) ?? /* @__PURE__ */ new Set();
2969
2972
  const wasUnused = handlers.size === 0;
2970
2973
  handlers.add(handler);
@@ -3014,8 +3017,20 @@ var CrossTabDataBus = class {
3014
3017
  resetDedup() {
3015
3018
  this.dedupManager.reset();
3016
3019
  }
3020
+ /** Warn (once per bus) that an empty topic is deprecated, without changing
3021
+ * behavior yet. `''` flows through routing as a literal channel, so the
3022
+ * subscription it creates can never be addressed by a transport: the message
3023
+ * silently goes nowhere. A future minor rejects it at this boundary. */
3024
+ warnEmptyTopic(operation) {
3025
+ if (this.emptyTopicWarned) return;
3026
+ this.emptyTopicWarned = true;
3027
+ console.warn(
3028
+ `cross-tab-worker-databus: ${operation}("") addresses a channel no transport can route. Use a non-empty topic; passing "" is planned to throw in a future minor.`
3029
+ );
3030
+ }
3017
3031
  /** Publish a message to `topic`. The owning Worker delivers it to the transport. */
3018
3032
  publish(topic, data, options) {
3033
+ if (topic === "") this.warnEmptyTopic("publish");
3019
3034
  this.ensureStarted();
3020
3035
  if (this.rejectPublishDuringStop("publish")) return;
3021
3036
  if (!this.cluster.publish(topic, data, options)) {
@@ -3034,6 +3049,7 @@ var CrossTabDataBus = class {
3034
3049
  publishBatch(topic, items) {
3035
3050
  this.ensureStarted();
3036
3051
  if (items.length === 0) return;
3052
+ if (topic === "") this.warnEmptyTopic("publishBatch");
3037
3053
  if (this.rejectPublishDuringStop("publishBatch")) return;
3038
3054
  if (items.length === 1) {
3039
3055
  const first = items[0];
@@ -3926,12 +3942,13 @@ var CentrifugeWorkerTransport = class {
3926
3942
  heartbeatHandle = null;
3927
3943
  localSession = null;
3928
3944
  handlers = null;
3929
- // Monotonically increasing counter, bumped each time a backend is created.
3930
- // Used to ignore late error events from a superseded Worker.
3945
+ // Monotonically increasing counter, bumped each time a backend is created or
3946
+ // the transport stops. Only the asynchronous credential bridge compares it:
3947
+ // a provider may settle after the Worker it was answering is gone. Worker
3948
+ // error events need no such check — stop() and onWorkerFailed() remove those
3949
+ // listeners before the generation moves on, so a superseded backend can no
3950
+ // longer reach this object at all.
3931
3951
  generation = 0;
3932
- // Generation captured when the current backend was created. Error handlers
3933
- // only act when the backend that registered them is still current.
3934
- backendGeneration = 0;
3935
3952
  get diagnosticsBackend() {
3936
3953
  return this.backend ?? "uninitialized";
3937
3954
  }
@@ -4024,7 +4041,7 @@ var CentrifugeWorkerTransport = class {
4024
4041
  }
4025
4042
  /** Create and initialise a dedicated Worker, then send the INIT message. */
4026
4043
  startDedicatedWorker(input) {
4027
- this.backendGeneration = ++this.generation;
4044
+ this.generation += 1;
4028
4045
  const worker = (this.workerFactory ?? createDefaultWorker)();
4029
4046
  this.worker = worker;
4030
4047
  worker.addEventListener("message", this.handleMessage);
@@ -4033,7 +4050,7 @@ var CentrifugeWorkerTransport = class {
4033
4050
  }
4034
4051
  /** Create and initialise a SharedWorker, open the MessagePort, and send the INIT message. */
4035
4052
  startSharedWorker(input) {
4036
- this.backendGeneration = ++this.generation;
4053
+ this.generation += 1;
4037
4054
  const shared = (this.sharedWorkerFactory ?? createDefaultSharedWorker)();
4038
4055
  this.sharedWorker = shared;
4039
4056
  const port = shared.port;
@@ -4103,9 +4120,10 @@ var CentrifugeWorkerTransport = class {
4103
4120
  }
4104
4121
  /** Handle a Worker-level failure (crash, message decode error). Discards the
4105
4122
  * dead backend so a later start()/reopen can rebuild from scratch, and
4106
- * signals an error status so the DataBus can trigger recovery.
4107
- * Only invoked when the generation guard confirms the failing backend is
4108
- * still current late errors from a superseded Worker are silently dropped. */
4123
+ * signals an error status so the DataBus can trigger recovery. A superseded
4124
+ * backend cannot reach this: its listeners are removed before the transport
4125
+ * moves on, which is what keeps late Worker errors from tearing down a fresh
4126
+ * session (pinned by 'ignores error events from a superseded worker'). */
4109
4127
  onWorkerFailed(message) {
4110
4128
  this.worker?.removeEventListener("message", this.handleMessage);
4111
4129
  this.worker?.removeEventListener("error", this.handleWorkerError);
@@ -4136,15 +4154,12 @@ var CentrifugeWorkerTransport = class {
4136
4154
  this.heartbeatHandle = null;
4137
4155
  }
4138
4156
  handleWorkerError = () => {
4139
- if (this.generation !== this.backendGeneration) return;
4140
4157
  this.onWorkerFailed("Centrifuge worker failed.");
4141
4158
  };
4142
4159
  handlePortError = () => {
4143
- if (this.generation !== this.backendGeneration) return;
4144
4160
  this.onWorkerFailed("Centrifuge shared worker message decoding failed.");
4145
4161
  };
4146
4162
  handleSharedWorkerError = () => {
4147
- if (this.generation !== this.backendGeneration) return;
4148
4163
  this.onWorkerFailed("Centrifuge shared worker failed.");
4149
4164
  };
4150
4165
  /** Clear the Worker/port/backend references after a failure or stop. */