@rotorsoft/act 1.31.2 → 1.31.3
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/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/{internal → builders}/build-classify.d.ts +1 -1
- package/dist/@types/{internal → builders}/build-classify.d.ts.map +1 -1
- package/dist/@types/builders/builder-base.d.ts +1 -1
- package/dist/@types/{internal → builders}/builder-utils.d.ts +1 -1
- package/dist/@types/{internal → builders}/builder-utils.d.ts.map +1 -1
- package/dist/@types/{internal → builders}/merge.d.ts +1 -1
- package/dist/@types/{internal → builders}/merge.d.ts.map +1 -1
- package/dist/@types/builders/projection-builder.d.ts.map +1 -1
- package/dist/@types/{internal/reactions.d.ts → builders/reaction-builder.d.ts} +15 -14
- package/dist/@types/builders/reaction-builder.d.ts.map +1 -0
- package/dist/@types/builders/slice-builder.d.ts +7 -0
- package/dist/@types/builders/slice-builder.d.ts.map +1 -1
- package/dist/@types/{internal/disposers.d.ts → disposers.d.ts} +1 -1
- package/dist/@types/disposers.d.ts.map +1 -0
- package/dist/@types/internal/defer-signal.d.ts +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +4 -7
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +2 -10
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/scoped.d.ts +86 -0
- package/dist/@types/scoped.d.ts.map +1 -0
- package/dist/{chunk-YUJ7ACSP.js → chunk-IBLR35N5.js} +27 -9
- package/dist/chunk-IBLR35N5.js.map +1 -0
- package/dist/index.cjs +597 -597
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +578 -591
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +5 -4
- package/dist/test/index.cjs.map +1 -1
- package/dist/test/index.js +1 -1
- package/package.json +2 -2
- package/dist/@types/internal/disposers.d.ts.map +0 -1
- package/dist/@types/internal/reacting.d.ts +0 -17
- package/dist/@types/internal/reacting.d.ts.map +0 -1
- package/dist/@types/internal/reactions.d.ts.map +0 -1
- package/dist/chunk-YUJ7ACSP.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -10,19 +10,21 @@ import {
|
|
|
10
10
|
TOMBSTONE_EVENT,
|
|
11
11
|
cache,
|
|
12
12
|
config,
|
|
13
|
+
current_reacting,
|
|
13
14
|
dateReviver,
|
|
14
15
|
dispose,
|
|
15
16
|
disposeAndExit,
|
|
16
17
|
extend,
|
|
17
18
|
is_literal_source,
|
|
18
19
|
log,
|
|
20
|
+
make_reaction_scope,
|
|
21
|
+
make_run_scoped,
|
|
19
22
|
port,
|
|
20
23
|
register_weak_disposer,
|
|
21
|
-
scoped,
|
|
22
24
|
sleep,
|
|
23
25
|
store,
|
|
24
26
|
validate
|
|
25
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-IBLR35N5.js";
|
|
26
28
|
import {
|
|
27
29
|
ActorSchema,
|
|
28
30
|
CausationEventSchema,
|
|
@@ -71,6 +73,58 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
71
73
|
// src/act.ts
|
|
72
74
|
import EventEmitter from "events";
|
|
73
75
|
|
|
76
|
+
// src/builders/build-classify.ts
|
|
77
|
+
var ALL_LANES = /* @__PURE__ */ Symbol("act-1103/all-lanes");
|
|
78
|
+
function classify_registry(registry, states) {
|
|
79
|
+
const statics = /* @__PURE__ */ new Map();
|
|
80
|
+
const target_lanes = /* @__PURE__ */ new Map();
|
|
81
|
+
const reactive_events = /* @__PURE__ */ new Set();
|
|
82
|
+
const event_to_lanes = /* @__PURE__ */ new Map();
|
|
83
|
+
for (const [name, register] of Object.entries(registry.events)) {
|
|
84
|
+
if (register.reactions.size > 0) reactive_events.add(name);
|
|
85
|
+
for (const reaction of register.reactions.values()) {
|
|
86
|
+
if (typeof reaction.resolver === "function") {
|
|
87
|
+
event_to_lanes.set(name, ALL_LANES);
|
|
88
|
+
} else {
|
|
89
|
+
const { target, source, priority = 0, lane } = reaction.resolver;
|
|
90
|
+
const lane_name = lane ?? "default";
|
|
91
|
+
const existing_lanes = event_to_lanes.get(name);
|
|
92
|
+
if (existing_lanes !== ALL_LANES) {
|
|
93
|
+
const set = existing_lanes ?? /* @__PURE__ */ new Set();
|
|
94
|
+
set.add(lane_name);
|
|
95
|
+
event_to_lanes.set(name, set);
|
|
96
|
+
}
|
|
97
|
+
if (!target_lanes.has(target)) {
|
|
98
|
+
target_lanes.set(target, lane);
|
|
99
|
+
} else if ((target_lanes.get(target) ?? void 0) !== (lane ?? void 0)) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Stream "${target}" has conflicting lane assignments ("${target_lanes.get(target) ?? "default"}" vs "${lane ?? "default"}")`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const key = `${target}|${source ?? ""}`;
|
|
105
|
+
const existing = statics.get(key);
|
|
106
|
+
if (!existing) {
|
|
107
|
+
statics.set(key, { stream: target, source, priority, lane });
|
|
108
|
+
} else if (priority > existing.priority) {
|
|
109
|
+
statics.set(key, { ...existing, priority });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const event_to_state = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const merged of states.values()) {
|
|
116
|
+
for (const event_name of Object.keys(merged.events)) {
|
|
117
|
+
event_to_state.set(event_name, merged);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
static_targets: [...statics.values()],
|
|
122
|
+
reactive_events,
|
|
123
|
+
event_to_state,
|
|
124
|
+
event_to_lanes
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
74
128
|
// src/internal/event-versions.ts
|
|
75
129
|
var VERSION_SUFFIX = /^(.+?)_v(\d+)$/;
|
|
76
130
|
function parse(name) {
|
|
@@ -822,432 +876,43 @@ function synthesize_autoclose_reactions(registry, states, config2) {
|
|
|
822
876
|
}
|
|
823
877
|
}
|
|
824
878
|
|
|
825
|
-
// src/internal/
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
const
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
event_to_lanes.set(name, set);
|
|
845
|
-
}
|
|
846
|
-
if (!target_lanes.has(target)) {
|
|
847
|
-
target_lanes.set(target, lane);
|
|
848
|
-
} else if ((target_lanes.get(target) ?? void 0) !== (lane ?? void 0)) {
|
|
849
|
-
throw new Error(
|
|
850
|
-
`Stream "${target}" has conflicting lane assignments ("${target_lanes.get(target) ?? "default"}" vs "${lane ?? "default"}")`
|
|
851
|
-
);
|
|
852
|
-
}
|
|
853
|
-
const key = `${target}|${source ?? ""}`;
|
|
854
|
-
const existing = statics.get(key);
|
|
855
|
-
if (!existing) {
|
|
856
|
-
statics.set(key, { stream: target, source, priority, lane });
|
|
857
|
-
} else if (priority > existing.priority) {
|
|
858
|
-
statics.set(key, { ...existing, priority });
|
|
859
|
-
}
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
const event_to_state = /* @__PURE__ */ new Map();
|
|
864
|
-
for (const merged of states.values()) {
|
|
865
|
-
for (const event_name of Object.keys(merged.events)) {
|
|
866
|
-
event_to_state.set(event_name, merged);
|
|
879
|
+
// src/internal/backoff.ts
|
|
880
|
+
function compute_backoff_delay(retry, opts) {
|
|
881
|
+
if (!opts || opts.baseMs <= 0) return 0;
|
|
882
|
+
const r = Math.max(0, retry);
|
|
883
|
+
let delay;
|
|
884
|
+
switch (opts.strategy) {
|
|
885
|
+
case "fixed":
|
|
886
|
+
delay = opts.baseMs;
|
|
887
|
+
break;
|
|
888
|
+
case "linear":
|
|
889
|
+
delay = opts.baseMs * (r + 1);
|
|
890
|
+
break;
|
|
891
|
+
case "exponential":
|
|
892
|
+
delay = opts.baseMs * 2 ** r;
|
|
893
|
+
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
894
|
+
break;
|
|
895
|
+
default: {
|
|
896
|
+
const _never = opts.strategy;
|
|
897
|
+
throw new Error(`unknown backoff strategy: ${String(_never)}`);
|
|
867
898
|
}
|
|
868
899
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
reactive_events,
|
|
872
|
-
event_to_state,
|
|
873
|
-
event_to_lanes
|
|
874
|
-
};
|
|
900
|
+
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
901
|
+
return Math.max(0, Math.floor(delay));
|
|
875
902
|
}
|
|
876
903
|
|
|
877
|
-
// src/internal/
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
});
|
|
890
|
-
function resolveReactionConfig(options) {
|
|
891
|
-
return ReactionOptionsSchema.parse(options);
|
|
892
|
-
}
|
|
893
|
-
var ActionOptionsSchema = z2.object({
|
|
894
|
-
maxRetries: z2.number().int().min(0).optional(),
|
|
895
|
-
backoff: BackoffOptionsSchema.optional()
|
|
896
|
-
});
|
|
897
|
-
function resolveActionConfig(options) {
|
|
898
|
-
return ActionOptionsSchema.parse(options);
|
|
899
|
-
}
|
|
900
|
-
var LaneConfigSchema = z2.object({
|
|
901
|
-
name: z2.string().min(1),
|
|
902
|
-
leaseMillis: z2.number().min(0).optional(),
|
|
903
|
-
streamLimit: z2.number().int().min(0).optional(),
|
|
904
|
-
cycleMs: z2.number().min(0).optional()
|
|
905
|
-
});
|
|
906
|
-
function resolveLaneConfig(options) {
|
|
907
|
-
LaneConfigSchema.parse(options);
|
|
908
|
-
return options;
|
|
909
|
-
}
|
|
910
|
-
var DrainOptionsSchema = z2.object({
|
|
911
|
-
streamLimit: z2.number().int().min(0).optional(),
|
|
912
|
-
eventLimit: z2.number().int().min(0).optional(),
|
|
913
|
-
leaseMillis: z2.number().min(0).optional()
|
|
914
|
-
});
|
|
915
|
-
function resolveDrainConfig(options) {
|
|
916
|
-
if (options === void 0) return void 0;
|
|
917
|
-
DrainOptionsSchema.parse(options);
|
|
918
|
-
return options;
|
|
919
|
-
}
|
|
920
|
-
var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
921
|
-
debounceMs: z2.number().min(0).optional(),
|
|
922
|
-
// `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
|
|
923
|
-
// a non-negative integer — `0` is legal and means "run no passes".
|
|
924
|
-
maxPasses: z2.number().int().min(0).optional()
|
|
925
|
-
}).loose();
|
|
926
|
-
function resolveSettleConfig(options) {
|
|
927
|
-
if (options === void 0) return void 0;
|
|
928
|
-
SettleOptionsSchema.parse(options);
|
|
929
|
-
return options;
|
|
930
|
-
}
|
|
931
|
-
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
932
|
-
var DEFAULT_SHUTDOWN_GRACE_MS = 1e4;
|
|
933
|
-
var ShutdownOptionsSchema = z2.object({
|
|
934
|
-
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
935
|
-
// return without waiting.
|
|
936
|
-
graceMs: z2.number().min(0).optional()
|
|
937
|
-
}).loose();
|
|
938
|
-
function resolveShutdownConfig(options) {
|
|
939
|
-
if (options === void 0) return void 0;
|
|
940
|
-
ShutdownOptionsSchema.parse(options);
|
|
941
|
-
return options;
|
|
942
|
-
}
|
|
943
|
-
var ActOptionsSchema = z2.object({
|
|
944
|
-
maxSubscribedStreams: z2.number().int().min(1).optional(),
|
|
945
|
-
settleDebounceMs: z2.number().int().min(0).optional()
|
|
946
|
-
}).loose();
|
|
947
|
-
function resolveActConfig(options) {
|
|
948
|
-
if (options === void 0) return void 0;
|
|
949
|
-
ActOptionsSchema.parse(options);
|
|
950
|
-
return options;
|
|
951
|
-
}
|
|
952
|
-
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
953
|
-
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
954
|
-
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
955
|
-
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
956
|
-
function is_valid_time_zone(tz) {
|
|
957
|
-
try {
|
|
958
|
-
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
959
|
-
return true;
|
|
960
|
-
} catch {
|
|
961
|
-
return false;
|
|
962
|
-
}
|
|
963
|
-
}
|
|
964
|
-
var AutocloseWindowSchema = z2.object({
|
|
965
|
-
start: z2.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
966
|
-
end: z2.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
967
|
-
timeZone: z2.string().refine(is_valid_time_zone, {
|
|
968
|
-
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
969
|
-
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
970
|
-
}).refine((w) => w.start !== w.end, {
|
|
971
|
-
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
972
|
-
});
|
|
973
|
-
var AutocloseOptionsSchema = z2.object({
|
|
974
|
-
autocloseCycleMinutes: z2.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
975
|
-
closeBatchSize: z2.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
976
|
-
closeYieldMs: z2.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
977
|
-
closeOnError: z2.boolean().default(false),
|
|
978
|
-
autocloseWindow: AutocloseWindowSchema.optional()
|
|
979
|
-
});
|
|
980
|
-
function resolveAutocloseConfig(options) {
|
|
981
|
-
return AutocloseOptionsSchema.parse({
|
|
982
|
-
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
983
|
-
closeBatchSize: options?.closeBatchSize,
|
|
984
|
-
closeYieldMs: options?.closeYieldMs,
|
|
985
|
-
closeOnError: options?.closeOnError,
|
|
986
|
-
autocloseWindow: options?.autocloseWindow
|
|
987
|
-
});
|
|
988
|
-
}
|
|
989
|
-
var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
990
|
-
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
991
|
-
var CircuitBreakerOptionsSchema = z2.object({
|
|
992
|
-
failureThreshold: z2.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
|
|
993
|
-
cooldownMs: z2.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
|
|
994
|
-
});
|
|
995
|
-
var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
|
|
996
|
-
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
997
|
-
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
998
|
-
var FoldOptionsSchema = z2.object({
|
|
999
|
-
flushEvery: z2.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
1000
|
-
maxCachedStates: z2.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
1001
|
-
});
|
|
1002
|
-
function resolveFoldConfig(options) {
|
|
1003
|
-
return FoldOptionsSchema.parse(options);
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// src/internal/defer-config.ts
|
|
1007
|
-
import { z as z3 } from "zod";
|
|
1008
|
-
var DeferDurationSchema = z3.object({
|
|
1009
|
-
days: z3.number().positive().optional(),
|
|
1010
|
-
hours: z3.number().positive().optional(),
|
|
1011
|
-
minutes: z3.number().positive().optional()
|
|
1012
|
-
}).strict().refine((d) => d.days != null || d.hours != null || d.minutes != null, {
|
|
1013
|
-
message: "defer: a duration needs at least one of days, hours, or minutes"
|
|
1014
|
-
});
|
|
1015
|
-
var DeferWhenSchema = z3.object({
|
|
1016
|
-
after: DeferDurationSchema.optional(),
|
|
1017
|
-
at: z3.date().optional()
|
|
1018
|
-
}).strict().refine((w) => w.after === void 0 !== (w.at === void 0), {
|
|
1019
|
-
message: "defer: specify exactly one of `after` or `at`"
|
|
1020
|
-
});
|
|
1021
|
-
function duration_ms(d) {
|
|
1022
|
-
return (d.days ?? 0) * 864e5 + (d.hours ?? 0) * 36e5 + (d.minutes ?? 0) * 6e4;
|
|
1023
|
-
}
|
|
1024
|
-
function resolve_defer_at(when, event) {
|
|
1025
|
-
const parsed = DeferWhenSchema.parse(when);
|
|
1026
|
-
if (parsed.after) return event.created.getTime() + duration_ms(parsed.after);
|
|
1027
|
-
return parsed.at.getTime();
|
|
1028
|
-
}
|
|
1029
|
-
function assert_defer_when(when) {
|
|
1030
|
-
DeferWhenSchema.parse(when);
|
|
1031
|
-
}
|
|
1032
|
-
function make_deferred(handler, schedule) {
|
|
1033
|
-
const call = handler;
|
|
1034
|
-
const deferred = async (event, stream, app) => {
|
|
1035
|
-
const when = typeof schedule === "function" ? schedule(event) : schedule;
|
|
1036
|
-
if (Date.now() < resolve_defer_at(when, event)) throw new DeferSignal(when);
|
|
1037
|
-
return call(event, stream, app);
|
|
1038
|
-
};
|
|
1039
|
-
Object.defineProperty(deferred, "name", { value: handler.name });
|
|
1040
|
-
return deferred;
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
// src/internal/merge.ts
|
|
1044
|
-
import { ZodObject } from "zod";
|
|
1045
|
-
function base_type_name(zodType) {
|
|
1046
|
-
let t = zodType;
|
|
1047
|
-
while (typeof t.unwrap === "function") {
|
|
1048
|
-
t = t.unwrap();
|
|
1049
|
-
}
|
|
1050
|
-
return t.constructor.name;
|
|
1051
|
-
}
|
|
1052
|
-
function merge_schemas(existing, incoming, state_name) {
|
|
1053
|
-
if (existing instanceof ZodObject && incoming instanceof ZodObject) {
|
|
1054
|
-
const existing_shape = existing.shape;
|
|
1055
|
-
const incoming_shape = incoming.shape;
|
|
1056
|
-
for (const key of Object.keys(incoming_shape)) {
|
|
1057
|
-
if (key in existing_shape) {
|
|
1058
|
-
const existing_base = base_type_name(existing_shape[key]);
|
|
1059
|
-
const incoming_base = base_type_name(incoming_shape[key]);
|
|
1060
|
-
if (existing_base !== incoming_base) {
|
|
1061
|
-
throw new Error(
|
|
1062
|
-
`Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
|
|
1063
|
-
);
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}
|
|
1067
|
-
return existing.extend(incoming_shape);
|
|
1068
|
-
}
|
|
1069
|
-
return existing;
|
|
1070
|
-
}
|
|
1071
|
-
function merge_inits(existing, incoming) {
|
|
1072
|
-
return () => ({ ...existing(), ...incoming() });
|
|
1073
|
-
}
|
|
1074
|
-
function register_state(state2, states, actions, events) {
|
|
1075
|
-
const existing = states.get(state2.name);
|
|
1076
|
-
if (existing) {
|
|
1077
|
-
merge_into_existing(state2, existing, states, actions, events);
|
|
1078
|
-
} else {
|
|
1079
|
-
register_new_state(state2, states, actions, events);
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
function register_new_state(state2, states, actions, events) {
|
|
1083
|
-
states.set(state2.name, state2);
|
|
1084
|
-
for (const name of Object.keys(state2.actions)) {
|
|
1085
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
1086
|
-
actions[name] = state2;
|
|
1087
|
-
}
|
|
1088
|
-
for (const name of Object.keys(state2.events)) {
|
|
1089
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1090
|
-
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
function merge_into_existing(state2, existing, states, actions, events) {
|
|
1094
|
-
for (const name of Object.keys(state2.actions)) {
|
|
1095
|
-
if (existing.actions[name] === state2.actions[name]) continue;
|
|
1096
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
1097
|
-
}
|
|
1098
|
-
for (const name of Object.keys(state2.events)) {
|
|
1099
|
-
if (existing.events[name] === state2.events[name]) continue;
|
|
1100
|
-
if (existing.events[name]) {
|
|
1101
|
-
throw new Error(
|
|
1102
|
-
`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.`
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1106
|
-
}
|
|
1107
|
-
const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
|
|
1108
|
-
const merged = {
|
|
1109
|
-
...existing,
|
|
1110
|
-
state: merge_schemas(existing.state, state2.state, state2.name),
|
|
1111
|
-
init: merge_inits(existing.init, state2.init),
|
|
1112
|
-
events: { ...existing.events, ...state2.events },
|
|
1113
|
-
actions: { ...existing.actions, ...state2.actions },
|
|
1114
|
-
patch: merged_patch,
|
|
1115
|
-
on: { ...existing.on, ...state2.on },
|
|
1116
|
-
given: { ...existing.given, ...state2.given },
|
|
1117
|
-
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
1118
|
-
throw new Error(
|
|
1119
|
-
`Duplicate snap strategy for state "${state2.name}"`
|
|
1120
|
-
);
|
|
1121
|
-
})() : state2.snap || existing.snap
|
|
1122
|
-
};
|
|
1123
|
-
states.set(state2.name, merged);
|
|
1124
|
-
for (const name of Object.keys(merged.actions)) {
|
|
1125
|
-
actions[name] = merged;
|
|
1126
|
-
}
|
|
1127
|
-
for (const name of Object.keys(state2.events)) {
|
|
1128
|
-
if (events[name]) continue;
|
|
1129
|
-
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
1130
|
-
}
|
|
1131
|
-
}
|
|
1132
|
-
function merge_patches(existing, incoming, state_name) {
|
|
1133
|
-
const merged = { ...existing };
|
|
1134
|
-
for (const name of Object.keys(incoming)) {
|
|
1135
|
-
const existing_p = existing[name];
|
|
1136
|
-
const incoming_p = incoming[name];
|
|
1137
|
-
if (!existing_p) {
|
|
1138
|
-
merged[name] = incoming_p;
|
|
1139
|
-
continue;
|
|
1140
|
-
}
|
|
1141
|
-
const existing_is_default = existing_p._passthrough;
|
|
1142
|
-
const incoming_is_default = incoming_p._passthrough;
|
|
1143
|
-
if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
|
|
1144
|
-
throw new Error(
|
|
1145
|
-
`Duplicate custom patch for event "${name}" in state "${state_name}"`
|
|
1146
|
-
);
|
|
1147
|
-
}
|
|
1148
|
-
if (existing_is_default && !incoming_is_default) {
|
|
1149
|
-
merged[name] = incoming_p;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
return merged;
|
|
1153
|
-
}
|
|
1154
|
-
function merge_event_register(target, source) {
|
|
1155
|
-
for (const [event_name, source_reg] of Object.entries(source)) {
|
|
1156
|
-
const target_reg = target[event_name];
|
|
1157
|
-
if (!target_reg) continue;
|
|
1158
|
-
for (const [name, reaction] of source_reg.reactions) {
|
|
1159
|
-
const existing = target_reg.reactions.get(name);
|
|
1160
|
-
if (existing !== void 0 && existing !== reaction)
|
|
1161
|
-
throw new Error(
|
|
1162
|
-
`Duplicate reaction "${name}" for event "${event_name}". Reaction handlers are keyed by function name; rename one of them.`
|
|
1163
|
-
);
|
|
1164
|
-
target_reg.reactions.set(name, reaction);
|
|
1165
|
-
}
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
function merge_projection(proj, events) {
|
|
1169
|
-
for (const event_name of Object.keys(proj.events)) {
|
|
1170
|
-
const proj_register = proj.events[event_name];
|
|
1171
|
-
const existing = events[event_name];
|
|
1172
|
-
if (!existing) {
|
|
1173
|
-
events[event_name] = {
|
|
1174
|
-
schema: proj_register.schema,
|
|
1175
|
-
reactions: new Map(proj_register.reactions)
|
|
1176
|
-
};
|
|
1177
|
-
} else {
|
|
1178
|
-
for (const [name, reaction] of proj_register.reactions) {
|
|
1179
|
-
if ([...existing.reactions.values()].includes(reaction)) continue;
|
|
1180
|
-
let key = name;
|
|
1181
|
-
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
1182
|
-
existing.reactions.set(key, reaction);
|
|
1183
|
-
}
|
|
1184
|
-
}
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
var _this_ = ({ stream }) => ({
|
|
1188
|
-
source: stream,
|
|
1189
|
-
target: stream
|
|
1190
|
-
});
|
|
1191
|
-
|
|
1192
|
-
// src/internal/builder-utils.ts
|
|
1193
|
-
function register_lane(config2, lanes) {
|
|
1194
|
-
if (config2.name === DEFAULT_LANE)
|
|
1195
|
-
throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
|
|
1196
|
-
if (lanes.some((l) => l.name === config2.name))
|
|
1197
|
-
throw new Error(`Lane "${config2.name}" was already declared`);
|
|
1198
|
-
lanes.push(config2);
|
|
1199
|
-
}
|
|
1200
|
-
function reaction_on(event, events, builder) {
|
|
1201
|
-
const register = (handler, options, schedule) => {
|
|
1202
|
-
if (!handler.name)
|
|
1203
|
-
throw new Error(
|
|
1204
|
-
`Reaction handler for "${String(event)}" must be a named function`
|
|
1205
|
-
);
|
|
1206
|
-
if (events[event].reactions.has(handler.name))
|
|
1207
|
-
throw new Error(
|
|
1208
|
-
`Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
|
|
1209
|
-
);
|
|
1210
|
-
if (schedule && typeof schedule !== "function") assert_defer_when(schedule);
|
|
1211
|
-
const reaction = {
|
|
1212
|
-
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
1213
|
-
resolver: _this_,
|
|
1214
|
-
// #1269: validate the whole bag at the declaration site so a bad
|
|
1215
|
-
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
1216
|
-
// not a NaN gate on the first retry.
|
|
1217
|
-
options: resolveReactionConfig({
|
|
1218
|
-
blockOnError: options?.blockOnError ?? true,
|
|
1219
|
-
maxRetries: options?.maxRetries ?? 3,
|
|
1220
|
-
backoff: options?.backoff
|
|
1221
|
-
})
|
|
1222
|
-
};
|
|
1223
|
-
events[event].reactions.set(handler.name, reaction);
|
|
1224
|
-
return Object.assign(builder, {
|
|
1225
|
-
to(resolver) {
|
|
1226
|
-
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
1227
|
-
return builder;
|
|
1228
|
-
}
|
|
1229
|
-
});
|
|
1230
|
-
};
|
|
1231
|
-
return {
|
|
1232
|
-
do: (handler, options) => register(handler, options),
|
|
1233
|
-
defer: (schedule) => ({
|
|
1234
|
-
do: (handler, options) => register(handler, options, schedule)
|
|
1235
|
-
})
|
|
1236
|
-
};
|
|
1237
|
-
}
|
|
1238
|
-
|
|
1239
|
-
// src/internal/circuit-breaker.ts
|
|
1240
|
-
var CircuitBreaker = class {
|
|
1241
|
-
_failures = 0;
|
|
1242
|
-
_opened_at;
|
|
1243
|
-
_wake;
|
|
1244
|
-
_threshold;
|
|
1245
|
-
_cooldown_ms;
|
|
1246
|
-
_hooks;
|
|
1247
|
-
constructor(config2, hooks = {}) {
|
|
1248
|
-
this._threshold = config2.failureThreshold;
|
|
1249
|
-
this._cooldown_ms = config2.cooldownMs;
|
|
1250
|
-
this._hooks = hooks;
|
|
904
|
+
// src/internal/circuit-breaker.ts
|
|
905
|
+
var CircuitBreaker = class {
|
|
906
|
+
_failures = 0;
|
|
907
|
+
_opened_at;
|
|
908
|
+
_wake;
|
|
909
|
+
_threshold;
|
|
910
|
+
_cooldown_ms;
|
|
911
|
+
_hooks;
|
|
912
|
+
constructor(config2, hooks = {}) {
|
|
913
|
+
this._threshold = config2.failureThreshold;
|
|
914
|
+
this._cooldown_ms = config2.cooldownMs;
|
|
915
|
+
this._hooks = hooks;
|
|
1251
916
|
}
|
|
1252
917
|
/** Current state given the wall-clock `now`. */
|
|
1253
918
|
state(now) {
|
|
@@ -1606,6 +1271,135 @@ async function truncate_and_warm_cache(guarded, seed_states, guard_events, corre
|
|
|
1606
1271
|
return truncated;
|
|
1607
1272
|
}
|
|
1608
1273
|
|
|
1274
|
+
// src/internal/config.ts
|
|
1275
|
+
import { z as z2 } from "zod";
|
|
1276
|
+
var BackoffOptionsSchema = z2.object({
|
|
1277
|
+
strategy: z2.enum(["fixed", "linear", "exponential"]),
|
|
1278
|
+
baseMs: z2.number().min(0),
|
|
1279
|
+
maxMs: z2.number().gt(0).optional(),
|
|
1280
|
+
jitter: z2.boolean().optional()
|
|
1281
|
+
});
|
|
1282
|
+
var ReactionOptionsSchema = z2.object({
|
|
1283
|
+
blockOnError: z2.boolean(),
|
|
1284
|
+
maxRetries: z2.number().int().min(0),
|
|
1285
|
+
backoff: BackoffOptionsSchema.optional()
|
|
1286
|
+
});
|
|
1287
|
+
function resolveReactionConfig(options) {
|
|
1288
|
+
return ReactionOptionsSchema.parse(options);
|
|
1289
|
+
}
|
|
1290
|
+
var ActionOptionsSchema = z2.object({
|
|
1291
|
+
maxRetries: z2.number().int().min(0).optional(),
|
|
1292
|
+
backoff: BackoffOptionsSchema.optional()
|
|
1293
|
+
});
|
|
1294
|
+
function resolveActionConfig(options) {
|
|
1295
|
+
return ActionOptionsSchema.parse(options);
|
|
1296
|
+
}
|
|
1297
|
+
var LaneConfigSchema = z2.object({
|
|
1298
|
+
name: z2.string().min(1),
|
|
1299
|
+
leaseMillis: z2.number().min(0).optional(),
|
|
1300
|
+
streamLimit: z2.number().int().min(0).optional(),
|
|
1301
|
+
cycleMs: z2.number().min(0).optional()
|
|
1302
|
+
});
|
|
1303
|
+
function resolveLaneConfig(options) {
|
|
1304
|
+
LaneConfigSchema.parse(options);
|
|
1305
|
+
return options;
|
|
1306
|
+
}
|
|
1307
|
+
var DrainOptionsSchema = z2.object({
|
|
1308
|
+
streamLimit: z2.number().int().min(0).optional(),
|
|
1309
|
+
eventLimit: z2.number().int().min(0).optional(),
|
|
1310
|
+
leaseMillis: z2.number().min(0).optional()
|
|
1311
|
+
});
|
|
1312
|
+
function resolveDrainConfig(options) {
|
|
1313
|
+
if (options === void 0) return void 0;
|
|
1314
|
+
DrainOptionsSchema.parse(options);
|
|
1315
|
+
return options;
|
|
1316
|
+
}
|
|
1317
|
+
var SettleOptionsSchema = DrainOptionsSchema.extend({
|
|
1318
|
+
debounceMs: z2.number().min(0).optional(),
|
|
1319
|
+
// `maxPasses` defaults to Infinity (no cap) when omitted; a present value is
|
|
1320
|
+
// a non-negative integer — `0` is legal and means "run no passes".
|
|
1321
|
+
maxPasses: z2.number().int().min(0).optional()
|
|
1322
|
+
}).loose();
|
|
1323
|
+
function resolveSettleConfig(options) {
|
|
1324
|
+
if (options === void 0) return void 0;
|
|
1325
|
+
SettleOptionsSchema.parse(options);
|
|
1326
|
+
return options;
|
|
1327
|
+
}
|
|
1328
|
+
var MAX_SHUTDOWN_GRACE_MS = 3e4;
|
|
1329
|
+
var DEFAULT_SHUTDOWN_GRACE_MS = 1e4;
|
|
1330
|
+
var ShutdownOptionsSchema = z2.object({
|
|
1331
|
+
// `0` is legal and is exactly today's behavior: stop scheduling and
|
|
1332
|
+
// return without waiting.
|
|
1333
|
+
graceMs: z2.number().min(0).optional()
|
|
1334
|
+
}).loose();
|
|
1335
|
+
function resolveShutdownConfig(options) {
|
|
1336
|
+
if (options === void 0) return void 0;
|
|
1337
|
+
ShutdownOptionsSchema.parse(options);
|
|
1338
|
+
return options;
|
|
1339
|
+
}
|
|
1340
|
+
var ActOptionsSchema = z2.object({
|
|
1341
|
+
maxSubscribedStreams: z2.number().int().min(1).optional(),
|
|
1342
|
+
settleDebounceMs: z2.number().int().min(0).optional()
|
|
1343
|
+
}).loose();
|
|
1344
|
+
function resolveActConfig(options) {
|
|
1345
|
+
if (options === void 0) return void 0;
|
|
1346
|
+
ActOptionsSchema.parse(options);
|
|
1347
|
+
return options;
|
|
1348
|
+
}
|
|
1349
|
+
var DEFAULT_AUTOCLOSE_CYCLE_MINUTES = 720;
|
|
1350
|
+
var DEFAULT_CLOSE_BATCH_SIZE = 64;
|
|
1351
|
+
var DEFAULT_CLOSE_YIELD_MS = 0;
|
|
1352
|
+
var DEFAULT_AUTOCLOSE_WINDOW_TZ = "UTC";
|
|
1353
|
+
function is_valid_time_zone(tz) {
|
|
1354
|
+
try {
|
|
1355
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
1356
|
+
return true;
|
|
1357
|
+
} catch {
|
|
1358
|
+
return false;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
var AutocloseWindowSchema = z2.object({
|
|
1362
|
+
start: z2.number().int().min(0).max(23, { message: "autocloseWindow.start must be an hour in [0, 23]" }),
|
|
1363
|
+
end: z2.number().int().min(0).max(23, { message: "autocloseWindow.end must be an hour in [0, 23]" }),
|
|
1364
|
+
timeZone: z2.string().refine(is_valid_time_zone, {
|
|
1365
|
+
message: "autocloseWindow.timeZone must be a valid IANA time zone"
|
|
1366
|
+
}).default(DEFAULT_AUTOCLOSE_WINDOW_TZ)
|
|
1367
|
+
}).refine((w) => w.start !== w.end, {
|
|
1368
|
+
message: "autocloseWindow.start and end must differ \u2014 an empty window disables autoclose"
|
|
1369
|
+
});
|
|
1370
|
+
var AutocloseOptionsSchema = z2.object({
|
|
1371
|
+
autocloseCycleMinutes: z2.number().int().min(1).max(1440).default(DEFAULT_AUTOCLOSE_CYCLE_MINUTES),
|
|
1372
|
+
closeBatchSize: z2.number().int().min(1).max(1024).default(DEFAULT_CLOSE_BATCH_SIZE),
|
|
1373
|
+
closeYieldMs: z2.number().min(0).max(1e3).default(DEFAULT_CLOSE_YIELD_MS),
|
|
1374
|
+
closeOnError: z2.boolean().default(false),
|
|
1375
|
+
autocloseWindow: AutocloseWindowSchema.optional()
|
|
1376
|
+
});
|
|
1377
|
+
function resolveAutocloseConfig(options) {
|
|
1378
|
+
return AutocloseOptionsSchema.parse({
|
|
1379
|
+
autocloseCycleMinutes: options?.autocloseCycleMinutes,
|
|
1380
|
+
closeBatchSize: options?.closeBatchSize,
|
|
1381
|
+
closeYieldMs: options?.closeYieldMs,
|
|
1382
|
+
closeOnError: options?.closeOnError,
|
|
1383
|
+
autocloseWindow: options?.autocloseWindow
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1386
|
+
var DEFAULT_CIRCUIT_FAILURE_THRESHOLD = 5;
|
|
1387
|
+
var DEFAULT_CIRCUIT_COOLDOWN_MS = 3e4;
|
|
1388
|
+
var CircuitBreakerOptionsSchema = z2.object({
|
|
1389
|
+
failureThreshold: z2.number().int().min(1).default(DEFAULT_CIRCUIT_FAILURE_THRESHOLD),
|
|
1390
|
+
cooldownMs: z2.number().int().min(100).max(36e5).default(DEFAULT_CIRCUIT_COOLDOWN_MS)
|
|
1391
|
+
});
|
|
1392
|
+
var resolveCircuitBreakerConfig = (options) => CircuitBreakerOptionsSchema.parse(options ?? {});
|
|
1393
|
+
var DEFAULT_FOLD_FLUSH_EVERY = 1e3;
|
|
1394
|
+
var DEFAULT_MAX_CACHED_STATES = 1e4;
|
|
1395
|
+
var FoldOptionsSchema = z2.object({
|
|
1396
|
+
flushEvery: z2.number().int().min(1).default(DEFAULT_FOLD_FLUSH_EVERY),
|
|
1397
|
+
maxCachedStates: z2.number().int().min(1).default(DEFAULT_MAX_CACHED_STATES)
|
|
1398
|
+
});
|
|
1399
|
+
function resolveFoldConfig(options) {
|
|
1400
|
+
return FoldOptionsSchema.parse(options);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1609
1403
|
// src/internal/correlate-cycle.ts
|
|
1610
1404
|
import { createHash, randomUUID } from "crypto";
|
|
1611
1405
|
var DEFAULT_COLD_START_BACK_SCAN = 1e4;
|
|
@@ -2000,6 +1794,43 @@ function close_correlation(correlator, actor) {
|
|
|
2000
1794
|
});
|
|
2001
1795
|
}
|
|
2002
1796
|
|
|
1797
|
+
// src/internal/defer-config.ts
|
|
1798
|
+
import { z as z3 } from "zod";
|
|
1799
|
+
var DeferDurationSchema = z3.object({
|
|
1800
|
+
days: z3.number().positive().optional(),
|
|
1801
|
+
hours: z3.number().positive().optional(),
|
|
1802
|
+
minutes: z3.number().positive().optional()
|
|
1803
|
+
}).strict().refine((d) => d.days != null || d.hours != null || d.minutes != null, {
|
|
1804
|
+
message: "defer: a duration needs at least one of days, hours, or minutes"
|
|
1805
|
+
});
|
|
1806
|
+
var DeferWhenSchema = z3.object({
|
|
1807
|
+
after: DeferDurationSchema.optional(),
|
|
1808
|
+
at: z3.date().optional()
|
|
1809
|
+
}).strict().refine((w) => w.after === void 0 !== (w.at === void 0), {
|
|
1810
|
+
message: "defer: specify exactly one of `after` or `at`"
|
|
1811
|
+
});
|
|
1812
|
+
function duration_ms(d) {
|
|
1813
|
+
return (d.days ?? 0) * 864e5 + (d.hours ?? 0) * 36e5 + (d.minutes ?? 0) * 6e4;
|
|
1814
|
+
}
|
|
1815
|
+
function resolve_defer_at(when, event) {
|
|
1816
|
+
const parsed = DeferWhenSchema.parse(when);
|
|
1817
|
+
if (parsed.after) return event.created.getTime() + duration_ms(parsed.after);
|
|
1818
|
+
return parsed.at.getTime();
|
|
1819
|
+
}
|
|
1820
|
+
function assert_defer_when(when) {
|
|
1821
|
+
DeferWhenSchema.parse(when);
|
|
1822
|
+
}
|
|
1823
|
+
function make_deferred(handler, schedule) {
|
|
1824
|
+
const call = handler;
|
|
1825
|
+
const deferred = async (event, stream, app) => {
|
|
1826
|
+
const when = typeof schedule === "function" ? schedule(event) : schedule;
|
|
1827
|
+
if (Date.now() < resolve_defer_at(when, event)) throw new DeferSignal(when);
|
|
1828
|
+
return call(event, stream, app);
|
|
1829
|
+
};
|
|
1830
|
+
Object.defineProperty(deferred, "name", { value: handler.name });
|
|
1831
|
+
return deferred;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
2003
1834
|
// src/internal/drain-cycle.ts
|
|
2004
1835
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
2005
1836
|
|
|
@@ -2127,37 +1958,6 @@ var subscribe = (streams, correlated_at, correlator) => store().subscribe(stream
|
|
|
2127
1958
|
|
|
2128
1959
|
// src/internal/event-sourcing.ts
|
|
2129
1960
|
import { patch } from "@rotorsoft/act-patch";
|
|
2130
|
-
|
|
2131
|
-
// src/internal/backoff.ts
|
|
2132
|
-
function compute_backoff_delay(retry, opts) {
|
|
2133
|
-
if (!opts || opts.baseMs <= 0) return 0;
|
|
2134
|
-
const r = Math.max(0, retry);
|
|
2135
|
-
let delay;
|
|
2136
|
-
switch (opts.strategy) {
|
|
2137
|
-
case "fixed":
|
|
2138
|
-
delay = opts.baseMs;
|
|
2139
|
-
break;
|
|
2140
|
-
case "linear":
|
|
2141
|
-
delay = opts.baseMs * (r + 1);
|
|
2142
|
-
break;
|
|
2143
|
-
case "exponential":
|
|
2144
|
-
delay = opts.baseMs * 2 ** r;
|
|
2145
|
-
if (opts.maxMs !== void 0) delay = Math.min(delay, opts.maxMs);
|
|
2146
|
-
break;
|
|
2147
|
-
default: {
|
|
2148
|
-
const _never = opts.strategy;
|
|
2149
|
-
throw new Error(`unknown backoff strategy: ${String(_never)}`);
|
|
2150
|
-
}
|
|
2151
|
-
}
|
|
2152
|
-
if (opts.jitter) delay = delay * (0.5 + Math.random());
|
|
2153
|
-
return Math.max(0, Math.floor(delay));
|
|
2154
|
-
}
|
|
2155
|
-
|
|
2156
|
-
// src/internal/reacting.ts
|
|
2157
|
-
import { AsyncLocalStorage } from "async_hooks";
|
|
2158
|
-
var reacting = new AsyncLocalStorage();
|
|
2159
|
-
|
|
2160
|
-
// src/internal/event-sourcing.ts
|
|
2161
1961
|
var bare_patch = (_me, state2, partial) => patch(state2, partial);
|
|
2162
1962
|
var validating_patch = (me, state2, partial, event) => {
|
|
2163
1963
|
const next = patch(state2, partial);
|
|
@@ -2414,7 +2214,7 @@ async function load(me, target, callback, patch_fn = bare_patch) {
|
|
|
2414
2214
|
async function action(me, action2, target, payload, options, patch_fn = bare_patch) {
|
|
2415
2215
|
const { stream, expectedVersion, actor } = target;
|
|
2416
2216
|
if (!stream) throw new Error("Missing target stream");
|
|
2417
|
-
const reactingTo = options?.reactingTo
|
|
2217
|
+
const reactingTo = options?.reactingTo;
|
|
2418
2218
|
const correlator = options?.correlator ?? default_correlator;
|
|
2419
2219
|
const validated = validate(action2, payload, me.actions[action2]);
|
|
2420
2220
|
const opts = me.options?.[action2];
|
|
@@ -3063,131 +2863,6 @@ function make_fold_handler(me, flush, config2, patch_fn = bare_patch, sensitive_
|
|
|
3063
2863
|
return handler;
|
|
3064
2864
|
}
|
|
3065
2865
|
|
|
3066
|
-
// src/internal/reactions.ts
|
|
3067
|
-
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
3068
|
-
if (!error) return { lease, handled, acked_at: at };
|
|
3069
|
-
logger.error(error);
|
|
3070
|
-
const non_retryable = error instanceof NonRetryableError;
|
|
3071
|
-
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
3072
|
-
if (block2)
|
|
3073
|
-
logger.error(
|
|
3074
|
-
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
3075
|
-
);
|
|
3076
|
-
const next_attempt_at = !block2 && options.backoff ? Date.now() + compute_backoff_delay(lease.retry, options.backoff) : void 0;
|
|
3077
|
-
return {
|
|
3078
|
-
lease,
|
|
3079
|
-
handled,
|
|
3080
|
-
acked_at: at,
|
|
3081
|
-
error: error.message,
|
|
3082
|
-
block: block2,
|
|
3083
|
-
next_attempt_at,
|
|
3084
|
-
failed_at
|
|
3085
|
-
};
|
|
3086
|
-
}
|
|
3087
|
-
function build_handle(deps) {
|
|
3088
|
-
const {
|
|
3089
|
-
logger,
|
|
3090
|
-
bound_do,
|
|
3091
|
-
bound_load,
|
|
3092
|
-
bound_query,
|
|
3093
|
-
bound_query_array,
|
|
3094
|
-
bound_forget
|
|
3095
|
-
} = deps;
|
|
3096
|
-
return async (lease, payloads) => {
|
|
3097
|
-
if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
|
|
3098
|
-
const stream = lease.stream;
|
|
3099
|
-
const last_index_of = /* @__PURE__ */ new Map();
|
|
3100
|
-
for (let i = 0; i < payloads.length; i++)
|
|
3101
|
-
last_index_of.set(payloads[i].event.id, i);
|
|
3102
|
-
let at = lease.at;
|
|
3103
|
-
let handled = 0;
|
|
3104
|
-
if (lease.retry > 0)
|
|
3105
|
-
logger.warn(
|
|
3106
|
-
`Retrying ${stream}@${payloads.at(0).event.id} (${lease.retry}).`
|
|
3107
|
-
);
|
|
3108
|
-
const scoped_app = {
|
|
3109
|
-
do: bound_do,
|
|
3110
|
-
load: bound_load,
|
|
3111
|
-
query: bound_query,
|
|
3112
|
-
query_array: bound_query_array,
|
|
3113
|
-
forget: bound_forget
|
|
3114
|
-
};
|
|
3115
|
-
for (let i = 0; i < payloads.length; i++) {
|
|
3116
|
-
const payload = payloads[i];
|
|
3117
|
-
const { event, handler } = payload;
|
|
3118
|
-
try {
|
|
3119
|
-
await reacting.run(
|
|
3120
|
-
event,
|
|
3121
|
-
() => handler(event, stream, scoped_app)
|
|
3122
|
-
);
|
|
3123
|
-
if (last_index_of.get(event.id) === i) {
|
|
3124
|
-
at = event.id;
|
|
3125
|
-
handled++;
|
|
3126
|
-
}
|
|
3127
|
-
} catch (error) {
|
|
3128
|
-
if (error instanceof DeferSignal)
|
|
3129
|
-
return {
|
|
3130
|
-
lease,
|
|
3131
|
-
handled,
|
|
3132
|
-
acked_at: at,
|
|
3133
|
-
defer: resolve_defer_at(error.when, event)
|
|
3134
|
-
};
|
|
3135
|
-
if (error instanceof CloseSignal)
|
|
3136
|
-
return {
|
|
3137
|
-
lease,
|
|
3138
|
-
handled: handled + 1,
|
|
3139
|
-
// Advance to the live head the handler evaluated against (when
|
|
3140
|
-
// provided) so the close-cycle guard sees this reaction caught up.
|
|
3141
|
-
acked_at: error.at ?? event.id,
|
|
3142
|
-
// Close the signalled stream (the autoclose reaction's aggregate,
|
|
3143
|
-
// which differs from its synthetic lease stream); a self-closing
|
|
3144
|
-
// user reaction omits it and closes its own lease stream. A
|
|
3145
|
-
// carried `before` makes it a windowed close (prune, not retire).
|
|
3146
|
-
close: {
|
|
3147
|
-
stream: error.stream ?? stream,
|
|
3148
|
-
archive: error.archive,
|
|
3149
|
-
before: error.before
|
|
3150
|
-
}
|
|
3151
|
-
};
|
|
3152
|
-
return finalize(
|
|
3153
|
-
lease,
|
|
3154
|
-
handled,
|
|
3155
|
-
at,
|
|
3156
|
-
error,
|
|
3157
|
-
payload.options,
|
|
3158
|
-
logger,
|
|
3159
|
-
event.id
|
|
3160
|
-
);
|
|
3161
|
-
}
|
|
3162
|
-
}
|
|
3163
|
-
return finalize(lease, handled, at, void 0, payloads[0].options, logger);
|
|
3164
|
-
};
|
|
3165
|
-
}
|
|
3166
|
-
function build_handle_batch(logger) {
|
|
3167
|
-
return async (lease, payloads, batchHandler) => {
|
|
3168
|
-
const stream = lease.stream;
|
|
3169
|
-
const events = payloads.map(
|
|
3170
|
-
(p) => p.event
|
|
3171
|
-
);
|
|
3172
|
-
const options = payloads[0].options;
|
|
3173
|
-
if (lease.retry > 0)
|
|
3174
|
-
logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
3175
|
-
try {
|
|
3176
|
-
await batchHandler(events, stream);
|
|
3177
|
-
return finalize(
|
|
3178
|
-
lease,
|
|
3179
|
-
events.length,
|
|
3180
|
-
events.at(-1).id,
|
|
3181
|
-
void 0,
|
|
3182
|
-
options,
|
|
3183
|
-
logger
|
|
3184
|
-
);
|
|
3185
|
-
} catch (error) {
|
|
3186
|
-
return finalize(lease, 0, lease.at, error, options, logger);
|
|
3187
|
-
}
|
|
3188
|
-
};
|
|
3189
|
-
}
|
|
3190
|
-
|
|
3191
2866
|
// src/internal/settle.ts
|
|
3192
2867
|
var SettleLoop = class {
|
|
3193
2868
|
_timer = void 0;
|
|
@@ -3298,8 +2973,119 @@ var SettleLoop = class {
|
|
|
3298
2973
|
clearTimeout(this._timer);
|
|
3299
2974
|
this._timer = void 0;
|
|
3300
2975
|
}
|
|
3301
|
-
}
|
|
3302
|
-
};
|
|
2976
|
+
}
|
|
2977
|
+
};
|
|
2978
|
+
|
|
2979
|
+
// src/builders/reaction-builder.ts
|
|
2980
|
+
function finalize(lease, handled, at, error, options, logger, failed_at) {
|
|
2981
|
+
if (!error) return { lease, handled, acked_at: at };
|
|
2982
|
+
logger.error(error);
|
|
2983
|
+
const non_retryable = error instanceof NonRetryableError;
|
|
2984
|
+
const block2 = options.blockOnError && (non_retryable || lease.retry >= options.maxRetries);
|
|
2985
|
+
if (block2)
|
|
2986
|
+
logger.error(
|
|
2987
|
+
non_retryable ? `Blocking ${lease.stream} on non-retryable error.` : `Blocking ${lease.stream} after ${lease.retry} retries.`
|
|
2988
|
+
);
|
|
2989
|
+
const next_attempt_at = !block2 && options.backoff ? Date.now() + compute_backoff_delay(lease.retry, options.backoff) : void 0;
|
|
2990
|
+
return {
|
|
2991
|
+
lease,
|
|
2992
|
+
handled,
|
|
2993
|
+
acked_at: at,
|
|
2994
|
+
error: error.message,
|
|
2995
|
+
block: block2,
|
|
2996
|
+
next_attempt_at,
|
|
2997
|
+
failed_at
|
|
2998
|
+
};
|
|
2999
|
+
}
|
|
3000
|
+
function build_handle(deps) {
|
|
3001
|
+
const { logger, reaction_scope } = deps;
|
|
3002
|
+
return async (lease, payloads) => {
|
|
3003
|
+
if (payloads.length === 0) return { lease, handled: 0, acked_at: lease.at };
|
|
3004
|
+
const stream = lease.stream;
|
|
3005
|
+
const last_index_of = /* @__PURE__ */ new Map();
|
|
3006
|
+
for (let i = 0; i < payloads.length; i++)
|
|
3007
|
+
last_index_of.set(payloads[i].event.id, i);
|
|
3008
|
+
let at = lease.at;
|
|
3009
|
+
let handled = 0;
|
|
3010
|
+
if (lease.retry > 0)
|
|
3011
|
+
logger.warn(
|
|
3012
|
+
`Retrying ${stream}@${payloads.at(0).event.id} (${lease.retry}).`
|
|
3013
|
+
);
|
|
3014
|
+
for (let i = 0; i < payloads.length; i++) {
|
|
3015
|
+
const payload = payloads[i];
|
|
3016
|
+
const { event, handler } = payload;
|
|
3017
|
+
try {
|
|
3018
|
+
await reaction_scope.run(
|
|
3019
|
+
event,
|
|
3020
|
+
() => handler(event, stream, reaction_scope.app)
|
|
3021
|
+
);
|
|
3022
|
+
if (last_index_of.get(event.id) === i) {
|
|
3023
|
+
at = event.id;
|
|
3024
|
+
handled++;
|
|
3025
|
+
}
|
|
3026
|
+
} catch (error) {
|
|
3027
|
+
if (error instanceof DeferSignal)
|
|
3028
|
+
return {
|
|
3029
|
+
lease,
|
|
3030
|
+
handled,
|
|
3031
|
+
acked_at: at,
|
|
3032
|
+
defer: resolve_defer_at(error.when, event)
|
|
3033
|
+
};
|
|
3034
|
+
if (error instanceof CloseSignal)
|
|
3035
|
+
return {
|
|
3036
|
+
lease,
|
|
3037
|
+
handled: handled + 1,
|
|
3038
|
+
// Advance to the live head the handler evaluated against (when
|
|
3039
|
+
// provided) so the close-cycle guard sees this reaction caught up.
|
|
3040
|
+
acked_at: error.at ?? event.id,
|
|
3041
|
+
// Close the signalled stream (the autoclose reaction's aggregate,
|
|
3042
|
+
// which differs from its synthetic lease stream); a self-closing
|
|
3043
|
+
// user reaction omits it and closes its own lease stream. A
|
|
3044
|
+
// carried `before` makes it a windowed close (prune, not retire).
|
|
3045
|
+
close: {
|
|
3046
|
+
stream: error.stream ?? stream,
|
|
3047
|
+
archive: error.archive,
|
|
3048
|
+
before: error.before
|
|
3049
|
+
}
|
|
3050
|
+
};
|
|
3051
|
+
return finalize(
|
|
3052
|
+
lease,
|
|
3053
|
+
handled,
|
|
3054
|
+
at,
|
|
3055
|
+
error,
|
|
3056
|
+
payload.options,
|
|
3057
|
+
logger,
|
|
3058
|
+
event.id
|
|
3059
|
+
);
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
return finalize(lease, handled, at, void 0, payloads[0].options, logger);
|
|
3063
|
+
};
|
|
3064
|
+
}
|
|
3065
|
+
function build_handle_batch(logger) {
|
|
3066
|
+
return async (lease, payloads, batchHandler) => {
|
|
3067
|
+
const stream = lease.stream;
|
|
3068
|
+
const events = payloads.map(
|
|
3069
|
+
(p) => p.event
|
|
3070
|
+
);
|
|
3071
|
+
const options = payloads[0].options;
|
|
3072
|
+
if (lease.retry > 0)
|
|
3073
|
+
logger.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
3074
|
+
try {
|
|
3075
|
+
await batchHandler(events, stream);
|
|
3076
|
+
return finalize(
|
|
3077
|
+
lease,
|
|
3078
|
+
events.length,
|
|
3079
|
+
events.at(-1).id,
|
|
3080
|
+
void 0,
|
|
3081
|
+
options,
|
|
3082
|
+
logger
|
|
3083
|
+
);
|
|
3084
|
+
} catch (error) {
|
|
3085
|
+
return finalize(lease, 0, lease.at, error, options, logger);
|
|
3086
|
+
}
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3303
3089
|
|
|
3304
3090
|
// src/act.ts
|
|
3305
3091
|
var DEFAULT_MAX_SUBSCRIBED_STREAMS = 1e3;
|
|
@@ -3532,17 +3318,21 @@ var Act = class {
|
|
|
3532
3318
|
this._batch_handlers = batch_handlers;
|
|
3533
3319
|
this._lanes = lanes;
|
|
3534
3320
|
validate_only_lanes(options, lanes);
|
|
3535
|
-
this._scoped =
|
|
3321
|
+
this._scoped = make_run_scoped(options.scoped);
|
|
3536
3322
|
this._correlator = options.correlator ?? default_correlator;
|
|
3537
3323
|
this._es = build_es(this._logger, this._correlator, patch_fn);
|
|
3538
3324
|
this._cd = build_drain(this._logger);
|
|
3539
3325
|
this._handle = build_handle({
|
|
3540
3326
|
logger: this._logger,
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
|
|
3544
|
-
|
|
3545
|
-
|
|
3327
|
+
// The orchestrator owns ambient context; `build_handle` only asks for
|
|
3328
|
+
// the triggering event to be in scope while the handler runs.
|
|
3329
|
+
reaction_scope: make_reaction_scope({
|
|
3330
|
+
do: this._bound_do,
|
|
3331
|
+
load: this._bound_load,
|
|
3332
|
+
query: this._bound_query,
|
|
3333
|
+
query_array: this._bound_query_array,
|
|
3334
|
+
forget: this._bound_forget
|
|
3335
|
+
})
|
|
3546
3336
|
});
|
|
3547
3337
|
this._handle_batch = build_handle_batch(this._logger);
|
|
3548
3338
|
const classification = classify_registry(this.registry, this._states);
|
|
@@ -3961,13 +3751,15 @@ var Act = class {
|
|
|
3961
3751
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
3962
3752
|
*/
|
|
3963
3753
|
async do(action2, target, payload, options) {
|
|
3754
|
+
const reacting_to = options?.reactingTo ?? current_reacting();
|
|
3755
|
+
const do_options = reacting_to === options?.reactingTo ? options : { ...options, reactingTo: reacting_to };
|
|
3964
3756
|
return this._scoped(async () => {
|
|
3965
3757
|
const snapshots = await this._es.action(
|
|
3966
3758
|
this.registry.actions[action2],
|
|
3967
3759
|
action2,
|
|
3968
3760
|
target,
|
|
3969
3761
|
payload,
|
|
3970
|
-
|
|
3762
|
+
do_options
|
|
3971
3763
|
);
|
|
3972
3764
|
if (this._reactive_events.size > 0)
|
|
3973
3765
|
this._arm_for_event_names(
|
|
@@ -4800,6 +4592,202 @@ var Act = class {
|
|
|
4800
4592
|
}
|
|
4801
4593
|
};
|
|
4802
4594
|
|
|
4595
|
+
// src/builders/merge.ts
|
|
4596
|
+
import { ZodObject } from "zod";
|
|
4597
|
+
function base_type_name(zodType) {
|
|
4598
|
+
let t = zodType;
|
|
4599
|
+
while (typeof t.unwrap === "function") {
|
|
4600
|
+
t = t.unwrap();
|
|
4601
|
+
}
|
|
4602
|
+
return t.constructor.name;
|
|
4603
|
+
}
|
|
4604
|
+
function merge_schemas(existing, incoming, state_name) {
|
|
4605
|
+
if (existing instanceof ZodObject && incoming instanceof ZodObject) {
|
|
4606
|
+
const existing_shape = existing.shape;
|
|
4607
|
+
const incoming_shape = incoming.shape;
|
|
4608
|
+
for (const key of Object.keys(incoming_shape)) {
|
|
4609
|
+
if (key in existing_shape) {
|
|
4610
|
+
const existing_base = base_type_name(existing_shape[key]);
|
|
4611
|
+
const incoming_base = base_type_name(incoming_shape[key]);
|
|
4612
|
+
if (existing_base !== incoming_base) {
|
|
4613
|
+
throw new Error(
|
|
4614
|
+
`Schema conflict in "${state_name}": key "${key}" has type "${existing_base}" but incoming partial declares "${incoming_base}"`
|
|
4615
|
+
);
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
}
|
|
4619
|
+
return existing.extend(incoming_shape);
|
|
4620
|
+
}
|
|
4621
|
+
return existing;
|
|
4622
|
+
}
|
|
4623
|
+
function merge_inits(existing, incoming) {
|
|
4624
|
+
return () => ({ ...existing(), ...incoming() });
|
|
4625
|
+
}
|
|
4626
|
+
function register_state(state2, states, actions, events) {
|
|
4627
|
+
const existing = states.get(state2.name);
|
|
4628
|
+
if (existing) {
|
|
4629
|
+
merge_into_existing(state2, existing, states, actions, events);
|
|
4630
|
+
} else {
|
|
4631
|
+
register_new_state(state2, states, actions, events);
|
|
4632
|
+
}
|
|
4633
|
+
}
|
|
4634
|
+
function register_new_state(state2, states, actions, events) {
|
|
4635
|
+
states.set(state2.name, state2);
|
|
4636
|
+
for (const name of Object.keys(state2.actions)) {
|
|
4637
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
4638
|
+
actions[name] = state2;
|
|
4639
|
+
}
|
|
4640
|
+
for (const name of Object.keys(state2.events)) {
|
|
4641
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
4642
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
function merge_into_existing(state2, existing, states, actions, events) {
|
|
4646
|
+
for (const name of Object.keys(state2.actions)) {
|
|
4647
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
4648
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
4649
|
+
}
|
|
4650
|
+
for (const name of Object.keys(state2.events)) {
|
|
4651
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
4652
|
+
if (existing.events[name]) {
|
|
4653
|
+
throw new Error(
|
|
4654
|
+
`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.`
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
4658
|
+
}
|
|
4659
|
+
const merged_patch = merge_patches(existing.patch, state2.patch, state2.name);
|
|
4660
|
+
const merged = {
|
|
4661
|
+
...existing,
|
|
4662
|
+
state: merge_schemas(existing.state, state2.state, state2.name),
|
|
4663
|
+
init: merge_inits(existing.init, state2.init),
|
|
4664
|
+
events: { ...existing.events, ...state2.events },
|
|
4665
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
4666
|
+
patch: merged_patch,
|
|
4667
|
+
on: { ...existing.on, ...state2.on },
|
|
4668
|
+
given: { ...existing.given, ...state2.given },
|
|
4669
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
4670
|
+
throw new Error(
|
|
4671
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
4672
|
+
);
|
|
4673
|
+
})() : state2.snap || existing.snap
|
|
4674
|
+
};
|
|
4675
|
+
states.set(state2.name, merged);
|
|
4676
|
+
for (const name of Object.keys(merged.actions)) {
|
|
4677
|
+
actions[name] = merged;
|
|
4678
|
+
}
|
|
4679
|
+
for (const name of Object.keys(state2.events)) {
|
|
4680
|
+
if (events[name]) continue;
|
|
4681
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
4682
|
+
}
|
|
4683
|
+
}
|
|
4684
|
+
function merge_patches(existing, incoming, state_name) {
|
|
4685
|
+
const merged = { ...existing };
|
|
4686
|
+
for (const name of Object.keys(incoming)) {
|
|
4687
|
+
const existing_p = existing[name];
|
|
4688
|
+
const incoming_p = incoming[name];
|
|
4689
|
+
if (!existing_p) {
|
|
4690
|
+
merged[name] = incoming_p;
|
|
4691
|
+
continue;
|
|
4692
|
+
}
|
|
4693
|
+
const existing_is_default = existing_p._passthrough;
|
|
4694
|
+
const incoming_is_default = incoming_p._passthrough;
|
|
4695
|
+
if (!existing_is_default && !incoming_is_default && existing_p !== incoming_p) {
|
|
4696
|
+
throw new Error(
|
|
4697
|
+
`Duplicate custom patch for event "${name}" in state "${state_name}"`
|
|
4698
|
+
);
|
|
4699
|
+
}
|
|
4700
|
+
if (existing_is_default && !incoming_is_default) {
|
|
4701
|
+
merged[name] = incoming_p;
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
4704
|
+
return merged;
|
|
4705
|
+
}
|
|
4706
|
+
function merge_event_register(target, source) {
|
|
4707
|
+
for (const [event_name, source_reg] of Object.entries(source)) {
|
|
4708
|
+
const target_reg = target[event_name];
|
|
4709
|
+
if (!target_reg) continue;
|
|
4710
|
+
for (const [name, reaction] of source_reg.reactions) {
|
|
4711
|
+
const existing = target_reg.reactions.get(name);
|
|
4712
|
+
if (existing !== void 0 && existing !== reaction)
|
|
4713
|
+
throw new Error(
|
|
4714
|
+
`Duplicate reaction "${name}" for event "${event_name}". Reaction handlers are keyed by function name; rename one of them.`
|
|
4715
|
+
);
|
|
4716
|
+
target_reg.reactions.set(name, reaction);
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
function merge_projection(proj, events) {
|
|
4721
|
+
for (const event_name of Object.keys(proj.events)) {
|
|
4722
|
+
const proj_register = proj.events[event_name];
|
|
4723
|
+
const existing = events[event_name];
|
|
4724
|
+
if (!existing) {
|
|
4725
|
+
events[event_name] = {
|
|
4726
|
+
schema: proj_register.schema,
|
|
4727
|
+
reactions: new Map(proj_register.reactions)
|
|
4728
|
+
};
|
|
4729
|
+
} else {
|
|
4730
|
+
for (const [name, reaction] of proj_register.reactions) {
|
|
4731
|
+
if ([...existing.reactions.values()].includes(reaction)) continue;
|
|
4732
|
+
let key = name;
|
|
4733
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
4734
|
+
existing.reactions.set(key, reaction);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4737
|
+
}
|
|
4738
|
+
}
|
|
4739
|
+
var _this_ = ({ stream }) => ({
|
|
4740
|
+
source: stream,
|
|
4741
|
+
target: stream
|
|
4742
|
+
});
|
|
4743
|
+
|
|
4744
|
+
// src/builders/builder-utils.ts
|
|
4745
|
+
function register_lane(config2, lanes) {
|
|
4746
|
+
if (config2.name === DEFAULT_LANE)
|
|
4747
|
+
throw new Error(`Lane "${DEFAULT_LANE}" is reserved`);
|
|
4748
|
+
if (lanes.some((l) => l.name === config2.name))
|
|
4749
|
+
throw new Error(`Lane "${config2.name}" was already declared`);
|
|
4750
|
+
lanes.push(config2);
|
|
4751
|
+
}
|
|
4752
|
+
function reaction_on(event, events, builder) {
|
|
4753
|
+
const register = (handler, options, schedule) => {
|
|
4754
|
+
if (!handler.name)
|
|
4755
|
+
throw new Error(
|
|
4756
|
+
`Reaction handler for "${String(event)}" must be a named function`
|
|
4757
|
+
);
|
|
4758
|
+
if (events[event].reactions.has(handler.name))
|
|
4759
|
+
throw new Error(
|
|
4760
|
+
`Duplicate reaction "${handler.name}" for event "${String(event)}". Reaction handlers are keyed by function name; rename one of them.`
|
|
4761
|
+
);
|
|
4762
|
+
if (schedule && typeof schedule !== "function") assert_defer_when(schedule);
|
|
4763
|
+
const reaction = {
|
|
4764
|
+
handler: schedule ? make_deferred(handler, schedule) : handler,
|
|
4765
|
+
resolver: _this_,
|
|
4766
|
+
// #1269: validate the whole bag at the declaration site so a bad
|
|
4767
|
+
// `maxRetries`/`backoff`/`blockOnError` throws ZodError at build,
|
|
4768
|
+
// not a NaN gate on the first retry.
|
|
4769
|
+
options: resolveReactionConfig({
|
|
4770
|
+
blockOnError: options?.blockOnError ?? true,
|
|
4771
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
4772
|
+
backoff: options?.backoff
|
|
4773
|
+
})
|
|
4774
|
+
};
|
|
4775
|
+
events[event].reactions.set(handler.name, reaction);
|
|
4776
|
+
return Object.assign(builder, {
|
|
4777
|
+
to(resolver) {
|
|
4778
|
+
reaction.resolver = typeof resolver === "string" ? { target: resolver } : resolver;
|
|
4779
|
+
return builder;
|
|
4780
|
+
}
|
|
4781
|
+
});
|
|
4782
|
+
};
|
|
4783
|
+
return {
|
|
4784
|
+
do: (handler, options) => register(handler, options),
|
|
4785
|
+
defer: (schedule) => ({
|
|
4786
|
+
do: (handler, options) => register(handler, options, schedule)
|
|
4787
|
+
})
|
|
4788
|
+
};
|
|
4789
|
+
}
|
|
4790
|
+
|
|
4803
4791
|
// src/builders/act-builder.ts
|
|
4804
4792
|
function register_batch_handler(proj, batch_handlers) {
|
|
4805
4793
|
if (!proj.batchHandler || !proj.target) return;
|
|
@@ -5584,7 +5572,6 @@ export {
|
|
|
5584
5572
|
port,
|
|
5585
5573
|
projection,
|
|
5586
5574
|
resolveAutocloseConfig,
|
|
5587
|
-
scoped,
|
|
5588
5575
|
sensitive,
|
|
5589
5576
|
sleep,
|
|
5590
5577
|
slice,
|