@rotorsoft/act 1.36.0 → 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.
package/dist/index.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  sleep,
41
41
  store,
42
42
  validate
43
- } from "./chunk-P7FXHUMP.js";
43
+ } from "./chunk-BRJZQ4IY.js";
44
44
  import {
45
45
  ActorSchema,
46
46
  CausationEventSchema,
@@ -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;
@@ -3010,11 +2994,9 @@ var Act = class {
3010
2994
  throw new Error(`committed: "${name}" is not a state of this app`);
3011
2995
  return `committed:${name}`;
3012
2996
  }
3013
- /**
3014
- * The one place a store failure is logged and emitted (the breaker's
3015
- * `on_error`). The emit is guarded: an unhandled `"error"` emission
3016
- * would crash the process.
3017
- */
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.
3018
3000
  _emit_error(error, circuit) {
3019
3001
  this._logger.error(error);
3020
3002
  if (this._emitter.listenerCount("error") > 0)
@@ -3041,20 +3023,20 @@ var Act = class {
3041
3023
  _batch_handlers;
3042
3024
  _es;
3043
3025
  _cd;
3044
- /** Unambiguous: merge rejects an event name declared by two states. */
3026
+ // Unambiguous: merge rejects an event name declared by two states.
3045
3027
  _event_to_state;
3046
- /** 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.
3047
3029
  _event_to_lanes;
3048
3030
  _audit_deps;
3049
3031
  _logger = log();
3050
- /** This Act's ports: its own bag, or the singleton adapters. */
3032
+ // This Act's ports: its own bag, or the singleton adapters.
3051
3033
  _ports;
3052
- /** Runs a body so internal `store()`/`cache()` calls resolve to `_ports`. */
3034
+ // Runs a body so internal `store()`/`cache()` calls resolve to `_ports`.
3053
3035
  _scoped;
3054
3036
  _correlator;
3055
3037
  _lanes;
3056
3038
  _close_lock = new CloseLock();
3057
- /** 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.
3058
3040
  _declared_lanes;
3059
3041
  /** Drain lanes declared via `.withLane(...)`. Implicit default not included. */
3060
3042
  get lanes() {
@@ -3119,7 +3101,7 @@ var Act = class {
3119
3101
  );
3120
3102
  register_weak_disposer(new WeakRef(this), (self) => self.shutdown());
3121
3103
  }
3122
- /** Shared by drain, settle and autoclose; bad knobs throw at build. */
3104
+ // Shared by drain, settle and autoclose; bad knobs throw at build.
3123
3105
  _build_breaker(options) {
3124
3106
  return new CircuitBreaker(
3125
3107
  resolveCircuitBreakerConfig(options.circuitBreaker),
@@ -3131,7 +3113,7 @@ var Act = class {
3131
3113
  }
3132
3114
  );
3133
3115
  }
3134
- /** Apps that declare no lanes pass no lane filter to `claim`. */
3116
+ // Apps that declare no lanes pass no lane filter to `claim`.
3135
3117
  _build_drain_controllers(options, lanes, handle, handle_batch) {
3136
3118
  const all_lanes = this._declared_lanes;
3137
3119
  const only_set = options.onlyLanes?.length ? new Set(options.onlyLanes) : void 0;
@@ -3177,7 +3159,7 @@ var Act = class {
3177
3159
  }
3178
3160
  return controllers;
3179
3161
  }
3180
- /** 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.
3181
3163
  _advise_orphaned_lanes() {
3182
3164
  const active = new Set(this._drain_controllers.keys());
3183
3165
  const orphaned = this._declared_lanes.filter((name) => !active.has(name));
@@ -3187,7 +3169,7 @@ var Act = class {
3187
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.`
3188
3170
  );
3189
3171
  }
3190
- /** 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.
3191
3173
  _build_audit_deps() {
3192
3174
  return {
3193
3175
  store,
@@ -3216,7 +3198,7 @@ var Act = class {
3216
3198
  }
3217
3199
  });
3218
3200
  }
3219
- /** 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.
3220
3202
  async _seed_persisted_defers() {
3221
3203
  const now = Date.now();
3222
3204
  await walk_streams(store(), (pos) => {
@@ -3270,10 +3252,8 @@ var Act = class {
3270
3252
  }
3271
3253
  return this._shutdown_promise;
3272
3254
  }
3273
- /**
3274
- * The largest `leaseMillis` among lanes with a cycle in flight (the
3275
- * default for none or 0, and for a running settle), capped.
3276
- */
3255
+ // The largest `leaseMillis` among lanes with a cycle in flight (the
3256
+ // default for none or 0, and for a running settle), capped.
3277
3257
  _derive_grace_ms(running, settling) {
3278
3258
  let max = settling ? DEFAULT_LEASE_MILLIS : 0;
3279
3259
  for (const c of running)
@@ -3302,7 +3282,7 @@ var Act = class {
3302
3282
  clearTimeout(timer);
3303
3283
  }
3304
3284
  }
3305
- /** Failures are logged, never thrown: `notify` is a hint. */
3285
+ // Failures are logged, never thrown: `notify` is a hint.
3306
3286
  async _wire_notify(s, listen) {
3307
3287
  if (!this._reactive) return void 0;
3308
3288
  if (!s.notify) return void 0;
@@ -3523,28 +3503,26 @@ var Act = class {
3523
3503
  return { fetched: [], leased: [], acked: [], blocked: [] };
3524
3504
  return this._scoped(() => this._drain_all(options));
3525
3505
  }
3526
- /** 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.
3527
3507
  _arm_all() {
3528
3508
  if (!this._reactive) return false;
3529
3509
  this._correlate.arm();
3530
3510
  for (const c of this._drain_controllers.values()) c.arm();
3531
3511
  return true;
3532
3512
  }
3533
- /**
3534
- * Wakes what a change freed (a commit, a reset, an unblock, a restore,
3535
- * a notification from another process): arms the lanes these events
3536
- * react on, or every lane without names, and settles them unless the app
3537
- * walks the pipeline itself (`autoSettle: false`).
3538
- */
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`).
3539
3517
  _wake(names) {
3540
3518
  const armed = names ? this._arm_for_event_names(names) : this._arm_all();
3541
3519
  if (armed && this._auto_settle) this._schedule();
3542
3520
  }
3543
- /** Schedules a settle pass; writer-only instances (`drain: false`) skip it. */
3521
+ // Schedules a settle pass; writer-only instances (`drain: false`) skip it.
3544
3522
  _schedule(options) {
3545
3523
  if (this._drain) this._settle.schedule(options);
3546
3524
  }
3547
- /** 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.
3548
3526
  _arm_for_event_names(names) {
3549
3527
  const to_arm = /* @__PURE__ */ new Set();
3550
3528
  for (const name of names) {
@@ -3561,7 +3539,7 @@ var Act = class {
3561
3539
  for (const lane of to_arm) this._drain_controllers.get(lane)?.arm();
3562
3540
  return true;
3563
3541
  }
3564
- /** 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.
3565
3543
  async _drain_all(options) {
3566
3544
  const merged = merge_drains(
3567
3545
  await Promise.all(
@@ -3603,7 +3581,7 @@ var Act = class {
3603
3581
  last_id: pass.last_id
3604
3582
  };
3605
3583
  }
3606
- /** One correlate pass; arms every lane when it found work. */
3584
+ // One correlate pass; arms every lane when it found work.
3607
3585
  async _correlate_pass(limit, lease = false) {
3608
3586
  if (!this._drain) return { kind: "idle", last_id: -1 };
3609
3587
  return this._scoped(async () => {
@@ -3891,7 +3869,7 @@ var Act = class {
3891
3869
  async prioritize(filter, priority) {
3892
3870
  return this._scoped(() => store().prioritize(filter, priority));
3893
3871
  }
3894
- /** Shared by `close()` and reaction-requested closes. */
3872
+ // Shared by `close()` and reaction-requested closes.
3895
3873
  async _run_close(targets) {
3896
3874
  const close_actor = { id: "$close", name: "close" };
3897
3875
  const result = await run_close_cycle(targets, {
@@ -4661,6 +4639,24 @@ function act() {
4661
4639
  const fold_specs = [];
4662
4640
  const lanes = [];
4663
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
+ };
4664
4660
  const read_wrap = (original) => {
4665
4661
  const wrapped = async (events, stream) => {
4666
4662
  const read = events.map((e) => _hr.get(e.name)?.(e) ?? e);
@@ -4799,6 +4795,7 @@ function act() {
4799
4795
  for (const [name, gate] of built.query_readers) _qg.set(name, gate);
4800
4796
  for (const [name, gate] of built.handler_readers) _hr.set(name, gate);
4801
4797
  finalize_deprecations();
4798
+ advise_pii_types();
4802
4799
  for (const state2 of states.values()) {
4803
4800
  if (state2.disclose) _dp.set(state2.name, state2.disclose);
4804
4801
  if (state2.autoclose) _ac.set(state2.name, state2.autoclose);
@@ -4829,6 +4826,12 @@ function act() {
4829
4826
  };
4830
4827
  return builder;
4831
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
+ }
4832
4835
 
4833
4836
  // src/builders/projection-builder.ts
4834
4837
  function _projection(target) {