@rotorsoft/act 1.33.16 → 1.33.18

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-E5H3NRCT.js";
43
+ } from "./chunk-3OTABSIB.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-CAL4XEVN.js";
73
+ } from "./chunk-SD7SW47Z.js";
74
74
 
75
75
  // src/signals.ts
76
76
  process.once("SIGINT", async (arg) => {
@@ -1168,10 +1168,13 @@ var CorrelateCycle = class {
1168
1168
  _raised = 1;
1169
1169
  _lowered = 0;
1170
1170
  /**
1171
- * Bumped by a release (shutdown) and a restart (restore), so a scan or
1172
- * lease reply that was out across one writes nothing.
1171
+ * Bumped by a release (shutdown), so a scan or lease reply that was out
1172
+ * across one writes nothing.
1173
1173
  */
1174
1174
  _epoch = 0;
1175
+ /** Passes in flight: a restore waits for them, and refuses new ones. */
1176
+ _passes = /* @__PURE__ */ new Set();
1177
+ _restoring = false;
1175
1178
  _init_promise;
1176
1179
  // Bounded by `maxSubscribedStreams`; static records live apart so eviction
1177
1180
  // never drops their +Infinity floor.
@@ -1230,14 +1233,22 @@ var CorrelateCycle = class {
1230
1233
  }
1231
1234
  await this._on_init?.();
1232
1235
  }
1233
- /** After `restore` replaced the log: forget everything and cold-start again. */
1234
- restart() {
1236
+ /**
1237
+ * Replace the log with no pass in flight, then cold-start: a pass that
1238
+ * read the old log writes before the restore, never into the new one.
1239
+ */
1240
+ async restart(restore) {
1241
+ this._restoring = true;
1242
+ try {
1243
+ await Promise.allSettled(this._passes);
1244
+ await restore();
1245
+ } finally {
1246
+ this._restoring = false;
1247
+ }
1235
1248
  this._init_promise = void 0;
1236
1249
  this._checkpoint = -1;
1237
1250
  this._dynamic_subscriptions.clear();
1238
1251
  this._static_subscriptions.clear();
1239
- this._raised++;
1240
- this._epoch++;
1241
1252
  }
1242
1253
  /** Forget dynamic targets whose rows a full close deleted, so they re-subscribe. */
1243
1254
  forget_subscribed(streams) {
@@ -1256,6 +1267,16 @@ var CorrelateCycle = class {
1256
1267
  }
1257
1268
  /** Scan past the checkpoint, subscribe new targets and raise their work marks. */
1258
1269
  async correlate(query, lease = false) {
1270
+ if (this._restoring) return { kind: "idle", last_id: this._checkpoint };
1271
+ const pass = this._scan(query, lease);
1272
+ this._passes.add(pass);
1273
+ try {
1274
+ return await pass;
1275
+ } finally {
1276
+ this._passes.delete(pass);
1277
+ }
1278
+ }
1279
+ async _scan(query, lease) {
1259
1280
  await this._init();
1260
1281
  const epoch = this._epoch;
1261
1282
  const idle = () => ({
@@ -1275,7 +1296,7 @@ var CorrelateCycle = class {
1275
1296
  this._lease_until = correlating ? now + CORRELATION_LEASE_MS : 0;
1276
1297
  if (correlating === false) return idle();
1277
1298
  }
1278
- const after = Math.max(this._checkpoint, query.after || -1);
1299
+ const after = this._checkpoint;
1279
1300
  const correlated = /* @__PURE__ */ new Map();
1280
1301
  let last_id = after;
1281
1302
  let stopped = false;
@@ -1349,7 +1370,7 @@ var CorrelateCycle = class {
1349
1370
  },
1350
1371
  // Resolvers see `pii: null`: correlate runs actor-less, and one
1351
1372
  // undecryptable row would otherwise stop every stream's reactions.
1352
- { ...query, after, with_pii: false }
1373
+ { after, limit: query.limit, with_pii: false }
1353
1374
  );
1354
1375
  if (this._declared_lanes.size > 1) await this._recall(correlated);
1355
1376
  const read = last_id > after;
@@ -2408,9 +2429,11 @@ var DrainController = class {
2408
2429
  get lane() {
2409
2430
  return this._deps.lane;
2410
2431
  }
2411
- /** Drains every `cycleMs` while armed; the unref'd timer never holds the process. */
2432
+ /**
2433
+ * Drains every `cycleMs` while armed; the unref'd timer never holds the
2434
+ * process. Called once, when the controller is built.
2435
+ */
2412
2436
  start(cycleMs) {
2413
- if (this._worker || this._stopped) return;
2414
2437
  const run = this._deps.run_scoped;
2415
2438
  const tick = async () => {
2416
2439
  if (this.armed) await run(() => this.drain());
@@ -2856,8 +2879,7 @@ var SettleLoop = class {
2856
2879
  this._timer = void 0;
2857
2880
  const {
2858
2881
  debounceMs: _,
2859
- // Every pass scans from correlation's own checkpoint.
2860
- correlate: { after: _after, ...correlate_query } = {},
2882
+ correlate: correlate_query = {},
2861
2883
  maxPasses = Infinity,
2862
2884
  ...drain_options
2863
2885
  } = this._next ?? {};
@@ -2870,7 +2892,7 @@ var SettleLoop = class {
2870
2892
  if (scanned) this._deps.breaker.passed();
2871
2893
  const drain = await this._deps.drain(drain_options);
2872
2894
  passes.push(drain);
2873
- const made_progress = drain.leased.length > 0 || scanned && (pass.subscribed > 0 || pass.read);
2895
+ const made_progress = drain.leased.length > 0 || scanned && pass.read;
2874
2896
  if (!made_progress) break;
2875
2897
  }
2876
2898
  if (passes.length) this._deps.on_settled(merge_drains(passes));
@@ -3529,8 +3551,9 @@ var Act = class {
3529
3551
  * target streams their reactions resolve to (static or computed from
3530
3552
  * the event), and marks their work so the next drain can claim it.
3531
3553
  *
3532
- * @param query - Scan filter — see {@link Query} for fields (typically
3533
- * `{ limit: <count> }`; the scan starts at the checkpoint)
3554
+ * @param query - Only `limit` is used: the most events the pass reads
3555
+ * (default 10). The scan always starts at the correlation checkpoint,
3556
+ * which every reaction shares, so filters and `after` are ignored.
3534
3557
  * @returns The number of newly subscribed streams and the last scanned event id
3535
3558
  *
3536
3559
  * @example Manual correlation
@@ -3540,7 +3563,7 @@ var Act = class {
3540
3563
  *
3541
3564
  * @see {@link start_correlations} for automatic periodic correlation
3542
3565
  */
3543
- async correlate(query = { after: -1, limit: 10 }) {
3566
+ async correlate(query = { limit: 10 }) {
3544
3567
  const pass = await this._correlate_pass(query);
3545
3568
  return {
3546
3569
  subscribed: pass.kind === "scanned" ? pass.subscribed : 0,
@@ -3552,8 +3575,7 @@ var Act = class {
3552
3575
  if (!this._drain) return { kind: "idle", last_id: -1 };
3553
3576
  return this._scoped(async () => {
3554
3577
  const pass = await this._correlate.correlate(query, lease);
3555
- if (pass.kind === "scanned" && (pass.subscribed > 0 || pass.marked > 0))
3556
- this._arm_all();
3578
+ if (pass.kind === "scanned" && pass.marked > 0) this._arm_all();
3557
3579
  return pass;
3558
3580
  });
3559
3581
  }
@@ -3694,6 +3716,12 @@ var Act = class {
3694
3716
  * adapter's {@link Store.restore} driver supplies the transaction
3695
3717
  * lifecycle and per-event insert.
3696
3718
  *
3719
+ * Safe in a live process: restoring into this Act's own store waits
3720
+ * for its correlate passes in flight, replaces the log, then restarts
3721
+ * correlation and settles. A correlate pass that starts meanwhile
3722
+ * reads nothing. Other processes on the same store keep their
3723
+ * correlation state and must be restarted.
3724
+ *
3697
3725
  * Throws if the adapter has no restore capability. Throws on the
3698
3726
  * first invalid event (negative version, malformed `created`) with
3699
3727
  * the running index in the message; atomic transaction rollback in
@@ -3736,14 +3764,15 @@ var Act = class {
3736
3764
  let kept = 0;
3737
3765
  let migrated = 0;
3738
3766
  let dropped = { closed_streams: 0, snapshots: 0 };
3739
- await target.restore(async (callback) => {
3767
+ const run = () => target.restore(async (callback) => {
3740
3768
  const partial = await scan(source, opts, callback);
3741
3769
  kept = partial.kept;
3742
3770
  migrated = partial.migrated;
3743
3771
  dropped = partial.dropped;
3744
3772
  });
3745
- if (!sink) {
3746
- this._correlate.restart();
3773
+ if (sink) await run();
3774
+ else {
3775
+ await this._correlate.restart(run);
3747
3776
  this._wake();
3748
3777
  }
3749
3778
  return { kept, migrated, dropped, duration_ms: Date.now() - started };