cross-tab-worker-databus 0.20.87 → 0.20.89

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.
@@ -1533,8 +1533,9 @@ var DataBusTraceReporter = class {
1533
1533
  /** Start the periodic metrics flush interval. No-op when mode is 'events'
1534
1534
  * (no metrics to emit), when disabled, or when already running. */
1535
1535
  start() {
1536
- if (!this.enabled || this.intervalHandle || this.mode === TRACE_MODE.EVENTS) return;
1536
+ if (!this.enabled) return;
1537
1537
  this.stopped = false;
1538
+ if (this.intervalHandle || this.mode === TRACE_MODE.EVENTS) return;
1538
1539
  this.intervalStartedAt = this.now();
1539
1540
  this.intervalHandle = setInterval(() => this.flush(), this.metricsIntervalMs);
1540
1541
  }
@@ -1547,6 +1548,7 @@ var DataBusTraceReporter = class {
1547
1548
  }
1548
1549
  stop() {
1549
1550
  this.stopped = true;
1551
+ this.pendingEvents = [];
1550
1552
  this.pause();
1551
1553
  }
1552
1554
  /** Synchronous sink state for diagnostics: whether delivery is async and how
@@ -1557,7 +1559,7 @@ var DataBusTraceReporter = class {
1557
1559
  }
1558
1560
  /** Record an instantaneous trace event (lifecycle, status, error, etc.). */
1559
1561
  event(event) {
1560
- if (!this.enabled || this.mode === TRACE_MODE.METRICS) return;
1562
+ if (!this.enabled || this.stopped || this.mode === TRACE_MODE.METRICS) return;
1561
1563
  this.emit({ ...event, timestamp: this.now() });
1562
1564
  }
1563
1565
  /** Synchronous snapshot of the current metrics window without resetting it.
@@ -1640,7 +1642,7 @@ var DataBusTraceReporter = class {
1640
1642
  * Extracted so the four record / flush methods share one guard expression
1641
1643
  * instead of repeating `!this.enabled || this.mode === 'events'` at each. */
1642
1644
  get metricsActive() {
1643
- return this.enabled && this.mode !== TRACE_MODE.EVENTS;
1645
+ return this.enabled && !this.stopped && this.mode !== TRACE_MODE.EVENTS;
1644
1646
  }
1645
1647
  /** Emit the accumulated metrics snapshot if the interval is active. */
1646
1648
  flush() {
@@ -1942,6 +1944,8 @@ var ReplayManager = class {
1942
1944
  * or stopped bus does not keep hammering the store) and stop the sweep. */
1943
1945
  suspend() {
1944
1946
  this.retryGeneration += 1;
1947
+ this.pendingReplayPersistence = [];
1948
+ this.retentionCutoff = null;
1945
1949
  this.stop();
1946
1950
  }
1947
1951
  /** Drop all in-memory buffers (used on full teardown). */
@@ -2040,19 +2044,20 @@ var ReplayManager = class {
2040
2044
  this.retentionCutoff = cutoff;
2041
2045
  }
2042
2046
  if (this.retentionCleanup) return;
2047
+ const generation = this.retryGeneration;
2043
2048
  this.retentionCleanup = (async () => {
2044
- while (this.retentionCutoff !== null) {
2049
+ while (this.retentionCutoff !== null && generation === this.retryGeneration) {
2045
2050
  const nextCutoff = this.retentionCutoff;
2046
2051
  this.retentionCutoff = null;
2047
2052
  try {
2048
2053
  await this.persistence.clearBefore(nextCutoff);
2049
2054
  } catch (error) {
2050
- this.onPersistenceError(error);
2055
+ if (generation === this.retryGeneration) this.onPersistenceError(error);
2051
2056
  }
2052
2057
  }
2053
2058
  })().finally(() => {
2054
2059
  this.retentionCleanup = null;
2055
- if (this.retentionCutoff !== null) {
2060
+ if (this.retentionCutoff !== null && generation === this.retryGeneration) {
2056
2061
  this.scheduleRetentionCleanup(this.retentionCutoff);
2057
2062
  }
2058
2063
  });
@@ -2069,7 +2074,9 @@ var ReplayManager = class {
2069
2074
  attempt += 1;
2070
2075
  try {
2071
2076
  if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();
2072
- return await operation();
2077
+ const result = await operation();
2078
+ if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();
2079
+ return result;
2073
2080
  } catch (error) {
2074
2081
  if (error instanceof PersistenceRetryCancelledError || generation !== this.retryGeneration) {
2075
2082
  throw new PersistenceRetryCancelledError();
@@ -2203,7 +2210,7 @@ var DedupManager = class {
2203
2210
  };
2204
2211
 
2205
2212
  // src/core/version.ts
2206
- var SDK_VERSION = true ? "0.20.87" : "";
2213
+ var SDK_VERSION = true ? "0.20.89" : "";
2207
2214
 
2208
2215
  // src/core/data-bus.ts
2209
2216
  var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
@@ -2397,10 +2404,7 @@ var CrossTabDataBus = class {
2397
2404
  this.suspendTransport();
2398
2405
  },
2399
2406
  onResume: () => {
2400
- this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.RESUME });
2401
- this.trace.start();
2402
- this.startDedupSweep();
2403
- this.replayManager.start();
2407
+ this.resumeSuspendedResources();
2404
2408
  this.resumeTransport();
2405
2409
  },
2406
2410
  onDiagnostic: (event) => {
@@ -2430,15 +2434,19 @@ var CrossTabDataBus = class {
2430
2434
  if (!transportDown) return Promise.resolve();
2431
2435
  this.activeConfig = config;
2432
2436
  this.resetFailureState();
2433
- return this.reopenTransport();
2437
+ const resumingFromSuspend = this.suspended;
2438
+ if (resumingFromSuspend) this.resumeSuspendedResources();
2439
+ const opening2 = this.reopenTransport();
2440
+ if (resumingFromSuspend) this.cluster.start();
2441
+ return opening2;
2434
2442
  }
2435
2443
  this.started = true;
2436
2444
  this.stopping = false;
2437
2445
  this.suspended = false;
2438
2446
  this.activeConfig = config;
2439
2447
  this.resetFailureState();
2440
- this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });
2441
2448
  this.trace.start();
2449
+ this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });
2442
2450
  this.startDedupSweep();
2443
2451
  this.replayManager.start();
2444
2452
  this.updateStatus(WORKER_STATUS.CONNECTING);
@@ -2553,6 +2561,7 @@ var CrossTabDataBus = class {
2553
2561
  this.transportReady = false;
2554
2562
  const chainedPendingStop = this.pendingStop;
2555
2563
  const isCurrentLifecycle = () => lifecycleEpoch === this.lifecycleEpoch;
2564
+ let startupInProgress = true;
2556
2565
  return before.catch(() => void 0).then(() => {
2557
2566
  if (!isCurrentLifecycle() || this.stopping || this.suspended) return;
2558
2567
  if (this.pendingStop === chainedPendingStop) this.pendingStop = null;
@@ -2563,13 +2572,16 @@ var CrossTabDataBus = class {
2563
2572
  if (isCurrentLifecycle()) this.handleTransportMessage(message);
2564
2573
  },
2565
2574
  onStatus: (status) => {
2566
- if (isCurrentLifecycle()) this.updateStatus(status);
2575
+ if (isCurrentLifecycle()) {
2576
+ this.updateStatus(status, status !== WORKER_STATUS.ERROR || !startupInProgress);
2577
+ }
2567
2578
  },
2568
2579
  onError: (error) => {
2569
2580
  if (isCurrentLifecycle()) this.reportError(error);
2570
2581
  }
2571
2582
  })
2572
2583
  ).then(() => {
2584
+ startupInProgress = false;
2573
2585
  if (!isCurrentLifecycle()) return;
2574
2586
  if (this.status === WORKER_STATUS.ERROR) {
2575
2587
  throw new Error("Transport failed during startup.");
@@ -2583,18 +2595,21 @@ var CrossTabDataBus = class {
2583
2595
  });
2584
2596
  }).catch((error) => {
2585
2597
  if (!isCurrentLifecycle()) throw error;
2598
+ startupInProgress = false;
2586
2599
  if (stopClusterOnFailure) this.started = false;
2587
2600
  if (!this.pendingStop) {
2588
2601
  this.pendingStop = this.createStopPromise();
2589
2602
  }
2590
2603
  this.transportReady = false;
2591
- this.updateStatus(WORKER_STATUS.ERROR);
2592
- this.reportError(error);
2593
2604
  if (stopClusterOnFailure) {
2594
2605
  this.stopping = true;
2595
2606
  this.cluster.stop();
2596
2607
  this.stopping = false;
2597
2608
  }
2609
+ this.recordError(error);
2610
+ this.startPromise = null;
2611
+ this.updateStatus(WORKER_STATUS.ERROR);
2612
+ this.notifyError(error);
2598
2613
  throw error;
2599
2614
  });
2600
2615
  }
@@ -2791,7 +2806,7 @@ var CrossTabDataBus = class {
2791
2806
  getHealthSummary() {
2792
2807
  const transport = this.transport;
2793
2808
  const transportDown = this.status !== WORKER_STATUS.CONNECTED;
2794
- const state = !this.started ? HEALTH_STATE.STOPPED : this.suspended ? HEALTH_STATE.SUSPENDED : transportDown ? this.recoveryExhausted ? HEALTH_STATE.DEGRADED : this.status === WORKER_STATUS.CONNECTING && this.recoveryAttempt === 0 ? HEALTH_STATE.STARTING : HEALTH_STATE.RECOVERING : HEALTH_STATE.HEALTHY;
2809
+ const state = !this.started || this.stopping ? HEALTH_STATE.STOPPED : this.suspended ? HEALTH_STATE.SUSPENDED : transportDown ? this.recoveryExhausted ? HEALTH_STATE.DEGRADED : this.status === WORKER_STATUS.CONNECTING && this.recoveryAttempt === 0 ? HEALTH_STATE.STARTING : HEALTH_STATE.RECOVERING : HEALTH_STATE.HEALTHY;
2795
2810
  return {
2796
2811
  healthy: state === HEALTH_STATE.HEALTHY,
2797
2812
  state,
@@ -2862,7 +2877,7 @@ var CrossTabDataBus = class {
2862
2877
  this.canceledQueuedStartToken = this.queuedStartToken;
2863
2878
  this.queuedStart = null;
2864
2879
  }
2865
- if (this.stopPromise) return this.stopPromise;
2880
+ if (this.stopPromise && this.stopping) return this.stopPromise;
2866
2881
  if (!this.started && !this.startPromise && !this.pendingStop && !this.transportReady) {
2867
2882
  return Promise.resolve();
2868
2883
  }
@@ -2958,7 +2973,7 @@ var CrossTabDataBus = class {
2958
2973
  * Propagate a status change to the cluster, trace, and all registered
2959
2974
  * status handlers. On reconnect, re-subscribe any topics assigned to us.
2960
2975
  */
2961
- updateStatus(status) {
2976
+ updateStatus(status, notifyHandlers = true) {
2962
2977
  const previousStatus = this.status;
2963
2978
  this.status = status;
2964
2979
  if (status === WORKER_STATUS.CONNECTED) this.transportHasConnected = true;
@@ -3010,9 +3025,9 @@ var CrossTabDataBus = class {
3010
3025
  } else if (status === WORKER_STATUS.ERROR) {
3011
3026
  this.releaseRecoveryGate();
3012
3027
  }
3013
- this.invokeHandlers(this.statusHandlers, (handler) => handler(status));
3028
+ if (notifyHandlers) this.invokeHandlers(this.statusHandlers, (handler) => handler(status));
3014
3029
  }
3015
- reportError(error, source = FAILURE_SOURCE.TRANSPORT) {
3030
+ recordError(error, source = FAILURE_SOURCE.TRANSPORT) {
3016
3031
  const at = this.now();
3017
3032
  if (source === FAILURE_SOURCE.TRANSPORT) {
3018
3033
  this.lastError = error;
@@ -3032,8 +3047,14 @@ var CrossTabDataBus = class {
3032
3047
  type: TRACE_EVENT_TYPE.ERROR,
3033
3048
  source: source === FAILURE_SOURCE.TRANSPORT ? TRACE_ERROR_SOURCE.TRANSPORT : TRACE_ERROR_SOURCE.OPERATION
3034
3049
  });
3050
+ }
3051
+ notifyError(error) {
3035
3052
  this.invokeHandlers(this.errorHandlers, (handler) => handler(error), INVOKE_LABEL.ERROR_HANDLER);
3036
3053
  }
3054
+ reportError(error, source = FAILURE_SOURCE.TRANSPORT) {
3055
+ this.recordError(error, source);
3056
+ this.notifyError(error);
3057
+ }
3037
3058
  /** Report a persistence failure to the trace and the unified failure ledger,
3038
3059
  * unless it is a {@link PersistenceRetryCancelledError} cancellation from a
3039
3060
  * lifecycle transition (teardown should stay quiet). */
@@ -3096,6 +3117,15 @@ var CrossTabDataBus = class {
3096
3117
  }
3097
3118
  }
3098
3119
  }
3120
+ /** Resume the resources paused by a pagehide suspension. Both the native
3121
+ * pageshow path and explicit start() must run this so an explicit resume
3122
+ * cannot leave trace metrics and periodic cleanup timers permanently off. */
3123
+ resumeSuspendedResources() {
3124
+ this.trace.start();
3125
+ this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.RESUME });
3126
+ this.startDedupSweep();
3127
+ this.replayManager.start();
3128
+ }
3099
3129
  /**
3100
3130
  * Suspend the transport when the tab goes hidden. Stops the transport and
3101
3131
  * clears subscription state so it will be re-established on resume.
@@ -3108,8 +3138,11 @@ var CrossTabDataBus = class {
3108
3138
  this.transportReady = false;
3109
3139
  this.transportSubscribedTopics.clear();
3110
3140
  this.updateStatus(WORKER_STATUS.DISCONNECTED);
3111
- if (this.pendingStop) return;
3112
- const pending = this.startPromise ?? Promise.resolve();
3141
+ if (this.pendingStop && (this.startPromise === null || this.startPromise === this.pendingStop)) {
3142
+ this.startPromise = this.pendingStop;
3143
+ return;
3144
+ }
3145
+ const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();
3113
3146
  const stopping = pending.catch(() => void 0).then(() => this.transport.stop()).catch((error) => this.reportError(error));
3114
3147
  this.startPromise = stopping;
3115
3148
  this.pendingStop = stopping;
@@ -3204,10 +3237,17 @@ var CrossTabDataBus = class {
3204
3237
  ready = this.reopenTransport();
3205
3238
  }
3206
3239
  if (!ready || this.stopping) return;
3207
- void ready.then(() => {
3208
- if (!this.started || this.stopping || this.suspended) return;
3209
- return operation();
3210
- }).catch((error) => this.reportError(error));
3240
+ void ready.then(
3241
+ () => {
3242
+ if (!this.started || this.stopping || this.suspended) return;
3243
+ return operation();
3244
+ },
3245
+ // The opening promise reports its own lifecycle failure through
3246
+ // openTransport(). Swallowing it here prevents a stale startup
3247
+ // rejection from being recorded again after an onStatus/onError
3248
+ // callback has already started and reset the ledger for a retry.
3249
+ () => void 0
3250
+ ).catch((error) => this.reportError(error));
3211
3251
  }
3212
3252
  /**
3213
3253
  * Publications started after teardown begins cannot reach any transport.
@@ -3302,4 +3342,4 @@ export {
3302
3342
  parseDataBusPublication,
3303
3343
  selectWorkerBackend
3304
3344
  };
3305
- //# sourceMappingURL=chunk-ZNHJ5OMY.js.map
3345
+ //# sourceMappingURL=chunk-77BQELW4.js.map