@rotorsoft/act 1.4.0 → 1.5.1
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/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +29 -4
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +6 -6
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +1 -1
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/lru-map.d.ts +2 -2
- package/dist/@types/internal/lru-map.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +3 -5
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +4 -30
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/{chunk-XKCTGUW2.js → chunk-I4L224TZ.js} +15 -12
- package/dist/chunk-I4L224TZ.js.map +1 -0
- package/dist/{chunk-TZWDSNSN.js → chunk-PMAZTOSO.js} +31 -5
- package/dist/chunk-PMAZTOSO.js.map +1 -0
- package/dist/index.cjs +224 -167
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +183 -155
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +34 -16
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +7 -6
- package/dist/test/index.js.map +1 -1
- package/dist/types/index.cjs +30 -4
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-TZWDSNSN.js.map +0 -1
- package/dist/chunk-XKCTGUW2.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
sleep,
|
|
20
20
|
store,
|
|
21
21
|
validate
|
|
22
|
-
} from "./chunk-
|
|
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-
|
|
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.
|
|
715
|
+
const { watermark } = await store().subscribe([...this._static_targets]);
|
|
711
716
|
this._checkpoint = watermark;
|
|
712
|
-
this.
|
|
713
|
-
for (const { stream } of this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
1494
|
-
this.
|
|
1495
|
-
this.
|
|
1496
|
-
this.
|
|
1497
|
-
this.
|
|
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.
|
|
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.
|
|
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.
|
|
1521
|
-
if (blocked.length) this.
|
|
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.
|
|
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.
|
|
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.
|
|
1822
|
+
await this._deps.init();
|
|
1814
1823
|
let lastDrain;
|
|
1815
1824
|
for (let i = 0; i < maxPasses; i++) {
|
|
1816
|
-
const { subscribed } = await this.
|
|
1825
|
+
const { subscribed } = await this._deps.correlate({
|
|
1817
1826
|
...correlateQuery,
|
|
1818
|
-
after: this.
|
|
1827
|
+
after: this._deps.checkpoint()
|
|
1819
1828
|
});
|
|
1820
|
-
lastDrain = await this.
|
|
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.
|
|
1825
|
-
})().catch((err) => this.
|
|
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
|
|
@@ -1847,16 +1967,16 @@ var Act = class {
|
|
|
1847
1967
|
* and projections in one pass.
|
|
1848
1968
|
*
|
|
1849
1969
|
* @param registry Schemas for every event and action across registered states
|
|
1850
|
-
* @param
|
|
1970
|
+
* @param states Merged map of state name → state definition
|
|
1851
1971
|
* @param batchHandlers Static-target projection batch handlers (target → handler)
|
|
1852
1972
|
* @param options Tuning knobs — see {@link ActOptions}
|
|
1853
1973
|
* @param lanes Declared drain lanes (ACT-1103). The builder collects
|
|
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,
|
|
1977
|
+
constructor(registry, states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map(), options = {}, lanes = []) {
|
|
1858
1978
|
this.registry = registry;
|
|
1859
|
-
this._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
|
|
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
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
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
|
};
|