@rotorsoft/act 1.32.15 → 1.32.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/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +10 -46
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +45 -3
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +12 -9
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +20 -34
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +8 -20
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +7 -12
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/index.cjs +84 -124
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +84 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1199,6 +1199,31 @@ var CircuitBreaker = class {
|
|
|
1199
1199
|
};
|
|
1200
1200
|
|
|
1201
1201
|
// src/internal/close-cycle.ts
|
|
1202
|
+
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
1203
|
+
var CLOSE_CATCH_UP_PASSES = 20;
|
|
1204
|
+
async function catch_up_correlation(cursor, correlate, until) {
|
|
1205
|
+
for (let pass = 0; pass < CLOSE_CATCH_UP_PASSES && cursor.checkpoint < until; pass++) {
|
|
1206
|
+
const before = cursor.checkpoint;
|
|
1207
|
+
cursor.arm();
|
|
1208
|
+
await correlate(CLOSE_CATCH_UP_LIMIT);
|
|
1209
|
+
if (cursor.checkpoint <= before) break;
|
|
1210
|
+
}
|
|
1211
|
+
return cursor.checkpoint;
|
|
1212
|
+
}
|
|
1213
|
+
var CloseLock = class {
|
|
1214
|
+
_tails = /* @__PURE__ */ new Map();
|
|
1215
|
+
/** Run `work` after every earlier close of `stream` has settled. */
|
|
1216
|
+
run(stream, work) {
|
|
1217
|
+
const prev = this._tails.get(stream) ?? Promise.resolve();
|
|
1218
|
+
const next = prev.then(work, work);
|
|
1219
|
+
this._tails.set(stream, next);
|
|
1220
|
+
const cleanup = () => {
|
|
1221
|
+
if (this._tails.get(stream) === next) this._tails.delete(stream);
|
|
1222
|
+
};
|
|
1223
|
+
next.then(cleanup, cleanup);
|
|
1224
|
+
return next;
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1202
1227
|
var SAFETY_PROBE_PAGE_SIZE = 1e3;
|
|
1203
1228
|
async function run_close_cycle(targets, deps) {
|
|
1204
1229
|
const target_map = new Map(targets.map((t) => [t.stream, t]));
|
|
@@ -1220,6 +1245,8 @@ async function run_close_cycle(targets, deps) {
|
|
|
1220
1245
|
deps,
|
|
1221
1246
|
() => run_full_closes(full, target_map, deps, skipped)
|
|
1222
1247
|
);
|
|
1248
|
+
const retired = [...truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
1249
|
+
if (retired.length) deps.forget_subscribed?.(retired);
|
|
1223
1250
|
for (const [stream, entry] of windowed_result) truncated.set(stream, entry);
|
|
1224
1251
|
return { truncated, skipped };
|
|
1225
1252
|
}
|
|
@@ -1378,12 +1405,12 @@ async function scan_stream_heads(streams) {
|
|
|
1378
1405
|
}
|
|
1379
1406
|
return out;
|
|
1380
1407
|
}
|
|
1381
|
-
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size,
|
|
1408
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size, catch_up_correlation2) {
|
|
1382
1409
|
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
1383
1410
|
let needed = -1;
|
|
1384
1411
|
for (const info of stream_info.values())
|
|
1385
1412
|
needed = Math.max(needed, info.max_id);
|
|
1386
|
-
const checkpoint = await
|
|
1413
|
+
const checkpoint = await catch_up_correlation2(needed);
|
|
1387
1414
|
const uncorrelated = /* @__PURE__ */ new Set();
|
|
1388
1415
|
for (const [stream, info] of stream_info) {
|
|
1389
1416
|
if (checkpoint < info.max_id) uncorrelated.add(stream);
|
|
@@ -1684,7 +1711,7 @@ var CorrelateCycle = class {
|
|
|
1684
1711
|
this._initialized = true;
|
|
1685
1712
|
}
|
|
1686
1713
|
async _run_init() {
|
|
1687
|
-
const { watermark, correlated_at } = await
|
|
1714
|
+
const { watermark, correlated_at } = await this._cd.subscribe([
|
|
1688
1715
|
...this._static_targets
|
|
1689
1716
|
]);
|
|
1690
1717
|
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
@@ -2756,7 +2783,16 @@ function warn_misrouted(seen, stream, handler, event) {
|
|
|
2756
2783
|
`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.`
|
|
2757
2784
|
);
|
|
2758
2785
|
}
|
|
2759
|
-
async function run_drain_cycle(
|
|
2786
|
+
async function run_drain_cycle(deps, cycle) {
|
|
2787
|
+
const { ops, registry, batch_handlers, handle, handle_batch, lane } = deps;
|
|
2788
|
+
const {
|
|
2789
|
+
lagging,
|
|
2790
|
+
leading,
|
|
2791
|
+
eventLimit,
|
|
2792
|
+
leaseMillis,
|
|
2793
|
+
misrouted,
|
|
2794
|
+
store_failing
|
|
2795
|
+
} = cycle;
|
|
2760
2796
|
const leased = await ops.claim(
|
|
2761
2797
|
lagging,
|
|
2762
2798
|
leading,
|
|
@@ -2828,7 +2864,7 @@ async function run_drain_cycle(ops, registry, batch_handlers, misrouted, store_f
|
|
|
2828
2864
|
const blocked = await ops.block(
|
|
2829
2865
|
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
2830
2866
|
);
|
|
2831
|
-
if (blocked.length) on_blocked(blocked);
|
|
2867
|
+
if (blocked.length) deps.on_blocked(blocked);
|
|
2832
2868
|
const submitted = handled.flatMap((h, i) => {
|
|
2833
2869
|
const advance = h.handled > 0 ? h.acked_at : leased[i].at;
|
|
2834
2870
|
return h.defer !== void 0 ? { ...h.lease, at: advance, due: h.defer, retry: -1 } : h.next_attempt_at !== void 0 ? { ...h.lease, at: advance, due: h.next_attempt_at } : h.handled > 0 || !h.error ? { ...h.lease, at: h.acked_at } : [];
|
|
@@ -2959,21 +2995,14 @@ var DrainController = class {
|
|
|
2959
2995
|
});
|
|
2960
2996
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
2961
2997
|
const leading = streamLimit - lagging;
|
|
2962
|
-
const cycle = await run_drain_cycle(
|
|
2963
|
-
this._deps.ops,
|
|
2964
|
-
this._deps.registry,
|
|
2965
|
-
this._deps.batch_handlers,
|
|
2966
|
-
this._misrouted,
|
|
2967
|
-
this._deps.breaker.failing,
|
|
2968
|
-
this._deps.handle,
|
|
2969
|
-
this._deps.handle_batch,
|
|
2998
|
+
const cycle = await run_drain_cycle(this._deps, {
|
|
2970
2999
|
lagging,
|
|
2971
3000
|
leading,
|
|
2972
3001
|
eventLimit,
|
|
2973
3002
|
leaseMillis,
|
|
2974
|
-
|
|
2975
|
-
this._deps.
|
|
2976
|
-
);
|
|
3003
|
+
misrouted: this._misrouted,
|
|
3004
|
+
store_failing: this._deps.breaker.failing
|
|
3005
|
+
});
|
|
2977
3006
|
if (!cycle) {
|
|
2978
3007
|
this._deps.breaker.passed();
|
|
2979
3008
|
this._armed = false;
|
|
@@ -3312,8 +3341,6 @@ function build_handle_batch(logger) {
|
|
|
3312
3341
|
}
|
|
3313
3342
|
|
|
3314
3343
|
// src/act.ts
|
|
3315
|
-
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
3316
|
-
var CLOSE_CATCH_UP_PASSES = 20;
|
|
3317
3344
|
function validate_only_lanes(options, lanes) {
|
|
3318
3345
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
3319
3346
|
const declared = /* @__PURE__ */ new Set(["default", ...lanes.map((l) => l.name)]);
|
|
@@ -3466,28 +3493,10 @@ var Act = class {
|
|
|
3466
3493
|
_handle_batch;
|
|
3467
3494
|
/** Declared drain lanes. */
|
|
3468
3495
|
_lanes;
|
|
3469
|
-
/**
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
*/
|
|
3474
|
-
_close_locks = /* @__PURE__ */ new Map();
|
|
3475
|
-
/**
|
|
3476
|
-
* Run `work` under the per-stream close lock. Serializes close
|
|
3477
|
-
* critical sections for the same stream (windowed prunes and full
|
|
3478
|
-
* closes) while letting different streams proceed in parallel.
|
|
3479
|
-
*/
|
|
3480
|
-
_with_close_lock(stream, work) {
|
|
3481
|
-
const prev = this._close_locks.get(stream) ?? Promise.resolve();
|
|
3482
|
-
const next = prev.then(work, work);
|
|
3483
|
-
this._close_locks.set(stream, next);
|
|
3484
|
-
const cleanup = () => {
|
|
3485
|
-
if (this._close_locks.get(stream) === next)
|
|
3486
|
-
this._close_locks.delete(stream);
|
|
3487
|
-
};
|
|
3488
|
-
next.then(cleanup, cleanup);
|
|
3489
|
-
return next;
|
|
3490
|
-
}
|
|
3496
|
+
/** Serializes close critical sections per stream. */
|
|
3497
|
+
_close_lock = new CloseLock();
|
|
3498
|
+
/** Every lane a controller can exist for: `"default"` plus each declared one. */
|
|
3499
|
+
_declared_lanes;
|
|
3491
3500
|
/** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
|
|
3492
3501
|
get lanes() {
|
|
3493
3502
|
return this._lanes;
|
|
@@ -3517,6 +3526,7 @@ var Act = class {
|
|
|
3517
3526
|
this._states = states;
|
|
3518
3527
|
this._batch_handlers = batch_handlers;
|
|
3519
3528
|
this._lanes = lanes;
|
|
3529
|
+
this._declared_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
3520
3530
|
validate_only_lanes(options, lanes);
|
|
3521
3531
|
this._ports = options.scoped ?? default_scope();
|
|
3522
3532
|
const ports = this._ports;
|
|
@@ -3554,7 +3564,7 @@ var Act = class {
|
|
|
3554
3564
|
this._drain = options.drain !== false;
|
|
3555
3565
|
this._breaker = this._build_breaker(options);
|
|
3556
3566
|
this._drain_controllers = this._build_drain_controllers(options, lanes);
|
|
3557
|
-
this._advise_orphaned_lanes(options
|
|
3567
|
+
this._advise_orphaned_lanes(options);
|
|
3558
3568
|
this._audit_deps = this._build_audit_deps();
|
|
3559
3569
|
this._correlate = this._build_correlate(options, classification);
|
|
3560
3570
|
this._settle = this._build_settle(options);
|
|
@@ -3585,7 +3595,7 @@ var Act = class {
|
|
|
3585
3595
|
* to `claim`.
|
|
3586
3596
|
*/
|
|
3587
3597
|
_build_drain_controllers(options, lanes) {
|
|
3588
|
-
const all_lanes =
|
|
3598
|
+
const all_lanes = this._declared_lanes;
|
|
3589
3599
|
const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
|
|
3590
3600
|
const active_lanes = only_set ? all_lanes.filter((n) => only_set.has(n)) : all_lanes;
|
|
3591
3601
|
const single_default_lane = lanes.length === 0;
|
|
@@ -3603,19 +3613,7 @@ var Act = class {
|
|
|
3603
3613
|
on_blocked: (blocked) => this.emit("blocked", blocked),
|
|
3604
3614
|
// Reaction-requested close: the same machinery as `app.close`.
|
|
3605
3615
|
on_close: async (targets) => {
|
|
3606
|
-
|
|
3607
|
-
const result = await run_close_cycle(targets, {
|
|
3608
|
-
reactive_events_size: this._reactive_events.size,
|
|
3609
|
-
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
3610
|
-
event_to_state: this._event_to_state,
|
|
3611
|
-
load: this._es.load,
|
|
3612
|
-
tombstone: this._es.tombstone,
|
|
3613
|
-
logger: this._logger,
|
|
3614
|
-
correlation: close_correlation(this._correlator, close_actor),
|
|
3615
|
-
with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
|
|
3616
|
-
});
|
|
3617
|
-
this._forget_closed_subscriptions(result);
|
|
3618
|
-
this.emit("closed", result);
|
|
3616
|
+
await this._run_close(targets);
|
|
3619
3617
|
},
|
|
3620
3618
|
breaker: this._breaker,
|
|
3621
3619
|
run_scoped: this._scoped,
|
|
@@ -3636,12 +3634,10 @@ var Act = class {
|
|
|
3636
3634
|
* controller here. One process can't check that the cluster covers every
|
|
3637
3635
|
* lane, so it reports its own gaps.
|
|
3638
3636
|
*/
|
|
3639
|
-
_advise_orphaned_lanes(options
|
|
3637
|
+
_advise_orphaned_lanes(options) {
|
|
3640
3638
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
3641
3639
|
const active = new Set(this._drain_controllers.keys());
|
|
3642
|
-
const orphaned =
|
|
3643
|
-
(name) => !active.has(name)
|
|
3644
|
-
);
|
|
3640
|
+
const orphaned = this._declared_lanes.filter((name) => !active.has(name));
|
|
3645
3641
|
if (orphaned.length === 0) return;
|
|
3646
3642
|
const list = orphaned.map((name) => `"${name}"`).join(", ");
|
|
3647
3643
|
this._logger.info(
|
|
@@ -3657,7 +3653,7 @@ var Act = class {
|
|
|
3657
3653
|
states: this._states,
|
|
3658
3654
|
// Declared lanes, not this process's controllers: a lane another
|
|
3659
3655
|
// worker drains is not `unknown-lane`.
|
|
3660
|
-
declared_lanes:
|
|
3656
|
+
declared_lanes: new Set(this._declared_lanes),
|
|
3661
3657
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
3662
3658
|
};
|
|
3663
3659
|
}
|
|
@@ -3674,10 +3670,7 @@ var Act = class {
|
|
|
3674
3670
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3675
3671
|
// Declared lanes (not active ones): a lane another process claims is
|
|
3676
3672
|
// still a valid destination.
|
|
3677
|
-
declared_lanes:
|
|
3678
|
-
"default",
|
|
3679
|
-
...this._lanes.map((l) => l.name)
|
|
3680
|
-
]),
|
|
3673
|
+
declared_lanes: new Set(this._declared_lanes),
|
|
3681
3674
|
on_init: () => {
|
|
3682
3675
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3683
3676
|
},
|
|
@@ -4567,6 +4560,31 @@ var Act = class {
|
|
|
4567
4560
|
async prioritize(filter, priority) {
|
|
4568
4561
|
return this._scoped(() => store().prioritize(filter, priority));
|
|
4569
4562
|
}
|
|
4563
|
+
/**
|
|
4564
|
+
* Run one close: the cycle with this Act's ports, then emit `closed`. Shared by `close()` and the drain's
|
|
4565
|
+
* reaction-requested closes. The close actor lets a custom correlator
|
|
4566
|
+
* still tag tenant context or trace ids.
|
|
4567
|
+
*/
|
|
4568
|
+
async _run_close(targets) {
|
|
4569
|
+
const close_actor = { id: "$close", name: "close" };
|
|
4570
|
+
const result = await run_close_cycle(targets, {
|
|
4571
|
+
reactive_events_size: this._reactive_events.size,
|
|
4572
|
+
catch_up_correlation: (until) => catch_up_correlation(
|
|
4573
|
+
this._correlate,
|
|
4574
|
+
(limit) => this.correlate({ limit }),
|
|
4575
|
+
until
|
|
4576
|
+
),
|
|
4577
|
+
event_to_state: this._event_to_state,
|
|
4578
|
+
load: this._es.load,
|
|
4579
|
+
tombstone: this._es.tombstone,
|
|
4580
|
+
logger: this._logger,
|
|
4581
|
+
correlation: close_correlation(this._correlator, close_actor),
|
|
4582
|
+
with_stream_lock: (stream, work) => this._close_lock.run(stream, work),
|
|
4583
|
+
forget_subscribed: (streams) => this._correlate.forget_subscribed(streams)
|
|
4584
|
+
});
|
|
4585
|
+
this.emit("closed", result);
|
|
4586
|
+
return result;
|
|
4587
|
+
}
|
|
4570
4588
|
/**
|
|
4571
4589
|
* Close the books — guard, archive, truncate, and optionally restart streams.
|
|
4572
4590
|
*
|
|
@@ -4625,69 +4643,11 @@ var Act = class {
|
|
|
4625
4643
|
* ]);
|
|
4626
4644
|
* ```
|
|
4627
4645
|
*/
|
|
4628
|
-
/**
|
|
4629
|
-
* After a close, forget any target whose subscription row the truncate
|
|
4630
|
-
* removed, so a later correlate can re-subscribe it. Restart
|
|
4631
|
-
* targets keep their row, so only fully-retired streams are forgotten.
|
|
4632
|
-
*/
|
|
4633
|
-
/**
|
|
4634
|
-
* Advance correlation until the read cursor reaches `until`, and report
|
|
4635
|
-
* where it landed. Used by the close cycle's safety probe, which
|
|
4636
|
-
* cannot judge a subscription's pending work over events correlate has
|
|
4637
|
-
* not resolved yet.
|
|
4638
|
-
*
|
|
4639
|
-
* Bounded on both ends: it stops as soon as a pass makes no progress (the
|
|
4640
|
-
* log has no more to give), and after {@link CLOSE_CATCH_UP_PASSES}
|
|
4641
|
-
* windows, so a close behind an enormous backlog degrades to skipping the
|
|
4642
|
-
* stream — the documented retryable outcome — instead of scanning the
|
|
4643
|
-
* whole log inside an operator call.
|
|
4644
|
-
*/
|
|
4645
|
-
async _catch_up_correlation(until) {
|
|
4646
|
-
for (let pass = 0; pass < CLOSE_CATCH_UP_PASSES && this._correlate.checkpoint < until; pass++) {
|
|
4647
|
-
const before = this._correlate.checkpoint;
|
|
4648
|
-
this._correlate.arm();
|
|
4649
|
-
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
4650
|
-
if (this._correlate.checkpoint <= before) break;
|
|
4651
|
-
}
|
|
4652
|
-
return this._correlate.checkpoint;
|
|
4653
|
-
}
|
|
4654
|
-
/**
|
|
4655
|
-
* Drop retired streams from correlate's in-process "already subscribed"
|
|
4656
|
-
* set, so a later scan re-issues `subscribe()` for them.
|
|
4657
|
-
*
|
|
4658
|
-
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
4659
|
-
* was restarted and is still consuming.
|
|
4660
|
-
*
|
|
4661
|
-
* `truncate` no longer removes the subscription row, so this is no
|
|
4662
|
-
* longer repairing damage the close itself did. It still matters, because
|
|
4663
|
-
* the row can disappear later: reclaiming retired subscriptions is an
|
|
4664
|
-
* operator job now, and that `DELETE` can land while this process is
|
|
4665
|
-
* running. Forgetting here keeps the in-process view from outliving a row
|
|
4666
|
-
* an operator removed, which is a silent-no-delivery failure — a reaction whose target is named after the stream would never
|
|
4667
|
-
* be re-registered.
|
|
4668
|
-
*/
|
|
4669
|
-
_forget_closed_subscriptions(result) {
|
|
4670
|
-
const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
4671
|
-
if (retired.length) this._correlate.forget_subscribed(retired);
|
|
4672
|
-
}
|
|
4673
4646
|
async close(targets) {
|
|
4674
4647
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
4675
4648
|
return this._scoped(async () => {
|
|
4676
|
-
await this.correlate({ limit:
|
|
4677
|
-
|
|
4678
|
-
const result = await run_close_cycle(targets, {
|
|
4679
|
-
reactive_events_size: this._reactive_events.size,
|
|
4680
|
-
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
4681
|
-
event_to_state: this._event_to_state,
|
|
4682
|
-
load: this._es.load,
|
|
4683
|
-
tombstone: this._es.tombstone,
|
|
4684
|
-
logger: this._logger,
|
|
4685
|
-
correlation: close_correlation(this._correlator, close_actor),
|
|
4686
|
-
with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
|
|
4687
|
-
});
|
|
4688
|
-
this._forget_closed_subscriptions(result);
|
|
4689
|
-
this.emit("closed", result);
|
|
4690
|
-
return result;
|
|
4649
|
+
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
4650
|
+
return this._run_close(targets);
|
|
4691
4651
|
});
|
|
4692
4652
|
}
|
|
4693
4653
|
/**
|