@rotorsoft/act 1.32.14 → 1.32.16
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 +98 -152
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +3 -3
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts +3 -3
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/build-classify.d.ts +1 -1
- package/dist/@types/builders/builder-base.d.ts +3 -3
- package/dist/@types/builders/builder-utils.d.ts +1 -1
- package/dist/@types/builders/event-builder.d.ts +1 -1
- package/dist/@types/builders/merge.d.ts +1 -1
- package/dist/@types/builders/reaction-builder.d.ts +1 -1
- package/dist/@types/builders/slice-builder.d.ts +2 -2
- package/dist/@types/builders/state-builder.d.ts +3 -4
- package/dist/@types/builders/state-builder.d.ts.map +1 -1
- package/dist/@types/csv.d.ts +1 -1
- package/dist/@types/disposers.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +12 -42
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/autoclose-policy.d.ts +3 -3
- package/dist/@types/internal/autoclose-reaction.d.ts +2 -2
- package/dist/@types/internal/autoclose-window.d.ts +1 -1
- package/dist/@types/internal/close-cycle.d.ts +10 -31
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/close-signal.d.ts +3 -3
- package/dist/@types/internal/config.d.ts +3 -3
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +39 -86
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlator.d.ts +1 -1
- package/dist/@types/internal/date-reviver.d.ts +1 -1
- package/dist/@types/internal/defer-config.d.ts +2 -2
- package/dist/@types/internal/defer-signal.d.ts +2 -2
- package/dist/@types/internal/defer-timer.d.ts +3 -3
- package/dist/@types/internal/drain-cycle.d.ts +44 -88
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +8 -19
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/event-versions.d.ts +1 -1
- package/dist/@types/internal/projection-fold.d.ts +2 -2
- package/dist/@types/internal/report-once.d.ts +3 -3
- package/dist/@types/internal/sensitive.d.ts +2 -2
- package/dist/@types/internal/settle.d.ts +6 -19
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +3 -3
- package/dist/@types/scoped.d.ts +6 -6
- package/dist/@types/scoped.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +23 -25
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/audit.d.ts +1 -1
- package/dist/@types/types/ports.d.ts +22 -26
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +15 -20
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/@types/types/registry.d.ts +1 -1
- package/dist/@types/types/schemas.d.ts +2 -2
- package/dist/@types/utils.d.ts +1 -1
- package/dist/{chunk-FVJFR7GH.js → chunk-JRTZGWVS.js} +1 -1
- package/dist/chunk-JRTZGWVS.js.map +1 -0
- package/dist/{chunk-JPPO3LZ3.js → chunk-PZP5ZEZ4.js} +9 -9
- package/dist/chunk-PZP5ZEZ4.js.map +1 -0
- package/dist/index.cjs +152 -372
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +147 -367
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +7 -7
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +2 -2
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-FVJFR7GH.js.map +0 -1
- package/dist/chunk-JPPO3LZ3.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-PZP5ZEZ4.js";
|
|
30
30
|
import {
|
|
31
31
|
ActorSchema,
|
|
32
32
|
CausationEventSchema,
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
pii_split,
|
|
54
54
|
pii_strip,
|
|
55
55
|
sensitive
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-JRTZGWVS.js";
|
|
57
57
|
|
|
58
58
|
// src/signals.ts
|
|
59
59
|
process.once("SIGINT", async (arg) => {
|
|
@@ -835,7 +835,7 @@ var CloseSignal = class extends Error {
|
|
|
835
835
|
*/
|
|
836
836
|
at;
|
|
837
837
|
/**
|
|
838
|
-
* Windowed close
|
|
838
|
+
* Windowed close: prune events older than this cutoff behind
|
|
839
839
|
* the closest safe snapshot instead of retiring the stream. Thrown by
|
|
840
840
|
* the autoclose reaction of a `.autocloses({ keep })` state; flows
|
|
841
841
|
* into {@link CloseTarget.before}. Omitted → a full close.
|
|
@@ -913,7 +913,7 @@ var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
|
913
913
|
// rejects `±Infinity`, and an integer check cannot pass it either. Wrapper
|
|
914
914
|
// code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)
|
|
915
915
|
// is the natural way to hit it. Accepting it also honors this module's own
|
|
916
|
-
// rule that a value which works today is never newly rejected
|
|
916
|
+
// rule that a value which works today is never newly rejected.
|
|
917
917
|
maxPasses: z2.union([z2.number().int().min(0), z2.literal(Number.POSITIVE_INFINITY)]).optional()
|
|
918
918
|
}).loose();
|
|
919
919
|
function resolveSettleConfig(options) {
|
|
@@ -1555,7 +1555,7 @@ var CorrelateCycle = class {
|
|
|
1555
1555
|
_checkpoint = -1;
|
|
1556
1556
|
_initialized = false;
|
|
1557
1557
|
/**
|
|
1558
|
-
* This worker's identity for the correlation lease
|
|
1558
|
+
* This worker's identity for the correlation lease. A per-instance
|
|
1559
1559
|
* UUID, matching the drain's convention, so a renewal is recognised as the
|
|
1560
1560
|
* same holder and a restarted process never inherits a stale claim.
|
|
1561
1561
|
*/
|
|
@@ -1571,24 +1571,15 @@ var CorrelateCycle = class {
|
|
|
1571
1571
|
* When this worker's correlation lease runs out, as a local clock reading,
|
|
1572
1572
|
* or 0 when it holds none.
|
|
1573
1573
|
*
|
|
1574
|
-
*
|
|
1575
|
-
*
|
|
1576
|
-
*
|
|
1577
|
-
* The act-sqlite perf gate caught that as a 1.5x regression on
|
|
1578
|
-
* correlate+drain, which is the shape an embedded app runs constantly.
|
|
1579
|
-
*
|
|
1580
|
-
* Believing this while the store disagrees is safe in the one direction it
|
|
1581
|
-
* can fail. A worker that scans without really holding the lease produces
|
|
1582
|
-
* the duplicate scan that existed before the lease, and the marks are
|
|
1583
|
-
* idempotent — so a stale belief costs work, never correctness.
|
|
1574
|
+
* Cached so the holder doesn't ask the store on every pass. A stale belief
|
|
1575
|
+
* can only cause a duplicate scan, and marks are idempotent, so it costs
|
|
1576
|
+
* work, never correctness.
|
|
1584
1577
|
*/
|
|
1585
1578
|
_lease_until = 0;
|
|
1586
1579
|
/**
|
|
1587
|
-
* Whether a scan might find anything
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
* equivalent, so a settle pass always scanned, including the final pass
|
|
1591
|
-
* whose only job is to confirm nothing changed (#1510).
|
|
1580
|
+
* Whether a scan might find anything, like the drain's flag: a commit or
|
|
1581
|
+
* notification raises it, a short page lowers it, and a disarmed scan
|
|
1582
|
+
* returns without touching the store.
|
|
1592
1583
|
*
|
|
1593
1584
|
* Starts armed: the log may already hold events this process has never
|
|
1594
1585
|
* correlated, and only a scan can find out.
|
|
@@ -1597,15 +1588,9 @@ var CorrelateCycle = class {
|
|
|
1597
1588
|
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
1598
1589
|
_init_promise;
|
|
1599
1590
|
// Dynamically discovered targets → what each was last subscribed at,
|
|
1600
|
-
// bounded by `maxSubscribedStreams`.
|
|
1601
|
-
//
|
|
1602
|
-
//
|
|
1603
|
-
// Every scan re-subscribes the
|
|
1604
|
-
// targets it resolved (that is how the work mark lands), so this no longer
|
|
1605
|
-
// decides *whether* a target is sent — it decides *what* is sent with it:
|
|
1606
|
-
// a resolution raises priority/lane only when it beats the recorded floor,
|
|
1607
|
-
// and otherwise re-sends the row's own values so the mark changes nothing
|
|
1608
|
-
// else. See {@link Subscription}.
|
|
1591
|
+
// bounded by `maxSubscribedStreams`. It decides *what* a scan sends with a
|
|
1592
|
+
// target: priority/lane only when the resolution beats the recorded floor,
|
|
1593
|
+
// otherwise the row's own values. See {@link Subscription}.
|
|
1609
1594
|
_dynamic_subscriptions;
|
|
1610
1595
|
/**
|
|
1611
1596
|
* What each static target was subscribed at by `init`. A plain map, never
|
|
@@ -1613,14 +1598,8 @@ var CorrelateCycle = class {
|
|
|
1613
1598
|
* is already bounded by the registry and costs nothing the registry does
|
|
1614
1599
|
* not already hold.
|
|
1615
1600
|
*
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1618
|
-
* churn of dynamic targets could evict a static record, and the next
|
|
1619
|
-
* dynamic resolution to that target found no record, took the
|
|
1620
|
-
* first-discovery branch, and re-subscribed the target with its own
|
|
1621
|
-
* priority and lane — silently re-laning a stream whose lane the
|
|
1622
|
-
* build-time subscribe owns, and starving it wherever `onlyLanes` had
|
|
1623
|
-
* provisioned a worker for the declared lane.
|
|
1601
|
+
* Kept out of the LRU so eviction can never drop a static target's
|
|
1602
|
+
* `+Infinity` floor and let a dynamic resolution re-lane it.
|
|
1624
1603
|
*/
|
|
1625
1604
|
_static_subscriptions = /* @__PURE__ */ new Map();
|
|
1626
1605
|
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
@@ -1630,17 +1609,13 @@ var CorrelateCycle = class {
|
|
|
1630
1609
|
_cd;
|
|
1631
1610
|
_on_init;
|
|
1632
1611
|
/**
|
|
1633
|
-
* Async cold-start hook
|
|
1634
|
-
*
|
|
1635
|
-
*
|
|
1636
|
-
* an idle deferred stream re-arms its drain across a restart. Kept
|
|
1637
|
-
* separate from `on_init` because seeding is an async store read; `init`
|
|
1638
|
-
* already awaits, so folding it in here preserves the "runs exactly once"
|
|
1639
|
-
* guarantee without a second gate on the Act side.
|
|
1612
|
+
* Async cold-start hook, run once after `on_init` inside `init()`. The
|
|
1613
|
+
* orchestrator re-seeds its defer timers from the persisted `deferred_at`
|
|
1614
|
+
* here, so an idle deferred stream re-arms across a restart.
|
|
1640
1615
|
*/
|
|
1641
1616
|
_on_init_async;
|
|
1642
1617
|
/**
|
|
1643
|
-
* Tail re-scan window applied to the cold-start checkpoint
|
|
1618
|
+
* Tail re-scan window applied to the cold-start checkpoint.
|
|
1644
1619
|
* See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
|
|
1645
1620
|
* public option) so tests can shrink it; defaults otherwise.
|
|
1646
1621
|
*/
|
|
@@ -1692,7 +1667,7 @@ var CorrelateCycle = class {
|
|
|
1692
1667
|
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
1693
1668
|
* flooring a first boot at `watermark - back_scan` so an event
|
|
1694
1669
|
* committed-but-not-correlated before a crash is re-scanned on
|
|
1695
|
-
* restart instead of skipped
|
|
1670
|
+
* restart instead of skipped
|
|
1696
1671
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
1697
1672
|
* - Populates the subscribed-streams LRU
|
|
1698
1673
|
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
@@ -1709,7 +1684,7 @@ var CorrelateCycle = class {
|
|
|
1709
1684
|
this._initialized = true;
|
|
1710
1685
|
}
|
|
1711
1686
|
async _run_init() {
|
|
1712
|
-
const { watermark, correlated_at } = await
|
|
1687
|
+
const { watermark, correlated_at } = await this._cd.subscribe([
|
|
1713
1688
|
...this._static_targets
|
|
1714
1689
|
]);
|
|
1715
1690
|
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
@@ -1723,21 +1698,6 @@ var CorrelateCycle = class {
|
|
|
1723
1698
|
}
|
|
1724
1699
|
await this._on_init_async?.();
|
|
1725
1700
|
}
|
|
1726
|
-
/**
|
|
1727
|
-
* Forget targets whose subscription rows no longer exist, so a later
|
|
1728
|
-
* scan can re-subscribe them.
|
|
1729
|
-
*
|
|
1730
|
-
* A full close deletes the closed stream's subscription row. The
|
|
1731
|
-
* in-process dedup would otherwise still believe the target is
|
|
1732
|
-
* subscribed and never re-issue `subscribe()`, silently stopping
|
|
1733
|
-
* delivery for any reaction whose target is named after the stream —
|
|
1734
|
-
* the documented per-aggregate shape `.to(e => ({target: e.stream}))`
|
|
1735
|
-
* makes those two namespaces collide by construction (#1398).
|
|
1736
|
-
*
|
|
1737
|
-
* Static targets are left alone: their record lives in
|
|
1738
|
-
* `_static_subscriptions`, which this never touches, so they stay pinned
|
|
1739
|
-
* at +Infinity and the dynamic path never re-opens them.
|
|
1740
|
-
*/
|
|
1741
1701
|
/**
|
|
1742
1702
|
* Start correlation over after the event log was replaced wholesale
|
|
1743
1703
|
* (`restore`). The restored log is renumbered from the start and its
|
|
@@ -1746,7 +1706,7 @@ var CorrelateCycle = class {
|
|
|
1746
1706
|
* and the init latch makes the next pass run cold-start again: static
|
|
1747
1707
|
* targets are re-subscribed, the checkpoint is re-read from the store
|
|
1748
1708
|
* (which `restore` reset), and dynamic targets are rediscovered by the
|
|
1749
|
-
* scan
|
|
1709
|
+
* scan.
|
|
1750
1710
|
*/
|
|
1751
1711
|
restart() {
|
|
1752
1712
|
this._initialized = false;
|
|
@@ -1756,6 +1716,11 @@ var CorrelateCycle = class {
|
|
|
1756
1716
|
this._static_subscriptions.clear();
|
|
1757
1717
|
this._armed = true;
|
|
1758
1718
|
}
|
|
1719
|
+
/**
|
|
1720
|
+
* Forget dynamic targets whose subscription rows no longer exist (a full
|
|
1721
|
+
* close deletes the row), so a later scan re-subscribes them. Static
|
|
1722
|
+
* targets keep their records.
|
|
1723
|
+
*/
|
|
1759
1724
|
forget_subscribed(streams) {
|
|
1760
1725
|
for (const stream of streams) this._dynamic_subscriptions.delete(stream);
|
|
1761
1726
|
}
|
|
@@ -1780,7 +1745,7 @@ var CorrelateCycle = class {
|
|
|
1780
1745
|
* Scan the events past the checkpoint, resolve each to its target
|
|
1781
1746
|
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
1782
1747
|
* targets get registered, and every target an event resolved to gets its
|
|
1783
|
-
* **work mark** raised to that event's id
|
|
1748
|
+
* **work mark** raised to that event's id.
|
|
1784
1749
|
*
|
|
1785
1750
|
* Both resolver kinds are walked. A static target is already subscribed
|
|
1786
1751
|
* at init, but marking it is what makes it claimable without probing the
|
|
@@ -1867,18 +1832,10 @@ var CorrelateCycle = class {
|
|
|
1867
1832
|
}
|
|
1868
1833
|
}
|
|
1869
1834
|
},
|
|
1870
|
-
// Decline the sensitive payload:
|
|
1871
|
-
//
|
|
1872
|
-
//
|
|
1873
|
-
//
|
|
1874
|
-
// decrypt failure here strands the whole app with nothing blocked and
|
|
1875
|
-
// nothing naming the row (#1675).
|
|
1876
|
-
//
|
|
1877
|
-
// A dynamic `.to(event => …)` resolver therefore sees `pii: null`.
|
|
1878
|
-
// That is deliberate and is itself a fix: correlate runs actor-less,
|
|
1879
|
-
// so handing a resolver decrypted plaintext is un-gated disclosure —
|
|
1880
|
-
// the defect #1673 corrected on the drain return and #1277 before it.
|
|
1881
|
-
// `data` already carries no sensitive keys; nothing merges them here.
|
|
1835
|
+
// Decline the sensitive payload: the scan only needs name/stream/id,
|
|
1836
|
+
// and one undecryptable row would otherwise stop every stream's
|
|
1837
|
+
// reactions. Resolvers therefore see `pii: null`; correlate runs
|
|
1838
|
+
// actor-less, so plaintext here would be un-gated disclosure.
|
|
1882
1839
|
{ ...query, after, with_pii: false }
|
|
1883
1840
|
);
|
|
1884
1841
|
const streams = [];
|
|
@@ -1922,18 +1879,9 @@ var CorrelateCycle = class {
|
|
|
1922
1879
|
/**
|
|
1923
1880
|
* Hand the correlation lease back early.
|
|
1924
1881
|
*
|
|
1925
|
-
*
|
|
1926
|
-
*
|
|
1927
|
-
*
|
|
1928
|
-
* holder renews, and renewing to a millisecond is a release in all but
|
|
1929
|
-
* name.
|
|
1930
|
-
*
|
|
1931
|
-
* Without this a worker that stops cleanly still blocks discovery for the
|
|
1932
|
-
* rest of its lease — invisible in a long-lived deployment, very visible
|
|
1933
|
-
* anywhere Acts are created and dropped inside one process.
|
|
1934
|
-
*
|
|
1935
|
-
* Best-effort: failing here costs the lease's remaining lifetime, which is
|
|
1936
|
-
* what would have happened had the process died instead.
|
|
1882
|
+
* The port has no release verb; re-acquiring as the same holder with a
|
|
1883
|
+
* zero duration releases it. Best-effort: a failure costs the lease's
|
|
1884
|
+
* remaining lifetime, as a crash would.
|
|
1937
1885
|
*/
|
|
1938
1886
|
async release_correlation() {
|
|
1939
1887
|
try {
|
|
@@ -2419,26 +2367,11 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2419
2367
|
{
|
|
2420
2368
|
stream,
|
|
2421
2369
|
stream_exact: true,
|
|
2422
|
-
//
|
|
2423
|
-
//
|
|
2424
|
-
//
|
|
2425
|
-
// a
|
|
2426
|
-
//
|
|
2427
|
-
// for the whole system; the stores apply the floor whenever asked and
|
|
2428
|
-
// never re-derive it (RFC 1274).
|
|
2429
|
-
//
|
|
2430
|
-
// The warm path resumes from `after`, and MUST also carry `with_snaps`
|
|
2431
|
-
// (#1345): `after` and `with_snaps` are independent store conditions —
|
|
2432
|
-
// `after` bounds the scan (id > cached.event_id), `with_snaps` keeps
|
|
2433
|
-
// `__snapshot__` rows in the result. A stale (lagging/cross-process)
|
|
2434
|
-
// cache checkpoint can sit below a newer `__snapshot__` boundary, and a
|
|
2435
|
-
// windowed close (`app.close`/`.autocloses({keep})`) may have pruned the
|
|
2436
|
-
// domain events between the checkpoint and that snapshot. Without
|
|
2437
|
-
// `with_snaps` the rebaselining snapshot is filtered out and the fold
|
|
2438
|
-
// silently applies the surviving tail on top of stale state (wrong
|
|
2439
|
-
// count, yet `version` still reports the true head — the concurrency
|
|
2440
|
-
// guard passes). With it, the snapshot in the after-window rebaselines
|
|
2441
|
-
// the fold; when no snapshot falls in the window it is a no-op.
|
|
2370
|
+
// `with_snaps` on every non-time-travel load. Cold, it resumes from the
|
|
2371
|
+
// latest snapshot (this is the only place that decides it; stores just
|
|
2372
|
+
// apply it). Warm, it keeps a snapshot above a stale checkpoint in the
|
|
2373
|
+
// result, so a windowed close that pruned the events in between can't
|
|
2374
|
+
// make the fold apply the tail onto stale state.
|
|
2442
2375
|
...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
|
|
2443
2376
|
}
|
|
2444
2377
|
);
|
|
@@ -2465,7 +2398,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2465
2398
|
// Head event id, captured atomically with `state`: the last replayed
|
|
2466
2399
|
// event's id on a cache miss, or the cached checkpoint's event_id on a
|
|
2467
2400
|
// warm hit with no new events. Consumers read this instead of a
|
|
2468
|
-
// separate cache lookup that could race a concurrent commit
|
|
2401
|
+
// separate cache lookup that could race a concurrent commit.
|
|
2469
2402
|
id: event?.id ?? cached?.event_id ?? -1,
|
|
2470
2403
|
patches,
|
|
2471
2404
|
snaps,
|
|
@@ -2543,14 +2476,8 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2543
2476
|
stream,
|
|
2544
2477
|
emitted,
|
|
2545
2478
|
meta,
|
|
2546
|
-
// Reactions skip the
|
|
2547
|
-
//
|
|
2548
|
-
// and forcing version checks here would turn ordinary catch-up into
|
|
2549
|
-
// spurious retries. An expectedVersion the caller passed explicitly is
|
|
2550
|
-
// still honored — dropping a guard the caller asked for is silent data
|
|
2551
|
-
// loss, and the reaction context can reach further than the handler
|
|
2552
|
-
// (detached timers, settle cycles) where the caller never intended a
|
|
2553
|
-
// reaction's semantics at all.
|
|
2479
|
+
// Reactions skip the inferred version guard (leases already
|
|
2480
|
+
// serialize them); an explicit expectedVersion is still honored.
|
|
2554
2481
|
reactingTo ? expectedVersion : expected
|
|
2555
2482
|
);
|
|
2556
2483
|
} catch (error) {
|
|
@@ -2829,7 +2756,16 @@ function warn_misrouted(seen, stream, handler, event) {
|
|
|
2829
2756
|
`Reaction "${handler}" on "${event}" resolved to target "${stream}", which a projection already serves. A target is served by one batch handler or one state projection, so this reaction can never run \u2014 and delivering to it would hand the projection an aggregate from another stream. Skipping it. The equivalent static \`.to({ target })\` is rejected at build; a dynamic resolver's target is only knowable here.`
|
|
2830
2757
|
);
|
|
2831
2758
|
}
|
|
2832
|
-
async function run_drain_cycle(
|
|
2759
|
+
async function run_drain_cycle(deps, cycle) {
|
|
2760
|
+
const { ops, registry, batch_handlers, handle, handle_batch, lane } = deps;
|
|
2761
|
+
const {
|
|
2762
|
+
lagging,
|
|
2763
|
+
leading,
|
|
2764
|
+
eventLimit,
|
|
2765
|
+
leaseMillis,
|
|
2766
|
+
misrouted,
|
|
2767
|
+
store_failing
|
|
2768
|
+
} = cycle;
|
|
2833
2769
|
const leased = await ops.claim(
|
|
2834
2770
|
lagging,
|
|
2835
2771
|
leading,
|
|
@@ -2901,7 +2837,7 @@ async function run_drain_cycle(ops, registry, batch_handlers, misrouted, store_f
|
|
|
2901
2837
|
const blocked = await ops.block(
|
|
2902
2838
|
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
2903
2839
|
);
|
|
2904
|
-
if (blocked.length) on_blocked(blocked);
|
|
2840
|
+
if (blocked.length) deps.on_blocked(blocked);
|
|
2905
2841
|
const submitted = handled.flatMap((h, i) => {
|
|
2906
2842
|
const advance = h.handled > 0 ? h.acked_at : leased[i].at;
|
|
2907
2843
|
return h.defer !== void 0 ? { ...h.lease, at: advance, due: h.defer, retry: -1 } : h.next_attempt_at !== void 0 ? { ...h.lease, at: advance, due: h.next_attempt_at } : h.handled > 0 || !h.error ? { ...h.lease, at: h.acked_at } : [];
|
|
@@ -2926,32 +2862,25 @@ var DrainController = class {
|
|
|
2926
2862
|
_locked = false;
|
|
2927
2863
|
_ratio = 0.5;
|
|
2928
2864
|
/**
|
|
2929
|
-
*
|
|
2930
|
-
*
|
|
2931
|
-
*
|
|
2932
|
-
* process memory — per-worker pacing by design (see {@link BackoffOptions}
|
|
2933
|
-
* for the multi-worker trade-off). Its wake re-arms drain at the earliest
|
|
2934
|
-
* pending visit.
|
|
2865
|
+
* Local wake for streams this worker parked (backoff or defer). The
|
|
2866
|
+
* schedule itself is persisted in the store; this only re-arms the drain
|
|
2867
|
+
* at the earliest pending visit.
|
|
2935
2868
|
*/
|
|
2936
2869
|
_defer = new DeferTimer(() => {
|
|
2937
2870
|
this._armed = true;
|
|
2938
2871
|
});
|
|
2939
|
-
/** Worker timer
|
|
2872
|
+
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
2940
2873
|
_worker;
|
|
2941
2874
|
/**
|
|
2942
|
-
* Misroutings this controller has reported
|
|
2875
|
+
* Misroutings this controller has reported. A resolver returning a
|
|
2943
2876
|
* projection's target does so for every matching event; one line per event
|
|
2944
2877
|
* would bury the signal it exists to raise.
|
|
2945
2878
|
*/
|
|
2946
2879
|
_misrouted = /* @__PURE__ */ new Set();
|
|
2947
2880
|
_stopped = false;
|
|
2948
2881
|
/**
|
|
2949
|
-
* Resolves when the cycle
|
|
2950
|
-
*
|
|
2951
|
-
* the overlap guard; this answers "tell me when it is done" for a graceful
|
|
2952
|
-
* shutdown, which needs to await the handler rather than abandon it
|
|
2953
|
-
* mid-`await` with the stream still leased. Never rejects — `drain()`
|
|
2954
|
-
* contains its own errors — so awaiting it is always safe.
|
|
2882
|
+
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
2883
|
+
* wait for it. Never rejects.
|
|
2955
2884
|
*/
|
|
2956
2885
|
_inflight;
|
|
2957
2886
|
_inflight_done;
|
|
@@ -2968,16 +2897,9 @@ var DrainController = class {
|
|
|
2968
2897
|
this._armed = true;
|
|
2969
2898
|
}
|
|
2970
2899
|
/**
|
|
2971
|
-
* Re-seed a persisted
|
|
2972
|
-
*
|
|
2973
|
-
*
|
|
2974
|
-
* autoclose aggregate) is durable in the store's `deferred_at` but has
|
|
2975
|
-
* nothing in memory to re-arm the drain at the due-time. The orchestrator
|
|
2976
|
-
* reads the persisted `deferred_at` for this controller's lane and calls
|
|
2977
|
-
* this to park the stream + (re)schedule the shared wake — so the drain
|
|
2978
|
-
* re-arms at the due-time with no intervening commit. `schedule()`
|
|
2979
|
-
* collapses many seeds into one timer, so callers may seed in a loop and
|
|
2980
|
-
* let the earliest due-time win.
|
|
2900
|
+
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
2901
|
+
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
2902
|
+
* Seeds collapse into one timer; the earliest wins.
|
|
2981
2903
|
*/
|
|
2982
2904
|
seed_defer(stream, at) {
|
|
2983
2905
|
this._defer.set(stream, at);
|
|
@@ -2987,31 +2909,21 @@ var DrainController = class {
|
|
|
2987
2909
|
get armed() {
|
|
2988
2910
|
return this._armed;
|
|
2989
2911
|
}
|
|
2990
|
-
/**
|
|
2991
|
-
* The cycle currently in flight, or `undefined` when idle (#1442). A
|
|
2992
|
-
* graceful shutdown awaits this so an in-flight handler reaches its `ack`
|
|
2993
|
-
* — which releases the stream's lease — instead of being abandoned with
|
|
2994
|
-
* the lease held until it expires.
|
|
2995
|
-
*/
|
|
2912
|
+
/** The cycle in flight, or `undefined` when idle. */
|
|
2996
2913
|
get inflight() {
|
|
2997
2914
|
return this._inflight;
|
|
2998
2915
|
}
|
|
2999
|
-
/**
|
|
3000
|
-
* This lane's configured lease budget, or `undefined` when the lane didn't
|
|
3001
|
-
* pin one. It is the operator's own statement of how long a handler may
|
|
3002
|
-
* legitimately hold a stream, which makes it the right basis for a
|
|
3003
|
-
* shutdown grace budget (#1442).
|
|
3004
|
-
*/
|
|
2916
|
+
/** This lane's configured lease, or `undefined`; the shutdown grace basis. */
|
|
3005
2917
|
get lease_millis() {
|
|
3006
2918
|
return this._deps.defaults?.leaseMillis;
|
|
3007
2919
|
}
|
|
3008
|
-
/** Lane this controller drains (undefined
|
|
2920
|
+
/** Lane this controller drains (undefined spans all lanes). */
|
|
3009
2921
|
get lane() {
|
|
3010
2922
|
return this._deps.lane;
|
|
3011
2923
|
}
|
|
3012
2924
|
/**
|
|
3013
2925
|
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
3014
|
-
* cadence
|
|
2926
|
+
* cadence. When armed, the worker calls `drain()` on every
|
|
3015
2927
|
* tick and re-schedules; when not armed, it still re-schedules at
|
|
3016
2928
|
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
3017
2929
|
*
|
|
@@ -3056,21 +2968,14 @@ var DrainController = class {
|
|
|
3056
2968
|
});
|
|
3057
2969
|
const lagging = Math.ceil(streamLimit * this._ratio);
|
|
3058
2970
|
const leading = streamLimit - lagging;
|
|
3059
|
-
const cycle = await run_drain_cycle(
|
|
3060
|
-
this._deps.ops,
|
|
3061
|
-
this._deps.registry,
|
|
3062
|
-
this._deps.batch_handlers,
|
|
3063
|
-
this._misrouted,
|
|
3064
|
-
this._deps.breaker.failing,
|
|
3065
|
-
this._deps.handle,
|
|
3066
|
-
this._deps.handle_batch,
|
|
2971
|
+
const cycle = await run_drain_cycle(this._deps, {
|
|
3067
2972
|
lagging,
|
|
3068
2973
|
leading,
|
|
3069
2974
|
eventLimit,
|
|
3070
2975
|
leaseMillis,
|
|
3071
|
-
|
|
3072
|
-
this._deps.
|
|
3073
|
-
);
|
|
2976
|
+
misrouted: this._misrouted,
|
|
2977
|
+
store_failing: this._deps.breaker.failing
|
|
2978
|
+
});
|
|
3074
2979
|
if (!cycle) {
|
|
3075
2980
|
this._deps.breaker.passed();
|
|
3076
2981
|
this._armed = false;
|
|
@@ -3198,26 +3103,13 @@ var SettleLoop = class {
|
|
|
3198
3103
|
_timer = void 0;
|
|
3199
3104
|
_running = false;
|
|
3200
3105
|
/**
|
|
3201
|
-
* Resolves when the cycle
|
|
3202
|
-
*
|
|
3203
|
-
* re-arm bookkeeping; this answers "tell me when it is done" for a
|
|
3204
|
-
* graceful shutdown.
|
|
3205
|
-
*
|
|
3206
|
-
* `stop()` only cancels *scheduling* — a cycle already inside its
|
|
3207
|
-
* correlate → drain loop keeps going, and because `DrainController.drain`
|
|
3208
|
-
* does not consult `_stopped`, it can claim a stream after teardown
|
|
3209
|
-
* returned and after the store adapter was disposed. Never rejects: the
|
|
3210
|
-
* cycle's own `catch` contains its errors, so awaiting this is safe.
|
|
3106
|
+
* Resolves when the cycle in flight finishes, so shutdown can wait for
|
|
3107
|
+
* it (`stop()` cancels scheduling only). Never rejects.
|
|
3211
3108
|
*/
|
|
3212
3109
|
_inflight;
|
|
3213
3110
|
/**
|
|
3214
|
-
*
|
|
3215
|
-
*
|
|
3216
|
-
* the wake-up isn't dropped — a commit landing during the final
|
|
3217
|
-
* no-progress drain pass would otherwise leave armed controllers with
|
|
3218
|
-
* nothing to re-drain on an instance with no lane `cycleMs` and no
|
|
3219
|
-
* polling. Carries the options of the dropped call so the re-armed
|
|
3220
|
-
* pass honors its `debounceMs`/`maxPasses`/drain overrides.
|
|
3111
|
+
* A wake-up that fired while a cycle was running. The running cycle's
|
|
3112
|
+
* `finally` schedules one more pass with these options, so it isn't lost.
|
|
3221
3113
|
*/
|
|
3222
3114
|
_pending = void 0;
|
|
3223
3115
|
_deps;
|
|
@@ -3289,7 +3181,7 @@ var SettleLoop = class {
|
|
|
3289
3181
|
}, debounceMs);
|
|
3290
3182
|
}
|
|
3291
3183
|
/**
|
|
3292
|
-
* The cycle currently in flight, or `undefined` when idle
|
|
3184
|
+
* The cycle currently in flight, or `undefined` when idle. A
|
|
3293
3185
|
* graceful shutdown awaits this alongside the drain controllers so a
|
|
3294
3186
|
* settle parked in `correlate` does not resume after teardown.
|
|
3295
3187
|
*/
|
|
@@ -3435,17 +3327,17 @@ function validate_only_lanes(options, lanes) {
|
|
|
3435
3327
|
}
|
|
3436
3328
|
var Act = class {
|
|
3437
3329
|
_emitter = new EventEmitter();
|
|
3438
|
-
/**
|
|
3330
|
+
/** Circuit breaker shared by all drain lanes. */
|
|
3439
3331
|
_breaker;
|
|
3440
|
-
/**
|
|
3332
|
+
/** Whether this instance subscribes to `Store.notify`. */
|
|
3441
3333
|
_listen;
|
|
3442
|
-
/**
|
|
3334
|
+
/** Whether this instance runs reactions (drain, settle, correlate). */
|
|
3443
3335
|
_drain;
|
|
3444
3336
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
3445
3337
|
_reactive_events;
|
|
3446
3338
|
/** One DrainController per active lane, keyed by lane name. */
|
|
3447
3339
|
_drain_controllers;
|
|
3448
|
-
/** Correlation
|
|
3340
|
+
/** Correlation: lazy init and the scan that marks work. */
|
|
3449
3341
|
_correlate;
|
|
3450
3342
|
/** Debounced correlate→drain catch-up loop. */
|
|
3451
3343
|
_settle;
|
|
@@ -3475,26 +3367,10 @@ var Act = class {
|
|
|
3475
3367
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
3476
3368
|
* via {@link ActLifecycleEvents}.
|
|
3477
3369
|
*
|
|
3478
|
-
*
|
|
3479
|
-
*
|
|
3480
|
-
*
|
|
3481
|
-
*
|
|
3482
|
-
*
|
|
3483
|
-
* - Wrapping the *emit* rather than each *listener* still let the first
|
|
3484
|
-
* thrower abort the rest: `EventEmitter.emit` stops dispatching on the
|
|
3485
|
-
* first exception, so a second `app.on("acked", …)` never ran. The
|
|
3486
|
-
* documented "the remaining sinks still fire" was false whenever an event
|
|
3487
|
-
* had more than one listener. Same lesson as #1423, where guarding the
|
|
3488
|
-
* loop instead of each callback left every later SSE subscriber unserved.
|
|
3489
|
-
* - Only *some* call sites wrapped at all. The drain contained `acked` and
|
|
3490
|
-
* `blocked`; `committed`, `forgotten` and `close()`'s `closed` did not, so
|
|
3491
|
-
* a throwing listener rejected `do()`, `forget()` and `close()` **after**
|
|
3492
|
-
* their durable work had already landed. A caller retrying a "failed"
|
|
3493
|
-
* `do()` writes the event twice, since the framework has no dedup by
|
|
3494
|
-
* design.
|
|
3495
|
-
*
|
|
3496
|
-
* Containing here makes it a property of emitting, so a new lifecycle event
|
|
3497
|
-
* cannot reintroduce the gap by omission.
|
|
3370
|
+
* Each listener is contained on its own: a throwing listener is logged,
|
|
3371
|
+
* the remaining listeners still run, and the caller (`do()`, `forget()`,
|
|
3372
|
+
* `close()`, the drain) never sees the throw, since its durable work has
|
|
3373
|
+
* already landed.
|
|
3498
3374
|
*
|
|
3499
3375
|
* Uses `rawListeners` so `once` wrappers still de-register themselves, and
|
|
3500
3376
|
* returns "had listeners" to preserve the `EventEmitter.emit` contract.
|
|
@@ -3555,29 +3431,23 @@ var Act = class {
|
|
|
3555
3431
|
*/
|
|
3556
3432
|
_event_to_state;
|
|
3557
3433
|
/**
|
|
3558
|
-
* Event-name → lane fan-in for selective arming
|
|
3434
|
+
* Event-name → lane fan-in for selective arming. Built by
|
|
3559
3435
|
* `classify_registry` once per build. `"all"` means at least one of
|
|
3560
3436
|
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3561
3437
|
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3562
3438
|
* reactions target.
|
|
3563
3439
|
*/
|
|
3564
3440
|
_event_to_lanes;
|
|
3565
|
-
/**
|
|
3566
|
-
* Audit dependency bag (#723). Built once at construction; held as
|
|
3567
|
-
* an immutable snapshot of the registry state the audit module
|
|
3568
|
-
* needs. Lives in `internal/audit.ts` — this orchestrator never
|
|
3569
|
-
* carries audit logic, only the deps + a one-liner that hands them
|
|
3570
|
-
* over.
|
|
3571
|
-
*/
|
|
3441
|
+
/** What `internal/audit.ts` needs, built once at construction. */
|
|
3572
3442
|
_audit_deps;
|
|
3573
3443
|
/** Logger resolved at construction time (after user port configuration) */
|
|
3574
3444
|
_logger = log();
|
|
3575
|
-
/** Wraps a public-method body so internal `store()`/`cache()` resolve to the
|
|
3576
|
-
* per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
|
|
3577
|
-
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
3578
|
-
* tests that dispose and re-seed mid-suite. */
|
|
3579
3445
|
/** This Act's ports: its own bag, or the singleton adapters. */
|
|
3580
3446
|
_ports;
|
|
3447
|
+
/**
|
|
3448
|
+
* Runs a body inside this Act's ports frame, so internal
|
|
3449
|
+
* `store()`/`cache()` calls resolve to `_ports`.
|
|
3450
|
+
*/
|
|
3581
3451
|
_scoped;
|
|
3582
3452
|
/**
|
|
3583
3453
|
* Correlation-id generator for originating actions. Bound at
|
|
@@ -3596,24 +3466,18 @@ var Act = class {
|
|
|
3596
3466
|
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
3597
3467
|
_handle;
|
|
3598
3468
|
_handle_batch;
|
|
3599
|
-
/** Declared drain lanes
|
|
3469
|
+
/** Declared drain lanes. */
|
|
3600
3470
|
_lanes;
|
|
3601
3471
|
/**
|
|
3602
|
-
* Per-stream close serialization tails
|
|
3603
|
-
*
|
|
3604
|
-
*
|
|
3605
|
-
* the same stream never run their guard-free prune concurrently — the
|
|
3606
|
-
* manual path bypasses the `__autoclose__:X` drain lease that would
|
|
3607
|
-
* otherwise exclude them, so without this both closers archive the
|
|
3608
|
-
* same prefix. Process-local: both racers run on the same Act
|
|
3609
|
-
* instance. Entries are dropped once their tail resolves so the map
|
|
3610
|
-
* doesn't grow with distinct stream names.
|
|
3472
|
+
* Per-stream close serialization tails, so a manual close and an
|
|
3473
|
+
* autoclose of the same stream never prune concurrently (the manual path
|
|
3474
|
+
* has no drain lease to exclude it). Entries are dropped once settled.
|
|
3611
3475
|
*/
|
|
3612
3476
|
_close_locks = /* @__PURE__ */ new Map();
|
|
3613
3477
|
/**
|
|
3614
|
-
* Run `work` under the per-stream close lock
|
|
3615
|
-
* critical sections for the same stream (windowed prunes
|
|
3616
|
-
*
|
|
3478
|
+
* Run `work` under the per-stream close lock. Serializes close
|
|
3479
|
+
* critical sections for the same stream (windowed prunes and full
|
|
3480
|
+
* closes) while letting different streams proceed in parallel.
|
|
3617
3481
|
*/
|
|
3618
3482
|
_with_close_lock(stream, work) {
|
|
3619
3483
|
const prev = this._close_locks.get(stream) ?? Promise.resolve();
|
|
@@ -3640,11 +3504,11 @@ var Act = class {
|
|
|
3640
3504
|
* @param states Merged map of state name → state definition
|
|
3641
3505
|
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
3642
3506
|
* @param options Tuning knobs — see {@link ActOptions}
|
|
3643
|
-
* @param lanes Declared drain lanes
|
|
3644
|
-
* these from `.withLane(...)` calls
|
|
3645
|
-
*
|
|
3507
|
+
* @param lanes Declared drain lanes. The builder collects
|
|
3508
|
+
* these from `.withLane(...)` calls; one `DrainController` runs
|
|
3509
|
+
* per lane, plus the implicit `"default"`.
|
|
3646
3510
|
* @param patch_fn The per-event patch step selected once by the builder
|
|
3647
|
-
* from `ActOptions.validateFoldedState`
|
|
3511
|
+
* from `ActOptions.validateFoldedState` — `bare_patch` by
|
|
3648
3512
|
* default, `validating_patch` when the flag is on. The builder uses
|
|
3649
3513
|
* the same value for its projection-fold handlers, so there is a
|
|
3650
3514
|
* single selection site. Defaults to `bare_patch` for direct
|
|
@@ -3659,16 +3523,15 @@ var Act = class {
|
|
|
3659
3523
|
this._ports = options.scoped ?? default_scope();
|
|
3660
3524
|
const ports = this._ports;
|
|
3661
3525
|
this._scoped = make_run_scoped({
|
|
3662
|
-
//
|
|
3663
|
-
//
|
|
3664
|
-
// whichever adapters happened to be installed at construction and
|
|
3665
|
-
// ignore a later `store(...)` / `cache(...)`.
|
|
3526
|
+
// Getters, not a spread: the singletons resolve lazily, so a later
|
|
3527
|
+
// `store(...)` / `cache(...)` still takes effect.
|
|
3666
3528
|
get store() {
|
|
3667
3529
|
return ports.store;
|
|
3668
3530
|
},
|
|
3669
3531
|
get cache() {
|
|
3670
3532
|
return ports.cache;
|
|
3671
3533
|
},
|
|
3534
|
+
// Parsed per build, so a bad window throws on every build.
|
|
3672
3535
|
autoclose_window: resolveAutocloseConfig(options).autocloseWindow
|
|
3673
3536
|
});
|
|
3674
3537
|
this._correlator = options.correlator ?? default_correlator;
|
|
@@ -3676,8 +3539,6 @@ var Act = class {
|
|
|
3676
3539
|
this._cd = build_drain(this._logger);
|
|
3677
3540
|
this._handle = build_handle({
|
|
3678
3541
|
logger: this._logger,
|
|
3679
|
-
// The orchestrator owns ambient context; `build_handle` only asks for
|
|
3680
|
-
// the triggering event to be in scope while the handler runs.
|
|
3681
3542
|
reaction_scope: make_reaction_scope({
|
|
3682
3543
|
do: this._bound_do,
|
|
3683
3544
|
load: this._bound_load,
|
|
@@ -3712,16 +3573,8 @@ var Act = class {
|
|
|
3712
3573
|
resolveCircuitBreakerConfig(options.circuitBreaker),
|
|
3713
3574
|
{
|
|
3714
3575
|
on_error: (error, circuit) => this._emit_error(error, circuit),
|
|
3715
|
-
//
|
|
3716
|
-
//
|
|
3717
|
-
// The wake fires `settle()`, which (in half-open) runs a real store
|
|
3718
|
-
// probe: for a dynamic-resolver app the probe is settle's correlate
|
|
3719
|
-
// (a store scan); for a static-reaction app correlate is a no-op that
|
|
3720
|
-
// records no health, so the probe is settle's DRAIN claim — either
|
|
3721
|
-
// way one success closes the breaker and every loop resumes, a
|
|
3722
|
-
// failure re-opens it and reschedules the wake. Settle does NOT close
|
|
3723
|
-
// the breaker off a no-op correlate (#1329) — only a real store op
|
|
3724
|
-
// (correlate scan or drain claim) records `passed()`.
|
|
3576
|
+
// When the cooldown elapses, settle once: its scan or claim is the
|
|
3577
|
+
// half-open probe that closes or re-opens the breaker.
|
|
3725
3578
|
on_retry: () => {
|
|
3726
3579
|
this.settle({ debounceMs: 0 });
|
|
3727
3580
|
}
|
|
@@ -3729,12 +3582,9 @@ var Act = class {
|
|
|
3729
3582
|
);
|
|
3730
3583
|
}
|
|
3731
3584
|
/**
|
|
3732
|
-
* One DrainController per active lane
|
|
3733
|
-
*
|
|
3734
|
-
*
|
|
3735
|
-
* no-lane-declared case with `lane: undefined` deps so claim() doesn't
|
|
3736
|
-
* filter (preserves the single-lane SQL planner shape for apps that never
|
|
3737
|
-
* call withLane).
|
|
3585
|
+
* One DrainController per active lane, plus the implicit "default" unless
|
|
3586
|
+
* `onlyLanes` excludes it. Apps that declare no lanes pass no lane filter
|
|
3587
|
+
* to `claim`.
|
|
3738
3588
|
*/
|
|
3739
3589
|
_build_drain_controllers(options, lanes) {
|
|
3740
3590
|
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
@@ -3753,11 +3603,7 @@ var Act = class {
|
|
|
3753
3603
|
handle_batch: this._handle_batch,
|
|
3754
3604
|
on_acked: (acked) => this.emit("acked", acked),
|
|
3755
3605
|
on_blocked: (blocked) => this.emit("blocked", blocked),
|
|
3756
|
-
// Reaction-requested close
|
|
3757
|
-
// `app.close` (tombstone guard + archive + atomic truncate) for the
|
|
3758
|
-
// targets a handler signalled via `CloseSignal`. No `correlate()`
|
|
3759
|
-
// here — the drain that produced these targets has already
|
|
3760
|
-
// correlated.
|
|
3606
|
+
// Reaction-requested close: the same machinery as `app.close`.
|
|
3761
3607
|
on_close: async (targets) => {
|
|
3762
3608
|
const close_actor = { id: "$close", name: "close" };
|
|
3763
3609
|
const result = await run_close_cycle(targets, {
|
|
@@ -3774,12 +3620,7 @@ var Act = class {
|
|
|
3774
3620
|
this.emit("closed", result);
|
|
3775
3621
|
},
|
|
3776
3622
|
breaker: this._breaker,
|
|
3777
|
-
// Re-scope the per-lane worker's auto-start ticks so their drain
|
|
3778
|
-
// resolves the scoped ports, not the singleton (#1191).
|
|
3779
3623
|
run_scoped: this._scoped,
|
|
3780
|
-
// Pass lane only when a true per-lane controller is active.
|
|
3781
|
-
// The all-lanes (single default) case keeps lane=undefined so
|
|
3782
|
-
// adapter SQL collapses to the single-lane shape.
|
|
3783
3624
|
lane: single_default_lane ? void 0 : name,
|
|
3784
3625
|
defaults: cfg && {
|
|
3785
3626
|
streamLimit: cfg.streamLimit,
|
|
@@ -3793,17 +3634,9 @@ var Act = class {
|
|
|
3793
3634
|
return controllers;
|
|
3794
3635
|
}
|
|
3795
3636
|
/**
|
|
3796
|
-
*
|
|
3797
|
-
*
|
|
3798
|
-
*
|
|
3799
|
-
* subscribes all static targets regardless of `onlyLanes`) but never
|
|
3800
|
-
* claimed here. If no peer worker deploys with those lanes in ITS
|
|
3801
|
-
* `onlyLanes`, their reactions accumulate forever, silently. A single
|
|
3802
|
-
* process can't verify the cluster invariant `∪ onlyLanes ⊇ declared
|
|
3803
|
-
* lanes`, so we surface the per-instance signal — "these declared lanes
|
|
3804
|
-
* have no controller here" — the same way the deprecated-event advisory
|
|
3805
|
-
* surfaces legacy events. No advisory when `onlyLanes` is unset (every
|
|
3806
|
-
* lane gets a controller) or covers every declared lane.
|
|
3637
|
+
* Startup advisory naming declared lanes that `onlyLanes` leaves without a
|
|
3638
|
+
* controller here. One process can't check that the cluster covers every
|
|
3639
|
+
* lane, so it reports its own gaps.
|
|
3807
3640
|
*/
|
|
3808
3641
|
_advise_orphaned_lanes(options, lanes) {
|
|
3809
3642
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
@@ -3817,25 +3650,15 @@ var Act = class {
|
|
|
3817
3650
|
`Act declared ${orphaned.length} orphaned lane(s) on this instance: ${list}. onlyLanes excludes them, so no DrainController claims their streams here \u2014 their reactions accumulate un-drained unless a peer worker deploys with these lanes in its onlyLanes. Ensure the cluster invariant holds: the union of every worker's onlyLanes must cover every declared lane. See docs/docs/guides/production-checklist.md \xA7 Sizing lanes.`
|
|
3818
3651
|
);
|
|
3819
3652
|
}
|
|
3820
|
-
/**
|
|
3821
|
-
* Audit deps bag. Snapshotted after registry classification and
|
|
3822
|
-
* drain-controller build so the audit module sees the finalized lane
|
|
3823
|
-
* set. Held as an immutable bag — the orchestrator never carries audit
|
|
3824
|
-
* logic itself, only this typed contract.
|
|
3825
|
-
*/
|
|
3653
|
+
/** Audit deps, built after the controllers so the lane set is final. */
|
|
3826
3654
|
_build_audit_deps() {
|
|
3827
3655
|
return {
|
|
3828
3656
|
store,
|
|
3829
3657
|
logger: this._logger,
|
|
3830
3658
|
event_to_state: this._event_to_state,
|
|
3831
3659
|
states: this._states,
|
|
3832
|
-
//
|
|
3833
|
-
//
|
|
3834
|
-
// An `onlyLanes`-filtered instance builds a controller only for its
|
|
3835
|
-
// slice of lanes, so keying off the active controller set would flag
|
|
3836
|
-
// a stream correctly assigned to an excluded-but-declared lane (one
|
|
3837
|
-
// another worker drains) as `unknown-lane`. The audit reports what's
|
|
3838
|
-
// structurally routable across the cluster, not what this process runs.
|
|
3660
|
+
// Declared lanes, not this process's controllers: a lane another
|
|
3661
|
+
// worker drains is not `unknown-lane`.
|
|
3839
3662
|
declared_lanes: /* @__PURE__ */ new Set(["default", ...this._lanes.map((l) => l.name)]),
|
|
3840
3663
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
3841
3664
|
};
|
|
@@ -3851,10 +3674,8 @@ var Act = class {
|
|
|
3851
3674
|
static_targets: classification.static_targets,
|
|
3852
3675
|
cd: this._cd,
|
|
3853
3676
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3854
|
-
//
|
|
3855
|
-
//
|
|
3856
|
-
// not active: `onlyLanes` shrinks this process's controllers, but a
|
|
3857
|
-
// lane another process claims is still a valid destination.
|
|
3677
|
+
// Declared lanes (not active ones): a lane another process claims is
|
|
3678
|
+
// still a valid destination.
|
|
3858
3679
|
declared_lanes: /* @__PURE__ */ new Set([
|
|
3859
3680
|
"default",
|
|
3860
3681
|
...this._lanes.map((l) => l.name)
|
|
@@ -3862,26 +3683,14 @@ var Act = class {
|
|
|
3862
3683
|
on_init: () => {
|
|
3863
3684
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3864
3685
|
},
|
|
3865
|
-
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
3866
|
-
// (`drain: false`) — they run no local controllers to re-arm.
|
|
3867
3686
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3868
3687
|
});
|
|
3869
3688
|
}
|
|
3870
3689
|
/**
|
|
3871
|
-
*
|
|
3872
|
-
*
|
|
3873
|
-
*
|
|
3874
|
-
*
|
|
3875
|
-
* The defer timer is worker memory: empty after a restart. A stream
|
|
3876
|
-
* deferred to a future due-time (the classic case: an idle autoclose
|
|
3877
|
-
* aggregate that deferred its terminal close) is durable in the store but
|
|
3878
|
-
* has nothing in memory to re-arm the drain — the controller disarms on
|
|
3879
|
-
* the first empty claim and, since the aggregate is idle, no commit ever
|
|
3880
|
-
* re-arms it. Reading the persisted schedule and seeding the owning lane's
|
|
3881
|
-
* timer restores the wake, so the close fires at the due-time.
|
|
3882
|
-
*
|
|
3883
|
-
* Streams whose lane has no controller on this instance (excluded by
|
|
3884
|
-
* `onlyLanes`) are skipped — a peer worker owns that lane's timer.
|
|
3690
|
+
* At cold start, re-seed each lane controller's local wake from the
|
|
3691
|
+
* persisted future `deferred_at`, so an idle deferred stream (an
|
|
3692
|
+
* autoclose waiting out its window) still fires on time. Streams on lanes
|
|
3693
|
+
* this instance doesn't run are skipped.
|
|
3885
3694
|
*/
|
|
3886
3695
|
async _seed_persisted_defers() {
|
|
3887
3696
|
const now = Date.now();
|
|
@@ -3895,10 +3704,6 @@ var Act = class {
|
|
|
3895
3704
|
_build_settle(options) {
|
|
3896
3705
|
return new SettleLoop(
|
|
3897
3706
|
{
|
|
3898
|
-
// Scope the init like every other store-touching path — a bare
|
|
3899
|
-
// `this._correlate.init()` runs `store().subscribe(...)` against
|
|
3900
|
-
// the singleton for a scoped Act, so static targets never land on
|
|
3901
|
-
// the scoped store and `_initialized` then blocks a retry (#1191).
|
|
3902
3707
|
init: () => this._scoped(() => this._correlate.init()),
|
|
3903
3708
|
checkpoint: () => this._correlate.checkpoint,
|
|
3904
3709
|
correlate: (q) => this._correlate_scanned(q, true),
|
|
@@ -3916,19 +3721,11 @@ var Act = class {
|
|
|
3916
3721
|
* already in flight a bounded chance to finish, then remove lifecycle
|
|
3917
3722
|
* listeners and tear down the cross-process notify subscription.
|
|
3918
3723
|
*
|
|
3919
|
-
*
|
|
3920
|
-
*
|
|
3921
|
-
*
|
|
3922
|
-
*
|
|
3923
|
-
*
|
|
3924
|
-
* (which #1418 then redelivers). Listeners come off *after* that wait, not
|
|
3925
|
-
* before, so an `acked` / `blocked` subscriber still observes the work
|
|
3926
|
-
* that completed during the grace window.
|
|
3927
|
-
*
|
|
3928
|
-
* The budget is a ceiling, not a delay — teardown continues the moment the
|
|
3929
|
-
* last in-flight cycle finishes. When it is exhausted, teardown proceeds
|
|
3930
|
-
* anyway: one stuck handler must not hang a deploy, which is the failure
|
|
3931
|
-
* mode an unbounded wait would trade for.
|
|
3724
|
+
* Scheduling stops first, then in-flight cycles get up to `graceMs` to
|
|
3725
|
+
* reach their `ack` (an abandoned handler holds its lease until it
|
|
3726
|
+
* expires). Listeners come off after the wait, so they still see work that
|
|
3727
|
+
* finished during it. The budget is a ceiling, not a delay; when it runs
|
|
3728
|
+
* out, teardown proceeds anyway.
|
|
3932
3729
|
*
|
|
3933
3730
|
* Idempotent — repeated calls return the same promise, and the first
|
|
3934
3731
|
* call's `graceMs` is the one that applies. Registered automatically with
|
|
@@ -3961,26 +3758,10 @@ var Act = class {
|
|
|
3961
3758
|
* whichever comes first. Cycle promises never reject (`drain()` contains
|
|
3962
3759
|
* its own errors), so this never throws.
|
|
3963
3760
|
*
|
|
3964
|
-
* An omitted budget is
|
|
3965
|
-
* in flight
|
|
3966
|
-
*
|
|
3967
|
-
*
|
|
3968
|
-
* lane that pinned no lease contributes `drain()`'s own fallback, and the
|
|
3969
|
-
* whole thing is capped so a long-leased lane cannot hold a deploy open.
|
|
3970
|
-
* Idle lanes do not count — nothing is running on them to wait for.
|
|
3971
|
-
*
|
|
3972
|
-
* An in-flight settle counts the same way, on the same fallback (#1617).
|
|
3973
|
-
* It is waited on like any cycle, and deriving from the lanes alone gave
|
|
3974
|
-
* it a budget of `0` whenever it was the only thing running — which
|
|
3975
|
-
* returned before the wait it was about to be added to.
|
|
3976
|
-
*
|
|
3977
|
-
* A lane configured `leaseMillis: 0` takes the fallback rather than its
|
|
3978
|
-
* own value, which is why this coalesces on falsy and not just on absent
|
|
3979
|
-
* (#1647). A zero-length lease expires the instant it is granted, so it
|
|
3980
|
-
* has no answer to offer for "how long may a handler hold this stream" —
|
|
3981
|
-
* it is the pinned-no-lease case spelled with a number, and #1617 already
|
|
3982
|
-
* settled what that case is worth. Taking it literally derived a budget
|
|
3983
|
-
* of `0` and abandoned the very cycle this had just found running.
|
|
3761
|
+
* An omitted budget is the largest `leaseMillis` among lanes with a cycle
|
|
3762
|
+
* in flight (the drain's fallback for lanes that pinned none, or pinned
|
|
3763
|
+
* 0, which is the same as none), with an in-flight settle counting on the
|
|
3764
|
+
* fallback, capped at the maximum. Idle lanes don't count.
|
|
3984
3765
|
*/
|
|
3985
3766
|
_derive_grace_ms(running, settling) {
|
|
3986
3767
|
let max = settling ? DEFAULT_SHUTDOWN_GRACE_MS : 0;
|
|
@@ -4253,7 +4034,7 @@ var Act = class {
|
|
|
4253
4034
|
}
|
|
4254
4035
|
/**
|
|
4255
4036
|
* Wipe the sensitive-data payload for every event on the stream — see
|
|
4256
|
-
* {@link IAct.forget}.
|
|
4037
|
+
* {@link IAct.forget}.
|
|
4257
4038
|
*
|
|
4258
4039
|
* Throws on adapters without `Store.forget_pii`, invalidates the cache
|
|
4259
4040
|
* entry for the stream, emits the `forgotten` lifecycle event with the
|
|
@@ -4294,7 +4075,7 @@ var Act = class {
|
|
|
4294
4075
|
*
|
|
4295
4076
|
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
4296
4077
|
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
4297
|
-
* `correlate` is the only component that raises that mark
|
|
4078
|
+
* `correlate` is the only component that raises that mark, so a
|
|
4298
4079
|
* commit no correlate has seen is not drainable — including for static
|
|
4299
4080
|
* targets, which were served by a probe of the event log before. For a
|
|
4300
4081
|
* higher-level API that handles debouncing, correlation, and signaling
|
|
@@ -4327,14 +4108,14 @@ var Act = class {
|
|
|
4327
4108
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
4328
4109
|
return this._scoped(() => this._drain_all(options));
|
|
4329
4110
|
}
|
|
4330
|
-
/** Arm every active lane controller
|
|
4111
|
+
/** Arm every active lane controller. */
|
|
4331
4112
|
_arm_all() {
|
|
4332
4113
|
this._correlate.arm();
|
|
4333
4114
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
4334
4115
|
}
|
|
4335
4116
|
/**
|
|
4336
4117
|
* Arm only the lane controllers whose reactions match the supplied
|
|
4337
|
-
* event names (
|
|
4118
|
+
* event names (selective arming). Events with any dynamic
|
|
4338
4119
|
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
4339
4120
|
* resolver's lane isn't known until correlate runs the function.
|
|
4340
4121
|
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
@@ -4438,7 +4219,7 @@ var Act = class {
|
|
|
4438
4219
|
return { subscribed, last_id };
|
|
4439
4220
|
}
|
|
4440
4221
|
/**
|
|
4441
|
-
* `correlate` plus whether the pass actually read the store
|
|
4222
|
+
* `correlate` plus whether the pass actually read the store.
|
|
4442
4223
|
*
|
|
4443
4224
|
* The settle loop needs that extra bit to decide whether the pass carries a
|
|
4444
4225
|
* circuit-breaker health signal, and a disarmed pass carries none. It stays
|
|
@@ -4699,7 +4480,7 @@ var Act = class {
|
|
|
4699
4480
|
});
|
|
4700
4481
|
}
|
|
4701
4482
|
/**
|
|
4702
|
-
* Operator-driven store audit
|
|
4483
|
+
* Operator-driven store audit.
|
|
4703
4484
|
*
|
|
4704
4485
|
* Walks the connected store and yields per-category findings —
|
|
4705
4486
|
* each tagged with the remediation it suggests. Same operator-
|
|
@@ -4751,7 +4532,7 @@ var Act = class {
|
|
|
4751
4532
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
4752
4533
|
*
|
|
4753
4534
|
* Operator-grade override of the `claim()` lagging-frontier
|
|
4754
|
-
* ordering
|
|
4535
|
+
* ordering. Useful when a long-running replay needs to
|
|
4755
4536
|
* jump ahead of other lagging streams, or when a no-longer-urgent
|
|
4756
4537
|
* job should yield slots back to the rest. Build-time priorities
|
|
4757
4538
|
* (set via the resolver's `priority` field) are subject to a
|
|
@@ -4848,12 +4629,12 @@ var Act = class {
|
|
|
4848
4629
|
*/
|
|
4849
4630
|
/**
|
|
4850
4631
|
* After a close, forget any target whose subscription row the truncate
|
|
4851
|
-
* removed, so a later correlate can re-subscribe it
|
|
4632
|
+
* removed, so a later correlate can re-subscribe it. Restart
|
|
4852
4633
|
* targets keep their row, so only fully-retired streams are forgotten.
|
|
4853
4634
|
*/
|
|
4854
4635
|
/**
|
|
4855
4636
|
* Advance correlation until the read cursor reaches `until`, and report
|
|
4856
|
-
* where it landed
|
|
4637
|
+
* where it landed. Used by the close cycle's safety probe, which
|
|
4857
4638
|
* cannot judge a subscription's pending work over events correlate has
|
|
4858
4639
|
* not resolved yet.
|
|
4859
4640
|
*
|
|
@@ -4879,13 +4660,12 @@ var Act = class {
|
|
|
4879
4660
|
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
4880
4661
|
* was restarted and is still consuming.
|
|
4881
4662
|
*
|
|
4882
|
-
* `truncate` no longer removes the subscription row
|
|
4663
|
+
* `truncate` no longer removes the subscription row, so this is no
|
|
4883
4664
|
* longer repairing damage the close itself did. It still matters, because
|
|
4884
4665
|
* the row can disappear later: reclaiming retired subscriptions is an
|
|
4885
4666
|
* operator job now, and that `DELETE` can land while this process is
|
|
4886
4667
|
* running. Forgetting here keeps the in-process view from outliving a row
|
|
4887
|
-
* an operator removed, which is
|
|
4888
|
-
* described — a reaction whose target is named after the stream would never
|
|
4668
|
+
* an operator removed, which is a silent-no-delivery failure — a reaction whose target is named after the stream would never
|
|
4889
4669
|
* be re-registered.
|
|
4890
4670
|
*/
|
|
4891
4671
|
_forget_closed_subscriptions(result) {
|
|
@@ -5160,7 +4940,7 @@ function reaction_on(event, events, builder) {
|
|
|
5160
4940
|
const reaction = {
|
|
5161
4941
|
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
5162
4942
|
resolver: _this_,
|
|
5163
|
-
//
|
|
4943
|
+
// Validate the whole bag at the declaration site so a bad
|
|
5164
4944
|
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
5165
4945
|
// not a NaN gate on the first retry.
|
|
5166
4946
|
options: resolveReactionConfig({
|
|
@@ -5362,7 +5142,7 @@ function act() {
|
|
|
5362
5142
|
spec.flush,
|
|
5363
5143
|
spec.config,
|
|
5364
5144
|
patch_fn,
|
|
5365
|
-
// Head loads strip sensitive keys like the warm path
|
|
5145
|
+
// Head loads strip sensitive keys like the warm path.
|
|
5366
5146
|
registry.sensitive_fields
|
|
5367
5147
|
)
|
|
5368
5148
|
)
|
|
@@ -5868,7 +5648,7 @@ var CsvFile = class {
|
|
|
5868
5648
|
name: fields[1],
|
|
5869
5649
|
// Revive dates like every Store adapter does — a Date in an event
|
|
5870
5650
|
// payload must not depend on whether it came back through a store or
|
|
5871
|
-
// through a CSV restore
|
|
5651
|
+
// through a CSV restore. The `created` column below has
|
|
5872
5652
|
// always been parsed explicitly; the JSON columns were missed.
|
|
5873
5653
|
data: JSON.parse(fields[2]),
|
|
5874
5654
|
stream: fields[3],
|