@rotorsoft/act 1.22.2 → 1.22.4

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
@@ -811,7 +811,7 @@ var InMemoryStore = class {
811
811
  _streams = /* @__PURE__ */ new Map();
812
812
  // last committed version per stream — O(1) replacement for filter-on-commit
813
813
  _stream_versions = /* @__PURE__ */ new Map();
814
- // max non-snapshot event id per stream — drives the source-pattern probe in claim()
814
+ // max non-snapshot event id per stream — drives the exact-source probe in claim()
815
815
  // without scanning the full event log.
816
816
  _max_event_id_by_stream = /* @__PURE__ */ new Map();
817
817
  // global max non-snapshot event id — fast pre-check for source-less streams in claim()
@@ -999,23 +999,11 @@ var InMemoryStore = class {
999
999
  */
1000
1000
  async claim(lagging, leading, by, millis, lane) {
1001
1001
  await sleep();
1002
- const source_regex = /* @__PURE__ */ new Map();
1003
- const get_regex = (source) => {
1004
- let re = source_regex.get(source);
1005
- if (!re) {
1006
- re = new RegExp(source);
1007
- source_regex.set(source, re);
1008
- }
1009
- return re;
1010
- };
1011
1002
  const has_work = (s) => {
1012
1003
  if (s.at < 0) return true;
1013
1004
  if (!s.source) return s.at < this._max_non_snap_event_id;
1014
- const re = get_regex(s.source);
1015
- for (const [stream_name, maxId] of this._max_event_id_by_stream) {
1016
- if (maxId > s.at && re.test(stream_name)) return true;
1017
- }
1018
- return false;
1005
+ const max_id = this._max_event_id_by_stream.get(s.source);
1006
+ return max_id !== void 0 && max_id > s.at;
1019
1007
  };
1020
1008
  const available = [...this._streams.values()].filter(
1021
1009
  (s) => s.is_available && has_work(s) && (lane === void 0 || s.lane === lane)
@@ -2899,13 +2887,22 @@ var CorrelateCycle = class {
2899
2887
  _has_dynamic_resolvers;
2900
2888
  _cd;
2901
2889
  _on_init;
2902
- constructor(registry, static_targets, has_dynamic_resolvers, cd, maxSubscribedStreams, on_init) {
2890
+ /**
2891
+ * Scope runner (#1191). The periodic `start_polling` timer fires
2892
+ * outside any caller frame, so its `correlate()` must be re-wrapped in
2893
+ * the Act's `_scoped` bag or `store()`/`cache()` resolve to the
2894
+ * singleton for a scoped Act. The orchestrator always threads its
2895
+ * `_scoped` (identity for a non-scoped Act), so it's required.
2896
+ */
2897
+ _run_scoped;
2898
+ constructor(registry, static_targets, has_dynamic_resolvers, cd, maxSubscribedStreams, on_init, run_scoped) {
2903
2899
  this._subscribed = new LruSet(maxSubscribedStreams);
2904
2900
  this._registry = registry;
2905
2901
  this._static_targets = static_targets;
2906
2902
  this._has_dynamic_resolvers = has_dynamic_resolvers;
2907
2903
  this._cd = cd;
2908
2904
  this._on_init = on_init;
2905
+ this._run_scoped = run_scoped;
2909
2906
  }
2910
2907
  /** Last correlated event id. */
2911
2908
  get checkpoint() {
@@ -3000,7 +2997,9 @@ var CorrelateCycle = class {
3000
2997
  if (this._timer) return false;
3001
2998
  const limit = query.limit || 100;
3002
2999
  this._timer = setInterval(
3003
- () => this.correlate({ ...query, after: this._checkpoint, limit }).then((result) => {
3000
+ () => this._run_scoped(
3001
+ () => this.correlate({ ...query, after: this._checkpoint, limit })
3002
+ ).then((result) => {
3004
3003
  if (callback && result.subscribed) callback(result.subscribed);
3005
3004
  }).catch((err) => log().error(err)),
3006
3005
  frequency
@@ -3305,12 +3304,15 @@ async function scan(source, opts = {}, callback) {
3305
3304
  const new_caused_by = id_map.get(caused_by);
3306
3305
  if (new_caused_by !== void 0 && new_caused_by !== caused_by) {
3307
3306
  remapped = {
3308
- ...event,
3307
+ ...migrated,
3309
3308
  meta: {
3310
- ...event.meta,
3309
+ ...migrated.meta,
3311
3310
  causation: {
3312
- ...event.meta.causation,
3313
- event: { ...event.meta.causation.event, id: new_caused_by }
3311
+ ...migrated.meta.causation,
3312
+ event: {
3313
+ ...migrated.meta.causation.event,
3314
+ id: new_caused_by
3315
+ }
3314
3316
  }
3315
3317
  }
3316
3318
  };
@@ -3377,7 +3379,8 @@ async function load(me, target, callback) {
3377
3379
  ...cached ? { after: cached.event_id } : { with_snaps: true, ...asOf }
3378
3380
  }
3379
3381
  );
3380
- if (replayed > 0 && !time_travel && event && !me.pii_aware) {
3382
+ const head_is_tombstone = event?.name === TOMBSTONE_EVENT;
3383
+ if (replayed > 0 && !time_travel && event && !me.pii_aware && !head_is_tombstone) {
3381
3384
  await cache2().set(stream, {
3382
3385
  stream,
3383
3386
  state: state2,
@@ -3811,8 +3814,9 @@ var DrainController = class {
3811
3814
  */
3812
3815
  start(cycleMs) {
3813
3816
  if (this._worker || this._stopped) return;
3817
+ const run = this._deps.run_scoped;
3814
3818
  const tick = async () => {
3815
- if (this._armed) await this.drain();
3819
+ if (this._armed) await run(() => this.drain());
3816
3820
  if (this._stopped) return;
3817
3821
  this._worker = setTimeout(tick, cycleMs);
3818
3822
  this._worker.unref();
@@ -4413,6 +4417,9 @@ var Act = class {
4413
4417
  this.emit("closed", result);
4414
4418
  },
4415
4419
  breaker: this._breaker,
4420
+ // Re-scope the per-lane worker's auto-start ticks so their drain
4421
+ // resolves the scoped ports, not the singleton (#1191).
4422
+ run_scoped: this._scoped,
4416
4423
  // Pass lane only when a true per-lane controller is active.
4417
4424
  // The all-lanes (single default) case keeps lane=undefined so
4418
4425
  // adapter SQL collapses to the single-lane shape.
@@ -4459,14 +4466,21 @@ var Act = class {
4459
4466
  options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
4460
4467
  () => {
4461
4468
  if (this._drain && this._reactive_events.size > 0) this._arm_all();
4462
- }
4469
+ },
4470
+ // Re-scope the background `start_correlations` timer so its
4471
+ // correlate resolves the scoped ports, not the singleton (#1191).
4472
+ this._scoped
4463
4473
  );
4464
4474
  }
4465
4475
  /** Settle loop driving correlate + drain to quiescence. */
4466
4476
  _build_settle(options) {
4467
4477
  return new SettleLoop(
4468
4478
  {
4469
- init: () => this._correlate.init(),
4479
+ // Scope the init like every other store-touching path — a bare
4480
+ // `this._correlate.init()` runs `store().subscribe(...)` against
4481
+ // the singleton for a scoped Act, so static targets never land on
4482
+ // the scoped store and `_initialized` then blocks a retry (#1191).
4483
+ init: () => this._scoped(() => this._correlate.init()),
4470
4484
  checkpoint: () => this._correlate.checkpoint,
4471
4485
  correlate: (q) => this.correlate(q),
4472
4486
  drain: (o) => this.drain(o),