@rotorsoft/act 1.32.14 → 1.32.15
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 +35 -82
- 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 +19 -19
- 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 +8 -8
- 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 +136 -358
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +131 -353
- 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)
|
|
@@ -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) {
|
|
@@ -2926,32 +2853,25 @@ var DrainController = class {
|
|
|
2926
2853
|
_locked = false;
|
|
2927
2854
|
_ratio = 0.5;
|
|
2928
2855
|
/**
|
|
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.
|
|
2856
|
+
* Local wake for streams this worker parked (backoff or defer). The
|
|
2857
|
+
* schedule itself is persisted in the store; this only re-arms the drain
|
|
2858
|
+
* at the earliest pending visit.
|
|
2935
2859
|
*/
|
|
2936
2860
|
_defer = new DeferTimer(() => {
|
|
2937
2861
|
this._armed = true;
|
|
2938
2862
|
});
|
|
2939
|
-
/** Worker timer
|
|
2863
|
+
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
2940
2864
|
_worker;
|
|
2941
2865
|
/**
|
|
2942
|
-
* Misroutings this controller has reported
|
|
2866
|
+
* Misroutings this controller has reported. A resolver returning a
|
|
2943
2867
|
* projection's target does so for every matching event; one line per event
|
|
2944
2868
|
* would bury the signal it exists to raise.
|
|
2945
2869
|
*/
|
|
2946
2870
|
_misrouted = /* @__PURE__ */ new Set();
|
|
2947
2871
|
_stopped = false;
|
|
2948
2872
|
/**
|
|
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.
|
|
2873
|
+
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
2874
|
+
* wait for it. Never rejects.
|
|
2955
2875
|
*/
|
|
2956
2876
|
_inflight;
|
|
2957
2877
|
_inflight_done;
|
|
@@ -2968,16 +2888,9 @@ var DrainController = class {
|
|
|
2968
2888
|
this._armed = true;
|
|
2969
2889
|
}
|
|
2970
2890
|
/**
|
|
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.
|
|
2891
|
+
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
2892
|
+
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
2893
|
+
* Seeds collapse into one timer; the earliest wins.
|
|
2981
2894
|
*/
|
|
2982
2895
|
seed_defer(stream, at) {
|
|
2983
2896
|
this._defer.set(stream, at);
|
|
@@ -2987,31 +2900,21 @@ var DrainController = class {
|
|
|
2987
2900
|
get armed() {
|
|
2988
2901
|
return this._armed;
|
|
2989
2902
|
}
|
|
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
|
-
*/
|
|
2903
|
+
/** The cycle in flight, or `undefined` when idle. */
|
|
2996
2904
|
get inflight() {
|
|
2997
2905
|
return this._inflight;
|
|
2998
2906
|
}
|
|
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
|
-
*/
|
|
2907
|
+
/** This lane's configured lease, or `undefined`; the shutdown grace basis. */
|
|
3005
2908
|
get lease_millis() {
|
|
3006
2909
|
return this._deps.defaults?.leaseMillis;
|
|
3007
2910
|
}
|
|
3008
|
-
/** Lane this controller drains (undefined
|
|
2911
|
+
/** Lane this controller drains (undefined spans all lanes). */
|
|
3009
2912
|
get lane() {
|
|
3010
2913
|
return this._deps.lane;
|
|
3011
2914
|
}
|
|
3012
2915
|
/**
|
|
3013
2916
|
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
3014
|
-
* cadence
|
|
2917
|
+
* cadence. When armed, the worker calls `drain()` on every
|
|
3015
2918
|
* tick and re-schedules; when not armed, it still re-schedules at
|
|
3016
2919
|
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
3017
2920
|
*
|
|
@@ -3198,26 +3101,13 @@ var SettleLoop = class {
|
|
|
3198
3101
|
_timer = void 0;
|
|
3199
3102
|
_running = false;
|
|
3200
3103
|
/**
|
|
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.
|
|
3104
|
+
* Resolves when the cycle in flight finishes, so shutdown can wait for
|
|
3105
|
+
* it (`stop()` cancels scheduling only). Never rejects.
|
|
3211
3106
|
*/
|
|
3212
3107
|
_inflight;
|
|
3213
3108
|
/**
|
|
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.
|
|
3109
|
+
* A wake-up that fired while a cycle was running. The running cycle's
|
|
3110
|
+
* `finally` schedules one more pass with these options, so it isn't lost.
|
|
3221
3111
|
*/
|
|
3222
3112
|
_pending = void 0;
|
|
3223
3113
|
_deps;
|
|
@@ -3289,7 +3179,7 @@ var SettleLoop = class {
|
|
|
3289
3179
|
}, debounceMs);
|
|
3290
3180
|
}
|
|
3291
3181
|
/**
|
|
3292
|
-
* The cycle currently in flight, or `undefined` when idle
|
|
3182
|
+
* The cycle currently in flight, or `undefined` when idle. A
|
|
3293
3183
|
* graceful shutdown awaits this alongside the drain controllers so a
|
|
3294
3184
|
* settle parked in `correlate` does not resume after teardown.
|
|
3295
3185
|
*/
|
|
@@ -3435,17 +3325,17 @@ function validate_only_lanes(options, lanes) {
|
|
|
3435
3325
|
}
|
|
3436
3326
|
var Act = class {
|
|
3437
3327
|
_emitter = new EventEmitter();
|
|
3438
|
-
/**
|
|
3328
|
+
/** Circuit breaker shared by all drain lanes. */
|
|
3439
3329
|
_breaker;
|
|
3440
|
-
/**
|
|
3330
|
+
/** Whether this instance subscribes to `Store.notify`. */
|
|
3441
3331
|
_listen;
|
|
3442
|
-
/**
|
|
3332
|
+
/** Whether this instance runs reactions (drain, settle, correlate). */
|
|
3443
3333
|
_drain;
|
|
3444
3334
|
/** Event names with at least one registered reaction (computed at build time) */
|
|
3445
3335
|
_reactive_events;
|
|
3446
3336
|
/** One DrainController per active lane, keyed by lane name. */
|
|
3447
3337
|
_drain_controllers;
|
|
3448
|
-
/** Correlation
|
|
3338
|
+
/** Correlation: lazy init and the scan that marks work. */
|
|
3449
3339
|
_correlate;
|
|
3450
3340
|
/** Debounced correlate→drain catch-up loop. */
|
|
3451
3341
|
_settle;
|
|
@@ -3475,26 +3365,10 @@ var Act = class {
|
|
|
3475
3365
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
3476
3366
|
* via {@link ActLifecycleEvents}.
|
|
3477
3367
|
*
|
|
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.
|
|
3368
|
+
* Each listener is contained on its own: a throwing listener is logged,
|
|
3369
|
+
* the remaining listeners still run, and the caller (`do()`, `forget()`,
|
|
3370
|
+
* `close()`, the drain) never sees the throw, since its durable work has
|
|
3371
|
+
* already landed.
|
|
3498
3372
|
*
|
|
3499
3373
|
* Uses `rawListeners` so `once` wrappers still de-register themselves, and
|
|
3500
3374
|
* returns "had listeners" to preserve the `EventEmitter.emit` contract.
|
|
@@ -3555,29 +3429,23 @@ var Act = class {
|
|
|
3555
3429
|
*/
|
|
3556
3430
|
_event_to_state;
|
|
3557
3431
|
/**
|
|
3558
|
-
* Event-name → lane fan-in for selective arming
|
|
3432
|
+
* Event-name → lane fan-in for selective arming. Built by
|
|
3559
3433
|
* `classify_registry` once per build. `"all"` means at least one of
|
|
3560
3434
|
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3561
3435
|
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3562
3436
|
* reactions target.
|
|
3563
3437
|
*/
|
|
3564
3438
|
_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
|
-
*/
|
|
3439
|
+
/** What `internal/audit.ts` needs, built once at construction. */
|
|
3572
3440
|
_audit_deps;
|
|
3573
3441
|
/** Logger resolved at construction time (after user port configuration) */
|
|
3574
3442
|
_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
3443
|
/** This Act's ports: its own bag, or the singleton adapters. */
|
|
3580
3444
|
_ports;
|
|
3445
|
+
/**
|
|
3446
|
+
* Runs a body inside this Act's ports frame, so internal
|
|
3447
|
+
* `store()`/`cache()` calls resolve to `_ports`.
|
|
3448
|
+
*/
|
|
3581
3449
|
_scoped;
|
|
3582
3450
|
/**
|
|
3583
3451
|
* Correlation-id generator for originating actions. Bound at
|
|
@@ -3596,24 +3464,18 @@ var Act = class {
|
|
|
3596
3464
|
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
3597
3465
|
_handle;
|
|
3598
3466
|
_handle_batch;
|
|
3599
|
-
/** Declared drain lanes
|
|
3467
|
+
/** Declared drain lanes. */
|
|
3600
3468
|
_lanes;
|
|
3601
3469
|
/**
|
|
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.
|
|
3470
|
+
* Per-stream close serialization tails, so a manual close and an
|
|
3471
|
+
* autoclose of the same stream never prune concurrently (the manual path
|
|
3472
|
+
* has no drain lease to exclude it). Entries are dropped once settled.
|
|
3611
3473
|
*/
|
|
3612
3474
|
_close_locks = /* @__PURE__ */ new Map();
|
|
3613
3475
|
/**
|
|
3614
|
-
* Run `work` under the per-stream close lock
|
|
3615
|
-
* critical sections for the same stream (windowed prunes
|
|
3616
|
-
*
|
|
3476
|
+
* Run `work` under the per-stream close lock. Serializes close
|
|
3477
|
+
* critical sections for the same stream (windowed prunes and full
|
|
3478
|
+
* closes) while letting different streams proceed in parallel.
|
|
3617
3479
|
*/
|
|
3618
3480
|
_with_close_lock(stream, work) {
|
|
3619
3481
|
const prev = this._close_locks.get(stream) ?? Promise.resolve();
|
|
@@ -3640,11 +3502,11 @@ var Act = class {
|
|
|
3640
3502
|
* @param states Merged map of state name → state definition
|
|
3641
3503
|
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
3642
3504
|
* @param options Tuning knobs — see {@link ActOptions}
|
|
3643
|
-
* @param lanes Declared drain lanes
|
|
3644
|
-
* these from `.withLane(...)` calls
|
|
3645
|
-
*
|
|
3505
|
+
* @param lanes Declared drain lanes. The builder collects
|
|
3506
|
+
* these from `.withLane(...)` calls; one `DrainController` runs
|
|
3507
|
+
* per lane, plus the implicit `"default"`.
|
|
3646
3508
|
* @param patch_fn The per-event patch step selected once by the builder
|
|
3647
|
-
* from `ActOptions.validateFoldedState`
|
|
3509
|
+
* from `ActOptions.validateFoldedState` — `bare_patch` by
|
|
3648
3510
|
* default, `validating_patch` when the flag is on. The builder uses
|
|
3649
3511
|
* the same value for its projection-fold handlers, so there is a
|
|
3650
3512
|
* single selection site. Defaults to `bare_patch` for direct
|
|
@@ -3659,16 +3521,15 @@ var Act = class {
|
|
|
3659
3521
|
this._ports = options.scoped ?? default_scope();
|
|
3660
3522
|
const ports = this._ports;
|
|
3661
3523
|
this._scoped = make_run_scoped({
|
|
3662
|
-
//
|
|
3663
|
-
//
|
|
3664
|
-
// whichever adapters happened to be installed at construction and
|
|
3665
|
-
// ignore a later `store(...)` / `cache(...)`.
|
|
3524
|
+
// Getters, not a spread: the singletons resolve lazily, so a later
|
|
3525
|
+
// `store(...)` / `cache(...)` still takes effect.
|
|
3666
3526
|
get store() {
|
|
3667
3527
|
return ports.store;
|
|
3668
3528
|
},
|
|
3669
3529
|
get cache() {
|
|
3670
3530
|
return ports.cache;
|
|
3671
3531
|
},
|
|
3532
|
+
// Parsed per build, so a bad window throws on every build.
|
|
3672
3533
|
autoclose_window: resolveAutocloseConfig(options).autocloseWindow
|
|
3673
3534
|
});
|
|
3674
3535
|
this._correlator = options.correlator ?? default_correlator;
|
|
@@ -3676,8 +3537,6 @@ var Act = class {
|
|
|
3676
3537
|
this._cd = build_drain(this._logger);
|
|
3677
3538
|
this._handle = build_handle({
|
|
3678
3539
|
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
3540
|
reaction_scope: make_reaction_scope({
|
|
3682
3541
|
do: this._bound_do,
|
|
3683
3542
|
load: this._bound_load,
|
|
@@ -3712,16 +3571,8 @@ var Act = class {
|
|
|
3712
3571
|
resolveCircuitBreakerConfig(options.circuitBreaker),
|
|
3713
3572
|
{
|
|
3714
3573
|
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()`.
|
|
3574
|
+
// When the cooldown elapses, settle once: its scan or claim is the
|
|
3575
|
+
// half-open probe that closes or re-opens the breaker.
|
|
3725
3576
|
on_retry: () => {
|
|
3726
3577
|
this.settle({ debounceMs: 0 });
|
|
3727
3578
|
}
|
|
@@ -3729,12 +3580,9 @@ var Act = class {
|
|
|
3729
3580
|
);
|
|
3730
3581
|
}
|
|
3731
3582
|
/**
|
|
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).
|
|
3583
|
+
* One DrainController per active lane, plus the implicit "default" unless
|
|
3584
|
+
* `onlyLanes` excludes it. Apps that declare no lanes pass no lane filter
|
|
3585
|
+
* to `claim`.
|
|
3738
3586
|
*/
|
|
3739
3587
|
_build_drain_controllers(options, lanes) {
|
|
3740
3588
|
const all_lanes = ["default", ...lanes.map((l) => l.name)];
|
|
@@ -3753,11 +3601,7 @@ var Act = class {
|
|
|
3753
3601
|
handle_batch: this._handle_batch,
|
|
3754
3602
|
on_acked: (acked) => this.emit("acked", acked),
|
|
3755
3603
|
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.
|
|
3604
|
+
// Reaction-requested close: the same machinery as `app.close`.
|
|
3761
3605
|
on_close: async (targets) => {
|
|
3762
3606
|
const close_actor = { id: "$close", name: "close" };
|
|
3763
3607
|
const result = await run_close_cycle(targets, {
|
|
@@ -3774,12 +3618,7 @@ var Act = class {
|
|
|
3774
3618
|
this.emit("closed", result);
|
|
3775
3619
|
},
|
|
3776
3620
|
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
3621
|
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
3622
|
lane: single_default_lane ? void 0 : name,
|
|
3784
3623
|
defaults: cfg && {
|
|
3785
3624
|
streamLimit: cfg.streamLimit,
|
|
@@ -3793,17 +3632,9 @@ var Act = class {
|
|
|
3793
3632
|
return controllers;
|
|
3794
3633
|
}
|
|
3795
3634
|
/**
|
|
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.
|
|
3635
|
+
* Startup advisory naming declared lanes that `onlyLanes` leaves without a
|
|
3636
|
+
* controller here. One process can't check that the cluster covers every
|
|
3637
|
+
* lane, so it reports its own gaps.
|
|
3807
3638
|
*/
|
|
3808
3639
|
_advise_orphaned_lanes(options, lanes) {
|
|
3809
3640
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
@@ -3817,25 +3648,15 @@ var Act = class {
|
|
|
3817
3648
|
`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
3649
|
);
|
|
3819
3650
|
}
|
|
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
|
-
*/
|
|
3651
|
+
/** Audit deps, built after the controllers so the lane set is final. */
|
|
3826
3652
|
_build_audit_deps() {
|
|
3827
3653
|
return {
|
|
3828
3654
|
store,
|
|
3829
3655
|
logger: this._logger,
|
|
3830
3656
|
event_to_state: this._event_to_state,
|
|
3831
3657
|
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.
|
|
3658
|
+
// Declared lanes, not this process's controllers: a lane another
|
|
3659
|
+
// worker drains is not `unknown-lane`.
|
|
3839
3660
|
declared_lanes: /* @__PURE__ */ new Set(["default", ...this._lanes.map((l) => l.name)]),
|
|
3840
3661
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
3841
3662
|
};
|
|
@@ -3851,10 +3672,8 @@ var Act = class {
|
|
|
3851
3672
|
static_targets: classification.static_targets,
|
|
3852
3673
|
cd: this._cd,
|
|
3853
3674
|
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.
|
|
3675
|
+
// Declared lanes (not active ones): a lane another process claims is
|
|
3676
|
+
// still a valid destination.
|
|
3858
3677
|
declared_lanes: /* @__PURE__ */ new Set([
|
|
3859
3678
|
"default",
|
|
3860
3679
|
...this._lanes.map((l) => l.name)
|
|
@@ -3862,26 +3681,14 @@ var Act = class {
|
|
|
3862
3681
|
on_init: () => {
|
|
3863
3682
|
if (this._drain && this._reactive_events.size > 0) this._arm_all();
|
|
3864
3683
|
},
|
|
3865
|
-
// Cold-start defer re-seed (#1221). Skipped on writer-only instances
|
|
3866
|
-
// (`drain: false`) — they run no local controllers to re-arm.
|
|
3867
3684
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3868
3685
|
});
|
|
3869
3686
|
}
|
|
3870
3687
|
/**
|
|
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.
|
|
3688
|
+
* At cold start, re-seed each lane controller's local wake from the
|
|
3689
|
+
* persisted future `deferred_at`, so an idle deferred stream (an
|
|
3690
|
+
* autoclose waiting out its window) still fires on time. Streams on lanes
|
|
3691
|
+
* this instance doesn't run are skipped.
|
|
3885
3692
|
*/
|
|
3886
3693
|
async _seed_persisted_defers() {
|
|
3887
3694
|
const now = Date.now();
|
|
@@ -3895,10 +3702,6 @@ var Act = class {
|
|
|
3895
3702
|
_build_settle(options) {
|
|
3896
3703
|
return new SettleLoop(
|
|
3897
3704
|
{
|
|
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
3705
|
init: () => this._scoped(() => this._correlate.init()),
|
|
3903
3706
|
checkpoint: () => this._correlate.checkpoint,
|
|
3904
3707
|
correlate: (q) => this._correlate_scanned(q, true),
|
|
@@ -3916,19 +3719,11 @@ var Act = class {
|
|
|
3916
3719
|
* already in flight a bounded chance to finish, then remove lifecycle
|
|
3917
3720
|
* listeners and tear down the cross-process notify subscription.
|
|
3918
3721
|
*
|
|
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.
|
|
3722
|
+
* Scheduling stops first, then in-flight cycles get up to `graceMs` to
|
|
3723
|
+
* reach their `ack` (an abandoned handler holds its lease until it
|
|
3724
|
+
* expires). Listeners come off after the wait, so they still see work that
|
|
3725
|
+
* finished during it. The budget is a ceiling, not a delay; when it runs
|
|
3726
|
+
* out, teardown proceeds anyway.
|
|
3932
3727
|
*
|
|
3933
3728
|
* Idempotent — repeated calls return the same promise, and the first
|
|
3934
3729
|
* call's `graceMs` is the one that applies. Registered automatically with
|
|
@@ -3961,26 +3756,10 @@ var Act = class {
|
|
|
3961
3756
|
* whichever comes first. Cycle promises never reject (`drain()` contains
|
|
3962
3757
|
* its own errors), so this never throws.
|
|
3963
3758
|
*
|
|
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.
|
|
3759
|
+
* An omitted budget is the largest `leaseMillis` among lanes with a cycle
|
|
3760
|
+
* in flight (the drain's fallback for lanes that pinned none, or pinned
|
|
3761
|
+
* 0, which is the same as none), with an in-flight settle counting on the
|
|
3762
|
+
* fallback, capped at the maximum. Idle lanes don't count.
|
|
3984
3763
|
*/
|
|
3985
3764
|
_derive_grace_ms(running, settling) {
|
|
3986
3765
|
let max = settling ? DEFAULT_SHUTDOWN_GRACE_MS : 0;
|
|
@@ -4253,7 +4032,7 @@ var Act = class {
|
|
|
4253
4032
|
}
|
|
4254
4033
|
/**
|
|
4255
4034
|
* Wipe the sensitive-data payload for every event on the stream — see
|
|
4256
|
-
* {@link IAct.forget}.
|
|
4035
|
+
* {@link IAct.forget}.
|
|
4257
4036
|
*
|
|
4258
4037
|
* Throws on adapters without `Store.forget_pii`, invalidates the cache
|
|
4259
4038
|
* entry for the stream, emits the `forgotten` lifecycle event with the
|
|
@@ -4294,7 +4073,7 @@ var Act = class {
|
|
|
4294
4073
|
*
|
|
4295
4074
|
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
4296
4075
|
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
4297
|
-
* `correlate` is the only component that raises that mark
|
|
4076
|
+
* `correlate` is the only component that raises that mark, so a
|
|
4298
4077
|
* commit no correlate has seen is not drainable — including for static
|
|
4299
4078
|
* targets, which were served by a probe of the event log before. For a
|
|
4300
4079
|
* higher-level API that handles debouncing, correlation, and signaling
|
|
@@ -4327,14 +4106,14 @@ var Act = class {
|
|
|
4327
4106
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
4328
4107
|
return this._scoped(() => this._drain_all(options));
|
|
4329
4108
|
}
|
|
4330
|
-
/** Arm every active lane controller
|
|
4109
|
+
/** Arm every active lane controller. */
|
|
4331
4110
|
_arm_all() {
|
|
4332
4111
|
this._correlate.arm();
|
|
4333
4112
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
4334
4113
|
}
|
|
4335
4114
|
/**
|
|
4336
4115
|
* Arm only the lane controllers whose reactions match the supplied
|
|
4337
|
-
* event names (
|
|
4116
|
+
* event names (selective arming). Events with any dynamic
|
|
4338
4117
|
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
4339
4118
|
* resolver's lane isn't known until correlate runs the function.
|
|
4340
4119
|
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
@@ -4438,7 +4217,7 @@ var Act = class {
|
|
|
4438
4217
|
return { subscribed, last_id };
|
|
4439
4218
|
}
|
|
4440
4219
|
/**
|
|
4441
|
-
* `correlate` plus whether the pass actually read the store
|
|
4220
|
+
* `correlate` plus whether the pass actually read the store.
|
|
4442
4221
|
*
|
|
4443
4222
|
* The settle loop needs that extra bit to decide whether the pass carries a
|
|
4444
4223
|
* circuit-breaker health signal, and a disarmed pass carries none. It stays
|
|
@@ -4699,7 +4478,7 @@ var Act = class {
|
|
|
4699
4478
|
});
|
|
4700
4479
|
}
|
|
4701
4480
|
/**
|
|
4702
|
-
* Operator-driven store audit
|
|
4481
|
+
* Operator-driven store audit.
|
|
4703
4482
|
*
|
|
4704
4483
|
* Walks the connected store and yields per-category findings —
|
|
4705
4484
|
* each tagged with the remediation it suggests. Same operator-
|
|
@@ -4751,7 +4530,7 @@ var Act = class {
|
|
|
4751
4530
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
4752
4531
|
*
|
|
4753
4532
|
* Operator-grade override of the `claim()` lagging-frontier
|
|
4754
|
-
* ordering
|
|
4533
|
+
* ordering. Useful when a long-running replay needs to
|
|
4755
4534
|
* jump ahead of other lagging streams, or when a no-longer-urgent
|
|
4756
4535
|
* job should yield slots back to the rest. Build-time priorities
|
|
4757
4536
|
* (set via the resolver's `priority` field) are subject to a
|
|
@@ -4848,12 +4627,12 @@ var Act = class {
|
|
|
4848
4627
|
*/
|
|
4849
4628
|
/**
|
|
4850
4629
|
* After a close, forget any target whose subscription row the truncate
|
|
4851
|
-
* removed, so a later correlate can re-subscribe it
|
|
4630
|
+
* removed, so a later correlate can re-subscribe it. Restart
|
|
4852
4631
|
* targets keep their row, so only fully-retired streams are forgotten.
|
|
4853
4632
|
*/
|
|
4854
4633
|
/**
|
|
4855
4634
|
* Advance correlation until the read cursor reaches `until`, and report
|
|
4856
|
-
* where it landed
|
|
4635
|
+
* where it landed. Used by the close cycle's safety probe, which
|
|
4857
4636
|
* cannot judge a subscription's pending work over events correlate has
|
|
4858
4637
|
* not resolved yet.
|
|
4859
4638
|
*
|
|
@@ -4879,13 +4658,12 @@ var Act = class {
|
|
|
4879
4658
|
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
4880
4659
|
* was restarted and is still consuming.
|
|
4881
4660
|
*
|
|
4882
|
-
* `truncate` no longer removes the subscription row
|
|
4661
|
+
* `truncate` no longer removes the subscription row, so this is no
|
|
4883
4662
|
* longer repairing damage the close itself did. It still matters, because
|
|
4884
4663
|
* the row can disappear later: reclaiming retired subscriptions is an
|
|
4885
4664
|
* operator job now, and that `DELETE` can land while this process is
|
|
4886
4665
|
* 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
|
|
4666
|
+
* an operator removed, which is a silent-no-delivery failure — a reaction whose target is named after the stream would never
|
|
4889
4667
|
* be re-registered.
|
|
4890
4668
|
*/
|
|
4891
4669
|
_forget_closed_subscriptions(result) {
|
|
@@ -5160,7 +4938,7 @@ function reaction_on(event, events, builder) {
|
|
|
5160
4938
|
const reaction = {
|
|
5161
4939
|
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
5162
4940
|
resolver: _this_,
|
|
5163
|
-
//
|
|
4941
|
+
// Validate the whole bag at the declaration site so a bad
|
|
5164
4942
|
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
5165
4943
|
// not a NaN gate on the first retry.
|
|
5166
4944
|
options: resolveReactionConfig({
|
|
@@ -5362,7 +5140,7 @@ function act() {
|
|
|
5362
5140
|
spec.flush,
|
|
5363
5141
|
spec.config,
|
|
5364
5142
|
patch_fn,
|
|
5365
|
-
// Head loads strip sensitive keys like the warm path
|
|
5143
|
+
// Head loads strip sensitive keys like the warm path.
|
|
5366
5144
|
registry.sensitive_fields
|
|
5367
5145
|
)
|
|
5368
5146
|
)
|
|
@@ -5868,7 +5646,7 @@ var CsvFile = class {
|
|
|
5868
5646
|
name: fields[1],
|
|
5869
5647
|
// Revive dates like every Store adapter does — a Date in an event
|
|
5870
5648
|
// payload must not depend on whether it came back through a store or
|
|
5871
|
-
// through a CSV restore
|
|
5649
|
+
// through a CSV restore. The `created` column below has
|
|
5872
5650
|
// always been parsed explicitly; the JSON columns were missed.
|
|
5873
5651
|
data: JSON.parse(fields[2]),
|
|
5874
5652
|
stream: fields[3],
|