@rotorsoft/act 1.37.5 → 1.37.7

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
@@ -416,7 +416,9 @@ var make_restart_candidate_pass = (deps, options) => {
416
416
  stream,
417
417
  stream_exact: true,
418
418
  names: [SNAP_EVENT],
419
- with_snaps: true
419
+ with_snaps: true,
420
+ // From the start: on its own, `with_snaps` resumes at the latest.
421
+ after: -1
420
422
  }
421
423
  );
422
424
  findings.push({
@@ -573,7 +575,7 @@ var make_correlation_gaps_pass = () => {
573
575
  seen_ids.add(e.id);
574
576
  const causation = e.meta?.causation;
575
577
  const parent_id = causation?.event?.id;
576
- if (parent_id !== void 0) {
578
+ if (parent_id !== void 0 && e.name !== TOMBSTONE_EVENT) {
577
579
  checks.push({ stream: e.stream, id: e.id, parent_id });
578
580
  }
579
581
  },
@@ -1458,7 +1460,7 @@ var CorrelateCycle = class {
1458
1460
  });
1459
1461
  } catch (error) {
1460
1462
  log().warn(
1461
- `Could not hand back the correlation lease during shutdown; it expires on its own within ${CORRELATION_LEASE_MS}ms and correlation resumes normally after that. On a single-writer store this usually means another connection holds the database. Cause: ${String(error)}`
1463
+ `Could not hand back the correlation lease during shutdown; it expires on its own within ${CORRELATION_LEASE_MS}ms and correlation resumes normally after that. On a single-writer store this usually means another connection holds the database. Cause: ${String(error.cause ?? error)}`
1462
1464
  );
1463
1465
  }
1464
1466
  }
@@ -1837,6 +1839,7 @@ async function scan(source, opts = {}, callback) {
1837
1839
  async function load(me, target, callback, patch_fn = bare_patch) {
1838
1840
  const { stream, actor, asOf } = target;
1839
1841
  const time_travel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
1842
+ const seeded = time_travel && asOf.limit === void 0 && !asOf.created_after;
1840
1843
  const cached = time_travel ? void 0 : await cache().get(stream);
1841
1844
  const cache_hit = !!cached;
1842
1845
  let state2 = cached?.state ?? (me.init ? me.init() : {});
@@ -1846,6 +1849,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
1846
1849
  let event;
1847
1850
  await store().query(
1848
1851
  (raw) => {
1852
+ if (seeded && raw.name === SNAP_EVENT && replayed > 0) return;
1849
1853
  event = me.view(raw, actor);
1850
1854
  if (event.name === SNAP_EVENT) {
1851
1855
  state2 = event.data;
@@ -1878,7 +1882,11 @@ async function load(me, target, callback, patch_fn = bare_patch) {
1878
1882
  // `with_snaps` on every non-time-travel load: cold, it resumes from the
1879
1883
  // latest snapshot; warm, it keeps a snapshot above a stale checkpoint,
1880
1884
  // so a windowed close's prune can't fold the tail onto stale state.
1881
- ...cached ? { after: cached.event_id, with_snaps: true } : { ...time_travel ? {} : { with_snaps: true }, ...asOf }
1885
+ ...cached ? { after: cached.event_id, with_snaps: true } : seeded ? (
1886
+ // From the start, past the resume floor, which could jump over the
1887
+ // time bound. The snapshots are for the seed.
1888
+ { with_snaps: true, after: -1, ...asOf }
1889
+ ) : time_travel ? asOf : { with_snaps: true }
1882
1890
  }
1883
1891
  );
1884
1892
  const head_is_tombstone = event?.name === TOMBSTONE_EVENT;
@@ -4019,6 +4027,10 @@ function policy_min_after_days(options) {
4019
4027
  if (parsed.or?.after) windows.push(parsed.or.after.days);
4020
4028
  return windows.length ? Math.min(...windows) : void 0;
4021
4029
  }
4030
+ function policy_after_windows(options) {
4031
+ const parsed = AutoclosePolicySchema.parse(options);
4032
+ return [parsed.after, parsed.or?.after].flatMap((w) => w ? [w.days] : []);
4033
+ }
4022
4034
  function policy_keep_days(options) {
4023
4035
  const parsed = AutoclosePolicySchema.parse(options);
4024
4036
  return parsed.keep?.days;
@@ -4090,7 +4102,7 @@ function synthesize_autoclose_reactions(registry, states, read_window) {
4090
4102
  for (const st of states.values()) {
4091
4103
  const predicate = st.autoclose;
4092
4104
  if (!predicate) continue;
4093
- const after_days = st.autoclose_after_days;
4105
+ const after_windows = st.autoclose_after_windows;
4094
4106
  const keep_days = st.autoclose_keep_days;
4095
4107
  const archiver = st.archive;
4096
4108
  const reaction = {
@@ -4100,10 +4112,13 @@ function synthesize_autoclose_reactions(registry, states, read_window) {
4100
4112
  // watermark would let autoclose's defer short-circuit the aggregate's
4101
4113
  // other reactions (the "a defer affects all reactions on a stream"
4102
4114
  // hazard). The close still targets the aggregate (`source`).
4103
- resolver: (e) => ({
4115
+ // A stream name with a regex metacharacter would read as a pattern, and a
4116
+ // store may refuse it at subscribe, stopping every reaction. Such a stream
4117
+ // has no autoclose: `app.close` still closes it.
4118
+ resolver: (e) => is_literal_source(e.stream) ? {
4104
4119
  target: `${AUTOCLOSE_TARGET_PREFIX}${e.stream}`,
4105
4120
  source: e.stream
4106
- }),
4121
+ } : void 0,
4107
4122
  // Never block on autoclose: a transient query/store error should retry,
4108
4123
  // not quarantine the synthetic stream.
4109
4124
  options: { blockOnError: false, maxRetries: DEFAULT_MAX_RETRIES },
@@ -4140,8 +4155,8 @@ function synthesize_autoclose_reactions(registry, states, read_window) {
4140
4155
  });
4141
4156
  }
4142
4157
  const dues = [];
4143
- if (after_days !== void 0)
4144
- dues.push(days_after(head.created, after_days));
4158
+ for (const days of after_windows)
4159
+ dues.push(days_after(head.created, days));
4145
4160
  if (keep_days !== void 0)
4146
4161
  dues.push(days_after(entry.tail.created, keep_days));
4147
4162
  const future = dues.map((d) => d.getTime()).filter((t) => t > Date.now());
@@ -4231,6 +4246,7 @@ function autoclose_of(existing, state2) {
4231
4246
  return {
4232
4247
  autoclose: owner.autoclose,
4233
4248
  autoclose_after_days: owner.autoclose_after_days,
4249
+ autoclose_after_windows: owner.autoclose_after_windows,
4234
4250
  autoclose_keep_days: owner.autoclose_keep_days
4235
4251
  };
4236
4252
  }
@@ -5085,6 +5101,7 @@ function action_builder(state2) {
5085
5101
  policy
5086
5102
  );
5087
5103
  internal.autoclose_after_days = policy_min_after_days(policy);
5104
+ internal.autoclose_after_windows = policy_after_windows(policy);
5088
5105
  internal.autoclose_keep_days = policy_keep_days(policy);
5089
5106
  return builder;
5090
5107
  },