@rotorsoft/act 1.32.22 → 1.32.24
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/adapters/in-memory-store.d.ts +16 -10
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/audit.d.ts +5 -45
- package/dist/@types/internal/audit.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +23 -163
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain-cycle.d.ts +19 -129
- package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +21 -158
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/{chunk-T3CWXVGT.js → chunk-OG5O36AJ.js} +37 -87
- package/dist/chunk-OG5O36AJ.js.map +1 -0
- package/dist/index.cjs +65 -258
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +30 -173
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +36 -86
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-T3CWXVGT.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-OG5O36AJ.js";
|
|
30
30
|
import {
|
|
31
31
|
ActorSchema,
|
|
32
32
|
CausationEventSchema,
|
|
@@ -1584,71 +1584,27 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
|
|
|
1584
1584
|
var CorrelateCycle = class {
|
|
1585
1585
|
_checkpoint = -1;
|
|
1586
1586
|
_initialized = false;
|
|
1587
|
-
/**
|
|
1588
|
-
* This worker's identity for the correlation lease. A per-instance
|
|
1589
|
-
* UUID, matching the drain's convention, so a renewal is recognised as the
|
|
1590
|
-
* same holder and a restarted process never inherits a stale claim.
|
|
1591
|
-
*/
|
|
1587
|
+
/** Lease holder id; per instance, so a restart never inherits a stale claim. */
|
|
1592
1588
|
_by = randomUUID();
|
|
1593
|
-
/**
|
|
1594
|
-
* Which correlator this is. Computed once from the registry so identical
|
|
1595
|
-
* workers share a lease and unrelated applications never do.
|
|
1596
|
-
*/
|
|
1597
1589
|
_key;
|
|
1598
|
-
/**
|
|
1599
|
-
* When this worker's correlation lease runs out, as a local clock reading,
|
|
1600
|
-
* or 0 when it holds none.
|
|
1601
|
-
*
|
|
1602
|
-
* Cached so the holder doesn't ask the store on every pass. A stale belief
|
|
1603
|
-
* can only cause a duplicate scan, and marks are idempotent, so it costs
|
|
1604
|
-
* work, never correctness.
|
|
1605
|
-
*/
|
|
1590
|
+
/** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
|
|
1606
1591
|
_lease_until = 0;
|
|
1607
|
-
/**
|
|
1608
|
-
* Whether a scan might find anything, like the drain's flag: a commit or
|
|
1609
|
-
* notification raises it, a short page lowers it, and a disarmed scan
|
|
1610
|
-
* returns without touching the store.
|
|
1611
|
-
*
|
|
1612
|
-
* Starts armed: the log may already hold events this process has never
|
|
1613
|
-
* correlated, and only a scan can find out.
|
|
1614
|
-
*/
|
|
1592
|
+
/** Raised by a commit or notification, lowered by a short page. Starts armed. */
|
|
1615
1593
|
_armed = true;
|
|
1616
|
-
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
1617
1594
|
_init_promise;
|
|
1618
|
-
//
|
|
1619
|
-
//
|
|
1620
|
-
// target: priority/lane only when the resolution beats the recorded floor,
|
|
1621
|
-
// otherwise the row's own values. See {@link Subscription}.
|
|
1595
|
+
// Bounded by `maxSubscribedStreams`; static records live apart so eviction
|
|
1596
|
+
// never drops their +Infinity floor.
|
|
1622
1597
|
_dynamic_subscriptions;
|
|
1623
|
-
/**
|
|
1624
|
-
* What each static target was subscribed at by `init`. A plain map, never
|
|
1625
|
-
* evicted: the collection is the build-time `_static_targets` list, so it
|
|
1626
|
-
* is already bounded by the registry and costs nothing the registry does
|
|
1627
|
-
* not already hold.
|
|
1628
|
-
*
|
|
1629
|
-
* Kept out of the LRU so eviction can never drop a static target's
|
|
1630
|
-
* `+Infinity` floor and let a dynamic resolution re-lane it.
|
|
1631
|
-
*/
|
|
1632
1598
|
_static_subscriptions = /* @__PURE__ */ new Map();
|
|
1633
|
-
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
1634
1599
|
_patterns = new LruMap(PATTERN_CACHE_SIZE);
|
|
1635
1600
|
_registry;
|
|
1636
1601
|
_static_targets;
|
|
1637
1602
|
_cd;
|
|
1638
1603
|
_on_init;
|
|
1639
|
-
/**
|
|
1640
|
-
* Async cold-start hook, run once after `on_init` inside `init()`. The
|
|
1641
|
-
* orchestrator re-seeds its defer timers from the persisted `deferred_at`
|
|
1642
|
-
* here, so an idle deferred stream re-arms across a restart.
|
|
1643
|
-
*/
|
|
1604
|
+
/** Runs once after `on_init`; re-seeds defer timers across a restart. */
|
|
1644
1605
|
_on_init_async;
|
|
1645
|
-
/** Lanes a controller exists for. See {@link CorrelateCycleDeps}. */
|
|
1646
1606
|
_declared_lanes;
|
|
1647
|
-
/**
|
|
1648
|
-
* Offending declarations already reported, so a resolver firing for every
|
|
1649
|
-
* matching event reports once. Owned by the instance rather than the
|
|
1650
|
-
* module: `internal/` holds no module-level state.
|
|
1651
|
-
*/
|
|
1607
|
+
/** Report-once keys; per instance because `internal/` holds no module state. */
|
|
1652
1608
|
_reported = /* @__PURE__ */ new Set();
|
|
1653
1609
|
constructor({
|
|
1654
1610
|
registry,
|
|
@@ -1669,28 +1625,13 @@ var CorrelateCycle = class {
|
|
|
1669
1625
|
this._on_init = on_init;
|
|
1670
1626
|
this._on_init_async = on_init_async;
|
|
1671
1627
|
}
|
|
1672
|
-
/** Last correlated event id. */
|
|
1673
1628
|
get checkpoint() {
|
|
1674
1629
|
return this._checkpoint;
|
|
1675
1630
|
}
|
|
1676
|
-
/**
|
|
1677
|
-
* Signal that a commit (local or remote) may have produced events this
|
|
1678
|
-
* process has not correlated. Cheap and idempotent — the orchestrator calls
|
|
1679
|
-
* it on every commit and every notification.
|
|
1680
|
-
*/
|
|
1631
|
+
/** A commit (local or remote) may have produced uncorrelated events. */
|
|
1681
1632
|
arm() {
|
|
1682
1633
|
this._armed = true;
|
|
1683
1634
|
}
|
|
1684
|
-
/**
|
|
1685
|
-
* Initialize correlation state on first call.
|
|
1686
|
-
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
1687
|
-
* flooring a first boot at `watermark - back_scan` so an event
|
|
1688
|
-
* committed-but-not-correlated before a crash is re-scanned on
|
|
1689
|
-
* restart instead of skipped
|
|
1690
|
-
* - Subscribes static resolver targets (idempotent upsert)
|
|
1691
|
-
* - Populates the subscribed-streams LRU
|
|
1692
|
-
* - Fires `on_init` once (Act uses this to flag a cold-start drain)
|
|
1693
|
-
*/
|
|
1694
1635
|
async init() {
|
|
1695
1636
|
if (this._initialized) return;
|
|
1696
1637
|
if (!this._init_promise) {
|
|
@@ -1717,16 +1658,7 @@ var CorrelateCycle = class {
|
|
|
1717
1658
|
}
|
|
1718
1659
|
await this._on_init_async?.();
|
|
1719
1660
|
}
|
|
1720
|
-
/**
|
|
1721
|
-
* Start correlation over after the event log was replaced wholesale
|
|
1722
|
-
* (`restore`). The restored log is renumbered from the start and its
|
|
1723
|
-
* subscription rows are gone, so the in-memory scan position and both
|
|
1724
|
-
* subscription records describe a log that no longer exists. Dropping them
|
|
1725
|
-
* and the init latch makes the next pass run cold-start again: static
|
|
1726
|
-
* targets are re-subscribed, the checkpoint is re-read from the store
|
|
1727
|
-
* (which `restore` reset), and dynamic targets are rediscovered by the
|
|
1728
|
-
* scan.
|
|
1729
|
-
*/
|
|
1661
|
+
/** After `restore` replaced the log: forget everything and cold-start again. */
|
|
1730
1662
|
restart() {
|
|
1731
1663
|
this._initialized = false;
|
|
1732
1664
|
this._init_promise = void 0;
|
|
@@ -1735,21 +1667,11 @@ var CorrelateCycle = class {
|
|
|
1735
1667
|
this._static_subscriptions.clear();
|
|
1736
1668
|
this._armed = true;
|
|
1737
1669
|
}
|
|
1738
|
-
/**
|
|
1739
|
-
* Forget dynamic targets whose subscription rows no longer exist (a full
|
|
1740
|
-
* close deletes the row), so a later scan re-subscribes them. Static
|
|
1741
|
-
* targets keep their records.
|
|
1742
|
-
*/
|
|
1670
|
+
/** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
|
|
1743
1671
|
forget_subscribed(streams) {
|
|
1744
1672
|
for (const stream of streams) this._dynamic_subscriptions.delete(stream);
|
|
1745
1673
|
}
|
|
1746
|
-
/**
|
|
1747
|
-
* Would an event from `stream` be fetched for a target subscribed with
|
|
1748
|
-
* `source`? The subscription's source is the filter `fetch` queries with
|
|
1749
|
-
* — literal names by equality, patterns compiled as a `RegExp` — so an
|
|
1750
|
-
* event outside it is not work for that target and must not mark it.
|
|
1751
|
-
* No source means the target consumes every stream.
|
|
1752
|
-
*/
|
|
1674
|
+
/** Would the target's own `fetch` (filtered by `source`) return this event? */
|
|
1753
1675
|
_in_fetch_window(source, stream) {
|
|
1754
1676
|
if (source === void 0 || source === stream) return true;
|
|
1755
1677
|
if (is_literal_source(source)) return false;
|
|
@@ -1760,16 +1682,7 @@ var CorrelateCycle = class {
|
|
|
1760
1682
|
}
|
|
1761
1683
|
return pattern.test(stream);
|
|
1762
1684
|
}
|
|
1763
|
-
/**
|
|
1764
|
-
* Scan the events past the checkpoint, resolve each to its target
|
|
1765
|
-
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
1766
|
-
* targets get registered, and every target an event resolved to gets its
|
|
1767
|
-
* **work mark** raised to that event's id.
|
|
1768
|
-
*
|
|
1769
|
-
* Both resolver kinds are walked. A static target is already subscribed
|
|
1770
|
-
* at init, but marking it is what makes it claimable without probing the
|
|
1771
|
-
* event log, so the scan runs for every app.
|
|
1772
|
-
*/
|
|
1685
|
+
/** Scan past the checkpoint, subscribe new targets and raise their work marks. */
|
|
1773
1686
|
async correlate(query = { after: -1, limit: 10 }, lease = false) {
|
|
1774
1687
|
await this.init();
|
|
1775
1688
|
if (!this._armed)
|
|
@@ -1854,10 +1767,8 @@ var CorrelateCycle = class {
|
|
|
1854
1767
|
}
|
|
1855
1768
|
}
|
|
1856
1769
|
},
|
|
1857
|
-
//
|
|
1858
|
-
//
|
|
1859
|
-
// reactions. Resolvers therefore see `pii: null`; correlate runs
|
|
1860
|
-
// actor-less, so plaintext here would be un-gated disclosure.
|
|
1770
|
+
// Resolvers see `pii: null`: correlate runs actor-less, and one
|
|
1771
|
+
// undecryptable row would otherwise stop every stream's reactions.
|
|
1861
1772
|
{ ...query, after, with_pii: false }
|
|
1862
1773
|
);
|
|
1863
1774
|
if (this._declared_lanes.size > 1) await this._recall(correlated);
|
|
@@ -1900,14 +1811,9 @@ var CorrelateCycle = class {
|
|
|
1900
1811
|
return { subscribed: 0, last_id, marked: 0, scanned: true };
|
|
1901
1812
|
}
|
|
1902
1813
|
/**
|
|
1903
|
-
*
|
|
1904
|
-
*
|
|
1905
|
-
*
|
|
1906
|
-
* has one, says what lane and priority it was subscribed at; a resolution
|
|
1907
|
-
* that doesn't beat that priority carries the row's values, exactly as if
|
|
1908
|
-
* the record had never been evicted. One read per target with no record,
|
|
1909
|
-
* and only when more than one lane is declared: with one lane there is no
|
|
1910
|
-
* lane to lose, and the store already keeps the higher priority.
|
|
1814
|
+
* Read the row of each target this scan had no record of (new or evicted),
|
|
1815
|
+
* so a resolution that doesn't beat its priority keeps the row's lane.
|
|
1816
|
+
* Only called with more than one lane declared.
|
|
1911
1817
|
*/
|
|
1912
1818
|
async _recall(correlated) {
|
|
1913
1819
|
const missed = [...correlated].filter(([, entry]) => entry.missed);
|
|
@@ -1943,13 +1849,7 @@ var CorrelateCycle = class {
|
|
|
1943
1849
|
})
|
|
1944
1850
|
);
|
|
1945
1851
|
}
|
|
1946
|
-
/**
|
|
1947
|
-
* Hand the correlation lease back early.
|
|
1948
|
-
*
|
|
1949
|
-
* The port has no release verb; re-acquiring as the same holder with a
|
|
1950
|
-
* zero duration releases it. Best-effort: a failure costs the lease's
|
|
1951
|
-
* remaining lifetime, as a crash would.
|
|
1952
|
-
*/
|
|
1852
|
+
/** Release the lease early by re-acquiring it for zero ms. Best-effort. */
|
|
1953
1853
|
async release_correlation() {
|
|
1954
1854
|
try {
|
|
1955
1855
|
this._lease_until = 0;
|
|
@@ -2426,11 +2326,9 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2426
2326
|
{
|
|
2427
2327
|
stream,
|
|
2428
2328
|
stream_exact: true,
|
|
2429
|
-
// `with_snaps` on every non-time-travel load
|
|
2430
|
-
// latest snapshot
|
|
2431
|
-
//
|
|
2432
|
-
// result, so a windowed close that pruned the events in between can't
|
|
2433
|
-
// make the fold apply the tail onto stale state.
|
|
2329
|
+
// `with_snaps` on every non-time-travel load: cold, it resumes from the
|
|
2330
|
+
// latest snapshot; warm, it keeps a snapshot above a stale checkpoint,
|
|
2331
|
+
// so a windowed close's prune can't fold the tail onto stale state.
|
|
2434
2332
|
...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
|
|
2435
2333
|
}
|
|
2436
2334
|
);
|
|
@@ -2454,10 +2352,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2454
2352
|
event,
|
|
2455
2353
|
state: state2,
|
|
2456
2354
|
version: event?.version ?? cached?.version ?? -1,
|
|
2457
|
-
//
|
|
2458
|
-
// event's id on a cache miss, or the cached checkpoint's event_id on a
|
|
2459
|
-
// warm hit with no new events. Consumers read this instead of a
|
|
2460
|
-
// separate cache lookup that could race a concurrent commit.
|
|
2355
|
+
// Captured with `state`, so consumers never race a separate cache lookup.
|
|
2461
2356
|
id: event?.id ?? cached?.event_id ?? -1,
|
|
2462
2357
|
patches,
|
|
2463
2358
|
snaps,
|
|
@@ -2519,8 +2414,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2519
2414
|
action: {
|
|
2520
2415
|
name: action2,
|
|
2521
2416
|
...target
|
|
2522
|
-
// payload intentionally omitted
|
|
2523
|
-
// callers correlate via the correlation id when they need it.
|
|
2417
|
+
// payload intentionally omitted
|
|
2524
2418
|
},
|
|
2525
2419
|
event: reactingTo ? {
|
|
2526
2420
|
id: reactingTo.id,
|
|
@@ -2923,61 +2817,35 @@ var DrainController = class {
|
|
|
2923
2817
|
_locked = false;
|
|
2924
2818
|
_ratio = 0.5;
|
|
2925
2819
|
/**
|
|
2926
|
-
* Local wake
|
|
2927
|
-
*
|
|
2928
|
-
*
|
|
2929
|
-
* the drain alone finds it, and on an idle app nothing else would.
|
|
2930
|
-
* Every drain re-aims it when it ends, and a due entry stays until a pass
|
|
2931
|
-
* handles it or a pass that started after it came due claims nothing, so
|
|
2932
|
-
* the wake keeps draining until every due stream is reached.
|
|
2933
|
-
* `stop()` cancels the timer and a stopped drain never re-schedules it,
|
|
2934
|
-
* so a wake never runs after a stop.
|
|
2820
|
+
* Local wake at the earliest parked (backoff or defer) visit; the schedule
|
|
2821
|
+
* itself is persisted. On an idle app nothing else would drain it. A due
|
|
2822
|
+
* entry stays until handled or until a later pass claims nothing.
|
|
2935
2823
|
*/
|
|
2936
2824
|
_defer = new DeferTimer(() => {
|
|
2937
2825
|
this._armed = true;
|
|
2938
2826
|
void this._deps.run_scoped(() => this.drain());
|
|
2939
2827
|
});
|
|
2940
|
-
/** Worker timer. Set when `start()` is active, undefined otherwise. */
|
|
2941
2828
|
_worker;
|
|
2942
|
-
/**
|
|
2943
|
-
* Misroutings this controller has reported. A resolver returning a
|
|
2944
|
-
* projection's target does so for every matching event; one line per event
|
|
2945
|
-
* would bury the signal it exists to raise.
|
|
2946
|
-
*/
|
|
2947
2829
|
_misrouted = /* @__PURE__ */ new Set();
|
|
2948
2830
|
_stopped = false;
|
|
2949
|
-
/**
|
|
2950
|
-
* Resolves when the cycle in flight finishes, so a graceful shutdown can
|
|
2951
|
-
* wait for it. Never rejects.
|
|
2952
|
-
*/
|
|
2831
|
+
/** Never rejects; graceful shutdown waits on it. */
|
|
2953
2832
|
_inflight;
|
|
2954
2833
|
_inflight_done;
|
|
2955
2834
|
_deps;
|
|
2956
2835
|
constructor(deps) {
|
|
2957
2836
|
this._deps = deps;
|
|
2958
2837
|
}
|
|
2959
|
-
/**
|
|
2960
|
-
* Signal that a commit (or reset / cold-start) may have produced work.
|
|
2961
|
-
* Subsequent `drain()` calls will run the pipeline; once the pipeline
|
|
2962
|
-
* settles to no-progress, the controller disarms itself.
|
|
2963
|
-
*/
|
|
2964
2838
|
arm() {
|
|
2965
2839
|
this._armed = true;
|
|
2966
2840
|
}
|
|
2967
|
-
/**
|
|
2968
|
-
* Re-seed a persisted `deferred_at` into the local wake at cold start, so
|
|
2969
|
-
* an idle deferred stream re-arms at its due-time with no new commit.
|
|
2970
|
-
* Seeds collapse into one timer; the earliest wins.
|
|
2971
|
-
*/
|
|
2841
|
+
/** Cold start: re-seed a persisted `deferred_at` into the local wake. */
|
|
2972
2842
|
seed_defer(stream, at) {
|
|
2973
2843
|
this._defer.set(stream, at);
|
|
2974
2844
|
this._defer.schedule();
|
|
2975
2845
|
}
|
|
2976
|
-
/** Read-only flag — true while a commit / reset is unprocessed. */
|
|
2977
2846
|
get armed() {
|
|
2978
2847
|
return this._armed;
|
|
2979
2848
|
}
|
|
2980
|
-
/** The cycle in flight, or `undefined` when idle. */
|
|
2981
2849
|
get inflight() {
|
|
2982
2850
|
return this._inflight;
|
|
2983
2851
|
}
|
|
@@ -2985,19 +2853,10 @@ var DrainController = class {
|
|
|
2985
2853
|
get lease_millis() {
|
|
2986
2854
|
return this._deps.defaults?.leaseMillis;
|
|
2987
2855
|
}
|
|
2988
|
-
/** Lane this controller drains (undefined spans all lanes). */
|
|
2989
2856
|
get lane() {
|
|
2990
2857
|
return this._deps.lane;
|
|
2991
2858
|
}
|
|
2992
|
-
/**
|
|
2993
|
-
* Start a per-lane worker that drains at the lane's `cycleMs`
|
|
2994
|
-
* cadence. When armed, the worker calls `drain()` on every
|
|
2995
|
-
* tick and re-schedules; when not armed, it still re-schedules at
|
|
2996
|
-
* `cycleMs` so a future `arm()` is picked up on the next tick.
|
|
2997
|
-
*
|
|
2998
|
-
* The setTimeout chain uses `unref()` so it doesn't keep the process
|
|
2999
|
-
* alive on its own.
|
|
3000
|
-
*/
|
|
2859
|
+
/** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
|
|
3001
2860
|
start(cycleMs) {
|
|
3002
2861
|
if (this._worker || this._stopped) return;
|
|
3003
2862
|
const run = this._deps.run_scoped;
|
|
@@ -3010,7 +2869,6 @@ var DrainController = class {
|
|
|
3010
2869
|
this._worker = setTimeout(tick, cycleMs);
|
|
3011
2870
|
this._worker.unref();
|
|
3012
2871
|
}
|
|
3013
|
-
/** Stop the per-lane worker. Idempotent. */
|
|
3014
2872
|
stop() {
|
|
3015
2873
|
this._stopped = true;
|
|
3016
2874
|
if (this._worker) {
|
|
@@ -3019,7 +2877,6 @@ var DrainController = class {
|
|
|
3019
2877
|
}
|
|
3020
2878
|
this._defer.stop();
|
|
3021
2879
|
}
|
|
3022
|
-
/** Run one drain pass. Short-circuits when not armed or already running. */
|
|
3023
2880
|
async drain(options = {}) {
|
|
3024
2881
|
if (!this._armed) return EMPTY_DRAIN;
|
|
3025
2882
|
if (this._locked) return EMPTY_DRAIN;
|