cross-tab-worker-databus 0.21.15 → 0.21.17
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 +40 -1
- package/dist/centrifuge.js +1 -1
- package/dist/{chunk-VQJIMGJN.js → chunk-EZYCSJRS.js} +68 -6
- package/dist/chunk-EZYCSJRS.js.map +7 -0
- package/dist/cjs/centrifuge.cjs +67 -5
- package/dist/cjs/centrifuge.cjs.map +2 -2
- package/dist/cjs/index.cjs +67 -5
- package/dist/cjs/index.cjs.map +2 -2
- package/dist/core/cluster.d.ts +28 -0
- package/dist/core/cluster.d.ts.map +1 -1
- package/dist/core/data-bus.d.ts +7 -0
- package/dist/core/data-bus.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/docs/api.md +1 -1
- package/docs/architecture.md +7 -5
- package/docs/release-checklist.md +1 -1
- package/docs/roadmap.md +51 -1
- package/docs/zh/api.md +1 -1
- package/docs/zh/architecture.md +7 -5
- package/docs/zh/release-checklist.md +1 -0
- package/docs/zh/roadmap.md +45 -1
- package/package.json +1 -1
- package/dist/chunk-VQJIMGJN.js.map +0 -7
package/dist/cjs/index.cjs
CHANGED
|
@@ -1379,6 +1379,7 @@ var WorkerClusterRuntime = class {
|
|
|
1379
1379
|
reconcile() {
|
|
1380
1380
|
if (!this.started) return;
|
|
1381
1381
|
const workers = this.reconcileWorkers();
|
|
1382
|
+
this.reconcileStrandedSelfRoutes();
|
|
1382
1383
|
const activeWorkers = selectActiveWorkers(workers, this.maxActiveWorkers);
|
|
1383
1384
|
this.reconcileSubscriptions(workers, activeWorkers);
|
|
1384
1385
|
this.reconcileAssignedTopics();
|
|
@@ -1443,6 +1444,52 @@ var WorkerClusterRuntime = class {
|
|
|
1443
1444
|
}
|
|
1444
1445
|
}
|
|
1445
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Repair a confirmed route that names this Worker while its assignment is gone.
|
|
1449
|
+
*
|
|
1450
|
+
* Every write that settles ownership stamps the new owner's assignment in the
|
|
1451
|
+
* same task as the confirmation (`confirmRoute()` has exactly three call sites,
|
|
1452
|
+
* each directly after an `assignedTopics.set`), so a route carrying
|
|
1453
|
+
* `confirmedAt` while this map has no entry can only mean the assignment was
|
|
1454
|
+
* taken *after* the route settled. That is what `pause()` does: it clears the
|
|
1455
|
+
* map unconditionally but rewrites only the routes it can hand off, so a
|
|
1456
|
+
* handoff whose final flush is cut short by a storage failure — and the
|
|
1457
|
+
* `discardPending()` that ends the teardown throws the rest away — leaves a
|
|
1458
|
+
* durable route naming this worker. If the same worker id registers again (a
|
|
1459
|
+
* BFCache restore, or a runtime rebuilt with an explicit `workerId`), every
|
|
1460
|
+
* peer reads that route as a live, confirmed owner and stands down, while no
|
|
1461
|
+
* worker holds a transport subscription for the topic any more. Both reconcile
|
|
1462
|
+
* passes miss it by construction: one walks `subscribedTopics`, the other
|
|
1463
|
+
* `assignedTopics`, and the phantom is in neither.
|
|
1464
|
+
*
|
|
1465
|
+
* Acting here is this worker's alone — a peer that elected it is still waiting
|
|
1466
|
+
* for its own CONTROL/SUBSCRIBE to be accepted, and the single-writer rule
|
|
1467
|
+
* recorded in `reconcileSubscriptions()` forbids anyone else rewriting the
|
|
1468
|
+
* record. Reclaim when the local subscription is still live, which restores the
|
|
1469
|
+
* assignment and the transport subscription without touching the route, so the
|
|
1470
|
+
* owner and its generation stay exactly as peers recorded them; release
|
|
1471
|
+
* otherwise, since an owner that has no handler for the topic and never will is
|
|
1472
|
+
* a record nothing can act on.
|
|
1473
|
+
*/
|
|
1474
|
+
reconcileStrandedSelfRoutes() {
|
|
1475
|
+
if (!this.storage) return;
|
|
1476
|
+
let released = false;
|
|
1477
|
+
for (const { key, value: route } of readAllByPrefix(this.storage, this.routePrefix)) {
|
|
1478
|
+
if (route.confirmedAt === void 0 || route.workerId !== this.workerId) continue;
|
|
1479
|
+
if (this.assignedTopics.has(route.topicKey)) continue;
|
|
1480
|
+
const topic = this.knownTopics.get(route.topicKey);
|
|
1481
|
+
if (topic !== void 0 && this.subscribedTopics.has(topic)) {
|
|
1482
|
+
this.sendControl(this.workerId, CONTROL_ACTION.SUBSCRIBE, topic, route.topicKey);
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
this.removeStorage(key);
|
|
1486
|
+
released = true;
|
|
1487
|
+
}
|
|
1488
|
+
if (released) {
|
|
1489
|
+
this.flushStorage();
|
|
1490
|
+
this.notifyRegistry();
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1446
1493
|
/** Drop assignments where the route no longer points to this worker. */
|
|
1447
1494
|
reconcileAssignedTopics() {
|
|
1448
1495
|
for (const [topicKey, topic] of [...this.assignedTopics]) {
|
|
@@ -2505,7 +2552,7 @@ var DedupManager = class {
|
|
|
2505
2552
|
};
|
|
2506
2553
|
|
|
2507
2554
|
// src/core/version.ts
|
|
2508
|
-
var SDK_VERSION = true ? "0.21.
|
|
2555
|
+
var SDK_VERSION = true ? "0.21.17" : "";
|
|
2509
2556
|
|
|
2510
2557
|
// src/core/data-bus.ts
|
|
2511
2558
|
var DEFAULT_REPLAY_MAX_PER_TOPIC = 100;
|
|
@@ -3456,12 +3503,18 @@ var CrossTabDataBus = class {
|
|
|
3456
3503
|
subscribeTransport(topic) {
|
|
3457
3504
|
if (this.transportSubscribedTopics.has(topic)) return false;
|
|
3458
3505
|
this.transportSubscribedTopics.add(topic);
|
|
3459
|
-
this.runTransport(
|
|
3506
|
+
this.runTransport(
|
|
3507
|
+
() => this.transport.subscribe(topic),
|
|
3508
|
+
() => this.transportSubscribedTopics.has(topic)
|
|
3509
|
+
);
|
|
3460
3510
|
return true;
|
|
3461
3511
|
}
|
|
3462
3512
|
unsubscribeTransport(topic) {
|
|
3463
3513
|
if (!this.transportSubscribedTopics.delete(topic)) return false;
|
|
3464
|
-
this.runTransport(
|
|
3514
|
+
this.runTransport(
|
|
3515
|
+
() => this.transport.unsubscribe(topic),
|
|
3516
|
+
() => !this.transportSubscribedTopics.has(topic)
|
|
3517
|
+
);
|
|
3465
3518
|
return true;
|
|
3466
3519
|
}
|
|
3467
3520
|
/** Invoke `callback` for each item in `handlers`, isolating a throwing
|
|
@@ -3579,8 +3632,15 @@ var CrossTabDataBus = class {
|
|
|
3579
3632
|
* Run a transport operation now if the transport is ready, otherwise queue
|
|
3580
3633
|
* it behind the start promise. This ensures subscribe/unsubscribe calls made
|
|
3581
3634
|
* during startup are not lost.
|
|
3635
|
+
*
|
|
3636
|
+
* `stillWanted` expresses the deferred work as the *current* desired state
|
|
3637
|
+
* rather than a captured call, and is checked at each place that resumes on a
|
|
3638
|
+
* later task — the recovery gate and the opening flush. The immediate branch
|
|
3639
|
+
* carries no check because every path into it evaluates the desired state in
|
|
3640
|
+
* the same task: a caller that just mutated `transportSubscribedTopics`, or
|
|
3641
|
+
* the gate re-entry one line above.
|
|
3582
3642
|
*/
|
|
3583
|
-
runTransport(operation) {
|
|
3643
|
+
runTransport(operation, stillWanted) {
|
|
3584
3644
|
if (this.suspended) return;
|
|
3585
3645
|
if (this.recoveryGate && !this.stopping) {
|
|
3586
3646
|
const gate = this.recoveryGate;
|
|
@@ -3590,7 +3650,8 @@ var CrossTabDataBus = class {
|
|
|
3590
3650
|
void gate.then(() => {
|
|
3591
3651
|
this.recoveryWaiters -= 1;
|
|
3592
3652
|
if (this.stopping || this.suspended || cancellationToken !== this.recoveryCancellationToken) return;
|
|
3593
|
-
|
|
3653
|
+
if (stillWanted && !stillWanted()) return;
|
|
3654
|
+
this.runTransport(operation, stillWanted);
|
|
3594
3655
|
});
|
|
3595
3656
|
return;
|
|
3596
3657
|
}
|
|
@@ -3611,6 +3672,7 @@ var CrossTabDataBus = class {
|
|
|
3611
3672
|
void ready.then(
|
|
3612
3673
|
() => {
|
|
3613
3674
|
if (!this.started || this.stopping || this.suspended) return;
|
|
3675
|
+
if (stillWanted && !stillWanted()) return;
|
|
3614
3676
|
return operation();
|
|
3615
3677
|
},
|
|
3616
3678
|
// The opening promise reports its own lifecycle failure through
|