@rotorsoft/act 1.4.0 → 1.5.0

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
@@ -19,7 +19,7 @@ import {
19
19
  sleep,
20
20
  store,
21
21
  validate
22
- } from "./chunk-XKCTGUW2.js";
22
+ } from "./chunk-I4L224TZ.js";
23
23
  import {
24
24
  ActorSchema,
25
25
  CausationEventSchema,
@@ -36,7 +36,7 @@ import {
36
36
  TargetSchema,
37
37
  ValidationError,
38
38
  ZodEmpty
39
- } from "./chunk-TZWDSNSN.js";
39
+ } from "./chunk-PMAZTOSO.js";
40
40
  import "./chunk-5WRI5ZAA.js";
41
41
 
42
42
  // src/signals.ts
@@ -681,18 +681,23 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
681
681
 
682
682
  // src/internal/correlate-cycle.ts
683
683
  var CorrelateCycle = class {
684
- constructor(registry, staticTargets, hasDynamicResolvers, cd, maxSubscribedStreams, onInit) {
685
- this.registry = registry;
686
- this.staticTargets = staticTargets;
687
- this.hasDynamicResolvers = hasDynamicResolvers;
688
- this.cd = cd;
689
- this.onInit = onInit;
690
- this._subscribed = new LruSet(maxSubscribedStreams);
691
- }
692
684
  _checkpoint = -1;
693
685
  _initialized = false;
694
686
  _timer = void 0;
695
687
  _subscribed;
688
+ _registry;
689
+ _static_targets;
690
+ _has_dynamic_resolvers;
691
+ _cd;
692
+ _on_init;
693
+ constructor(registry, staticTargets, hasDynamicResolvers, cd, maxSubscribedStreams, onInit) {
694
+ this._subscribed = new LruSet(maxSubscribedStreams);
695
+ this._registry = registry;
696
+ this._static_targets = staticTargets;
697
+ this._has_dynamic_resolvers = hasDynamicResolvers;
698
+ this._cd = cd;
699
+ this._on_init = onInit;
700
+ }
696
701
  /** Last correlated event id. */
697
702
  get checkpoint() {
698
703
  return this._checkpoint;
@@ -707,10 +712,10 @@ var CorrelateCycle = class {
707
712
  async init() {
708
713
  if (this._initialized) return;
709
714
  this._initialized = true;
710
- const { watermark } = await store().subscribe([...this.staticTargets]);
715
+ const { watermark } = await store().subscribe([...this._static_targets]);
711
716
  this._checkpoint = watermark;
712
- this.onInit?.();
713
- for (const { stream } of this.staticTargets) {
717
+ this._on_init?.();
718
+ for (const { stream } of this._static_targets) {
714
719
  this._subscribed.add(stream);
715
720
  }
716
721
  }
@@ -721,7 +726,7 @@ var CorrelateCycle = class {
721
726
  */
722
727
  async correlate(query = { after: -1, limit: 10 }) {
723
728
  await this.init();
724
- if (!this.hasDynamicResolvers)
729
+ if (!this._has_dynamic_resolvers)
725
730
  return { subscribed: 0, last_id: this._checkpoint };
726
731
  const after = Math.max(this._checkpoint, query.after || -1);
727
732
  const correlated = /* @__PURE__ */ new Map();
@@ -729,7 +734,7 @@ var CorrelateCycle = class {
729
734
  await store().query(
730
735
  (event) => {
731
736
  last_id = event.id;
732
- const register = this.registry.events[event.name];
737
+ const register = this._registry.events[event.name];
733
738
  if (register) {
734
739
  for (const reaction of register.reactions.values()) {
735
740
  if (typeof reaction.resolver !== "function") continue;
@@ -765,7 +770,7 @@ var CorrelateCycle = class {
765
770
  lane
766
771
  })
767
772
  );
768
- const { subscribed } = await this.cd.subscribe(streams);
773
+ const { subscribed } = await this._cd.subscribe(streams);
769
774
  this._checkpoint = last_id;
770
775
  if (subscribed) {
771
776
  for (const { stream } of streams) {
@@ -1389,9 +1394,6 @@ var EMPTY_DRAIN = {
1389
1394
  blocked: []
1390
1395
  };
1391
1396
  var DrainController = class {
1392
- constructor(deps) {
1393
- this.deps = deps;
1394
- }
1395
1397
  _armed = false;
1396
1398
  _locked = false;
1397
1399
  _ratio = 0.5;
@@ -1407,6 +1409,10 @@ var DrainController = class {
1407
1409
  /** Worker timer (ACT-1103). Set when `start()` is active, undefined otherwise. */
1408
1410
  _worker;
1409
1411
  _stopped = false;
1412
+ _deps;
1413
+ constructor(deps) {
1414
+ this._deps = deps;
1415
+ }
1410
1416
  /**
1411
1417
  * Signal that a commit (or reset / cold-start) may have produced work.
1412
1418
  * Subsequent `drain()` calls will run the pipeline; once the pipeline
@@ -1447,7 +1453,7 @@ var DrainController = class {
1447
1453
  }
1448
1454
  /** Lane this controller drains (undefined = legacy single-lane span). */
1449
1455
  get lane() {
1450
- return this.deps.lane;
1456
+ return this._deps.lane;
1451
1457
  }
1452
1458
  /**
1453
1459
  * Start a per-lane worker that drains at the lane's `cycleMs`
@@ -1481,7 +1487,7 @@ var DrainController = class {
1481
1487
  async drain(options = {}) {
1482
1488
  if (!this._armed) return EMPTY_DRAIN;
1483
1489
  if (this._locked) return EMPTY_DRAIN;
1484
- const d = this.deps.defaults ?? {};
1490
+ const d = this._deps.defaults ?? {};
1485
1491
  const streamLimit = d.streamLimit ?? options.streamLimit ?? 10;
1486
1492
  const eventLimit = d.eventLimit ?? options.eventLimit ?? 10;
1487
1493
  const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? 1e4;
@@ -1490,24 +1496,24 @@ var DrainController = class {
1490
1496
  const lagging = Math.ceil(streamLimit * this._ratio);
1491
1497
  const leading = streamLimit - lagging;
1492
1498
  const cycle = await runDrainCycle(
1493
- this.deps.ops,
1494
- this.deps.registry,
1495
- this.deps.batchHandlers,
1496
- this.deps.handle,
1497
- this.deps.handleBatch,
1499
+ this._deps.ops,
1500
+ this._deps.registry,
1501
+ this._deps.batchHandlers,
1502
+ this._deps.handle,
1503
+ this._deps.handleBatch,
1498
1504
  lagging,
1499
1505
  leading,
1500
1506
  eventLimit,
1501
1507
  leaseMillis,
1502
1508
  this._backoff.size > 0 ? this.isDeferred : void 0,
1503
- this.deps.lane
1509
+ this._deps.lane
1504
1510
  );
1505
1511
  if (!cycle) {
1506
1512
  this._armed = false;
1507
1513
  return EMPTY_DRAIN;
1508
1514
  }
1509
1515
  const { leased, fetched, handled, acked, blocked } = cycle;
1510
- traceCycle(this.deps.logger, leased, fetched, handled, acked, blocked);
1516
+ traceCycle(this._deps.logger, leased, fetched, handled, acked, blocked);
1511
1517
  this._ratio = computeLagLeadRatio(handled, lagging, leading);
1512
1518
  for (const lease of acked) this._backoff.delete(lease.stream);
1513
1519
  for (const lease of blocked) this._backoff.delete(lease.stream);
@@ -1517,13 +1523,13 @@ var DrainController = class {
1517
1523
  }
1518
1524
  }
1519
1525
  if (this._backoff.size > 0) this.scheduleBackoffWake();
1520
- if (acked.length) this.deps.onAcked(acked);
1521
- if (blocked.length) this.deps.onBlocked(blocked);
1526
+ if (acked.length) this._deps.onAcked(acked);
1527
+ if (blocked.length) this._deps.onBlocked(blocked);
1522
1528
  const hasErrors = handled.some(({ error }) => error);
1523
1529
  if (!acked.length && !blocked.length && !hasErrors) this._armed = false;
1524
1530
  return { fetched, leased, acked, blocked };
1525
1531
  } catch (error) {
1526
- this.deps.logger.error(error);
1532
+ this._deps.logger.error(error);
1527
1533
  return EMPTY_DRAIN;
1528
1534
  } finally {
1529
1535
  this._locked = false;
@@ -1784,12 +1790,15 @@ function buildHandleBatch(logger) {
1784
1790
 
1785
1791
  // src/internal/settle.ts
1786
1792
  var SettleLoop = class {
1787
- constructor(deps, defaultDebounceMs) {
1788
- this.deps = deps;
1789
- this.defaultDebounceMs = defaultDebounceMs;
1790
- }
1791
1793
  _timer = void 0;
1792
1794
  _running = false;
1795
+ _deps;
1796
+ /** Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`. */
1797
+ _default_debounce_ms;
1798
+ constructor(deps, defaultDebounceMs) {
1799
+ this._deps = deps;
1800
+ this._default_debounce_ms = defaultDebounceMs;
1801
+ }
1793
1802
  /**
1794
1803
  * Schedule a settle pass. Multiple calls inside the debounce window
1795
1804
  * coalesce into one cycle. The cycle runs correlate→drain in a loop
@@ -1799,7 +1808,7 @@ var SettleLoop = class {
1799
1808
  */
1800
1809
  schedule(options = {}) {
1801
1810
  const {
1802
- debounceMs = this.defaultDebounceMs,
1811
+ debounceMs = this._default_debounce_ms,
1803
1812
  correlate: correlateQuery = { after: -1, limit: 100 },
1804
1813
  maxPasses = Infinity,
1805
1814
  ...drainOptions
@@ -1810,19 +1819,19 @@ var SettleLoop = class {
1810
1819
  if (this._running) return;
1811
1820
  this._running = true;
1812
1821
  (async () => {
1813
- await this.deps.init();
1822
+ await this._deps.init();
1814
1823
  let lastDrain;
1815
1824
  for (let i = 0; i < maxPasses; i++) {
1816
- const { subscribed } = await this.deps.correlate({
1825
+ const { subscribed } = await this._deps.correlate({
1817
1826
  ...correlateQuery,
1818
- after: this.deps.checkpoint()
1827
+ after: this._deps.checkpoint()
1819
1828
  });
1820
- lastDrain = await this.deps.drain(drainOptions);
1829
+ lastDrain = await this._deps.drain(drainOptions);
1821
1830
  const made_progress = subscribed > 0 || lastDrain.acked.length > 0 || lastDrain.blocked.length > 0;
1822
1831
  if (!made_progress) break;
1823
1832
  }
1824
- if (lastDrain) this.deps.onSettled(lastDrain);
1825
- })().catch((err) => this.deps.logger.error(err)).finally(() => {
1833
+ if (lastDrain) this._deps.onSettled(lastDrain);
1834
+ })().catch((err) => this._deps.logger.error(err)).finally(() => {
1826
1835
  this._running = false;
1827
1836
  });
1828
1837
  }, debounceMs);
@@ -1840,6 +1849,117 @@ var SettleLoop = class {
1840
1849
  var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
1841
1850
  var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
1842
1851
  var Act = class {
1852
+ _emitter = new EventEmitter();
1853
+ /** #803: gate the `Store.notify` subscription side. */
1854
+ _listen;
1855
+ /** #803: gate the local reaction pipeline (drain controllers, settle, correlate). */
1856
+ _drain;
1857
+ /** Event names with at least one registered reaction (computed at build time) */
1858
+ _reactive_events;
1859
+ /** One DrainController per active lane, keyed by lane name. */
1860
+ _drain_controllers;
1861
+ /** Correlation state machine: lazy init, dynamic-resolver scan, periodic worker. */
1862
+ _correlate;
1863
+ /** Debounced correlate→drain catch-up loop. */
1864
+ _settle;
1865
+ /**
1866
+ * Disposer for the cross-process notify subscription, set up eagerly
1867
+ * during construction. Held as a promise because the subscription
1868
+ * itself may be async (the PG adapter checks out a dedicated client
1869
+ * and runs `LISTEN` before resolving). Resolves to `undefined` when
1870
+ * the store doesn't implement `notify` or there are no registered
1871
+ * reactions.
1872
+ *
1873
+ * **Contract:** the configured store must be injected via
1874
+ * {@link store}`(adapter)` *before* calling `act()...build()`. The
1875
+ * orchestrator wires notify against whatever store is current at
1876
+ * construction time — late injection after build is unsupported.
1877
+ */
1878
+ _notify_disposer;
1879
+ /** Public registry — kept as-is per the no-prefix-on-public convention. */
1880
+ registry;
1881
+ /** Map of state name → state definition; populated by the builder. */
1882
+ _states;
1883
+ /**
1884
+ * Emit a lifecycle event. The payload type is inferred from the event name
1885
+ * via {@link ActLifecycleEvents}.
1886
+ */
1887
+ emit(event, args) {
1888
+ return this._emitter.emit(event, args);
1889
+ }
1890
+ /**
1891
+ * Register a listener for a lifecycle event. The listener receives the
1892
+ * event-specific payload.
1893
+ */
1894
+ on(event, listener) {
1895
+ this._emitter.on(event, listener);
1896
+ return this;
1897
+ }
1898
+ /**
1899
+ * Remove a previously registered lifecycle listener.
1900
+ */
1901
+ off(event, listener) {
1902
+ this._emitter.off(event, listener);
1903
+ return this;
1904
+ }
1905
+ /** Batch handlers for static-target projections (target → handler) */
1906
+ _batch_handlers;
1907
+ /** Event-sourcing handlers, optionally wrapped with trace decorators */
1908
+ _es;
1909
+ /** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
1910
+ _cd;
1911
+ /**
1912
+ * Event-name → owning state, computed at build time. The duplicate-event
1913
+ * guard in merge.ts ensures one event name maps to at most one state, so
1914
+ * this lookup is unambiguous. Used by `close()` to pick the right reducer
1915
+ * set when seeding a `restart` snapshot in multi-state apps.
1916
+ */
1917
+ _event_to_state;
1918
+ /**
1919
+ * Event-name → lane fan-in for selective arming (ACT-1103). Built by
1920
+ * `classifyRegistry` once per build. `"all"` means at least one of
1921
+ * the event's reactions is a dynamic resolver (lane opaque until
1922
+ * runtime); a `Set<string>` lists the static lanes only that event's
1923
+ * reactions target.
1924
+ */
1925
+ _event_to_lanes;
1926
+ /**
1927
+ * Audit dependency bag (#723). Built once at construction; held as
1928
+ * an immutable snapshot of the registry state the audit module
1929
+ * needs. Lives in `internal/audit.ts` — this orchestrator never
1930
+ * carries audit logic, only the deps + a one-liner that hands them
1931
+ * over.
1932
+ */
1933
+ _audit_deps;
1934
+ /** Logger resolved at construction time (after user port configuration) */
1935
+ _logger = log();
1936
+ /** Wraps a public-method body so internal `store()`/`cache()` resolve to the
1937
+ * per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
1938
+ * path keeps reading fresh `store()`/`cache()` per call, which matters for
1939
+ * tests that dispose and re-seed mid-suite. */
1940
+ _scoped;
1941
+ /**
1942
+ * Correlation-id generator for originating actions. Bound at
1943
+ * construction from `options.correlator ?? defaultCorrelator`. The
1944
+ * `do()` path passes this into the `_es.action` closure; close-cycle
1945
+ * uses it via {@link closeCorrelation}.
1946
+ */
1947
+ _correlator;
1948
+ /** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
1949
+ * payload (it captures the triggering event for reactingTo auto-inject). */
1950
+ _bound_do = this.do.bind(this);
1951
+ _bound_load = this.load.bind(this);
1952
+ _bound_query = this.query.bind(this);
1953
+ _bound_query_array = this.query_array.bind(this);
1954
+ /** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
1955
+ _handle;
1956
+ _handle_batch;
1957
+ /** Declared drain lanes (ACT-1103). */
1958
+ _lanes;
1959
+ /** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
1960
+ get lanes() {
1961
+ return this._lanes;
1962
+ }
1843
1963
  /**
1844
1964
  * Create a new Act orchestrator. Prefer the {@link act} builder over
1845
1965
  * direct construction — `act()...build()` wires the registry, merges
@@ -1854,9 +1974,9 @@ var Act = class {
1854
1974
  * these from `.withLane(...)` calls. Slice 1 records them on the
1855
1975
  * instance; later slices fan out one `DrainController` per lane.
1856
1976
  */
1857
- constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
1977
+ constructor(registry, states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
1858
1978
  this.registry = registry;
1859
- this._states = _states;
1979
+ this._states = states;
1860
1980
  this._batch_handlers = batchHandlers;
1861
1981
  this._lanes = lanes;
1862
1982
  if (options.onlyLanes && options.onlyLanes.length > 0) {
@@ -1890,6 +2010,8 @@ var Act = class {
1890
2010
  eventToLanes
1891
2011
  } = classifyRegistry(this.registry, this._states);
1892
2012
  this._reactive_events = reactiveEvents;
2013
+ this._listen = options.listen !== false;
2014
+ this._drain = options.drain !== false;
1893
2015
  this._event_to_state = eventToState;
1894
2016
  this._event_to_lanes = eventToLanes;
1895
2017
  const allLanes = ["default", ...lanes.map((l) => l.name)];
@@ -1917,7 +2039,8 @@ var Act = class {
1917
2039
  leaseMillis: cfg.leaseMillis
1918
2040
  }
1919
2041
  });
1920
- if (cfg?.cycleMs !== void 0) controller.start(cfg.cycleMs);
2042
+ if (cfg?.cycleMs !== void 0 && options.drain !== false)
2043
+ controller.start(cfg.cycleMs);
1921
2044
  this._drain_controllers.set(name, controller);
1922
2045
  }
1923
2046
  this._audit_deps = {
@@ -1935,9 +2058,10 @@ var Act = class {
1935
2058
  hasDynamicResolvers,
1936
2059
  this._cd,
1937
2060
  options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
1938
- // Cold start: assume drain is needed (historical events may need processing)
2061
+ // Cold start: assume drain is needed (historical events may need processing).
2062
+ // #803: writer-only instances skip the cold-start arm.
1939
2063
  () => {
1940
- if (this._reactive_events.size > 0) this._armAll();
2064
+ if (this._drain && this._reactive_events.size > 0) this._armAll();
1941
2065
  }
1942
2066
  );
1943
2067
  this._settle = new SettleLoop(
@@ -1954,109 +2078,6 @@ var Act = class {
1954
2078
  this._notify_disposer = this._wireNotify(options.scoped?.store ?? store());
1955
2079
  dispose(() => this.shutdown());
1956
2080
  }
1957
- _emitter = new EventEmitter();
1958
- /** Event names with at least one registered reaction (computed at build time) */
1959
- _reactive_events;
1960
- /** One DrainController per active lane, keyed by lane name. */
1961
- _drain_controllers;
1962
- /** Correlation state machine: lazy init, dynamic-resolver scan, periodic worker. */
1963
- _correlate;
1964
- /** Debounced correlate→drain catch-up loop. */
1965
- _settle;
1966
- /**
1967
- * Disposer for the cross-process notify subscription, set up eagerly
1968
- * during construction. Held as a promise because the subscription
1969
- * itself may be async (the PG adapter checks out a dedicated client
1970
- * and runs `LISTEN` before resolving). Resolves to `undefined` when
1971
- * the store doesn't implement `notify` or there are no registered
1972
- * reactions.
1973
- *
1974
- * **Contract:** the configured store must be injected via
1975
- * {@link store}`(adapter)` *before* calling `act()...build()`. The
1976
- * orchestrator wires notify against whatever store is current at
1977
- * construction time — late injection after build is unsupported.
1978
- */
1979
- _notify_disposer;
1980
- /**
1981
- * Emit a lifecycle event. The payload type is inferred from the event name
1982
- * via {@link ActLifecycleEvents}.
1983
- */
1984
- emit(event, args) {
1985
- return this._emitter.emit(event, args);
1986
- }
1987
- /**
1988
- * Register a listener for a lifecycle event. The listener receives the
1989
- * event-specific payload.
1990
- */
1991
- on(event, listener) {
1992
- this._emitter.on(event, listener);
1993
- return this;
1994
- }
1995
- /**
1996
- * Remove a previously registered lifecycle listener.
1997
- */
1998
- off(event, listener) {
1999
- this._emitter.off(event, listener);
2000
- return this;
2001
- }
2002
- /** Batch handlers for static-target projections (target → handler) */
2003
- _batch_handlers;
2004
- /** Event-sourcing handlers, optionally wrapped with trace decorators */
2005
- _es;
2006
- /** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
2007
- _cd;
2008
- /**
2009
- * Event-name → owning state, computed at build time. The duplicate-event
2010
- * guard in merge.ts ensures one event name maps to at most one state, so
2011
- * this lookup is unambiguous. Used by `close()` to pick the right reducer
2012
- * set when seeding a `restart` snapshot in multi-state apps.
2013
- */
2014
- _event_to_state;
2015
- /**
2016
- * Event-name → lane fan-in for selective arming (ACT-1103). Built by
2017
- * `classifyRegistry` once per build. `"all"` means at least one of
2018
- * the event's reactions is a dynamic resolver (lane opaque until
2019
- * runtime); a `Set<string>` lists the static lanes only that event's
2020
- * reactions target.
2021
- */
2022
- _event_to_lanes;
2023
- /**
2024
- * Audit dependency bag (#723). Built once at construction; held as
2025
- * an immutable snapshot of the registry state the audit module
2026
- * needs. Lives in `internal/audit.ts` — this orchestrator never
2027
- * carries audit logic, only the deps + a one-liner that hands them
2028
- * over.
2029
- */
2030
- _audit_deps;
2031
- /** Logger resolved at construction time (after user port configuration) */
2032
- _logger = log();
2033
- /** Wraps a public-method body so internal `store()`/`cache()` resolve to the
2034
- * per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
2035
- * path keeps reading fresh `store()`/`cache()` per call, which matters for
2036
- * tests that dispose and re-seed mid-suite. */
2037
- _scoped;
2038
- /**
2039
- * Correlation-id generator for originating actions. Bound at
2040
- * construction from `options.correlator ?? defaultCorrelator`. The
2041
- * `do()` path passes this into the `_es.action` closure; close-cycle
2042
- * uses it via {@link closeCorrelation}.
2043
- */
2044
- _correlator;
2045
- /** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
2046
- * payload (it captures the triggering event for reactingTo auto-inject). */
2047
- _bound_do = this.do.bind(this);
2048
- _bound_load = this.load.bind(this);
2049
- _bound_query = this.query.bind(this);
2050
- _bound_query_array = this.query_array.bind(this);
2051
- /** Reaction dispatchers built once and handed to runDrainCycle each cycle. */
2052
- _handle;
2053
- _handle_batch;
2054
- /** Declared drain lanes (ACT-1103). */
2055
- _lanes;
2056
- /** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
2057
- get lanes() {
2058
- return this._lanes;
2059
- }
2060
2081
  /** True after the first `shutdown()` call. Guards idempotency. */
2061
2082
  _shutdown_promise;
2062
2083
  /**
@@ -2091,14 +2112,17 @@ var Act = class {
2091
2112
  async _wireNotify(s) {
2092
2113
  if (this._reactive_events.size === 0) return void 0;
2093
2114
  if (!s.notify) return void 0;
2115
+ if (!this._listen) return void 0;
2094
2116
  try {
2095
2117
  return await s.notify((notification) => {
2096
2118
  try {
2097
2119
  this.emit("notified", notification);
2098
- const armed = this._armForEventNames(
2099
- notification.events.map((e) => e.name)
2100
- );
2101
- if (armed) this._settle.schedule({ debounceMs: 0 });
2120
+ if (this._drain) {
2121
+ const armed = this._armForEventNames(
2122
+ notification.events.map((e) => e.name)
2123
+ );
2124
+ if (armed) this._settle.schedule({ debounceMs: 0 });
2125
+ }
2102
2126
  } catch (err) {
2103
2127
  this._logger.error(err, "notified handler threw");
2104
2128
  }
@@ -2349,6 +2373,8 @@ var Act = class {
2349
2373
  * @see {@link start_correlations} for automatic correlation
2350
2374
  */
2351
2375
  async drain(options = {}) {
2376
+ if (!this._drain)
2377
+ return { fetched: [], leased: [], acked: [], blocked: [] };
2352
2378
  return this._scoped(() => this._drainAll(options));
2353
2379
  }
2354
2380
  /** Arm every active lane controller (ACT-1103). */
@@ -2449,6 +2475,7 @@ var Act = class {
2449
2475
  * @see {@link stop_correlations} to stop automatic correlation
2450
2476
  */
2451
2477
  async correlate(query = { after: -1, limit: 10 }) {
2478
+ if (!this._drain) return { subscribed: 0, last_id: -1 };
2452
2479
  return this._scoped(() => this._correlate.correlate(query));
2453
2480
  }
2454
2481
  /**
@@ -2874,6 +2901,7 @@ var Act = class {
2874
2901
  * @see {@link correlate} for manual correlation
2875
2902
  */
2876
2903
  settle(options = {}) {
2904
+ if (!this._drain) return;
2877
2905
  this._settle.schedule(options);
2878
2906
  }
2879
2907
  };