@rotorsoft/act 1.23.33 → 1.23.35

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.cjs CHANGED
@@ -395,10 +395,17 @@ var import_zod = require("zod");
395
395
  var REDACTED = "[REDACTED]";
396
396
  var SHREDDED = "[SHREDDED]";
397
397
  var _registry = import_zod.z.registry();
398
+ var _SENSITIVE = /* @__PURE__ */ Symbol.for("act.sensitive");
399
+ function _mark_sensitive(schema) {
400
+ const def = schema._zod?.def;
401
+ if (def) def[_SENSITIVE] = true;
402
+ }
398
403
  function is_pii(schema) {
399
404
  let cur = schema;
400
405
  while (true) {
401
406
  if (_registry.has(cur)) return true;
407
+ const def = cur._zod?.def;
408
+ if (def?.[_SENSITIVE] === true) return true;
402
409
  const inner = cur._def?.innerType;
403
410
  if (!inner || inner === cur) return false;
404
411
  cur = inner;
@@ -406,12 +413,21 @@ function is_pii(schema) {
406
413
  }
407
414
  function pii_fields(schema) {
408
415
  const shape = schema.shape;
409
- if (!shape || typeof shape !== "object") return [];
410
- const fields = [];
411
- for (const key of Object.keys(shape)) {
412
- if (is_pii(shape[key])) fields.push(key);
416
+ if (shape && typeof shape === "object") {
417
+ const fields = [];
418
+ for (const key of Object.keys(shape)) {
419
+ if (is_pii(shape[key])) fields.push(key);
420
+ }
421
+ return fields;
413
422
  }
414
- return fields;
423
+ const options = schema.options;
424
+ if (Array.isArray(options)) {
425
+ const fields = /* @__PURE__ */ new Set();
426
+ for (const option of options)
427
+ for (const key of pii_fields(option)) fields.add(key);
428
+ return [...fields];
429
+ }
430
+ return [];
415
431
  }
416
432
  function pii_split(emitted, fields) {
417
433
  const data = { ...emitted.data };
@@ -464,6 +480,7 @@ function pii_strip(event, fields) {
464
480
  var ZodEmpty = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.never());
465
481
  function sensitive(schema) {
466
482
  _registry.add(schema, { sensitive: true });
483
+ _mark_sensitive(schema);
467
484
  return schema;
468
485
  }
469
486
  var ActorSchema = import_zod2.z.object({
@@ -1418,8 +1435,11 @@ var InMemoryStore = class {
1418
1435
  }
1419
1436
  }
1420
1437
  this._events = this._events.filter((e) => !stream_set.has(e.stream));
1438
+ const restarted = new Set(
1439
+ full.filter((t) => t.snapshot !== void 0).map((t) => t.stream)
1440
+ );
1421
1441
  for (const stream of stream_set) {
1422
- this._streams.delete(stream);
1442
+ if (!restarted.has(stream)) this._streams.delete(stream);
1423
1443
  this._stream_versions.delete(stream);
1424
1444
  this._max_event_id_by_stream.delete(stream);
1425
1445
  this._pii.delete(stream);
@@ -3095,6 +3115,8 @@ var DEFAULT_COLD_START_BACK_SCAN = 1e4;
3095
3115
  var CorrelateCycle = class {
3096
3116
  _checkpoint = -1;
3097
3117
  _initialized = false;
3118
+ /** In-flight init, memoized for single-flight and cleared on failure. */
3119
+ _init_promise;
3098
3120
  _timer = void 0;
3099
3121
  // target → the priority it was last subscribed at. A dynamic target is
3100
3122
  // re-subscribed (so the store's `GREATEST` upsert runs) only when a later
@@ -3177,7 +3199,16 @@ var CorrelateCycle = class {
3177
3199
  */
3178
3200
  async init() {
3179
3201
  if (this._initialized) return;
3202
+ if (!this._init_promise) {
3203
+ this._init_promise = this._run_init().catch((error) => {
3204
+ this._init_promise = void 0;
3205
+ throw error;
3206
+ });
3207
+ }
3208
+ await this._init_promise;
3180
3209
  this._initialized = true;
3210
+ }
3211
+ async _run_init() {
3181
3212
  const { watermark } = await store2().subscribe([...this._static_targets]);
3182
3213
  this._checkpoint = this._has_dynamic_resolvers ? Math.max(-1, watermark - this._cold_start_back_scan) : watermark;
3183
3214
  this._on_init?.();
@@ -3186,6 +3217,26 @@ var CorrelateCycle = class {
3186
3217
  }
3187
3218
  await this._on_init_async?.();
3188
3219
  }
3220
+ /**
3221
+ * Forget targets whose subscription rows no longer exist, so a later
3222
+ * scan can re-subscribe them.
3223
+ *
3224
+ * A full close deletes the closed stream's subscription row. The
3225
+ * in-process dedup would otherwise still believe the target is
3226
+ * subscribed and never re-issue `subscribe()`, silently stopping
3227
+ * delivery for any reaction whose target is named after the stream —
3228
+ * the documented per-aggregate shape `.to(e => ({target: e.stream}))`
3229
+ * makes those two namespaces collide by construction (#1398).
3230
+ *
3231
+ * Static targets are left alone: they are subscribed once at init and
3232
+ * recorded at +Infinity so the dynamic path never re-opens them.
3233
+ */
3234
+ forget_subscribed(streams) {
3235
+ const statics = new Set(this._static_targets.map((t) => t.stream));
3236
+ for (const stream of streams) {
3237
+ if (!statics.has(stream)) this._subscribed.delete(stream);
3238
+ }
3239
+ }
3189
3240
  /**
3190
3241
  * Discover dynamic-resolver targets in the events past the checkpoint
3191
3242
  * and register any new streams via `cd.subscribe`. Static targets are
@@ -4808,6 +4859,7 @@ var Act = class {
4808
4859
  correlation: close_correlation(this._correlator, close_actor),
4809
4860
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
4810
4861
  });
4862
+ this._forget_closed_subscriptions(result);
4811
4863
  try {
4812
4864
  this.emit("closed", result);
4813
4865
  } catch (err) {
@@ -5802,6 +5854,15 @@ var Act = class {
5802
5854
  * ]);
5803
5855
  * ```
5804
5856
  */
5857
+ /**
5858
+ * After a close, forget any target whose subscription row the truncate
5859
+ * removed, so a later correlate can re-subscribe it (#1398). Restart
5860
+ * targets keep their row, so only fully-retired streams are forgotten.
5861
+ */
5862
+ _forget_closed_subscriptions(result) {
5863
+ const retired = [...result.truncated.entries()].filter(([, r]) => r.committed.name === TOMBSTONE_EVENT).map(([stream]) => stream);
5864
+ if (retired.length) this._correlate.forget_subscribed(retired);
5865
+ }
5805
5866
  async close(targets) {
5806
5867
  if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
5807
5868
  return this._scoped(async () => {
@@ -5816,6 +5877,7 @@ var Act = class {
5816
5877
  correlation: close_correlation(this._correlator, close_actor),
5817
5878
  with_stream_lock: (stream, work) => this._with_close_lock(stream, work)
5818
5879
  });
5880
+ this._forget_closed_subscriptions(result);
5819
5881
  this.emit("closed", result);
5820
5882
  return result;
5821
5883
  });