@rotorsoft/act 1.23.33 → 1.23.34

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
@@ -21,7 +21,7 @@ import {
21
21
  sleep,
22
22
  store,
23
23
  validate
24
- } from "./chunk-5B45U2IS.js";
24
+ } from "./chunk-TTOT5CRM.js";
25
25
  import {
26
26
  ActorSchema,
27
27
  CausationEventSchema,
@@ -1573,6 +1573,8 @@ var DEFAULT_COLD_START_BACK_SCAN = 1e4;
1573
1573
  var CorrelateCycle = class {
1574
1574
  _checkpoint = -1;
1575
1575
  _initialized = false;
1576
+ /** In-flight init, memoized for single-flight and cleared on failure. */
1577
+ _init_promise;
1576
1578
  _timer = void 0;
1577
1579
  // target → the priority it was last subscribed at. A dynamic target is
1578
1580
  // re-subscribed (so the store's `GREATEST` upsert runs) only when a later
@@ -1655,7 +1657,16 @@ var CorrelateCycle = class {
1655
1657
  */
1656
1658
  async init() {
1657
1659
  if (this._initialized) return;
1660
+ if (!this._init_promise) {
1661
+ this._init_promise = this._run_init().catch((error) => {
1662
+ this._init_promise = void 0;
1663
+ throw error;
1664
+ });
1665
+ }
1666
+ await this._init_promise;
1658
1667
  this._initialized = true;
1668
+ }
1669
+ async _run_init() {
1659
1670
  const { watermark } = await store().subscribe([...this._static_targets]);
1660
1671
  this._checkpoint = this._has_dynamic_resolvers ? Math.max(-1, watermark - this._cold_start_back_scan) : watermark;
1661
1672
  this._on_init?.();
@@ -1664,6 +1675,26 @@ var CorrelateCycle = class {
1664
1675
  }
1665
1676
  await this._on_init_async?.();
1666
1677
  }
1678
+ /**
1679
+ * Forget targets whose subscription rows no longer exist, so a later
1680
+ * scan can re-subscribe them.
1681
+ *
1682
+ * A full close deletes the closed stream's subscription row. The
1683
+ * in-process dedup would otherwise still believe the target is
1684
+ * subscribed and never re-issue `subscribe()`, silently stopping
1685
+ * delivery for any reaction whose target is named after the stream —
1686
+ * the documented per-aggregate shape `.to(e => ({target: e.stream}))`
1687
+ * makes those two namespaces collide by construction (#1398).
1688
+ *
1689
+ * Static targets are left alone: they are subscribed once at init and
1690
+ * recorded at +Infinity so the dynamic path never re-opens them.
1691
+ */
1692
+ forget_subscribed(streams) {
1693
+ const statics = new Set(this._static_targets.map((t) => t.stream));
1694
+ for (const stream of streams) {
1695
+ if (!statics.has(stream)) this._subscribed.delete(stream);
1696
+ }
1697
+ }
1667
1698
  /**
1668
1699
  * Discover dynamic-resolver targets in the events past the checkpoint
1669
1700
  * and register any new streams via `cd.subscribe`. Static targets are
@@ -3286,6 +3317,7 @@ var Act = class {
3286
3317
  correlation: close_correlation(this._correlator, close_actor),
3287
3318
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
3288
3319
  });
3320
+ this._forget_closed_subscriptions(result);
3289
3321
  try {
3290
3322
  this.emit("closed", result);
3291
3323
  } catch (err) {
@@ -4280,6 +4312,15 @@ var Act = class {
4280
4312
  * ]);
4281
4313
  * ```
4282
4314
  */
4315
+ /**
4316
+ * After a close, forget any target whose subscription row the truncate
4317
+ * removed, so a later correlate can re-subscribe it (#1398). Restart
4318
+ * targets keep their row, so only fully-retired streams are forgotten.
4319
+ */
4320
+ _forget_closed_subscriptions(result) {
4321
+ const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
4322
+ if (retired.length) this._correlate.forget_subscribed(retired);
4323
+ }
4283
4324
  async close(targets) {
4284
4325
  if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
4285
4326
  return this._scoped(async () => {
@@ -4294,6 +4335,7 @@ var Act = class {
4294
4335
  correlation: close_correlation(this._correlator, close_actor),
4295
4336
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
4296
4337
  });
4338
+ this._forget_closed_subscriptions(result);
4297
4339
  this.emit("closed", result);
4298
4340
  return result;
4299
4341
  });