@rotorsoft/act 1.32.21 → 1.32.22

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.
Files changed (33) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/@types/act.d.ts +1 -2
  3. package/dist/@types/act.d.ts.map +1 -1
  4. package/dist/@types/adapters/in-memory-store.d.ts +2 -6
  5. package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
  6. package/dist/@types/builders/act-builder.d.ts.map +1 -1
  7. package/dist/@types/internal/audit.d.ts +1 -4
  8. package/dist/@types/internal/audit.d.ts.map +1 -1
  9. package/dist/@types/internal/config.d.ts +3 -9
  10. package/dist/@types/internal/config.d.ts.map +1 -1
  11. package/dist/@types/internal/correlate-cycle.d.ts +1 -11
  12. package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
  13. package/dist/@types/internal/defer-timer.d.ts +0 -5
  14. package/dist/@types/internal/defer-timer.d.ts.map +1 -1
  15. package/dist/@types/internal/drain-cycle.d.ts +3 -5
  16. package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
  17. package/dist/@types/internal/index.d.ts +1 -1
  18. package/dist/@types/internal/index.d.ts.map +1 -1
  19. package/dist/@types/types/ports.d.ts +10 -10
  20. package/dist/@types/types/ports.d.ts.map +1 -1
  21. package/dist/@types/utils.d.ts +3 -2
  22. package/dist/@types/utils.d.ts.map +1 -1
  23. package/dist/{chunk-QOSQI3CA.js → chunk-T3CWXVGT.js} +14 -55
  24. package/dist/chunk-T3CWXVGT.js.map +1 -0
  25. package/dist/index.cjs +39 -105
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.js +27 -52
  28. package/dist/index.js.map +1 -1
  29. package/dist/test/index.cjs +13 -54
  30. package/dist/test/index.cjs.map +1 -1
  31. package/dist/test/index.js +1 -1
  32. package/package.json +1 -1
  33. package/dist/chunk-QOSQI3CA.js.map +0 -1
package/dist/index.cjs CHANGED
@@ -641,8 +641,7 @@ var InMemoryStream = class {
641
641
  // in-process backoff, this is durable store state shared across workers.
642
642
  _deferred_at = void 0;
643
643
  // Work mark: highest event id observed to resolve to this target.
644
- // `undefined` means UNKNOWN — the row predates the mark, so `claim` falls
645
- // back to the legacy has-work probe rather than treating it as "no work".
644
+ // `undefined` means the row was never correlated, so `claim` skips it.
646
645
  _correlated_at = void 0;
647
646
  constructor(stream, source, priority = 0, lane = DEFAULT_LANE) {
648
647
  this.stream = stream;
@@ -845,12 +844,6 @@ var InMemoryStore = class {
845
844
  _correlators = /* @__PURE__ */ new Map();
846
845
  // last committed version per stream — O(1) replacement for filter-on-commit
847
846
  _stream_versions = /* @__PURE__ */ new Map();
848
- // max non-snapshot event id per stream — drives the has-work probe in
849
- // claim(): an O(1) lookup for a literal source, and the per-stream max
850
- // that a pattern source scans, without touching the full event log.
851
- _max_event_id_by_stream = /* @__PURE__ */ new Map();
852
- // global max non-snapshot event id — fast pre-check for source-less streams in claim()
853
- _max_non_snap_event_id = -1;
854
847
  // stream → (event_id → cloned sensitive payload). Two-level so `forget_pii`
855
848
  // is O(1) — drop the inner Map for the stream and the wipe is done — mirroring
856
849
  // the `DELETE WHERE stream = ?` scope that durable adapters get from their
@@ -862,10 +855,17 @@ var InMemoryStore = class {
862
855
  this._next_id = 0;
863
856
  this._correlated_at = -1;
864
857
  this._stream_versions.clear();
865
- this._max_event_id_by_stream.clear();
866
- this._max_non_snap_event_id = -1;
867
858
  this._pii.clear();
868
859
  }
860
+ // Stores a cloned copy, so caller-side mutation can't reach it.
861
+ _put_pii(stream, id, pii) {
862
+ let per_stream = this._pii.get(stream);
863
+ if (!per_stream) {
864
+ per_stream = /* @__PURE__ */ new Map();
865
+ this._pii.set(stream, per_stream);
866
+ }
867
+ per_stream.set(id, structuredClone(pii));
868
+ }
869
869
  // First index whose event id is greater than `after`. `_events` stays
870
870
  // sorted ascending by id (append-only commits; deletions preserve
871
871
  // order), so id-bounded scans binary-search their start instead of
@@ -985,7 +985,6 @@ var InMemoryStore = class {
985
985
  );
986
986
  }
987
987
  let version = current_version + 1;
988
- let last_non_snap_id = -1;
989
988
  const committed = msgs.map(({ name, data, pii }) => {
990
989
  const c = {
991
990
  id: this._next_id++,
@@ -997,23 +996,11 @@ var InMemoryStore = class {
997
996
  meta
998
997
  };
999
998
  this._events.push(c);
1000
- if (pii != null) {
1001
- let per_stream = this._pii.get(stream);
1002
- if (!per_stream) {
1003
- per_stream = /* @__PURE__ */ new Map();
1004
- this._pii.set(stream, per_stream);
1005
- }
1006
- per_stream.set(c.id, structuredClone(pii));
1007
- }
1008
- if (name !== SNAP_EVENT) last_non_snap_id = c.id;
999
+ if (pii != null) this._put_pii(stream, c.id, pii);
1009
1000
  version++;
1010
1001
  return this._with_pii(c);
1011
1002
  });
1012
1003
  this._stream_versions.set(stream, version - 1);
1013
- if (last_non_snap_id >= 0) {
1014
- this._max_event_id_by_stream.set(stream, last_non_snap_id);
1015
- this._max_non_snap_event_id = last_non_snap_id;
1016
- }
1017
1004
  return committed;
1018
1005
  }
1019
1006
  /**
@@ -1363,10 +1350,7 @@ var InMemoryStore = class {
1363
1350
  * Scope rules:
1364
1351
  * - Array `input` — explicit stream names, regardless of subscription.
1365
1352
  * - Filter `input` — `stream`/`stream_exact` match against event-bearing
1366
- * stream names; `source`/`source_exact`/`blocked` require a
1367
- * corresponding subscription in `_streams` (those are subscription
1368
- * concepts, not event concepts). Empty filter `{}` matches every
1369
- * event-bearing stream.
1353
+ * stream names. Empty filter `{}` matches every event-bearing stream.
1370
1354
  */
1371
1355
  async query_stats(input, options) {
1372
1356
  await sleep();
@@ -1472,7 +1456,6 @@ var InMemoryStore = class {
1472
1456
  return false;
1473
1457
  });
1474
1458
  this._stream_versions.delete(stream);
1475
- this._max_event_id_by_stream.delete(stream);
1476
1459
  this._pii.delete(stream);
1477
1460
  const event = {
1478
1461
  id: this._next_id++,
@@ -1485,15 +1468,8 @@ var InMemoryStore = class {
1485
1468
  };
1486
1469
  this._events.push(event);
1487
1470
  this._stream_versions.set(stream, 0);
1488
- if (event.name !== SNAP_EVENT) {
1489
- this._max_event_id_by_stream.set(stream, event.id);
1490
- }
1491
1471
  result.set(stream, { deleted, committed: event });
1492
1472
  }
1493
- let max = -1;
1494
- for (const id of this._max_event_id_by_stream.values())
1495
- if (id > max) max = id;
1496
- this._max_non_snap_event_id = max;
1497
1473
  return result;
1498
1474
  }
1499
1475
  /**
@@ -1514,8 +1490,6 @@ var InMemoryStore = class {
1514
1490
  const prev_next_id = this._next_id;
1515
1491
  const prev_streams = this._streams;
1516
1492
  const prev_stream_versions = this._stream_versions;
1517
- const prev_max_event_id_by_stream = this._max_event_id_by_stream;
1518
- const prev_max_non_snap_event_id = this._max_non_snap_event_id;
1519
1493
  const prev_pii = this._pii;
1520
1494
  const prev_correlated_at = this._correlated_at;
1521
1495
  const prev_correlators = new Map(
@@ -1527,8 +1501,6 @@ var InMemoryStore = class {
1527
1501
  this._next_id = 0;
1528
1502
  this._streams = /* @__PURE__ */ new Map();
1529
1503
  this._stream_versions = /* @__PURE__ */ new Map();
1530
- this._max_event_id_by_stream = /* @__PURE__ */ new Map();
1531
- this._max_non_snap_event_id = -1;
1532
1504
  this._pii = /* @__PURE__ */ new Map();
1533
1505
  try {
1534
1506
  await driver(async (event) => {
@@ -1536,19 +1508,8 @@ var InMemoryStore = class {
1536
1508
  const { pii, ...rest } = event;
1537
1509
  const committed = { ...rest, id };
1538
1510
  this._events.push(committed);
1539
- if (pii != null) {
1540
- let per_stream = this._pii.get(event.stream);
1541
- if (!per_stream) {
1542
- per_stream = /* @__PURE__ */ new Map();
1543
- this._pii.set(event.stream, per_stream);
1544
- }
1545
- per_stream.set(id, structuredClone(pii));
1546
- }
1511
+ if (pii != null) this._put_pii(event.stream, id, pii);
1547
1512
  this._stream_versions.set(event.stream, event.version);
1548
- if (event.name !== SNAP_EVENT) {
1549
- this._max_event_id_by_stream.set(event.stream, id);
1550
- this._max_non_snap_event_id = id;
1551
- }
1552
1513
  return id;
1553
1514
  });
1554
1515
  } catch (err) {
@@ -1556,8 +1517,6 @@ var InMemoryStore = class {
1556
1517
  this._next_id = prev_next_id;
1557
1518
  this._streams = prev_streams;
1558
1519
  this._stream_versions = prev_stream_versions;
1559
- this._max_event_id_by_stream = prev_max_event_id_by_stream;
1560
- this._max_non_snap_event_id = prev_max_non_snap_event_id;
1561
1520
  this._pii = prev_pii;
1562
1521
  this._correlated_at = prev_correlated_at;
1563
1522
  this._correlators = prev_correlators;
@@ -2561,7 +2520,6 @@ function resolveSettleConfig(options) {
2561
2520
  return options;
2562
2521
  }
2563
2522
  var MAX_SHUTDOWN_GRACE_MS = 3e4;
2564
- var DEFAULT_SHUTDOWN_GRACE_MS = DEFAULT_LEASE_MILLIS;
2565
2523
  var ShutdownOptionsSchema = import_zod6.z.object({
2566
2524
  // `0` is legal and is exactly today's behavior: stop scheduling and
2567
2525
  // return without waiting.
@@ -3196,8 +3154,8 @@ function report_once(seen, key, message) {
3196
3154
  }
3197
3155
 
3198
3156
  // src/internal/correlate-cycle.ts
3199
- var DEFAULT_COLD_START_BACK_SCAN = 1e4;
3200
- var DEFAULT_CORRELATION_LEASE_MS = 5e3;
3157
+ var COLD_START_BACK_SCAN = 1e4;
3158
+ var CORRELATION_LEASE_MS = 5e3;
3201
3159
  var registry_key = (events) => {
3202
3160
  const shape = Object.keys(events).filter((name) => events[name].reactions.size > 0).sort().map((name) => [name, [...events[name].reactions.keys()].sort()]);
3203
3161
  return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 32);
@@ -3226,8 +3184,6 @@ var CorrelateCycle = class {
3226
3184
  * same holder and a restarted process never inherits a stale claim.
3227
3185
  */
3228
3186
  _by = (0, import_node_crypto.randomUUID)();
3229
- /** How long the correlation lease is taken for. */
3230
- _lease_millis;
3231
3187
  /**
3232
3188
  * Which correlator this is. Computed once from the registry so identical
3233
3189
  * workers share a lease and unrelated applications never do.
@@ -3280,12 +3236,6 @@ var CorrelateCycle = class {
3280
3236
  * here, so an idle deferred stream re-arms across a restart.
3281
3237
  */
3282
3238
  _on_init_async;
3283
- /**
3284
- * Tail re-scan window applied to the cold-start checkpoint.
3285
- * See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
3286
- * public option) so tests can shrink it; defaults otherwise.
3287
- */
3288
- _cold_start_back_scan;
3289
3239
  /** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
3290
3240
  _declared_lanes;
3291
3241
  /**
@@ -3302,11 +3252,8 @@ var CorrelateCycle = class {
3302
3252
  declared_lanes,
3303
3253
  shard,
3304
3254
  on_init,
3305
- on_init_async,
3306
- cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
3307
- lease_millis = DEFAULT_CORRELATION_LEASE_MS
3255
+ on_init_async
3308
3256
  }) {
3309
- this._lease_millis = lease_millis;
3310
3257
  this._key = shard ? `${registry_key(registry.events)}:${[...shard].sort().join(",")}` : registry_key(registry.events);
3311
3258
  this._dynamic_subscriptions = new LruMap(max_subscribed_streams);
3312
3259
  this._registry = registry;
@@ -3315,7 +3262,6 @@ var CorrelateCycle = class {
3315
3262
  this._cd = cd;
3316
3263
  this._on_init = on_init;
3317
3264
  this._on_init_async = on_init_async;
3318
- this._cold_start_back_scan = cold_start_back_scan;
3319
3265
  }
3320
3266
  /** Last correlated event id. */
3321
3267
  get checkpoint() {
@@ -3354,7 +3300,7 @@ var CorrelateCycle = class {
3354
3300
  const { watermark, correlated_at } = await this._cd.subscribe([
3355
3301
  ...this._static_targets
3356
3302
  ]);
3357
- this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
3303
+ this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - COLD_START_BACK_SCAN);
3358
3304
  this._on_init?.();
3359
3305
  for (const { stream, priority = 0, lane } of this._static_targets) {
3360
3306
  this._static_subscriptions.set(stream, {
@@ -3428,13 +3374,13 @@ var CorrelateCycle = class {
3428
3374
  scanned: false
3429
3375
  };
3430
3376
  const now = Date.now();
3431
- if (lease && now >= this._lease_until - this._lease_millis / 2) {
3377
+ if (lease && now >= this._lease_until - CORRELATION_LEASE_MS / 2) {
3432
3378
  const { correlating } = await this._cd.subscribe([], void 0, {
3433
3379
  key: this._key,
3434
3380
  by: this._by,
3435
- millis: this._lease_millis
3381
+ millis: CORRELATION_LEASE_MS
3436
3382
  });
3437
- this._lease_until = correlating ? now + this._lease_millis : 0;
3383
+ this._lease_until = correlating ? now + CORRELATION_LEASE_MS : 0;
3438
3384
  if (correlating === false)
3439
3385
  return {
3440
3386
  subscribed: 0,
@@ -3525,10 +3471,10 @@ var CorrelateCycle = class {
3525
3471
  const { subscribed, correlating } = await this._cd.subscribe(
3526
3472
  streams,
3527
3473
  last_id,
3528
- lease ? { key: this._key, by: this._by, millis: this._lease_millis } : void 0
3474
+ lease ? { key: this._key, by: this._by, millis: CORRELATION_LEASE_MS } : void 0
3529
3475
  );
3530
3476
  if (lease && correlating !== false)
3531
- this._lease_until = renewed_at + this._lease_millis;
3477
+ this._lease_until = renewed_at + CORRELATION_LEASE_MS;
3532
3478
  const marked = streams.filter(
3533
3479
  (entry) => entry.correlated_at !== void 0
3534
3480
  ).length;
@@ -3608,7 +3554,7 @@ var CorrelateCycle = class {
3608
3554
  });
3609
3555
  } catch (error) {
3610
3556
  log().warn(
3611
- `Could not hand back the correlation lease during shutdown; it expires on its own within ${this._lease_millis}ms and correlation resumes normally after that. On a single-writer store this usually means another connection holds the database. Cause: ${String(error)}`
3557
+ `Could not hand back the correlation lease during shutdown; it expires on its own within ${CORRELATION_LEASE_MS}ms and correlation resumes normally after that. On a single-writer store this usually means another connection holds the database. Cause: ${String(error)}`
3612
3558
  );
3613
3559
  }
3614
3560
  }
@@ -3760,14 +3706,6 @@ var DeferTimer = class {
3760
3706
  get size() {
3761
3707
  return this._due.size;
3762
3708
  }
3763
- /**
3764
- * True while `stream` is parked with a due-time still in the future.
3765
- * Consumers skip work for deferred streams until their window elapses.
3766
- */
3767
- is_deferred = (stream) => {
3768
- const next = this._due.get(stream);
3769
- return next !== void 0 && next > Date.now();
3770
- };
3771
3709
  /**
3772
3710
  * Park `stream` for a re-visit at `at` (ms since epoch). A plain
3773
3711
  * overwrite: the caller computes the authoritative next-visit for the
@@ -4450,7 +4388,7 @@ function trace_cycle(logger, leased, fetched, handled, acked, blocked) {
4450
4388
  function budget_exhausted(lease, options, store_failing) {
4451
4389
  if (store_failing || !options?.blockOnError || lease.retry <= options.maxRetries)
4452
4390
  return void 0;
4453
- const error = `Blocking ${lease.stream} after ${lease.retry} claims with no acknowledged progress \u2014 the retry budget (${options.maxRetries}) was spent without the handler ever reporting an error. That means every attempt lost its lease before it could ack: raise leaseMillis for this handler, then unblock the stream.`;
4391
+ const error = `Blocking ${lease.stream} after ${lease.retry} claims with no acknowledged progress \u2014 the retry budget (${options.maxRetries}) was spent without the handler ever reporting an error. That means every attempt lost its lease before it could ack: move the reaction to a lane whose leaseMillis covers the handler, then unblock the stream.`;
4454
4392
  log().error(error);
4455
4393
  return { lease, handled: 0, acked_at: lease.at, error, block: true };
4456
4394
  }
@@ -4530,7 +4468,9 @@ async function run_drain_cycle(deps, cycle) {
4530
4468
  handled: 0,
4531
4469
  acked_at: lease.at,
4532
4470
  error,
4533
- ...block2 ? { block: true } : { next_attempt_at: Date.now() + DEFAULT_BACKOFF.baseMs }
4471
+ ...block2 ? { block: true } : {
4472
+ next_attempt_at: Date.now() + compute_backoff_delay(lease.retry, DEFAULT_BACKOFF)
4473
+ }
4534
4474
  };
4535
4475
  return Promise.resolve(failed);
4536
4476
  }
@@ -4679,7 +4619,7 @@ var DrainController = class {
4679
4619
  if (this._locked) return EMPTY_DRAIN;
4680
4620
  const d = this._deps.defaults ?? {};
4681
4621
  const streamLimit = d.streamLimit ?? options.streamLimit ?? DEFAULT_STREAM_LIMIT;
4682
- const eventLimit = d.eventLimit ?? options.eventLimit ?? DEFAULT_EVENT_LIMIT;
4622
+ const eventLimit = options.eventLimit ?? DEFAULT_EVENT_LIMIT;
4683
4623
  const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
4684
4624
  if (this._deps.breaker.state(Date.now()) === "open")
4685
4625
  return EMPTY_DRAIN;
@@ -5343,9 +5283,7 @@ var Act = class {
5343
5283
  _build_audit_deps() {
5344
5284
  return {
5345
5285
  store: store2,
5346
- logger: this._logger,
5347
5286
  event_to_state: this._event_to_state,
5348
- states: this._states,
5349
5287
  // Declared lanes, not this process's controllers: a lane another
5350
5288
  // worker drains is not `unknown-lane`.
5351
5289
  declared_lanes: new Set(this._declared_lanes),
@@ -5452,9 +5390,9 @@ var Act = class {
5452
5390
  * fallback, capped at the maximum. Idle lanes don't count.
5453
5391
  */
5454
5392
  _derive_grace_ms(running, settling) {
5455
- let max = settling ? DEFAULT_SHUTDOWN_GRACE_MS : 0;
5393
+ let max = settling ? DEFAULT_LEASE_MILLIS : 0;
5456
5394
  for (const c of running)
5457
- max = Math.max(max, c.lease_millis || DEFAULT_SHUTDOWN_GRACE_MS);
5395
+ max = Math.max(max, c.lease_millis || DEFAULT_LEASE_MILLIS);
5458
5396
  return Math.min(max, MAX_SHUTDOWN_GRACE_MS);
5459
5397
  }
5460
5398
  async _await_inflight(grace_ms) {
@@ -5764,8 +5702,7 @@ var Act = class {
5764
5702
  * Call `correlate()` before `drain()`. It is not only how dynamic targets
5765
5703
  * are discovered: a stream is claimable while `at < correlated_at`, and
5766
5704
  * `correlate` is the only component that raises that mark, so a
5767
- * commit no correlate has seen is not drainable — including for static
5768
- * targets, which were served by a probe of the event log before. For a
5705
+ * commit no correlate has seen is not drainable, static targets included. For a
5769
5706
  * higher-level API that handles debouncing, correlation, and signaling
5770
5707
  * automatically, use {@link settle}.
5771
5708
  *
@@ -6771,6 +6708,12 @@ function act() {
6771
6708
  };
6772
6709
  const fold_projections = [];
6773
6710
  const batch_handlers = /* @__PURE__ */ new Map();
6711
+ const add_projection = (proj) => {
6712
+ record_projection_reactions(proj);
6713
+ merge_projection(proj, registry.events);
6714
+ register_batch_handler(proj, batch_handlers);
6715
+ if (proj.fold) fold_projections.push(proj);
6716
+ };
6774
6717
  const fold_specs = [];
6775
6718
  const lanes = [];
6776
6719
  let _built = false;
@@ -6870,11 +6813,7 @@ function act() {
6870
6813
  },
6871
6814
  withProjection: (proj) => {
6872
6815
  if (_built) closed("withProjection");
6873
- record_projection_reactions(proj);
6874
- merge_projection(proj, registry.events);
6875
- register_batch_handler(proj, batch_handlers);
6876
- if (proj.fold)
6877
- fold_projections.push(proj);
6816
+ add_projection(proj);
6878
6817
  return builder;
6879
6818
  },
6880
6819
  withActor: () => builder,
@@ -6891,12 +6830,7 @@ function act() {
6891
6830
  resolveActConfig(options);
6892
6831
  const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
6893
6832
  if (!_built) {
6894
- for (const proj of pending_projections) {
6895
- record_projection_reactions(proj);
6896
- merge_projection(proj, registry.events);
6897
- register_batch_handler(proj, batch_handlers);
6898
- if (proj.fold) fold_projections.push(proj);
6899
- }
6833
+ for (const proj of pending_projections) add_projection(proj);
6900
6834
  for (const proj of fold_projections) {
6901
6835
  const fold = proj.fold;
6902
6836
  const merged = states.get(fold.name);