@rotorsoft/act 1.32.11 → 1.32.13

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
@@ -26,7 +26,7 @@ import {
26
26
  sleep,
27
27
  store,
28
28
  validate
29
- } from "./chunk-UVFP2BQ3.js";
29
+ } from "./chunk-FLAGQMWE.js";
30
30
  import {
31
31
  ActorSchema,
32
32
  CausationEventSchema,
@@ -851,6 +851,151 @@ var CloseSignal = class extends Error {
851
851
  }
852
852
  };
853
853
 
854
+ // src/internal/config.ts
855
+ import { z as z2 } from "zod";
856
+ var BackoffOptionsSchema = z2.object({
857
+ strategy: z2.enum(["fixed", "linear", "exponential"]),
858
+ baseMs: z2.number().min(0),
859
+ maxMs: z2.number().gt(0).optional(),
860
+ jitter: z2.boolean().optional()
861
+ });
862
+ var DEFAULT_MAX_RETRIES = 3;
863
+ var DEFAULT_BLOCK_ON_ERROR = true;
864
+ var ReactionOptionsSchema = z2.object({
865
+ blockOnError: z2.boolean(),
866
+ maxRetries: z2.number().int().min(0),
867
+ backoff: BackoffOptionsSchema.optional()
868
+ });
869
+ function resolveReactionConfig(options) {
870
+ return ReactionOptionsSchema.parse(options);
871
+ }
872
+ var ActionOptionsSchema = z2.object({
873
+ maxRetries: z2.number().int().min(0).optional(),
874
+ backoff: BackoffOptionsSchema.optional()
875
+ });
876
+ function resolveActionConfig(options) {
877
+ return ActionOptionsSchema.parse(options);
878
+ }
879
+ var LaneConfigSchema = z2.object({
880
+ name: z2.string().min(1),
881
+ leaseMillis: z2.number().min(0).optional(),
882
+ streamLimit: z2.number().int().min(0).optional(),
883
+ cycleMs: z2.number().min(0).optional()
884
+ });
885
+ function resolveLaneConfig(options) {
886
+ LaneConfigSchema.parse(options);
887
+ return options;
888
+ }
889
+ var DEFAULT_STREAM_LIMIT = 10;
890
+ var DEFAULT_EVENT_LIMIT = 10;
891
+ var DEFAULT_LEASE_MILLIS = 1e4;
892
+ var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
893
+ var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
894
+ var DrainOptionsSchema = z2.object({
895
+ streamLimit: z2.number().int().min(0).optional(),
896
+ eventLimit: z2.number().int().min(0).optional(),
897
+ leaseMillis: z2.number().min(0).optional()
898
+ });
899
+ function resolveDrainConfig(options) {
900
+ if (options === void 0) return void 0;
901
+ DrainOptionsSchema.parse(options);
902
+ return options;
903
+ }
904
+ var SettleOptionsSchema = DrainOptionsSchema.extend({
905
+ debounceMs: z2.number().min(0).optional(),
906
+ // `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
907
+ // a non-negative integer — `0` is legal and means "run no passes".
908
+ //
909
+ // `Infinity` is accepted explicitly. It is the documented default
910
+ // (`Act.settle`'s doc-comment and `SettleOptions.maxPasses` both name it),
911
+ // and `settle.ts` destructures `maxPasses = Infinity`, so the one value the
912
+ // docs advertise was the one value `z.number().int()` refused — Zod 4
913
+ // rejects `±Infinity`, and an integer check cannot pass it either. Wrapper
914
+ // code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)
915
+ // is the natural way to hit it. Accepting it also honors this module's own
916
+ // rule that a value which works today is never newly rejected (#1711).
917
+ maxPasses: z2.union([z2.number().int().min(0), z2.literal(Number.POSITIVE_INFINITY)]).optional()
918
+ }).loose();
919
+ function resolveSettleConfig(options) {
920
+ if (options === void 0) return void 0;
921
+ SettleOptionsSchema.parse(options);
922
+ return options;
923
+ }
924
+ var MAX_SHUTDOWN_GRACE_MS = 3e4;
925
+ var DEFAULT_SHUTDOWN_GRACE_MS = DEFAULT_LEASE_MILLIS;
926
+ var ShutdownOptionsSchema = z2.object({
927
+ // `0` is legal and is exactly today's behavior: stop scheduling and
928
+ // return without waiting.
929
+ graceMs: z2.number().min(0).optional()
930
+ }).loose();
931
+ function resolveShutdownConfig(options) {
932
+ if (options === void 0) return void 0;
933
+ ShutdownOptionsSchema.parse(options);
934
+ return options;
935
+ }
936
+ var ActOptionsSchema = z2.object({
937
+ maxSubscribedStreams: z2.number().int().min(1).optional(),
938
+ settleDebounceMs: z2.number().int().min(0).optional()
939
+ }).loose();
940
+ function resolveActConfig(options) {
941
+ if (options === void 0) return void 0;
942
+ ActOptionsSchema.parse(options);
943
+ return options;
944
+ }
945
+ var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
946
+ var DEFAULT_CLOSE_BATCH_SIZE = 64;
947
+ var DEFAULT_CLOSE_YIELD_MS = 0;
948
+ var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
949
+ function is_valid_time_zone(tz) {
950
+ try {
951
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
952
+ return true;
953
+ } catch {
954
+ return false;
955
+ }
956
+ }
957
+ var AutocloseWindowSchema = z2.object({
958
+ start: z2.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
959
+ end: z2.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
960
+ timeZone: z2.string().refine(is_valid_time_zone, {
961
+ message: "autocloseWindow.timeZone must be a valid IANA time zone"
962
+ }).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
963
+ }).refine((w) => w.start !== w.end, {
964
+ message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
965
+ });
966
+ var AutocloseOptionsSchema = z2.object({
967
+ autocloseCycleMinutes: z2.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
968
+ closeBatchSize: z2.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
969
+ closeYieldMs: z2.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
970
+ closeOnError: z2.boolean().default(false),
971
+ autocloseWindow: AutocloseWindowSchema.optional()
972
+ });
973
+ function resolveAutocloseConfig(options) {
974
+ return AutocloseOptionsSchema.parse({
975
+ autocloseCycleMinutes: options?.autocloseCycleMinutes,
976
+ closeBatchSize: options?.closeBatchSize,
977
+ closeYieldMs: options?.closeYieldMs,
978
+ closeOnError: options?.closeOnError,
979
+ autocloseWindow: options?.autocloseWindow
980
+ });
981
+ }
982
+ var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
983
+ var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
984
+ var CircuitBreakerOptionsSchema = z2.object({
985
+ failureThreshold: z2.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
986
+ cooldownMs: z2.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
987
+ });
988
+ var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
989
+ var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
990
+ var DEFAULT_MAX_CACHED_STATES = 1e4;
991
+ var FoldOptionsSchema = z2.object({
992
+ flushEvery: z2.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
993
+ maxCachedStates: z2.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
994
+ });
995
+ function resolveFoldConfig(options) {
996
+ return FoldOptionsSchema.parse(options);
997
+ }
998
+
854
999
  // src/internal/defer-signal.ts
855
1000
  var DeferSignal = class extends Error {
856
1001
  /**
@@ -887,7 +1032,7 @@ function synthesize_autoclose_reactions(registry, states, read_window) {
887
1032
  }),
888
1033
  // Never block on autoclose: a transient query/store error should retry,
889
1034
  // not quarantine the synthetic stream.
890
- options: { blockOnError: false, maxRetries: 3 },
1035
+ options: { blockOnError: false, maxRetries: DEFAULT_MAX_RETRIES },
891
1036
  handler: async (event) => {
892
1037
  const aggregate = event.stream;
893
1038
  const window = read_window();
@@ -1374,144 +1519,6 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
1374
1519
  return truncated;
1375
1520
  }
1376
1521
 
1377
- // src/internal/config.ts
1378
- import { z as z2 } from "zod";
1379
- var BackoffOptionsSchema = z2.object({
1380
- strategy: z2.enum(["fixed", "linear", "exponential"]),
1381
- baseMs: z2.number().min(0),
1382
- maxMs: z2.number().gt(0).optional(),
1383
- jitter: z2.boolean().optional()
1384
- });
1385
- var ReactionOptionsSchema = z2.object({
1386
- blockOnError: z2.boolean(),
1387
- maxRetries: z2.number().int().min(0),
1388
- backoff: BackoffOptionsSchema.optional()
1389
- });
1390
- function resolveReactionConfig(options) {
1391
- return ReactionOptionsSchema.parse(options);
1392
- }
1393
- var ActionOptionsSchema = z2.object({
1394
- maxRetries: z2.number().int().min(0).optional(),
1395
- backoff: BackoffOptionsSchema.optional()
1396
- });
1397
- function resolveActionConfig(options) {
1398
- return ActionOptionsSchema.parse(options);
1399
- }
1400
- var LaneConfigSchema = z2.object({
1401
- name: z2.string().min(1),
1402
- leaseMillis: z2.number().min(0).optional(),
1403
- streamLimit: z2.number().int().min(0).optional(),
1404
- cycleMs: z2.number().min(0).optional()
1405
- });
1406
- function resolveLaneConfig(options) {
1407
- LaneConfigSchema.parse(options);
1408
- return options;
1409
- }
1410
- var DrainOptionsSchema = z2.object({
1411
- streamLimit: z2.number().int().min(0).optional(),
1412
- eventLimit: z2.number().int().min(0).optional(),
1413
- leaseMillis: z2.number().min(0).optional()
1414
- });
1415
- function resolveDrainConfig(options) {
1416
- if (options === void 0) return void 0;
1417
- DrainOptionsSchema.parse(options);
1418
- return options;
1419
- }
1420
- var SettleOptionsSchema = DrainOptionsSchema.extend({
1421
- debounceMs: z2.number().min(0).optional(),
1422
- // `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
1423
- // a non-negative integer — `0` is legal and means "run no passes".
1424
- //
1425
- // `Infinity` is accepted explicitly. It is the documented default
1426
- // (`Act.settle`'s doc-comment and `SettleOptions.maxPasses` both name it),
1427
- // and `settle.ts` destructures `maxPasses = Infinity`, so the one value the
1428
- // docs advertise was the one value `z.number().int()` refused — Zod 4
1429
- // rejects `±Infinity`, and an integer check cannot pass it either. Wrapper
1430
- // code normalizing an optional cap (`maxPasses: opts.maxPasses ?? Infinity`)
1431
- // is the natural way to hit it. Accepting it also honors this module's own
1432
- // rule that a value which works today is never newly rejected (#1711).
1433
- maxPasses: z2.union([z2.number().int().min(0), z2.literal(Number.POSITIVE_INFINITY)]).optional()
1434
- }).loose();
1435
- function resolveSettleConfig(options) {
1436
- if (options === void 0) return void 0;
1437
- SettleOptionsSchema.parse(options);
1438
- return options;
1439
- }
1440
- var MAX_SHUTDOWN_GRACE_MS = 3e4;
1441
- var DEFAULT_SHUTDOWN_GRACE_MS = 1e4;
1442
- var ShutdownOptionsSchema = z2.object({
1443
- // `0` is legal and is exactly today's behavior: stop scheduling and
1444
- // return without waiting.
1445
- graceMs: z2.number().min(0).optional()
1446
- }).loose();
1447
- function resolveShutdownConfig(options) {
1448
- if (options === void 0) return void 0;
1449
- ShutdownOptionsSchema.parse(options);
1450
- return options;
1451
- }
1452
- var ActOptionsSchema = z2.object({
1453
- maxSubscribedStreams: z2.number().int().min(1).optional(),
1454
- settleDebounceMs: z2.number().int().min(0).optional()
1455
- }).loose();
1456
- function resolveActConfig(options) {
1457
- if (options === void 0) return void 0;
1458
- ActOptionsSchema.parse(options);
1459
- return options;
1460
- }
1461
- var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
1462
- var DEFAULT_CLOSE_BATCH_SIZE = 64;
1463
- var DEFAULT_CLOSE_YIELD_MS = 0;
1464
- var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
1465
- function is_valid_time_zone(tz) {
1466
- try {
1467
- new Intl.DateTimeFormat("en-US", { timeZone: tz });
1468
- return true;
1469
- } catch {
1470
- return false;
1471
- }
1472
- }
1473
- var AutocloseWindowSchema = z2.object({
1474
- start: z2.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
1475
- end: z2.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
1476
- timeZone: z2.string().refine(is_valid_time_zone, {
1477
- message: "autocloseWindow.timeZone must be a valid IANA time zone"
1478
- }).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
1479
- }).refine((w) => w.start !== w.end, {
1480
- message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
1481
- });
1482
- var AutocloseOptionsSchema = z2.object({
1483
- autocloseCycleMinutes: z2.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
1484
- closeBatchSize: z2.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
1485
- closeYieldMs: z2.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
1486
- closeOnError: z2.boolean().default(false),
1487
- autocloseWindow: AutocloseWindowSchema.optional()
1488
- });
1489
- function resolveAutocloseConfig(options) {
1490
- return AutocloseOptionsSchema.parse({
1491
- autocloseCycleMinutes: options?.autocloseCycleMinutes,
1492
- closeBatchSize: options?.closeBatchSize,
1493
- closeYieldMs: options?.closeYieldMs,
1494
- closeOnError: options?.closeOnError,
1495
- autocloseWindow: options?.autocloseWindow
1496
- });
1497
- }
1498
- var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
1499
- var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
1500
- var CircuitBreakerOptionsSchema = z2.object({
1501
- failureThreshold: z2.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
1502
- cooldownMs: z2.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
1503
- });
1504
- var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
1505
- var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
1506
- var DEFAULT_MAX_CACHED_STATES = 1e4;
1507
- var FoldOptionsSchema = z2.object({
1508
- flushEvery: z2.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
1509
- maxCachedStates: z2.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
1510
- });
1511
- function resolveFoldConfig(options) {
1512
- return FoldOptionsSchema.parse(options);
1513
- }
1514
-
1515
1522
  // src/internal/correlate-cycle.ts
1516
1523
  import { createHash, randomUUID } from "crypto";
1517
1524
 
@@ -1589,7 +1596,6 @@ var CorrelateCycle = class {
1589
1596
  _armed = true;
1590
1597
  /** In-flight init, memoized for single-flight and cleared on failure. */
1591
1598
  _init_promise;
1592
- _timer = void 0;
1593
1599
  // Dynamically discovered targets → what each was last subscribed at,
1594
1600
  // bounded by `maxSubscribedStreams`. The static half lives in
1595
1601
  // `_static_subscriptions` below, which is deliberately not evictable.
@@ -1633,14 +1639,6 @@ var CorrelateCycle = class {
1633
1639
  * guarantee without a second gate on the Act side.
1634
1640
  */
1635
1641
  _on_init_async;
1636
- /**
1637
- * Scope runner (#1191). The periodic `start_polling` timer fires
1638
- * outside any caller frame, so its `correlate()` must be re-wrapped in
1639
- * the Act's `_scoped` bag or `store()`/`cache()` resolve to the
1640
- * singleton for a scoped Act. The orchestrator always threads its
1641
- * `_scoped` (identity for a non-scoped Act), so it's required.
1642
- */
1643
- _run_scoped;
1644
1642
  /**
1645
1643
  * Tail re-scan window applied to the cold-start checkpoint (ACT-1207).
1646
1644
  * See {@link DEFAULT_COLD_START_BACK_SCAN}. Constructor arg (not a
@@ -1661,7 +1659,6 @@ var CorrelateCycle = class {
1661
1659
  cd,
1662
1660
  max_subscribed_streams,
1663
1661
  declared_lanes,
1664
- run_scoped,
1665
1662
  on_init,
1666
1663
  on_init_async,
1667
1664
  cold_start_back_scan = DEFAULT_COLD_START_BACK_SCAN,
@@ -1675,7 +1672,6 @@ var CorrelateCycle = class {
1675
1672
  this._static_targets = static_targets;
1676
1673
  this._cd = cd;
1677
1674
  this._on_init = on_init;
1678
- this._run_scoped = run_scoped;
1679
1675
  this._on_init_async = on_init_async;
1680
1676
  this._cold_start_back_scan = cold_start_back_scan;
1681
1677
  }
@@ -1923,33 +1919,6 @@ var CorrelateCycle = class {
1923
1919
  this._armed = found === query.limit;
1924
1920
  return { subscribed: 0, last_id, marked: 0, scanned: true };
1925
1921
  }
1926
- /**
1927
- * Start a periodic correlation worker. Returns false if one is already
1928
- * running. Errors from `correlate()` are routed through `log()` so they
1929
- * land in the configured logger (the timer keeps running on failure).
1930
- */
1931
- start_polling(query = {}, frequency = 1e4, callback) {
1932
- if (this._timer) return false;
1933
- const limit = query.limit || 100;
1934
- this._timer = setInterval(
1935
- () => this._run_scoped(() => {
1936
- this.arm();
1937
- return this.correlate(
1938
- {
1939
- ...query,
1940
- after: this._checkpoint,
1941
- limit
1942
- },
1943
- true
1944
- );
1945
- }).then((result) => {
1946
- if (callback && result.subscribed) callback(result.subscribed);
1947
- }).catch((err) => log().error(err)),
1948
- frequency
1949
- );
1950
- return true;
1951
- }
1952
- /** Stop the periodic correlation worker. Idempotent. */
1953
1922
  /**
1954
1923
  * Hand the correlation lease back early.
1955
1924
  *
@@ -1980,12 +1949,6 @@ var CorrelateCycle = class {
1980
1949
  );
1981
1950
  }
1982
1951
  }
1983
- stop_polling() {
1984
- if (this._timer) {
1985
- clearInterval(this._timer);
1986
- this._timer = void 0;
1987
- }
1988
- }
1989
1952
  };
1990
1953
 
1991
1954
  // src/internal/correlator.ts
@@ -3083,9 +3046,9 @@ var DrainController = class {
3083
3046
  if (this._deps.breaker.state(Date.now()) === "open")
3084
3047
  return EMPTY_DRAIN;
3085
3048
  const d = this._deps.defaults ?? {};
3086
- const streamLimit = d.streamLimit ?? options.streamLimit ?? 10;
3087
- const eventLimit = d.eventLimit ?? options.eventLimit ?? 10;
3088
- const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? 1e4;
3049
+ const streamLimit = d.streamLimit ?? options.streamLimit ?? DEFAULT_STREAM_LIMIT;
3050
+ const eventLimit = d.eventLimit ?? options.eventLimit ?? DEFAULT_EVENT_LIMIT;
3051
+ const leaseMillis = d.leaseMillis ?? options.leaseMillis ?? DEFAULT_LEASE_MILLIS;
3089
3052
  try {
3090
3053
  this._locked = true;
3091
3054
  this._inflight = new Promise((done) => {
@@ -3459,10 +3422,8 @@ function build_handle_batch(logger) {
3459
3422
  }
3460
3423
 
3461
3424
  // src/act.ts
3462
- var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
3463
3425
  var CLOSE_CATCH_UP_LIMIT = 1e3;
3464
3426
  var CLOSE_CATCH_UP_PASSES = 20;
3465
- var DEFAULT_SETTLE_DEBOUNCE_MS = 10;
3466
3427
  function validate_only_lanes(options, lanes) {
3467
3428
  if (!options.onlyLanes || options.onlyLanes.length === 0) return;
3468
3429
  const declared = /* @__PURE__ */ new Set(["default", ...lanes.map((l) => l.name)]);
@@ -3488,6 +3449,10 @@ var Act = class {
3488
3449
  _correlate;
3489
3450
  /** Debounced correlate→drain catch-up loop. */
3490
3451
  _settle;
3452
+ /** Timer behind {@link start_correlations}. */
3453
+ _poll;
3454
+ /** The deprecated `start_correlations` callback, while the poll runs. */
3455
+ _on_discovered;
3491
3456
  /**
3492
3457
  * Disposer for the cross-process notify subscription, set up eagerly
3493
3458
  * during construction. Held as a promise because the subscription
@@ -3897,9 +3862,6 @@ var Act = class {
3897
3862
  on_init: () => {
3898
3863
  if (this._drain && this._reactive_events.size > 0) this._arm_all();
3899
3864
  },
3900
- // Re-scope the background `start_correlations` timer so its
3901
- // correlate resolves the scoped ports, not the singleton (#1191).
3902
- run_scoped: this._scoped,
3903
3865
  // Cold-start defer re-seed (#1221). Skipped on writer-only instances
3904
3866
  // (`drain: false`) — they run no local controllers to re-arm.
3905
3867
  on_init_async: this._drain ? () => this._seed_persisted_defers() : void 0
@@ -4489,86 +4451,63 @@ var Act = class {
4489
4451
  const { subscribed, last_id, marked, scanned } = await this._correlate.correlate(query, lease);
4490
4452
  if ((subscribed > 0 || marked > 0) && this._reactive_events.size > 0)
4491
4453
  this._arm_all();
4454
+ if (subscribed > 0) this._on_discovered?.(subscribed);
4492
4455
  return { subscribed, last_id, scanned };
4493
4456
  });
4494
4457
  }
4495
4458
  /**
4496
- * Starts automatic periodic correlation worker for discovering new streams.
4459
+ * Starts a background poll that settles on a timer: each tick looks for
4460
+ * new events, subscribes the streams their reactions target, and drains
4461
+ * them.
4462
+ *
4463
+ * Use it when this process can miss commits — another process writes to a
4464
+ * store without `notify`, or a notification is lost. It is the safety net
4465
+ * under `notify`: a missed wakeup costs at most one `frequency` of latency.
4466
+ *
4467
+ * Only one poll runs per Act instance. On a writer-only instance
4468
+ * (`drain: false`) each tick is a no-op, like `settle()`.
4469
+ *
4470
+ * @param query - Only `limit` is used: the most events each correlate pass
4471
+ * reads (default 100). The scan always starts at the correlation
4472
+ * checkpoint, so `after` is ignored.
4473
+ * @param frequency - Milliseconds between ticks (default: 10000)
4474
+ * @param callback - Called with the number of newly subscribed streams
4475
+ * after each correlate pass that found some.
4476
+ * @deprecated Listen to the `"settled"` lifecycle event instead.
4477
+ * @returns `true` if the poll started, `false` if one is already running
4497
4478
  *
4498
- * The correlation worker runs in the background, scanning for new events and identifying
4499
- * new target streams based on reaction resolvers. It maintains a sliding window that
4500
- * advances with each scan, ensuring all events are eventually correlated.
4501
- *
4502
- * This is useful for dynamic stream creation patterns where you don't know all streams
4503
- * upfront - they're discovered as events arrive.
4504
- *
4505
- * **Note:** Only one correlation worker can run at a time per Act instance.
4506
- *
4507
- * @param query - Query filter for correlation scans — see {@link Query}
4508
- * (typically `{ after: -1, limit: 100 }`)
4509
- * @param frequency - Correlation frequency in milliseconds (default: 10000)
4510
- * @param callback - Optional callback invoked with newly discovered streams
4511
- * @returns `true` if worker started, `false` if already running
4512
- *
4513
- * @example Start automatic correlation
4479
+ * @example
4514
4480
  * ```typescript
4515
- * // Start correlation worker scanning every 5 seconds
4516
- * app.start_correlations(
4517
- * { after: 0, limit: 100 },
4518
- * 5000,
4519
- * (leased) => {
4520
- * console.log(`Discovered ${leased.length} new streams`);
4521
- * }
4522
- * );
4523
- *
4524
- * // Later, stop it
4481
+ * app.start_correlations({ limit: 100 }, 5000);
4482
+ * // later
4525
4483
  * app.stop_correlations();
4526
4484
  * ```
4527
4485
  *
4528
- * @example With checkpoint persistence
4529
- * ```typescript
4530
- * // Load last checkpoint
4531
- * const lastId = await loadCheckpoint();
4532
- *
4533
- * app.start_correlations(
4534
- * { after: lastId, limit: 100 },
4535
- * 10000,
4536
- * async (leased) => {
4537
- * // Save checkpoint for next restart
4538
- * if (leased.length) {
4539
- * const maxId = Math.max(...leased.map(l => l.at));
4540
- * await saveCheckpoint(maxId);
4541
- * }
4542
- * }
4543
- * );
4544
- * ```
4545
- *
4546
- * @see {@link correlate} for manual one-time correlation
4547
- * @see {@link stop_correlations} to stop the worker
4486
+ * @see {@link settle} for what each tick runs
4487
+ * @see {@link stop_correlations} to stop the poll
4548
4488
  */
4549
4489
  start_correlations(query = {}, frequency = 1e4, callback) {
4550
- const started = this._correlate.start_polling(query, frequency, callback);
4551
- return started;
4490
+ if (this._poll) return false;
4491
+ this._on_discovered = callback;
4492
+ const correlate = { limit: query.limit || 100 };
4493
+ this._poll = setInterval(() => {
4494
+ this._correlate.arm();
4495
+ this.settle({ debounceMs: 0, correlate });
4496
+ }, frequency);
4497
+ return true;
4552
4498
  }
4553
4499
  /**
4554
- * Stops the automatic correlation worker.
4555
- *
4556
- * Call this to stop the background correlation worker started by {@link start_correlations}.
4557
- * This is automatically called when the Act instance is disposed.
4558
- *
4559
- * @example
4560
- * ```typescript
4561
- * // Start correlation
4562
- * app.start_correlations();
4563
- *
4564
- * // Later, stop it
4565
- * app.stop_correlations();
4566
- * ```
4500
+ * Stops the poll started by {@link start_correlations}. Idempotent, and
4501
+ * called by `shutdown()`.
4567
4502
  *
4568
4503
  * @see {@link start_correlations}
4569
4504
  */
4570
4505
  stop_correlations() {
4571
- this._correlate.stop_polling();
4506
+ if (this._poll) {
4507
+ clearInterval(this._poll);
4508
+ this._poll = void 0;
4509
+ }
4510
+ this._on_discovered = void 0;
4572
4511
  void this._scoped(() => this._correlate.release_correlation());
4573
4512
  }
4574
4513
  /**
@@ -5225,8 +5164,8 @@ function reaction_on(event, events, builder) {
5225
5164
  // `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
5226
5165
  // not a NaN gate on the first retry.
5227
5166
  options: resolveReactionConfig({
5228
- blockOnError: options?.blockOnError ?? true,
5229
- maxRetries: options?.maxRetries ?? 3,
5167
+ blockOnError: options?.blockOnError ?? DEFAULT_BLOCK_ON_ERROR,
5168
+ maxRetries: options?.maxRetries ?? DEFAULT_MAX_RETRIES,
5230
5169
  backoff: options?.backoff
5231
5170
  })
5232
5171
  };
@@ -5617,8 +5556,8 @@ function _projection(target) {
5617
5556
  handler,
5618
5557
  resolver: default_resolver ?? _this_,
5619
5558
  options: {
5620
- blockOnError: true,
5621
- maxRetries: 3
5559
+ blockOnError: DEFAULT_BLOCK_ON_ERROR,
5560
+ maxRetries: DEFAULT_MAX_RETRIES
5622
5561
  }
5623
5562
  };
5624
5563
  const register = events[event];
@@ -5691,7 +5630,10 @@ function _projection(target) {
5691
5630
  {
5692
5631
  handler: noop,
5693
5632
  resolver: { target },
5694
- options: { blockOnError: true, maxRetries: 3 }
5633
+ options: {
5634
+ blockOnError: DEFAULT_BLOCK_ON_ERROR,
5635
+ maxRetries: DEFAULT_MAX_RETRIES
5636
+ }
5695
5637
  }
5696
5638
  ]
5697
5639
  ])