@rotorsoft/act 1.32.24 → 1.32.25
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 -155
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +10 -190
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/internal/close-cycle.d.ts +0 -8
- package/dist/@types/internal/close-cycle.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +1 -1
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/{chunk-OG5O36AJ.js → chunk-QXW3JV2H.js} +21 -240
- package/dist/chunk-QXW3JV2H.js.map +1 -0
- package/dist/index.cjs +96 -482
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +78 -245
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +20 -239
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-OG5O36AJ.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-QXW3JV2H.js";
|
|
30
30
|
import {
|
|
31
31
|
ActorSchema,
|
|
32
32
|
CausationEventSchema,
|
|
@@ -1355,37 +1355,34 @@ async function windowed_prune_pending(stream, before, max_id) {
|
|
|
1355
1355
|
}
|
|
1356
1356
|
async function probe_min_watermarks(streams, page_size, checkpoint) {
|
|
1357
1357
|
const min_at = /* @__PURE__ */ new Map();
|
|
1358
|
-
const
|
|
1359
|
-
|
|
1360
|
-
|
|
1358
|
+
const matches = source_matcher();
|
|
1359
|
+
await walk_streams(
|
|
1360
|
+
store(),
|
|
1361
|
+
(position) => {
|
|
1362
|
+
const pending = position.correlated_at === void 0 || position.at < position.correlated_at;
|
|
1363
|
+
const cap = pending ? position.at : Math.max(position.at, checkpoint);
|
|
1364
|
+
for (const stream of streams) {
|
|
1365
|
+
if (matches(position.source, stream)) {
|
|
1366
|
+
const prev = min_at.get(stream);
|
|
1367
|
+
if (prev === void 0 || cap < prev) min_at.set(stream, cap);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
},
|
|
1371
|
+
{ limit: page_size, source_matches: streams }
|
|
1372
|
+
);
|
|
1373
|
+
return min_at;
|
|
1374
|
+
}
|
|
1375
|
+
function source_matcher() {
|
|
1376
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
1377
|
+
return (source, stream) => {
|
|
1378
|
+
if (!source) return true;
|
|
1379
|
+
let re = compiled.get(source);
|
|
1361
1380
|
if (!re) {
|
|
1362
1381
|
re = new RegExp(source);
|
|
1363
|
-
|
|
1382
|
+
compiled.set(source, re);
|
|
1364
1383
|
}
|
|
1365
|
-
return re;
|
|
1384
|
+
return re.test(stream);
|
|
1366
1385
|
};
|
|
1367
|
-
let after;
|
|
1368
|
-
for (; ; ) {
|
|
1369
|
-
let last;
|
|
1370
|
-
const { count } = await store().query_streams(
|
|
1371
|
-
(position) => {
|
|
1372
|
-
last = position.stream;
|
|
1373
|
-
const source_re = position.source ? get_regex(position.source) : void 0;
|
|
1374
|
-
const pending = position.correlated_at === void 0 || position.at < position.correlated_at;
|
|
1375
|
-
const cap = pending ? position.at : Math.max(position.at, checkpoint);
|
|
1376
|
-
for (const stream of streams) {
|
|
1377
|
-
if (!source_re || source_re.test(stream)) {
|
|
1378
|
-
const prev = min_at.get(stream);
|
|
1379
|
-
if (prev === void 0 || cap < prev) min_at.set(stream, cap);
|
|
1380
|
-
}
|
|
1381
|
-
}
|
|
1382
|
-
},
|
|
1383
|
-
{ after, limit: page_size, source_matches: streams }
|
|
1384
|
-
);
|
|
1385
|
-
if (count < page_size) break;
|
|
1386
|
-
after = last;
|
|
1387
|
-
}
|
|
1388
|
-
return min_at;
|
|
1389
1386
|
}
|
|
1390
1387
|
async function scan_stream_heads(streams) {
|
|
1391
1388
|
const stats = await store().query_stats(streams, {
|
|
@@ -1419,36 +1416,20 @@ async function partition_by_safety(stream_info, reactive_events_size, skipped, p
|
|
|
1419
1416
|
if (checkpoint < info.max_id) uncorrelated.add(stream);
|
|
1420
1417
|
}
|
|
1421
1418
|
const pending_set = /* @__PURE__ */ new Set();
|
|
1422
|
-
const
|
|
1423
|
-
const get_regex = (source) => {
|
|
1424
|
-
let re = source_regex.get(source);
|
|
1425
|
-
if (!re) {
|
|
1426
|
-
re = new RegExp(source);
|
|
1427
|
-
source_regex.set(source, re);
|
|
1428
|
-
}
|
|
1429
|
-
return re;
|
|
1430
|
-
};
|
|
1419
|
+
const matches = source_matcher();
|
|
1431
1420
|
const targets = [...stream_info.keys()];
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
(
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
},
|
|
1447
|
-
{ after, limit: page_size, source_matches: targets }
|
|
1448
|
-
);
|
|
1449
|
-
if (count < page_size) break;
|
|
1450
|
-
after = last;
|
|
1451
|
-
}
|
|
1421
|
+
await walk_streams(
|
|
1422
|
+
store(),
|
|
1423
|
+
(position) => {
|
|
1424
|
+
const has_work = position.correlated_at !== void 0 && position.at < position.correlated_at;
|
|
1425
|
+
if (!has_work) return;
|
|
1426
|
+
for (const [stream, info] of stream_info) {
|
|
1427
|
+
if (matches(position.source, stream) && position.at < info.max_id)
|
|
1428
|
+
pending_set.add(stream);
|
|
1429
|
+
}
|
|
1430
|
+
},
|
|
1431
|
+
{ limit: page_size, source_matches: targets }
|
|
1432
|
+
);
|
|
1452
1433
|
const safe = [];
|
|
1453
1434
|
for (const [stream] of stream_info) {
|
|
1454
1435
|
if (pending_set.has(stream) || uncorrelated.has(stream))
|
|
@@ -2132,6 +2113,18 @@ var validating_patch = (me, state2, partial, event) => {
|
|
|
2132
2113
|
);
|
|
2133
2114
|
};
|
|
2134
2115
|
var DEFAULT_BATCH = 500;
|
|
2116
|
+
var cache_put = (stream, entry) => cache().set(stream, entry).catch(
|
|
2117
|
+
(err) => log().warn(
|
|
2118
|
+
err,
|
|
2119
|
+
"cache set skipped; the next load re-folds from the store"
|
|
2120
|
+
)
|
|
2121
|
+
);
|
|
2122
|
+
var cache_drop = (stream) => cache().invalidate(stream).catch(
|
|
2123
|
+
(err) => log().warn(
|
|
2124
|
+
err,
|
|
2125
|
+
"cache invalidate skipped; a stale checkpoint re-folds from its event_id"
|
|
2126
|
+
)
|
|
2127
|
+
);
|
|
2135
2128
|
async function snap(snapshot) {
|
|
2136
2129
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
2137
2130
|
try {
|
|
@@ -2334,19 +2327,14 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2334
2327
|
);
|
|
2335
2328
|
const head_is_tombstone = event?.name === TOMBSTONE_EVENT;
|
|
2336
2329
|
if (replayed > 0 && !time_travel && event && !me.pii_aware && !head_is_tombstone) {
|
|
2337
|
-
await
|
|
2330
|
+
await cache_put(stream, {
|
|
2338
2331
|
stream,
|
|
2339
2332
|
state: state2,
|
|
2340
2333
|
version: event.version,
|
|
2341
2334
|
event_id: event.id,
|
|
2342
2335
|
patches,
|
|
2343
2336
|
snaps
|
|
2344
|
-
})
|
|
2345
|
-
(err) => log().warn(
|
|
2346
|
-
err,
|
|
2347
|
-
"cache set skipped; the next load re-folds from the store"
|
|
2348
|
-
)
|
|
2349
|
-
);
|
|
2337
|
+
});
|
|
2350
2338
|
}
|
|
2351
2339
|
return {
|
|
2352
2340
|
event,
|
|
@@ -2434,14 +2422,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2434
2422
|
reactingTo ? expectedVersion : expected
|
|
2435
2423
|
);
|
|
2436
2424
|
} catch (error) {
|
|
2437
|
-
if (error instanceof ConcurrencyError)
|
|
2438
|
-
cache().invalidate(stream).catch(
|
|
2439
|
-
(err) => log().warn(
|
|
2440
|
-
err,
|
|
2441
|
-
"cache invalidate skipped; a stale checkpoint re-folds from its event_id"
|
|
2442
|
-
)
|
|
2443
|
-
);
|
|
2444
|
-
}
|
|
2425
|
+
if (error instanceof ConcurrencyError) void cache_drop(stream);
|
|
2445
2426
|
throw error;
|
|
2446
2427
|
}
|
|
2447
2428
|
let { state: state2, patches } = snapshot;
|
|
@@ -2468,26 +2449,15 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
|
|
|
2468
2449
|
const snap_event = snapped ? await snap(last) : void 0;
|
|
2469
2450
|
if (!me.pii_aware) {
|
|
2470
2451
|
if (contiguous)
|
|
2471
|
-
|
|
2452
|
+
void cache_put(stream, {
|
|
2472
2453
|
stream,
|
|
2473
2454
|
state: last.state,
|
|
2474
2455
|
version: snap_event?.version ?? last.event.version,
|
|
2475
2456
|
event_id: snap_event?.id ?? last.event.id,
|
|
2476
2457
|
patches: snap_event ? 0 : last.patches,
|
|
2477
2458
|
snaps: snap_event ? last.snaps + 1 : last.snaps
|
|
2478
|
-
})
|
|
2479
|
-
|
|
2480
|
-
err,
|
|
2481
|
-
"cache set skipped; the next load re-folds from the store"
|
|
2482
|
-
)
|
|
2483
|
-
);
|
|
2484
|
-
else
|
|
2485
|
-
cache().invalidate(stream).catch(
|
|
2486
|
-
(err) => log().warn(
|
|
2487
|
-
err,
|
|
2488
|
-
"cache invalidate skipped; a stale checkpoint re-folds from its event_id"
|
|
2489
|
-
)
|
|
2490
|
-
);
|
|
2459
|
+
});
|
|
2460
|
+
else void cache_drop(stream);
|
|
2491
2461
|
}
|
|
2492
2462
|
return snapshots;
|
|
2493
2463
|
} catch (error) {
|
|
@@ -3250,41 +3220,23 @@ function validate_only_lanes(options, lanes) {
|
|
|
3250
3220
|
}
|
|
3251
3221
|
var Act = class {
|
|
3252
3222
|
_emitter = new EventEmitter();
|
|
3253
|
-
/** Circuit breaker shared by all drain lanes. */
|
|
3254
3223
|
_breaker;
|
|
3255
|
-
/** Whether this instance subscribes to `Store.notify`. */
|
|
3256
3224
|
_listen;
|
|
3257
|
-
/** Whether this instance runs reactions (drain, settle, correlate). */
|
|
3258
3225
|
_drain;
|
|
3259
|
-
/** Event names with at least one registered reaction (computed at build time) */
|
|
3260
3226
|
_reactive_events;
|
|
3261
|
-
/** One DrainController per active lane, keyed by lane name. */
|
|
3262
3227
|
_drain_controllers;
|
|
3263
|
-
/** Correlation: lazy init and the scan that marks work. */
|
|
3264
3228
|
_correlate;
|
|
3265
|
-
/** Debounced correlate→drain catch-up loop. */
|
|
3266
3229
|
_settle;
|
|
3267
|
-
/** Timer behind {@link start_correlations}. */
|
|
3268
3230
|
_poll;
|
|
3269
3231
|
/** The deprecated `start_correlations` callback, while the poll runs. */
|
|
3270
3232
|
_on_discovered;
|
|
3271
3233
|
/**
|
|
3272
|
-
*
|
|
3273
|
-
*
|
|
3274
|
-
* itself may be async (the PG adapter checks out a dedicated client
|
|
3275
|
-
* and runs `LISTEN` before resolving). Resolves to `undefined` when
|
|
3276
|
-
* the store doesn't implement `notify` or there are no registered
|
|
3277
|
-
* reactions.
|
|
3278
|
-
*
|
|
3279
|
-
* **Contract:** the configured store must be injected via
|
|
3280
|
-
* {@link store}`(adapter)` *before* calling `act()...build()`. The
|
|
3281
|
-
* orchestrator wires notify against whatever store is current at
|
|
3282
|
-
* construction time — late injection after build is unsupported.
|
|
3234
|
+
* Wired at construction against the store current then, so a store
|
|
3235
|
+
* injected after `build()` is never notified.
|
|
3283
3236
|
*/
|
|
3284
3237
|
_notify_disposer;
|
|
3285
3238
|
/** Public registry — kept as-is per the no-prefix-on-public convention. */
|
|
3286
3239
|
registry;
|
|
3287
|
-
/** Map of state name → state definition; populated by the builder. */
|
|
3288
3240
|
_states;
|
|
3289
3241
|
/**
|
|
3290
3242
|
* Emit a lifecycle event. The payload type is inferred from the event name
|
|
@@ -3310,15 +3262,9 @@ var Act = class {
|
|
|
3310
3262
|
return listeners.length > 0;
|
|
3311
3263
|
}
|
|
3312
3264
|
/**
|
|
3313
|
-
* The
|
|
3314
|
-
*
|
|
3315
|
-
*
|
|
3316
|
-
* `breaker.failed(now, error)` and never log or emit themselves.
|
|
3317
|
-
*
|
|
3318
|
-
* The emit is guarded against Node's `EventEmitter` contract that an
|
|
3319
|
-
* unhandled `"error"` emission is rethrown (which would crash the process
|
|
3320
|
-
* from inside the drain catch); logging is unconditional so failures are
|
|
3321
|
-
* never silent.
|
|
3265
|
+
* The one place a store failure is logged and emitted (the breaker's
|
|
3266
|
+
* `on_error`). The emit is guarded: an unhandled `"error"` emission
|
|
3267
|
+
* would crash the process.
|
|
3322
3268
|
*/
|
|
3323
3269
|
_emit_error(error, circuit) {
|
|
3324
3270
|
this._logger.error(error);
|
|
@@ -3340,58 +3286,29 @@ var Act = class {
|
|
|
3340
3286
|
this._emitter.off(event, listener);
|
|
3341
3287
|
return this;
|
|
3342
3288
|
}
|
|
3343
|
-
/** Batch handlers for static-target projections (target → handler) */
|
|
3344
3289
|
_batch_handlers;
|
|
3345
|
-
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
3346
3290
|
_es;
|
|
3347
|
-
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
3348
3291
|
_cd;
|
|
3349
|
-
/**
|
|
3350
|
-
* Event-name → owning state, computed at build time. The duplicate-event
|
|
3351
|
-
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
3352
|
-
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
3353
|
-
* set when seeding a `restart` snapshot in multi-state apps.
|
|
3354
|
-
*/
|
|
3292
|
+
/** Unambiguous: merge rejects an event name declared by two states. */
|
|
3355
3293
|
_event_to_state;
|
|
3356
|
-
/**
|
|
3357
|
-
* Event-name → lane fan-in for selective arming. Built by
|
|
3358
|
-
* `classify_registry` once per build. `"all"` means at least one of
|
|
3359
|
-
* the event's reactions is a dynamic resolver (lane opaque until
|
|
3360
|
-
* runtime); a `Set<string>` lists the static lanes only that event's
|
|
3361
|
-
* reactions target.
|
|
3362
|
-
*/
|
|
3294
|
+
/** Lanes to arm per event; `"all"` when a dynamic resolver hides the lane. */
|
|
3363
3295
|
_event_to_lanes;
|
|
3364
|
-
/** What `internal/audit.ts` needs, built once at construction. */
|
|
3365
3296
|
_audit_deps;
|
|
3366
|
-
/** Logger resolved at construction time (after user port configuration) */
|
|
3367
3297
|
_logger = log();
|
|
3368
3298
|
/** This Act's ports: its own bag, or the singleton adapters. */
|
|
3369
3299
|
_ports;
|
|
3370
|
-
/**
|
|
3371
|
-
* Runs a body inside this Act's ports frame, so internal
|
|
3372
|
-
* `store()`/`cache()` calls resolve to `_ports`.
|
|
3373
|
-
*/
|
|
3300
|
+
/** Runs a body so internal `store()`/`cache()` calls resolve to `_ports`. */
|
|
3374
3301
|
_scoped;
|
|
3375
|
-
/**
|
|
3376
|
-
* Correlation-id generator for originating actions. Bound at
|
|
3377
|
-
* construction from `options.correlator ?? default_correlator`. The
|
|
3378
|
-
* `do()` path passes this into the `_es.action` closure; close-cycle
|
|
3379
|
-
* uses it via {@link close_correlation}.
|
|
3380
|
-
*/
|
|
3381
3302
|
_correlator;
|
|
3382
|
-
|
|
3383
|
-
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
3303
|
+
// Bound once, reused by every reaction payload.
|
|
3384
3304
|
_bound_do = this.do.bind(this);
|
|
3385
3305
|
_bound_load = this.load.bind(this);
|
|
3386
3306
|
_bound_query = this.query.bind(this);
|
|
3387
3307
|
_bound_query_array = this.query_array.bind(this);
|
|
3388
3308
|
_bound_forget = this.forget.bind(this);
|
|
3389
|
-
/** Reaction dispatchers built once and handed to run_drain_cycle each cycle. */
|
|
3390
3309
|
_handle;
|
|
3391
3310
|
_handle_batch;
|
|
3392
|
-
/** Declared drain lanes. */
|
|
3393
3311
|
_lanes;
|
|
3394
|
-
/** Serializes close critical sections per stream. */
|
|
3395
3312
|
_close_lock = new CloseLock();
|
|
3396
3313
|
/** Every lane a controller can exist for: `"default"` plus each declared one. */
|
|
3397
3314
|
_declared_lanes;
|
|
@@ -3399,26 +3316,7 @@ var Act = class {
|
|
|
3399
3316
|
get lanes() {
|
|
3400
3317
|
return this._lanes;
|
|
3401
3318
|
}
|
|
3402
|
-
/**
|
|
3403
|
-
* Create a new Act orchestrator. Prefer the {@link act} builder over
|
|
3404
|
-
* direct construction — `act()...build()` wires the registry, merges
|
|
3405
|
-
* partial states, and collects batch handlers from registered slices
|
|
3406
|
-
* and projections in one pass.
|
|
3407
|
-
*
|
|
3408
|
-
* @param registry Schemas for every event and action across registered states
|
|
3409
|
-
* @param states Merged map of state name → state definition
|
|
3410
|
-
* @param batch_handlers Static-target projection batch handlers (target → handler)
|
|
3411
|
-
* @param options Tuning knobs — see {@link ActOptions}
|
|
3412
|
-
* @param lanes Declared drain lanes. The builder collects
|
|
3413
|
-
* these from `.withLane(...)` calls; one `DrainController` runs
|
|
3414
|
-
* per lane, plus the implicit `"default"`.
|
|
3415
|
-
* @param patch_fn The per-event patch step selected once by the builder
|
|
3416
|
-
* from `ActOptions.validateFoldedState` — `bare_patch` by
|
|
3417
|
-
* default, `validating_patch` when the flag is on. The builder uses
|
|
3418
|
-
* the same value for its projection-fold handlers, so there is a
|
|
3419
|
-
* single selection site. Defaults to `bare_patch` for direct
|
|
3420
|
-
* construction.
|
|
3421
|
-
*/
|
|
3319
|
+
/** Use the {@link act} builder rather than constructing directly. */
|
|
3422
3320
|
constructor(registry, states = /* @__PURE__ */ new Map(), batch_handlers = /* @__PURE__ */ new Map(), options = {}, lanes = [], patch_fn = bare_patch) {
|
|
3423
3321
|
this.registry = registry;
|
|
3424
3322
|
this._states = states;
|
|
@@ -3469,11 +3367,7 @@ var Act = class {
|
|
|
3469
3367
|
this._notify_disposer = this._wire_notify(this._ports.store);
|
|
3470
3368
|
register_weak_disposer(new WeakRef(this), (self) => self.shutdown());
|
|
3471
3369
|
}
|
|
3472
|
-
/**
|
|
3473
|
-
* Circuit breaker shared by every store-polling loop (drain, the settle
|
|
3474
|
-
* correlate, autoclose). Validates the knobs eagerly so out-of-range
|
|
3475
|
-
* values throw at build time, not on the first cycle tick.
|
|
3476
|
-
*/
|
|
3370
|
+
/** Shared by drain, settle and autoclose; bad knobs throw at build. */
|
|
3477
3371
|
_build_breaker(options) {
|
|
3478
3372
|
return new CircuitBreaker(
|
|
3479
3373
|
resolveCircuitBreakerConfig(options.circuitBreaker),
|
|
@@ -3487,11 +3381,7 @@ var Act = class {
|
|
|
3487
3381
|
}
|
|
3488
3382
|
);
|
|
3489
3383
|
}
|
|
3490
|
-
/**
|
|
3491
|
-
* One DrainController per active lane, plus the implicit "default" unless
|
|
3492
|
-
* `onlyLanes` excludes it. Apps that declare no lanes pass no lane filter
|
|
3493
|
-
* to `claim`.
|
|
3494
|
-
*/
|
|
3384
|
+
/** Apps that declare no lanes pass no lane filter to `claim`. */
|
|
3495
3385
|
_build_drain_controllers(options, lanes) {
|
|
3496
3386
|
const all_lanes = this._declared_lanes;
|
|
3497
3387
|
const only_set = options.onlyLanes && options.onlyLanes.length > 0 ? new Set(options.onlyLanes) : void 0;
|
|
@@ -3527,11 +3417,7 @@ var Act = class {
|
|
|
3527
3417
|
}
|
|
3528
3418
|
return controllers;
|
|
3529
3419
|
}
|
|
3530
|
-
/**
|
|
3531
|
-
* Startup advisory naming declared lanes that `onlyLanes` leaves without a
|
|
3532
|
-
* controller here. One process can't check that the cluster covers every
|
|
3533
|
-
* lane, so it reports its own gaps.
|
|
3534
|
-
*/
|
|
3420
|
+
/** One process can't see the cluster, so it reports its own lane gaps. */
|
|
3535
3421
|
_advise_orphaned_lanes(options) {
|
|
3536
3422
|
if (!options.onlyLanes || options.onlyLanes.length === 0) return;
|
|
3537
3423
|
const active = new Set(this._drain_controllers.keys());
|
|
@@ -3553,11 +3439,6 @@ var Act = class {
|
|
|
3553
3439
|
routed_events: new Set(this._event_to_lanes.keys())
|
|
3554
3440
|
};
|
|
3555
3441
|
}
|
|
3556
|
-
/**
|
|
3557
|
-
* Correlate cycle over the classified registry. The cold-start callback
|
|
3558
|
-
* arms every controller — historical events may need processing —
|
|
3559
|
-
* except on writer-only instances (`drain: false`).
|
|
3560
|
-
*/
|
|
3561
3442
|
_build_correlate(options, classification) {
|
|
3562
3443
|
return new CorrelateCycle({
|
|
3563
3444
|
registry: this.registry,
|
|
@@ -3575,12 +3456,7 @@ var Act = class {
|
|
|
3575
3456
|
on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
|
|
3576
3457
|
});
|
|
3577
3458
|
}
|
|
3578
|
-
/**
|
|
3579
|
-
* At cold start, re-seed each lane controller's local wake from the
|
|
3580
|
-
* persisted future `deferred_at`, so an idle deferred stream (an
|
|
3581
|
-
* autoclose waiting out its window) still fires on time. Streams on lanes
|
|
3582
|
-
* this instance doesn't run are skipped.
|
|
3583
|
-
*/
|
|
3459
|
+
/** Cold start: an idle deferred stream still fires on time after a restart. */
|
|
3584
3460
|
async _seed_persisted_defers() {
|
|
3585
3461
|
const now = Date.now();
|
|
3586
3462
|
await walk_streams(store(), (pos) => {
|
|
@@ -3589,7 +3465,6 @@ var Act = class {
|
|
|
3589
3465
|
controller?.seed_defer(pos.stream, pos.deferred_at);
|
|
3590
3466
|
});
|
|
3591
3467
|
}
|
|
3592
|
-
/** Settle loop driving correlate + drain to quiescence. */
|
|
3593
3468
|
_build_settle(options) {
|
|
3594
3469
|
return new SettleLoop(
|
|
3595
3470
|
{
|
|
@@ -3603,7 +3478,6 @@ var Act = class {
|
|
|
3603
3478
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
3604
3479
|
);
|
|
3605
3480
|
}
|
|
3606
|
-
/** True after the first `shutdown()` call. Guards idempotency. */
|
|
3607
3481
|
_shutdown_promise;
|
|
3608
3482
|
/**
|
|
3609
3483
|
* Per-instance teardown: stop scheduling new work, give drain cycles
|
|
@@ -3643,14 +3517,8 @@ var Act = class {
|
|
|
3643
3517
|
return this._shutdown_promise;
|
|
3644
3518
|
}
|
|
3645
3519
|
/**
|
|
3646
|
-
*
|
|
3647
|
-
*
|
|
3648
|
-
* its own errors), so this never throws.
|
|
3649
|
-
*
|
|
3650
|
-
* An omitted budget is the largest `leaseMillis` among lanes with a cycle
|
|
3651
|
-
* in flight (the drain's fallback for lanes that pinned none, or pinned
|
|
3652
|
-
* 0, which is the same as none), with an in-flight settle counting on the
|
|
3653
|
-
* fallback, capped at the maximum. Idle lanes don't count.
|
|
3520
|
+
* The largest `leaseMillis` among lanes with a cycle in flight (the
|
|
3521
|
+
* default for none or 0, and for a running settle), capped.
|
|
3654
3522
|
*/
|
|
3655
3523
|
_derive_grace_ms(running, settling) {
|
|
3656
3524
|
let max = settling ? DEFAULT_LEASE_MILLIS : 0;
|
|
@@ -3679,12 +3547,7 @@ var Act = class {
|
|
|
3679
3547
|
clearTimeout(timer);
|
|
3680
3548
|
}
|
|
3681
3549
|
}
|
|
3682
|
-
/**
|
|
3683
|
-
* Subscribe to {@link Store.notify} when both the store and the
|
|
3684
|
-
* registry support it. Returns the disposer (or `undefined` when no
|
|
3685
|
-
* subscription was made). Errors during subscription are logged but
|
|
3686
|
-
* never thrown — `notify` is a hint, not a contract.
|
|
3687
|
-
*/
|
|
3550
|
+
/** Failures are logged, never thrown: `notify` is a hint. */
|
|
3688
3551
|
async _wire_notify(s) {
|
|
3689
3552
|
if (this._reactive_events.size === 0) return void 0;
|
|
3690
3553
|
if (!s.notify) return void 0;
|
|
@@ -3996,20 +3859,11 @@ var Act = class {
|
|
|
3996
3859
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
3997
3860
|
return this._scoped(() => this._drain_all(options));
|
|
3998
3861
|
}
|
|
3999
|
-
/** Arm every active lane controller. */
|
|
4000
3862
|
_arm_all() {
|
|
4001
3863
|
this._correlate.arm();
|
|
4002
3864
|
for (const c of this._drain_controllers.values()) c.arm();
|
|
4003
3865
|
}
|
|
4004
|
-
/**
|
|
4005
|
-
* Arm only the lane controllers whose reactions match the supplied
|
|
4006
|
-
* event names (selective arming). Events with any dynamic
|
|
4007
|
-
* resolver fall back to `_arm_all()` via the `"all"` sentinel — the
|
|
4008
|
-
* resolver's lane isn't known until correlate runs the function.
|
|
4009
|
-
* Events with no reactions are skipped; `_event_to_lanes` doesn't
|
|
4010
|
-
* carry them. Returns true when any controller was armed (used by
|
|
4011
|
-
* the notify handler to decide whether to schedule a settle).
|
|
4012
|
-
*/
|
|
3866
|
+
/** Arms only the lanes these events react on; true when any was armed. */
|
|
4013
3867
|
_arm_for_event_names(names) {
|
|
4014
3868
|
const to_arm = /* @__PURE__ */ new Set();
|
|
4015
3869
|
for (const name of names) {
|
|
@@ -4026,14 +3880,7 @@ var Act = class {
|
|
|
4026
3880
|
for (const lane of to_arm) this._drain_controllers.get(lane)?.arm();
|
|
4027
3881
|
return true;
|
|
4028
3882
|
}
|
|
4029
|
-
/**
|
|
4030
|
-
*
|
|
4031
|
-
* Parallel — not sequential — so a slow lane's in-flight handler does
|
|
4032
|
-
* not block a fast lane's claim/dispatch/ack cycle. Each controller's
|
|
4033
|
-
* `claim()` is independent (filtered by lane); the store's
|
|
4034
|
-
* `SKIP LOCKED` keeps cross-controller races safe. Lifecycle events
|
|
4035
|
-
* (`acked`, `blocked`) may interleave by lane — listeners filter via
|
|
4036
|
-
* `lease.lane`. */
|
|
3883
|
+
/** In parallel, so a slow lane never holds up a fast one. */
|
|
4037
3884
|
async _drain_all(options) {
|
|
4038
3885
|
const results = await Promise.all(
|
|
4039
3886
|
[...this._drain_controllers.values()].map((c) => c.drain(options))
|
|
@@ -4106,14 +3953,7 @@ var Act = class {
|
|
|
4106
3953
|
const { subscribed, last_id } = await this._correlate_scanned(query);
|
|
4107
3954
|
return { subscribed, last_id };
|
|
4108
3955
|
}
|
|
4109
|
-
/**
|
|
4110
|
-
* `correlate` plus whether the pass actually read the store.
|
|
4111
|
-
*
|
|
4112
|
-
* The settle loop needs that extra bit to decide whether the pass carries a
|
|
4113
|
-
* circuit-breaker health signal, and a disarmed pass carries none. It stays
|
|
4114
|
-
* internal rather than widening the public `correlate` return, which is
|
|
4115
|
-
* charter-covered and has no use for it.
|
|
4116
|
-
*/
|
|
3956
|
+
/** `correlate` plus `scanned`, which settle needs for the breaker. */
|
|
4117
3957
|
async _correlate_scanned(query, lease = false) {
|
|
4118
3958
|
if (!this._drain) return { subscribed: 0, last_id: -1, scanned: false };
|
|
4119
3959
|
return this._scoped(async () => {
|
|
@@ -4454,11 +4294,7 @@ var Act = class {
|
|
|
4454
4294
|
async prioritize(filter, priority) {
|
|
4455
4295
|
return this._scoped(() => store().prioritize(filter, priority));
|
|
4456
4296
|
}
|
|
4457
|
-
/**
|
|
4458
|
-
* Run one close: the cycle with this Act's ports, then emit `closed`. Shared by `close()` and the drain's
|
|
4459
|
-
* reaction-requested closes. The close actor lets a custom correlator
|
|
4460
|
-
* still tag tenant context or trace ids.
|
|
4461
|
-
*/
|
|
4297
|
+
/** Shared by `close()` and reaction-requested closes. */
|
|
4462
4298
|
async _run_close(targets) {
|
|
4463
4299
|
const close_actor = { id: "$close", name: "close" };
|
|
4464
4300
|
const result = await run_close_cycle(targets, {
|
|
@@ -4539,10 +4375,7 @@ var Act = class {
|
|
|
4539
4375
|
*/
|
|
4540
4376
|
async close(targets) {
|
|
4541
4377
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
4542
|
-
return this._scoped(
|
|
4543
|
-
await this.correlate({ limit: CLOSE_CATCH_UP_LIMIT });
|
|
4544
|
-
return this._run_close(targets);
|
|
4545
|
-
});
|
|
4378
|
+
return this._scoped(() => this._run_close(targets));
|
|
4546
4379
|
}
|
|
4547
4380
|
/**
|
|
4548
4381
|
* Debounced, non-blocking correlate→drain cycle.
|