cross-tab-worker-databus 0.21.42 → 0.21.44
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 +22 -0
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-VWVYUSIN.js → chunk-QPM2OGBB.js} +96 -18
- package/dist/chunk-QPM2OGBB.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +95 -17
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +95 -17
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +24 -0
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/core/dedup-manager.d.ts +26 -0
- package/dist/core/dedup-manager.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/api.md +1 -1
- package/docs/architecture.md +13 -5
- package/docs/configuration.md +1 -1
- package/docs/roadmap.md +11 -1
- package/docs/zh/api.md +2 -2
- package/docs/zh/architecture.md +6 -4
- package/docs/zh/configuration.md +1 -1
- package/docs/zh/roadmap.md +11 -1
- package/package.json +1 -1
- package/dist/chunk-VWVYUSIN.js.map +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
## [0.21.44] - 2026-09-26
|
|
2
|
+
|
|
3
|
+
`0.21.43` guarded one clock; enumerating the rest found that the same input reached durable state, where the consequence is not a wrong number but a sentinel. No API, option, default, frame or storage key changed; the coverage floors and the zero-count arm ledger are `0.21.43`'s.
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **A non-finite environment clock was written into localStorage as `null`, and `null` coerces to `0`.** `ClusterEnvironment.now` is a required field whose documented hazard is a *backwards* step, which every `now - stamped > ttl` site already reads as "not yet expired" — but a missing reading is outside that contract, and it did not degrade one field: the durable worker, route and subscriber records were written with `"updatedAt": null`, because `JSON.stringify(NaN)` is `null`. That is worse than a wrong number rather than merely different, because `null` coerces to `0` in the TTL arithmetic, so **the first tick after the clock recovered would read every record as ancient and prune the whole cluster at once**. A poisoned in-memory comparison heals when the clock recovers; a poisoned record does not. The cluster now holds the last finite reading across its ten read sites, and the bus normalizes once at the single place it binds a clock — which covers its own stamps *and* the dedup manager it hands the same function to, the site `0.21.43` guarded at its own boundary.
|
|
8
|
+
- **Both guards are pinned by cases that had to be rewritten to measure anything.** The cluster case first filtered records with `typeof reading !== 'number'`, and the value this bug leaves is `null`, whose `typeof` is `"object"` — the predicate skipped exactly what it existed to catch, and the guard's own mutation passed it. The bus case only read fields the broken window never touches, so it passed the unguarded binding too; driving a rewrite under the broken clock is what made it a check. Each mutation now dies at one test: `stored a non-finite heartbeatAt`, and `the recovery ledger stamps a finite time`.
|
|
9
|
+
|
|
10
|
+
## [0.21.43] - 2026-09-26
|
|
11
|
+
|
|
12
|
+
Two claims about the storage boundary turned out to be narrower than the storage was, and a public clock could report `NaN` into the health summary. No library behavior, export, option, default, frame or storage key changed beyond the one guard; the coverage floors and the zero-count arm ledger are `0.21.42`'s.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **A caller-injected dedup clock that returns a non-finite number poisoned every comparison that reads it.** `dedup.now` is a public option, and a non-wall-clock host is exactly what it exists for — one whose reading can be *missing* rather than merely unusual. Since every comparison in the dedup manager is arithmetic on a timestamp, a single `NaN` reading did not degrade one field: the adaptive window's `elapsed >= ADAPTIVE_WINDOW_MS` never fired, the hot path's `now - timestamp > ttlMs` became `NaN > ttlMs` (false, so nothing expired), the periodic sweep pruned nothing, and `getDedupStats().ttlMs` reported **`NaN`** — which `JSON.stringify` writes as **`null`**, so a health summary or a diagnostics log carried a null where a duration belongs. Measured on the pre-fix class: `ttlMs` → `NaN`, serialized → `{"ttlMs":null}`, and a message ID stayed suppressed for as long as it stayed in the map. The manager now holds the last finite reading, which is the degradation that changes least — time stands still, expiry pauses, the adaptive window keeps its last value — where substituting `Date.now()` would silently mix two clocks and zeroing would expire the whole map on the next call. The guard sits at the clock rather than at each comparison, which is the shape this repository already uses for the same class of input in `trace.ts`, `websocket.ts` and `replay-manager.ts`. Pinned by two cases whose legs fail apart: removing the guard fails both, while removing only the constructor's fallback — the one site with no previous reading to hold — fails the second alone, because a `NaN` baseline would make every later `elapsed` non-finite too. `docs/api.md` and its Chinese mirror state the guarantee where `dedup.now` is documented.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **The storage boundary is now stated where it was widest, not only where it was found.** `docs/configuration.md` and `docs/architecture.md` each said the coordination records are isolated per `clusterKey` and that Topic plaintext reaches a durable store only through two named opt-ins; the architecture guide carried that claim in a *dedicated* section ("Two DataBus instances with different `clusterKey` values … operate on completely isolated storage namespaces") and as an absolute above its key listing ("All keys are isolated by `createOpaqueKey(clusterKey)`"). Both now name the exception the second opt-in introduces — its IndexedDB object store is keyed by the plaintext Topic and its database name defaults to the storage prefix, so it is not partitioned by `clusterKey` at all, and `dbName` per cluster is what separates them — in both languages.
|
|
21
|
+
- **The key/plaintext pairing rule is now described the way the code enforces it.** The protocol table's row "`topicKey === createOpaqueKey(topic)`" became unconditionally true in `0.21.42`, but the prose beneath it still said only that a pair which *disagrees* is a substitution — which reads as a rule about mismatches and leaves a frame carrying no `topicKey` sounding like a tolerated legacy shape, which is the reading that once kept a guard in place. The shipped text now says the comparison is unconditional, that a missing field is a disagreement of the same kind, and that a custom environment should always send the field. Both languages.
|
|
22
|
+
|
|
1
23
|
## [0.21.42] - 2026-09-26
|
|
2
24
|
|
|
3
25
|
The library's central privacy claim — that a topic's plaintext never reaches localStorage — was enforced on keys by accident and on values by nothing, and the guard that protects the coordination plane's key/topic pair was written to tolerate a missing field it never had to tolerate. Both halves of that sentence were found by re-auditing claims rather than code: the first by measuring which mutations anything noticed, the second by enumerating the readers of a pair. One behavioral change, no API, option, default, frame or storage-key change.
|
package/dist/centrifuge.js
CHANGED
|
@@ -645,6 +645,10 @@ var WorkerClusterRuntime = class {
|
|
|
645
645
|
channelName;
|
|
646
646
|
/** Adaptive load weighting options; undefined keeps legacy topic-count routing. */
|
|
647
647
|
loadWeighting;
|
|
648
|
+
/** The most recent finite reading of the environment's clock, and the fallback for
|
|
649
|
+
* a broken one — see {@link nowMs}. Seeded to 0 because a clock that has never
|
|
650
|
+
* produced a finite reading leaves nothing to hold. */
|
|
651
|
+
lastFiniteNow = 0;
|
|
648
652
|
/** Rolling traffic accumulator folded into the worker record on writeRecord. */
|
|
649
653
|
throughputWindow = {
|
|
650
654
|
startedAt: 0,
|
|
@@ -706,7 +710,7 @@ var WorkerClusterRuntime = class {
|
|
|
706
710
|
this.storage = canUseStorage(this.environment.storage, `${baseKey}:probe`) ? new BatchingStorageWriter(this.environment.storage) : null;
|
|
707
711
|
this.tabId = options.tabId ?? getOrCreateTabId(this.environment, `${prefix}:tab-id`);
|
|
708
712
|
this.workerId = options.workerId ?? `worker-${this.tabId}-${this.environment.randomId()}`;
|
|
709
|
-
const now = this.
|
|
713
|
+
const now = this.nowMs();
|
|
710
714
|
this.currentRecord = {
|
|
711
715
|
protocolVersion: CLUSTER_PROTOCOL_VERSION,
|
|
712
716
|
workerId: this.workerId,
|
|
@@ -719,6 +723,33 @@ var WorkerClusterRuntime = class {
|
|
|
719
723
|
registeredAt: now
|
|
720
724
|
};
|
|
721
725
|
}
|
|
726
|
+
/**
|
|
727
|
+
* The environment's clock, normalized.
|
|
728
|
+
*
|
|
729
|
+
* `ClusterEnvironment.now` is a required field, and the documented hazard is a
|
|
730
|
+
* *backwards* step rather than a missing reading — the library's own subtraction
|
|
731
|
+
* sites tolerate one, since every `now - stamped > ttl` reads a backwards clock as
|
|
732
|
+
* "not yet expired". A non-finite reading is outside that contract, and it does not
|
|
733
|
+
* degrade one field: measured before this guard, a `NaN` clock was written straight
|
|
734
|
+
* into durable state — the worker, route and subscriber records all carried
|
|
735
|
+
* `"updatedAt": null`, because `JSON.stringify(NaN)` is `null` — and that is worse
|
|
736
|
+
* than a wrong number rather than merely different, because `null` coerces to `0`
|
|
737
|
+
* in the TTL arithmetic, so the first tick after the clock recovered would read
|
|
738
|
+
* every record as ancient and prune the whole cluster at once.
|
|
739
|
+
*
|
|
740
|
+
* Holding the last finite reading is the same treatment `data-bus.ts` applies to
|
|
741
|
+
* `dedup.now` and `DedupManager` applies to its own boundary: a paused clock, not a
|
|
742
|
+
* poisoned one. It is a helper rather than nine conditions because the broken input
|
|
743
|
+
* is the clock, not any particular comparison.
|
|
744
|
+
*/
|
|
745
|
+
nowMs() {
|
|
746
|
+
const reading = this.environment.now();
|
|
747
|
+
if (Number.isFinite(reading)) {
|
|
748
|
+
this.lastFiniteNow = reading;
|
|
749
|
+
return reading;
|
|
750
|
+
}
|
|
751
|
+
return this.lastFiniteNow;
|
|
752
|
+
}
|
|
722
753
|
/** Start the cluster: register, listen for lifecycle events, and begin heartbeats. */
|
|
723
754
|
start() {
|
|
724
755
|
if (this.started) return;
|
|
@@ -775,7 +806,7 @@ var WorkerClusterRuntime = class {
|
|
|
775
806
|
return;
|
|
776
807
|
}
|
|
777
808
|
this.channel?.addEventListener("message", this.handleMessage);
|
|
778
|
-
const now = this.
|
|
809
|
+
const now = this.nowMs();
|
|
779
810
|
this.currentRecord = {
|
|
780
811
|
...this.currentRecord,
|
|
781
812
|
heartbeatAt: now,
|
|
@@ -1261,7 +1292,7 @@ var WorkerClusterRuntime = class {
|
|
|
1261
1292
|
* will never complete — while a fresh unconfirmed route may simply be
|
|
1262
1293
|
* waiting out its confirmation flush and must be left alone. */
|
|
1263
1294
|
isStaleHandoff(route) {
|
|
1264
|
-
return this.
|
|
1295
|
+
return this.nowMs() - route.updatedAt > this.workerTtlMs;
|
|
1265
1296
|
}
|
|
1266
1297
|
/** Full reconciliation cycle: workers, subscriptions, and assigned topics. */
|
|
1267
1298
|
reconcile() {
|
|
@@ -1446,7 +1477,7 @@ var WorkerClusterRuntime = class {
|
|
|
1446
1477
|
/** Read all live worker records from storage, pruning stale entries past the TTL. */
|
|
1447
1478
|
readWorkers() {
|
|
1448
1479
|
if (!this.storage) return [this.currentRecord];
|
|
1449
|
-
const now = this.
|
|
1480
|
+
const now = this.nowMs();
|
|
1450
1481
|
const workers = [];
|
|
1451
1482
|
for (const { key, value: worker } of readAllByPrefix(this.storage, this.workerPrefix)) {
|
|
1452
1483
|
if (worker.workerId !== this.workerId && now - worker.heartbeatAt > this.workerTtlMs) {
|
|
@@ -1498,7 +1529,7 @@ var WorkerClusterRuntime = class {
|
|
|
1498
1529
|
topicKey,
|
|
1499
1530
|
workerId: this.workerId,
|
|
1500
1531
|
tabId: this.tabId,
|
|
1501
|
-
updatedAt: this.
|
|
1532
|
+
updatedAt: this.nowMs(),
|
|
1502
1533
|
generation: 1
|
|
1503
1534
|
};
|
|
1504
1535
|
}
|
|
@@ -1515,7 +1546,7 @@ var WorkerClusterRuntime = class {
|
|
|
1515
1546
|
topicKey,
|
|
1516
1547
|
workerId: owner.workerId,
|
|
1517
1548
|
tabId: owner.tabId,
|
|
1518
|
-
updatedAt: this.
|
|
1549
|
+
updatedAt: this.nowMs(),
|
|
1519
1550
|
generation,
|
|
1520
1551
|
...handoffFromWorkerId ? { handoffFromWorkerId } : {}
|
|
1521
1552
|
};
|
|
@@ -1527,7 +1558,7 @@ var WorkerClusterRuntime = class {
|
|
|
1527
1558
|
if (!route || route.workerId !== this.workerId || route.confirmedAt !== void 0) return;
|
|
1528
1559
|
writeJson(this.storage, this.routeStorageKey(topicKey), {
|
|
1529
1560
|
...route,
|
|
1530
|
-
confirmedAt: this.
|
|
1561
|
+
confirmedAt: this.nowMs()
|
|
1531
1562
|
});
|
|
1532
1563
|
const topic = this.knownTopics.get(topicKey);
|
|
1533
1564
|
if (topic) this.handlers.onDiagnostic?.({ operation: RELIABILITY_OPERATION.ROUTE_ACK, topic });
|
|
@@ -1535,7 +1566,7 @@ var WorkerClusterRuntime = class {
|
|
|
1535
1566
|
/** Remove routes whose topic has no subscribers and whose TTL has expired. */
|
|
1536
1567
|
cleanupOrphanedRoutes(workers) {
|
|
1537
1568
|
if (!this.storage) return;
|
|
1538
|
-
const now = this.
|
|
1569
|
+
const now = this.nowMs();
|
|
1539
1570
|
for (const { key, value: route } of readAllByPrefix(this.storage, this.routePrefix)) {
|
|
1540
1571
|
if (now - route.updatedAt <= this.workerTtlMs) continue;
|
|
1541
1572
|
if (this.readSubscriberTabIds(route.topicKey, workers).length > 0) continue;
|
|
@@ -1555,7 +1586,7 @@ var WorkerClusterRuntime = class {
|
|
|
1555
1586
|
if (!this.storage) return;
|
|
1556
1587
|
writeJson(this.storage, this.subscriberStorageKey(topicKey, this.tabId), {
|
|
1557
1588
|
tabId: this.tabId,
|
|
1558
|
-
updatedAt: this.
|
|
1589
|
+
updatedAt: this.nowMs()
|
|
1559
1590
|
});
|
|
1560
1591
|
}
|
|
1561
1592
|
/** Persist the current worker record with an updated heartbeat timestamp.
|
|
@@ -1566,7 +1597,7 @@ var WorkerClusterRuntime = class {
|
|
|
1566
1597
|
* status, visibility and load changes pass true; the heartbeat tick and a
|
|
1567
1598
|
* role change pass false. */
|
|
1568
1599
|
writeRecord(notify) {
|
|
1569
|
-
const now = this.
|
|
1600
|
+
const now = this.nowMs();
|
|
1570
1601
|
const sample = this.loadWeighting !== void 0 ? this.sampleThroughput(now) : void 0;
|
|
1571
1602
|
this.currentRecord = {
|
|
1572
1603
|
...this.currentRecord,
|
|
@@ -2366,6 +2397,8 @@ var DedupManager = class {
|
|
|
2366
2397
|
seenMessageIds = /* @__PURE__ */ new Map();
|
|
2367
2398
|
sweepTimer = null;
|
|
2368
2399
|
windowStartedAt = 0;
|
|
2400
|
+
/** The most recent finite reading of `now`, and the fallback for a broken one. */
|
|
2401
|
+
lastFiniteNow = 0;
|
|
2369
2402
|
windowAccepted = 0;
|
|
2370
2403
|
suppressed = 0;
|
|
2371
2404
|
accepted = 0;
|
|
@@ -2377,14 +2410,46 @@ var DedupManager = class {
|
|
|
2377
2410
|
this.sweepMs = options.sweepMs;
|
|
2378
2411
|
this.now = options.now;
|
|
2379
2412
|
this.trace = options.trace;
|
|
2380
|
-
this.
|
|
2413
|
+
this.lastFiniteNow = Number.isFinite(this.now()) ? this.now() : 0;
|
|
2414
|
+
this.windowStartedAt = this.lastFiniteNow;
|
|
2415
|
+
}
|
|
2416
|
+
/**
|
|
2417
|
+
* The injected clock, normalized.
|
|
2418
|
+
*
|
|
2419
|
+
* `dedup.now` is a public option and a non-wall-clock host is exactly the case it
|
|
2420
|
+
* exists for — one whose reading can be *missing* rather than merely unusual. Every
|
|
2421
|
+
* comparison this class makes is arithmetic on a timestamp, so a single non-finite
|
|
2422
|
+
* reading does not degrade one field: `now - timestamp > ttlMs` becomes
|
|
2423
|
+
* `NaN > ttlMs` (false, so nothing expires), the adaptive window's
|
|
2424
|
+
* `elapsed >= ADAPTIVE_WINDOW_MS` never fires, and `getStats().ttlMs` reports
|
|
2425
|
+
* `NaN` — which `JSON.stringify` turns into `null`, so a health summary or a
|
|
2426
|
+
* diagnostics log would carry a null where a duration belongs. Measured on the
|
|
2427
|
+
* pre-fix class: `getStats().ttlMs` → `NaN`, serialized → `null`, and an id
|
|
2428
|
+
* stayed suppressed for as long as it stayed in the map.
|
|
2429
|
+
*
|
|
2430
|
+
* Holding the last finite reading is the degradation that changes least: time
|
|
2431
|
+
* stands still, expiry pauses, and the adaptive window keeps the value it last
|
|
2432
|
+
* computed. Substituting `Date.now()` would be worse — it silently mixes two
|
|
2433
|
+
* clocks — and zeroing would expire the whole map on the next call. This is the
|
|
2434
|
+
* same class of guard `trace.ts` carries on its own public clock, and that
|
|
2435
|
+
* `websocket.ts` and `replay-manager.ts` apply to their numeric options; it is one
|
|
2436
|
+
* helper rather than a condition per site because the broken input is the *clock*,
|
|
2437
|
+
* not any particular comparison.
|
|
2438
|
+
*/
|
|
2439
|
+
nowMs() {
|
|
2440
|
+
const reading = this.now();
|
|
2441
|
+
if (Number.isFinite(reading)) {
|
|
2442
|
+
this.lastFiniteNow = reading;
|
|
2443
|
+
return reading;
|
|
2444
|
+
}
|
|
2445
|
+
return this.lastFiniteNow;
|
|
2381
2446
|
}
|
|
2382
2447
|
/** True when a publication carrying `messageId` was already seen. Records the
|
|
2383
2448
|
* ID and updates counters/trace on acceptance. Disabled or ID-less messages
|
|
2384
2449
|
* always pass through. */
|
|
2385
2450
|
isDuplicate(messageId, topic) {
|
|
2386
2451
|
if (!this.enabled || !messageId) return false;
|
|
2387
|
-
const now = this.
|
|
2452
|
+
const now = this.nowMs();
|
|
2388
2453
|
const ttlMs = this.currentTtl();
|
|
2389
2454
|
for (const [id, timestamp] of this.seenMessageIds) {
|
|
2390
2455
|
if (now - timestamp > ttlMs) this.seenMessageIds.delete(id);
|
|
@@ -2435,12 +2500,12 @@ var DedupManager = class {
|
|
|
2435
2500
|
this.seenMessageIds.clear();
|
|
2436
2501
|
this.suppressed = 0;
|
|
2437
2502
|
this.accepted = 0;
|
|
2438
|
-
this.windowStartedAt = this.
|
|
2503
|
+
this.windowStartedAt = this.nowMs();
|
|
2439
2504
|
this.windowAccepted = 0;
|
|
2440
2505
|
}
|
|
2441
2506
|
/** Remove IDs whose timestamp predates the effective TTL cutoff. */
|
|
2442
2507
|
pruneExpired() {
|
|
2443
|
-
const cutoff = this.
|
|
2508
|
+
const cutoff = this.nowMs() - this.currentTtl();
|
|
2444
2509
|
for (const [id, timestamp] of this.seenMessageIds) {
|
|
2445
2510
|
if (timestamp < cutoff) this.seenMessageIds.delete(id);
|
|
2446
2511
|
}
|
|
@@ -2451,7 +2516,7 @@ var DedupManager = class {
|
|
|
2451
2516
|
* every ADAPTIVE_WINDOW_MS. */
|
|
2452
2517
|
currentTtl() {
|
|
2453
2518
|
if (!this.adaptiveBounds) return this.ttlMs;
|
|
2454
|
-
const now = this.
|
|
2519
|
+
const now = this.nowMs();
|
|
2455
2520
|
const elapsed = now - this.windowStartedAt;
|
|
2456
2521
|
if (elapsed >= ADAPTIVE_WINDOW_MS) {
|
|
2457
2522
|
this.windowStartedAt = now;
|
|
@@ -2475,7 +2540,7 @@ var SDK_VERSION = (
|
|
|
2475
2540
|
// declaration's type is `string | undefined` and the export's is `string`
|
|
2476
2541
|
// (dropping the conditional is TS2322, measured). Recorded here so the leg reads
|
|
2477
2542
|
// as a shape the harness cannot reach rather than as a case nobody exercised.
|
|
2478
|
-
true ? "0.21.
|
|
2543
|
+
true ? "0.21.44" : ""
|
|
2479
2544
|
);
|
|
2480
2545
|
|
|
2481
2546
|
// src/core/data-bus.ts
|
|
@@ -2595,7 +2660,7 @@ var CrossTabDataBus = class {
|
|
|
2595
2660
|
assertRecoveryOptions(recovery);
|
|
2596
2661
|
this.recoveryCooldownMs = recovery?.cooldownMs ?? 1e3;
|
|
2597
2662
|
this.recoveryMaxAttempts = recovery?.maxAttempts ?? Number.POSITIVE_INFINITY;
|
|
2598
|
-
this.now = dedup?.now ?? Date.now;
|
|
2663
|
+
this.now = holdLastFinite(dedup?.now ?? Date.now);
|
|
2599
2664
|
this.transport = transport;
|
|
2600
2665
|
this.initialConfig = initialConfig;
|
|
2601
2666
|
this.hasInitialConfig = "initialConfig" in options;
|
|
@@ -3654,6 +3719,19 @@ function formatWorkerTrace(worker) {
|
|
|
3654
3719
|
function formatRouteTrace(route) {
|
|
3655
3720
|
return `${route.topicKey}@${route.workerId}|confirmed=${route.confirmedAt !== void 0}`;
|
|
3656
3721
|
}
|
|
3722
|
+
function holdLastFinite(source) {
|
|
3723
|
+
let last = 0;
|
|
3724
|
+
let primed = false;
|
|
3725
|
+
return () => {
|
|
3726
|
+
const reading = source();
|
|
3727
|
+
if (Number.isFinite(reading)) {
|
|
3728
|
+
last = reading;
|
|
3729
|
+
primed = true;
|
|
3730
|
+
return reading;
|
|
3731
|
+
}
|
|
3732
|
+
return primed ? last : 0;
|
|
3733
|
+
};
|
|
3734
|
+
}
|
|
3657
3735
|
|
|
3658
3736
|
// src/worker-mode.ts
|
|
3659
3737
|
function selectWorkerBackend(mode, availability = {}) {
|
|
@@ -3715,4 +3793,4 @@ export {
|
|
|
3715
3793
|
parseDataBusPublication,
|
|
3716
3794
|
selectWorkerBackend
|
|
3717
3795
|
};
|
|
3718
|
-
//# sourceMappingURL=chunk-
|
|
3796
|
+
//# sourceMappingURL=chunk-QPM2OGBB.js.map
|