@rotorsoft/act 1.32.26 → 1.32.28

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 (57) hide show
  1. package/README.md +2 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/act.d.ts +7 -152
  4. package/dist/@types/act.d.ts.map +1 -1
  5. package/dist/@types/adapters/in-memory-store.d.ts +0 -21
  6. package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
  7. package/dist/@types/builders/act-builder.d.ts +0 -25
  8. package/dist/@types/builders/act-builder.d.ts.map +1 -1
  9. package/dist/@types/builders/build-classify.d.ts +4 -4
  10. package/dist/@types/builders/build-classify.d.ts.map +1 -1
  11. package/dist/@types/builders/projection-builder.d.ts.map +1 -1
  12. package/dist/@types/builders/reaction-builder.d.ts +3 -3
  13. package/dist/@types/builders/reaction-builder.d.ts.map +1 -1
  14. package/dist/@types/builders/slice-builder.d.ts +1 -12
  15. package/dist/@types/builders/slice-builder.d.ts.map +1 -1
  16. package/dist/@types/builders/state-builder.d.ts +0 -189
  17. package/dist/@types/builders/state-builder.d.ts.map +1 -1
  18. package/dist/@types/config.d.ts +0 -27
  19. package/dist/@types/config.d.ts.map +1 -1
  20. package/dist/@types/internal/circuit-breaker.d.ts +2 -0
  21. package/dist/@types/internal/circuit-breaker.d.ts.map +1 -1
  22. package/dist/@types/internal/close-cycle.d.ts +2 -1
  23. package/dist/@types/internal/close-cycle.d.ts.map +1 -1
  24. package/dist/@types/internal/drain-cycle.d.ts +13 -4
  25. package/dist/@types/internal/drain-cycle.d.ts.map +1 -1
  26. package/dist/@types/internal/index.d.ts +1 -1
  27. package/dist/@types/internal/index.d.ts.map +1 -1
  28. package/dist/@types/internal/settle.d.ts +2 -1
  29. package/dist/@types/internal/settle.d.ts.map +1 -1
  30. package/dist/@types/ports.d.ts +0 -7
  31. package/dist/@types/ports.d.ts.map +1 -1
  32. package/dist/@types/test/sandbox.d.ts +0 -14
  33. package/dist/@types/test/sandbox.d.ts.map +1 -1
  34. package/dist/@types/types/action.d.ts +0 -27
  35. package/dist/@types/types/action.d.ts.map +1 -1
  36. package/dist/@types/types/errors.d.ts +1 -92
  37. package/dist/@types/types/errors.d.ts.map +1 -1
  38. package/dist/@types/types/ports.d.ts +0 -70
  39. package/dist/@types/types/ports.d.ts.map +1 -1
  40. package/dist/@types/types/reaction.d.ts +1 -34
  41. package/dist/@types/types/reaction.d.ts.map +1 -1
  42. package/dist/{chunk-QXW3JV2H.js → chunk-5FXT63KF.js} +2 -2
  43. package/dist/chunk-5FXT63KF.js.map +1 -0
  44. package/dist/{chunk-XSG2NW4J.js → chunk-F7K7SIG6.js} +1 -1
  45. package/dist/chunk-F7K7SIG6.js.map +1 -0
  46. package/dist/index.cjs +83 -215
  47. package/dist/index.cjs.map +1 -1
  48. package/dist/index.js +85 -217
  49. package/dist/index.js.map +1 -1
  50. package/dist/test/index.cjs.map +1 -1
  51. package/dist/test/index.js +2 -2
  52. package/dist/test/index.js.map +1 -1
  53. package/dist/types/index.cjs.map +1 -1
  54. package/dist/types/index.js +1 -1
  55. package/package.json +1 -4
  56. package/dist/chunk-QXW3JV2H.js.map +0 -1
  57. package/dist/chunk-XSG2NW4J.js.map +0 -1
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  sleep,
27
27
  store,
28
28
  validate
29
- } from "./chunk-QXW3JV2H.js";
29
+ } from "./chunk-5FXT63KF.js";
30
30
  import {
31
31
  ActorSchema,
32
32
  CausationEventSchema,
@@ -53,7 +53,7 @@ import {
53
53
  pii_split,
54
54
  pii_strip,
55
55
  sensitive
56
- } from "./chunk-XSG2NW4J.js";
56
+ } from "./chunk-F7K7SIG6.js";
57
57
 
58
58
  // src/signals.ts
59
59
  process.once("SIGINT", async (arg) => {
@@ -81,10 +81,8 @@ var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
81
81
  function classify_registry(registry, states) {
82
82
  const statics = /* @__PURE__ */ new Map();
83
83
  const target_lanes = /* @__PURE__ */ new Map();
84
- const reactive_events = /* @__PURE__ */ new Set();
85
84
  const event_to_lanes = /* @__PURE__ */ new Map();
86
85
  for (const [name, register] of Object.entries(registry.events)) {
87
- if (register.reactions.size > 0) reactive_events.add(name);
88
86
  for (const reaction of register.reactions.values()) {
89
87
  if (typeof reaction.resolver === "function") {
90
88
  event_to_lanes.set(name, ALL_LANES);
@@ -122,7 +120,6 @@ function classify_registry(registry, states) {
122
120
  }
123
121
  return {
124
122
  static_targets: [...statics.values()],
125
- reactive_events,
126
123
  event_to_state,
127
124
  event_to_lanes
128
125
  };
@@ -1135,6 +1132,10 @@ var CircuitBreaker = class {
1135
1132
  get failing() {
1136
1133
  return this._failures > 0;
1137
1134
  }
1135
+ /** How long the store is left alone after a failure before a retry. */
1136
+ get cooldown_ms() {
1137
+ return this._cooldown_ms;
1138
+ }
1138
1139
  /** Current state given the wall-clock `now`. */
1139
1140
  state(now) {
1140
1141
  if (this._opened_at === void 0) return "closed";
@@ -1279,7 +1280,7 @@ async function run_full_closes(full, target_map, deps, skipped) {
1279
1280
  }
1280
1281
  const safe = await partition_by_safety(
1281
1282
  stream_info,
1282
- deps.reactive_events_size,
1283
+ deps.reactive,
1283
1284
  skipped,
1284
1285
  deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
1285
1286
  deps.catch_up_correlation
@@ -1316,8 +1317,8 @@ async function run_full_closes(full, target_map, deps, skipped) {
1316
1317
  );
1317
1318
  }
1318
1319
  async function run_windowed_closes(windowed, deps, skipped) {
1319
- const checkpoint = deps.reactive_events_size > 0 ? await deps.catch_up_correlation(Number.MAX_SAFE_INTEGER) : -1;
1320
- const min_at = deps.reactive_events_size > 0 ? await probe_min_watermarks(
1320
+ const checkpoint = deps.reactive ? await deps.catch_up_correlation(Number.MAX_SAFE_INTEGER) : -1;
1321
+ const min_at = deps.reactive ? await probe_min_watermarks(
1321
1322
  windowed.map((t) => t.stream),
1322
1323
  deps.probe_page_size ?? SAFETY_PROBE_PAGE_SIZE,
1323
1324
  checkpoint
@@ -1405,8 +1406,8 @@ async function scan_stream_heads(streams) {
1405
1406
  }
1406
1407
  return out;
1407
1408
  }
1408
- async function partition_by_safety(stream_info, reactive_events_size, skipped, page_size, catch_up_correlation2) {
1409
- if (reactive_events_size === 0) return [...stream_info.keys()];
1409
+ async function partition_by_safety(stream_info, reactive, skipped, page_size, catch_up_correlation2) {
1410
+ if (!reactive) return [...stream_info.keys()];
1410
1411
  let needed = -1;
1411
1412
  for (const info of stream_info.values())
1412
1413
  needed = Math.max(needed, info.max_id);
@@ -2685,20 +2686,24 @@ function fetch_failure_policy(registry) {
2685
2686
  }
2686
2687
  return { blockOnError, maxRetries };
2687
2688
  }
2688
- function resolver_failed(lease, { error, options }) {
2689
- log().error(error);
2690
- const block2 = options.blockOnError && lease.retry >= options.maxRetries;
2689
+ function finalize(lease, handled, at, error, options, logger, failed_at) {
2690
+ if (!error) return { lease, handled, acked_at: at };
2691
+ logger.error(error);
2692
+ const non_retryable = error instanceof NonRetryableError;
2693
+ const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
2694
+ if (block2)
2695
+ logger.error(
2696
+ non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
2697
+ );
2698
+ const next_attempt_at = block2 ? void 0 : Date.now() + compute_backoff_delay(lease.retry, options.backoff ?? DEFAULT_BACKOFF);
2691
2699
  return {
2692
2700
  lease,
2693
- handled: 0,
2694
- acked_at: lease.at,
2695
- error,
2696
- ...block2 ? { block: true } : {
2697
- next_attempt_at: Date.now() + compute_backoff_delay(
2698
- lease.retry,
2699
- options.backoff ?? DEFAULT_BACKOFF
2700
- )
2701
- }
2701
+ handled,
2702
+ acked_at: at,
2703
+ error: error.message,
2704
+ block: block2,
2705
+ next_attempt_at,
2706
+ failed_at
2702
2707
  };
2703
2708
  }
2704
2709
  function warn_misrouted(seen, stream, handler, event) {
@@ -2746,7 +2751,9 @@ async function run_drain_cycle(deps, cycle) {
2746
2751
  resolved = dynamic ? resolver(event) : resolver;
2747
2752
  } catch (error) {
2748
2753
  failed ??= {
2749
- error: `Resolver of reaction "${reaction.handler.name}" threw on ${String(event.name)} #${event.id} (stream "${event.stream}"): ${String(error)}`,
2754
+ error: new Error(
2755
+ `Resolver of reaction "${reaction.handler.name}" threw on ${String(event.name)} #${event.id} (stream "${event.stream}"): ${String(error)}`
2756
+ ),
2750
2757
  options: reaction.options
2751
2758
  };
2752
2759
  return false;
@@ -2768,23 +2775,29 @@ async function run_drain_cycle(deps, cycle) {
2768
2775
  leased.map((lease) => {
2769
2776
  const entry = fetch_map.get(lease.stream);
2770
2777
  if (entry.fetch.error !== void 0) {
2771
- const error = `Fetch failed for ${lease.stream}: ${entry.fetch.error}`;
2772
- log().error(error);
2773
2778
  const policy = fetch_failure_policy(registry);
2774
- const block2 = !store_failing && policy.blockOnError && lease.retry >= policy.maxRetries;
2775
- const failed = {
2776
- lease,
2777
- handled: 0,
2778
- acked_at: lease.at,
2779
- error,
2780
- ...block2 ? { block: true } : {
2781
- next_attempt_at: Date.now() + compute_backoff_delay(lease.retry, DEFAULT_BACKOFF)
2782
- }
2783
- };
2784
- return Promise.resolve(failed);
2779
+ return Promise.resolve(
2780
+ finalize(
2781
+ lease,
2782
+ 0,
2783
+ lease.at,
2784
+ new Error(`Fetch failed for ${lease.stream}: ${entry.fetch.error}`),
2785
+ store_failing ? { ...policy, blockOnError: false } : policy,
2786
+ deps.logger
2787
+ )
2788
+ );
2785
2789
  }
2786
2790
  if (entry.failed)
2787
- return Promise.resolve(resolver_failed(lease, entry.failed));
2791
+ return Promise.resolve(
2792
+ finalize(
2793
+ lease,
2794
+ 0,
2795
+ lease.at,
2796
+ entry.failed.error,
2797
+ entry.failed.options,
2798
+ deps.logger
2799
+ )
2800
+ );
2788
2801
  const at = entry.fetch.events.at(-1)?.id || fetch_window_at;
2789
2802
  const { payloads } = entry;
2790
2803
  const exhausted = budget_exhausted(
@@ -2823,18 +2836,23 @@ var EMPTY_DRAIN = {
2823
2836
  acked: [],
2824
2837
  blocked: []
2825
2838
  };
2839
+ var RETRY = "";
2826
2840
  var DrainController = class {
2827
2841
  _armed = false;
2828
2842
  _locked = false;
2829
2843
  _ratio = 0.5;
2830
2844
  /**
2831
- * Local wake at the earliest parked (backoff or defer) visit; the schedule
2832
- * itself is persisted. On an idle app nothing else would drain it. A due
2833
- * entry stays until handled or until a later pass claims nothing.
2845
+ * Local wake at the earliest parked (backoff or defer) visit, or at the
2846
+ * lease expiry after a failed pass (the {@link RETRY} key); the schedule
2847
+ * itself is persisted. On an idle app nothing else would drain it. It
2848
+ * drains until a pass leases nothing, and an entry stays until handled or
2849
+ * until a later pass claims nothing.
2834
2850
  */
2835
2851
  _defer = new DeferTimer(() => {
2836
2852
  this._armed = true;
2837
- void this._deps.run_scoped(() => this.drain());
2853
+ void this._deps.run_scoped(async () => {
2854
+ while ((await this.drain()).leased.length > 0) ;
2855
+ });
2838
2856
  });
2839
2857
  _worker;
2840
2858
  _misrouted = /* @__PURE__ */ new Set();
@@ -2931,10 +2949,12 @@ var DrainController = class {
2931
2949
  if (acked.length) this._deps.on_acked(acked);
2932
2950
  if (closeable.length) await this._deps.on_close(closeable);
2933
2951
  this._deps.breaker.passed();
2934
- const has_errors = handled.some(({ error }) => error);
2935
- if (!acked.length && !blocked.length && !has_errors) this._armed = false;
2936
2952
  return { fetched, leased, acked, blocked };
2937
2953
  } catch (error) {
2954
+ this._defer.set(
2955
+ RETRY,
2956
+ Date.now() + Math.max(leaseMillis, this._deps.breaker.cooldown_ms)
2957
+ );
2938
2958
  this._deps.breaker.failed(Date.now(), error);
2939
2959
  return EMPTY_DRAIN;
2940
2960
  } finally {
@@ -3058,7 +3078,8 @@ var SettleLoop = class {
3058
3078
  /**
3059
3079
  * Schedule a settle pass. Multiple calls inside the debounce window
3060
3080
  * coalesce into one cycle. The cycle runs correlate→drain in a loop
3061
- * until no progress is made (no new subscriptions, no acks, no blocks)
3081
+ * until no progress is made (no new subscriptions, no events read, no
3082
+ * streams leased)
3062
3083
  * or `maxPasses` is reached, then emits the `"settled"` lifecycle event
3063
3084
  * via {@link SettleDeps.on_settled}.
3064
3085
  */
@@ -3098,7 +3119,7 @@ var SettleLoop = class {
3098
3119
  acked: [...settled_drain.acked, ...drain.acked],
3099
3120
  blocked: [...settled_drain.blocked, ...drain.blocked]
3100
3121
  } : drain;
3101
- const made_progress = subscribed > 0 || drain.acked.length > 0 || drain.blocked.length > 0 || last_id > after_before;
3122
+ const made_progress = subscribed > 0 || drain.leased.length > 0 || last_id > after_before;
3102
3123
  if (!made_progress) break;
3103
3124
  }
3104
3125
  if (settled_drain) this._deps.on_settled(settled_drain);
@@ -3135,26 +3156,6 @@ var SettleLoop = class {
3135
3156
  };
3136
3157
 
3137
3158
  // src/builders/reaction-builder.ts
3138
- function finalize(lease, handled, at, error, options, logger, failed_at) {
3139
- if (!error) return { lease, handled, acked_at: at };
3140
- logger.error(error);
3141
- const non_retryable = error instanceof NonRetryableError;
3142
- const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
3143
- if (block2)
3144
- logger.error(
3145
- non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
3146
- );
3147
- const next_attempt_at = block2 ? void 0 : Date.now() + compute_backoff_delay(lease.retry, options.backoff ?? DEFAULT_BACKOFF);
3148
- return {
3149
- lease,
3150
- handled,
3151
- acked_at: at,
3152
- error: error.message,
3153
- block: block2,
3154
- next_attempt_at,
3155
- failed_at
3156
- };
3157
- }
3158
3159
  function build_handle(deps) {
3159
3160
  const { logger, reaction_scope } = deps;
3160
3161
  return async (lease, payloads) => {
@@ -3264,7 +3265,8 @@ var Act = class {
3264
3265
  _breaker;
3265
3266
  _listen;
3266
3267
  _drain;
3267
- _reactive_events;
3268
+ /** Whether any event has a reaction; nothing arms without one. */
3269
+ _reactive;
3268
3270
  _drain_controllers;
3269
3271
  _correlate;
3270
3272
  _settle;
@@ -3394,7 +3396,7 @@ var Act = class {
3394
3396
  });
3395
3397
  this._handle_batch = build_handle_batch(this._logger);
3396
3398
  const classification = classify_registry(this.registry, this._states);
3397
- this._reactive_events = classification.reactive_events;
3399
+ this._reactive = classification.event_to_lanes.size > 0;
3398
3400
  this._event_to_state = classification.event_to_state;
3399
3401
  this._event_to_lanes = classification.event_to_lanes;
3400
3402
  this._listen = options.listen !== false;
@@ -3501,9 +3503,8 @@ var Act = class {
3501
3503
  declared_lanes: new Set(this._declared_lanes),
3502
3504
  // Built after the controllers, so their keys are the active lanes.
3503
3505
  shard: this._drain_controllers.size < this._declared_lanes.length ? [...this._drain_controllers.keys()] : void 0,
3504
- on_init: () => {
3505
- if (this._drain && this._reactive_events.size > 0) this._arm_all();
3506
- },
3506
+ // Only reached on a draining app: writer-only ones never correlate.
3507
+ on_init: () => this._arm_all(),
3507
3508
  on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
3508
3509
  });
3509
3510
  }
@@ -3600,7 +3601,7 @@ var Act = class {
3600
3601
  }
3601
3602
  /** Failures are logged, never thrown: `notify` is a hint. */
3602
3603
  async _wire_notify(s) {
3603
- if (this._reactive_events.size === 0) return void 0;
3604
+ if (!this._reactive) return void 0;
3604
3605
  if (!s.notify) return void 0;
3605
3606
  if (!this._listen) return void 0;
3606
3607
  try {
@@ -3660,46 +3661,6 @@ var Act = class {
3660
3661
  * console.log(snapshots[0].state.count); // Current count after increment
3661
3662
  * ```
3662
3663
  *
3663
- * @example With error handling
3664
- * ```typescript
3665
- * try {
3666
- * await app.do(
3667
- * "withdraw",
3668
- * { stream: "account-123", actor: { id: "user1", name: "Alice" } },
3669
- * { amount: 1000 }
3670
- * );
3671
- * } catch (error) {
3672
- * if (error instanceof InvariantError) {
3673
- * console.error("Business rule violated:", error.description);
3674
- * } else if (error instanceof ConcurrencyError) {
3675
- * console.error("Concurrent modification detected, retry...");
3676
- * } else if (error instanceof ValidationError) {
3677
- * console.error("Invalid payload:", error.details);
3678
- * }
3679
- * }
3680
- * ```
3681
- *
3682
- * @example Reaction triggering another action (reactingTo auto-injected)
3683
- * ```typescript
3684
- * const app = act()
3685
- * .withState(Order)
3686
- * .withState(Inventory)
3687
- * .on("OrderPlaced")
3688
- * .do(async function reduceInventory(event, _stream, app) {
3689
- * // Inside reaction handlers, reactingTo is auto-injected when omitted.
3690
- * // The triggering event is used by default, maintaining the correlation chain.
3691
- * await app.do(
3692
- * "reduceStock",
3693
- * { stream: "inventory-1", actor: { id: "sys", name: "system" } },
3694
- * { amount: event.data.items.length }
3695
- * );
3696
- * // To use a different correlation, pass reactingTo explicitly:
3697
- * // await app.do("reduceStock", target, payload, { reactingTo: customEvent });
3698
- * })
3699
- * .to("inventory-1")
3700
- * .build();
3701
- * ```
3702
- *
3703
3664
  * @see {@link Target} for target structure
3704
3665
  * @see {@link Snapshot} for return value structure
3705
3666
  * @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
@@ -3715,10 +3676,9 @@ var Act = class {
3715
3676
  payload,
3716
3677
  do_options
3717
3678
  );
3718
- if (this._reactive_events.size > 0)
3719
- this._arm_for_event_names(
3720
- snapshots.map((s) => s.event.name)
3721
- );
3679
+ this._arm_for_event_names(
3680
+ snapshots.map((s) => s.event.name)
3681
+ );
3722
3682
  this.emit("committed", snapshots);
3723
3683
  return snapshots;
3724
3684
  });
@@ -3765,26 +3725,6 @@ var Act = class {
3765
3725
  * console.log(`Found ${count} events from ${first?.id} to ${last?.id}`);
3766
3726
  * ```
3767
3727
  *
3768
- * @example Query specific event types
3769
- * ```typescript
3770
- * const { count } = await app.query(
3771
- * { name: "UserCreated", limit: 100 },
3772
- * (event) => {
3773
- * console.log("User created:", event.data.email);
3774
- * }
3775
- * );
3776
- * ```
3777
- *
3778
- * @example Query events in time range
3779
- * ```typescript
3780
- * const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
3781
- * const { count } = await app.query({
3782
- * created_after: yesterday,
3783
- * stream: "user-123"
3784
- * });
3785
- * console.log(`User had ${count} events in last 24 hours`);
3786
- * ```
3787
- *
3788
3728
  * @see {@link query_array} for loading events into memory
3789
3729
  */
3790
3730
  async query(query, callback) {
@@ -3816,14 +3756,6 @@ var Act = class {
3816
3756
  * events.forEach(event => console.log(event.name, event.data));
3817
3757
  * ```
3818
3758
  *
3819
- * @example Get recent events
3820
- * ```typescript
3821
- * const recent = await app.query_array({
3822
- * stream: "user-123",
3823
- * limit: 10
3824
- * });
3825
- * ```
3826
- *
3827
3759
  * @see {@link query} for large result sets
3828
3760
  */
3829
3761
  async query_array(query) {
@@ -3894,12 +3826,6 @@ var Act = class {
3894
3826
  * await app.drain();
3895
3827
  * ```
3896
3828
  *
3897
- * @example In production, prefer settle()
3898
- * ```typescript
3899
- * await app.do("CreateItem", target, input);
3900
- * app.settle(); // debounced correlate→drain, emits "settled"
3901
- * ```
3902
- *
3903
3829
  * @see {@link settle} for debounced correlate→drain with lifecycle events
3904
3830
  * @see {@link correlate} for dynamic stream discovery
3905
3831
  * @see {@link start_correlations} for automatic correlation
@@ -3911,6 +3837,7 @@ var Act = class {
3911
3837
  return this._scoped(() => this._drain_all(options));
3912
3838
  }
3913
3839
  _arm_all() {
3840
+ if (!this._reactive) return;
3914
3841
  this._correlate.arm();
3915
3842
  for (const c of this._drain_controllers.values()) c.arm();
3916
3843
  }
@@ -3966,37 +3893,20 @@ var Act = class {
3966
3893
  * on reaction resolvers. It then registers these streams so they'll be picked up by
3967
3894
  * the next drain cycle.
3968
3895
  *
3969
- * @param query - Query filter to scan for new correlations
3970
3896
  * @param query - Scan filter — see {@link Query} for fields (typically
3971
3897
  * `{ after: <event-id>, limit: <count> }`)
3972
- * @returns Object with newly leased streams and last scanned event ID
3898
+ * @returns The number of newly subscribed streams and the last scanned event id
3973
3899
  *
3974
3900
  * @example Manual correlation
3975
3901
  * ```typescript
3976
3902
  * // Scan for new streams
3977
- * const { leased, last_id } = await app.correlate({ after: 0, limit: 100 });
3978
- * console.log(`Found ${leased.length} new streams`);
3903
+ * const { subscribed, last_id } = await app.correlate({ after: 0, limit: 100 });
3904
+ * console.log(`Subscribed ${subscribed} new streams`);
3979
3905
  *
3980
3906
  * // Save last_id for next scan
3981
3907
  * await saveCheckpoint(last_id);
3982
3908
  * ```
3983
3909
  *
3984
- * @example Dynamic stream creation
3985
- * ```typescript
3986
- * const app = act()
3987
- * .withState(User)
3988
- * .withState(UserStats)
3989
- * .on("UserLoggedIn")
3990
- * .do(async (event) => ["incrementLoginCount", {}])
3991
- * .to((event) => ({
3992
- * target: `stats-${event.stream}` // Dynamic target per user
3993
- * }))
3994
- * .build();
3995
- *
3996
- * // Discover stats streams as users log in
3997
- * await app.correlate();
3998
- * ```
3999
- *
4000
3910
  * @see {@link start_correlations} for automatic periodic correlation
4001
3911
  * @see {@link stop_correlations} to stop automatic correlation
4002
3912
  */
@@ -4009,8 +3919,7 @@ var Act = class {
4009
3919
  if (!this._drain) return { subscribed: 0, last_id: -1, scanned: false };
4010
3920
  return this._scoped(async () => {
4011
3921
  const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query, lease);
4012
- if ((subscribed > 0 || marked > 0) && this._reactive_events.size > 0)
4013
- this._arm_all();
3922
+ if (subscribed > 0 || marked > 0) this._arm_all();
4014
3923
  if (subscribed > 0) this._on_discovered?.(subscribed);
4015
3924
  return { subscribed, last_id, scanned };
4016
3925
  });
@@ -4102,12 +4011,6 @@ var Act = class {
4102
4011
  * app.settle({ eventLimit: 1000 }); // emits "settled" when fully replayed
4103
4012
  * ```
4104
4013
  *
4105
- * @example Rebuild a projection (tests / scripts)
4106
- * ```typescript
4107
- * await app.reset(["my-projection"]);
4108
- * await app.drain({ eventLimit: 1000 }); // small streams: one pass is enough
4109
- * ```
4110
- *
4111
4014
  * @see {@link Store.reset} for the underlying store primitive
4112
4015
  * @see {@link settle} for the debounced full-catch-up loop
4113
4016
  */
@@ -4116,7 +4019,7 @@ var Act = class {
4116
4019
  const count = await store().reset(input);
4117
4020
  for (const handler of this._batch_handlers.values())
4118
4021
  handler[FOLD_RESET]?.();
4119
- if (count > 0 && this._reactive_events.size > 0) this._arm_all();
4022
+ if (count > 0) this._arm_all();
4120
4023
  return count;
4121
4024
  });
4122
4025
  }
@@ -4150,7 +4053,7 @@ var Act = class {
4150
4053
  async unblock(input) {
4151
4054
  return this._scoped(async () => {
4152
4055
  const count = await store().unblock(input);
4153
- if (count > 0 && this._reactive_events.size > 0) this._arm_all();
4056
+ if (count > 0) this._arm_all();
4154
4057
  return count;
4155
4058
  });
4156
4059
  }
@@ -4186,11 +4089,6 @@ var Act = class {
4186
4089
  * await cache().clear(); // operator's responsibility
4187
4090
  * ```
4188
4091
  *
4189
- * @example Copy one store into another
4190
- * ```typescript
4191
- * await app.restore(pg, {}, sqlite);
4192
- * ```
4193
- *
4194
4092
  * @see {@link Store.restore} for the underlying driver-pattern primitive.
4195
4093
  */
4196
4094
  async restore(source, opts = {}, sink) {
@@ -4329,16 +4227,6 @@ var Act = class {
4329
4227
  * await app.prioritize({ stream: "^proj-orders$", stream_exact: false }, 10);
4330
4228
  * ```
4331
4229
  *
4332
- * @example Drop all audit projections to background
4333
- * ```typescript
4334
- * await app.prioritize({ source: "^audit-" }, -5);
4335
- * ```
4336
- *
4337
- * @example Reset everyone to default
4338
- * ```typescript
4339
- * await app.prioritize({}, 0);
4340
- * ```
4341
- *
4342
4230
  * @see {@link Store.prioritize} for the underlying primitive
4343
4231
  * @see {@link claim} for how priority biases scheduling
4344
4232
  */
@@ -4349,7 +4237,7 @@ var Act = class {
4349
4237
  async _run_close(targets) {
4350
4238
  const close_actor = { id: "$close", name: "close" };
4351
4239
  const result = await run_close_cycle(targets, {
4352
- reactive_events_size: this._reactive_events.size,
4240
+ reactive: this._reactive,
4353
4241
  catch_up_correlation: (until) => catch_up_correlation(
4354
4242
  this._correlate,
4355
4243
  (limit) => this.correlate({ limit }),
@@ -4402,27 +4290,6 @@ var Act = class {
4402
4290
  * { stream: "order-456" },
4403
4291
  * ]);
4404
4292
  * ```
4405
- *
4406
- * @example Close with restart (state loaded automatically after guard)
4407
- * ```typescript
4408
- * await app.close([
4409
- * { stream: "counter-1", restart: true },
4410
- * { stream: "counter-2" }, // tombstoned
4411
- * ]);
4412
- * ```
4413
- *
4414
- * @example Windowed close — keep the last 180 days of real events
4415
- * ```typescript
4416
- * const cutoff = new Date();
4417
- * cutoff.setDate(cutoff.getDate() - 180);
4418
- * await app.close([
4419
- * {
4420
- * stream: "ledger-acme",
4421
- * before: cutoff,
4422
- * archive: async () => { await archiveToS3("ledger-acme", cutoff); },
4423
- * },
4424
- * ]);
4425
- * ```
4426
4293
  */
4427
4294
  async close(targets) {
4428
4295
  if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
@@ -4434,7 +4301,8 @@ var Act = class {
4434
4301
  * Call this after `app.do()` (or `app.reset()`) to schedule a background
4435
4302
  * drain. Multiple rapid calls within the debounce window are coalesced
4436
4303
  * into a single cycle. Runs correlate→drain in a loop until a pass makes
4437
- * no progress — no new subscriptions, no acks, no blocks — then emits
4304
+ * no progress — no new subscriptions, no events read, no streams leased —
4305
+ * then emits
4438
4306
  * the `"settled"` lifecycle event. This means a single `settle()` call
4439
4307
  * fully catches up paginated streams (e.g. after `reset()` on a long
4440
4308
  * projection) without forcing callers to loop.