@rotorsoft/act 1.25.2 → 1.25.3

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/index.js CHANGED
@@ -3022,6 +3022,19 @@ function build_handle_batch(logger) {
3022
3022
  var SettleLoop = class {
3023
3023
  _timer = void 0;
3024
3024
  _running = false;
3025
+ /**
3026
+ * Resolves when the cycle currently in flight finishes; `undefined` when
3027
+ * idle (#1468). `_running` answers "is a cycle running?" for the
3028
+ * re-arm bookkeeping; this answers "tell me when it is done" for a
3029
+ * graceful shutdown.
3030
+ *
3031
+ * `stop()` only cancels *scheduling* — a cycle already inside its
3032
+ * correlate → drain loop keeps going, and because `DrainController.drain`
3033
+ * does not consult `_stopped`, it can claim a stream after teardown
3034
+ * returned and after the store adapter was disposed. Never rejects: the
3035
+ * cycle's own `catch` contains its errors, so awaiting this is safe.
3036
+ */
3037
+ _inflight;
3025
3038
  /**
3026
3039
  * Set when a `schedule()` timer fires while a cycle is still running
3027
3040
  * (ACT-1205). The in-flight cycle's `finally` re-arms one more pass so
@@ -3061,6 +3074,10 @@ var SettleLoop = class {
3061
3074
  return;
3062
3075
  }
3063
3076
  this._running = true;
3077
+ let settle_done;
3078
+ this._inflight = new Promise((done) => {
3079
+ settle_done = done;
3080
+ });
3064
3081
  (async () => {
3065
3082
  await this._deps.init();
3066
3083
  let settled_drain;
@@ -3086,6 +3103,8 @@ var SettleLoop = class {
3086
3103
  this._deps.breaker.failed(Date.now(), err);
3087
3104
  }).finally(() => {
3088
3105
  this._running = false;
3106
+ this._inflight = void 0;
3107
+ settle_done();
3089
3108
  const pending = this._pending;
3090
3109
  if (pending !== void 0) {
3091
3110
  this._pending = void 0;
@@ -3094,6 +3113,14 @@ var SettleLoop = class {
3094
3113
  });
3095
3114
  }, debounceMs);
3096
3115
  }
3116
+ /**
3117
+ * The cycle currently in flight, or `undefined` when idle (#1468). A
3118
+ * graceful shutdown awaits this alongside the drain controllers so a
3119
+ * settle parked in `correlate` does not resume after teardown.
3120
+ */
3121
+ get inflight() {
3122
+ return this._inflight;
3123
+ }
3097
3124
  /** Cancel any pending or active settle cycle. Idempotent. */
3098
3125
  stop() {
3099
3126
  this._pending = void 0;
@@ -3636,10 +3663,12 @@ var Act = class {
3636
3663
  const running = [...this._drain_controllers.values()].filter(
3637
3664
  (c) => c.inflight !== void 0
3638
3665
  );
3639
- if (running.length === 0) return;
3666
+ const settling = this._settle.inflight;
3667
+ if (running.length === 0 && !settling) return;
3640
3668
  const grace = grace_ms ?? this._derive_grace_ms(running);
3641
3669
  if (grace <= 0) return;
3642
3670
  const inflight = running.map((c) => c.inflight);
3671
+ if (settling) inflight.push(settling);
3643
3672
  let timer;
3644
3673
  const budget = new Promise((resolve) => {
3645
3674
  timer = setTimeout(resolve, grace);
@@ -4591,6 +4620,14 @@ function act() {
4591
4620
  autoclose_archiver: (state_name) => _aa.get(state_name) ?? null
4592
4621
  };
4593
4622
  const pending_projections = [];
4623
+ const projection_reactions = /* @__PURE__ */ new Set();
4624
+ const record_projection_reactions = (proj) => {
4625
+ for (const register of Object.values(
4626
+ proj.events
4627
+ ))
4628
+ for (const reaction of register.reactions.values())
4629
+ projection_reactions.add(reaction);
4630
+ };
4594
4631
  const fold_projections = [];
4595
4632
  const batch_handlers = /* @__PURE__ */ new Map();
4596
4633
  const fold_specs = [];
@@ -4687,6 +4724,7 @@ function act() {
4687
4724
  return builder;
4688
4725
  },
4689
4726
  withProjection: (proj) => {
4727
+ record_projection_reactions(proj);
4690
4728
  merge_projection(proj, registry.events);
4691
4729
  register_batch_handler(proj, batch_handlers);
4692
4730
  if (proj.fold)
@@ -4704,6 +4742,7 @@ function act() {
4704
4742
  const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
4705
4743
  if (!_built) {
4706
4744
  for (const proj of pending_projections) {
4745
+ record_projection_reactions(proj);
4707
4746
  merge_projection(proj, registry.events);
4708
4747
  register_batch_handler(proj, batch_handlers);
4709
4748
  if (proj.fold) fold_projections.push(proj);
@@ -4722,17 +4761,31 @@ function act() {
4722
4761
  throw new Error(
4723
4762
  `State projection "${proj.target}" of "${fold.name}" is missing events ${missing.join(", ")} \u2014 pass every partial of the state to .of()`
4724
4763
  );
4725
- if (batch_handlers.has(proj.target) || fold_specs.some((s) => s.target === proj.target))
4764
+ const claimed = fold_specs.find((s) => s.target === proj.target);
4765
+ if (claimed?.projection === proj) continue;
4766
+ if (batch_handlers.has(proj.target) || claimed)
4726
4767
  throw new Error(
4727
4768
  `Duplicate projection target "${proj.target}" \u2014 a target is served by one batch handler or one state projection, never both`
4728
4769
  );
4729
4770
  fold_specs.push({
4730
4771
  target: proj.target,
4772
+ projection: proj,
4731
4773
  merged,
4732
4774
  flush: fold.flush,
4733
4775
  config: fold.config
4734
4776
  });
4735
4777
  }
4778
+ for (const register of Object.values(registry.events)) {
4779
+ for (const reaction of register.reactions.values()) {
4780
+ if (typeof reaction.resolver === "function") continue;
4781
+ const target = reaction.resolver.target;
4782
+ const claimed = batch_handlers.has(target) || fold_specs.some((f) => f.target === target);
4783
+ if (claimed && !projection_reactions.has(reaction))
4784
+ throw new Error(
4785
+ `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`
4786
+ );
4787
+ }
4788
+ }
4736
4789
  finalize_deprecations();
4737
4790
  validate_lane_references(registry, lanes);
4738
4791
  for (const [event_name, reg] of Object.entries(