@rotorsoft/act 1.26.0 → 1.28.1
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 +2 -7
- 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 +30 -20
- package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
- package/dist/@types/internal/drain.d.ts +2 -7
- package/dist/@types/internal/drain.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 +46 -15
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/{chunk-BAIMGDI4.js → chunk-7WFKIQKY.js} +25 -7
- package/dist/chunk-7WFKIQKY.js.map +1 -0
- package/dist/index.cjs +153 -80
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +130 -75
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +24 -6
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-BAIMGDI4.js.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -637,6 +637,10 @@ var InMemoryStream = class {
|
|
|
637
637
|
// re-claimed (and `retry` is never bumped) until its due-time passes. Unlike
|
|
638
638
|
// in-process backoff, this is durable store state shared across workers.
|
|
639
639
|
_deferred_at = void 0;
|
|
640
|
+
// Work mark (#1485): highest event id observed to resolve to this target.
|
|
641
|
+
// `undefined` means UNKNOWN — the row predates the mark, so `claim` falls
|
|
642
|
+
// back to the legacy has-work probe rather than treating it as "no work".
|
|
643
|
+
_correlated_at = void 0;
|
|
640
644
|
constructor(stream, source, priority = 0, lane = DEFAULT_LANE) {
|
|
641
645
|
this.stream = stream;
|
|
642
646
|
this.source = source;
|
|
@@ -660,6 +664,17 @@ var InMemoryStream = class {
|
|
|
660
664
|
bump_priority(priority) {
|
|
661
665
|
if (priority > this._priority) this._priority = priority;
|
|
662
666
|
}
|
|
667
|
+
get correlated_at() {
|
|
668
|
+
return this._correlated_at;
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Raise the work mark via {@link subscribe}: keeps the maximum, for every
|
|
672
|
+
* value including zero and negatives, so a mark never regresses.
|
|
673
|
+
*/
|
|
674
|
+
mark(correlated_at) {
|
|
675
|
+
if (this._correlated_at === void 0 || correlated_at > this._correlated_at)
|
|
676
|
+
this._correlated_at = correlated_at;
|
|
677
|
+
}
|
|
663
678
|
/**
|
|
664
679
|
* Set the priority outright via {@link prioritize}: operator
|
|
665
680
|
* runtime override that ignores the build-time `max()` invariant.
|
|
@@ -1014,6 +1029,7 @@ var InMemoryStore = class {
|
|
|
1014
1029
|
async claim(lagging, leading, by, millis, lane) {
|
|
1015
1030
|
await sleep();
|
|
1016
1031
|
const has_work = (s) => {
|
|
1032
|
+
if (s.correlated_at !== void 0) return s.at < s.correlated_at;
|
|
1017
1033
|
if (!s.source) return s.at < this._max_non_snap_event_id;
|
|
1018
1034
|
if (is_literal_source(s.source)) {
|
|
1019
1035
|
const max_id = this._max_event_id_by_stream.get(s.source);
|
|
@@ -1075,17 +1091,18 @@ var InMemoryStore = class {
|
|
|
1075
1091
|
stream,
|
|
1076
1092
|
source,
|
|
1077
1093
|
priority = 0,
|
|
1078
|
-
lane = DEFAULT_LANE
|
|
1094
|
+
lane = DEFAULT_LANE,
|
|
1095
|
+
correlated_at: correlated_at2
|
|
1079
1096
|
} of streams) {
|
|
1080
1097
|
const existing = this._streams.get(stream);
|
|
1081
1098
|
if (existing) {
|
|
1082
1099
|
existing.bump_priority(priority);
|
|
1083
1100
|
existing.lane = lane;
|
|
1101
|
+
if (correlated_at2 !== void 0) existing.mark(correlated_at2);
|
|
1084
1102
|
} else {
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
);
|
|
1103
|
+
const created = new InMemoryStream(stream, source, priority, lane);
|
|
1104
|
+
if (correlated_at2 !== void 0) created.mark(correlated_at2);
|
|
1105
|
+
this._streams.set(stream, created);
|
|
1089
1106
|
subscribed++;
|
|
1090
1107
|
}
|
|
1091
1108
|
}
|
|
@@ -1312,7 +1329,8 @@ var InMemoryStore = class {
|
|
|
1312
1329
|
leased_by: s.leased_by,
|
|
1313
1330
|
leased_until: s.leased_until,
|
|
1314
1331
|
lane: s.lane,
|
|
1315
|
-
deferred_at: s.deferred_at
|
|
1332
|
+
deferred_at: s.deferred_at,
|
|
1333
|
+
correlated_at: s.correlated_at
|
|
1316
1334
|
});
|
|
1317
1335
|
count++;
|
|
1318
1336
|
if (count >= limit) break;
|
|
@@ -2401,12 +2419,10 @@ function classify_registry(registry, states) {
|
|
|
2401
2419
|
const target_lanes = /* @__PURE__ */ new Map();
|
|
2402
2420
|
const reactive_events = /* @__PURE__ */ new Set();
|
|
2403
2421
|
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
2404
|
-
let has_dynamic_resolvers = false;
|
|
2405
2422
|
for (const [name, register] of Object.entries(registry.events)) {
|
|
2406
2423
|
if (register.reactions.size > 0) reactive_events.add(name);
|
|
2407
2424
|
for (const reaction of register.reactions.values()) {
|
|
2408
2425
|
if (typeof reaction.resolver === "function") {
|
|
2409
|
-
has_dynamic_resolvers = true;
|
|
2410
2426
|
event_to_lanes.set(name, ALL_LANES);
|
|
2411
2427
|
} else {
|
|
2412
2428
|
const { target, source, priority = 0, lane } = reaction.resolver;
|
|
@@ -2442,7 +2458,6 @@ function classify_registry(registry, states) {
|
|
|
2442
2458
|
}
|
|
2443
2459
|
return {
|
|
2444
2460
|
static_targets: [...statics.values()],
|
|
2445
|
-
has_dynamic_resolvers,
|
|
2446
2461
|
reactive_events,
|
|
2447
2462
|
event_to_state,
|
|
2448
2463
|
event_to_lanes
|
|
@@ -2926,7 +2941,8 @@ async function run_close_cycle(targets, deps) {
|
|
|
2926
2941
|
stream_info,
|
|
2927
2942
|
deps.reactive_events_size,
|
|
2928
2943
|
skipped,
|
|
2929
|
-
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE
|
|
2944
|
+
deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
|
|
2945
|
+
deps.catch_up_correlation
|
|
2930
2946
|
);
|
|
2931
2947
|
if (!safe.length) return { truncated: windowed_result, skipped };
|
|
2932
2948
|
const { guarded, guard_events } = await guard_with_tombstones(
|
|
@@ -3045,8 +3061,16 @@ async function scan_stream_heads(streams) {
|
|
|
3045
3061
|
}
|
|
3046
3062
|
return out;
|
|
3047
3063
|
}
|
|
3048
|
-
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size) {
|
|
3064
|
+
async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size, catch_up_correlation) {
|
|
3049
3065
|
if (reactive_events_size === 0) return [...stream_info.keys()];
|
|
3066
|
+
let needed = -1;
|
|
3067
|
+
for (const info of stream_info.values())
|
|
3068
|
+
needed = Math.max(needed, info.max_id);
|
|
3069
|
+
const checkpoint = await catch_up_correlation(needed);
|
|
3070
|
+
const uncorrelated = /* @__PURE__ */ new Set();
|
|
3071
|
+
for (const [stream, info] of stream_info) {
|
|
3072
|
+
if (checkpoint < info.max_id) uncorrelated.add(stream);
|
|
3073
|
+
}
|
|
3050
3074
|
const pending_set = /* @__PURE__ */ new Set();
|
|
3051
3075
|
const source_regex = /* @__PURE__ */ new Map();
|
|
3052
3076
|
const get_regex = (source) => {
|
|
@@ -3065,6 +3089,8 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped, p
|
|
|
3065
3089
|
(position) => {
|
|
3066
3090
|
last = position.stream;
|
|
3067
3091
|
const source_re = position.source ? get_regex(position.source) : void 0;
|
|
3092
|
+
const has_work = position.correlated_at === void 0 || position.at < position.correlated_at;
|
|
3093
|
+
if (!has_work) return;
|
|
3068
3094
|
for (const [stream, info] of stream_info) {
|
|
3069
3095
|
if ((!source_re || source_re.test(stream)) && position.at < info.max_id) {
|
|
3070
3096
|
pending_set.add(stream);
|
|
@@ -3078,7 +3104,8 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped, p
|
|
|
3078
3104
|
}
|
|
3079
3105
|
const safe = [];
|
|
3080
3106
|
for (const [stream] of stream_info) {
|
|
3081
|
-
if (pending_set.has(stream)
|
|
3107
|
+
if (pending_set.has(stream) || uncorrelated.has(stream))
|
|
3108
|
+
skipped.push(stream);
|
|
3082
3109
|
else safe.push(stream);
|
|
3083
3110
|
}
|
|
3084
3111
|
return safe;
|
|
@@ -3168,21 +3195,24 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
3168
3195
|
|
|
3169
3196
|
// src/internal/correlate-cycle.ts
|
|
3170
3197
|
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
3198
|
+
var PATTERN_CACHE_SIZE = 32;
|
|
3171
3199
|
var CorrelateCycle = class {
|
|
3172
3200
|
_checkpoint = -1;
|
|
3173
3201
|
_initialized = false;
|
|
3174
3202
|
/** In-flight init, memoized for single-flight and cleared on failure. */
|
|
3175
3203
|
_init_promise;
|
|
3176
3204
|
_timer = void 0;
|
|
3177
|
-
// target →
|
|
3178
|
-
//
|
|
3179
|
-
//
|
|
3180
|
-
//
|
|
3181
|
-
//
|
|
3205
|
+
// target → what it was last subscribed at. Every scan re-subscribes the
|
|
3206
|
+
// targets it resolved (that is how the work mark lands), so this no longer
|
|
3207
|
+
// decides *whether* a target is sent — it decides *what* is sent with it:
|
|
3208
|
+
// a resolution raises priority/lane only when it beats the recorded floor,
|
|
3209
|
+
// and otherwise re-sends the row's own values so the mark changes nothing
|
|
3210
|
+
// else. See {@link Subscription}.
|
|
3182
3211
|
_subscribed;
|
|
3212
|
+
/** Compiled pattern sources, bounded by {@link PATTERN_CACHE_SIZE}. */
|
|
3213
|
+
_patterns = new LruMap(PATTERN_CACHE_SIZE);
|
|
3183
3214
|
_registry;
|
|
3184
3215
|
_static_targets;
|
|
3185
|
-
_has_dynamic_resolvers;
|
|
3186
3216
|
_cd;
|
|
3187
3217
|
_on_init;
|
|
3188
3218
|
/**
|
|
@@ -3212,7 +3242,6 @@ var CorrelateCycle = class {
|
|
|
3212
3242
|
constructor({
|
|
3213
3243
|
registry,
|
|
3214
3244
|
static_targets,
|
|
3215
|
-
has_dynamic_resolvers,
|
|
3216
3245
|
cd,
|
|
3217
3246
|
max_subscribed_streams,
|
|
3218
3247
|
run_scoped,
|
|
@@ -3223,7 +3252,6 @@ var CorrelateCycle = class {
|
|
|
3223
3252
|
this._subscribed = new LruMap(max_subscribed_streams);
|
|
3224
3253
|
this._registry = registry;
|
|
3225
3254
|
this._static_targets = static_targets;
|
|
3226
|
-
this._has_dynamic_resolvers = has_dynamic_resolvers;
|
|
3227
3255
|
this._cd = cd;
|
|
3228
3256
|
this._on_init = on_init;
|
|
3229
3257
|
this._run_scoped = run_scoped;
|
|
@@ -3234,19 +3262,10 @@ var CorrelateCycle = class {
|
|
|
3234
3262
|
get checkpoint() {
|
|
3235
3263
|
return this._checkpoint;
|
|
3236
3264
|
}
|
|
3237
|
-
/**
|
|
3238
|
-
* Whether any reaction uses a dynamic resolver — i.e. whether `correlate`
|
|
3239
|
-
* performs a store scan rather than the static no-op early-return. The
|
|
3240
|
-
* settle loop reads this to avoid recording a fictitious circuit-breaker
|
|
3241
|
-
* success on a static app whose correlate never touches the store (#1329).
|
|
3242
|
-
*/
|
|
3243
|
-
get has_dynamic_resolvers() {
|
|
3244
|
-
return this._has_dynamic_resolvers;
|
|
3245
|
-
}
|
|
3246
3265
|
/**
|
|
3247
3266
|
* Initialize correlation state on first call.
|
|
3248
|
-
* - Reads max(at) from store,
|
|
3249
|
-
* `watermark - back_scan`
|
|
3267
|
+
* - Reads the durable correlate checkpoint (and max(at)) from the store,
|
|
3268
|
+
* flooring a first boot at `watermark - back_scan` so an event
|
|
3250
3269
|
* committed-but-not-correlated before a crash is re-scanned on
|
|
3251
3270
|
* restart instead of skipped (ACT-1207)
|
|
3252
3271
|
* - Subscribes static resolver targets (idempotent upsert)
|
|
@@ -3268,10 +3287,14 @@ var CorrelateCycle = class {
|
|
|
3268
3287
|
const { watermark, correlated_at } = await store2().subscribe([
|
|
3269
3288
|
...this._static_targets
|
|
3270
3289
|
]);
|
|
3271
|
-
this._checkpoint =
|
|
3290
|
+
this._checkpoint = correlated_at >= 0 ? correlated_at : Math.max(-1, watermark - this._cold_start_back_scan);
|
|
3272
3291
|
this._on_init?.();
|
|
3273
|
-
for (const { stream } of this._static_targets) {
|
|
3274
|
-
this._subscribed.set(stream,
|
|
3292
|
+
for (const { stream, priority = 0, lane } of this._static_targets) {
|
|
3293
|
+
this._subscribed.set(stream, {
|
|
3294
|
+
floor: Number.POSITIVE_INFINITY,
|
|
3295
|
+
priority,
|
|
3296
|
+
lane
|
|
3297
|
+
});
|
|
3275
3298
|
}
|
|
3276
3299
|
await this._on_init_async?.();
|
|
3277
3300
|
}
|
|
@@ -3296,14 +3319,34 @@ var CorrelateCycle = class {
|
|
|
3296
3319
|
}
|
|
3297
3320
|
}
|
|
3298
3321
|
/**
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
3301
|
-
*
|
|
3322
|
+
* Would an event from `stream` be fetched for a target subscribed with
|
|
3323
|
+
* `source`? The subscription's source is the filter `fetch` queries with
|
|
3324
|
+
* — literal names by equality, patterns compiled as a `RegExp` — so an
|
|
3325
|
+
* event outside it is not work for that target and must not mark it.
|
|
3326
|
+
* No source means the target consumes every stream.
|
|
3327
|
+
*/
|
|
3328
|
+
_in_fetch_window(source, stream) {
|
|
3329
|
+
if (source === void 0 || source === stream) return true;
|
|
3330
|
+
if (is_literal_source(source)) return false;
|
|
3331
|
+
let pattern = this._patterns.get(source);
|
|
3332
|
+
if (!pattern) {
|
|
3333
|
+
pattern = new RegExp(source);
|
|
3334
|
+
this._patterns.set(source, pattern);
|
|
3335
|
+
}
|
|
3336
|
+
return pattern.test(stream);
|
|
3337
|
+
}
|
|
3338
|
+
/**
|
|
3339
|
+
* Scan the events past the checkpoint, resolve each to its target
|
|
3340
|
+
* streams, and record what it found through `cd.subscribe` — new dynamic
|
|
3341
|
+
* targets get registered, and every target an event resolved to gets its
|
|
3342
|
+
* **work mark** raised to that event's id (#1487).
|
|
3343
|
+
*
|
|
3344
|
+
* Both resolver kinds are walked. A static target is already subscribed
|
|
3345
|
+
* at init, but marking it is what makes it claimable without probing the
|
|
3346
|
+
* event log, so the scan runs for every app.
|
|
3302
3347
|
*/
|
|
3303
3348
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
3304
3349
|
await this.init();
|
|
3305
|
-
if (!this._has_dynamic_resolvers)
|
|
3306
|
-
return { subscribed: 0, last_id: this._checkpoint };
|
|
3307
3350
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
3308
3351
|
const correlated = /* @__PURE__ */ new Map();
|
|
3309
3352
|
let last_id = after;
|
|
@@ -3313,47 +3356,53 @@ var CorrelateCycle = class {
|
|
|
3313
3356
|
const register = this._registry.events[event.name];
|
|
3314
3357
|
if (register) {
|
|
3315
3358
|
for (const reaction of register.reactions.values()) {
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
entry.
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
event
|
|
3334
|
-
});
|
|
3335
|
-
correlated.set(resolved.target, entry);
|
|
3359
|
+
const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
|
|
3360
|
+
if (!resolved) continue;
|
|
3361
|
+
const recorded = this._subscribed.get(resolved.target);
|
|
3362
|
+
const priority = resolved.priority ?? 0;
|
|
3363
|
+
const upgraded = !recorded || priority > recorded.floor;
|
|
3364
|
+
const carried = upgraded ? { priority, lane: resolved.lane } : { priority: recorded.priority, lane: recorded.lane };
|
|
3365
|
+
const entry = correlated.get(resolved.target) || {
|
|
3366
|
+
source: resolved.source,
|
|
3367
|
+
priority: carried.priority,
|
|
3368
|
+
lane: carried.lane,
|
|
3369
|
+
upgraded,
|
|
3370
|
+
correlated_at: void 0
|
|
3371
|
+
};
|
|
3372
|
+
if (carried.priority > entry.priority) {
|
|
3373
|
+
entry.priority = carried.priority;
|
|
3374
|
+
entry.lane = carried.lane;
|
|
3375
|
+
entry.upgraded = upgraded;
|
|
3336
3376
|
}
|
|
3377
|
+
if (this._in_fetch_window(resolved.source, event.stream))
|
|
3378
|
+
entry.correlated_at = event.id;
|
|
3379
|
+
correlated.set(resolved.target, entry);
|
|
3337
3380
|
}
|
|
3338
3381
|
}
|
|
3339
3382
|
},
|
|
3340
3383
|
{ ...query, after }
|
|
3341
3384
|
);
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3385
|
+
const streams = [];
|
|
3386
|
+
for (const [stream, entry] of correlated) {
|
|
3387
|
+
if (entry.upgraded || entry.correlated_at !== void 0)
|
|
3388
|
+
streams.push({
|
|
3345
3389
|
stream,
|
|
3346
|
-
source,
|
|
3347
|
-
priority,
|
|
3348
|
-
lane
|
|
3349
|
-
|
|
3350
|
-
|
|
3390
|
+
source: entry.source,
|
|
3391
|
+
priority: entry.priority,
|
|
3392
|
+
lane: entry.lane,
|
|
3393
|
+
correlated_at: entry.correlated_at
|
|
3394
|
+
});
|
|
3395
|
+
}
|
|
3396
|
+
if (streams.length) {
|
|
3351
3397
|
const { subscribed } = await this._cd.subscribe(streams, last_id);
|
|
3352
3398
|
this._checkpoint = last_id;
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
this._subscribed.set(stream,
|
|
3356
|
-
|
|
3399
|
+
for (const { stream, priority, lane } of streams) {
|
|
3400
|
+
if (correlated.get(stream)?.upgraded)
|
|
3401
|
+
this._subscribed.set(stream, {
|
|
3402
|
+
floor: priority,
|
|
3403
|
+
priority,
|
|
3404
|
+
lane
|
|
3405
|
+
});
|
|
3357
3406
|
}
|
|
3358
3407
|
return { subscribed, last_id };
|
|
3359
3408
|
}
|
|
@@ -4661,7 +4710,7 @@ var SettleLoop = class {
|
|
|
4661
4710
|
...correlate_query,
|
|
4662
4711
|
after: after_before
|
|
4663
4712
|
});
|
|
4664
|
-
|
|
4713
|
+
this._deps.breaker.passed();
|
|
4665
4714
|
const drain = await this._deps.drain(drain_options);
|
|
4666
4715
|
settled_drain = settled_drain ? {
|
|
4667
4716
|
fetched: [...settled_drain.fetched, ...drain.fetched],
|
|
@@ -4707,6 +4756,8 @@ var SettleLoop = class {
|
|
|
4707
4756
|
|
|
4708
4757
|
// src/act.ts
|
|
4709
4758
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
4759
|
+
var CLOSE_CATCH_UP_LIMIT = 1e3;
|
|
4760
|
+
var CLOSE_CATCH_UP_PASSES = 20;
|
|
4710
4761
|
var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
|
|
4711
4762
|
function validate_only_lanes(options, lanes) {
|
|
4712
4763
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
@@ -5022,6 +5073,7 @@ var Act = class {
|
|
|
5022
5073
|
const close_actor = { id: "$close", name: "close" };
|
|
5023
5074
|
const result = await run_close_cycle(targets, {
|
|
5024
5075
|
reactive_events_size: this._reactive_events.size,
|
|
5076
|
+
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
5025
5077
|
event_to_state: this._event_to_state,
|
|
5026
5078
|
load: this._es.load,
|
|
5027
5079
|
tombstone: this._es.tombstone,
|
|
@@ -5108,7 +5160,6 @@ var Act = class {
|
|
|
5108
5160
|
return new CorrelateCycle({
|
|
5109
5161
|
registry: this.registry,
|
|
5110
5162
|
static_targets: classification.static_targets,
|
|
5111
|
-
has_dynamic_resolvers: classification.has_dynamic_resolvers,
|
|
5112
5163
|
cd: this._cd,
|
|
5113
5164
|
max_subscribed_streams: options.maxSubscribedStreams ?? DEFAULT_MAX_SUBSCRIBED_STREAMS,
|
|
5114
5165
|
on_init: () => {
|
|
@@ -5159,11 +5210,7 @@ var Act = class {
|
|
|
5159
5210
|
correlate: (q) => this.correlate(q),
|
|
5160
5211
|
drain: (o) => this.drain(o),
|
|
5161
5212
|
on_settled: (drain) => this.emit("settled", drain),
|
|
5162
|
-
breaker: this._breaker
|
|
5163
|
-
// Static-reaction apps' correlate is a no-op (no store call), so a
|
|
5164
|
-
// settle pass carries no store-health signal — don't let it record a
|
|
5165
|
-
// breaker success (#1329).
|
|
5166
|
-
correlate_probes_store: this._correlate.has_dynamic_resolvers
|
|
5213
|
+
breaker: this._breaker
|
|
5167
5214
|
},
|
|
5168
5215
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
5169
5216
|
);
|
|
@@ -5536,8 +5583,13 @@ var Act = class {
|
|
|
5536
5583
|
* Drain uses a dual-frontier strategy to balance processing of new streams (lagging)
|
|
5537
5584
|
* vs active streams (leading). The ratio adapts based on event pressure.
|
|
5538
5585
|
*
|
|
5539
|
-
* Call `correlate()` before `drain()
|
|
5540
|
-
*
|
|
5586
|
+
* Call `correlate()` before `drain()`. It is not only how dynamic targets
|
|
5587
|
+
* are discovered: a stream is claimable while `at < correlated_at`, and
|
|
5588
|
+
* `correlate` is the only component that raises that mark (#1487), so a
|
|
5589
|
+
* commit no correlate has seen is not drainable — including for static
|
|
5590
|
+
* targets, which were served by a probe of the event log before. For a
|
|
5591
|
+
* higher-level API that handles debouncing, correlation, and signaling
|
|
5592
|
+
* automatically, use {@link settle}.
|
|
5541
5593
|
*
|
|
5542
5594
|
* @param options - Drain configuration — see {@link DrainOptions} for fields
|
|
5543
5595
|
* (`streamLimit`, `eventLimit`, `leaseMillis`).
|
|
@@ -6087,6 +6139,26 @@ var Act = class {
|
|
|
6087
6139
|
* removed, so a later correlate can re-subscribe it (#1398). Restart
|
|
6088
6140
|
* targets keep their row, so only fully-retired streams are forgotten.
|
|
6089
6141
|
*/
|
|
6142
|
+
/**
|
|
6143
|
+
* Advance correlation until the read cursor reaches `until`, and report
|
|
6144
|
+
* where it landed (#1487). Used by the close cycle's safety probe, which
|
|
6145
|
+
* cannot judge a subscription's pending work over events correlate has
|
|
6146
|
+
* not resolved yet.
|
|
6147
|
+
*
|
|
6148
|
+
* Bounded on both ends: it stops as soon as a pass makes no progress (the
|
|
6149
|
+
* log has no more to give), and after {@link CLOSE_CATCH_UP_PASSES}
|
|
6150
|
+
* windows, so a close behind an enormous backlog degrades to skipping the
|
|
6151
|
+
* stream — the documented retryable outcome — instead of scanning the
|
|
6152
|
+
* whole log inside an operator call.
|
|
6153
|
+
*/
|
|
6154
|
+
async _catch_up_correlation(until) {
|
|
6155
|
+
for (let pass = 0; pass < CLOSE_CATCH_UP_PASSES && this._correlate.checkpoint < until; pass++) {
|
|
6156
|
+
const before = this._correlate.checkpoint;
|
|
6157
|
+
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
6158
|
+
if (this._correlate.checkpoint <= before) break;
|
|
6159
|
+
}
|
|
6160
|
+
return this._correlate.checkpoint;
|
|
6161
|
+
}
|
|
6090
6162
|
_forget_closed_subscriptions(result) {
|
|
6091
6163
|
const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
|
|
6092
6164
|
if (retired.length) this._correlate.forget_subscribed(retired);
|
|
@@ -6098,6 +6170,7 @@ var Act = class {
|
|
|
6098
6170
|
const close_actor = { id: "$close", name: "close" };
|
|
6099
6171
|
const result = await run_close_cycle(targets, {
|
|
6100
6172
|
reactive_events_size: this._reactive_events.size,
|
|
6173
|
+
catch_up_correlation: (until) => this._catch_up_correlation(until),
|
|
6101
6174
|
event_to_state: this._event_to_state,
|
|
6102
6175
|
load: this._es.load,
|
|
6103
6176
|
tombstone: this._es.tombstone,
|