@rotorsoft/act 1.23.31 → 1.23.33

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
@@ -1311,6 +1311,19 @@ async function run_close_cycle(targets, deps) {
1311
1311
  if (!full.length) return { truncated: windowed_result, skipped };
1312
1312
  const streams = full.map((t) => t.stream);
1313
1313
  const stream_info = await scan_stream_heads(streams);
1314
+ for (const target of full) {
1315
+ if (!target.restart) continue;
1316
+ const info = stream_info.get(target.stream);
1317
+ if (!info) continue;
1318
+ const owner = deps.event_to_state.get(info.last_event_name);
1319
+ if (owner?.pii_aware) {
1320
+ deps.logger.error(
1321
+ `Refusing to close "${target.stream}" with restart: state "${owner.name}" carries sensitive fields, so a restart seed would persist redacted values while deleting the originals. Close it without restart to retire the stream, or leave it open.`
1322
+ );
1323
+ skipped.push(target.stream);
1324
+ stream_info.delete(target.stream);
1325
+ }
1326
+ }
1314
1327
  const safe = await partition_by_safety(
1315
1328
  stream_info,
1316
1329
  deps.reactive_events_size,
@@ -1417,14 +1430,19 @@ async function scan_stream_heads(streams) {
1417
1430
  const stats = await store().query_stats(streams, {
1418
1431
  exclude: [SNAP_EVENT]
1419
1432
  });
1433
+ const domain_heads = await store().query_stats(streams, {
1434
+ exclude: [SNAP_EVENT, TOMBSTONE_EVENT]
1435
+ });
1420
1436
  const true_heads = await store().query_stats(streams, {});
1421
1437
  const out = /* @__PURE__ */ new Map();
1422
- for (const [stream, { head }] of stats) {
1423
- if (head.name === TOMBSTONE_EVENT) continue;
1438
+ for (const [stream, { head }] of domain_heads) {
1439
+ const marker_head = stats.get(stream)?.head;
1440
+ const interrupted = marker_head?.name === TOMBSTONE_EVENT;
1424
1441
  out.set(stream, {
1425
1442
  max_id: head.id,
1426
1443
  version: true_heads.get(stream).head.version,
1427
- last_event_name: head.name
1444
+ last_event_name: head.name,
1445
+ ...interrupted ? { resumed_guard: { id: marker_head.id } } : {}
1428
1446
  });
1429
1447
  }
1430
1448
  return out;
@@ -1473,6 +1491,11 @@ async function guard_with_tombstones(safe, stream_info, correlation, tombstone2,
1473
1491
  await Promise.all(
1474
1492
  safe.map(async (stream) => {
1475
1493
  const info = stream_info.get(stream);
1494
+ if (info.resumed_guard) {
1495
+ guarded.push(stream);
1496
+ guard_events.set(stream, { id: info.resumed_guard.id, stream });
1497
+ return;
1498
+ }
1476
1499
  const committed = await tombstone2(stream, info.version, correlation);
1477
1500
  if (committed) {
1478
1501
  guarded.push(stream);
@@ -2542,13 +2565,6 @@ var contain = (logger, sink, fn) => {
2542
2565
  logger.error(error, `${sink} listener threw`);
2543
2566
  }
2544
2567
  };
2545
- var contain_async = async (logger, sink, fn) => {
2546
- try {
2547
- await fn();
2548
- } catch (error) {
2549
- logger.error(error, `${sink} listener threw`);
2550
- }
2551
- };
2552
2568
  var DrainController = class {
2553
2569
  _armed = false;
2554
2570
  _locked = false;
@@ -2660,8 +2676,8 @@ var DrainController = class {
2660
2676
  (b) => contain(this._deps.logger, "blocked", () => this._deps.on_blocked(b)),
2661
2677
  this._deps.lane
2662
2678
  );
2663
- this._deps.breaker.passed();
2664
2679
  if (!cycle) {
2680
+ this._deps.breaker.passed();
2665
2681
  this._armed = false;
2666
2682
  return EMPTY_DRAIN;
2667
2683
  }
@@ -2677,12 +2693,8 @@ var DrainController = class {
2677
2693
  if (this._defer.size > 0) this._defer.schedule();
2678
2694
  if (acked.length)
2679
2695
  contain(this._deps.logger, "acked", () => this._deps.on_acked(acked));
2680
- if (closeable.length)
2681
- await contain_async(
2682
- this._deps.logger,
2683
- "close",
2684
- () => this._deps.on_close(closeable)
2685
- );
2696
+ if (closeable.length) await this._deps.on_close(closeable);
2697
+ this._deps.breaker.passed();
2686
2698
  const has_errors = handled.some(({ error }) => error);
2687
2699
  if (!acked.length && !blocked.length && !has_errors) this._armed = false;
2688
2700
  return { fetched, leased, acked, blocked };
@@ -3274,7 +3286,11 @@ var Act = class {
3274
3286
  correlation: close_correlation(this._correlator, close_actor),
3275
3287
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
3276
3288
  });
3277
- this.emit("closed", result);
3289
+ try {
3290
+ this.emit("closed", result);
3291
+ } catch (err) {
3292
+ this._logger.error(err, "closed listener threw");
3293
+ }
3278
3294
  },
3279
3295
  breaker: this._breaker,
3280
3296
  // Re-scope the per-lane worker's auto-start ticks so their drain