@rotorsoft/act 1.23.6 → 1.23.7

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
@@ -496,84 +496,8 @@ var PASS_FACTORIES = {
496
496
  "clock-anomalies": make_clock_anomalies_pass
497
497
  };
498
498
 
499
- // src/internal/autoclose-config.ts
500
- import { z } from "zod";
501
- var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
502
- var DEFAULT_CLOSE_BATCH_SIZE = 64;
503
- var DEFAULT_CLOSE_YIELD_MS = 0;
504
- var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
505
- function is_valid_time_zone(tz) {
506
- try {
507
- new Intl.DateTimeFormat("en-US", { timeZone: tz });
508
- return true;
509
- } catch {
510
- return false;
511
- }
512
- }
513
- var AutocloseWindowSchema = z.object({
514
- start: z.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
515
- end: z.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
516
- timeZone: z.string().refine(is_valid_time_zone, {
517
- message: "autocloseWindow.timeZone must be a valid IANA time zone"
518
- }).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
519
- }).refine((w) => w.start !== w.end, {
520
- message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
521
- });
522
- var AutocloseOptionsSchema = z.object({
523
- autocloseCycleMinutes: z.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
524
- closeBatchSize: z.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
525
- closeYieldMs: z.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
526
- closeOnError: z.boolean().default(false),
527
- autocloseWindow: AutocloseWindowSchema.optional()
528
- });
529
- function resolveAutocloseConfig(options) {
530
- return AutocloseOptionsSchema.parse({
531
- autocloseCycleMinutes: options?.autocloseCycleMinutes,
532
- closeBatchSize: options?.closeBatchSize,
533
- closeYieldMs: options?.closeYieldMs,
534
- closeOnError: options?.closeOnError,
535
- autocloseWindow: options?.autocloseWindow
536
- });
537
- }
538
- function hour_in_zone(now, timeZone) {
539
- const parts = new Intl.DateTimeFormat("en-US", {
540
- timeZone,
541
- hour: "2-digit",
542
- hourCycle: "h23"
543
- }).formatToParts(now);
544
- return Number(parts.find((p) => p.type === "hour")?.value);
545
- }
546
- function hour_in_range(hour, start, end) {
547
- return start < end ? hour >= start && hour < end : hour >= start || hour < end;
548
- }
549
- function is_dst_gap_open(now, timeZone, start) {
550
- const hour = hour_in_zone(now, timeZone);
551
- if (hour <= start) return false;
552
- const prev = hour_in_zone(new Date(now.getTime() - 36e5), timeZone);
553
- return prev === start - 1 && hour === start + 1;
554
- }
555
- function in_autoclose_window(window, now) {
556
- if (!window) return true;
557
- const hour = hour_in_zone(now, window.timeZone);
558
- return hour_in_range(hour, window.start, window.end) || is_dst_gap_open(now, window.timeZone, window.start);
559
- }
560
- function next_window_open(window, now) {
561
- const start_ms = now.getTime();
562
- for (let h = 0; h <= 48; h++) {
563
- const candidate = new Date(start_ms + h * 36e5);
564
- if (hour_in_zone(candidate, window.timeZone) === window.start)
565
- return candidate;
566
- const boundary = new Date(
567
- candidate.getTime() - candidate.getTime() % 36e5
568
- );
569
- if (is_dst_gap_open(boundary, window.timeZone, window.start))
570
- return boundary;
571
- }
572
- return new Date(start_ms + 864e5);
573
- }
574
-
575
499
  // src/internal/autoclose-policy.ts
576
- import { z as z2 } from "zod";
500
+ import { z } from "zod";
577
501
  var DAY_MS = 864e5;
578
502
  var MIN_AFTER_DAYS = 1 / 1440;
579
503
  var MIN_KEEP_DAYS = 1;
@@ -583,21 +507,21 @@ function days_after(date, days) {
583
507
  function days_before_now(days) {
584
508
  return new Date(Date.now() - days * DAY_MS);
585
509
  }
586
- var window_schema = (field, min_days, floor) => z2.object({
587
- days: z2.number({ message: `autocloses: ${field}.days must be a number` }).positive(`autocloses: ${field}.days must be > 0`)
510
+ var window_schema = (field, min_days, floor) => z.object({
511
+ days: z.number({ message: `autocloses: ${field}.days must be a number` }).positive(`autocloses: ${field}.days must be > 0`)
588
512
  }).refine((o) => o.days >= min_days, {
589
513
  message: `autocloses: ${field} resolves to < ${floor}, which is too short to be a meaningful retention window`
590
514
  });
591
515
  var AfterSchema = window_schema("after", MIN_AFTER_DAYS, "one minute");
592
516
  var KeepSchema = window_schema("keep", MIN_KEEP_DAYS, "one day");
593
- var IsSchema = z2.union([
594
- z2.string().min(1, "autocloses: is must be a non-empty event name"),
595
- z2.array(
596
- z2.string().min(1, "autocloses: is entries must be non-empty event names")
517
+ var IsSchema = z.union([
518
+ z.string().min(1, "autocloses: is must be a non-empty event name"),
519
+ z.array(
520
+ z.string().min(1, "autocloses: is entries must be non-empty event names")
597
521
  ).min(1, "autocloses: is must include at least one event name").readonly()
598
522
  ]);
599
- var ReachesSchema = z2.number({ message: "autocloses: reaches must be a number" }).int("autocloses: reaches must be an integer").min(1, "autocloses: reaches must be >= 1");
600
- var OrBlockSchema = z2.object({
523
+ var ReachesSchema = z.number({ message: "autocloses: reaches must be a number" }).int("autocloses: reaches must be an integer").min(1, "autocloses: reaches must be >= 1");
524
+ var OrBlockSchema = z.object({
601
525
  after: AfterSchema.optional(),
602
526
  is: IsSchema.optional(),
603
527
  reaches: ReachesSchema.optional()
@@ -607,7 +531,7 @@ var OrBlockSchema = z2.object({
607
531
  message: "autocloses: `or` block must include at least one of after / is / reaches"
608
532
  }
609
533
  );
610
- var AutoclosePolicySchema = z2.object({
534
+ var AutoclosePolicySchema = z.object({
611
535
  after: AfterSchema.optional(),
612
536
  is: IsSchema.optional(),
613
537
  reaches: ReachesSchema.optional(),
@@ -663,6 +587,44 @@ function compile_autoclose_policy(options) {
663
587
  };
664
588
  }
665
589
 
590
+ // src/internal/autoclose-window.ts
591
+ function hour_in_zone(now, timeZone) {
592
+ const parts = new Intl.DateTimeFormat("en-US", {
593
+ timeZone,
594
+ hour: "2-digit",
595
+ hourCycle: "h23"
596
+ }).formatToParts(now);
597
+ return Number(parts.find((p) => p.type === "hour")?.value);
598
+ }
599
+ function hour_in_range(hour, start, end) {
600
+ return start < end ? hour >= start && hour < end : hour >= start || hour < end;
601
+ }
602
+ function is_dst_gap_open(now, timeZone, start) {
603
+ const hour = hour_in_zone(now, timeZone);
604
+ if (hour <= start) return false;
605
+ const prev = hour_in_zone(new Date(now.getTime() - 36e5), timeZone);
606
+ return prev === start - 1 && hour === start + 1;
607
+ }
608
+ function in_autoclose_window(window, now) {
609
+ if (!window) return true;
610
+ const hour = hour_in_zone(now, window.timeZone);
611
+ return hour_in_range(hour, window.start, window.end) || is_dst_gap_open(now, window.timeZone, window.start);
612
+ }
613
+ function next_window_open(window, now) {
614
+ const start_ms = now.getTime();
615
+ for (let h = 0; h <= 48; h++) {
616
+ const candidate = new Date(start_ms + h * 36e5);
617
+ if (hour_in_zone(candidate, window.timeZone) === window.start)
618
+ return candidate;
619
+ const boundary = new Date(
620
+ candidate.getTime() - candidate.getTime() % 36e5
621
+ );
622
+ if (is_dst_gap_open(boundary, window.timeZone, window.start))
623
+ return boundary;
624
+ }
625
+ return new Date(start_ms + 864e5);
626
+ }
627
+
666
628
  // src/internal/close-signal.ts
667
629
  var CloseSignal = class extends Error {
668
630
  /**
@@ -843,6 +805,123 @@ function classify_registry(registry, states) {
843
805
  };
844
806
  }
845
807
 
808
+ // src/internal/config.ts
809
+ import { z as z2 } from "zod";
810
+ var BackoffOptionsSchema = z2.object({
811
+ strategy: z2.enum(["fixed", "linear", "exponential"]),
812
+ baseMs: z2.number().min(0),
813
+ maxMs: z2.number().gt(0).optional(),
814
+ jitter: z2.boolean().optional()
815
+ });
816
+ var ReactionOptionsSchema = z2.object({
817
+ blockOnError: z2.boolean(),
818
+ maxRetries: z2.number().int().min(0),
819
+ backoff: BackoffOptionsSchema.optional()
820
+ });
821
+ function resolveReactionConfig(options) {
822
+ return ReactionOptionsSchema.parse(options);
823
+ }
824
+ var ActionOptionsSchema = z2.object({
825
+ maxRetries: z2.number().int().min(0).optional(),
826
+ backoff: BackoffOptionsSchema.optional()
827
+ });
828
+ function resolveActionConfig(options) {
829
+ return ActionOptionsSchema.parse(options);
830
+ }
831
+ var LaneConfigSchema = z2.object({
832
+ name: z2.string().min(1),
833
+ leaseMillis: z2.number().min(0).optional(),
834
+ streamLimit: z2.number().int().min(0).optional(),
835
+ cycleMs: z2.number().min(0).optional()
836
+ });
837
+ function resolveLaneConfig(options) {
838
+ LaneConfigSchema.parse(options);
839
+ return options;
840
+ }
841
+ var DrainOptionsSchema = z2.object({
842
+ streamLimit: z2.number().int().min(0).optional(),
843
+ eventLimit: z2.number().int().min(0).optional(),
844
+ leaseMillis: z2.number().min(0).optional()
845
+ });
846
+ function resolveDrainConfig(options) {
847
+ if (options === void 0) return void 0;
848
+ DrainOptionsSchema.parse(options);
849
+ return options;
850
+ }
851
+ var SettleOptionsSchema = DrainOptionsSchema.extend({
852
+ debounceMs: z2.number().min(0).optional(),
853
+ // `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
854
+ // a non-negative integer — `0` is legal and means "run no passes".
855
+ maxPasses: z2.number().int().min(0).optional()
856
+ }).loose();
857
+ function resolveSettleConfig(options) {
858
+ if (options === void 0) return void 0;
859
+ SettleOptionsSchema.parse(options);
860
+ return options;
861
+ }
862
+ var ActOptionsSchema = z2.object({
863
+ maxSubscribedStreams: z2.number().int().min(1).optional(),
864
+ settleDebounceMs: z2.number().int().min(0).optional()
865
+ }).loose();
866
+ function resolveActConfig(options) {
867
+ if (options === void 0) return void 0;
868
+ ActOptionsSchema.parse(options);
869
+ return options;
870
+ }
871
+ var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
872
+ var DEFAULT_CLOSE_BATCH_SIZE = 64;
873
+ var DEFAULT_CLOSE_YIELD_MS = 0;
874
+ var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
875
+ function is_valid_time_zone(tz) {
876
+ try {
877
+ new Intl.DateTimeFormat("en-US", { timeZone: tz });
878
+ return true;
879
+ } catch {
880
+ return false;
881
+ }
882
+ }
883
+ var AutocloseWindowSchema = z2.object({
884
+ start: z2.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
885
+ end: z2.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
886
+ timeZone: z2.string().refine(is_valid_time_zone, {
887
+ message: "autocloseWindow.timeZone must be a valid IANA time zone"
888
+ }).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
889
+ }).refine((w) => w.start !== w.end, {
890
+ message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
891
+ });
892
+ var AutocloseOptionsSchema = z2.object({
893
+ autocloseCycleMinutes: z2.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
894
+ closeBatchSize: z2.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
895
+ closeYieldMs: z2.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
896
+ closeOnError: z2.boolean().default(false),
897
+ autocloseWindow: AutocloseWindowSchema.optional()
898
+ });
899
+ function resolveAutocloseConfig(options) {
900
+ return AutocloseOptionsSchema.parse({
901
+ autocloseCycleMinutes: options?.autocloseCycleMinutes,
902
+ closeBatchSize: options?.closeBatchSize,
903
+ closeYieldMs: options?.closeYieldMs,
904
+ closeOnError: options?.closeOnError,
905
+ autocloseWindow: options?.autocloseWindow
906
+ });
907
+ }
908
+ var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
909
+ var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
910
+ var CircuitBreakerOptionsSchema = z2.object({
911
+ failureThreshold: z2.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
912
+ cooldownMs: z2.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
913
+ });
914
+ var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
915
+ var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
916
+ var DEFAULT_MAX_CACHED_STATES = 1e4;
917
+ var FoldOptionsSchema = z2.object({
918
+ flushEvery: z2.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
919
+ maxCachedStates: z2.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
920
+ });
921
+ function resolveFoldConfig(options) {
922
+ return FoldOptionsSchema.parse(options);
923
+ }
924
+
846
925
  // src/internal/defer-config.ts
847
926
  import { z as z3 } from "zod";
848
927
  var DeferDurationSchema = z3.object({
@@ -1050,11 +1129,14 @@ function reaction_on(event, events, builder) {
1050
1129
  const reaction = {
1051
1130
  handler: schedule ? make_deferred(handler, schedule) : handler,
1052
1131
  resolver: _this_,
1053
- options: {
1132
+ // #1269: validate the whole bag at the declaration site so a bad
1133
+ // `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
1134
+ // not a NaN gate on the first retry.
1135
+ options: resolveReactionConfig({
1054
1136
  blockOnError: options?.blockOnError ?? true,
1055
1137
  maxRetries: options?.maxRetries ?? 3,
1056
1138
  backoff: options?.backoff
1057
- }
1139
+ })
1058
1140
  };
1059
1141
  events[event].reactions.set(handler.name, reaction);
1060
1142
  return Object.assign(builder, {
@@ -1073,14 +1155,6 @@ function reaction_on(event, events, builder) {
1073
1155
  }
1074
1156
 
1075
1157
  // src/internal/circuit-breaker.ts
1076
- import { z as z4 } from "zod";
1077
- var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
1078
- var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
1079
- var CircuitBreakerOptionsSchema = z4.object({
1080
- failureThreshold: z4.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
1081
- cooldownMs: z4.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
1082
- });
1083
- var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
1084
1158
  var CircuitBreaker = class {
1085
1159
  _failures = 0;
1086
1160
  _opened_at;
@@ -1748,6 +1822,10 @@ function compute_backoff_delay(retry, opts) {
1748
1822
  delay = opts.baseMs * 2 ** r;
1749
1823
  if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
1750
1824
  break;
1825
+ default: {
1826
+ const _never = opts.strategy;
1827
+ throw new Error(`unknown backoff strategy: ${String(_never)}`);
1828
+ }
1751
1829
  }
1752
1830
  if (opts.jitter) delay = delay * (0.5 + Math.random());
1753
1831
  return Math.max(0, Math.floor(delay));
@@ -2504,16 +2582,6 @@ var DrainController = class {
2504
2582
  };
2505
2583
 
2506
2584
  // src/internal/projection-fold.ts
2507
- import { z as z5 } from "zod";
2508
- var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
2509
- var DEFAULT_MAX_CACHED_STATES = 1e4;
2510
- var FoldOptionsSchema = z5.object({
2511
- flushEvery: z5.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
2512
- maxCachedStates: z5.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
2513
- });
2514
- function resolveFoldConfig(options) {
2515
- return FoldOptionsSchema.parse(options);
2516
- }
2517
2585
  function make_fold_handler(me, flush, config2, patch_fn = bare_patch) {
2518
2586
  const cache2 = /* @__PURE__ */ new Map();
2519
2587
  const row = ({ dirty: _, ...row2 }) => row2;
@@ -3540,6 +3608,7 @@ var Act = class {
3540
3608
  * @see {@link start_correlations} for automatic correlation
3541
3609
  */
3542
3610
  async drain(options = {}) {
3611
+ resolveDrainConfig(options);
3543
3612
  if (!this._drain)
3544
3613
  return { fetched: [], leased: [], acked: [], blocked: [] };
3545
3614
  return this._scoped(() => this._drain_all(options));
@@ -4102,6 +4171,7 @@ var Act = class {
4102
4171
  * @see {@link correlate} for manual correlation
4103
4172
  */
4104
4173
  settle(options = {}) {
4174
+ resolveSettleConfig(options);
4105
4175
  if (!this._drain) return;
4106
4176
  this._settle.schedule(options);
4107
4177
  }
@@ -4222,11 +4292,12 @@ function act() {
4222
4292
  },
4223
4293
  withActor: () => builder,
4224
4294
  withLane: (config2) => {
4225
- register_lane(config2, lanes);
4295
+ register_lane(resolveLaneConfig(config2), lanes);
4226
4296
  return builder;
4227
4297
  },
4228
4298
  on: (event) => reaction_on(event, registry.events, builder),
4229
4299
  build: (options) => {
4300
+ resolveActConfig(options);
4230
4301
  const patch_fn = options?.validateFoldedState === true ? validating_patch : bare_patch;
4231
4302
  if (!_built) {
4232
4303
  for (const proj of pending_projections) {
@@ -4543,6 +4614,7 @@ function action_builder(state2) {
4543
4614
  throw new Error(`Duplicate action "${action2}"`);
4544
4615
  internal.actions[action2] = schema;
4545
4616
  if (options) {
4617
+ resolveActionConfig(options);
4546
4618
  internal.options ??= {};
4547
4619
  internal.options[action2] = options;
4548
4620
  }