@rotorsoft/act 1.35.3 → 1.37.0

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.
Files changed (39) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/@types/act.d.ts +19 -39
  3. package/dist/@types/act.d.ts.map +1 -1
  4. package/dist/@types/adapters/in-memory-store.d.ts +0 -4
  5. package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
  6. package/dist/@types/builders/act-builder.d.ts.map +1 -1
  7. package/dist/@types/internal/autoclose-reaction.d.ts +2 -2
  8. package/dist/@types/internal/circuit-breaker.d.ts +0 -7
  9. package/dist/@types/internal/circuit-breaker.d.ts.map +1 -1
  10. package/dist/@types/internal/correlate-cycle.d.ts +0 -18
  11. package/dist/@types/internal/correlate-cycle.d.ts.map +1 -1
  12. package/dist/@types/internal/drain-cycle.d.ts +0 -14
  13. package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
  14. package/dist/@types/internal/event-sourcing.d.ts +1 -1
  15. package/dist/@types/internal/settle.d.ts +0 -9
  16. package/dist/@types/internal/settle.d.ts.map +1 -1
  17. package/dist/@types/types/action.d.ts +2 -2
  18. package/dist/@types/types/errors.d.ts +11 -26
  19. package/dist/@types/types/errors.d.ts.map +1 -1
  20. package/dist/{chunk-H2QN755D.js → chunk-A3XDAB7T.js} +1 -1
  21. package/dist/chunk-A3XDAB7T.js.map +1 -0
  22. package/dist/{chunk-SVVVS6GA.js → chunk-BRJZQ4IY.js} +8 -8
  23. package/dist/chunk-BRJZQ4IY.js.map +1 -0
  24. package/dist/{chunk-F2YAA4SE.cjs → chunk-C67XTW3W.cjs} +19 -19
  25. package/dist/{chunk-F2YAA4SE.cjs.map → chunk-C67XTW3W.cjs.map} +1 -1
  26. package/dist/{chunk-4KDMNSPJ.cjs → chunk-NXURHBSB.cjs} +1 -1
  27. package/dist/chunk-NXURHBSB.cjs.map +1 -0
  28. package/dist/index.cjs +264 -243
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.js +122 -101
  31. package/dist/index.js.map +1 -1
  32. package/dist/test/index.cjs +4 -4
  33. package/dist/test/index.js +2 -2
  34. package/dist/types/index.cjs +2 -2
  35. package/dist/types/index.js +1 -1
  36. package/package.json +1 -1
  37. package/dist/chunk-4KDMNSPJ.cjs.map +0 -1
  38. package/dist/chunk-H2QN755D.js.map +0 -1
  39. package/dist/chunk-SVVVS6GA.js.map +0 -1
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  sleep,
41
41
  store,
42
42
  validate
43
- } from "./chunk-SVVVS6GA.js";
43
+ } from "./chunk-BRJZQ4IY.js";
44
44
  import {
45
45
  ActorSchema,
46
46
  CausationEventSchema,
@@ -71,7 +71,7 @@ import {
71
71
  pii_fields,
72
72
  pii_schemas,
73
73
  sensitive
74
- } from "./chunk-H2QN755D.js";
74
+ } from "./chunk-A3XDAB7T.js";
75
75
 
76
76
  // src/signals.ts
77
77
  process.once("SIGINT", async (arg) => {
@@ -709,12 +709,10 @@ var CircuitBreaker = class {
709
709
  stop() {
710
710
  this._clear_wake();
711
711
  }
712
- /**
713
- * Schedule the `on_retry` wake `cooldownMs` out so the breaker re-trials
714
- * the store on its own. No-op when no `on_retry` hook is wired (e.g. unit
715
- * tests of the pure state machine). The timer is `unref`'d so it never
716
- * keeps the process alive.
717
- */
712
+ // Schedule the `on_retry` wake `cooldownMs` out so the breaker re-trials
713
+ // the store on its own. No-op when no `on_retry` hook is wired (e.g. unit
714
+ // tests of the pure state machine). The timer is `unref`'d so it never
715
+ // keeps the process alive.
718
716
  _schedule_wake() {
719
717
  if (!this._hooks.on_retry) return;
720
718
  this._clear_wake();
@@ -730,7 +728,7 @@ var CircuitBreaker = class {
730
728
  this._wake = void 0;
731
729
  }
732
730
  }
733
- /** Pure state transition for a failure — no side effects. */
731
+ // Pure state transition for a failure — no side effects.
734
732
  _advance(now) {
735
733
  if (this._opened_at !== void 0) {
736
734
  this._opened_at = now;
@@ -1157,23 +1155,19 @@ function report_lane_conflict(seen, handler, target, kept, dropped) {
1157
1155
  }
1158
1156
  var CorrelateCycle = class {
1159
1157
  _checkpoint = -1;
1160
- /** Lease holder id; per instance, so a restart never inherits a stale claim. */
1158
+ // Lease holder id; per instance, so a restart never inherits a stale claim.
1161
1159
  _by = randomUUID();
1162
1160
  _key;
1163
- /** Local expiry of the held lease (0 = none); stale only costs a duplicate scan. */
1161
+ // Local expiry of the held lease (0 = none); stale only costs a duplicate scan.
1164
1162
  _lease_until = 0;
1165
- /**
1166
- * Armed while `_raised` (bumped by a commit or notification) is ahead of
1167
- * `_lowered` (the raise count the last short page saw). Starts armed.
1168
- */
1163
+ // Armed while `_raised` (bumped by a commit or notification) is ahead of
1164
+ // `_lowered` (the raise count the last short page saw). Starts armed.
1169
1165
  _raised = 1;
1170
1166
  _lowered = 0;
1171
- /**
1172
- * Bumped by a release (shutdown), so a scan or lease reply that was out
1173
- * across one writes nothing.
1174
- */
1167
+ // Bumped by a release (shutdown), so a scan or lease reply that was out
1168
+ // across one writes nothing.
1175
1169
  _epoch = 0;
1176
- /** Passes in flight: a restore waits for them, and refuses new ones. */
1170
+ // Passes in flight: a restore waits for them, and refuses new ones.
1177
1171
  _passes = /* @__PURE__ */ new Set();
1178
1172
  _restoring = false;
1179
1173
  _init_promise;
@@ -1187,7 +1181,7 @@ var CorrelateCycle = class {
1187
1181
  _cd;
1188
1182
  _on_init;
1189
1183
  _declared_lanes;
1190
- /** Report-once keys; per instance because `internal/` holds no module state. */
1184
+ // Report-once keys; per instance because `internal/` holds no module state.
1191
1185
  _reported = /* @__PURE__ */ new Set();
1192
1186
  constructor({
1193
1187
  registry,
@@ -1254,7 +1248,7 @@ var CorrelateCycle = class {
1254
1248
  forget_subscribed(streams) {
1255
1249
  for (const stream of streams) this._dynamic_subscriptions.delete(stream);
1256
1250
  }
1257
- /** Would the target's own `fetch` (filtered by `source`) return this event? */
1251
+ // Would the target's own `fetch` (filtered by `source`) return this event?
1258
1252
  _in_fetch_window(source, stream) {
1259
1253
  if (source === void 0 || source === stream) return true;
1260
1254
  if (is_literal_source(source)) return false;
@@ -1413,11 +1407,9 @@ var CorrelateCycle = class {
1413
1407
  if (!stopped && found !== limit) this._lowered = raised;
1414
1408
  return { kind: "scanned", last_id, subscribed: 0, marked: 0, read };
1415
1409
  }
1416
- /**
1417
- * Read the row of each target this scan had no record of (new or evicted),
1418
- * so a resolution that doesn't beat its priority keeps the row's lane.
1419
- * Only called with more than one lane declared.
1420
- */
1410
+ // Read the row of each target this scan had no record of (new or evicted),
1411
+ // so a resolution that doesn't beat its priority keeps the row's lane.
1412
+ // Only called with more than one lane declared.
1421
1413
  async _recall(correlated) {
1422
1414
  const missed = [...correlated].filter(([, entry]) => entry.missed);
1423
1415
  await Promise.all(
@@ -2396,21 +2388,17 @@ function merge_drains(drains) {
2396
2388
  }
2397
2389
  var RETRY = "";
2398
2390
  var DrainController = class {
2399
- /**
2400
- * Armed while `_raised` is ahead of `_lowered` (the raise count the last
2401
- * empty claim saw), so a claim never lowers over a raise that landed
2402
- * during it.
2403
- */
2391
+ // Armed while `_raised` is ahead of `_lowered` (the raise count the last
2392
+ // empty claim saw), so a claim never lowers over a raise that landed
2393
+ // during it.
2404
2394
  _raised = 0;
2405
2395
  _lowered = 0;
2406
2396
  _ratio = 0.5;
2407
- /**
2408
- * Local wake at the earliest parked (backoff or defer) visit, or at the
2409
- * lease expiry after a failed pass (the {@link RETRY} key); the schedule
2410
- * itself is persisted. On an idle app nothing else would drain it. It
2411
- * drains until a pass leases nothing or the controller stops, and an entry
2412
- * stays until handled or until a later pass claims nothing.
2413
- */
2397
+ // Local wake at the earliest parked (backoff or defer) visit, or at the
2398
+ // lease expiry after a failed pass (the {@link RETRY} key); the schedule
2399
+ // itself is persisted. On an idle app nothing else would drain it. It
2400
+ // drains until a pass leases nothing or the controller stops, and an entry
2401
+ // stays until handled or until a later pass claims nothing.
2414
2402
  _defer = new DeferTimer(() => {
2415
2403
  this.arm();
2416
2404
  void this._deps.run_scoped(async () => {
@@ -2420,7 +2408,7 @@ var DrainController = class {
2420
2408
  _worker;
2421
2409
  _misrouted = /* @__PURE__ */ new Set();
2422
2410
  _stopped = false;
2423
- /** The pass in flight. Never rejects; graceful shutdown waits on it. */
2411
+ // The pass in flight. Never rejects; graceful shutdown waits on it.
2424
2412
  _inflight;
2425
2413
  _deps;
2426
2414
  constructor(deps) {
@@ -2487,7 +2475,7 @@ var DrainController = class {
2487
2475
  this._inflight = pass;
2488
2476
  return pass;
2489
2477
  }
2490
- /** One claim → fetch → handle → ack pass. Never throws. */
2478
+ // One claim → fetch → handle → ack pass. Never throws.
2491
2479
  async _pass(streamLimit, eventLimit, leaseMillis) {
2492
2480
  try {
2493
2481
  const lagging = Math.ceil(streamLimit * this._ratio);
@@ -2848,21 +2836,19 @@ function build_handle_batch(logger) {
2848
2836
  // src/internal/settle.ts
2849
2837
  var SettleLoop = class {
2850
2838
  _timer = void 0;
2851
- /**
2852
- * The cycle in flight, set while one runs. Shutdown waits for it
2853
- * (`stop()` cancels scheduling only). Never rejects.
2854
- */
2839
+ // The cycle in flight, set while one runs. Shutdown waits for it
2840
+ // (`stop()` cancels scheduling only). Never rejects.
2855
2841
  _inflight;
2856
- /** A wake-up arrived while a cycle ran: run one more pass after it. */
2842
+ // A wake-up arrived while a cycle ran: run one more pass after it.
2857
2843
  _pending = false;
2858
- /** The last caller's options, used by the next pass. */
2844
+ // The last caller's options, used by the next pass.
2859
2845
  _next = void 0;
2860
- /** When the scheduled pass fires (epoch ms). */
2846
+ // When the scheduled pass fires (epoch ms).
2861
2847
  _due = 0;
2862
- /** Set at shutdown: nothing schedules again. */
2848
+ // Set at shutdown: nothing schedules again.
2863
2849
  _stopped = false;
2864
2850
  _deps;
2865
- /** Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`. */
2851
+ // Debounce window applied when the caller doesn't override via `SettleOptions.debounceMs`.
2866
2852
  _default_debounce_ms;
2867
2853
  constructor(deps, default_debounce_ms) {
2868
2854
  this._deps = deps;
@@ -2960,18 +2946,16 @@ var Act = class {
2960
2946
  _breaker;
2961
2947
  _drain;
2962
2948
  _auto_settle;
2963
- /** Whether any event has a reaction; nothing arms without one. */
2949
+ // Whether any event has a reaction; nothing arms without one.
2964
2950
  _reactive;
2965
2951
  _drain_controllers;
2966
2952
  _correlate;
2967
2953
  _settle;
2968
2954
  _poll;
2969
- /** The poll's correlate `limit`, used by every settle pass while it runs. */
2955
+ // The poll's correlate `limit`, used by every settle pass while it runs.
2970
2956
  _poll_limit;
2971
- /**
2972
- * Wired at construction against the store current then, so a store
2973
- * injected after `build()` is never notified.
2974
- */
2957
+ // Wired at construction against the store current then, so a store
2958
+ // injected after `build()` is never notified.
2975
2959
  _notify_disposer;
2976
2960
  /** Public registry — kept as-is per the no-prefix-on-public convention. */
2977
2961
  registry;
@@ -2989,58 +2973,70 @@ var Act = class {
2989
2973
  * returns "had listeners" to preserve the `EventEmitter.emit` contract.
2990
2974
  */
2991
2975
  emit(event, args) {
2992
- const listeners = this._emitter.rawListeners(event);
2976
+ return this._notify(event, args);
2977
+ }
2978
+ // Run each listener of `key` on its own, logging any that throws.
2979
+ _notify(key, args) {
2980
+ const listeners = this._emitter.rawListeners(key);
2993
2981
  for (const listener of listeners) {
2994
2982
  try {
2995
2983
  listener(args);
2996
2984
  } catch (error) {
2997
- this._logger.error(error, `${String(event)} listener threw`);
2985
+ this._logger.error(error, `${key} listener threw`);
2998
2986
  }
2999
2987
  }
3000
2988
  return listeners.length > 0;
3001
2989
  }
3002
- /**
3003
- * The one place a store failure is logged and emitted (the breaker's
3004
- * `on_error`). The emit is guarded: an unhandled `"error"` emission
3005
- * would crash the process.
3006
- */
2990
+ // The emitter key of one state's `committed` listeners.
2991
+ _committed_key(state2) {
2992
+ const name = typeof state2 === "string" ? state2 : state2.name;
2993
+ if (!this._states.has(name))
2994
+ throw new Error(`committed: "${name}" is not a state of this app`);
2995
+ return `committed:${name}`;
2996
+ }
2997
+ // The one place a store failure is logged and emitted (the breaker's
2998
+ // `on_error`). The emit is guarded: an unhandled `"error"` emission
2999
+ // would crash the process.
3007
3000
  _emit_error(error, circuit) {
3008
3001
  this._logger.error(error);
3009
3002
  if (this._emitter.listenerCount("error") > 0)
3010
3003
  this.emit("error", { error, circuit });
3011
3004
  }
3012
- /**
3013
- * Register a listener for a lifecycle event. The listener receives the
3014
- * event-specific payload.
3015
- */
3016
- on(event, listener) {
3017
- this._emitter.on(event, listener);
3005
+ on(event, state_or_listener, listener) {
3006
+ if (listener)
3007
+ this._emitter.on(
3008
+ this._committed_key(state_or_listener),
3009
+ listener
3010
+ );
3011
+ else this._emitter.on(event, state_or_listener);
3018
3012
  return this;
3019
3013
  }
3020
- /**
3021
- * Remove a previously registered lifecycle listener.
3022
- */
3023
- off(event, listener) {
3024
- this._emitter.off(event, listener);
3014
+ off(event, state_or_listener, listener) {
3015
+ if (listener)
3016
+ this._emitter.off(
3017
+ this._committed_key(state_or_listener),
3018
+ listener
3019
+ );
3020
+ else this._emitter.off(event, state_or_listener);
3025
3021
  return this;
3026
3022
  }
3027
3023
  _batch_handlers;
3028
3024
  _es;
3029
3025
  _cd;
3030
- /** Unambiguous: merge rejects an event name declared by two states. */
3026
+ // Unambiguous: merge rejects an event name declared by two states.
3031
3027
  _event_to_state;
3032
- /** Lanes to arm per event; `"all"` when a dynamic resolver hides the lane. */
3028
+ // Lanes to arm per event; `"all"` when a dynamic resolver hides the lane.
3033
3029
  _event_to_lanes;
3034
3030
  _audit_deps;
3035
3031
  _logger = log();
3036
- /** This Act's ports: its own bag, or the singleton adapters. */
3032
+ // This Act's ports: its own bag, or the singleton adapters.
3037
3033
  _ports;
3038
- /** Runs a body so internal `store()`/`cache()` calls resolve to `_ports`. */
3034
+ // Runs a body so internal `store()`/`cache()` calls resolve to `_ports`.
3039
3035
  _scoped;
3040
3036
  _correlator;
3041
3037
  _lanes;
3042
3038
  _close_lock = new CloseLock();
3043
- /** Every lane a controller can exist for: `"default"` plus each declared one. */
3039
+ // Every lane a controller can exist for: `"default"` plus each declared one.
3044
3040
  _declared_lanes;
3045
3041
  /** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
3046
3042
  get lanes() {
@@ -3105,7 +3101,7 @@ var Act = class {
3105
3101
  );
3106
3102
  register_weak_disposer(new WeakRef(this), (self) => self.shutdown());
3107
3103
  }
3108
- /** Shared by drain, settle and autoclose; bad knobs throw at build. */
3104
+ // Shared by drain, settle and autoclose; bad knobs throw at build.
3109
3105
  _build_breaker(options) {
3110
3106
  return new CircuitBreaker(
3111
3107
  resolveCircuitBreakerConfig(options.circuitBreaker),
@@ -3117,7 +3113,7 @@ var Act = class {
3117
3113
  }
3118
3114
  );
3119
3115
  }
3120
- /** Apps that declare no lanes pass no lane filter to `claim`. */
3116
+ // Apps that declare no lanes pass no lane filter to `claim`.
3121
3117
  _build_drain_controllers(options, lanes, handle, handle_batch) {
3122
3118
  const all_lanes = this._declared_lanes;
3123
3119
  const only_set = options.onlyLanes?.length ? new Set(options.onlyLanes) : void 0;
@@ -3163,7 +3159,7 @@ var Act = class {
3163
3159
  }
3164
3160
  return controllers;
3165
3161
  }
3166
- /** One process can't see the cluster, so it reports its own lane gaps. */
3162
+ // One process can't see the cluster, so it reports its own lane gaps.
3167
3163
  _advise_orphaned_lanes() {
3168
3164
  const active = new Set(this._drain_controllers.keys());
3169
3165
  const orphaned = this._declared_lanes.filter((name) => !active.has(name));
@@ -3173,7 +3169,7 @@ var Act = class {
3173
3169
  `Act declared ${orphaned.length} orphaned lane(s) on this instance: ${list}. onlyLanes excludes them, so no DrainController claims their streams here \u2014 their reactions accumulate un-drained unless a peer worker deploys with these lanes in its onlyLanes. Ensure the cluster invariant holds: the union of every worker's onlyLanes must cover every declared lane. See docs/docs/guides/production-checklist.md \xA7 Sizing lanes.`
3174
3170
  );
3175
3171
  }
3176
- /** Audit deps, built after the controllers so the lane set is final. */
3172
+ // Audit deps, built after the controllers so the lane set is final.
3177
3173
  _build_audit_deps() {
3178
3174
  return {
3179
3175
  store,
@@ -3202,7 +3198,7 @@ var Act = class {
3202
3198
  }
3203
3199
  });
3204
3200
  }
3205
- /** Cold start: an idle deferred stream still fires on time after a restart. */
3201
+ // Cold start: an idle deferred stream still fires on time after a restart.
3206
3202
  async _seed_persisted_defers() {
3207
3203
  const now = Date.now();
3208
3204
  await walk_streams(store(), (pos) => {
@@ -3256,10 +3252,8 @@ var Act = class {
3256
3252
  }
3257
3253
  return this._shutdown_promise;
3258
3254
  }
3259
- /**
3260
- * The largest `leaseMillis` among lanes with a cycle in flight (the
3261
- * default for none or 0, and for a running settle), capped.
3262
- */
3255
+ // The largest `leaseMillis` among lanes with a cycle in flight (the
3256
+ // default for none or 0, and for a running settle), capped.
3263
3257
  _derive_grace_ms(running, settling) {
3264
3258
  let max = settling ? DEFAULT_LEASE_MILLIS : 0;
3265
3259
  for (const c of running)
@@ -3288,7 +3282,7 @@ var Act = class {
3288
3282
  clearTimeout(timer);
3289
3283
  }
3290
3284
  }
3291
- /** Failures are logged, never thrown: `notify` is a hint. */
3285
+ // Failures are logged, never thrown: `notify` is a hint.
3292
3286
  async _wire_notify(s, listen) {
3293
3287
  if (!this._reactive) return void 0;
3294
3288
  if (!s.notify) return void 0;
@@ -3360,6 +3354,10 @@ var Act = class {
3360
3354
  if (!snapshots[0].patch) return snapshots;
3361
3355
  this._wake(snapshots.map((s) => s.event.name));
3362
3356
  this.emit("committed", snapshots);
3357
+ this._notify(
3358
+ `committed:${this.registry.actions[action2].name}`,
3359
+ snapshots
3360
+ );
3363
3361
  return snapshots;
3364
3362
  });
3365
3363
  }
@@ -3505,28 +3503,26 @@ var Act = class {
3505
3503
  return { fetched: [], leased: [], acked: [], blocked: [] };
3506
3504
  return this._scoped(() => this._drain_all(options));
3507
3505
  }
3508
- /** Arms correlate and every lane; false on an app with no reactions. */
3506
+ // Arms correlate and every lane; false on an app with no reactions.
3509
3507
  _arm_all() {
3510
3508
  if (!this._reactive) return false;
3511
3509
  this._correlate.arm();
3512
3510
  for (const c of this._drain_controllers.values()) c.arm();
3513
3511
  return true;
3514
3512
  }
3515
- /**
3516
- * Wakes what a change freed (a commit, a reset, an unblock, a restore,
3517
- * a notification from another process): arms the lanes these events
3518
- * react on, or every lane without names, and settles them unless the app
3519
- * walks the pipeline itself (`autoSettle: false`).
3520
- */
3513
+ // Wakes what a change freed (a commit, a reset, an unblock, a restore,
3514
+ // a notification from another process): arms the lanes these events
3515
+ // react on, or every lane without names, and settles them unless the app
3516
+ // walks the pipeline itself (`autoSettle: false`).
3521
3517
  _wake(names) {
3522
3518
  const armed = names ? this._arm_for_event_names(names) : this._arm_all();
3523
3519
  if (armed && this._auto_settle) this._schedule();
3524
3520
  }
3525
- /** Schedules a settle pass; writer-only instances (`drain: false`) skip it. */
3521
+ // Schedules a settle pass; writer-only instances (`drain: false`) skip it.
3526
3522
  _schedule(options) {
3527
3523
  if (this._drain) this._settle.schedule(options);
3528
3524
  }
3529
- /** Arms only the lanes these events react on; true when any was armed. */
3525
+ // Arms only the lanes these events react on; true when any was armed.
3530
3526
  _arm_for_event_names(names) {
3531
3527
  const to_arm = /* @__PURE__ */ new Set();
3532
3528
  for (const name of names) {
@@ -3543,7 +3539,7 @@ var Act = class {
3543
3539
  for (const lane of to_arm) this._drain_controllers.get(lane)?.arm();
3544
3540
  return true;
3545
3541
  }
3546
- /** In parallel, so a slow lane never holds up a fast one. */
3542
+ // In parallel, so a slow lane never holds up a fast one.
3547
3543
  async _drain_all(options) {
3548
3544
  const merged = merge_drains(
3549
3545
  await Promise.all(
@@ -3585,7 +3581,7 @@ var Act = class {
3585
3581
  last_id: pass.last_id
3586
3582
  };
3587
3583
  }
3588
- /** One correlate pass; arms every lane when it found work. */
3584
+ // One correlate pass; arms every lane when it found work.
3589
3585
  async _correlate_pass(limit, lease = false) {
3590
3586
  if (!this._drain) return { kind: "idle", last_id: -1 };
3591
3587
  return this._scoped(async () => {
@@ -3873,7 +3869,7 @@ var Act = class {
3873
3869
  async prioritize(filter, priority) {
3874
3870
  return this._scoped(() => store().prioritize(filter, priority));
3875
3871
  }
3876
- /** Shared by `close()` and reaction-requested closes. */
3872
+ // Shared by `close()` and reaction-requested closes.
3877
3873
  async _run_close(targets) {
3878
3874
  const close_actor = { id: "$close", name: "close" };
3879
3875
  const result = await run_close_cycle(targets, {
@@ -4643,6 +4639,24 @@ function act() {
4643
4639
  const fold_specs = [];
4644
4640
  const lanes = [];
4645
4641
  let _built = false;
4642
+ const advise_pii_types = () => {
4643
+ const fields = /* @__PURE__ */ new Set();
4644
+ for (const state2 of states.values()) {
4645
+ const shape = state2.state.shape ?? {};
4646
+ for (const [event, schema] of Object.entries(state2.events))
4647
+ for (const [field, field_schema] of Object.entries(
4648
+ pii_schemas(schema)
4649
+ )) {
4650
+ if (!reads_as_string(field_schema)) fields.add(`${event}.${field}`);
4651
+ if (shape[field] && !reads_as_string(shape[field]))
4652
+ fields.add(`${state2.name}.${field}`);
4653
+ }
4654
+ }
4655
+ if (fields.size > 0)
4656
+ log().warn(
4657
+ `Act has ${fields.size} sensitive field(s) whose type isn't a string: ${[...fields].join(", ")}. A caller who may not see one reads "[REDACTED]" or "[SHREDDED]" there instead, which its type doesn't allow. Declare sensitive fields as strings. See docs/docs/guides/sensitive-data.md.`
4658
+ );
4659
+ };
4646
4660
  const read_wrap = (original) => {
4647
4661
  const wrapped = async (events, stream) => {
4648
4662
  const read = events.map((e) => _hr.get(e.name)?.(e) ?? e);
@@ -4781,6 +4795,7 @@ function act() {
4781
4795
  for (const [name, gate] of built.query_readers) _qg.set(name, gate);
4782
4796
  for (const [name, gate] of built.handler_readers) _hr.set(name, gate);
4783
4797
  finalize_deprecations();
4798
+ advise_pii_types();
4784
4799
  for (const state2 of states.values()) {
4785
4800
  if (state2.disclose) _dp.set(state2.name, state2.disclose);
4786
4801
  if (state2.autoclose) _ac.set(state2.name, state2.autoclose);
@@ -4811,6 +4826,12 @@ function act() {
4811
4826
  };
4812
4827
  return builder;
4813
4828
  }
4829
+ function reads_as_string(schema) {
4830
+ const def = schema._zod.def;
4831
+ if (def.type === "string" || def.type === "transform") return true;
4832
+ if (def.out) return reads_as_string(def.out);
4833
+ return def.innerType ? reads_as_string(def.innerType) : false;
4834
+ }
4814
4835
 
4815
4836
  // src/builders/projection-builder.ts
4816
4837
  function _projection(target) {