cross-tab-worker-databus 0.20.91 → 0.20.93
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.
- package/CHANGELOG.md +38 -1
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-DMM5KBNG.js → chunk-OXEPZGXR.js} +273 -90
- package/dist/chunk-OXEPZGXR.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +272 -89
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +272 -89
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +8 -5
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +21 -1
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/environment.d.ts.map +1 -1
- package/dist/core/replay-manager.d.ts +17 -3
- package/dist/core/replay-manager.d.ts.map +1 -1
- package/dist/core/storage-batch.d.ts +4 -0
- package/dist/core/storage-batch.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/architecture.md +9 -6
- package/docs/benchmarks.md +8 -8
- package/docs/capabilities.md +3 -1
- package/docs/release-checklist.md +2 -2
- package/docs/roadmap.md +17 -1
- package/docs/zh/architecture.md +9 -6
- package/docs/zh/benchmarks.md +8 -8
- package/docs/zh/capabilities.md +3 -1
- package/docs/zh/release-checklist.md +2 -2
- package/docs/zh/roadmap.md +17 -1
- package/package.json +5 -5
- package/dist/chunk-DMM5KBNG.js.map +0 -7
|
@@ -85,6 +85,7 @@ function createStorageEventChannel(options) {
|
|
|
85
85
|
};
|
|
86
86
|
}
|
|
87
87
|
var tabIdentityInitialized = false;
|
|
88
|
+
var cachedTabId = null;
|
|
88
89
|
function createBrowserEnvironment(options) {
|
|
89
90
|
const channelFallback = options?.channelFallback ?? CHANNEL_FALLBACK.NONE;
|
|
90
91
|
return {
|
|
@@ -130,8 +131,9 @@ function canUseStorage(storage, probeKey) {
|
|
|
130
131
|
if (!storage) return false;
|
|
131
132
|
try {
|
|
132
133
|
storage.setItem(probeKey, "1");
|
|
134
|
+
const readable = storage.getItem(probeKey) === "1";
|
|
133
135
|
storage.removeItem(probeKey);
|
|
134
|
-
return
|
|
136
|
+
return readable;
|
|
135
137
|
} catch {
|
|
136
138
|
return false;
|
|
137
139
|
}
|
|
@@ -143,14 +145,21 @@ function getOrCreateTabId(environment, key = TAB_ID_STORAGE_KEY) {
|
|
|
143
145
|
const hasOpener = typeof window !== "undefined" && Boolean(window.opener);
|
|
144
146
|
if (existing && (!hasOpener || tabIdentityInitialized)) {
|
|
145
147
|
tabIdentityInitialized = true;
|
|
148
|
+
cachedTabId = existing;
|
|
146
149
|
return existing;
|
|
147
150
|
}
|
|
151
|
+
if (tabIdentityInitialized && cachedTabId) return cachedTabId;
|
|
148
152
|
const created = `tab-${environment.randomId()}`;
|
|
149
153
|
storage?.setItem(key, created);
|
|
150
154
|
tabIdentityInitialized = true;
|
|
155
|
+
cachedTabId = created;
|
|
151
156
|
return created;
|
|
152
157
|
} catch {
|
|
153
|
-
|
|
158
|
+
if (cachedTabId) return cachedTabId;
|
|
159
|
+
const created = `tab-${environment.randomId()}`;
|
|
160
|
+
tabIdentityInitialized = true;
|
|
161
|
+
cachedTabId = created;
|
|
162
|
+
return created;
|
|
154
163
|
}
|
|
155
164
|
}
|
|
156
165
|
|
|
@@ -428,7 +437,17 @@ var BatchingStorageWriter = class {
|
|
|
428
437
|
clear() {
|
|
429
438
|
this.pending.clear();
|
|
430
439
|
this.flushScheduled = false;
|
|
440
|
+
this.cancelRetry();
|
|
441
|
+
this.retryCount.clear();
|
|
442
|
+
this.retryDelayMs = INITIAL_RETRY_DELAY_MS;
|
|
431
443
|
this.storage.clear();
|
|
444
|
+
}
|
|
445
|
+
/** Drop queued mutations and cancel retry state without clearing storage.
|
|
446
|
+
* Used when the owning runtime is torn down: final best-effort writes have
|
|
447
|
+
* already been flushed, and failed writes must not keep timers alive. */
|
|
448
|
+
discardPending() {
|
|
449
|
+
this.pending.clear();
|
|
450
|
+
this.flushScheduled = false;
|
|
432
451
|
this.cancelRetry();
|
|
433
452
|
this.retryCount.clear();
|
|
434
453
|
this.retryDelayMs = INITIAL_RETRY_DELAY_MS;
|
|
@@ -612,6 +631,9 @@ var WorkerClusterRuntime = class {
|
|
|
612
631
|
started = false;
|
|
613
632
|
suspended = false;
|
|
614
633
|
lifecycleListening = false;
|
|
634
|
+
/** Invalidates an in-flight pageshow resume when a synchronous onResume
|
|
635
|
+
* callback stops or pauses the cluster before activate() is reached. */
|
|
636
|
+
lifecycleGeneration = 0;
|
|
615
637
|
currentRecord;
|
|
616
638
|
constructor(options) {
|
|
617
639
|
assertClusterOptions(options);
|
|
@@ -648,8 +670,14 @@ var WorkerClusterRuntime = class {
|
|
|
648
670
|
/** Start the cluster: register, listen for lifecycle events, and begin heartbeats. */
|
|
649
671
|
start() {
|
|
650
672
|
if (this.started) return;
|
|
651
|
-
this.
|
|
673
|
+
this.lifecycleGeneration += 1;
|
|
652
674
|
this.addLifecycleListeners();
|
|
675
|
+
if (this.environment.getVisibilityState() === TAB_VISIBILITY.HIDDEN) {
|
|
676
|
+
this.suspended = true;
|
|
677
|
+
this.handlers.onSuspend?.();
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.suspended = false;
|
|
653
681
|
this.activate();
|
|
654
682
|
}
|
|
655
683
|
/**
|
|
@@ -660,6 +688,7 @@ var WorkerClusterRuntime = class {
|
|
|
660
688
|
* stop() path where callers expect every Set/Map to be empty afterwards.
|
|
661
689
|
*/
|
|
662
690
|
stop() {
|
|
691
|
+
this.lifecycleGeneration += 1;
|
|
663
692
|
if (!this.started && !this.suspended) return;
|
|
664
693
|
this.pause();
|
|
665
694
|
this.flushStorage();
|
|
@@ -678,7 +707,15 @@ var WorkerClusterRuntime = class {
|
|
|
678
707
|
activate() {
|
|
679
708
|
if (this.started) return;
|
|
680
709
|
this.started = true;
|
|
681
|
-
|
|
710
|
+
if (this.storage) {
|
|
711
|
+
try {
|
|
712
|
+
this.channel = this.environment.createChannel(this.channelName);
|
|
713
|
+
} catch {
|
|
714
|
+
this.channel = null;
|
|
715
|
+
}
|
|
716
|
+
} else {
|
|
717
|
+
this.channel = null;
|
|
718
|
+
}
|
|
682
719
|
if (!this.channel) this.storage = null;
|
|
683
720
|
this.channel?.addEventListener("message", this.handleMessage);
|
|
684
721
|
const now = this.environment.now();
|
|
@@ -706,7 +743,11 @@ var WorkerClusterRuntime = class {
|
|
|
706
743
|
* to other workers, remove our worker record, and close the channel.
|
|
707
744
|
*/
|
|
708
745
|
pause() {
|
|
709
|
-
|
|
746
|
+
this.lifecycleGeneration += 1;
|
|
747
|
+
if (!this.started) {
|
|
748
|
+
if (this.lifecycleListening) this.suspended = true;
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
710
751
|
this.started = false;
|
|
711
752
|
this.suspended = true;
|
|
712
753
|
if (this.heartbeatHandle !== null) this.environment.clearInterval(this.heartbeatHandle);
|
|
@@ -720,6 +761,7 @@ var WorkerClusterRuntime = class {
|
|
|
720
761
|
this.removeStorage(this.workerStorageKey(this.workerId));
|
|
721
762
|
this.flushStorage();
|
|
722
763
|
this.notifyRegistry();
|
|
764
|
+
if (this.storage instanceof BatchingStorageWriter) this.storage.discardPending();
|
|
723
765
|
const channel = this.channel;
|
|
724
766
|
this.channel = null;
|
|
725
767
|
this.handlers.onSuspend?.();
|
|
@@ -1021,8 +1063,10 @@ var WorkerClusterRuntime = class {
|
|
|
1021
1063
|
handlePageHide = () => this.pause();
|
|
1022
1064
|
handlePageShow = () => {
|
|
1023
1065
|
if (!this.suspended) return;
|
|
1066
|
+
const generation = ++this.lifecycleGeneration;
|
|
1024
1067
|
this.suspended = false;
|
|
1025
1068
|
this.handlers.onResume?.();
|
|
1069
|
+
if (generation !== this.lifecycleGeneration) return;
|
|
1026
1070
|
this.activate();
|
|
1027
1071
|
};
|
|
1028
1072
|
handleVisibilityChange = () => {
|
|
@@ -1077,10 +1121,16 @@ var WorkerClusterRuntime = class {
|
|
|
1077
1121
|
if (message.targetWorkerId !== this.workerId) return;
|
|
1078
1122
|
this.rememberTopic(message.topic);
|
|
1079
1123
|
switch (message.action) {
|
|
1080
|
-
case CONTROL_ACTION.SUBSCRIBE:
|
|
1124
|
+
case CONTROL_ACTION.SUBSCRIBE: {
|
|
1125
|
+
const route = this.readRoute(message.topicKey);
|
|
1126
|
+
if (route && route.workerId !== this.workerId) return;
|
|
1127
|
+
if (route?.workerId === this.workerId && route.handoffFromWorkerId !== void 0 && route.confirmedAt === void 0) {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1081
1130
|
this.assignedTopics.set(message.topicKey, message.topic);
|
|
1082
1131
|
this.confirmRoute(message.topicKey);
|
|
1083
1132
|
break;
|
|
1133
|
+
}
|
|
1084
1134
|
case CONTROL_ACTION.UNSUBSCRIBE:
|
|
1085
1135
|
if (this.releaseHandoffOnUnsubscribe(message)) return;
|
|
1086
1136
|
break;
|
|
@@ -1141,8 +1191,8 @@ var WorkerClusterRuntime = class {
|
|
|
1141
1191
|
}
|
|
1142
1192
|
/**
|
|
1143
1193
|
* Accept a graceful handoff only when the route still points to this worker,
|
|
1144
|
-
* the release comes from the recorded previous owner, and the generation
|
|
1145
|
-
*
|
|
1194
|
+
* the release comes from the recorded previous owner, and the generation
|
|
1195
|
+
* exactly matches ours. Any other ROUTE_RELEASED is stale and dropped.
|
|
1146
1196
|
*/
|
|
1147
1197
|
handleRouteReleasedMessage(message) {
|
|
1148
1198
|
if (message.targetWorkerId !== this.workerId) return;
|
|
@@ -1155,11 +1205,11 @@ var WorkerClusterRuntime = class {
|
|
|
1155
1205
|
}
|
|
1156
1206
|
/** A ROUTE_RELEASED is stale (and must be dropped) unless the route still
|
|
1157
1207
|
* points to us, the release comes from the recorded previous owner, and
|
|
1158
|
-
* the release generation
|
|
1159
|
-
* earlier handoff round
|
|
1160
|
-
*
|
|
1208
|
+
* the release generation exactly matches ours. A delayed ACK from either an
|
|
1209
|
+
* earlier or later handoff round belongs to a different route and must not
|
|
1210
|
+
* confirm the current round. */
|
|
1161
1211
|
isStaleRouteRelease(route, message) {
|
|
1162
|
-
return route.workerId !== this.workerId || route.handoffFromWorkerId !== message.sourceWorkerId || message.generation
|
|
1212
|
+
return route.workerId !== this.workerId || route.handoffFromWorkerId !== message.sourceWorkerId || message.generation !== route.generation;
|
|
1163
1213
|
}
|
|
1164
1214
|
/** True when an unconfirmed handoff route has been stuck longer than a
|
|
1165
1215
|
* worker TTL. The ACK for a live handoff is posted synchronously with the
|
|
@@ -1786,7 +1836,7 @@ var ReplayManager = class {
|
|
|
1786
1836
|
this.trace = deps.trace;
|
|
1787
1837
|
this.onPersistenceError = deps.onPersistenceError;
|
|
1788
1838
|
this.onDispatchError = deps.onDispatchError;
|
|
1789
|
-
|
|
1839
|
+
void this.requestHydration();
|
|
1790
1840
|
}
|
|
1791
1841
|
deps;
|
|
1792
1842
|
buffers;
|
|
@@ -1805,7 +1855,18 @@ var ReplayManager = class {
|
|
|
1805
1855
|
retryGeneration = 0;
|
|
1806
1856
|
pendingReplayPersistence = [];
|
|
1807
1857
|
persistenceFlushScheduled = false;
|
|
1808
|
-
hydration
|
|
1858
|
+
/** Current hydration operation, if one belongs to the active lifecycle. */
|
|
1859
|
+
hydration = null;
|
|
1860
|
+
/** Invalidates a load from a superseded suspend/reset lifecycle. */
|
|
1861
|
+
hydrationEpoch = 0;
|
|
1862
|
+
/** Whether the active lifecycle has finished (or deliberately skipped) hydration. */
|
|
1863
|
+
hydrationComplete = false;
|
|
1864
|
+
/** A failed load is retried by the next explicit start(), not every replay request. */
|
|
1865
|
+
hydrationFailed = false;
|
|
1866
|
+
/** Mutations that must be applied to a load already in flight. */
|
|
1867
|
+
hydrationClearAll = false;
|
|
1868
|
+
hydrationClearedTopics = /* @__PURE__ */ new Set();
|
|
1869
|
+
hydrationClearBefore = null;
|
|
1809
1870
|
/** Coalesced retention cleanup: the newest cutoff wins while one is running. */
|
|
1810
1871
|
retentionCleanup = null;
|
|
1811
1872
|
retentionCutoff = null;
|
|
@@ -1859,7 +1920,7 @@ var ReplayManager = class {
|
|
|
1859
1920
|
if (!this.buffers) return;
|
|
1860
1921
|
const limit = typeof replayOption === "number" ? Math.min(Math.floor(replayOption), this.maxPerTopic) : this.maxPerTopic;
|
|
1861
1922
|
if (this.persistence) {
|
|
1862
|
-
void this.
|
|
1923
|
+
void this.requestHydration().then(() => {
|
|
1863
1924
|
if (isHandlerActive?.() ?? true) this.deliver(topic, limit, handler);
|
|
1864
1925
|
});
|
|
1865
1926
|
return;
|
|
@@ -1871,22 +1932,24 @@ var ReplayManager = class {
|
|
|
1871
1932
|
* clearTopic), and prune durable history. */
|
|
1872
1933
|
onTopicUnsubscribed(topic) {
|
|
1873
1934
|
if (!this.buffers) return;
|
|
1935
|
+
const clearing = this.persistence?.clearTopic ? this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence.clearTopic(topic)) : null;
|
|
1936
|
+
this.hydrationClearedTopics.add(topic);
|
|
1874
1937
|
this.buffers.delete(topic);
|
|
1875
1938
|
this.pendingReplayPersistence = this.pendingReplayPersistence.filter((message) => message.topic !== topic);
|
|
1876
|
-
if (this.
|
|
1877
|
-
void this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence.clearTopic(topic)).catch((error) => this.onPersistenceError(error));
|
|
1878
|
-
}
|
|
1939
|
+
if (clearing) void clearing.catch((error) => this.onPersistenceError(error));
|
|
1879
1940
|
}
|
|
1880
1941
|
/** Clear all in-memory replay buffers and, when supported, durable history.
|
|
1881
1942
|
* Reports persistence failures and rethrows, mirroring the public API
|
|
1882
1943
|
* contract that callers can observe a failed clear. */
|
|
1883
1944
|
async clearAll() {
|
|
1884
1945
|
if (!this.buffers) return;
|
|
1946
|
+
const clearing = this.persistence?.clear ? this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR, () => this.persistence.clear()) : null;
|
|
1947
|
+
this.hydrationClearAll = true;
|
|
1885
1948
|
this.buffers.clear();
|
|
1886
1949
|
this.pendingReplayPersistence = [];
|
|
1887
|
-
if (
|
|
1950
|
+
if (clearing) {
|
|
1888
1951
|
try {
|
|
1889
|
-
await
|
|
1952
|
+
await clearing;
|
|
1890
1953
|
} catch (error) {
|
|
1891
1954
|
this.onPersistenceError(error);
|
|
1892
1955
|
throw error;
|
|
@@ -1896,11 +1959,13 @@ var ReplayManager = class {
|
|
|
1896
1959
|
/** Clear replay history for one exact topic, including durable storage. */
|
|
1897
1960
|
async clearTopic(topic) {
|
|
1898
1961
|
if (!this.buffers) return;
|
|
1962
|
+
const clearing = this.persistence?.clearTopic ? this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_TOPIC, () => this.persistence.clearTopic(topic)) : null;
|
|
1963
|
+
this.hydrationClearedTopics.add(topic);
|
|
1899
1964
|
this.buffers.delete(topic);
|
|
1900
1965
|
this.pendingReplayPersistence = this.pendingReplayPersistence.filter((message) => message.topic !== topic);
|
|
1901
|
-
if (
|
|
1966
|
+
if (clearing) {
|
|
1902
1967
|
try {
|
|
1903
|
-
await
|
|
1968
|
+
await clearing;
|
|
1904
1969
|
} catch (error) {
|
|
1905
1970
|
this.onPersistenceError(error);
|
|
1906
1971
|
throw error;
|
|
@@ -1910,7 +1975,9 @@ var ReplayManager = class {
|
|
|
1910
1975
|
/** Remove replay entries older than an epoch-millisecond cutoff. */
|
|
1911
1976
|
async clearBefore(timestamp) {
|
|
1912
1977
|
if (!Number.isFinite(timestamp)) throw new TypeError("timestamp must be finite.");
|
|
1978
|
+
const clearing = this.persistence?.clearBefore ? this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_BEFORE, () => this.persistence.clearBefore(timestamp)) : null;
|
|
1913
1979
|
if (this.buffers) {
|
|
1980
|
+
this.hydrationClearBefore = this.hydrationClearBefore === null ? timestamp : Math.max(this.hydrationClearBefore, timestamp);
|
|
1914
1981
|
for (const [topic, messages] of this.buffers) {
|
|
1915
1982
|
const kept = messages.filter((message) => message.timestamp === void 0 || message.timestamp >= timestamp);
|
|
1916
1983
|
if (kept.length) this.buffers.set(topic, kept);
|
|
@@ -1920,9 +1987,9 @@ var ReplayManager = class {
|
|
|
1920
1987
|
this.pendingReplayPersistence = this.pendingReplayPersistence.filter(
|
|
1921
1988
|
(message) => message.timestamp === void 0 || message.timestamp >= timestamp
|
|
1922
1989
|
);
|
|
1923
|
-
if (
|
|
1990
|
+
if (clearing) {
|
|
1924
1991
|
try {
|
|
1925
|
-
await
|
|
1992
|
+
await clearing;
|
|
1926
1993
|
} catch (error) {
|
|
1927
1994
|
this.onPersistenceError(error);
|
|
1928
1995
|
throw error;
|
|
@@ -1932,6 +1999,11 @@ var ReplayManager = class {
|
|
|
1932
1999
|
/** Start the periodic retention sweep. No-op when no durable retention
|
|
1933
2000
|
* config makes it necessary. */
|
|
1934
2001
|
start() {
|
|
2002
|
+
if (this.hydrationFailed) {
|
|
2003
|
+
this.hydrationComplete = false;
|
|
2004
|
+
this.hydrationFailed = false;
|
|
2005
|
+
}
|
|
2006
|
+
void this.requestHydration();
|
|
1935
2007
|
if (this.retentionTimer || !this.retentionMs || !this.retentionSweepMs || !this.persistence?.clearBefore) return;
|
|
1936
2008
|
this.retentionTimer = setInterval(() => {
|
|
1937
2009
|
this.scheduleRetentionCleanup(this.now() - this.retentionMs);
|
|
@@ -1946,13 +2018,25 @@ var ReplayManager = class {
|
|
|
1946
2018
|
* or stopped bus does not keep hammering the store) and stop the sweep. */
|
|
1947
2019
|
suspend() {
|
|
1948
2020
|
this.retryGeneration += 1;
|
|
2021
|
+
if (this.hydration) {
|
|
2022
|
+
this.hydrationEpoch += 1;
|
|
2023
|
+
this.hydration = null;
|
|
2024
|
+
this.hydrationComplete = false;
|
|
2025
|
+
}
|
|
1949
2026
|
this.pendingReplayPersistence = [];
|
|
1950
2027
|
this.retentionCutoff = null;
|
|
1951
2028
|
this.stop();
|
|
1952
2029
|
}
|
|
1953
|
-
/** Drop all in-memory buffers
|
|
2030
|
+
/** Drop all in-memory buffers and require hydration for the next lifecycle. */
|
|
1954
2031
|
resetBuffers() {
|
|
1955
2032
|
this.buffers?.clear();
|
|
2033
|
+
this.hydrationEpoch += 1;
|
|
2034
|
+
this.hydration = null;
|
|
2035
|
+
this.hydrationComplete = false;
|
|
2036
|
+
this.hydrationFailed = false;
|
|
2037
|
+
this.hydrationClearAll = false;
|
|
2038
|
+
this.hydrationClearedTopics.clear();
|
|
2039
|
+
this.hydrationClearBefore = null;
|
|
1956
2040
|
}
|
|
1957
2041
|
/** Buffer occupancy for diagnostics. `bytes` is an approximate in-memory
|
|
1958
2042
|
* payload footprint (same heuristic as adaptive load weighting), computed on
|
|
@@ -2004,38 +2088,78 @@ var ReplayManager = class {
|
|
|
2004
2088
|
void this.withPersistenceRetry(PERSISTENCE_OPERATION.APPEND, () => this.persistence.appendBatch(batch)).catch((error) => this.onPersistenceError(error));
|
|
2005
2089
|
});
|
|
2006
2090
|
}
|
|
2007
|
-
/**
|
|
2091
|
+
/** Start the active lifecycle's one-shot hydration, if it has not completed
|
|
2092
|
+
* or deliberately skipped hydration already. */
|
|
2093
|
+
requestHydration() {
|
|
2094
|
+
if (!this.buffers || !this.persistence) {
|
|
2095
|
+
this.hydrationComplete = true;
|
|
2096
|
+
return Promise.resolve();
|
|
2097
|
+
}
|
|
2098
|
+
if (this.hydration) return this.hydration;
|
|
2099
|
+
if (this.hydrationComplete) return Promise.resolve();
|
|
2100
|
+
const epoch = this.hydrationEpoch;
|
|
2101
|
+
const generation = this.retryGeneration;
|
|
2102
|
+
const operation = this.hydrate(epoch, generation);
|
|
2103
|
+
this.hydration = operation;
|
|
2104
|
+
return operation;
|
|
2105
|
+
}
|
|
2106
|
+
/** Load durable history into the in-memory rings once per lifecycle, pruning
|
|
2008
2107
|
* entries past the retention window first. Failures are reported but do not
|
|
2009
2108
|
* block startup — the bus runs with whatever survived. */
|
|
2010
|
-
async hydrate() {
|
|
2109
|
+
async hydrate(epoch, generation) {
|
|
2011
2110
|
if (!this.buffers || !this.persistence) {
|
|
2012
2111
|
return;
|
|
2013
2112
|
}
|
|
2014
2113
|
try {
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
if (
|
|
2022
|
-
|
|
2023
|
-
|
|
2114
|
+
try {
|
|
2115
|
+
if (this.retentionMs !== void 0 && this.persistence.clearBefore) {
|
|
2116
|
+
await this.withPersistenceRetry(PERSISTENCE_OPERATION.CLEAR_BEFORE, () => this.persistence.clearBefore(this.now() - this.retentionMs));
|
|
2117
|
+
}
|
|
2118
|
+
const loaded = await this.withPersistenceRetry(PERSISTENCE_OPERATION.LOAD, () => this.persistence.load());
|
|
2119
|
+
if (generation !== this.retryGeneration) throw new PersistenceRetryCancelledError();
|
|
2120
|
+
if (epoch !== this.hydrationEpoch) return;
|
|
2121
|
+
const loadedByTopic = /* @__PURE__ */ new Map();
|
|
2122
|
+
for (const message of loaded) {
|
|
2123
|
+
if (this.hydrationClearAll || this.hydrationClearedTopics.has(message.topic) || this.hydrationClearBefore !== null && message.timestamp !== void 0 && message.timestamp < this.hydrationClearBefore) continue;
|
|
2124
|
+
let buffer = loadedByTopic.get(message.topic);
|
|
2125
|
+
if (!buffer) {
|
|
2126
|
+
buffer = [];
|
|
2127
|
+
loadedByTopic.set(message.topic, buffer);
|
|
2128
|
+
}
|
|
2129
|
+
buffer.push(message);
|
|
2130
|
+
}
|
|
2131
|
+
for (const [topic, durableBuffer] of loadedByTopic) {
|
|
2132
|
+
const liveBuffer = this.buffers.get(topic);
|
|
2133
|
+
this.buffers.set(topic, liveBuffer ? [...durableBuffer, ...liveBuffer] : durableBuffer);
|
|
2134
|
+
}
|
|
2135
|
+
const hydrationNow = this.now();
|
|
2136
|
+
for (const [topic, buffer] of this.buffers) {
|
|
2137
|
+
const pruned = pruneReplayHistory(buffer, {
|
|
2138
|
+
maxPerTopic: this.maxPerTopic,
|
|
2139
|
+
pruneStrategy: this.pruneStrategy,
|
|
2140
|
+
retentionMs: this.retentionMs,
|
|
2141
|
+
now: hydrationNow
|
|
2142
|
+
});
|
|
2143
|
+
if (pruned !== buffer) this.buffers.set(topic, pruned);
|
|
2144
|
+
}
|
|
2145
|
+
this.hydrationComplete = true;
|
|
2146
|
+
this.hydrationFailed = false;
|
|
2147
|
+
} catch (error) {
|
|
2148
|
+
if (generation !== this.retryGeneration) {
|
|
2149
|
+
this.onPersistenceError(
|
|
2150
|
+
error instanceof PersistenceRetryCancelledError ? error : new PersistenceRetryCancelledError()
|
|
2151
|
+
);
|
|
2152
|
+
return;
|
|
2024
2153
|
}
|
|
2025
|
-
|
|
2154
|
+
if (epoch !== this.hydrationEpoch) return;
|
|
2155
|
+
this.onPersistenceError(error);
|
|
2156
|
+
this.hydrationComplete = true;
|
|
2157
|
+
this.hydrationFailed = true;
|
|
2026
2158
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
maxPerTopic: this.maxPerTopic,
|
|
2031
|
-
pruneStrategy: this.pruneStrategy,
|
|
2032
|
-
retentionMs: this.retentionMs,
|
|
2033
|
-
now: hydrationNow
|
|
2034
|
-
});
|
|
2035
|
-
if (pruned !== buffer) this.buffers.set(topic, pruned);
|
|
2159
|
+
} finally {
|
|
2160
|
+
if (this.hydrationEpoch === epoch && this.retryGeneration === generation) {
|
|
2161
|
+
this.hydration = null;
|
|
2036
2162
|
}
|
|
2037
|
-
} catch (error) {
|
|
2038
|
-
this.onPersistenceError(error);
|
|
2039
2163
|
}
|
|
2040
2164
|
}
|
|
2041
2165
|
/** Coalesce retention cleanup: the newest cutoff wins while one pass runs,
|
|
@@ -2059,7 +2183,7 @@ var ReplayManager = class {
|
|
|
2059
2183
|
}
|
|
2060
2184
|
})().finally(() => {
|
|
2061
2185
|
this.retentionCleanup = null;
|
|
2062
|
-
if (this.retentionCutoff !== null
|
|
2186
|
+
if (this.retentionCutoff !== null) {
|
|
2063
2187
|
this.scheduleRetentionCleanup(this.retentionCutoff);
|
|
2064
2188
|
}
|
|
2065
2189
|
});
|
|
@@ -2212,7 +2336,7 @@ var DedupManager = class {
|
|
|
2212
2336
|
};
|
|
2213
2337
|
|
|
2214
2338
|
// src/core/version.ts
|
|
2215
|
-
var SDK_VERSION = true ? "0.20.
|
|
2339
|
+
var SDK_VERSION = true ? "0.20.93" : "";
|
|
2216
2340
|
|
|
2217
2341
|
// src/core/data-bus.ts
|
|
2218
2342
|
var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
|
|
@@ -2288,11 +2412,21 @@ var CrossTabDataBus = class {
|
|
|
2288
2412
|
recoveryGateRelease = null;
|
|
2289
2413
|
recoveryTimer = null;
|
|
2290
2414
|
recoveryTimerToken = 0;
|
|
2415
|
+
// Invalidates operations parked on the recovery gate when a hide/stop
|
|
2416
|
+
// supersedes the recovery cycle. Their microtask may run after an immediate
|
|
2417
|
+
// explicit start has cleared `suspended`, so a state check alone is not
|
|
2418
|
+
// enough to keep stale work from reaching the replacement transport.
|
|
2419
|
+
recoveryCancellationToken = 0;
|
|
2291
2420
|
// Once an automatic attempt fails, an explicit transport operation may
|
|
2292
2421
|
// recover immediately instead of waiting for the next paced attempt. The
|
|
2293
2422
|
// gate still stays closed so the operation cannot reach the failed
|
|
2294
2423
|
// transport; it is released by the successful on-demand reopen.
|
|
2295
2424
|
recoveryDemandAllowed = false;
|
|
2425
|
+
// Number of transport operations currently parked behind `recoveryGate`.
|
|
2426
|
+
// When an automatic attempt fails, these already-parked operations are
|
|
2427
|
+
// themselves demand: the failure path starts an on-demand reopen instead of
|
|
2428
|
+
// stranding them until some unrelated future operation arrives.
|
|
2429
|
+
recoveryWaiters = 0;
|
|
2296
2430
|
/** Monotonic generation incremented on every successful transport open.
|
|
2297
2431
|
* Stays in lockstep with `lastSuccessAt` so callers can detect that the
|
|
2298
2432
|
* transport has been reopened even if the timestamp window is short. */
|
|
@@ -2407,7 +2541,7 @@ var CrossTabDataBus = class {
|
|
|
2407
2541
|
this.suspendTransport();
|
|
2408
2542
|
},
|
|
2409
2543
|
onResume: () => {
|
|
2410
|
-
this.resumeSuspendedResources();
|
|
2544
|
+
if (!this.resumeSuspendedResources()) return;
|
|
2411
2545
|
this.resumeTransport();
|
|
2412
2546
|
},
|
|
2413
2547
|
onDiagnostic: (event) => {
|
|
@@ -2436,11 +2570,13 @@ var CrossTabDataBus = class {
|
|
|
2436
2570
|
const transportDown = !this.transportReady || this.status === WORKER_STATUS.ERROR || this.status === WORKER_STATUS.DISCONNECTED;
|
|
2437
2571
|
if (!transportDown) return Promise.resolve();
|
|
2438
2572
|
this.activeConfig = config;
|
|
2439
|
-
this.resetFailureState();
|
|
2573
|
+
this.resetFailureState(true);
|
|
2440
2574
|
const resumingFromSuspend = this.suspended;
|
|
2441
|
-
if (resumingFromSuspend
|
|
2575
|
+
if (resumingFromSuspend && !this.resumeSuspendedResources()) {
|
|
2576
|
+
return this.stopPromise ?? Promise.resolve();
|
|
2577
|
+
}
|
|
2442
2578
|
const opening2 = this.reopenTransport();
|
|
2443
|
-
if (resumingFromSuspend) this.cluster.start();
|
|
2579
|
+
if (resumingFromSuspend && !this.stopping && !this.suspended) this.cluster.start();
|
|
2444
2580
|
return opening2;
|
|
2445
2581
|
}
|
|
2446
2582
|
this.started = true;
|
|
@@ -2448,12 +2584,6 @@ var CrossTabDataBus = class {
|
|
|
2448
2584
|
this.suspended = false;
|
|
2449
2585
|
this.activeConfig = config;
|
|
2450
2586
|
this.resetFailureState();
|
|
2451
|
-
this.trace.start();
|
|
2452
|
-
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });
|
|
2453
|
-
this.startDedupSweep();
|
|
2454
|
-
this.replayManager.start();
|
|
2455
|
-
this.updateStatus(WORKER_STATUS.CONNECTING);
|
|
2456
|
-
this.cluster.start();
|
|
2457
2587
|
const lifecycleEpoch = ++this.lifecycleEpoch;
|
|
2458
2588
|
const opening = this.openTransport(
|
|
2459
2589
|
config,
|
|
@@ -2462,7 +2592,17 @@ var CrossTabDataBus = class {
|
|
|
2462
2592
|
lifecycleEpoch
|
|
2463
2593
|
);
|
|
2464
2594
|
this.startPromise = opening;
|
|
2595
|
+
this.trace.start();
|
|
2596
|
+
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.START });
|
|
2597
|
+
if (lifecycleEpoch !== this.lifecycleEpoch || this.stopping) return opening;
|
|
2598
|
+
this.startDedupSweep();
|
|
2599
|
+
this.replayManager.start();
|
|
2600
|
+
this.updateStatus(WORKER_STATUS.CONNECTING);
|
|
2601
|
+
if (lifecycleEpoch !== this.lifecycleEpoch || this.stopping) return opening;
|
|
2602
|
+
this.cluster.start();
|
|
2603
|
+
if (lifecycleEpoch !== this.lifecycleEpoch || this.stopping) return opening;
|
|
2465
2604
|
for (const topic of this.topicHandlers.keys()) {
|
|
2605
|
+
if (lifecycleEpoch !== this.lifecycleEpoch || this.stopping) break;
|
|
2466
2606
|
this.cluster.subscribe(topic);
|
|
2467
2607
|
}
|
|
2468
2608
|
void opening.then(
|
|
@@ -2524,27 +2664,49 @@ var CrossTabDataBus = class {
|
|
|
2524
2664
|
/** Cancel a pending automatic retry when an explicit lifecycle transition
|
|
2525
2665
|
* supersedes it. The released gate re-enters runTransport(), which then
|
|
2526
2666
|
* follows the newest start/stop/suspend intent. */
|
|
2527
|
-
cancelScheduledRecovery() {
|
|
2667
|
+
cancelScheduledRecovery(invalidateParkedOperations = false, releaseGate = true) {
|
|
2528
2668
|
this.recoveryTimerToken += 1;
|
|
2669
|
+
if (invalidateParkedOperations) this.recoveryCancellationToken += 1;
|
|
2529
2670
|
if (this.recoveryTimer !== null) {
|
|
2530
2671
|
clearTimeout(this.recoveryTimer);
|
|
2531
2672
|
this.recoveryTimer = null;
|
|
2532
2673
|
}
|
|
2533
|
-
this.releaseRecoveryGate();
|
|
2674
|
+
if (releaseGate) this.releaseRecoveryGate();
|
|
2534
2675
|
}
|
|
2535
2676
|
/** Keep the recovery gate closed after a failed attempt while allowing the
|
|
2536
2677
|
* next explicit transport operation to start an immediate on-demand reopen.
|
|
2537
|
-
* If no gate/successor retry remains, release any waiters.
|
|
2538
|
-
|
|
2678
|
+
* If no gate/successor retry remains, release any waiters.
|
|
2679
|
+
*
|
|
2680
|
+
* `kickParkedWaiters` is set only when the failure is an automatic attempt: an
|
|
2681
|
+
* operation that was already parked on the gate is itself demand, so it must
|
|
2682
|
+
* not wait for some unrelated future operation. A failed *on-demand* reopen
|
|
2683
|
+
* passes `false`, so it re-arms the flag for a later operation instead of
|
|
2684
|
+
* looping on its own failure. */
|
|
2685
|
+
allowDemandRecovery(kickParkedWaiters = false) {
|
|
2539
2686
|
if (this.recoveryGate !== null && this.started && !this.stopping && !this.suspended && this.status === WORKER_STATUS.ERROR) {
|
|
2540
2687
|
this.recoveryDemandAllowed = true;
|
|
2688
|
+
if (kickParkedWaiters && this.recoveryWaiters > 0) this.startDemandRecovery();
|
|
2541
2689
|
return;
|
|
2542
2690
|
}
|
|
2543
2691
|
this.releaseRecoveryGate();
|
|
2544
2692
|
}
|
|
2693
|
+
/** Start one on-demand reopen if a failed attempt has left parked operations
|
|
2694
|
+
* and enabled demand recovery. Consumes the demand token so at most one
|
|
2695
|
+
* reopen is issued; every waiter stays behind the gate until it succeeds. */
|
|
2696
|
+
startDemandRecovery() {
|
|
2697
|
+
if (!this.recoveryDemandAllowed) return;
|
|
2698
|
+
if (this.status !== WORKER_STATUS.ERROR || this.suspended || this.stopping) return;
|
|
2699
|
+
this.recoveryDemandAllowed = false;
|
|
2700
|
+
const opening = this.reopenTransport();
|
|
2701
|
+
void opening.then(
|
|
2702
|
+
() => this.releaseRecoveryGate(),
|
|
2703
|
+
() => this.allowDemandRecovery()
|
|
2704
|
+
);
|
|
2705
|
+
}
|
|
2545
2706
|
/** Reset failure and recovery diagnostics for a new explicit start session. */
|
|
2546
|
-
resetFailureState() {
|
|
2547
|
-
this.cancelScheduledRecovery();
|
|
2707
|
+
resetFailureState(preserveRecoveryGate = false) {
|
|
2708
|
+
this.cancelScheduledRecovery(false, !preserveRecoveryGate);
|
|
2709
|
+
if (preserveRecoveryGate) this.recoveryDemandAllowed = false;
|
|
2548
2710
|
this.lastError = null;
|
|
2549
2711
|
this.lastErrorAt = null;
|
|
2550
2712
|
this.lastFailure = null;
|
|
@@ -2884,22 +3046,36 @@ var CrossTabDataBus = class {
|
|
|
2884
3046
|
if (!this.started && !this.startPromise && !this.pendingStop && !this.transportReady) {
|
|
2885
3047
|
return Promise.resolve();
|
|
2886
3048
|
}
|
|
3049
|
+
let resolveGate;
|
|
3050
|
+
const stopGate = new Promise((resolve) => {
|
|
3051
|
+
resolveGate = resolve;
|
|
3052
|
+
});
|
|
3053
|
+
this.stopPromise = stopGate;
|
|
3054
|
+
this.beginStop();
|
|
2887
3055
|
const stopPromise = this.performStop();
|
|
2888
|
-
this.stopPromise = stopPromise;
|
|
2889
3056
|
void stopPromise.then(
|
|
2890
3057
|
() => {
|
|
2891
|
-
if (this.stopPromise ===
|
|
3058
|
+
if (this.stopPromise === stopGate) this.stopPromise = null;
|
|
3059
|
+
resolveGate();
|
|
2892
3060
|
},
|
|
2893
3061
|
() => {
|
|
2894
|
-
if (this.stopPromise ===
|
|
3062
|
+
if (this.stopPromise === stopGate) this.stopPromise = null;
|
|
3063
|
+
resolveGate();
|
|
2895
3064
|
}
|
|
2896
3065
|
);
|
|
2897
|
-
return
|
|
3066
|
+
return stopGate;
|
|
2898
3067
|
}
|
|
2899
|
-
|
|
3068
|
+
/**
|
|
3069
|
+
* Synchronous teardown prelude. Flips `stopping` (the authoritative
|
|
3070
|
+
* in-flight signal), cancels scheduled work, releases handlers, and emits the
|
|
3071
|
+
* observable STOP lifecycle event. stop() calls it after the shared gate is
|
|
3072
|
+
* installed but in the same tick, so the stop still takes effect immediately
|
|
3073
|
+
* while a re-entrant stop() from the STOP event shares the one teardown.
|
|
3074
|
+
*/
|
|
3075
|
+
beginStop() {
|
|
2900
3076
|
this.lifecycleEpoch += 1;
|
|
2901
3077
|
this.stopping = true;
|
|
2902
|
-
this.cancelScheduledRecovery();
|
|
3078
|
+
this.cancelScheduledRecovery(true);
|
|
2903
3079
|
this.replayManager.suspend();
|
|
2904
3080
|
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.STOP });
|
|
2905
3081
|
this.trace.stop();
|
|
@@ -2907,6 +3083,8 @@ var CrossTabDataBus = class {
|
|
|
2907
3083
|
this.topicHandlers.clear();
|
|
2908
3084
|
this.replayManager.resetBuffers();
|
|
2909
3085
|
this.cluster.stop();
|
|
3086
|
+
}
|
|
3087
|
+
async performStop() {
|
|
2910
3088
|
try {
|
|
2911
3089
|
await this.startPromise?.catch(() => void 0);
|
|
2912
3090
|
const pendingStop = this.pendingStop;
|
|
@@ -2923,8 +3101,6 @@ var CrossTabDataBus = class {
|
|
|
2923
3101
|
this.transportReady = false;
|
|
2924
3102
|
this.startPromise = null;
|
|
2925
3103
|
this.pendingStop = null;
|
|
2926
|
-
this.lastError = null;
|
|
2927
|
-
this.lastErrorAt = null;
|
|
2928
3104
|
this.activeConfig = void 0;
|
|
2929
3105
|
this.recoveryAttempt = 0;
|
|
2930
3106
|
this.recoveryExhausted = false;
|
|
@@ -3021,7 +3197,9 @@ var CrossTabDataBus = class {
|
|
|
3021
3197
|
const opening = this.reopenTransport(attempt);
|
|
3022
3198
|
void opening.then(
|
|
3023
3199
|
() => this.releaseRecoveryGate(),
|
|
3024
|
-
|
|
3200
|
+
// Operations already parked on the gate are demand: run one
|
|
3201
|
+
// on-demand reopen now instead of waiting for an unrelated event.
|
|
3202
|
+
() => this.allowDemandRecovery(true)
|
|
3025
3203
|
);
|
|
3026
3204
|
}, this.recoveryCooldownMs);
|
|
3027
3205
|
}
|
|
@@ -3124,10 +3302,13 @@ var CrossTabDataBus = class {
|
|
|
3124
3302
|
* pageshow path and explicit start() must run this so an explicit resume
|
|
3125
3303
|
* cannot leave trace metrics and periodic cleanup timers permanently off. */
|
|
3126
3304
|
resumeSuspendedResources() {
|
|
3305
|
+
const lifecycleEpoch = this.lifecycleEpoch;
|
|
3127
3306
|
this.trace.start();
|
|
3128
3307
|
this.trace.event({ type: TRACE_EVENT_TYPE.LIFECYCLE, action: TRACE_LIFECYCLE_ACTION.RESUME });
|
|
3308
|
+
if (lifecycleEpoch !== this.lifecycleEpoch || this.stopping) return false;
|
|
3129
3309
|
this.startDedupSweep();
|
|
3130
3310
|
this.replayManager.start();
|
|
3311
|
+
return lifecycleEpoch === this.lifecycleEpoch && !this.stopping;
|
|
3131
3312
|
}
|
|
3132
3313
|
/**
|
|
3133
3314
|
* Suspend the transport when the tab goes hidden. Stops the transport and
|
|
@@ -3135,12 +3316,13 @@ var CrossTabDataBus = class {
|
|
|
3135
3316
|
*/
|
|
3136
3317
|
suspendTransport() {
|
|
3137
3318
|
if (this.stopping) return;
|
|
3138
|
-
this.lifecycleEpoch
|
|
3319
|
+
const suspensionEpoch = ++this.lifecycleEpoch;
|
|
3139
3320
|
this.suspended = true;
|
|
3140
|
-
this.cancelScheduledRecovery();
|
|
3321
|
+
this.cancelScheduledRecovery(true);
|
|
3141
3322
|
this.transportReady = false;
|
|
3142
3323
|
this.transportSubscribedTopics.clear();
|
|
3143
3324
|
this.updateStatus(WORKER_STATUS.DISCONNECTED);
|
|
3325
|
+
if (suspensionEpoch !== this.lifecycleEpoch || this.stopping || !this.suspended) return;
|
|
3144
3326
|
if (this.pendingStop && (this.startPromise === null || this.startPromise === this.pendingStop)) {
|
|
3145
3327
|
this.startPromise = this.pendingStop;
|
|
3146
3328
|
return;
|
|
@@ -3175,13 +3357,18 @@ var CrossTabDataBus = class {
|
|
|
3175
3357
|
if (this.startPromise && this.startPromise !== this.pendingStop) return this.startPromise;
|
|
3176
3358
|
const config = this.activeConfig;
|
|
3177
3359
|
const traceAttempt = recoveryAttempt ?? (this.recoveryAttempt > 0 ? this.recoveryAttempt : void 0);
|
|
3178
|
-
this.started = true;
|
|
3179
|
-
this.suspended = false;
|
|
3180
|
-
this.updateStatus(WORKER_STATUS.CONNECTING);
|
|
3181
3360
|
const lifecycleEpoch = ++this.lifecycleEpoch;
|
|
3182
3361
|
const pending = this.startPromise ?? this.pendingStop ?? Promise.resolve();
|
|
3183
3362
|
const opening = pending.catch(() => void 0).then(() => this.openTransport(config, Promise.resolve(), false, lifecycleEpoch));
|
|
3184
3363
|
this.startPromise = opening;
|
|
3364
|
+
this.started = true;
|
|
3365
|
+
this.suspended = false;
|
|
3366
|
+
this.transportReady = false;
|
|
3367
|
+
this.updateStatus(WORKER_STATUS.CONNECTING);
|
|
3368
|
+
if (lifecycleEpoch !== this.lifecycleEpoch || this.stopping || this.suspended) {
|
|
3369
|
+
void opening.catch(() => void 0);
|
|
3370
|
+
return opening;
|
|
3371
|
+
}
|
|
3185
3372
|
void opening.then(
|
|
3186
3373
|
() => {
|
|
3187
3374
|
if (this.startPromise === opening) this.startPromise = null;
|
|
@@ -3211,17 +3398,13 @@ var CrossTabDataBus = class {
|
|
|
3211
3398
|
runTransport(operation) {
|
|
3212
3399
|
if (this.suspended) return;
|
|
3213
3400
|
if (this.recoveryGate && !this.stopping) {
|
|
3214
|
-
if (this.recoveryDemandAllowed && this.status === WORKER_STATUS.ERROR && !this.suspended) {
|
|
3215
|
-
this.recoveryDemandAllowed = false;
|
|
3216
|
-
const opening = this.reopenTransport();
|
|
3217
|
-
void opening.then(
|
|
3218
|
-
() => this.releaseRecoveryGate(),
|
|
3219
|
-
() => this.allowDemandRecovery()
|
|
3220
|
-
);
|
|
3221
|
-
}
|
|
3222
3401
|
const gate = this.recoveryGate;
|
|
3402
|
+
const cancellationToken = this.recoveryCancellationToken;
|
|
3403
|
+
this.startDemandRecovery();
|
|
3404
|
+
this.recoveryWaiters += 1;
|
|
3223
3405
|
void gate.then(() => {
|
|
3224
|
-
|
|
3406
|
+
this.recoveryWaiters -= 1;
|
|
3407
|
+
if (this.stopping || this.suspended || cancellationToken !== this.recoveryCancellationToken) return;
|
|
3225
3408
|
this.runTransport(operation);
|
|
3226
3409
|
});
|
|
3227
3410
|
return;
|
|
@@ -3345,4 +3528,4 @@ export {
|
|
|
3345
3528
|
parseDataBusPublication,
|
|
3346
3529
|
selectWorkerBackend
|
|
3347
3530
|
};
|
|
3348
|
-
//# sourceMappingURL=chunk-
|
|
3531
|
+
//# sourceMappingURL=chunk-OXEPZGXR.js.map
|