@rotorsoft/act 1.31.7 → 1.31.9
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/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +16 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +7 -1
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/report-once.d.ts +39 -0
- package/dist/@types/internal/report-once.d.ts.map +1 -0
- package/dist/index.cjs +85 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +85 -5
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1403,6 +1403,15 @@ function resolveFoldConfig(options) {
|
|
|
1403
1403
|
|
|
1404
1404
|
// src/internal/correlate-cycle.ts
|
|
1405
1405
|
import { createHash, randomUUID } from "crypto";
|
|
1406
|
+
|
|
1407
|
+
// src/internal/report-once.ts
|
|
1408
|
+
function report_once(seen, key, message) {
|
|
1409
|
+
if (seen.has(key)) return;
|
|
1410
|
+
seen.add(key);
|
|
1411
|
+
log().error(new Error(message));
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
// src/internal/correlate-cycle.ts
|
|
1406
1415
|
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
1407
1416
|
var DEFAULT_CORRELATION_LEASE_MS = 5e3;
|
|
1408
1417
|
var registry_key = (events) => {
|
|
@@ -1410,6 +1419,20 @@ var registry_key = (events) => {
|
|
|
1410
1419
|
return createHash("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 32);
|
|
1411
1420
|
};
|
|
1412
1421
|
var PATTERN_CACHE_SIZE = 32;
|
|
1422
|
+
function report_undeclared_lane(seen, target, lane, declared) {
|
|
1423
|
+
report_once(
|
|
1424
|
+
seen,
|
|
1425
|
+
`lane|${target}|${lane}`,
|
|
1426
|
+
`Reaction resolved target "${target}" onto undeclared lane "${lane}". Declared lanes: ${[...declared].map((l) => `"${l}"`).join(", ")}. No controller claims it, so the stream would never drain \u2014 running it on "default" instead. The equivalent static \`.to({ lane })\` is rejected at build; a dynamic resolver's lane is only knowable here.`
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
function report_lane_conflict(seen, target, kept, dropped) {
|
|
1430
|
+
report_once(
|
|
1431
|
+
seen,
|
|
1432
|
+
`conflict|${target}|${kept}|${dropped}`,
|
|
1433
|
+
`Stream "${target}" has conflicting lane assignments ("${kept}" vs "${dropped}") from two dynamic resolutions at equal priority. Keeping "${kept}", the lane it was first discovered on \u2014 re-laning a live stream would move it out from under a worker holding its lease. The reaction resolving "${dropped}" runs inside the "${kept}" lane's budget, and a process restricted to "${dropped}" via onlyLanes never runs it. The equivalent static declaration is rejected at build; align the resolvers, or split the target.`
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1413
1436
|
var CorrelateCycle = class {
|
|
1414
1437
|
_checkpoint = -1;
|
|
1415
1438
|
_initialized = false;
|
|
@@ -1493,11 +1516,20 @@ var CorrelateCycle = class {
|
|
|
1493
1516
|
* public option) so tests can shrink it; defaults otherwise.
|
|
1494
1517
|
*/
|
|
1495
1518
|
_cold_start_back_scan;
|
|
1519
|
+
/** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
|
|
1520
|
+
_declared_lanes;
|
|
1521
|
+
/**
|
|
1522
|
+
* Offending declarations already reported, so a resolver firing for every
|
|
1523
|
+
* matching event reports once. Owned by the instance rather than the
|
|
1524
|
+
* module: `internal/` holds no module-level state.
|
|
1525
|
+
*/
|
|
1526
|
+
_reported = /* @__PURE__ */ new Set();
|
|
1496
1527
|
constructor({
|
|
1497
1528
|
registry,
|
|
1498
1529
|
static_targets,
|
|
1499
1530
|
cd,
|
|
1500
1531
|
max_subscribed_streams,
|
|
1532
|
+
declared_lanes,
|
|
1501
1533
|
run_scoped,
|
|
1502
1534
|
on_init,
|
|
1503
1535
|
on_init_async,
|
|
@@ -1508,6 +1540,7 @@ var CorrelateCycle = class {
|
|
|
1508
1540
|
this._key = registry_key(registry.events);
|
|
1509
1541
|
this._subscribed = new LruMap(max_subscribed_streams);
|
|
1510
1542
|
this._registry = registry;
|
|
1543
|
+
this._declared_lanes = declared_lanes;
|
|
1511
1544
|
this._static_targets = static_targets;
|
|
1512
1545
|
this._cd = cd;
|
|
1513
1546
|
this._on_init = on_init;
|
|
@@ -1646,10 +1679,20 @@ var CorrelateCycle = class {
|
|
|
1646
1679
|
for (const reaction of register.reactions.values()) {
|
|
1647
1680
|
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1648
1681
|
if (!resolved) continue;
|
|
1682
|
+
let lane = resolved.lane;
|
|
1683
|
+
if (lane !== void 0 && !this._declared_lanes.has(lane)) {
|
|
1684
|
+
report_undeclared_lane(
|
|
1685
|
+
this._reported,
|
|
1686
|
+
resolved.target,
|
|
1687
|
+
lane,
|
|
1688
|
+
this._declared_lanes
|
|
1689
|
+
);
|
|
1690
|
+
lane = void 0;
|
|
1691
|
+
}
|
|
1649
1692
|
const recorded = this._subscribed.get(resolved.target);
|
|
1650
1693
|
const priority = resolved.priority ?? 0;
|
|
1651
1694
|
const upgraded = !recorded || priority > recorded.floor;
|
|
1652
|
-
const carried = upgraded ? { priority, lane
|
|
1695
|
+
const carried = upgraded ? { priority, lane } : { priority: recorded.priority, lane: recorded.lane };
|
|
1653
1696
|
const entry = correlated.get(resolved.target) || {
|
|
1654
1697
|
source: resolved.source,
|
|
1655
1698
|
priority: carried.priority,
|
|
@@ -1657,6 +1700,9 @@ var CorrelateCycle = class {
|
|
|
1657
1700
|
upgraded,
|
|
1658
1701
|
correlated_at: void 0
|
|
1659
1702
|
};
|
|
1703
|
+
const held = correlated.has(resolved.target) ? entry.lane : recorded?.lane;
|
|
1704
|
+
if (held !== void 0 && lane !== void 0 && held !== lane && priority === entry.priority)
|
|
1705
|
+
report_lane_conflict(this._reported, resolved.target, held, lane);
|
|
1660
1706
|
if (carried.priority > entry.priority) {
|
|
1661
1707
|
entry.priority = carried.priority;
|
|
1662
1708
|
entry.lane = carried.lane;
|
|
@@ -1760,7 +1806,9 @@ var CorrelateCycle = class {
|
|
|
1760
1806
|
millis: 0
|
|
1761
1807
|
});
|
|
1762
1808
|
} catch (error) {
|
|
1763
|
-
log().
|
|
1809
|
+
log().warn(
|
|
1810
|
+
`Could not hand back the correlation lease during shutdown; it expires on its own within ${this._lease_millis}ms and correlation resumes normally after that. On a single-writer store this usually means another connection holds the database. Cause: ${String(error)}`
|
|
1811
|
+
);
|
|
1764
1812
|
}
|
|
1765
1813
|
}
|
|
1766
1814
|
stop_polling() {
|
|
@@ -2536,7 +2584,14 @@ function budget_exhausted(lease, options) {
|
|
|
2536
2584
|
log().error(error);
|
|
2537
2585
|
return { lease, handled: 0, acked_at: lease.at, error, block: true };
|
|
2538
2586
|
}
|
|
2539
|
-
|
|
2587
|
+
function warn_misrouted(seen, stream, handler, event) {
|
|
2588
|
+
report_once(
|
|
2589
|
+
seen,
|
|
2590
|
+
`${stream}|${handler}|${event}`,
|
|
2591
|
+
`Reaction "${handler}" on "${event}" resolved to target "${stream}", which a projection already serves. A target is served by one batch handler or one state projection, so this reaction can never run \u2014 and delivering to it would hand the projection an aggregate from another stream. Skipping it. The equivalent static \`.to({ target })\` is rejected at build; a dynamic resolver's target is only knowable here.`
|
|
2592
|
+
);
|
|
2593
|
+
}
|
|
2594
|
+
async function run_drain_cycle(ops, registry, batch_handlers, misrouted, handle, handle_batch, lagging, leading, eventLimit, leaseMillis, on_blocked, lane) {
|
|
2540
2595
|
const leased = await ops.claim(
|
|
2541
2596
|
lagging,
|
|
2542
2597
|
leading,
|
|
@@ -2557,8 +2612,18 @@ async function run_drain_cycle(ops, registry, batch_handlers, handle, handle_bat
|
|
|
2557
2612
|
const register = registry.events[event.name];
|
|
2558
2613
|
if (!register) return [];
|
|
2559
2614
|
return [...register.reactions.values()].filter((reaction) => {
|
|
2560
|
-
const
|
|
2561
|
-
|
|
2615
|
+
const resolver = reaction.resolver;
|
|
2616
|
+
const dynamic = typeof resolver === "function";
|
|
2617
|
+
const resolved = dynamic ? resolver(event) : resolver;
|
|
2618
|
+
if (!resolved || resolved.target !== stream) return false;
|
|
2619
|
+
if (!dynamic || !batch_handlers.has(stream)) return true;
|
|
2620
|
+
warn_misrouted(
|
|
2621
|
+
misrouted,
|
|
2622
|
+
stream,
|
|
2623
|
+
reaction.handler.name,
|
|
2624
|
+
String(event.name)
|
|
2625
|
+
);
|
|
2626
|
+
return false;
|
|
2562
2627
|
}).map((reaction) => ({ ...reaction, event }));
|
|
2563
2628
|
});
|
|
2564
2629
|
fetch_map.set(stream, { fetch: f, payloads });
|
|
@@ -2619,6 +2684,12 @@ var DrainController = class {
|
|
|
2619
2684
|
});
|
|
2620
2685
|
/** Worker timer (ACT-1103). Set when `start()` is active, undefined otherwise. */
|
|
2621
2686
|
_worker;
|
|
2687
|
+
/**
|
|
2688
|
+
* Misroutings this controller has reported (#1563). A resolver returning a
|
|
2689
|
+
* projection's target does so for every matching event; one line per event
|
|
2690
|
+
* would bury the signal it exists to raise.
|
|
2691
|
+
*/
|
|
2692
|
+
_misrouted = /* @__PURE__ */ new Set();
|
|
2622
2693
|
_stopped = false;
|
|
2623
2694
|
/**
|
|
2624
2695
|
* Resolves when the cycle currently in flight finishes; `undefined` when
|
|
@@ -2735,6 +2806,7 @@ var DrainController = class {
|
|
|
2735
2806
|
this._deps.ops,
|
|
2736
2807
|
this._deps.registry,
|
|
2737
2808
|
this._deps.batch_handlers,
|
|
2809
|
+
this._misrouted,
|
|
2738
2810
|
this._deps.handle,
|
|
2739
2811
|
this._deps.handle_batch,
|
|
2740
2812
|
lagging,
|
|
@@ -3500,6 +3572,14 @@ var Act = class {
|
|
|
3500
3572
|
static_targets: classification.static_targets,
|
|
3501
3573
|
cd: this._cd,
|
|
3502
3574
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3575
|
+
// Every lane a controller can exist for, so correlate can reroute a
|
|
3576
|
+
// dynamic resolution that names one that doesn't (#1564). Declared,
|
|
3577
|
+
// not active: `onlyLanes` shrinks this process's controllers, but a
|
|
3578
|
+
// lane another process claims is still a valid destination.
|
|
3579
|
+
declared_lanes: /* @__PURE__ */ new Set([
|
|
3580
|
+
"default",
|
|
3581
|
+
...this._lanes.map((l) => l.name)
|
|
3582
|
+
]),
|
|
3503
3583
|
on_init: () => {
|
|
3504
3584
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3505
3585
|
},
|