@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.cjs
CHANGED
|
@@ -2834,6 +2834,31 @@ var CircuitBreaker = class {
|
|
|
2834
2834
|
};
|
|
2835
2835
|
|
|
2836
2836
|
// src/internal/close-cycle.ts
|
|
2837
|
+
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
2838
|
+
var CLOSE_CATCH_UP_PASSES = 20;
|
|
2839
|
+
async function catch_up_correlation(cursor, correlate, until) {
|
|
2840
|
+
for (let pass = 0; pass < CLOSE_CATCH_UP_PASSES && cursor.checkpoint < until; pass++) {
|
|
2841
|
+
const before = cursor.checkpoint;
|
|
2842
|
+
cursor.arm();
|
|
2843
|
+
await correlate(CLOSE_CATCH_UP_LIMIT);
|
|
2844
|
+
if (cursor.checkpoint <= before) break;
|
|
2845
|
+
}
|
|
2846
|
+
return cursor.checkpoint;
|
|
2847
|
+
}
|
|
2848
|
+
var CloseLock = class {
|
|
2849
|
+
_tails = /* @__PURE__ */ new Map();
|
|
2850
|
+
/** Run `work` after every earlier close of `stream` has settled. */
|
|
2851
|
+
run(stream, work) {
|
|
2852
|
+
const prev = this._tails.get(stream) ?? Promise.resolve();
|
|
2853
|
+
const next = prev.then(work, work);
|
|
2854
|
+
this._tails.set(stream, next);
|
|
2855
|
+
const cleanup = () => {
|
|
2856
|
+
if (this._tails.get(stream) === next) this._tails.delete(stream);
|
|
2857
|
+
};
|
|
2858
|
+
next.then(cleanup, cleanup);
|
|
2859
|
+
return next;
|
|
2860
|
+
}
|
|
2861
|
+
};
|
|
2837
2862
|
var SAFETY_PROBE_PAGE_SIZE = 1e3;
|
|
2838
2863
|
async function run_close_cycle(targets, deps) {
|
|
2839
2864
|
const target_map = new Map(targets.map((t) => [t.stream, t]));
|
|
@@ -2855,6 +2880,8 @@ async function run_close_cycle(targets, deps) {
|
|
|
2855
2880
|
deps,
|
|
2856
2881
|
() => run_full_closes(full, target_map, deps, skipped)
|
|
2857
2882
|
);
|
|
2883
|
+
const retired = [...truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
2884
|
+
if (retired.length) deps.forget_subscribed?.(retired);
|
|
2858
2885
|
for (const [stream, entry] of windowed_result) truncated.set(stream, entry);
|
|
2859
2886
|
return { truncated, skipped };
|
|
2860
2887
|
}
|
|
@@ -3013,12 +3040,12 @@ async function scan_stream_heads(streams) {
|
|
|
3013
3040
|
}
|
|
3014
3041
|
return out;
|
|
3015
3042
|
}
|
|
3016
|
-
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size,
|
|
3043
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size, catch_up_correlation2) {
|
|
3017
3044
|
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
3018
3045
|
let needed = -1;
|
|
3019
3046
|
for (const info of stream_info.values())
|
|
3020
3047
|
needed = Math.max(needed, info.max_id);
|
|
3021
|
-
const checkpoint = await
|
|
3048
|
+
const checkpoint = await catch_up_correlation2(needed);
|
|
3022
3049
|
const uncorrelated = /* @__PURE__ */ new Set();
|
|
3023
3050
|
for (const [stream, info] of stream_info) {
|
|
3024
3051
|
if (checkpoint < info.max_id) uncorrelated.add(stream);
|
|
@@ -3319,7 +3346,7 @@ var CorrelateCycle = class {
|
|
|
3319
3346
|
this._initialized = true;
|
|
3320
3347
|
}
|
|
3321
3348
|
async _run_init() {
|
|
3322
|
-
const { watermark, correlated_at } = await
|
|
3349
|
+
const { watermark, correlated_at } = await this._cd.subscribe([
|
|
3323
3350
|
...this._static_targets
|
|
3324
3351
|
]);
|
|
3325
3352
|
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
@@ -4391,7 +4418,16 @@ function warn_misrouted(seen, stream, handler, event) {
|
|
|
4391
4418
|
`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.`
|
|
4392
4419
|
);
|
|
4393
4420
|
}
|
|
4394
|
-
async function run_drain_cycle(
|
|
4421
|
+
async function run_drain_cycle(deps, cycle) {
|
|
4422
|
+
const { ops, registry, batch_handlers, handle, handle_batch, lane } = deps;
|
|
4423
|
+
const {
|
|
4424
|
+
lagging,
|
|
4425
|
+
leading,
|
|
4426
|
+
eventLimit,
|
|
4427
|
+
leaseMillis,
|
|
4428
|
+
misrouted,
|
|
4429
|
+
store_failing
|
|
4430
|
+
} = cycle;
|
|
4395
4431
|
const leased = await ops.claim(
|
|
4396
4432
|
lagging,
|
|
4397
4433
|
leading,
|
|
@@ -4463,7 +4499,7 @@ async function run_drain_cycle(ops, registry, batch_handlers, misrouted, store_f
|
|
|
4463
4499
|
const blocked = await ops.block(
|
|
4464
4500
|
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
4465
4501
|
);
|
|
4466
|
-
if (blocked.length) on_blocked(blocked);
|
|
4502
|
+
if (blocked.length) deps.on_blocked(blocked);
|
|
4467
4503
|
const submitted = handled.flatMap((h, i) => {
|
|
4468
4504
|
const advance = h.handled > 0 ? h.acked_at : leased[i].at;
|
|
4469
4505
|
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 } : [];
|
|
@@ -4594,21 +4630,14 @@ var DrainController = class {
|
|
|
4594
4630
|
});
|
|
4595
4631
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
4596
4632
|
const leading = streamLimit - lagging;
|
|
4597
|
-
const cycle = await run_drain_cycle(
|
|
4598
|
-
this._deps.ops,
|
|
4599
|
-
this._deps.registry,
|
|
4600
|
-
this._deps.batch_handlers,
|
|
4601
|
-
this._misrouted,
|
|
4602
|
-
this._deps.breaker.failing,
|
|
4603
|
-
this._deps.handle,
|
|
4604
|
-
this._deps.handle_batch,
|
|
4633
|
+
const cycle = await run_drain_cycle(this._deps, {
|
|
4605
4634
|
lagging,
|
|
4606
4635
|
leading,
|
|
4607
4636
|
eventLimit,
|
|
4608
4637
|
leaseMillis,
|
|
4609
|
-
|
|
4610
|
-
this._deps.
|
|
4611
|
-
);
|
|
4638
|
+
misrouted: this._misrouted,
|
|
4639
|
+
store_failing: this._deps.breaker.failing
|
|
4640
|
+
});
|
|
4612
4641
|
if (!cycle) {
|
|
4613
4642
|
this._deps.breaker.passed();
|
|
4614
4643
|
this._armed = false;
|
|
@@ -4947,8 +4976,6 @@ function build_handle_batch(logger) {
|
|
|
4947
4976
|
}
|
|
4948
4977
|
|
|
4949
4978
|
// src/act.ts
|
|
4950
|
-
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
4951
|
-
var CLOSE_CATCH_UP_PASSES = 20;
|
|
4952
4979
|
function validate_only_lanes(options, lanes) {
|
|
4953
4980
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
4954
4981
|
const declared = /* @__PURE__ */ new Set(["default", ...lanes.map((l) => l.name)]);
|
|
@@ -5101,28 +5128,10 @@ var Act = class {
|
|
|
5101
5128
|
_handle_batch;
|
|
5102
5129
|
/** Declared drain lanes. */
|
|
5103
5130
|
_lanes;
|
|
5104
|
-
/**
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
*/
|
|
5109
|
-
_close_locks = /* @__PURE__ */ new Map();
|
|
5110
|
-
/**
|
|
5111
|
-
* Run `work` under the per-stream close lock. Serializes close
|
|
5112
|
-
* critical sections for the same stream (windowed prunes and full
|
|
5113
|
-
* closes) while letting different streams proceed in parallel.
|
|
5114
|
-
*/
|
|
5115
|
-
_with_close_lock(stream, work) {
|
|
5116
|
-
const prev = this._close_locks.get(stream) ?? Promise.resolve();
|
|
5117
|
-
const next = prev.then(work, work);
|
|
5118
|
-
this._close_locks.set(stream, next);
|
|
5119
|
-
const cleanup = () => {
|
|
5120
|
-
if (this._close_locks.get(stream) === next)
|
|
5121
|
-
this._close_locks.delete(stream);
|
|
5122
|
-
};
|
|
5123
|
-
next.then(cleanup, cleanup);
|
|
5124
|
-
return next;
|
|
5125
|
-
}
|
|
5131
|
+
/** Serializes close critical sections per stream. */
|
|
5132
|
+
_close_lock = new CloseLock();
|
|
5133
|
+
/** Every lane a controller can exist for: `"default"` plus each declared one. */
|
|
5134
|
+
_declared_lanes;
|
|
5126
5135
|
/** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
|
|
5127
5136
|
get lanes() {
|
|
5128
5137
|
return this._lanes;
|
|
@@ -5152,6 +5161,7 @@ var Act = class {
|
|
|
5152
5161
|
this._states = states;
|
|
5153
5162
|
this._batch_handlers = batch_handlers;
|
|
5154
5163
|
this._lanes = lanes;
|
|
5164
|
+
this._declared_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
5155
5165
|
validate_only_lanes(options, lanes);
|
|
5156
5166
|
this._ports = options.scoped ?? default_scope();
|
|
5157
5167
|
const ports = this._ports;
|
|
@@ -5189,7 +5199,7 @@ var Act = class {
|
|
|
5189
5199
|
this._drain = options.drain !== false;
|
|
5190
5200
|
this._breaker = this._build_breaker(options);
|
|
5191
5201
|
this._drain_controllers = this._build_drain_controllers(options, lanes);
|
|
5192
|
-
this._advise_orphaned_lanes(options
|
|
5202
|
+
this._advise_orphaned_lanes(options);
|
|
5193
5203
|
this._audit_deps = this._build_audit_deps();
|
|
5194
5204
|
this._correlate = this._build_correlate(options, classification);
|
|
5195
5205
|
this._settle = this._build_settle(options);
|
|
@@ -5220,7 +5230,7 @@ var Act = class {
|
|
|
5220
5230
|
* to `claim`.
|
|
5221
5231
|
*/
|
|
5222
5232
|
_build_drain_controllers(options, lanes) {
|
|
5223
|
-
const all_lanes =
|
|
5233
|
+
const all_lanes = this._declared_lanes;
|
|
5224
5234
|
const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
|
|
5225
5235
|
const active_lanes = only_set ? all_lanes.filter((n) => only_set.has(n)) : all_lanes;
|
|
5226
5236
|
const single_default_lane = lanes.length === 0;
|
|
@@ -5238,19 +5248,7 @@ var Act = class {
|
|
|
5238
5248
|
on_blocked: (blocked) => this.emit("blocked", blocked),
|
|
5239
5249
|
// Reaction-requested close: the same machinery as `app.close`.
|
|
5240
5250
|
on_close: async (targets) => {
|
|
5241
|
-
|
|
5242
|
-
const result = await run_close_cycle(targets, {
|
|
5243
|
-
reactive_events_size: this._reactive_events.size,
|
|
5244
|
-
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
5245
|
-
event_to_state: this._event_to_state,
|
|
5246
|
-
load: this._es.load,
|
|
5247
|
-
tombstone: this._es.tombstone,
|
|
5248
|
-
logger: this._logger,
|
|
5249
|
-
correlation: close_correlation(this._correlator, close_actor),
|
|
5250
|
-
with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
|
|
5251
|
-
});
|
|
5252
|
-
this._forget_closed_subscriptions(result);
|
|
5253
|
-
this.emit("closed", result);
|
|
5251
|
+
await this._run_close(targets);
|
|
5254
5252
|
},
|
|
5255
5253
|
breaker: this._breaker,
|
|
5256
5254
|
run_scoped: this._scoped,
|
|
@@ -5271,12 +5269,10 @@ var Act = class {
|
|
|
5271
5269
|
* controller here. One process can't check that the cluster covers every
|
|
5272
5270
|
* lane, so it reports its own gaps.
|
|
5273
5271
|
*/
|
|
5274
|
-
_advise_orphaned_lanes(options
|
|
5272
|
+
_advise_orphaned_lanes(options) {
|
|
5275
5273
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
5276
5274
|
const active = new Set(this._drain_controllers.keys());
|
|
5277
|
-
const orphaned =
|
|
5278
|
-
(name) => !active.has(name)
|
|
5279
|
-
);
|
|
5275
|
+
const orphaned = this._declared_lanes.filter((name) => !active.has(name));
|
|
5280
5276
|
if (orphaned.length === 0) return;
|
|
5281
5277
|
const list = orphaned.map((name) => `"${name}"`).join(", ");
|
|
5282
5278
|
this._logger.info(
|
|
@@ -5292,7 +5288,7 @@ var Act = class {
|
|
|
5292
5288
|
states: this._states,
|
|
5293
5289
|
// Declared lanes, not this process's controllers: a lane another
|
|
5294
5290
|
// worker drains is not `unknown-lane`.
|
|
5295
|
-
declared_lanes:
|
|
5291
|
+
declared_lanes: new Set(this._declared_lanes),
|
|
5296
5292
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
5297
5293
|
};
|
|
5298
5294
|
}
|
|
@@ -5309,10 +5305,7 @@ var Act = class {
|
|
|
5309
5305
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
5310
5306
|
// Declared lanes (not active ones): a lane another process claims is
|
|
5311
5307
|
// still a valid destination.
|
|
5312
|
-
declared_lanes:
|
|
5313
|
-
"default",
|
|
5314
|
-
...this._lanes.map((l) => l.name)
|
|
5315
|
-
]),
|
|
5308
|
+
declared_lanes: new Set(this._declared_lanes),
|
|
5316
5309
|
on_init: () => {
|
|
5317
5310
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
5318
5311
|
},
|
|
@@ -6202,6 +6195,31 @@ var Act = class {
|
|
|
6202
6195
|
async prioritize(filter, priority) {
|
|
6203
6196
|
return this._scoped(() => store2().prioritize(filter, priority));
|
|
6204
6197
|
}
|
|
6198
|
+
/**
|
|
6199
|
+
* Run one close: the cycle with this Act's ports, then emit `closed`. Shared by `close()` and the drain's
|
|
6200
|
+
* reaction-requested closes. The close actor lets a custom correlator
|
|
6201
|
+
* still tag tenant context or trace ids.
|
|
6202
|
+
*/
|
|
6203
|
+
async _run_close(targets) {
|
|
6204
|
+
const close_actor = { id: "$close", name: "close" };
|
|
6205
|
+
const result = await run_close_cycle(targets, {
|
|
6206
|
+
reactive_events_size: this._reactive_events.size,
|
|
6207
|
+
catch_up_correlation: (until) => catch_up_correlation(
|
|
6208
|
+
this._correlate,
|
|
6209
|
+
(limit) => this.correlate({ limit }),
|
|
6210
|
+
until
|
|
6211
|
+
),
|
|
6212
|
+
event_to_state: this._event_to_state,
|
|
6213
|
+
load: this._es.load,
|
|
6214
|
+
tombstone: this._es.tombstone,
|
|
6215
|
+
logger: this._logger,
|
|
6216
|
+
correlation: close_correlation(this._correlator, close_actor),
|
|
6217
|
+
with_stream_lock: (stream, work) => this._close_lock.run(stream, work),
|
|
6218
|
+
forget_subscribed: (streams) => this._correlate.forget_subscribed(streams)
|
|
6219
|
+
});
|
|
6220
|
+
this.emit("closed", result);
|
|
6221
|
+
return result;
|
|
6222
|
+
}
|
|
6205
6223
|
/**
|
|
6206
6224
|
* Close the books — guard, archive, truncate, and optionally restart streams.
|
|
6207
6225
|
*
|
|
@@ -6260,69 +6278,11 @@ var Act = class {
|
|
|
6260
6278
|
* ]);
|
|
6261
6279
|
* ```
|
|
6262
6280
|
*/
|
|
6263
|
-
/**
|
|
6264
|
-
* After a close, forget any target whose subscription row the truncate
|
|
6265
|
-
* removed, so a later correlate can re-subscribe it. Restart
|
|
6266
|
-
* targets keep their row, so only fully-retired streams are forgotten.
|
|
6267
|
-
*/
|
|
6268
|
-
/**
|
|
6269
|
-
* Advance correlation until the read cursor reaches `until`, and report
|
|
6270
|
-
* where it landed. Used by the close cycle's safety probe, which
|
|
6271
|
-
* cannot judge a subscription's pending work over events correlate has
|
|
6272
|
-
* not resolved yet.
|
|
6273
|
-
*
|
|
6274
|
-
* Bounded on both ends: it stops as soon as a pass makes no progress (the
|
|
6275
|
-
* log has no more to give), and after {@link CLOSE_CATCH_UP_PASSES}
|
|
6276
|
-
* windows, so a close behind an enormous backlog degrades to skipping the
|
|
6277
|
-
* stream — the documented retryable outcome — instead of scanning the
|
|
6278
|
-
* whole log inside an operator call.
|
|
6279
|
-
*/
|
|
6280
|
-
async _catch_up_correlation(until) {
|
|
6281
|
-
for (let pass = 0; pass < CLOSE_CATCH_UP_PASSES && this._correlate.checkpoint < until; pass++) {
|
|
6282
|
-
const before = this._correlate.checkpoint;
|
|
6283
|
-
this._correlate.arm();
|
|
6284
|
-
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
6285
|
-
if (this._correlate.checkpoint <= before) break;
|
|
6286
|
-
}
|
|
6287
|
-
return this._correlate.checkpoint;
|
|
6288
|
-
}
|
|
6289
|
-
/**
|
|
6290
|
-
* Drop retired streams from correlate's in-process "already subscribed"
|
|
6291
|
-
* set, so a later scan re-issues `subscribe()` for them.
|
|
6292
|
-
*
|
|
6293
|
-
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
6294
|
-
* was restarted and is still consuming.
|
|
6295
|
-
*
|
|
6296
|
-
* `truncate` no longer removes the subscription row, so this is no
|
|
6297
|
-
* longer repairing damage the close itself did. It still matters, because
|
|
6298
|
-
* the row can disappear later: reclaiming retired subscriptions is an
|
|
6299
|
-
* operator job now, and that `DELETE` can land while this process is
|
|
6300
|
-
* running. Forgetting here keeps the in-process view from outliving a row
|
|
6301
|
-
* an operator removed, which is a silent-no-delivery failure — a reaction whose target is named after the stream would never
|
|
6302
|
-
* be re-registered.
|
|
6303
|
-
*/
|
|
6304
|
-
_forget_closed_subscriptions(result) {
|
|
6305
|
-
const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
6306
|
-
if (retired.length) this._correlate.forget_subscribed(retired);
|
|
6307
|
-
}
|
|
6308
6281
|
async close(targets) {
|
|
6309
6282
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
6310
6283
|
return this._scoped(async () => {
|
|
6311
|
-
await this.correlate({ limit:
|
|
6312
|
-
|
|
6313
|
-
const result = await run_close_cycle(targets, {
|
|
6314
|
-
reactive_events_size: this._reactive_events.size,
|
|
6315
|
-
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
6316
|
-
event_to_state: this._event_to_state,
|
|
6317
|
-
load: this._es.load,
|
|
6318
|
-
tombstone: this._es.tombstone,
|
|
6319
|
-
logger: this._logger,
|
|
6320
|
-
correlation: close_correlation(this._correlator, close_actor),
|
|
6321
|
-
with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
|
|
6322
|
-
});
|
|
6323
|
-
this._forget_closed_subscriptions(result);
|
|
6324
|
-
this.emit("closed", result);
|
|
6325
|
-
return result;
|
|
6284
|
+
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
6285
|
+
return this._run_close(targets);
|
|
6326
6286
|
});
|
|
6327
6287
|
}
|
|
6328
6288
|
/**
|