@rotorsoft/act 1.29.2 → 1.31.0
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 +16 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +16 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +64 -2
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain.d.ts +6 -6
- package/dist/@types/internal/drain.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +105 -22
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-PX3IBVLW.js → chunk-YUJ7ACSP.js} +43 -9
- package/dist/chunk-YUJ7ACSP.js.map +1 -0
- package/dist/index.cjs +168 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +125 -15
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +42 -8
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-PX3IBVLW.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
sleep,
|
|
23
23
|
store,
|
|
24
24
|
validate
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-YUJ7ACSP.js";
|
|
26
26
|
import {
|
|
27
27
|
ActorSchema,
|
|
28
28
|
CausationEventSchema,
|
|
@@ -1607,11 +1607,46 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
1607
1607
|
}
|
|
1608
1608
|
|
|
1609
1609
|
// src/internal/correlate-cycle.ts
|
|
1610
|
+
import { createHash, randomUUID } from "crypto";
|
|
1610
1611
|
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
1612
|
+
var DEFAULT_CORRELATION_LEASE_MS = 5e3;
|
|
1613
|
+
var registry_key = (events) => {
|
|
1614
|
+
const shape = Object.keys(events).filter((name) => events[name].reactions.size > 0).sort().map((name) => [name, [...events[name].reactions.keys()].sort()]);
|
|
1615
|
+
return createHash("sha256").update(JSON.stringify(shape)).digest("hex").slice(0, 32);
|
|
1616
|
+
};
|
|
1611
1617
|
var PATTERN_CACHE_SIZE = 32;
|
|
1612
1618
|
var CorrelateCycle = class {
|
|
1613
1619
|
_checkpoint = -1;
|
|
1614
1620
|
_initialized = false;
|
|
1621
|
+
/**
|
|
1622
|
+
* This worker's identity for the correlation lease (#1532). A per-instance
|
|
1623
|
+
* UUID, matching the drain's convention, so a renewal is recognised as the
|
|
1624
|
+
* same holder and a restarted process never inherits a stale claim.
|
|
1625
|
+
*/
|
|
1626
|
+
_by = randomUUID();
|
|
1627
|
+
/** How long the correlation lease is taken for. */
|
|
1628
|
+
_lease_millis;
|
|
1629
|
+
/**
|
|
1630
|
+
* Which correlator this is. Computed once from the registry so identical
|
|
1631
|
+
* workers share a lease and unrelated applications never do.
|
|
1632
|
+
*/
|
|
1633
|
+
_key;
|
|
1634
|
+
/**
|
|
1635
|
+
* When this worker's correlation lease runs out, as a local clock reading,
|
|
1636
|
+
* or 0 when it holds none.
|
|
1637
|
+
*
|
|
1638
|
+
* Asking the store on every pass costs a round trip that the holder — which
|
|
1639
|
+
* is the *only* worker in a single-node deployment, and the steady-state
|
|
1640
|
+
* one everywhere else — gains nothing from: it already knows the answer.
|
|
1641
|
+
* The act-sqlite perf gate caught that as a 1.5x regression on
|
|
1642
|
+
* correlate+drain, which is the shape an embedded app runs constantly.
|
|
1643
|
+
*
|
|
1644
|
+
* Believing this while the store disagrees is safe in the one direction it
|
|
1645
|
+
* can fail. A worker that scans without really holding the lease produces
|
|
1646
|
+
* the duplicate scan that existed before the lease, and the marks are
|
|
1647
|
+
* idempotent — so a stale belief costs work, never correctness.
|
|
1648
|
+
*/
|
|
1649
|
+
_lease_until = 0;
|
|
1615
1650
|
/**
|
|
1616
1651
|
* Whether a scan might find anything. The drain has carried the same flag
|
|
1617
1652
|
* since it was written — a commit raises it, an empty claim lowers it, and
|
|
@@ -1671,8 +1706,11 @@ var CorrelateCycle = class {
|
|
|
1671
1706
|
run_scoped,
|
|
1672
1707
|
on_init,
|
|
1673
1708
|
on_init_async,
|
|
1674
|
-
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN
|
|
1709
|
+
cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
|
|
1710
|
+
lease_millis = DEFAULT_CORRELATION_LEASE_MS
|
|
1675
1711
|
}) {
|
|
1712
|
+
this._lease_millis = lease_millis;
|
|
1713
|
+
this._key = registry_key(registry.events);
|
|
1676
1714
|
this._subscribed = new LruMap(max_subscribed_streams);
|
|
1677
1715
|
this._registry = registry;
|
|
1678
1716
|
this._static_targets = static_targets;
|
|
@@ -1777,7 +1815,7 @@ var CorrelateCycle = class {
|
|
|
1777
1815
|
* at init, but marking it is what makes it claimable without probing the
|
|
1778
1816
|
* event log, so the scan runs for every app.
|
|
1779
1817
|
*/
|
|
1780
|
-
async correlate(query = { after: -1, limit: 10 }) {
|
|
1818
|
+
async correlate(query = { after: -1, limit: 10 }, lease = false) {
|
|
1781
1819
|
await this.init();
|
|
1782
1820
|
if (!this._armed)
|
|
1783
1821
|
return {
|
|
@@ -1786,6 +1824,22 @@ var CorrelateCycle = class {
|
|
|
1786
1824
|
marked: 0,
|
|
1787
1825
|
scanned: false
|
|
1788
1826
|
};
|
|
1827
|
+
const now = Date.now();
|
|
1828
|
+
if (lease && now >= this._lease_until - this._lease_millis / 2) {
|
|
1829
|
+
const { correlating } = await this._cd.subscribe([], void 0, {
|
|
1830
|
+
key: this._key,
|
|
1831
|
+
by: this._by,
|
|
1832
|
+
millis: this._lease_millis
|
|
1833
|
+
});
|
|
1834
|
+
this._lease_until = correlating ? now + this._lease_millis : 0;
|
|
1835
|
+
if (correlating === false)
|
|
1836
|
+
return {
|
|
1837
|
+
subscribed: 0,
|
|
1838
|
+
last_id: this._checkpoint,
|
|
1839
|
+
marked: 0,
|
|
1840
|
+
scanned: false
|
|
1841
|
+
};
|
|
1842
|
+
}
|
|
1789
1843
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
1790
1844
|
const correlated = /* @__PURE__ */ new Map();
|
|
1791
1845
|
let last_id = after;
|
|
@@ -1833,7 +1887,14 @@ var CorrelateCycle = class {
|
|
|
1833
1887
|
});
|
|
1834
1888
|
}
|
|
1835
1889
|
if (streams.length) {
|
|
1836
|
-
const
|
|
1890
|
+
const renewed_at = Date.now();
|
|
1891
|
+
const { subscribed, correlating } = await this._cd.subscribe(
|
|
1892
|
+
streams,
|
|
1893
|
+
last_id,
|
|
1894
|
+
lease ? { key: this._key, by: this._by, millis: this._lease_millis } : void 0
|
|
1895
|
+
);
|
|
1896
|
+
if (lease && correlating !== false)
|
|
1897
|
+
this._lease_until = renewed_at + this._lease_millis;
|
|
1837
1898
|
const marked = streams.filter(
|
|
1838
1899
|
(entry) => entry.correlated_at !== void 0
|
|
1839
1900
|
).length;
|
|
@@ -1863,11 +1924,14 @@ var CorrelateCycle = class {
|
|
|
1863
1924
|
this._timer = setInterval(
|
|
1864
1925
|
() => this._run_scoped(() => {
|
|
1865
1926
|
this.arm();
|
|
1866
|
-
return this.correlate(
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1927
|
+
return this.correlate(
|
|
1928
|
+
{
|
|
1929
|
+
...query,
|
|
1930
|
+
after: this._checkpoint,
|
|
1931
|
+
limit
|
|
1932
|
+
},
|
|
1933
|
+
true
|
|
1934
|
+
);
|
|
1871
1935
|
}).then((result) => {
|
|
1872
1936
|
if (callback && result.subscribed) callback(result.subscribed);
|
|
1873
1937
|
}).catch((err) => log().error(err)),
|
|
@@ -1876,6 +1940,34 @@ var CorrelateCycle = class {
|
|
|
1876
1940
|
return true;
|
|
1877
1941
|
}
|
|
1878
1942
|
/** Stop the periodic correlation worker. Idempotent. */
|
|
1943
|
+
/**
|
|
1944
|
+
* Hand the correlation lease back early.
|
|
1945
|
+
*
|
|
1946
|
+
* There is no release verb on the port, deliberately — expiry is the only
|
|
1947
|
+
* path, which keeps a crash and a clean stop on the same code path. A
|
|
1948
|
+
* holder can still shorten its own lease, because re-acquiring as the same
|
|
1949
|
+
* holder renews, and renewing to a millisecond is a release in all but
|
|
1950
|
+
* name.
|
|
1951
|
+
*
|
|
1952
|
+
* Without this a worker that stops cleanly still blocks discovery for the
|
|
1953
|
+
* rest of its lease — invisible in a long-lived deployment, very visible
|
|
1954
|
+
* anywhere Acts are created and dropped inside one process.
|
|
1955
|
+
*
|
|
1956
|
+
* Best-effort: failing here costs the lease's remaining lifetime, which is
|
|
1957
|
+
* what would have happened had the process died instead.
|
|
1958
|
+
*/
|
|
1959
|
+
async release_correlation() {
|
|
1960
|
+
try {
|
|
1961
|
+
this._lease_until = 0;
|
|
1962
|
+
await this._cd.subscribe([], void 0, {
|
|
1963
|
+
key: this._key,
|
|
1964
|
+
by: this._by,
|
|
1965
|
+
millis: 0
|
|
1966
|
+
});
|
|
1967
|
+
} catch (error) {
|
|
1968
|
+
log().error(error);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1879
1971
|
stop_polling() {
|
|
1880
1972
|
if (this._timer) {
|
|
1881
1973
|
clearInterval(this._timer);
|
|
@@ -1909,7 +2001,7 @@ function close_correlation(correlator, actor) {
|
|
|
1909
2001
|
}
|
|
1910
2002
|
|
|
1911
2003
|
// src/internal/drain-cycle.ts
|
|
1912
|
-
import { randomUUID } from "crypto";
|
|
2004
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1913
2005
|
|
|
1914
2006
|
// src/internal/defer-timer.ts
|
|
1915
2007
|
var MAX_TIMER_DELAY_MS = 2147483647;
|
|
@@ -2031,7 +2123,7 @@ async function fetch(leased, eventLimit) {
|
|
|
2031
2123
|
}
|
|
2032
2124
|
var ack = (leases) => store().ack(leases);
|
|
2033
2125
|
var block = (leases) => store().block(leases);
|
|
2034
|
-
var subscribe = (streams, correlated_at) => store().subscribe(streams, correlated_at);
|
|
2126
|
+
var subscribe = (streams, correlated_at, correlator) => store().subscribe(streams, correlated_at, correlator);
|
|
2035
2127
|
|
|
2036
2128
|
// src/internal/event-sourcing.ts
|
|
2037
2129
|
import { patch } from "@rotorsoft/act-patch";
|
|
@@ -2639,7 +2731,7 @@ async function run_drain_cycle(ops, registry, batch_handlers, handle, handle_bat
|
|
|
2639
2731
|
const leased = await ops.claim(
|
|
2640
2732
|
lagging,
|
|
2641
2733
|
leading,
|
|
2642
|
-
|
|
2734
|
+
randomUUID2(),
|
|
2643
2735
|
leaseMillis,
|
|
2644
2736
|
lane
|
|
2645
2737
|
);
|
|
@@ -3655,7 +3747,7 @@ var Act = class {
|
|
|
3655
3747
|
// the scoped store and `_initialized` then blocks a retry (#1191).
|
|
3656
3748
|
init: () => this._scoped(() => this._correlate.init()),
|
|
3657
3749
|
checkpoint: () => this._correlate.checkpoint,
|
|
3658
|
-
correlate: (q) => this._correlate_scanned(q),
|
|
3750
|
+
correlate: (q) => this._correlate_scanned(q, true),
|
|
3659
3751
|
drain: (o) => this.drain(o),
|
|
3660
3752
|
on_settled: (drain) => this.emit("settled", drain),
|
|
3661
3753
|
breaker: this._breaker
|
|
@@ -3698,6 +3790,7 @@ var Act = class {
|
|
|
3698
3790
|
resolveShutdownConfig(options);
|
|
3699
3791
|
this._shutdown_promise = (async () => {
|
|
3700
3792
|
this.stop_correlations();
|
|
3793
|
+
await this._scoped(() => this._correlate.release_correlation());
|
|
3701
3794
|
this.stop_settling();
|
|
3702
3795
|
this._breaker.stop();
|
|
3703
3796
|
for (const c of this._drain_controllers.values()) c.stop();
|
|
@@ -4177,10 +4270,10 @@ var Act = class {
|
|
|
4177
4270
|
* internal rather than widening the public `correlate` return, which is
|
|
4178
4271
|
* charter-covered and has no use for it.
|
|
4179
4272
|
*/
|
|
4180
|
-
async _correlate_scanned(query) {
|
|
4273
|
+
async _correlate_scanned(query, lease = false) {
|
|
4181
4274
|
if (!this._drain) return { subscribed: 0, last_id: -1, scanned: false };
|
|
4182
4275
|
return this._scoped(async () => {
|
|
4183
|
-
const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query);
|
|
4276
|
+
const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query, lease);
|
|
4184
4277
|
if ((subscribed > 0 || marked > 0) && this._reactive_events.size > 0)
|
|
4185
4278
|
this._arm_all();
|
|
4186
4279
|
return { subscribed, last_id, scanned };
|
|
@@ -4263,6 +4356,7 @@ var Act = class {
|
|
|
4263
4356
|
*/
|
|
4264
4357
|
stop_correlations() {
|
|
4265
4358
|
this._correlate.stop_polling();
|
|
4359
|
+
void this._scoped(() => this._correlate.release_correlation());
|
|
4266
4360
|
}
|
|
4267
4361
|
/**
|
|
4268
4362
|
* Cancels any pending or active settle cycle.
|
|
@@ -4622,6 +4716,22 @@ var Act = class {
|
|
|
4622
4716
|
}
|
|
4623
4717
|
return this._correlate.checkpoint;
|
|
4624
4718
|
}
|
|
4719
|
+
/**
|
|
4720
|
+
* Drop retired streams from correlate's in-process "already subscribed"
|
|
4721
|
+
* set, so a later scan re-issues `subscribe()` for them.
|
|
4722
|
+
*
|
|
4723
|
+
* A tombstone seed means the stream was retired; a snapshot seed means it
|
|
4724
|
+
* was restarted and is still consuming.
|
|
4725
|
+
*
|
|
4726
|
+
* `truncate` no longer removes the subscription row (#1527), so this is no
|
|
4727
|
+
* longer repairing damage the close itself did. It still matters, because
|
|
4728
|
+
* the row can disappear later: reclaiming retired subscriptions is an
|
|
4729
|
+
* operator job now, and that `DELETE` can land while this process is
|
|
4730
|
+
* running. Forgetting here keeps the in-process view from outliving a row
|
|
4731
|
+
* an operator removed, which is the same silent-no-delivery failure #1398
|
|
4732
|
+
* described — a reaction whose target is named after the stream would never
|
|
4733
|
+
* be re-registered.
|
|
4734
|
+
*/
|
|
4625
4735
|
_forget_closed_subscriptions(result) {
|
|
4626
4736
|
const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
4627
4737
|
if (retired.length) this._correlate.forget_subscribed(retired);
|