@rotorsoft/act 1.33.12 → 1.33.14

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/index.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  sleep,
41
41
  store,
42
42
  validate
43
- } from "./chunk-YZ2NKMVS.js";
43
+ } from "./chunk-MSD67APM.js";
44
44
  import {
45
45
  ActorSchema,
46
46
  CausationEventSchema,
@@ -70,7 +70,7 @@ import {
70
70
  pii_fields,
71
71
  pii_schemas,
72
72
  sensitive
73
- } from "./chunk-URF33KO3.js";
73
+ } from "./chunk-CAL4XEVN.js";
74
74
 
75
75
  // src/signals.ts
76
76
  process.once("SIGINT", async (arg) => {
@@ -1156,7 +1156,6 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
1156
1156
  }
1157
1157
  var CorrelateCycle = class {
1158
1158
  _checkpoint = -1;
1159
- _initialized = false;
1160
1159
  /** Lease holder id; per instance, so a restart never inherits a stale claim. */
1161
1160
  _by = randomUUID();
1162
1161
  _key;
@@ -1210,16 +1209,12 @@ var CorrelateCycle = class {
1210
1209
  arm() {
1211
1210
  this._raised++;
1212
1211
  }
1213
- async init() {
1214
- if (this._initialized) return;
1215
- if (!this._init_promise) {
1216
- this._init_promise = this._run_init().catch((error) => {
1217
- this._init_promise = void 0;
1218
- throw error;
1219
- });
1220
- }
1212
+ async _init() {
1213
+ this._init_promise ??= this._run_init().catch((error) => {
1214
+ this._init_promise = void 0;
1215
+ throw error;
1216
+ });
1221
1217
  await this._init_promise;
1222
- this._initialized = true;
1223
1218
  }
1224
1219
  async _run_init() {
1225
1220
  const { watermark, correlated_at } = await this._cd.subscribe([
@@ -1237,7 +1232,6 @@ var CorrelateCycle = class {
1237
1232
  }
1238
1233
  /** After `restore` replaced the log: forget everything and cold-start again. */
1239
1234
  restart() {
1240
- this._initialized = false;
1241
1235
  this._init_promise = void 0;
1242
1236
  this._checkpoint = -1;
1243
1237
  this._dynamic_subscriptions.clear();
@@ -1261,14 +1255,12 @@ var CorrelateCycle = class {
1261
1255
  return pattern.test(stream);
1262
1256
  }
1263
1257
  /** Scan past the checkpoint, subscribe new targets and raise their work marks. */
1264
- async correlate(query = { after: -1, limit: 10 }, lease = false) {
1265
- await this.init();
1258
+ async correlate(query, lease = false) {
1259
+ await this._init();
1266
1260
  const epoch = this._epoch;
1267
1261
  const idle = () => ({
1268
- subscribed: 0,
1269
- last_id: this._checkpoint,
1270
- marked: 0,
1271
- scanned: false
1262
+ kind: "idle",
1263
+ last_id: this._checkpoint
1272
1264
  });
1273
1265
  if (this._raised === this._lowered) return idle();
1274
1266
  const raised = this._raised;
@@ -1360,6 +1352,7 @@ var CorrelateCycle = class {
1360
1352
  { ...query, after, with_pii: false }
1361
1353
  );
1362
1354
  if (this._declared_lanes.size > 1) await this._recall(correlated);
1355
+ const read = last_id > after;
1363
1356
  if (this._epoch !== epoch) return idle();
1364
1357
  const streams = [];
1365
1358
  for (const [stream, entry] of correlated) {
@@ -1393,11 +1386,11 @@ var CorrelateCycle = class {
1393
1386
  lane
1394
1387
  });
1395
1388
  }
1396
- return { subscribed, last_id, marked, scanned: true };
1389
+ return { kind: "scanned", last_id, subscribed, marked, read };
1397
1390
  }
1398
1391
  this._checkpoint = last_id;
1399
1392
  if (!stopped && found !== query.limit) this._lowered = raised;
1400
- return { subscribed: 0, last_id, marked: 0, scanned: true };
1393
+ return { kind: "scanned", last_id, subscribed: 0, marked: 0, read };
1401
1394
  }
1402
1395
  /**
1403
1396
  * Read the row of each target this scan had no record of (new or evicted),
@@ -2354,6 +2347,14 @@ var EMPTY_DRAIN = {
2354
2347
  acked: [],
2355
2348
  blocked: []
2356
2349
  };
2350
+ function merge_drains(drains) {
2351
+ return {
2352
+ fetched: drains.flatMap((d) => d.fetched),
2353
+ leased: drains.flatMap((d) => d.leased),
2354
+ acked: drains.flatMap((d) => d.acked),
2355
+ blocked: drains.flatMap((d) => d.blocked)
2356
+ };
2357
+ }
2357
2358
  var RETRY = "";
2358
2359
  var DrainController = class {
2359
2360
  /**
@@ -2380,9 +2381,8 @@ var DrainController = class {
2380
2381
  _worker;
2381
2382
  _misrouted = /* @__PURE__ */ new Set();
2382
2383
  _stopped = false;
2383
- /** Never rejects; graceful shutdown waits on it. */
2384
+ /** The pass in flight. Never rejects; graceful shutdown waits on it. */
2384
2385
  _inflight;
2385
- _inflight_done;
2386
2386
  _deps;
2387
2387
  constructor(deps) {
2388
2388
  this._deps = deps;
@@ -2438,10 +2438,17 @@ var DrainController = class {
2438
2438
  const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
2439
2439
  if (this._deps.breaker.state(Date.now()) === "open")
2440
2440
  return EMPTY_DRAIN;
2441
+ const pass = this._pass(streamLimit, eventLimit, leaseMillis).finally(
2442
+ () => {
2443
+ this._inflight = void 0;
2444
+ }
2445
+ );
2446
+ this._inflight = pass;
2447
+ return pass;
2448
+ }
2449
+ /** One claim → fetch → handle → ack pass. Never throws. */
2450
+ async _pass(streamLimit, eventLimit, leaseMillis) {
2441
2451
  try {
2442
- this._inflight = new Promise((done) => {
2443
- this._inflight_done = done;
2444
- });
2445
2452
  const lagging = Math.ceil(streamLimit * this._ratio);
2446
2453
  const leading = streamLimit - lagging;
2447
2454
  const started = Date.now();
@@ -2482,9 +2489,6 @@ var DrainController = class {
2482
2489
  return EMPTY_DRAIN;
2483
2490
  } finally {
2484
2491
  if (!this._stopped) this._defer.schedule();
2485
- this._inflight = void 0;
2486
- this._inflight_done?.();
2487
- this._inflight_done = void 0;
2488
2492
  }
2489
2493
  }
2490
2494
  };
@@ -2852,32 +2856,24 @@ var SettleLoop = class {
2852
2856
  this._timer = void 0;
2853
2857
  const {
2854
2858
  debounceMs: _,
2855
- correlate: correlate_query = {},
2859
+ // Every pass scans from correlation's own checkpoint.
2860
+ correlate: { after: _after, ...correlate_query } = {},
2856
2861
  maxPasses = Infinity,
2857
2862
  ...drain_options
2858
2863
  } = this._next ?? {};
2859
2864
  this._next = void 0;
2860
2865
  this._inflight = (async () => {
2861
- await this._deps.init();
2862
- let settled_drain;
2866
+ const passes = [];
2863
2867
  for (let i = 0; i < maxPasses && !this._stopped; i++) {
2864
- const after_before = this._deps.checkpoint();
2865
- const { subscribed, last_id, scanned } = await this._deps.correlate({
2866
- ...correlate_query,
2867
- after: after_before
2868
- });
2868
+ const pass = await this._deps.correlate(correlate_query);
2869
+ const scanned = pass.kind === "scanned";
2869
2870
  if (scanned) this._deps.breaker.passed();
2870
2871
  const drain = await this._deps.drain(drain_options);
2871
- settled_drain = settled_drain ? {
2872
- fetched: [...settled_drain.fetched, ...drain.fetched],
2873
- leased: [...settled_drain.leased, ...drain.leased],
2874
- acked: [...settled_drain.acked, ...drain.acked],
2875
- blocked: [...settled_drain.blocked, ...drain.blocked]
2876
- } : drain;
2877
- const made_progress = subscribed > 0 || drain.leased.length > 0 || last_id > after_before;
2872
+ passes.push(drain);
2873
+ const made_progress = drain.leased.length > 0 || scanned && (pass.subscribed > 0 || pass.read);
2878
2874
  if (!made_progress) break;
2879
2875
  }
2880
- if (settled_drain) this._deps.on_settled(settled_drain);
2876
+ if (passes.length) this._deps.on_settled(merge_drains(passes));
2881
2877
  })().catch((err) => {
2882
2878
  this._deps.breaker.failed(Date.now(), err);
2883
2879
  }).finally(() => {
@@ -3182,9 +3178,7 @@ var Act = class {
3182
3178
  _build_settle(options) {
3183
3179
  return new SettleLoop(
3184
3180
  {
3185
- init: () => this._scoped(() => this._correlate.init()),
3186
- checkpoint: () => this._correlate.checkpoint,
3187
- correlate: (q) => this._correlate_scanned(
3181
+ correlate: (q) => this._correlate_pass(
3188
3182
  { ...q, limit: q.limit ?? this._poll_limit ?? 100 },
3189
3183
  true
3190
3184
  ),
@@ -3247,15 +3241,16 @@ var Act = class {
3247
3241
  if (running.length === 0 && !settling) return;
3248
3242
  const grace = grace_ms ?? this._derive_grace_ms(running, !!settling);
3249
3243
  if (grace <= 0) return;
3250
- const inflight = running.map((c) => c.inflight);
3251
- if (settling) inflight.push(settling);
3252
3244
  let timer;
3253
3245
  const budget = new Promise((resolve) => {
3254
3246
  timer = setTimeout(resolve, grace);
3255
3247
  timer.unref();
3256
3248
  });
3257
3249
  try {
3258
- await Promise.race([Promise.all(inflight), budget]);
3250
+ await Promise.race([
3251
+ Promise.all([...running.map((c) => c.inflight), settling]),
3252
+ budget
3253
+ ]);
3259
3254
  } finally {
3260
3255
  clearTimeout(timer);
3261
3256
  }
@@ -3514,27 +3509,20 @@ var Act = class {
3514
3509
  }
3515
3510
  /** In parallel, so a slow lane never holds up a fast one. */
3516
3511
  async _drain_all(options) {
3517
- const results = await Promise.all(
3518
- [...this._drain_controllers.values()].map((c) => c.drain(options))
3512
+ const merged = merge_drains(
3513
+ await Promise.all(
3514
+ [...this._drain_controllers.values()].map((c) => c.drain(options))
3515
+ )
3519
3516
  );
3520
- const fetched = [];
3521
- const leased = [];
3522
- const acked = [];
3523
- const blocked = [];
3524
- for (const r of results) {
3525
- fetched.push(
3526
- ...r.fetched.map((f) => ({
3527
- ...f,
3528
- events: f.events.map(
3529
- (e) => this.registry.query_gate(e.name)(e)
3530
- )
3531
- }))
3532
- );
3533
- leased.push(...r.leased);
3534
- acked.push(...r.acked);
3535
- blocked.push(...r.blocked);
3536
- }
3537
- return { fetched, leased, acked, blocked };
3517
+ return {
3518
+ ...merged,
3519
+ fetched: merged.fetched.map((f) => ({
3520
+ ...f,
3521
+ events: f.events.map(
3522
+ (e) => this.registry.query_gate(e.name)(e)
3523
+ )
3524
+ }))
3525
+ };
3538
3526
  }
3539
3527
  /**
3540
3528
  * Scans committed events from the correlation checkpoint, subscribes the
@@ -3553,16 +3541,20 @@ var Act = class {
3553
3541
  * @see {@link start_correlations} for automatic periodic correlation
3554
3542
  */
3555
3543
  async correlate(query = { after: -1, limit: 10 }) {
3556
- const { subscribed, last_id } = await this._correlate_scanned(query);
3557
- return { subscribed, last_id };
3544
+ const pass = await this._correlate_pass(query);
3545
+ return {
3546
+ subscribed: pass.kind === "scanned" ? pass.subscribed : 0,
3547
+ last_id: pass.last_id
3548
+ };
3558
3549
  }
3559
- /** `correlate` plus `scanned`, which settle needs for the breaker. */
3560
- async _correlate_scanned(query, lease = false) {
3561
- if (!this._drain) return { subscribed: 0, last_id: -1, scanned: false };
3550
+ /** One correlate pass; arms every lane when it found work. */
3551
+ async _correlate_pass(query, lease = false) {
3552
+ if (!this._drain) return { kind: "idle", last_id: -1 };
3562
3553
  return this._scoped(async () => {
3563
- const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query, lease);
3564
- if (subscribed > 0 || marked > 0) this._arm_all();
3565
- return { subscribed, last_id, scanned };
3554
+ const pass = await this._correlate.correlate(query, lease);
3555
+ if (pass.kind === "scanned" && (pass.subscribed > 0 || pass.marked > 0))
3556
+ this._arm_all();
3557
+ return pass;
3566
3558
  });
3567
3559
  }
3568
3560
  /**