@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.cjs CHANGED
@@ -1418,8 +1418,11 @@ var InMemoryStore = class {
1418
1418
  }
1419
1419
  }
1420
1420
  this._events = this._events.filter((e) => !stream_set.has(e.stream));
1421
+ const restarted = new Set(
1422
+ full.filter((t) => t.snapshot !== void 0).map((t) => t.stream)
1423
+ );
1421
1424
  for (const stream of stream_set) {
1422
- this._streams.delete(stream);
1425
+ if (!restarted.has(stream)) this._streams.delete(stream);
1423
1426
  this._stream_versions.delete(stream);
1424
1427
  this._max_event_id_by_stream.delete(stream);
1425
1428
  this._pii.delete(stream);
@@ -3095,6 +3098,8 @@ var DEFAULT_COLD_START_BACK_SCAN = 1e4;
3095
3098
  var CorrelateCycle = class {
3096
3099
  _checkpoint = -1;
3097
3100
  _initialized = false;
3101
+ /** In-flight init, memoized for single-flight and cleared on failure. */
3102
+ _init_promise;
3098
3103
  _timer = void 0;
3099
3104
  // target → the priority it was last subscribed at. A dynamic target is
3100
3105
  // re-subscribed (so the store's `GREATEST` upsert runs) only when a later
@@ -3177,7 +3182,16 @@ var CorrelateCycle = class {
3177
3182
  */
3178
3183
  async init() {
3179
3184
  if (this._initialized) return;
3185
+ if (!this._init_promise) {
3186
+ this._init_promise = this._run_init().catch((error) => {
3187
+ this._init_promise = void 0;
3188
+ throw error;
3189
+ });
3190
+ }
3191
+ await this._init_promise;
3180
3192
  this._initialized = true;
3193
+ }
3194
+ async _run_init() {
3181
3195
  const { watermark } = await store2().subscribe([...this._static_targets]);
3182
3196
  this._checkpoint = this._has_dynamic_resolvers ? Math.max(-1, watermark - this._cold_start_back_scan) : watermark;
3183
3197
  this._on_init?.();
@@ -3186,6 +3200,26 @@ var CorrelateCycle = class {
3186
3200
  }
3187
3201
  await this._on_init_async?.();
3188
3202
  }
3203
+ /**
3204
+ * Forget targets whose subscription rows no longer exist, so a later
3205
+ * scan can re-subscribe them.
3206
+ *
3207
+ * A full close deletes the closed stream's subscription row. The
3208
+ * in-process dedup would otherwise still believe the target is
3209
+ * subscribed and never re-issue `subscribe()`, silently stopping
3210
+ * delivery for any reaction whose target is named after the stream —
3211
+ * the documented per-aggregate shape `.to(e => ({target: e.stream}))`
3212
+ * makes those two namespaces collide by construction (#1398).
3213
+ *
3214
+ * Static targets are left alone: they are subscribed once at init and
3215
+ * recorded at +Infinity so the dynamic path never re-opens them.
3216
+ */
3217
+ forget_subscribed(streams) {
3218
+ const statics = new Set(this._static_targets.map((t) => t.stream));
3219
+ for (const stream of streams) {
3220
+ if (!statics.has(stream)) this._subscribed.delete(stream);
3221
+ }
3222
+ }
3189
3223
  /**
3190
3224
  * Discover dynamic-resolver targets in the events past the checkpoint
3191
3225
  * and register any new streams via `cd.subscribe`. Static targets are
@@ -4808,6 +4842,7 @@ var Act = class {
4808
4842
  correlation: close_correlation(this._correlator, close_actor),
4809
4843
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
4810
4844
  });
4845
+ this._forget_closed_subscriptions(result);
4811
4846
  try {
4812
4847
  this.emit("closed", result);
4813
4848
  } catch (err) {
@@ -5802,6 +5837,15 @@ var Act = class {
5802
5837
  * ]);
5803
5838
  * ```
5804
5839
  */
5840
+ /**
5841
+ * After a close, forget any target whose subscription row the truncate
5842
+ * removed, so a later correlate can re-subscribe it (#1398). Restart
5843
+ * targets keep their row, so only fully-retired streams are forgotten.
5844
+ */
5845
+ _forget_closed_subscriptions(result) {
5846
+ const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
5847
+ if (retired.length) this._correlate.forget_subscribed(retired);
5848
+ }
5805
5849
  async close(targets) {
5806
5850
  if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
5807
5851
  return this._scoped(async () => {
@@ -5816,6 +5860,7 @@ var Act = class {
5816
5860
  correlation: close_correlation(this._correlator, close_actor),
5817
5861
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
5818
5862
  });
5863
+ this._forget_closed_subscriptions(result);
5819
5864
  this.emit("closed", result);
5820
5865
  return result;
5821
5866
  });