@rotorsoft/act 1.32.23 → 1.32.25
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 +20 -155
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +10 -190
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +5 -45
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +0 -8
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +23 -163
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +19 -129
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +21 -158
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/{chunk-OG5O36AJ.js → chunk-QXW3JV2H.js} +21 -240
- package/dist/chunk-QXW3JV2H.js.map +1 -0
- package/dist/index.cjs +125 -654
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +107 -417
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +20 -239
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OG5O36AJ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
sleep,
|
|
27
27
|
store,
|
|
28
28
|
validate
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-QXW3JV2H.js";
|
|
30
30
|
import {
|
|
31
31
|
ActorSchema,
|
|
32
32
|
CausationEventSchema,
|
|
@@ -1355,37 +1355,34 @@ async function windowed_prune_pending(stream, before, max_id) {
|
|
|
1355
1355
|
}
|
|
1356
1356
|
async function probe_min_watermarks(streams, page_size, checkpoint) {
|
|
1357
1357
|
const min_at = /* @__PURE__ */ new Map();
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1358
|
+
const matches = source_matcher();
|
|
1359
|
+
await walk_streams(
|
|
1360
|
+
store(),
|
|
1361
|
+
(position) => {
|
|
1362
|
+
const pending = position.correlated_at === void 0 || position.at < position.correlated_at;
|
|
1363
|
+
const cap = pending ? position.at : Math.max(position.at, checkpoint);
|
|
1364
|
+
for (const stream of streams) {
|
|
1365
|
+
if (matches(position.source, stream)) {
|
|
1366
|
+
const prev = min_at.get(stream);
|
|
1367
|
+
if (prev === void 0 || cap < prev) min_at.set(stream, cap);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
{ limit: page_size, source_matches: streams }
|
|
1372
|
+
);
|
|
1373
|
+
return min_at;
|
|
1374
|
+
}
|
|
1375
|
+
function source_matcher() {
|
|
1376
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
1377
|
+
return (source, stream) => {
|
|
1378
|
+
if (!source) return true;
|
|
1379
|
+
let re = compiled.get(source);
|
|
1361
1380
|
if (!re) {
|
|
1362
1381
|
re = new RegExp(source);
|
|
1363
|
-
|
|
1382
|
+
compiled.set(source, re);
|
|
1364
1383
|
}
|
|
1365
|
-
return re;
|
|
1384
|
+
return re.test(stream);
|
|
1366
1385
|
};
|
|
1367
|
-
let after;
|
|
1368
|
-
for (; ; ) {
|
|
1369
|
-
let last;
|
|
1370
|
-
const { count } = await store().query_streams(
|
|
1371
|
-
(position) => {
|
|
1372
|
-
last = position.stream;
|
|
1373
|
-
const source_re = position.source ? get_regex(position.source) : void 0;
|
|
1374
|
-
const pending = position.correlated_at === void 0 || position.at < position.correlated_at;
|
|
1375
|
-
const cap = pending ? position.at : Math.max(position.at, checkpoint);
|
|
1376
|
-
for (const stream of streams) {
|
|
1377
|
-
if (!source_re || source_re.test(stream)) {
|
|
1378
|
-
const prev = min_at.get(stream);
|
|
1379
|
-
if (prev === void 0 || cap < prev) min_at.set(stream, cap);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
},
|
|
1383
|
-
{ after, limit: page_size, source_matches: streams }
|
|
1384
|
-
);
|
|
1385
|
-
if (count < page_size) break;
|
|
1386
|
-
after = last;
|
|
1387
|
-
}
|
|
1388
|
-
return min_at;
|
|
1389
1386
|
}
|
|
1390
1387
|
async function scan_stream_heads(streams) {
|
|
1391
1388
|
const stats = await store().query_stats(streams, {
|
|
@@ -1419,36 +1416,20 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped, p
|
|
|
1419
1416
|
if (checkpoint < info.max_id) uncorrelated.add(stream);
|
|
1420
1417
|
}
|
|
1421
1418
|
const pending_set = /* @__PURE__ */ new Set();
|
|
1422
|
-
const
|
|
1423
|
-
const get_regex = (source) => {
|
|
1424
|
-
let re = source_regex.get(source);
|
|
1425
|
-
if (!re) {
|
|
1426
|
-
re = new RegExp(source);
|
|
1427
|
-
source_regex.set(source, re);
|
|
1428
|
-
}
|
|
1429
|
-
return re;
|
|
1430
|
-
};
|
|
1419
|
+
const matches = source_matcher();
|
|
1431
1420
|
const targets = [...stream_info.keys()];
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
(
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
},
|
|
1447
|
-
{ after, limit: page_size, source_matches: targets }
|
|
1448
|
-
);
|
|
1449
|
-
if (count < page_size) break;
|
|
1450
|
-
after = last;
|
|
1451
|
-
}
|
|
1421
|
+
await walk_streams(
|
|
1422
|
+
store(),
|
|
1423
|
+
(position) => {
|
|
1424
|
+
const has_work = position.correlated_at !== void 0 && position.at < position.correlated_at;
|
|
1425
|
+
if (!has_work) return;
|
|
1426
|
+
for (const [stream, info] of stream_info) {
|
|
1427
|
+
if (matches(position.source, stream) && position.at < info.max_id)
|
|
1428
|
+
pending_set.add(stream);
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
{ limit: page_size, source_matches: targets }
|
|
1432
|
+
);
|
|
1452
1433
|
const safe = [];
|
|
1453
1434
|
for (const [stream] of stream_info) {
|
|
1454
1435
|
if (pending_set.has(stream) || uncorrelated.has(stream))
|
|
@@ -1584,71 +1565,27 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
|
|
|
1584
1565
|
var CorrelateCycle = class {
|
|
1585
1566
|
_checkpoint = -1;
|
|
1586
1567
|
_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
|
-
*/
|
|
1568
|
+
/** Lease holder id; per instance, so a restart never inherits a stale claim. */
|
|
1592
1569
|
_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
1570
|
_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
|
-
*/
|
|
1571
|
+
/** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
|
|
1606
1572
|
_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
|
-
*/
|
|
1573
|
+
/** Raised by a commit or notification, lowered by a short page. Starts armed. */
|
|
1615
1574
|
_armed = true;
|
|
1616
|
-
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
1617
1575
|
_init_promise;
|
|
1618
|
-
//
|
|
1619
|
-
//
|
|
1620
|
-
// target: priority/lane only when the resolution beats the recorded floor,
|
|
1621
|
-
// otherwise the row's own values. See {@link Subscription}.
|
|
1576
|
+
// Bounded by `maxSubscribedStreams`; static records live apart so eviction
|
|
1577
|
+
// never drops their +Infinity floor.
|
|
1622
1578
|
_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
1579
|
_static_subscriptions = /* @__PURE__ */ new Map();
|
|
1633
|
-
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
1634
1580
|
_patterns = new LruMap(PATTERN_CACHE_SIZE);
|
|
1635
1581
|
_registry;
|
|
1636
1582
|
_static_targets;
|
|
1637
1583
|
_cd;
|
|
1638
1584
|
_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
|
-
*/
|
|
1585
|
+
/** Runs once after `on_init`; re-seeds defer timers across a restart. */
|
|
1644
1586
|
_on_init_async;
|
|
1645
|
-
/** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
|
|
1646
1587
|
_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
|
-
*/
|
|
1588
|
+
/** Report-once keys; per instance because `internal/` holds no module state. */
|
|
1652
1589
|
_reported = /* @__PURE__ */ new Set();
|
|
1653
1590
|
constructor({
|
|
1654
1591
|
registry,
|
|
@@ -1669,28 +1606,13 @@ var CorrelateCycle = class {
|
|
|
1669
1606
|
this._on_init = on_init;
|
|
1670
1607
|
this._on_init_async = on_init_async;
|
|
1671
1608
|
}
|
|
1672
|
-
/** Last correlated event id. */
|
|
1673
1609
|
get checkpoint() {
|
|
1674
1610
|
return this._checkpoint;
|
|
1675
1611
|
}
|
|
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
|
-
*/
|
|
1612
|
+
/** A commit (local or remote) may have produced uncorrelated events. */
|
|
1681
1613
|
arm() {
|
|
1682
1614
|
this._armed = true;
|
|
1683
1615
|
}
|
|
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
1616
|
async init() {
|
|
1695
1617
|
if (this._initialized) return;
|
|
1696
1618
|
if (!this._init_promise) {
|
|
@@ -1717,16 +1639,7 @@ var CorrelateCycle = class {
|
|
|
1717
1639
|
}
|
|
1718
1640
|
await this._on_init_async?.();
|
|
1719
1641
|
}
|
|
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
|
-
*/
|
|
1642
|
+
/** After `restore` replaced the log: forget everything and cold-start again. */
|
|
1730
1643
|
restart() {
|
|
1731
1644
|
this._initialized = false;
|
|
1732
1645
|
this._init_promise = void 0;
|
|
@@ -1735,21 +1648,11 @@ var CorrelateCycle = class {
|
|
|
1735
1648
|
this._static_subscriptions.clear();
|
|
1736
1649
|
this._armed = true;
|
|
1737
1650
|
}
|
|
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
|
-
*/
|
|
1651
|
+
/** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
|
|
1743
1652
|
forget_subscribed(streams) {
|
|
1744
1653
|
for (const stream of streams) this._dynamic_subscriptions.delete(stream);
|
|
1745
1654
|
}
|
|
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
|
-
*/
|
|
1655
|
+
/** Would the target's own `fetch` (filtered by `source`) return this event? */
|
|
1753
1656
|
_in_fetch_window(source, stream) {
|
|
1754
1657
|
if (source === void 0 || source === stream) return true;
|
|
1755
1658
|
if (is_literal_source(source)) return false;
|
|
@@ -1760,16 +1663,7 @@ var CorrelateCycle = class {
|
|
|
1760
1663
|
}
|
|
1761
1664
|
return pattern.test(stream);
|
|
1762
1665
|
}
|
|
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
|
-
*/
|
|
1666
|
+
/** Scan past the checkpoint, subscribe new targets and raise their work marks. */
|
|
1773
1667
|
async correlate(query = { after: -1, limit: 10 }, lease = false) {
|
|
1774
1668
|
await this.init();
|
|
1775
1669
|
if (!this._armed)
|
|
@@ -1854,10 +1748,8 @@ var CorrelateCycle = class {
|
|
|
1854
1748
|
}
|
|
1855
1749
|
}
|
|
1856
1750
|
},
|
|
1857
|
-
//
|
|
1858
|
-
//
|
|
1859
|
-
// reactions. Resolvers therefore see `pii: null`; correlate runs
|
|
1860
|
-
// actor-less, so plaintext here would be un-gated disclosure.
|
|
1751
|
+
// Resolvers see `pii: null`: correlate runs actor-less, and one
|
|
1752
|
+
// undecryptable row would otherwise stop every stream's reactions.
|
|
1861
1753
|
{ ...query, after, with_pii: false }
|
|
1862
1754
|
);
|
|
1863
1755
|
if (this._declared_lanes.size > 1) await this._recall(correlated);
|
|
@@ -1900,14 +1792,9 @@ var CorrelateCycle = class {
|
|
|
1900
1792
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
1901
1793
|
}
|
|
1902
1794
|
/**
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
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.
|
|
1795
|
+
* Read the row of each target this scan had no record of (new or evicted),
|
|
1796
|
+
* so a resolution that doesn't beat its priority keeps the row's lane.
|
|
1797
|
+
* Only called with more than one lane declared.
|
|
1911
1798
|
*/
|
|
1912
1799
|
async _recall(correlated) {
|
|
1913
1800
|
const missed = [...correlated].filter(([, entry]) => entry.missed);
|
|
@@ -1943,13 +1830,7 @@ var CorrelateCycle = class {
|
|
|
1943
1830
|
})
|
|
1944
1831
|
);
|
|
1945
1832
|
}
|
|
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
|
-
*/
|
|
1833
|
+
/** Release the lease early by re-acquiring it for zero ms. Best-effort. */
|
|
1953
1834
|
async release_correlation() {
|
|
1954
1835
|
try {
|
|
1955
1836
|
this._lease_until = 0;
|
|
@@ -2232,6 +2113,18 @@ var validating_patch = (me, state2, partial, event) => {
|
|
|
2232
2113
|
);
|
|
2233
2114
|
};
|
|
2234
2115
|
var DEFAULT_BATCH = 500;
|
|
2116
|
+
var cache_put = (stream, entry) => cache().set(stream, entry).catch(
|
|
2117
|
+
(err) => log().warn(
|
|
2118
|
+
err,
|
|
2119
|
+
"cache set skipped; the next load re-folds from the store"
|
|
2120
|
+
)
|
|
2121
|
+
);
|
|
2122
|
+
var cache_drop = (stream) => cache().invalidate(stream).catch(
|
|
2123
|
+
(err) => log().warn(
|
|
2124
|
+
err,
|
|
2125
|
+
"cache invalidate skipped; a stale checkpoint re-folds from its event_id"
|
|
2126
|
+
)
|
|
2127
|
+
);
|
|
2235
2128
|
async function snap(snapshot) {
|
|
2236
2129
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
2237
2130
|
try {
|
|
@@ -2426,38 +2319,28 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2426
2319
|
{
|
|
2427
2320
|
stream,
|
|
2428
2321
|
stream_exact: true,
|
|
2429
|
-
// `with_snaps` on every non-time-travel load
|
|
2430
|
-
// latest snapshot
|
|
2431
|
-
//
|
|
2432
|
-
// result, so a windowed close that pruned the events in between can't
|
|
2433
|
-
// make the fold apply the tail onto stale state.
|
|
2322
|
+
// `with_snaps` on every non-time-travel load: cold, it resumes from the
|
|
2323
|
+
// latest snapshot; warm, it keeps a snapshot above a stale checkpoint,
|
|
2324
|
+
// so a windowed close's prune can't fold the tail onto stale state.
|
|
2434
2325
|
...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
|
|
2435
2326
|
}
|
|
2436
2327
|
);
|
|
2437
2328
|
const head_is_tombstone = event?.name === TOMBSTONE_EVENT;
|
|
2438
2329
|
if (replayed > 0 && !time_travel && event && !me.pii_aware && !head_is_tombstone) {
|
|
2439
|
-
await
|
|
2330
|
+
await cache_put(stream, {
|
|
2440
2331
|
stream,
|
|
2441
2332
|
state: state2,
|
|
2442
2333
|
version: event.version,
|
|
2443
2334
|
event_id: event.id,
|
|
2444
2335
|
patches,
|
|
2445
2336
|
snaps
|
|
2446
|
-
})
|
|
2447
|
-
(err) => log().warn(
|
|
2448
|
-
err,
|
|
2449
|
-
"cache set skipped; the next load re-folds from the store"
|
|
2450
|
-
)
|
|
2451
|
-
);
|
|
2337
|
+
});
|
|
2452
2338
|
}
|
|
2453
2339
|
return {
|
|
2454
2340
|
event,
|
|
2455
2341
|
state: state2,
|
|
2456
2342
|
version: event?.version ?? cached?.version ?? -1,
|
|
2457
|
-
//
|
|
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.
|
|
2343
|
+
// Captured with `state`, so consumers never race a separate cache lookup.
|
|
2461
2344
|
id: event?.id ?? cached?.event_id ?? -1,
|
|
2462
2345
|
patches,
|
|
2463
2346
|
snaps,
|
|
@@ -2519,8 +2402,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2519
2402
|
action: {
|
|
2520
2403
|
name: action2,
|
|
2521
2404
|
...target
|
|
2522
|
-
// payload intentionally omitted
|
|
2523
|
-
// callers correlate via the correlation id when they need it.
|
|
2405
|
+
// payload intentionally omitted
|
|
2524
2406
|
},
|
|
2525
2407
|
event: reactingTo ? {
|
|
2526
2408
|
id: reactingTo.id,
|
|
@@ -2540,14 +2422,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2540
2422
|
reactingTo ? expectedVersion : expected
|
|
2541
2423
|
);
|
|
2542
2424
|
} catch (error) {
|
|
2543
|
-
if (error instanceof ConcurrencyError)
|
|
2544
|
-
cache().invalidate(stream).catch(
|
|
2545
|
-
(err) => log().warn(
|
|
2546
|
-
err,
|
|
2547
|
-
"cache invalidate skipped; a stale checkpoint re-folds from its event_id"
|
|
2548
|
-
)
|
|
2549
|
-
);
|
|
2550
|
-
}
|
|
2425
|
+
if (error instanceof ConcurrencyError) void cache_drop(stream);
|
|
2551
2426
|
throw error;
|
|
2552
2427
|
}
|
|
2553
2428
|
let { state: state2, patches } = snapshot;
|
|
@@ -2574,26 +2449,15 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2574
2449
|
const snap_event = snapped ? await snap(last) : void 0;
|
|
2575
2450
|
if (!me.pii_aware) {
|
|
2576
2451
|
if (contiguous)
|
|
2577
|
-
|
|
2452
|
+
void cache_put(stream, {
|
|
2578
2453
|
stream,
|
|
2579
2454
|
state: last.state,
|
|
2580
2455
|
version: snap_event?.version ?? last.event.version,
|
|
2581
2456
|
event_id: snap_event?.id ?? last.event.id,
|
|
2582
2457
|
patches: snap_event ? 0 : last.patches,
|
|
2583
2458
|
snaps: snap_event ? last.snaps + 1 : last.snaps
|
|
2584
|
-
})
|
|
2585
|
-
|
|
2586
|
-
err,
|
|
2587
|
-
"cache set skipped; the next load re-folds from the store"
|
|
2588
|
-
)
|
|
2589
|
-
);
|
|
2590
|
-
else
|
|
2591
|
-
cache().invalidate(stream).catch(
|
|
2592
|
-
(err) => log().warn(
|
|
2593
|
-
err,
|
|
2594
|
-
"cache invalidate skipped; a stale checkpoint re-folds from its event_id"
|
|
2595
|
-
)
|
|
2596
|
-
);
|
|
2459
|
+
});
|
|
2460
|
+
else void cache_drop(stream);
|
|
2597
2461
|
}
|
|
2598
2462
|
return snapshots;
|
|
2599
2463
|
} catch (error) {
|
|
@@ -2923,61 +2787,35 @@ var DrainController = class {
|
|
|
2923
2787
|
_locked = false;
|
|
2924
2788
|
_ratio = 0.5;
|
|
2925
2789
|
/**
|
|
2926
|
-
* Local wake
|
|
2927
|
-
*
|
|
2928
|
-
*
|
|
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.
|
|
2790
|
+
* Local wake at the earliest parked (backoff or defer) visit; the schedule
|
|
2791
|
+
* itself is persisted. On an idle app nothing else would drain it. A due
|
|
2792
|
+
* entry stays until handled or until a later pass claims nothing.
|
|
2935
2793
|
*/
|
|
2936
2794
|
_defer = new DeferTimer(() => {
|
|
2937
2795
|
this._armed = true;
|
|
2938
2796
|
void this._deps.run_scoped(() => this.drain());
|
|
2939
2797
|
});
|
|
2940
|
-
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
2941
2798
|
_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
2799
|
_misrouted = /* @__PURE__ */ new Set();
|
|
2948
2800
|
_stopped = false;
|
|
2949
|
-
/**
|
|
2950
|
-
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
2951
|
-
* wait for it. Never rejects.
|
|
2952
|
-
*/
|
|
2801
|
+
/** Never rejects; graceful shutdown waits on it. */
|
|
2953
2802
|
_inflight;
|
|
2954
2803
|
_inflight_done;
|
|
2955
2804
|
_deps;
|
|
2956
2805
|
constructor(deps) {
|
|
2957
2806
|
this._deps = deps;
|
|
2958
2807
|
}
|
|
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
2808
|
arm() {
|
|
2965
2809
|
this._armed = true;
|
|
2966
2810
|
}
|
|
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
|
-
*/
|
|
2811
|
+
/** Cold start: re-seed a persisted `deferred_at` into the local wake. */
|
|
2972
2812
|
seed_defer(stream, at) {
|
|
2973
2813
|
this._defer.set(stream, at);
|
|
2974
2814
|
this._defer.schedule();
|
|
2975
2815
|
}
|
|
2976
|
-
/** Read-only flag — true while a commit / reset is unprocessed. */
|
|
2977
2816
|
get armed() {
|
|
2978
2817
|
return this._armed;
|
|
2979
2818
|
}
|
|
2980
|
-
/** The cycle in flight, or `undefined` when idle. */
|
|
2981
2819
|
get inflight() {
|
|
2982
2820
|
return this._inflight;
|
|
2983
2821
|
}
|
|
@@ -2985,19 +2823,10 @@ var DrainController = class {
|
|
|
2985
2823
|
get lease_millis() {
|
|
2986
2824
|
return this._deps.defaults?.leaseMillis;
|
|
2987
2825
|
}
|
|
2988
|
-
/** Lane this controller drains (undefined spans all lanes). */
|
|
2989
2826
|
get lane() {
|
|
2990
2827
|
return this._deps.lane;
|
|
2991
2828
|
}
|
|
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
|
-
*/
|
|
2829
|
+
/** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
|
|
3001
2830
|
start(cycleMs) {
|
|
3002
2831
|
if (this._worker || this._stopped) return;
|
|
3003
2832
|
const run = this._deps.run_scoped;
|
|
@@ -3010,7 +2839,6 @@ var DrainController = class {
|
|
|
3010
2839
|
this._worker = setTimeout(tick, cycleMs);
|
|
3011
2840
|
this._worker.unref();
|
|
3012
2841
|
}
|
|
3013
|
-
/** Stop the per-lane worker. Idempotent. */
|
|
3014
2842
|
stop() {
|
|
3015
2843
|
this._stopped = true;
|
|
3016
2844
|
if (this._worker) {
|
|
@@ -3019,7 +2847,6 @@ var DrainController = class {
|
|
|
3019
2847
|
}
|
|
3020
2848
|
this._defer.stop();
|
|
3021
2849
|
}
|
|
3022
|
-
/** Run one drain pass. Short-circuits when not armed or already running. */
|
|
3023
2850
|
async drain(options = {}) {
|
|
3024
2851
|
if (!this._armed) return EMPTY_DRAIN;
|
|
3025
2852
|
if (this._locked) return EMPTY_DRAIN;
|
|
@@ -3393,41 +3220,23 @@ function validate_only_lanes(options, lanes) {
|
|
|
3393
3220
|
}
|
|
3394
3221
|
var Act = class {
|
|
3395
3222
|
_emitter = new EventEmitter();
|
|
3396
|
-
/** Circuit breaker shared by all drain lanes. */
|
|
3397
3223
|
_breaker;
|
|
3398
|
-
/** Whether this instance subscribes to `Store.notify`. */
|
|
3399
3224
|
_listen;
|
|
3400
|
-
/** Whether this instance runs reactions (drain, settle, correlate). */
|
|
3401
3225
|
_drain;
|
|
3402
|
-
/** Event names with at least one registered reaction (computed at build time) */
|
|
3403
3226
|
_reactive_events;
|
|
3404
|
-
/** One DrainController per active lane, keyed by lane name. */
|
|
3405
3227
|
_drain_controllers;
|
|
3406
|
-
/** Correlation: lazy init and the scan that marks work. */
|
|
3407
3228
|
_correlate;
|
|
3408
|
-
/** Debounced correlate→drain catch-up loop. */
|
|
3409
3229
|
_settle;
|
|
3410
|
-
/** Timer behind {@link start_correlations}. */
|
|
3411
3230
|
_poll;
|
|
3412
3231
|
/** The deprecated `start_correlations` callback, while the poll runs. */
|
|
3413
3232
|
_on_discovered;
|
|
3414
3233
|
/**
|
|
3415
|
-
*
|
|
3416
|
-
*
|
|
3417
|
-
* itself may be async (the PG adapter checks out a dedicated client
|
|
3418
|
-
* and runs `LISTEN` before resolving). Resolves to `undefined` when
|
|
3419
|
-
* the store doesn't implement `notify` or there are no registered
|
|
3420
|
-
* reactions.
|
|
3421
|
-
*
|
|
3422
|
-
* **Contract:** the configured store must be injected via
|
|
3423
|
-
* {@link store}`(adapter)` *before* calling `act()...build()`. The
|
|
3424
|
-
* orchestrator wires notify against whatever store is current at
|
|
3425
|
-
* construction time — late injection after build is unsupported.
|
|
3234
|
+
* Wired at construction against the store current then, so a store
|
|
3235
|
+
* injected after `build()` is never notified.
|
|
3426
3236
|
*/
|
|
3427
3237
|
_notify_disposer;
|
|
3428
3238
|
/** Public registry — kept as-is per the no-prefix-on-public convention. */
|
|
3429
3239
|
registry;
|
|
3430
|
-
/** Map of state name → state definition; populated by the builder. */
|
|
3431
3240
|
_states;
|
|
3432
3241
|
/**
|
|
3433
3242
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
@@ -3453,15 +3262,9 @@ var Act = class {
|
|
|
3453
3262
|
return listeners.length > 0;
|
|
3454
3263
|
}
|
|
3455
3264
|
/**
|
|
3456
|
-
* The
|
|
3457
|
-
*
|
|
3458
|
-
*
|
|
3459
|
-
* `breaker.failed(now, error)` and never log or emit themselves.
|
|
3460
|
-
*
|
|
3461
|
-
* The emit is guarded against Node's `EventEmitter` contract that an
|
|
3462
|
-
* unhandled `"error"` emission is rethrown (which would crash the process
|
|
3463
|
-
* from inside the drain catch); logging is unconditional so failures are
|
|
3464
|
-
* never silent.
|
|
3265
|
+
* The one place a store failure is logged and emitted (the breaker's
|
|
3266
|
+
* `on_error`). The emit is guarded: an unhandled `"error"` emission
|
|
3267
|
+
* would crash the process.
|
|
3465
3268
|
*/
|
|
3466
3269
|
_emit_error(error, circuit) {
|
|
3467
3270
|
this._logger.error(error);
|
|
@@ -3483,58 +3286,29 @@ var Act = class {
|
|
|
3483
3286
|
this._emitter.off(event, listener);
|
|
3484
3287
|
return this;
|
|
3485
3288
|
}
|
|
3486
|
-
/** Batch handlers for static-target projections (target → handler) */
|
|
3487
3289
|
_batch_handlers;
|
|
3488
|
-
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
3489
3290
|
_es;
|
|
3490
|
-
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
3491
3291
|
_cd;
|
|
3492
|
-
/**
|
|
3493
|
-
* Event-name → owning state, computed at build time. The duplicate-event
|
|
3494
|
-
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
3495
|
-
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
3496
|
-
* set when seeding a `restart` snapshot in multi-state apps.
|
|
3497
|
-
*/
|
|
3292
|
+
/** Unambiguous: merge rejects an event name declared by two states. */
|
|
3498
3293
|
_event_to_state;
|
|
3499
|
-
/**
|
|
3500
|
-
* Event-name → lane fan-in for selective arming. Built by
|
|
3501
|
-
* `classify_registry` once per build. `"all"` means at least one of
|
|
3502
|
-
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3503
|
-
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3504
|
-
* reactions target.
|
|
3505
|
-
*/
|
|
3294
|
+
/** Lanes to arm per event; `"all"` when a dynamic resolver hides the lane. */
|
|
3506
3295
|
_event_to_lanes;
|
|
3507
|
-
/** What `internal/audit.ts` needs, built once at construction. */
|
|
3508
3296
|
_audit_deps;
|
|
3509
|
-
/** Logger resolved at construction time (after user port configuration) */
|
|
3510
3297
|
_logger = log();
|
|
3511
3298
|
/** This Act's ports: its own bag, or the singleton adapters. */
|
|
3512
3299
|
_ports;
|
|
3513
|
-
/**
|
|
3514
|
-
* Runs a body inside this Act's ports frame, so internal
|
|
3515
|
-
* `store()`/`cache()` calls resolve to `_ports`.
|
|
3516
|
-
*/
|
|
3300
|
+
/** Runs a body so internal `store()`/`cache()` calls resolve to `_ports`. */
|
|
3517
3301
|
_scoped;
|
|
3518
|
-
/**
|
|
3519
|
-
* Correlation-id generator for originating actions. Bound at
|
|
3520
|
-
* construction from `options.correlator ?? default_correlator`. The
|
|
3521
|
-
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
3522
|
-
* uses it via {@link close_correlation}.
|
|
3523
|
-
*/
|
|
3524
3302
|
_correlator;
|
|
3525
|
-
|
|
3526
|
-
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
3303
|
+
// Bound once, reused by every reaction payload.
|
|
3527
3304
|
_bound_do = this.do.bind(this);
|
|
3528
3305
|
_bound_load = this.load.bind(this);
|
|
3529
3306
|
_bound_query = this.query.bind(this);
|
|
3530
3307
|
_bound_query_array = this.query_array.bind(this);
|
|
3531
3308
|
_bound_forget = this.forget.bind(this);
|
|
3532
|
-
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
3533
3309
|
_handle;
|
|
3534
3310
|
_handle_batch;
|
|
3535
|
-
/** Declared drain lanes. */
|
|
3536
3311
|
_lanes;
|
|
3537
|
-
/** Serializes close critical sections per stream. */
|
|
3538
3312
|
_close_lock = new CloseLock();
|
|
3539
3313
|
/** Every lane a controller can exist for: `"default"` plus each declared one. */
|
|
3540
3314
|
_declared_lanes;
|
|
@@ -3542,26 +3316,7 @@ var Act = class {
|
|
|
3542
3316
|
get lanes() {
|
|
3543
3317
|
return this._lanes;
|
|
3544
3318
|
}
|
|
3545
|
-
/**
|
|
3546
|
-
* Create a new Act orchestrator. Prefer the {@link act} builder over
|
|
3547
|
-
* direct construction — `act()...build()` wires the registry, merges
|
|
3548
|
-
* partial states, and collects batch handlers from registered slices
|
|
3549
|
-
* and projections in one pass.
|
|
3550
|
-
*
|
|
3551
|
-
* @param registry Schemas for every event and action across registered states
|
|
3552
|
-
* @param states Merged map of state name → state definition
|
|
3553
|
-
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
3554
|
-
* @param options Tuning knobs — see {@link ActOptions}
|
|
3555
|
-
* @param lanes Declared drain lanes. The builder collects
|
|
3556
|
-
* these from `.withLane(...)` calls; one `DrainController` runs
|
|
3557
|
-
* per lane, plus the implicit `"default"`.
|
|
3558
|
-
* @param patch_fn The per-event patch step selected once by the builder
|
|
3559
|
-
* from `ActOptions.validateFoldedState` — `bare_patch` by
|
|
3560
|
-
* default, `validating_patch` when the flag is on. The builder uses
|
|
3561
|
-
* the same value for its projection-fold handlers, so there is a
|
|
3562
|
-
* single selection site. Defaults to `bare_patch` for direct
|
|
3563
|
-
* construction.
|
|
3564
|
-
*/
|
|
3319
|
+
/** Use the {@link act} builder rather than constructing directly. */
|
|
3565
3320
|
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = [], patch_fn = bare_patch) {
|
|
3566
3321
|
this.registry = registry;
|
|
3567
3322
|
this._states = states;
|
|
@@ -3612,11 +3367,7 @@ var Act = class {
|
|
|
3612
3367
|
this._notify_disposer = this._wire_notify(this._ports.store);
|
|
3613
3368
|
register_weak_disposer(new WeakRef(this), (self) => self.shutdown());
|
|
3614
3369
|
}
|
|
3615
|
-
/**
|
|
3616
|
-
* Circuit breaker shared by every store-polling loop (drain, the settle
|
|
3617
|
-
* correlate, autoclose). Validates the knobs eagerly so out-of-range
|
|
3618
|
-
* values throw at build time, not on the first cycle tick.
|
|
3619
|
-
*/
|
|
3370
|
+
/** Shared by drain, settle and autoclose; bad knobs throw at build. */
|
|
3620
3371
|
_build_breaker(options) {
|
|
3621
3372
|
return new CircuitBreaker(
|
|
3622
3373
|
resolveCircuitBreakerConfig(options.circuitBreaker),
|
|
@@ -3630,11 +3381,7 @@ var Act = class {
|
|
|
3630
3381
|
}
|
|
3631
3382
|
);
|
|
3632
3383
|
}
|
|
3633
|
-
/**
|
|
3634
|
-
* One DrainController per active lane, plus the implicit "default" unless
|
|
3635
|
-
* `onlyLanes` excludes it. Apps that declare no lanes pass no lane filter
|
|
3636
|
-
* to `claim`.
|
|
3637
|
-
*/
|
|
3384
|
+
/** Apps that declare no lanes pass no lane filter to `claim`. */
|
|
3638
3385
|
_build_drain_controllers(options, lanes) {
|
|
3639
3386
|
const all_lanes = this._declared_lanes;
|
|
3640
3387
|
const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
|
|
@@ -3670,11 +3417,7 @@ var Act = class {
|
|
|
3670
3417
|
}
|
|
3671
3418
|
return controllers;
|
|
3672
3419
|
}
|
|
3673
|
-
/**
|
|
3674
|
-
* Startup advisory naming declared lanes that `onlyLanes` leaves without a
|
|
3675
|
-
* controller here. One process can't check that the cluster covers every
|
|
3676
|
-
* lane, so it reports its own gaps.
|
|
3677
|
-
*/
|
|
3420
|
+
/** One process can't see the cluster, so it reports its own lane gaps. */
|
|
3678
3421
|
_advise_orphaned_lanes(options) {
|
|
3679
3422
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
3680
3423
|
const active = new Set(this._drain_controllers.keys());
|
|
@@ -3696,11 +3439,6 @@ var Act = class {
|
|
|
3696
3439
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
3697
3440
|
};
|
|
3698
3441
|
}
|
|
3699
|
-
/**
|
|
3700
|
-
* Correlate cycle over the classified registry. The cold-start callback
|
|
3701
|
-
* arms every controller — historical events may need processing —
|
|
3702
|
-
* except on writer-only instances (`drain: false`).
|
|
3703
|
-
*/
|
|
3704
3442
|
_build_correlate(options, classification) {
|
|
3705
3443
|
return new CorrelateCycle({
|
|
3706
3444
|
registry: this.registry,
|
|
@@ -3718,12 +3456,7 @@ var Act = class {
|
|
|
3718
3456
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3719
3457
|
});
|
|
3720
3458
|
}
|
|
3721
|
-
/**
|
|
3722
|
-
* At cold start, re-seed each lane controller's local wake from the
|
|
3723
|
-
* persisted future `deferred_at`, so an idle deferred stream (an
|
|
3724
|
-
* autoclose waiting out its window) still fires on time. Streams on lanes
|
|
3725
|
-
* this instance doesn't run are skipped.
|
|
3726
|
-
*/
|
|
3459
|
+
/** Cold start: an idle deferred stream still fires on time after a restart. */
|
|
3727
3460
|
async _seed_persisted_defers() {
|
|
3728
3461
|
const now = Date.now();
|
|
3729
3462
|
await walk_streams(store(), (pos) => {
|
|
@@ -3732,7 +3465,6 @@ var Act = class {
|
|
|
3732
3465
|
controller?.seed_defer(pos.stream, pos.deferred_at);
|
|
3733
3466
|
});
|
|
3734
3467
|
}
|
|
3735
|
-
/** Settle loop driving correlate + drain to quiescence. */
|
|
3736
3468
|
_build_settle(options) {
|
|
3737
3469
|
return new SettleLoop(
|
|
3738
3470
|
{
|
|
@@ -3746,7 +3478,6 @@ var Act = class {
|
|
|
3746
3478
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
3747
3479
|
);
|
|
3748
3480
|
}
|
|
3749
|
-
/** True after the first `shutdown()` call. Guards idempotency. */
|
|
3750
3481
|
_shutdown_promise;
|
|
3751
3482
|
/**
|
|
3752
3483
|
* Per-instance teardown: stop scheduling new work, give drain cycles
|
|
@@ -3786,14 +3517,8 @@ var Act = class {
|
|
|
3786
3517
|
return this._shutdown_promise;
|
|
3787
3518
|
}
|
|
3788
3519
|
/**
|
|
3789
|
-
*
|
|
3790
|
-
*
|
|
3791
|
-
* its own errors), so this never throws.
|
|
3792
|
-
*
|
|
3793
|
-
* An omitted budget is the largest `leaseMillis` among lanes with a cycle
|
|
3794
|
-
* in flight (the drain's fallback for lanes that pinned none, or pinned
|
|
3795
|
-
* 0, which is the same as none), with an in-flight settle counting on the
|
|
3796
|
-
* fallback, capped at the maximum. Idle lanes don't count.
|
|
3520
|
+
* The largest `leaseMillis` among lanes with a cycle in flight (the
|
|
3521
|
+
* default for none or 0, and for a running settle), capped.
|
|
3797
3522
|
*/
|
|
3798
3523
|
_derive_grace_ms(running, settling) {
|
|
3799
3524
|
let max = settling ? DEFAULT_LEASE_MILLIS : 0;
|
|
@@ -3822,12 +3547,7 @@ var Act = class {
|
|
|
3822
3547
|
clearTimeout(timer);
|
|
3823
3548
|
}
|
|
3824
3549
|
}
|
|
3825
|
-
/**
|
|
3826
|
-
* Subscribe to {@link Store.notify} when both the store and the
|
|
3827
|
-
* registry support it. Returns the disposer (or `undefined` when no
|
|
3828
|
-
* subscription was made). Errors during subscription are logged but
|
|
3829
|
-
* never thrown — `notify` is a hint, not a contract.
|
|
3830
|
-
*/
|
|
3550
|
+
/** Failures are logged, never thrown: `notify` is a hint. */
|
|
3831
3551
|
async _wire_notify(s) {
|
|
3832
3552
|
if (this._reactive_events.size === 0) return void 0;
|
|
3833
3553
|
if (!s.notify) return void 0;
|
|
@@ -4139,20 +3859,11 @@ var Act = class {
|
|
|
4139
3859
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
4140
3860
|
return this._scoped(() => this._drain_all(options));
|
|
4141
3861
|
}
|
|
4142
|
-
/** Arm every active lane controller. */
|
|
4143
3862
|
_arm_all() {
|
|
4144
3863
|
this._correlate.arm();
|
|
4145
3864
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
4146
3865
|
}
|
|
4147
|
-
/**
|
|
4148
|
-
* Arm only the lane controllers whose reactions match the supplied
|
|
4149
|
-
* event names (selective arming). Events with any dynamic
|
|
4150
|
-
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
4151
|
-
* resolver's lane isn't known until correlate runs the function.
|
|
4152
|
-
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
4153
|
-
* carry them. Returns true when any controller was armed (used by
|
|
4154
|
-
* the notify handler to decide whether to schedule a settle).
|
|
4155
|
-
*/
|
|
3866
|
+
/** Arms only the lanes these events react on; true when any was armed. */
|
|
4156
3867
|
_arm_for_event_names(names) {
|
|
4157
3868
|
const to_arm = /* @__PURE__ */ new Set();
|
|
4158
3869
|
for (const name of names) {
|
|
@@ -4169,14 +3880,7 @@ var Act = class {
|
|
|
4169
3880
|
for (const lane of to_arm) this._drain_controllers.get(lane)?.arm();
|
|
4170
3881
|
return true;
|
|
4171
3882
|
}
|
|
4172
|
-
/**
|
|
4173
|
-
*
|
|
4174
|
-
* Parallel — not sequential — so a slow lane's in-flight handler does
|
|
4175
|
-
* not block a fast lane's claim/dispatch/ack cycle. Each controller's
|
|
4176
|
-
* `claim()` is independent (filtered by lane); the store's
|
|
4177
|
-
* `SKIP LOCKED` keeps cross-controller races safe. Lifecycle events
|
|
4178
|
-
* (`acked`, `blocked`) may interleave by lane — listeners filter via
|
|
4179
|
-
* `lease.lane`. */
|
|
3883
|
+
/** In parallel, so a slow lane never holds up a fast one. */
|
|
4180
3884
|
async _drain_all(options) {
|
|
4181
3885
|
const results = await Promise.all(
|
|
4182
3886
|
[...this._drain_controllers.values()].map((c) => c.drain(options))
|
|
@@ -4249,14 +3953,7 @@ var Act = class {
|
|
|
4249
3953
|
const { subscribed, last_id } = await this._correlate_scanned(query);
|
|
4250
3954
|
return { subscribed, last_id };
|
|
4251
3955
|
}
|
|
4252
|
-
/**
|
|
4253
|
-
* `correlate` plus whether the pass actually read the store.
|
|
4254
|
-
*
|
|
4255
|
-
* The settle loop needs that extra bit to decide whether the pass carries a
|
|
4256
|
-
* circuit-breaker health signal, and a disarmed pass carries none. It stays
|
|
4257
|
-
* internal rather than widening the public `correlate` return, which is
|
|
4258
|
-
* charter-covered and has no use for it.
|
|
4259
|
-
*/
|
|
3956
|
+
/** `correlate` plus `scanned`, which settle needs for the breaker. */
|
|
4260
3957
|
async _correlate_scanned(query, lease = false) {
|
|
4261
3958
|
if (!this._drain) return { subscribed: 0, last_id: -1, scanned: false };
|
|
4262
3959
|
return this._scoped(async () => {
|
|
@@ -4597,11 +4294,7 @@ var Act = class {
|
|
|
4597
4294
|
async prioritize(filter, priority) {
|
|
4598
4295
|
return this._scoped(() => store().prioritize(filter, priority));
|
|
4599
4296
|
}
|
|
4600
|
-
/**
|
|
4601
|
-
* Run one close: the cycle with this Act's ports, then emit `closed`. Shared by `close()` and the drain's
|
|
4602
|
-
* reaction-requested closes. The close actor lets a custom correlator
|
|
4603
|
-
* still tag tenant context or trace ids.
|
|
4604
|
-
*/
|
|
4297
|
+
/** Shared by `close()` and reaction-requested closes. */
|
|
4605
4298
|
async _run_close(targets) {
|
|
4606
4299
|
const close_actor = { id: "$close", name: "close" };
|
|
4607
4300
|
const result = await run_close_cycle(targets, {
|
|
@@ -4682,10 +4375,7 @@ var Act = class {
|
|
|
4682
4375
|
*/
|
|
4683
4376
|
async close(targets) {
|
|
4684
4377
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
4685
|
-
return this._scoped(
|
|
4686
|
-
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
4687
|
-
return this._run_close(targets);
|
|
4688
|
-
});
|
|
4378
|
+
return this._scoped(() => this._run_close(targets));
|
|
4689
4379
|
}
|
|
4690
4380
|
/**
|
|
4691
4381
|
* Debounced, non-blocking correlate→drain cycle.
|