@rotorsoft/act 1.32.22 → 1.32.24

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
@@ -26,7 +26,7 @@ import {
26
26
  sleep,
27
27
  store,
28
28
  validate
29
- } from "./chunk-T3CWXVGT.js";
29
+ } from "./chunk-OG5O36AJ.js";
30
30
  import {
31
31
  ActorSchema,
32
32
  CausationEventSchema,
@@ -1584,71 +1584,27 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
1584
1584
  var CorrelateCycle = class {
1585
1585
  _checkpoint = -1;
1586
1586
  _initialized = false;
1587
- /**
1588
- * This worker's identity for the correlation lease. A per-instance
1589
- * UUID, matching the drain's convention, so a renewal is recognised as the
1590
- * same holder and a restarted process never inherits a stale claim.
1591
- */
1587
+ /** Lease holder id; per instance, so a restart never inherits a stale claim. */
1592
1588
  _by = randomUUID();
1593
- /**
1594
- * Which correlator this is. Computed once from the registry so identical
1595
- * workers share a lease and unrelated applications never do.
1596
- */
1597
1589
  _key;
1598
- /**
1599
- * When this worker's correlation lease runs out, as a local clock reading,
1600
- * or 0 when it holds none.
1601
- *
1602
- * Cached so the holder doesn't ask the store on every pass. A stale belief
1603
- * can only cause a duplicate scan, and marks are idempotent, so it costs
1604
- * work, never correctness.
1605
- */
1590
+ /** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
1606
1591
  _lease_until = 0;
1607
- /**
1608
- * Whether a scan might find anything, like the drain's flag: a commit or
1609
- * notification raises it, a short page lowers it, and a disarmed scan
1610
- * returns without touching the store.
1611
- *
1612
- * Starts armed: the log may already hold events this process has never
1613
- * correlated, and only a scan can find out.
1614
- */
1592
+ /** Raised by a commit or notification, lowered by a short page. Starts armed. */
1615
1593
  _armed = true;
1616
- /** In-flight init, memoized for single-flight and cleared on failure. */
1617
1594
  _init_promise;
1618
- // Dynamically discovered targets → what each was last subscribed at,
1619
- // bounded by `maxSubscribedStreams`. It decides *what* a scan sends with a
1620
- // target: priority/lane only when the resolution beats the recorded floor,
1621
- // otherwise the row's own values. See {@link Subscription}.
1595
+ // Bounded by `maxSubscribedStreams`; static records live apart so eviction
1596
+ // never drops their +Infinity floor.
1622
1597
  _dynamic_subscriptions;
1623
- /**
1624
- * What each static target was subscribed at by `init`. A plain map, never
1625
- * evicted: the collection is the build-time `_static_targets` list, so it
1626
- * is already bounded by the registry and costs nothing the registry does
1627
- * not already hold.
1628
- *
1629
- * Kept out of the LRU so eviction can never drop a static target's
1630
- * `+Infinity` floor and let a dynamic resolution re-lane it.
1631
- */
1632
1598
  _static_subscriptions = /* @__PURE__ */ new Map();
1633
- /** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
1634
1599
  _patterns = new LruMap(PATTERN_CACHE_SIZE);
1635
1600
  _registry;
1636
1601
  _static_targets;
1637
1602
  _cd;
1638
1603
  _on_init;
1639
- /**
1640
- * Async cold-start hook, run once after `on_init` inside `init()`. The
1641
- * orchestrator re-seeds its defer timers from the persisted `deferred_at`
1642
- * here, so an idle deferred stream re-arms across a restart.
1643
- */
1604
+ /** Runs once after `on_init`; re-seeds defer timers across a restart. */
1644
1605
  _on_init_async;
1645
- /** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
1646
1606
  _declared_lanes;
1647
- /**
1648
- * Offending declarations already reported, so a resolver firing for every
1649
- * matching event reports once. Owned by the instance rather than the
1650
- * module: `internal/` holds no module-level state.
1651
- */
1607
+ /** Report-once keys; per instance because `internal/` holds no module state. */
1652
1608
  _reported = /* @__PURE__ */ new Set();
1653
1609
  constructor({
1654
1610
  registry,
@@ -1669,28 +1625,13 @@ var CorrelateCycle = class {
1669
1625
  this._on_init = on_init;
1670
1626
  this._on_init_async = on_init_async;
1671
1627
  }
1672
- /** Last correlated event id. */
1673
1628
  get checkpoint() {
1674
1629
  return this._checkpoint;
1675
1630
  }
1676
- /**
1677
- * Signal that a commit (local or remote) may have produced events this
1678
- * process has not correlated. Cheap and idempotent — the orchestrator calls
1679
- * it on every commit and every notification.
1680
- */
1631
+ /** A commit (local or remote) may have produced uncorrelated events. */
1681
1632
  arm() {
1682
1633
  this._armed = true;
1683
1634
  }
1684
- /**
1685
- * Initialize correlation state on first call.
1686
- * - Reads the durable correlate checkpoint (and max(at)) from the store,
1687
- * flooring a first boot at `watermark - back_scan` so an event
1688
- * committed-but-not-correlated before a crash is re-scanned on
1689
- * restart instead of skipped
1690
- * - Subscribes static resolver targets (idempotent upsert)
1691
- * - Populates the subscribed-streams LRU
1692
- * - Fires `on_init` once (Act uses this to flag a cold-start drain)
1693
- */
1694
1635
  async init() {
1695
1636
  if (this._initialized) return;
1696
1637
  if (!this._init_promise) {
@@ -1717,16 +1658,7 @@ var CorrelateCycle = class {
1717
1658
  }
1718
1659
  await this._on_init_async?.();
1719
1660
  }
1720
- /**
1721
- * Start correlation over after the event log was replaced wholesale
1722
- * (`restore`). The restored log is renumbered from the start and its
1723
- * subscription rows are gone, so the in-memory scan position and both
1724
- * subscription records describe a log that no longer exists. Dropping them
1725
- * and the init latch makes the next pass run cold-start again: static
1726
- * targets are re-subscribed, the checkpoint is re-read from the store
1727
- * (which `restore` reset), and dynamic targets are rediscovered by the
1728
- * scan.
1729
- */
1661
+ /** After `restore` replaced the log: forget everything and cold-start again. */
1730
1662
  restart() {
1731
1663
  this._initialized = false;
1732
1664
  this._init_promise = void 0;
@@ -1735,21 +1667,11 @@ var CorrelateCycle = class {
1735
1667
  this._static_subscriptions.clear();
1736
1668
  this._armed = true;
1737
1669
  }
1738
- /**
1739
- * Forget dynamic targets whose subscription rows no longer exist (a full
1740
- * close deletes the row), so a later scan re-subscribes them. Static
1741
- * targets keep their records.
1742
- */
1670
+ /** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
1743
1671
  forget_subscribed(streams) {
1744
1672
  for (const stream of streams) this._dynamic_subscriptions.delete(stream);
1745
1673
  }
1746
- /**
1747
- * Would an event from `stream` be fetched for a target subscribed with
1748
- * `source`? The subscription's source is the filter `fetch` queries with
1749
- * — literal names by equality, patterns compiled as a `RegExp` — so an
1750
- * event outside it is not work for that target and must not mark it.
1751
- * No source means the target consumes every stream.
1752
- */
1674
+ /** Would the target's own `fetch` (filtered by `source`) return this event? */
1753
1675
  _in_fetch_window(source, stream) {
1754
1676
  if (source === void 0 || source === stream) return true;
1755
1677
  if (is_literal_source(source)) return false;
@@ -1760,16 +1682,7 @@ var CorrelateCycle = class {
1760
1682
  }
1761
1683
  return pattern.test(stream);
1762
1684
  }
1763
- /**
1764
- * Scan the events past the checkpoint, resolve each to its target
1765
- * streams, and record what it found through `cd.subscribe` — new dynamic
1766
- * targets get registered, and every target an event resolved to gets its
1767
- * **work mark** raised to that event's id.
1768
- *
1769
- * Both resolver kinds are walked. A static target is already subscribed
1770
- * at init, but marking it is what makes it claimable without probing the
1771
- * event log, so the scan runs for every app.
1772
- */
1685
+ /** Scan past the checkpoint, subscribe new targets and raise their work marks. */
1773
1686
  async correlate(query = { after: -1, limit: 10 }, lease = false) {
1774
1687
  await this.init();
1775
1688
  if (!this._armed)
@@ -1854,10 +1767,8 @@ var CorrelateCycle = class {
1854
1767
  }
1855
1768
  }
1856
1769
  },
1857
- // Decline the sensitive payload: the scan only needs name/stream/id,
1858
- // and one undecryptable row would otherwise stop every stream's
1859
- // reactions. Resolvers therefore see `pii: null`; correlate runs
1860
- // actor-less, so plaintext here would be un-gated disclosure.
1770
+ // Resolvers see `pii: null`: correlate runs actor-less, and one
1771
+ // undecryptable row would otherwise stop every stream's reactions.
1861
1772
  { ...query, after, with_pii: false }
1862
1773
  );
1863
1774
  if (this._declared_lanes.size > 1) await this._recall(correlated);
@@ -1900,14 +1811,9 @@ var CorrelateCycle = class {
1900
1811
  return { subscribed: 0, last_id, marked: 0, scanned: true };
1901
1812
  }
1902
1813
  /**
1903
- * Give each target this scan had no record of the record its row holds.
1904
- *
1905
- * A target with no record is either new or forgotten. Its row, when it
1906
- * has one, says what lane and priority it was subscribed at; a resolution
1907
- * that doesn't beat that priority carries the row's values, exactly as if
1908
- * the record had never been evicted. One read per target with no record,
1909
- * and only when more than one lane is declared: with one lane there is no
1910
- * lane to lose, and the store already keeps the higher priority.
1814
+ * Read the row of each target this scan had no record of (new or evicted),
1815
+ * so a resolution that doesn't beat its priority keeps the row's lane.
1816
+ * Only called with more than one lane declared.
1911
1817
  */
1912
1818
  async _recall(correlated) {
1913
1819
  const missed = [...correlated].filter(([, entry]) => entry.missed);
@@ -1943,13 +1849,7 @@ var CorrelateCycle = class {
1943
1849
  })
1944
1850
  );
1945
1851
  }
1946
- /**
1947
- * Hand the correlation lease back early.
1948
- *
1949
- * The port has no release verb; re-acquiring as the same holder with a
1950
- * zero duration releases it. Best-effort: a failure costs the lease's
1951
- * remaining lifetime, as a crash would.
1952
- */
1852
+ /** Release the lease early by re-acquiring it for zero ms. Best-effort. */
1953
1853
  async release_correlation() {
1954
1854
  try {
1955
1855
  this._lease_until = 0;
@@ -2426,11 +2326,9 @@ async function load(me, target, callback, patch_fn = bare_patch) {
2426
2326
  {
2427
2327
  stream,
2428
2328
  stream_exact: true,
2429
- // `with_snaps` on every non-time-travel load. Cold, it resumes from the
2430
- // latest snapshot (this is the only place that decides it; stores just
2431
- // apply it). Warm, it keeps a snapshot above a stale checkpoint in the
2432
- // result, so a windowed close that pruned the events in between can't
2433
- // make the fold apply the tail onto stale state.
2329
+ // `with_snaps` on every non-time-travel load: cold, it resumes from the
2330
+ // latest snapshot; warm, it keeps a snapshot above a stale checkpoint,
2331
+ // so a windowed close's prune can't fold the tail onto stale state.
2434
2332
  ...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
2435
2333
  }
2436
2334
  );
@@ -2454,10 +2352,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
2454
2352
  event,
2455
2353
  state: state2,
2456
2354
  version: event?.version ?? cached?.version ?? -1,
2457
- // Head event id, captured atomically with `state`: the last replayed
2458
- // event's id on a cache miss, or the cached checkpoint's event_id on a
2459
- // warm hit with no new events. Consumers read this instead of a
2460
- // separate cache lookup that could race a concurrent commit.
2355
+ // Captured with `state`, so consumers never race a separate cache lookup.
2461
2356
  id: event?.id ?? cached?.event_id ?? -1,
2462
2357
  patches,
2463
2358
  snaps,
@@ -2519,8 +2414,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
2519
2414
  action: {
2520
2415
  name: action2,
2521
2416
  ...target
2522
- // payload intentionally omitted from causation metadata —
2523
- // callers correlate via the correlation id when they need it.
2417
+ // payload intentionally omitted
2524
2418
  },
2525
2419
  event: reactingTo ? {
2526
2420
  id: reactingTo.id,
@@ -2923,61 +2817,35 @@ var DrainController = class {
2923
2817
  _locked = false;
2924
2818
  _ratio = 0.5;
2925
2819
  /**
2926
- * Local wake for streams this worker parked (backoff or defer). The
2927
- * schedule itself is persisted in the store; this runs the drain at the
2928
- * earliest pending visit. A parked stream already carries its mark, so
2929
- * the drain alone finds it, and on an idle app nothing else would.
2930
- * Every drain re-aims it when it ends, and a due entry stays until a pass
2931
- * handles it or a pass that started after it came due claims nothing, so
2932
- * the wake keeps draining until every due stream is reached.
2933
- * `stop()` cancels the timer and a stopped drain never re-schedules it,
2934
- * so a wake never runs after a stop.
2820
+ * Local wake at the earliest parked (backoff or defer) visit; the schedule
2821
+ * itself is persisted. On an idle app nothing else would drain it. A due
2822
+ * entry stays until handled or until a later pass claims nothing.
2935
2823
  */
2936
2824
  _defer = new DeferTimer(() => {
2937
2825
  this._armed = true;
2938
2826
  void this._deps.run_scoped(() => this.drain());
2939
2827
  });
2940
- /** Worker timer. Set when `start()` is active, undefined otherwise. */
2941
2828
  _worker;
2942
- /**
2943
- * Misroutings this controller has reported. A resolver returning a
2944
- * projection's target does so for every matching event; one line per event
2945
- * would bury the signal it exists to raise.
2946
- */
2947
2829
  _misrouted = /* @__PURE__ */ new Set();
2948
2830
  _stopped = false;
2949
- /**
2950
- * Resolves when the cycle in flight finishes, so a graceful shutdown can
2951
- * wait for it. Never rejects.
2952
- */
2831
+ /** Never rejects; graceful shutdown waits on it. */
2953
2832
  _inflight;
2954
2833
  _inflight_done;
2955
2834
  _deps;
2956
2835
  constructor(deps) {
2957
2836
  this._deps = deps;
2958
2837
  }
2959
- /**
2960
- * Signal that a commit (or reset / cold-start) may have produced work.
2961
- * Subsequent `drain()` calls will run the pipeline; once the pipeline
2962
- * settles to no-progress, the controller disarms itself.
2963
- */
2964
2838
  arm() {
2965
2839
  this._armed = true;
2966
2840
  }
2967
- /**
2968
- * Re-seed a persisted `deferred_at` into the local wake at cold start, so
2969
- * an idle deferred stream re-arms at its due-time with no new commit.
2970
- * Seeds collapse into one timer; the earliest wins.
2971
- */
2841
+ /** Cold start: re-seed a persisted `deferred_at` into the local wake. */
2972
2842
  seed_defer(stream, at) {
2973
2843
  this._defer.set(stream, at);
2974
2844
  this._defer.schedule();
2975
2845
  }
2976
- /** Read-only flag — true while a commit / reset is unprocessed. */
2977
2846
  get armed() {
2978
2847
  return this._armed;
2979
2848
  }
2980
- /** The cycle in flight, or `undefined` when idle. */
2981
2849
  get inflight() {
2982
2850
  return this._inflight;
2983
2851
  }
@@ -2985,19 +2853,10 @@ var DrainController = class {
2985
2853
  get lease_millis() {
2986
2854
  return this._deps.defaults?.leaseMillis;
2987
2855
  }
2988
- /** Lane this controller drains (undefined spans all lanes). */
2989
2856
  get lane() {
2990
2857
  return this._deps.lane;
2991
2858
  }
2992
- /**
2993
- * Start a per-lane worker that drains at the lane's `cycleMs`
2994
- * cadence. When armed, the worker calls `drain()` on every
2995
- * tick and re-schedules; when not armed, it still re-schedules at
2996
- * `cycleMs` so a future `arm()` is picked up on the next tick.
2997
- *
2998
- * The setTimeout chain uses `unref()` so it doesn't keep the process
2999
- * alive on its own.
3000
- */
2859
+ /** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
3001
2860
  start(cycleMs) {
3002
2861
  if (this._worker || this._stopped) return;
3003
2862
  const run = this._deps.run_scoped;
@@ -3010,7 +2869,6 @@ var DrainController = class {
3010
2869
  this._worker = setTimeout(tick, cycleMs);
3011
2870
  this._worker.unref();
3012
2871
  }
3013
- /** Stop the per-lane worker. Idempotent. */
3014
2872
  stop() {
3015
2873
  this._stopped = true;
3016
2874
  if (this._worker) {
@@ -3019,7 +2877,6 @@ var DrainController = class {
3019
2877
  }
3020
2878
  this._defer.stop();
3021
2879
  }
3022
- /** Run one drain pass. Short-circuits when not armed or already running. */
3023
2880
  async drain(options = {}) {
3024
2881
  if (!this._armed) return EMPTY_DRAIN;
3025
2882
  if (this._locked) return EMPTY_DRAIN;