@rotorsoft/act 1.17.0 → 1.18.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
@@ -670,13 +670,258 @@ function classify_registry(registry, states) {
670
670
  };
671
671
  }
672
672
 
673
- // src/internal/circuit-breaker.ts
673
+ // src/internal/defer-config.ts
674
674
  import { z as z3 } from "zod";
675
+
676
+ // src/internal/defer-signal.ts
677
+ var DeferSignal = class extends Error {
678
+ /**
679
+ * The unresolved schedule. The dispatcher turns this into an absolute
680
+ * due-time by resolving it against the triggering event.
681
+ */
682
+ when;
683
+ constructor(when) {
684
+ super("reaction deferred");
685
+ this.name = "DeferSignal";
686
+ this.when = when;
687
+ }
688
+ };
689
+
690
+ // src/internal/defer-config.ts
691
+ var DeferDurationSchema = z3.object({
692
+ days: z3.number().positive().optional(),
693
+ hours: z3.number().positive().optional(),
694
+ minutes: z3.number().positive().optional()
695
+ }).strict().refine((d) => d.days != null || d.hours != null || d.minutes != null, {
696
+ message: "defer: a duration needs at least one of days, hours, or minutes"
697
+ });
698
+ var DeferWhenSchema = z3.object({
699
+ after: DeferDurationSchema.optional(),
700
+ at: z3.date().optional()
701
+ }).strict().refine((w) => w.after === void 0 !== (w.at === void 0), {
702
+ message: "defer: specify exactly one of `after` or `at`"
703
+ });
704
+ function duration_ms(d) {
705
+ return (d.days ?? 0) * 864e5 + (d.hours ?? 0) * 36e5 + (d.minutes ?? 0) * 6e4;
706
+ }
707
+ function resolve_defer_at(when, event) {
708
+ const parsed = DeferWhenSchema.parse(when);
709
+ if (parsed.after) return event.created.getTime() + duration_ms(parsed.after);
710
+ return parsed.at.getTime();
711
+ }
712
+ function assert_defer_when(when) {
713
+ DeferWhenSchema.parse(when);
714
+ }
715
+ function make_deferred(handler, schedule) {
716
+ const call = handler;
717
+ const deferred = async (event, stream, app) => {
718
+ const when = typeof schedule === "function" ? schedule(event) : schedule;
719
+ if (Date.now() < resolve_defer_at(when, event)) throw new DeferSignal(when);
720
+ return call(event, stream, app);
721
+ };
722
+ Object.defineProperty(deferred, "name", { value: handler.name });
723
+ return deferred;
724
+ }
725
+
726
+ // src/internal/merge.ts
727
+ import { ZodObject } from "zod";
728
+ function base_type_name(zodType) {
729
+ let t = zodType;
730
+ while (typeof t.unwrap === "function") {
731
+ t = t.unwrap();
732
+ }
733
+ return t.constructor.name;
734
+ }
735
+ function merge_schemas(existing, incoming, state_name) {
736
+ if (existing instanceof ZodObject && incoming instanceof ZodObject) {
737
+ const existing_shape = existing.shape;
738
+ const incoming_shape = incoming.shape;
739
+ for (const key of Object.keys(incoming_shape)) {
740
+ if (key in existing_shape) {
741
+ const existing_base = base_type_name(existing_shape[key]);
742
+ const incoming_base = base_type_name(incoming_shape[key]);
743
+ if (existing_base !== incoming_base) {
744
+ throw new Error(
745
+ `Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
746
+ );
747
+ }
748
+ }
749
+ }
750
+ return existing.extend(incoming_shape);
751
+ }
752
+ return existing;
753
+ }
754
+ function merge_inits(existing, incoming) {
755
+ return () => ({ ...existing(), ...incoming() });
756
+ }
757
+ function register_state(state2, states, actions, events) {
758
+ const existing = states.get(state2.name);
759
+ if (existing) {
760
+ merge_into_existing(state2, existing, states, actions, events);
761
+ } else {
762
+ register_new_state(state2, states, actions, events);
763
+ }
764
+ }
765
+ function register_new_state(state2, states, actions, events) {
766
+ states.set(state2.name, state2);
767
+ for (const name of Object.keys(state2.actions)) {
768
+ if (actions[name]) throw new Error(`Duplicate action "${name}"`);
769
+ actions[name] = state2;
770
+ }
771
+ for (const name of Object.keys(state2.events)) {
772
+ if (events[name]) throw new Error(`Duplicate event "${name}"`);
773
+ events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
774
+ }
775
+ }
776
+ function merge_into_existing(state2, existing, states, actions, events) {
777
+ for (const name of Object.keys(state2.actions)) {
778
+ if (existing.actions[name] === state2.actions[name]) continue;
779
+ if (actions[name]) throw new Error(`Duplicate action "${name}"`);
780
+ }
781
+ for (const name of Object.keys(state2.events)) {
782
+ if (existing.events[name] === state2.events[name]) continue;
783
+ if (existing.events[name]) {
784
+ throw new Error(
785
+ `Event "${name}" in state "${state2.name}" is declared with different Zod schemas across slices. Cross-slice event schemas must reference the same instance \u2014 extract a shared schema (e.g. \`export const ${name} = z.object({ ... })\` in a shared module) and import it in every slice that declares it.`
786
+ );
787
+ }
788
+ if (events[name]) throw new Error(`Duplicate event "${name}"`);
789
+ }
790
+ const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
791
+ const merged = {
792
+ ...existing,
793
+ state: merge_schemas(existing.state, state2.state, state2.name),
794
+ init: merge_inits(existing.init, state2.init),
795
+ events: { ...existing.events, ...state2.events },
796
+ actions: { ...existing.actions, ...state2.actions },
797
+ patch: merged_patch,
798
+ on: { ...existing.on, ...state2.on },
799
+ given: { ...existing.given, ...state2.given },
800
+ snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
801
+ throw new Error(
802
+ `Duplicate snap strategy for state "${state2.name}"`
803
+ );
804
+ })() : state2.snap || existing.snap
805
+ };
806
+ states.set(state2.name, merged);
807
+ for (const name of Object.keys(merged.actions)) {
808
+ actions[name] = merged;
809
+ }
810
+ for (const name of Object.keys(state2.events)) {
811
+ if (events[name]) continue;
812
+ events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
813
+ }
814
+ }
815
+ function merge_patches(existing, incoming, state_name) {
816
+ const merged = { ...existing };
817
+ for (const name of Object.keys(incoming)) {
818
+ const existing_p = existing[name];
819
+ const incoming_p = incoming[name];
820
+ if (!existing_p) {
821
+ merged[name] = incoming_p;
822
+ continue;
823
+ }
824
+ const existing_is_default = existing_p._passthrough;
825
+ const incoming_is_default = incoming_p._passthrough;
826
+ if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
827
+ throw new Error(
828
+ `Duplicate custom patch for event "${name}" in state "${state_name}"`
829
+ );
830
+ }
831
+ if (existing_is_default && !incoming_is_default) {
832
+ merged[name] = incoming_p;
833
+ }
834
+ }
835
+ return merged;
836
+ }
837
+ function merge_event_register(target, source) {
838
+ for (const [event_name, source_reg] of Object.entries(source)) {
839
+ const target_reg = target[event_name];
840
+ if (!target_reg) continue;
841
+ for (const [name, reaction] of source_reg.reactions) {
842
+ const existing = target_reg.reactions.get(name);
843
+ if (existing !== void 0 && existing !== reaction)
844
+ throw new Error(
845
+ `Duplicate reaction "${name}" for event "${event_name}". Reaction handlers are keyed by function name; rename one of them.`
846
+ );
847
+ target_reg.reactions.set(name, reaction);
848
+ }
849
+ }
850
+ }
851
+ function merge_projection(proj, events) {
852
+ for (const event_name of Object.keys(proj.events)) {
853
+ const proj_register = proj.events[event_name];
854
+ const existing = events[event_name];
855
+ if (!existing) {
856
+ events[event_name] = {
857
+ schema: proj_register.schema,
858
+ reactions: new Map(proj_register.reactions)
859
+ };
860
+ } else {
861
+ for (const [name, reaction] of proj_register.reactions) {
862
+ let key = name;
863
+ while (existing.reactions.has(key)) key = `${key}_p`;
864
+ existing.reactions.set(key, reaction);
865
+ }
866
+ }
867
+ }
868
+ }
869
+ var _this_ = ({ stream }) => ({
870
+ source: stream,
871
+ target: stream
872
+ });
873
+
874
+ // src/internal/builder-utils.ts
875
+ function register_lane(config2, lanes) {
876
+ if (config2.name === DEFAULT_LANE)
877
+ throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
878
+ if (lanes.some((l) => l.name === config2.name))
879
+ throw new Error(`Lane "${config2.name}" was already declared`);
880
+ lanes.push(config2);
881
+ }
882
+ function reaction_on(event, events, builder) {
883
+ const register = (handler, options, schedule) => {
884
+ if (!handler.name)
885
+ throw new Error(
886
+ `Reaction handler for "${String(event)}" must be a named function`
887
+ );
888
+ if (events[event].reactions.has(handler.name))
889
+ throw new Error(
890
+ `Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
891
+ );
892
+ if (schedule && typeof schedule !== "function") assert_defer_when(schedule);
893
+ const reaction = {
894
+ handler: schedule ? make_deferred(handler, schedule) : handler,
895
+ resolver: _this_,
896
+ options: {
897
+ blockOnError: options?.blockOnError ?? true,
898
+ maxRetries: options?.maxRetries ?? 3,
899
+ backoff: options?.backoff
900
+ }
901
+ };
902
+ events[event].reactions.set(handler.name, reaction);
903
+ return Object.assign(builder, {
904
+ to(resolver) {
905
+ reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
906
+ return builder;
907
+ }
908
+ });
909
+ };
910
+ return {
911
+ do: (handler, options) => register(handler, options),
912
+ defer: (schedule) => ({
913
+ do: (handler, options) => register(handler, options, schedule)
914
+ })
915
+ };
916
+ }
917
+
918
+ // src/internal/circuit-breaker.ts
919
+ import { z as z4 } from "zod";
675
920
  var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
676
921
  var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
677
- var CircuitBreakerOptionsSchema = z3.object({
678
- failureThreshold: z3.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
679
- cooldownMs: z3.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
922
+ var CircuitBreakerOptionsSchema = z4.object({
923
+ failureThreshold: z4.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
924
+ cooldownMs: z4.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
680
925
  });
681
926
  var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
682
927
  var CircuitBreaker = class {
@@ -1106,17 +1351,6 @@ function close_correlation(correlator, actor) {
1106
1351
  });
1107
1352
  }
1108
1353
 
1109
- // src/internal/defer-signal.ts
1110
- var DeferSignal = class extends Error {
1111
- /** Wall-clock time (ms since epoch) to re-visit the stream. */
1112
- defer_at;
1113
- constructor(defer_at) {
1114
- super("reaction deferred");
1115
- this.name = "DeferSignal";
1116
- this.defer_at = defer_at;
1117
- }
1118
- };
1119
-
1120
1354
  // src/internal/drain-cycle.ts
1121
1355
  import { randomUUID } from "crypto";
1122
1356
 
@@ -1963,154 +2197,6 @@ var DrainController = class {
1963
2197
  }
1964
2198
  };
1965
2199
 
1966
- // src/internal/merge.ts
1967
- import { ZodObject } from "zod";
1968
- function base_type_name(zodType) {
1969
- let t = zodType;
1970
- while (typeof t.unwrap === "function") {
1971
- t = t.unwrap();
1972
- }
1973
- return t.constructor.name;
1974
- }
1975
- function merge_schemas(existing, incoming, state_name) {
1976
- if (existing instanceof ZodObject && incoming instanceof ZodObject) {
1977
- const existing_shape = existing.shape;
1978
- const incoming_shape = incoming.shape;
1979
- for (const key of Object.keys(incoming_shape)) {
1980
- if (key in existing_shape) {
1981
- const existing_base = base_type_name(existing_shape[key]);
1982
- const incoming_base = base_type_name(incoming_shape[key]);
1983
- if (existing_base !== incoming_base) {
1984
- throw new Error(
1985
- `Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
1986
- );
1987
- }
1988
- }
1989
- }
1990
- return existing.extend(incoming_shape);
1991
- }
1992
- return existing;
1993
- }
1994
- function merge_inits(existing, incoming) {
1995
- return () => ({ ...existing(), ...incoming() });
1996
- }
1997
- function register_state(state2, states, actions, events) {
1998
- const existing = states.get(state2.name);
1999
- if (existing) {
2000
- merge_into_existing(state2, existing, states, actions, events);
2001
- } else {
2002
- register_new_state(state2, states, actions, events);
2003
- }
2004
- }
2005
- function register_new_state(state2, states, actions, events) {
2006
- states.set(state2.name, state2);
2007
- for (const name of Object.keys(state2.actions)) {
2008
- if (actions[name]) throw new Error(`Duplicate action "${name}"`);
2009
- actions[name] = state2;
2010
- }
2011
- for (const name of Object.keys(state2.events)) {
2012
- if (events[name]) throw new Error(`Duplicate event "${name}"`);
2013
- events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
2014
- }
2015
- }
2016
- function merge_into_existing(state2, existing, states, actions, events) {
2017
- for (const name of Object.keys(state2.actions)) {
2018
- if (existing.actions[name] === state2.actions[name]) continue;
2019
- if (actions[name]) throw new Error(`Duplicate action "${name}"`);
2020
- }
2021
- for (const name of Object.keys(state2.events)) {
2022
- if (existing.events[name] === state2.events[name]) continue;
2023
- if (existing.events[name]) {
2024
- throw new Error(
2025
- `Event "${name}" in state "${state2.name}" is declared with different Zod schemas across slices. Cross-slice event schemas must reference the same instance \u2014 extract a shared schema (e.g. \`export const ${name} = z.object({ ... })\` in a shared module) and import it in every slice that declares it.`
2026
- );
2027
- }
2028
- if (events[name]) throw new Error(`Duplicate event "${name}"`);
2029
- }
2030
- const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
2031
- const merged = {
2032
- ...existing,
2033
- state: merge_schemas(existing.state, state2.state, state2.name),
2034
- init: merge_inits(existing.init, state2.init),
2035
- events: { ...existing.events, ...state2.events },
2036
- actions: { ...existing.actions, ...state2.actions },
2037
- patch: merged_patch,
2038
- on: { ...existing.on, ...state2.on },
2039
- given: { ...existing.given, ...state2.given },
2040
- snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
2041
- throw new Error(
2042
- `Duplicate snap strategy for state "${state2.name}"`
2043
- );
2044
- })() : state2.snap || existing.snap
2045
- };
2046
- states.set(state2.name, merged);
2047
- for (const name of Object.keys(merged.actions)) {
2048
- actions[name] = merged;
2049
- }
2050
- for (const name of Object.keys(state2.events)) {
2051
- if (events[name]) continue;
2052
- events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
2053
- }
2054
- }
2055
- function merge_patches(existing, incoming, state_name) {
2056
- const merged = { ...existing };
2057
- for (const name of Object.keys(incoming)) {
2058
- const existing_p = existing[name];
2059
- const incoming_p = incoming[name];
2060
- if (!existing_p) {
2061
- merged[name] = incoming_p;
2062
- continue;
2063
- }
2064
- const existing_is_default = existing_p._passthrough;
2065
- const incoming_is_default = incoming_p._passthrough;
2066
- if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
2067
- throw new Error(
2068
- `Duplicate custom patch for event "${name}" in state "${state_name}"`
2069
- );
2070
- }
2071
- if (existing_is_default && !incoming_is_default) {
2072
- merged[name] = incoming_p;
2073
- }
2074
- }
2075
- return merged;
2076
- }
2077
- function merge_event_register(target, source) {
2078
- for (const [event_name, source_reg] of Object.entries(source)) {
2079
- const target_reg = target[event_name];
2080
- if (!target_reg) continue;
2081
- for (const [name, reaction] of source_reg.reactions) {
2082
- const existing = target_reg.reactions.get(name);
2083
- if (existing !== void 0 && existing !== reaction)
2084
- throw new Error(
2085
- `Duplicate reaction "${name}" for event "${event_name}". Reaction handlers are keyed by function name; rename one of them.`
2086
- );
2087
- target_reg.reactions.set(name, reaction);
2088
- }
2089
- }
2090
- }
2091
- function merge_projection(proj, events) {
2092
- for (const event_name of Object.keys(proj.events)) {
2093
- const proj_register = proj.events[event_name];
2094
- const existing = events[event_name];
2095
- if (!existing) {
2096
- events[event_name] = {
2097
- schema: proj_register.schema,
2098
- reactions: new Map(proj_register.reactions)
2099
- };
2100
- } else {
2101
- for (const [name, reaction] of proj_register.reactions) {
2102
- let key = name;
2103
- while (existing.reactions.has(key)) key = `${key}_p`;
2104
- existing.reactions.set(key, reaction);
2105
- }
2106
- }
2107
- }
2108
- }
2109
- var _this_ = ({ stream }) => ({
2110
- source: stream,
2111
- target: stream
2112
- });
2113
-
2114
2200
  // src/internal/reactions.ts
2115
2201
  function finalize(lease, handled, at, error, options, logger, failed_at) {
2116
2202
  if (!error) return { lease, handled, acked_at: at };
@@ -2167,7 +2253,12 @@ function build_handle(deps) {
2167
2253
  handled++;
2168
2254
  } catch (error) {
2169
2255
  if (error instanceof DeferSignal)
2170
- return { lease, handled, acked_at: at, defer: error.defer_at };
2256
+ return {
2257
+ lease,
2258
+ handled,
2259
+ acked_at: at,
2260
+ defer: resolve_defer_at(error.when, event)
2261
+ };
2171
2262
  if (error instanceof CloseSignal)
2172
2263
  return {
2173
2264
  lease,
@@ -2487,9 +2578,9 @@ var Act = class {
2487
2578
  const aggregate = event.stream;
2488
2579
  const config2 = this._autoclose_config;
2489
2580
  if (!in_autoclose_window(config2.autocloseWindow, /* @__PURE__ */ new Date()))
2490
- throw new DeferSignal(
2491
- Date.now() + config2.autocloseCycleMinutes * 6e4
2492
- );
2581
+ throw new DeferSignal({
2582
+ at: new Date(Date.now() + config2.autocloseCycleMinutes * 6e4)
2583
+ });
2493
2584
  const stats = await store().query_stats([aggregate], {
2494
2585
  count: true,
2495
2586
  exclude: [TOMBSTONE_EVENT]
@@ -2507,7 +2598,9 @@ var Act = class {
2507
2598
  at: head.id
2508
2599
  });
2509
2600
  if (after_ms !== void 0)
2510
- throw new DeferSignal(head.created.getTime() + after_ms);
2601
+ throw new DeferSignal({
2602
+ at: new Date(head.created.getTime() + after_ms)
2603
+ });
2511
2604
  }
2512
2605
  };
2513
2606
  const key = `__autoclose_${st.name}`;
@@ -3608,41 +3701,10 @@ function act() {
3608
3701
  },
3609
3702
  withActor: () => builder,
3610
3703
  withLane: (config2) => {
3611
- if (config2.name === DEFAULT_LANE)
3612
- throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
3613
- if (lanes.some((l) => l.name === config2.name))
3614
- throw new Error(`Lane "${config2.name}" was already declared`);
3615
- lanes.push(config2);
3704
+ register_lane(config2, lanes);
3616
3705
  return builder;
3617
3706
  },
3618
- on: (event) => ({
3619
- do: (handler, options) => {
3620
- const reaction = {
3621
- handler,
3622
- resolver: _this_,
3623
- options: {
3624
- blockOnError: options?.blockOnError ?? true,
3625
- maxRetries: options?.maxRetries ?? 3,
3626
- backoff: options?.backoff
3627
- }
3628
- };
3629
- if (!handler.name)
3630
- throw new Error(
3631
- `Reaction handler for "${String(event)}" must be a named function`
3632
- );
3633
- if (registry.events[event].reactions.has(handler.name))
3634
- throw new Error(
3635
- `Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
3636
- );
3637
- registry.events[event].reactions.set(handler.name, reaction);
3638
- return Object.assign(builder, {
3639
- to(resolver) {
3640
- reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
3641
- return builder;
3642
- }
3643
- });
3644
- }
3645
- }),
3707
+ on: (event) => reaction_on(event, registry.events, builder),
3646
3708
  build: (options) => {
3647
3709
  if (!_built) {
3648
3710
  for (const proj of pending_projections) {
@@ -3807,41 +3869,10 @@ function slice() {
3807
3869
  return builder;
3808
3870
  },
3809
3871
  withLane: (config2) => {
3810
- if (config2.name === DEFAULT_LANE)
3811
- throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
3812
- if (lanes.some((l) => l.name === config2.name))
3813
- throw new Error(`Lane "${config2.name}" was already declared`);
3814
- lanes.push(config2);
3872
+ register_lane(config2, lanes);
3815
3873
  return builder;
3816
3874
  },
3817
- on: (event) => ({
3818
- do: (handler, options) => {
3819
- const reaction = {
3820
- handler,
3821
- resolver: _this_,
3822
- options: {
3823
- blockOnError: options?.blockOnError ?? true,
3824
- maxRetries: options?.maxRetries ?? 3,
3825
- backoff: options?.backoff
3826
- }
3827
- };
3828
- if (!handler.name)
3829
- throw new Error(
3830
- `Reaction handler for "${String(event)}" must be a named function`
3831
- );
3832
- if (events[event].reactions.has(handler.name))
3833
- throw new Error(
3834
- `Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
3835
- );
3836
- events[event].reactions.set(handler.name, reaction);
3837
- return Object.assign(builder, {
3838
- to(resolver) {
3839
- reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
3840
- return builder;
3841
- }
3842
- });
3843
- }
3844
- }),
3875
+ on: (event) => reaction_on(event, events, builder),
3845
3876
  build: () => ({
3846
3877
  _tag: "Slice",
3847
3878
  states,
@@ -4151,6 +4182,7 @@ export {
4151
4182
  DEFAULT_LANE,
4152
4183
  DEFAULT_MAX_SUBSCRIBED_STREAMS,
4153
4184
  DEFAULT_SETTLE_DEBOUNCE_MS,
4185
+ DeferSignal,
4154
4186
  Environments,
4155
4187
  Errors,
4156
4188
  EventMetaSchema,