@rotorsoft/act 1.27.0 → 1.29.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 +20 -2
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/build-classify.d.ts +4 -6
- package/dist/@types/internal/build-classify.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +19 -0
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/correlate-cycle.d.ts +31 -20
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/settle.d.ts +0 -10
- package/dist/@types/internal/settle.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +8 -0
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-66PTM4QB.js → chunk-PX3IBVLW.js} +4 -15
- package/dist/chunk-PX3IBVLW.js.map +1 -0
- package/dist/index.cjs +140 -93
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +138 -80
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +3 -18
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-66PTM4QB.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-PX3IBVLW.js";
|
|
26
26
|
import {
|
|
27
27
|
ActorSchema,
|
|
28
28
|
CausationEventSchema,
|
|
@@ -829,12 +829,10 @@ function classify_registry(registry, states) {
|
|
|
829
829
|
const target_lanes = /* @__PURE__ */ new Map();
|
|
830
830
|
const reactive_events = /* @__PURE__ */ new Set();
|
|
831
831
|
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
832
|
-
let has_dynamic_resolvers = false;
|
|
833
832
|
for (const [name, register] of Object.entries(registry.events)) {
|
|
834
833
|
if (register.reactions.size > 0) reactive_events.add(name);
|
|
835
834
|
for (const reaction of register.reactions.values()) {
|
|
836
835
|
if (typeof reaction.resolver === "function") {
|
|
837
|
-
has_dynamic_resolvers = true;
|
|
838
836
|
event_to_lanes.set(name, ALL_LANES);
|
|
839
837
|
} else {
|
|
840
838
|
const { target, source, priority = 0, lane } = reaction.resolver;
|
|
@@ -870,7 +868,6 @@ function classify_registry(registry, states) {
|
|
|
870
868
|
}
|
|
871
869
|
return {
|
|
872
870
|
static_targets: [...statics.values()],
|
|
873
|
-
has_dynamic_resolvers,
|
|
874
871
|
reactive_events,
|
|
875
872
|
event_to_state,
|
|
876
873
|
event_to_lanes
|
|
@@ -1354,7 +1351,8 @@ async function run_close_cycle(targets, deps) {
|
|
|
1354
1351
|
stream_info,
|
|
1355
1352
|
deps.reactive_events_size,
|
|
1356
1353
|
skipped,
|
|
1357
|
-
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE
|
|
1354
|
+
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
|
|
1355
|
+
deps.catch_up_correlation
|
|
1358
1356
|
);
|
|
1359
1357
|
if (!safe.length) return { truncated: windowed_result, skipped };
|
|
1360
1358
|
const { guarded, guard_events } = await guard_with_tombstones(
|
|
@@ -1473,8 +1471,16 @@ async function scan_stream_heads(streams) {
|
|
|
1473
1471
|
}
|
|
1474
1472
|
return out;
|
|
1475
1473
|
}
|
|
1476
|
-
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size) {
|
|
1474
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size, catch_up_correlation) {
|
|
1477
1475
|
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
1476
|
+
let needed = -1;
|
|
1477
|
+
for (const info of stream_info.values())
|
|
1478
|
+
needed = Math.max(needed, info.max_id);
|
|
1479
|
+
const checkpoint = await catch_up_correlation(needed);
|
|
1480
|
+
const uncorrelated = /* @__PURE__ */ new Set();
|
|
1481
|
+
for (const [stream, info] of stream_info) {
|
|
1482
|
+
if (checkpoint < info.max_id) uncorrelated.add(stream);
|
|
1483
|
+
}
|
|
1478
1484
|
const pending_set = /* @__PURE__ */ new Set();
|
|
1479
1485
|
const source_regex = /* @__PURE__ */ new Map();
|
|
1480
1486
|
const get_regex = (source) => {
|
|
@@ -1493,6 +1499,8 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped, p
|
|
|
1493
1499
|
(position) => {
|
|
1494
1500
|
last = position.stream;
|
|
1495
1501
|
const source_re = position.source ? get_regex(position.source) : void 0;
|
|
1502
|
+
const has_work = position.correlated_at !== void 0 && position.at < position.correlated_at;
|
|
1503
|
+
if (!has_work) return;
|
|
1496
1504
|
for (const [stream, info] of stream_info) {
|
|
1497
1505
|
if ((!source_re || source_re.test(stream)) && position.at < info.max_id) {
|
|
1498
1506
|
pending_set.add(stream);
|
|
@@ -1506,7 +1514,8 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped, p
|
|
|
1506
1514
|
}
|
|
1507
1515
|
const safe = [];
|
|
1508
1516
|
for (const [stream] of stream_info) {
|
|
1509
|
-
if (pending_set.has(stream)
|
|
1517
|
+
if (pending_set.has(stream) || uncorrelated.has(stream))
|
|
1518
|
+
skipped.push(stream);
|
|
1510
1519
|
else safe.push(stream);
|
|
1511
1520
|
}
|
|
1512
1521
|
return safe;
|
|
@@ -1596,21 +1605,24 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
1596
1605
|
|
|
1597
1606
|
// src/internal/correlate-cycle.ts
|
|
1598
1607
|
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
1608
|
+
var PATTERN_CACHE_SIZE = 32;
|
|
1599
1609
|
var CorrelateCycle = class {
|
|
1600
1610
|
_checkpoint = -1;
|
|
1601
1611
|
_initialized = false;
|
|
1602
1612
|
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
1603
1613
|
_init_promise;
|
|
1604
1614
|
_timer = void 0;
|
|
1605
|
-
// target →
|
|
1606
|
-
//
|
|
1607
|
-
//
|
|
1608
|
-
//
|
|
1609
|
-
//
|
|
1615
|
+
// target → what it was last subscribed at. Every scan re-subscribes the
|
|
1616
|
+
// targets it resolved (that is how the work mark lands), so this no longer
|
|
1617
|
+
// decides *whether* a target is sent — it decides *what* is sent with it:
|
|
1618
|
+
// a resolution raises priority/lane only when it beats the recorded floor,
|
|
1619
|
+
// and otherwise re-sends the row's own values so the mark changes nothing
|
|
1620
|
+
// else. See {@link Subscription}.
|
|
1610
1621
|
_subscribed;
|
|
1622
|
+
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
1623
|
+
_patterns = new LruMap(PATTERN_CACHE_SIZE);
|
|
1611
1624
|
_registry;
|
|
1612
1625
|
_static_targets;
|
|
1613
|
-
_has_dynamic_resolvers;
|
|
1614
1626
|
_cd;
|
|
1615
1627
|
_on_init;
|
|
1616
1628
|
/**
|
|
@@ -1640,7 +1652,6 @@ var CorrelateCycle = class {
|
|
|
1640
1652
|
constructor({
|
|
1641
1653
|
registry,
|
|
1642
1654
|
static_targets,
|
|
1643
|
-
has_dynamic_resolvers,
|
|
1644
1655
|
cd,
|
|
1645
1656
|
max_subscribed_streams,
|
|
1646
1657
|
run_scoped,
|
|
@@ -1651,7 +1662,6 @@ var CorrelateCycle = class {
|
|
|
1651
1662
|
this._subscribed = new LruMap(max_subscribed_streams);
|
|
1652
1663
|
this._registry = registry;
|
|
1653
1664
|
this._static_targets = static_targets;
|
|
1654
|
-
this._has_dynamic_resolvers = has_dynamic_resolvers;
|
|
1655
1665
|
this._cd = cd;
|
|
1656
1666
|
this._on_init = on_init;
|
|
1657
1667
|
this._run_scoped = run_scoped;
|
|
@@ -1662,19 +1672,10 @@ var CorrelateCycle = class {
|
|
|
1662
1672
|
get checkpoint() {
|
|
1663
1673
|
return this._checkpoint;
|
|
1664
1674
|
}
|
|
1665
|
-
/**
|
|
1666
|
-
* Whether any reaction uses a dynamic resolver — i.e. whether `correlate`
|
|
1667
|
-
* performs a store scan rather than the static no-op early-return. The
|
|
1668
|
-
* settle loop reads this to avoid recording a fictitious circuit-breaker
|
|
1669
|
-
* success on a static app whose correlate never touches the store (#1329).
|
|
1670
|
-
*/
|
|
1671
|
-
get has_dynamic_resolvers() {
|
|
1672
|
-
return this._has_dynamic_resolvers;
|
|
1673
|
-
}
|
|
1674
1675
|
/**
|
|
1675
1676
|
* Initialize correlation state on first call.
|
|
1676
|
-
* - Reads max(at) from store,
|
|
1677
|
-
* `watermark - back_scan`
|
|
1677
|
+
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
1678
|
+
* flooring a first boot at `watermark - back_scan` so an event
|
|
1678
1679
|
* committed-but-not-correlated before a crash is re-scanned on
|
|
1679
1680
|
* restart instead of skipped (ACT-1207)
|
|
1680
1681
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
@@ -1696,10 +1697,14 @@ var CorrelateCycle = class {
|
|
|
1696
1697
|
const { watermark, correlated_at } = await store().subscribe([
|
|
1697
1698
|
...this._static_targets
|
|
1698
1699
|
]);
|
|
1699
|
-
this._checkpoint =
|
|
1700
|
+
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
1700
1701
|
this._on_init?.();
|
|
1701
|
-
for (const { stream } of this._static_targets) {
|
|
1702
|
-
this._subscribed.set(stream,
|
|
1702
|
+
for (const { stream, priority = 0, lane } of this._static_targets) {
|
|
1703
|
+
this._subscribed.set(stream, {
|
|
1704
|
+
floor: Number.POSITIVE_INFINITY,
|
|
1705
|
+
priority,
|
|
1706
|
+
lane
|
|
1707
|
+
});
|
|
1703
1708
|
}
|
|
1704
1709
|
await this._on_init_async?.();
|
|
1705
1710
|
}
|
|
@@ -1724,14 +1729,34 @@ var CorrelateCycle = class {
|
|
|
1724
1729
|
}
|
|
1725
1730
|
}
|
|
1726
1731
|
/**
|
|
1727
|
-
*
|
|
1728
|
-
*
|
|
1729
|
-
*
|
|
1732
|
+
* Would an event from `stream` be fetched for a target subscribed with
|
|
1733
|
+
* `source`? The subscription's source is the filter `fetch` queries with
|
|
1734
|
+
* — literal names by equality, patterns compiled as a `RegExp` — so an
|
|
1735
|
+
* event outside it is not work for that target and must not mark it.
|
|
1736
|
+
* No source means the target consumes every stream.
|
|
1737
|
+
*/
|
|
1738
|
+
_in_fetch_window(source, stream) {
|
|
1739
|
+
if (source === void 0 || source === stream) return true;
|
|
1740
|
+
if (is_literal_source(source)) return false;
|
|
1741
|
+
let pattern = this._patterns.get(source);
|
|
1742
|
+
if (!pattern) {
|
|
1743
|
+
pattern = new RegExp(source);
|
|
1744
|
+
this._patterns.set(source, pattern);
|
|
1745
|
+
}
|
|
1746
|
+
return pattern.test(stream);
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Scan the events past the checkpoint, resolve each to its target
|
|
1750
|
+
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
1751
|
+
* targets get registered, and every target an event resolved to gets its
|
|
1752
|
+
* **work mark** raised to that event's id (#1487).
|
|
1753
|
+
*
|
|
1754
|
+
* Both resolver kinds are walked. A static target is already subscribed
|
|
1755
|
+
* at init, but marking it is what makes it claimable without probing the
|
|
1756
|
+
* event log, so the scan runs for every app.
|
|
1730
1757
|
*/
|
|
1731
1758
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
1732
1759
|
await this.init();
|
|
1733
|
-
if (!this._has_dynamic_resolvers)
|
|
1734
|
-
return { subscribed: 0, last_id: this._checkpoint };
|
|
1735
1760
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
1736
1761
|
const correlated = /* @__PURE__ */ new Map();
|
|
1737
1762
|
let last_id = after;
|
|
@@ -1741,52 +1766,61 @@ var CorrelateCycle = class {
|
|
|
1741
1766
|
const register = this._registry.events[event.name];
|
|
1742
1767
|
if (register) {
|
|
1743
1768
|
for (const reaction of register.reactions.values()) {
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
entry.
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
event
|
|
1762
|
-
});
|
|
1763
|
-
correlated.set(resolved.target, entry);
|
|
1769
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
1770
|
+
if (!resolved) continue;
|
|
1771
|
+
const recorded = this._subscribed.get(resolved.target);
|
|
1772
|
+
const priority = resolved.priority ?? 0;
|
|
1773
|
+
const upgraded = !recorded || priority > recorded.floor;
|
|
1774
|
+
const carried = upgraded ? { priority, lane: resolved.lane } : { priority: recorded.priority, lane: recorded.lane };
|
|
1775
|
+
const entry = correlated.get(resolved.target) || {
|
|
1776
|
+
source: resolved.source,
|
|
1777
|
+
priority: carried.priority,
|
|
1778
|
+
lane: carried.lane,
|
|
1779
|
+
upgraded,
|
|
1780
|
+
correlated_at: void 0
|
|
1781
|
+
};
|
|
1782
|
+
if (carried.priority > entry.priority) {
|
|
1783
|
+
entry.priority = carried.priority;
|
|
1784
|
+
entry.lane = carried.lane;
|
|
1785
|
+
entry.upgraded = upgraded;
|
|
1764
1786
|
}
|
|
1787
|
+
if (this._in_fetch_window(resolved.source, event.stream))
|
|
1788
|
+
entry.correlated_at = event.id;
|
|
1789
|
+
correlated.set(resolved.target, entry);
|
|
1765
1790
|
}
|
|
1766
1791
|
}
|
|
1767
1792
|
},
|
|
1768
1793
|
{ ...query, after }
|
|
1769
1794
|
);
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1795
|
+
const streams = [];
|
|
1796
|
+
for (const [stream, entry] of correlated) {
|
|
1797
|
+
if (entry.upgraded || entry.correlated_at !== void 0)
|
|
1798
|
+
streams.push({
|
|
1773
1799
|
stream,
|
|
1774
|
-
source,
|
|
1775
|
-
priority,
|
|
1776
|
-
lane
|
|
1777
|
-
|
|
1778
|
-
|
|
1800
|
+
source: entry.source,
|
|
1801
|
+
priority: entry.priority,
|
|
1802
|
+
lane: entry.lane,
|
|
1803
|
+
correlated_at: entry.correlated_at
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
if (streams.length) {
|
|
1779
1807
|
const { subscribed } = await this._cd.subscribe(streams, last_id);
|
|
1808
|
+
const marked = streams.filter(
|
|
1809
|
+
(entry) => entry.correlated_at !== void 0
|
|
1810
|
+
).length;
|
|
1780
1811
|
this._checkpoint = last_id;
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
this._subscribed.set(stream,
|
|
1784
|
-
|
|
1812
|
+
for (const { stream, priority, lane } of streams) {
|
|
1813
|
+
if (correlated.get(stream)?.upgraded)
|
|
1814
|
+
this._subscribed.set(stream, {
|
|
1815
|
+
floor: priority,
|
|
1816
|
+
priority,
|
|
1817
|
+
lane
|
|
1818
|
+
});
|
|
1785
1819
|
}
|
|
1786
|
-
return { subscribed, last_id };
|
|
1820
|
+
return { subscribed, last_id, marked };
|
|
1787
1821
|
}
|
|
1788
1822
|
this._checkpoint = last_id;
|
|
1789
|
-
return { subscribed: 0, last_id };
|
|
1823
|
+
return { subscribed: 0, last_id, marked: 0 };
|
|
1790
1824
|
}
|
|
1791
1825
|
/**
|
|
1792
1826
|
* Start a periodic correlation worker. Returns false if one is already
|
|
@@ -3089,7 +3123,7 @@ var SettleLoop = class {
|
|
|
3089
3123
|
...correlate_query,
|
|
3090
3124
|
after: after_before
|
|
3091
3125
|
});
|
|
3092
|
-
|
|
3126
|
+
this._deps.breaker.passed();
|
|
3093
3127
|
const drain = await this._deps.drain(drain_options);
|
|
3094
3128
|
settled_drain = settled_drain ? {
|
|
3095
3129
|
fetched: [...settled_drain.fetched, ...drain.fetched],
|
|
@@ -3135,6 +3169,8 @@ var SettleLoop = class {
|
|
|
3135
3169
|
|
|
3136
3170
|
// src/act.ts
|
|
3137
3171
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
3172
|
+
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
3173
|
+
var CLOSE_CATCH_UP_PASSES = 20;
|
|
3138
3174
|
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
3139
3175
|
function validate_only_lanes(options, lanes) {
|
|
3140
3176
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
@@ -3450,6 +3486,7 @@ var Act = class {
|
|
|
3450
3486
|
const close_actor = { id: "$close", name: "close" };
|
|
3451
3487
|
const result = await run_close_cycle(targets, {
|
|
3452
3488
|
reactive_events_size: this._reactive_events.size,
|
|
3489
|
+
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
3453
3490
|
event_to_state: this._event_to_state,
|
|
3454
3491
|
load: this._es.load,
|
|
3455
3492
|
tombstone: this._es.tombstone,
|
|
@@ -3536,7 +3573,6 @@ var Act = class {
|
|
|
3536
3573
|
return new CorrelateCycle({
|
|
3537
3574
|
registry: this.registry,
|
|
3538
3575
|
static_targets: classification.static_targets,
|
|
3539
|
-
has_dynamic_resolvers: classification.has_dynamic_resolvers,
|
|
3540
3576
|
cd: this._cd,
|
|
3541
3577
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
3542
3578
|
on_init: () => {
|
|
@@ -3587,11 +3623,7 @@ var Act = class {
|
|
|
3587
3623
|
correlate: (q) => this.correlate(q),
|
|
3588
3624
|
drain: (o) => this.drain(o),
|
|
3589
3625
|
on_settled: (drain) => this.emit("settled", drain),
|
|
3590
|
-
breaker: this._breaker
|
|
3591
|
-
// Static-reaction apps' correlate is a no-op (no store call), so a
|
|
3592
|
-
// settle pass carries no store-health signal — don't let it record a
|
|
3593
|
-
// breaker success (#1329).
|
|
3594
|
-
correlate_probes_store: this._correlate.has_dynamic_resolvers
|
|
3626
|
+
breaker: this._breaker
|
|
3595
3627
|
},
|
|
3596
3628
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
3597
3629
|
);
|
|
@@ -3964,8 +3996,13 @@ var Act = class {
|
|
|
3964
3996
|
* Drain uses a dual-frontier strategy to balance processing of new streams (lagging)
|
|
3965
3997
|
* vs active streams (leading). The ratio adapts based on event pressure.
|
|
3966
3998
|
*
|
|
3967
|
-
* Call `correlate()` before `drain()
|
|
3968
|
-
*
|
|
3999
|
+
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
4000
|
+
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
4001
|
+
* `correlate` is the only component that raises that mark (#1487), so a
|
|
4002
|
+
* commit no correlate has seen is not drainable — including for static
|
|
4003
|
+
* targets, which were served by a probe of the event log before. For a
|
|
4004
|
+
* higher-level API that handles debouncing, correlation, and signaling
|
|
4005
|
+
* automatically, use {@link settle}.
|
|
3969
4006
|
*
|
|
3970
4007
|
* @param options - Drain configuration — see {@link DrainOptions} for fields
|
|
3971
4008
|
* (`streamLimit`, `eventLimit`, `leaseMillis`).
|
|
@@ -4094,10 +4131,10 @@ var Act = class {
|
|
|
4094
4131
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
4095
4132
|
if (!this._drain) return { subscribed: 0, last_id: -1 };
|
|
4096
4133
|
return this._scoped(async () => {
|
|
4097
|
-
const
|
|
4098
|
-
if (
|
|
4134
|
+
const { subscribed, last_id, marked } = await this._correlate.correlate(query);
|
|
4135
|
+
if ((subscribed > 0 || marked > 0) && this._reactive_events.size > 0)
|
|
4099
4136
|
this._arm_all();
|
|
4100
|
-
return
|
|
4137
|
+
return { subscribed, last_id };
|
|
4101
4138
|
});
|
|
4102
4139
|
}
|
|
4103
4140
|
/**
|
|
@@ -4515,6 +4552,26 @@ var Act = class {
|
|
|
4515
4552
|
* removed, so a later correlate can re-subscribe it (#1398). Restart
|
|
4516
4553
|
* targets keep their row, so only fully-retired streams are forgotten.
|
|
4517
4554
|
*/
|
|
4555
|
+
/**
|
|
4556
|
+
* Advance correlation until the read cursor reaches `until`, and report
|
|
4557
|
+
* where it landed (#1487). Used by the close cycle's safety probe, which
|
|
4558
|
+
* cannot judge a subscription's pending work over events correlate has
|
|
4559
|
+
* not resolved yet.
|
|
4560
|
+
*
|
|
4561
|
+
* Bounded on both ends: it stops as soon as a pass makes no progress (the
|
|
4562
|
+
* log has no more to give), and after {@link CLOSE_CATCH_UP_PASSES}
|
|
4563
|
+
* windows, so a close behind an enormous backlog degrades to skipping the
|
|
4564
|
+
* stream — the documented retryable outcome — instead of scanning the
|
|
4565
|
+
* whole log inside an operator call.
|
|
4566
|
+
*/
|
|
4567
|
+
async _catch_up_correlation(until) {
|
|
4568
|
+
for (let pass = 0; pass < CLOSE_CATCH_UP_PASSES && this._correlate.checkpoint < until; pass++) {
|
|
4569
|
+
const before = this._correlate.checkpoint;
|
|
4570
|
+
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
4571
|
+
if (this._correlate.checkpoint <= before) break;
|
|
4572
|
+
}
|
|
4573
|
+
return this._correlate.checkpoint;
|
|
4574
|
+
}
|
|
4518
4575
|
_forget_closed_subscriptions(result) {
|
|
4519
4576
|
const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
4520
4577
|
if (retired.length) this._correlate.forget_subscribed(retired);
|
|
@@ -4526,6 +4583,7 @@ var Act = class {
|
|
|
4526
4583
|
const close_actor = { id: "$close", name: "close" };
|
|
4527
4584
|
const result = await run_close_cycle(targets, {
|
|
4528
4585
|
reactive_events_size: this._reactive_events.size,
|
|
4586
|
+
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
4529
4587
|
event_to_state: this._event_to_state,
|
|
4530
4588
|
load: this._es.load,
|
|
4531
4589
|
tombstone: this._es.tombstone,
|