@rotorsoft/act 1.25.2 → 1.26.0
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/adapters/in-memory-store.d.ts +4 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain.d.ts +2 -1
- package/dist/@types/internal/drain.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +19 -0
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +33 -1
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-NHJQCCBS.js → chunk-BAIMGDI4.js} +8 -3
- package/dist/chunk-BAIMGDI4.js.map +1 -0
- package/dist/index.cjs +68 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +62 -7
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +7 -2
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-NHJQCCBS.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -815,6 +815,8 @@ var InMemoryStore = class {
|
|
|
815
815
|
_next_id = 0;
|
|
816
816
|
// stored stream positions and other metadata
|
|
817
817
|
_streams = /* @__PURE__ */ new Map();
|
|
818
|
+
/** Correlate checkpoint (#1484): how far the log has been READ. */
|
|
819
|
+
_correlated_at = -1;
|
|
818
820
|
// last committed version per stream — O(1) replacement for filter-on-commit
|
|
819
821
|
_stream_versions = /* @__PURE__ */ new Map();
|
|
820
822
|
// max non-snapshot event id per stream — drives the has-work probe in
|
|
@@ -832,6 +834,7 @@ var InMemoryStore = class {
|
|
|
832
834
|
_reset_indexes() {
|
|
833
835
|
this._events.length = 0;
|
|
834
836
|
this._next_id = 0;
|
|
837
|
+
this._correlated_at = -1;
|
|
835
838
|
this._stream_versions.clear();
|
|
836
839
|
this._max_event_id_by_stream.clear();
|
|
837
840
|
this._max_non_snap_event_id = -1;
|
|
@@ -1063,7 +1066,9 @@ var InMemoryStore = class {
|
|
|
1063
1066
|
* @param streams - Streams to register with optional source + priority.
|
|
1064
1067
|
* @returns subscribed count and current max watermark.
|
|
1065
1068
|
*/
|
|
1066
|
-
async subscribe(streams) {
|
|
1069
|
+
async subscribe(streams, correlated_at) {
|
|
1070
|
+
if (correlated_at !== void 0 && correlated_at > this._correlated_at)
|
|
1071
|
+
this._correlated_at = correlated_at;
|
|
1067
1072
|
await sleep();
|
|
1068
1073
|
let subscribed = 0;
|
|
1069
1074
|
for (const {
|
|
@@ -1088,7 +1093,7 @@ var InMemoryStore = class {
|
|
|
1088
1093
|
for (const s of this._streams.values()) {
|
|
1089
1094
|
if (s.at > watermark) watermark = s.at;
|
|
1090
1095
|
}
|
|
1091
|
-
return { subscribed, watermark };
|
|
1096
|
+
return { subscribed, watermark, correlated_at: this._correlated_at };
|
|
1092
1097
|
}
|
|
1093
1098
|
/**
|
|
1094
1099
|
* Acknowledge completion of processing for leased streams.
|
|
@@ -3260,8 +3265,10 @@ var CorrelateCycle = class {
|
|
|
3260
3265
|
this._initialized = true;
|
|
3261
3266
|
}
|
|
3262
3267
|
async _run_init() {
|
|
3263
|
-
const { watermark } = await store2().subscribe([
|
|
3264
|
-
|
|
3268
|
+
const { watermark, correlated_at } = await store2().subscribe([
|
|
3269
|
+
...this._static_targets
|
|
3270
|
+
]);
|
|
3271
|
+
this._checkpoint = !this._has_dynamic_resolvers ? watermark : correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
3265
3272
|
this._on_init?.();
|
|
3266
3273
|
for (const { stream } of this._static_targets) {
|
|
3267
3274
|
this._subscribed.set(stream, Number.POSITIVE_INFINITY);
|
|
@@ -3341,7 +3348,7 @@ var CorrelateCycle = class {
|
|
|
3341
3348
|
lane
|
|
3342
3349
|
})
|
|
3343
3350
|
);
|
|
3344
|
-
const { subscribed } = await this._cd.subscribe(streams);
|
|
3351
|
+
const { subscribed } = await this._cd.subscribe(streams, last_id);
|
|
3345
3352
|
this._checkpoint = last_id;
|
|
3346
3353
|
if (subscribed) {
|
|
3347
3354
|
for (const { stream, priority } of streams) {
|
|
@@ -3527,7 +3534,7 @@ async function fetch(leased, eventLimit) {
|
|
|
3527
3534
|
}
|
|
3528
3535
|
var ack = (leases) => store2().ack(leases);
|
|
3529
3536
|
var block = (leases) => store2().block(leases);
|
|
3530
|
-
var subscribe = (streams) => store2().subscribe(streams);
|
|
3537
|
+
var subscribe = (streams, correlated_at) => store2().subscribe(streams, correlated_at);
|
|
3531
3538
|
|
|
3532
3539
|
// src/internal/event-sourcing.ts
|
|
3533
3540
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
@@ -4589,6 +4596,19 @@ function build_handle_batch(logger) {
|
|
|
4589
4596
|
var SettleLoop = class {
|
|
4590
4597
|
_timer = void 0;
|
|
4591
4598
|
_running = false;
|
|
4599
|
+
/**
|
|
4600
|
+
* Resolves when the cycle currently in flight finishes; `undefined` when
|
|
4601
|
+
* idle (#1468). `_running` answers "is a cycle running?" for the
|
|
4602
|
+
* re-arm bookkeeping; this answers "tell me when it is done" for a
|
|
4603
|
+
* graceful shutdown.
|
|
4604
|
+
*
|
|
4605
|
+
* `stop()` only cancels *scheduling* — a cycle already inside its
|
|
4606
|
+
* correlate → drain loop keeps going, and because `DrainController.drain`
|
|
4607
|
+
* does not consult `_stopped`, it can claim a stream after teardown
|
|
4608
|
+
* returned and after the store adapter was disposed. Never rejects: the
|
|
4609
|
+
* cycle's own `catch` contains its errors, so awaiting this is safe.
|
|
4610
|
+
*/
|
|
4611
|
+
_inflight;
|
|
4592
4612
|
/**
|
|
4593
4613
|
* Set when a `schedule()` timer fires while a cycle is still running
|
|
4594
4614
|
* (ACT-1205). The in-flight cycle's `finally` re-arms one more pass so
|
|
@@ -4628,6 +4648,10 @@ var SettleLoop = class {
|
|
|
4628
4648
|
return;
|
|
4629
4649
|
}
|
|
4630
4650
|
this._running = true;
|
|
4651
|
+
let settle_done;
|
|
4652
|
+
this._inflight = new Promise((done) => {
|
|
4653
|
+
settle_done = done;
|
|
4654
|
+
});
|
|
4631
4655
|
(async () => {
|
|
4632
4656
|
await this._deps.init();
|
|
4633
4657
|
let settled_drain;
|
|
@@ -4653,6 +4677,8 @@ var SettleLoop = class {
|
|
|
4653
4677
|
this._deps.breaker.failed(Date.now(), err);
|
|
4654
4678
|
}).finally(() => {
|
|
4655
4679
|
this._running = false;
|
|
4680
|
+
this._inflight = void 0;
|
|
4681
|
+
settle_done();
|
|
4656
4682
|
const pending = this._pending;
|
|
4657
4683
|
if (pending !== void 0) {
|
|
4658
4684
|
this._pending = void 0;
|
|
@@ -4661,6 +4687,14 @@ var SettleLoop = class {
|
|
|
4661
4687
|
});
|
|
4662
4688
|
}, debounceMs);
|
|
4663
4689
|
}
|
|
4690
|
+
/**
|
|
4691
|
+
* The cycle currently in flight, or `undefined` when idle (#1468). A
|
|
4692
|
+
* graceful shutdown awaits this alongside the drain controllers so a
|
|
4693
|
+
* settle parked in `correlate` does not resume after teardown.
|
|
4694
|
+
*/
|
|
4695
|
+
get inflight() {
|
|
4696
|
+
return this._inflight;
|
|
4697
|
+
}
|
|
4664
4698
|
/** Cancel any pending or active settle cycle. Idempotent. */
|
|
4665
4699
|
stop() {
|
|
4666
4700
|
this._pending = void 0;
|
|
@@ -5203,10 +5237,12 @@ var Act = class {
|
|
|
5203
5237
|
const running = [...this._drain_controllers.values()].filter(
|
|
5204
5238
|
(c) => c.inflight !== void 0
|
|
5205
5239
|
);
|
|
5206
|
-
|
|
5240
|
+
const settling = this._settle.inflight;
|
|
5241
|
+
if (running.length === 0 && !settling) return;
|
|
5207
5242
|
const grace = grace_ms ?? this._derive_grace_ms(running);
|
|
5208
5243
|
if (grace <= 0) return;
|
|
5209
5244
|
const inflight = running.map((c) => c.inflight);
|
|
5245
|
+
if (settling) inflight.push(settling);
|
|
5210
5246
|
let timer;
|
|
5211
5247
|
const budget = new Promise((resolve) => {
|
|
5212
5248
|
timer = setTimeout(resolve, grace);
|
|
@@ -6158,6 +6194,14 @@ function act() {
|
|
|
6158
6194
|
autoclose_archiver: (state_name) => _aa.get(state_name) ?? null
|
|
6159
6195
|
};
|
|
6160
6196
|
const pending_projections = [];
|
|
6197
|
+
const projection_reactions = /* @__PURE__ */ new Set();
|
|
6198
|
+
const record_projection_reactions = (proj) => {
|
|
6199
|
+
for (const register of Object.values(
|
|
6200
|
+
proj.events
|
|
6201
|
+
))
|
|
6202
|
+
for (const reaction of register.reactions.values())
|
|
6203
|
+
projection_reactions.add(reaction);
|
|
6204
|
+
};
|
|
6161
6205
|
const fold_projections = [];
|
|
6162
6206
|
const batch_handlers = /* @__PURE__ */ new Map();
|
|
6163
6207
|
const fold_specs = [];
|
|
@@ -6254,6 +6298,7 @@ function act() {
|
|
|
6254
6298
|
return builder;
|
|
6255
6299
|
},
|
|
6256
6300
|
withProjection: (proj) => {
|
|
6301
|
+
record_projection_reactions(proj);
|
|
6257
6302
|
merge_projection(proj, registry.events);
|
|
6258
6303
|
register_batch_handler(proj, batch_handlers);
|
|
6259
6304
|
if (proj.fold)
|
|
@@ -6271,6 +6316,7 @@ function act() {
|
|
|
6271
6316
|
const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
|
|
6272
6317
|
if (!_built) {
|
|
6273
6318
|
for (const proj of pending_projections) {
|
|
6319
|
+
record_projection_reactions(proj);
|
|
6274
6320
|
merge_projection(proj, registry.events);
|
|
6275
6321
|
register_batch_handler(proj, batch_handlers);
|
|
6276
6322
|
if (proj.fold) fold_projections.push(proj);
|
|
@@ -6289,17 +6335,31 @@ function act() {
|
|
|
6289
6335
|
throw new Error(
|
|
6290
6336
|
`State projection "${proj.target}" of "${fold.name}" is missing events ${missing.join(", ")} \u2014 pass every partial of the state to .of()`
|
|
6291
6337
|
);
|
|
6292
|
-
|
|
6338
|
+
const claimed = fold_specs.find((s) => s.target === proj.target);
|
|
6339
|
+
if (claimed?.projection === proj) continue;
|
|
6340
|
+
if (batch_handlers.has(proj.target) || claimed)
|
|
6293
6341
|
throw new Error(
|
|
6294
6342
|
`Duplicate projection target "${proj.target}" \u2014 a target is served by one batch handler or one state projection, never both`
|
|
6295
6343
|
);
|
|
6296
6344
|
fold_specs.push({
|
|
6297
6345
|
target: proj.target,
|
|
6346
|
+
projection: proj,
|
|
6298
6347
|
merged,
|
|
6299
6348
|
flush: fold.flush,
|
|
6300
6349
|
config: fold.config
|
|
6301
6350
|
});
|
|
6302
6351
|
}
|
|
6352
|
+
for (const register of Object.values(registry.events)) {
|
|
6353
|
+
for (const reaction of register.reactions.values()) {
|
|
6354
|
+
if (typeof reaction.resolver === "function") continue;
|
|
6355
|
+
const target = reaction.resolver.target;
|
|
6356
|
+
const claimed = batch_handlers.has(target) || fold_specs.some((f) => f.target === target);
|
|
6357
|
+
if (claimed && !projection_reactions.has(reaction))
|
|
6358
|
+
throw new Error(
|
|
6359
|
+
`Reaction on target "${target}" conflicts with the projection that already serves it \u2014 a target is served by one batch handler or one state projection, and a reaction to it would never run`
|
|
6360
|
+
);
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6303
6363
|
finalize_deprecations();
|
|
6304
6364
|
validate_lane_references(registry, lanes);
|
|
6305
6365
|
for (const [event_name, reg] of Object.entries(
|