@rotorsoft/act 0.31.1 → 0.32.1
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-builder.d.ts.map +1 -1
- package/dist/@types/act.d.ts +4 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/internal/drain.d.ts +43 -0
- package/dist/@types/internal/drain.d.ts.map +1 -0
- package/dist/@types/{event-sourcing.d.ts → internal/event-sourcing.d.ts} +15 -2
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -0
- package/dist/@types/internal/index.d.ts +17 -0
- package/dist/@types/internal/index.d.ts.map +1 -0
- package/dist/@types/{merge.d.ts → internal/merge.d.ts} +2 -2
- package/dist/@types/internal/merge.d.ts.map +1 -0
- package/dist/@types/internal/tracing.d.ts +37 -0
- package/dist/@types/internal/tracing.d.ts.map +1 -0
- package/dist/@types/ports.d.ts +1 -25
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/projection-builder.d.ts.map +1 -1
- package/dist/@types/slice-builder.d.ts.map +1 -1
- package/dist/chunk-JBKZJXQZ.js +127 -0
- package/dist/chunk-JBKZJXQZ.js.map +1 -0
- package/dist/index.cjs +294 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +322 -366
- package/dist/index.js.map +1 -1
- package/dist/types/index.cjs +166 -0
- package/dist/types/index.cjs.map +1 -0
- package/dist/types/index.js +33 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +6 -1
- package/dist/@types/event-sourcing.d.ts.map +0 -1
- package/dist/@types/merge.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -53,7 +53,6 @@ __export(index_exports, {
|
|
|
53
53
|
ValidationError: () => ValidationError,
|
|
54
54
|
ZodEmpty: () => ZodEmpty,
|
|
55
55
|
act: () => act,
|
|
56
|
-
build_tracer: () => build_tracer,
|
|
57
56
|
cache: () => cache,
|
|
58
57
|
config: () => config,
|
|
59
58
|
dispose: () => dispose,
|
|
@@ -815,63 +814,6 @@ function dispose(disposer) {
|
|
|
815
814
|
}
|
|
816
815
|
var SNAP_EVENT = "__snapshot__";
|
|
817
816
|
var TOMBSTONE_EVENT = "__tombstone__";
|
|
818
|
-
function build_tracer(logLevel2) {
|
|
819
|
-
if (logLevel2 === "trace") {
|
|
820
|
-
const logger4 = log();
|
|
821
|
-
return {
|
|
822
|
-
fetched: (fetched) => {
|
|
823
|
-
const data = Object.fromEntries(
|
|
824
|
-
fetched.map(({ stream, source, events }) => {
|
|
825
|
-
const key = source ? `${stream}<-${source}` : stream;
|
|
826
|
-
const value = Object.fromEntries(
|
|
827
|
-
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
828
|
-
);
|
|
829
|
-
return [key, value];
|
|
830
|
-
})
|
|
831
|
-
);
|
|
832
|
-
logger4.trace(data, ">> fetch");
|
|
833
|
-
},
|
|
834
|
-
correlated: (streams) => {
|
|
835
|
-
const data = streams.map(({ stream }) => stream).join(" ");
|
|
836
|
-
logger4.trace(`>> correlate ${data}`);
|
|
837
|
-
},
|
|
838
|
-
leased: (leases) => {
|
|
839
|
-
const data = Object.fromEntries(
|
|
840
|
-
leases.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
841
|
-
);
|
|
842
|
-
logger4.trace(data, ">> lease");
|
|
843
|
-
},
|
|
844
|
-
acked: (leases) => {
|
|
845
|
-
const data = Object.fromEntries(
|
|
846
|
-
leases.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
847
|
-
);
|
|
848
|
-
logger4.trace(data, ">> ack");
|
|
849
|
-
},
|
|
850
|
-
blocked: (leases) => {
|
|
851
|
-
const data = Object.fromEntries(
|
|
852
|
-
leases.map(({ stream, at, retry, error }) => [
|
|
853
|
-
stream,
|
|
854
|
-
{ at, retry, error }
|
|
855
|
-
])
|
|
856
|
-
);
|
|
857
|
-
logger4.trace(data, ">> block");
|
|
858
|
-
}
|
|
859
|
-
};
|
|
860
|
-
} else {
|
|
861
|
-
return {
|
|
862
|
-
fetched: () => {
|
|
863
|
-
},
|
|
864
|
-
correlated: () => {
|
|
865
|
-
},
|
|
866
|
-
leased: () => {
|
|
867
|
-
},
|
|
868
|
-
acked: () => {
|
|
869
|
-
},
|
|
870
|
-
blocked: () => {
|
|
871
|
-
}
|
|
872
|
-
};
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
817
|
|
|
876
818
|
// src/signals.ts
|
|
877
819
|
var logger = log();
|
|
@@ -896,14 +838,33 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
896
838
|
var import_crypto2 = require("crypto");
|
|
897
839
|
var import_events = __toESM(require("events"), 1);
|
|
898
840
|
|
|
899
|
-
// src/
|
|
841
|
+
// src/internal/drain.ts
|
|
842
|
+
var claim = (lagging, leading, by, millis) => store().claim(lagging, leading, by, millis);
|
|
843
|
+
async function fetch(leased, eventLimit) {
|
|
844
|
+
return Promise.all(
|
|
845
|
+
leased.map(async ({ stream, source, at, lagging }) => {
|
|
846
|
+
const events = [];
|
|
847
|
+
await store().query((e) => events.push(e), {
|
|
848
|
+
stream: source,
|
|
849
|
+
after: at,
|
|
850
|
+
limit: eventLimit
|
|
851
|
+
});
|
|
852
|
+
return { stream, source, at, lagging, events };
|
|
853
|
+
})
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
var ack = (leases) => store().ack(leases);
|
|
857
|
+
var block = (leases) => store().block(leases);
|
|
858
|
+
var subscribe = (streams) => store().subscribe(streams);
|
|
859
|
+
|
|
860
|
+
// src/internal/event-sourcing.ts
|
|
900
861
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
901
862
|
var import_crypto = require("crypto");
|
|
902
863
|
var logger2 = log();
|
|
903
864
|
async function snap(snapshot) {
|
|
904
865
|
try {
|
|
905
866
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
906
|
-
|
|
867
|
+
await store().commit(
|
|
907
868
|
stream,
|
|
908
869
|
[{ name: SNAP_EVENT, data: snapshot.state }],
|
|
909
870
|
{
|
|
@@ -913,7 +874,6 @@ async function snap(snapshot) {
|
|
|
913
874
|
version
|
|
914
875
|
// IMPORTANT! - state events are committed right after the snapshot event
|
|
915
876
|
);
|
|
916
|
-
logger2.trace(snapped, "\u{1F7E0} snap");
|
|
917
877
|
} catch (error) {
|
|
918
878
|
logger2.error(error);
|
|
919
879
|
}
|
|
@@ -925,7 +885,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
925
885
|
let patches = cached?.patches ?? 0;
|
|
926
886
|
let snaps = cached?.snaps ?? 0;
|
|
927
887
|
let event;
|
|
928
|
-
|
|
888
|
+
await store().query(
|
|
929
889
|
(e) => {
|
|
930
890
|
event = e;
|
|
931
891
|
if (e.name === SNAP_EVENT) {
|
|
@@ -944,10 +904,6 @@ async function load(me, stream, callback, asOf) {
|
|
|
944
904
|
...cached ? { after: cached.event_id } : { with_snaps: true, ...asOf }
|
|
945
905
|
}
|
|
946
906
|
);
|
|
947
|
-
logger2.trace(
|
|
948
|
-
state2,
|
|
949
|
-
`\u{1F7E2} load ${stream}${cached && count === 0 ? " (cached)" : ""}${timeTravel ? " (as-of)" : ""}`
|
|
950
|
-
);
|
|
951
907
|
return { event, state: state2, patches, snaps };
|
|
952
908
|
}
|
|
953
909
|
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
@@ -958,10 +914,6 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
958
914
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
959
915
|
throw new StreamClosedError(stream);
|
|
960
916
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
961
|
-
logger2.trace(
|
|
962
|
-
payload,
|
|
963
|
-
`\u{1F535} ${stream}.${action2}${typeof expected === "number" ? `.${expected}` : ""}`
|
|
964
|
-
);
|
|
965
917
|
if (me.given) {
|
|
966
918
|
const invariants = me.given[action2] || [];
|
|
967
919
|
invariants.forEach(({ valid, description }) => {
|
|
@@ -1001,10 +953,6 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1001
953
|
} : void 0
|
|
1002
954
|
}
|
|
1003
955
|
};
|
|
1004
|
-
logger2.trace(
|
|
1005
|
-
emitted.map((e) => e.data),
|
|
1006
|
-
`\u{1F534} commit ${stream}.${emitted.map((e) => e.name).join(", ")}`
|
|
1007
|
-
);
|
|
1008
956
|
let committed;
|
|
1009
957
|
try {
|
|
1010
958
|
committed = await store().commit(
|
|
@@ -1040,14 +988,257 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1040
988
|
return snapshots;
|
|
1041
989
|
}
|
|
1042
990
|
|
|
1043
|
-
// src/
|
|
991
|
+
// src/internal/merge.ts
|
|
992
|
+
var import_zod4 = require("zod");
|
|
993
|
+
function baseTypeName(zodType) {
|
|
994
|
+
let t = zodType;
|
|
995
|
+
while (typeof t.unwrap === "function") {
|
|
996
|
+
t = t.unwrap();
|
|
997
|
+
}
|
|
998
|
+
return t.constructor.name;
|
|
999
|
+
}
|
|
1000
|
+
function mergeSchemas(existing, incoming, stateName) {
|
|
1001
|
+
if (existing instanceof import_zod4.ZodObject && incoming instanceof import_zod4.ZodObject) {
|
|
1002
|
+
const existingShape = existing.shape;
|
|
1003
|
+
const incomingShape = incoming.shape;
|
|
1004
|
+
for (const key of Object.keys(incomingShape)) {
|
|
1005
|
+
if (key in existingShape) {
|
|
1006
|
+
const existingBase = baseTypeName(existingShape[key]);
|
|
1007
|
+
const incomingBase = baseTypeName(incomingShape[key]);
|
|
1008
|
+
if (existingBase !== incomingBase) {
|
|
1009
|
+
throw new Error(
|
|
1010
|
+
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return existing.extend(incomingShape);
|
|
1016
|
+
}
|
|
1017
|
+
return existing;
|
|
1018
|
+
}
|
|
1019
|
+
function mergeInits(existing, incoming) {
|
|
1020
|
+
return () => ({ ...existing(), ...incoming() });
|
|
1021
|
+
}
|
|
1022
|
+
function registerState(state2, states, actions, events) {
|
|
1023
|
+
if (states.has(state2.name)) {
|
|
1024
|
+
const existing = states.get(state2.name);
|
|
1025
|
+
for (const name of Object.keys(state2.actions)) {
|
|
1026
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
1027
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
1028
|
+
}
|
|
1029
|
+
for (const name of Object.keys(state2.events)) {
|
|
1030
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
1031
|
+
if (existing.events[name]) continue;
|
|
1032
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1033
|
+
}
|
|
1034
|
+
const mergedPatch = { ...existing.patch };
|
|
1035
|
+
for (const name of Object.keys(state2.patch)) {
|
|
1036
|
+
const existingP = existing.patch[name];
|
|
1037
|
+
const incomingP = state2.patch[name];
|
|
1038
|
+
if (!existingP) {
|
|
1039
|
+
mergedPatch[name] = incomingP;
|
|
1040
|
+
} else {
|
|
1041
|
+
const existingIsDefault = existingP._passthrough;
|
|
1042
|
+
const incomingIsDefault = incomingP._passthrough;
|
|
1043
|
+
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
1044
|
+
throw new Error(
|
|
1045
|
+
`Duplicate custom patch for event "${name}" in state "${state2.name}"`
|
|
1046
|
+
);
|
|
1047
|
+
}
|
|
1048
|
+
if (existingIsDefault && !incomingIsDefault) {
|
|
1049
|
+
mergedPatch[name] = incomingP;
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
const merged = {
|
|
1054
|
+
...existing,
|
|
1055
|
+
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
1056
|
+
init: mergeInits(existing.init, state2.init),
|
|
1057
|
+
events: { ...existing.events, ...state2.events },
|
|
1058
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
1059
|
+
patch: mergedPatch,
|
|
1060
|
+
on: { ...existing.on, ...state2.on },
|
|
1061
|
+
given: { ...existing.given, ...state2.given },
|
|
1062
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
1063
|
+
throw new Error(
|
|
1064
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
1065
|
+
);
|
|
1066
|
+
})() : state2.snap || existing.snap
|
|
1067
|
+
};
|
|
1068
|
+
states.set(state2.name, merged);
|
|
1069
|
+
for (const name of Object.keys(merged.actions)) {
|
|
1070
|
+
actions[name] = merged;
|
|
1071
|
+
}
|
|
1072
|
+
for (const name of Object.keys(state2.events)) {
|
|
1073
|
+
if (events[name]) continue;
|
|
1074
|
+
events[name] = {
|
|
1075
|
+
schema: state2.events[name],
|
|
1076
|
+
reactions: /* @__PURE__ */ new Map()
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
states.set(state2.name, state2);
|
|
1081
|
+
for (const name of Object.keys(state2.actions)) {
|
|
1082
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
1083
|
+
actions[name] = state2;
|
|
1084
|
+
}
|
|
1085
|
+
for (const name of Object.keys(state2.events)) {
|
|
1086
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1087
|
+
events[name] = {
|
|
1088
|
+
schema: state2.events[name],
|
|
1089
|
+
reactions: /* @__PURE__ */ new Map()
|
|
1090
|
+
};
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
function mergeProjection(proj, events) {
|
|
1095
|
+
for (const eventName of Object.keys(proj.events)) {
|
|
1096
|
+
const projRegister = proj.events[eventName];
|
|
1097
|
+
const existing = events[eventName];
|
|
1098
|
+
if (!existing) {
|
|
1099
|
+
events[eventName] = {
|
|
1100
|
+
schema: projRegister.schema,
|
|
1101
|
+
reactions: new Map(projRegister.reactions)
|
|
1102
|
+
};
|
|
1103
|
+
} else {
|
|
1104
|
+
for (const [name, reaction] of projRegister.reactions) {
|
|
1105
|
+
let key = name;
|
|
1106
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
1107
|
+
existing.reactions.set(key, reaction);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
var _this_ = ({ stream }) => ({
|
|
1113
|
+
source: stream,
|
|
1114
|
+
target: stream
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
// src/internal/tracing.ts
|
|
1044
1118
|
var logger3 = log();
|
|
1045
|
-
var
|
|
1119
|
+
var withSnapTrace = (inner) => async (snapshot) => {
|
|
1120
|
+
logger3.trace(
|
|
1121
|
+
`\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
|
|
1122
|
+
);
|
|
1123
|
+
return inner(snapshot);
|
|
1124
|
+
};
|
|
1125
|
+
var withLoadTrace = (inner) => async (me, stream, callback, asOf) => {
|
|
1126
|
+
logger3.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
|
|
1127
|
+
return inner(me, stream, callback, asOf);
|
|
1128
|
+
};
|
|
1129
|
+
var withActionTrace = (inner) => async (me, action2, target, payload, reactingTo, skipValidation) => {
|
|
1130
|
+
logger3.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
|
|
1131
|
+
const snapshots = await inner(
|
|
1132
|
+
me,
|
|
1133
|
+
action2,
|
|
1134
|
+
target,
|
|
1135
|
+
payload,
|
|
1136
|
+
reactingTo,
|
|
1137
|
+
skipValidation
|
|
1138
|
+
);
|
|
1139
|
+
const committed = snapshots.filter((s) => s.event);
|
|
1140
|
+
if (committed.length) {
|
|
1141
|
+
logger3.trace(
|
|
1142
|
+
committed.map((s) => s.event.data),
|
|
1143
|
+
`\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
return snapshots;
|
|
1147
|
+
};
|
|
1148
|
+
function buildEs(level) {
|
|
1149
|
+
if (level !== "trace") {
|
|
1150
|
+
return { snap, load, action };
|
|
1151
|
+
}
|
|
1152
|
+
return {
|
|
1153
|
+
snap: withSnapTrace(snap),
|
|
1154
|
+
load: withLoadTrace(load),
|
|
1155
|
+
action: withActionTrace(action)
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
var withClaimTrace = (inner) => async (lagging, leading, by, millis) => {
|
|
1159
|
+
const leased = await inner(lagging, leading, by, millis);
|
|
1160
|
+
if (leased.length) {
|
|
1161
|
+
const data = Object.fromEntries(
|
|
1162
|
+
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1163
|
+
);
|
|
1164
|
+
logger3.trace(data, ">> lease");
|
|
1165
|
+
}
|
|
1166
|
+
return leased;
|
|
1167
|
+
};
|
|
1168
|
+
var withFetchTrace = (inner) => async (leased, eventLimit) => {
|
|
1169
|
+
const fetched = await inner(leased, eventLimit);
|
|
1170
|
+
const data = Object.fromEntries(
|
|
1171
|
+
fetched.map(({ stream, source, events }) => {
|
|
1172
|
+
const key = source ? `${stream}<-${source}` : stream;
|
|
1173
|
+
const value = Object.fromEntries(
|
|
1174
|
+
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
1175
|
+
);
|
|
1176
|
+
return [key, value];
|
|
1177
|
+
})
|
|
1178
|
+
);
|
|
1179
|
+
logger3.trace(data, ">> fetch");
|
|
1180
|
+
return fetched;
|
|
1181
|
+
};
|
|
1182
|
+
var withAckTrace = (inner) => async (leases) => {
|
|
1183
|
+
const acked = await inner(leases);
|
|
1184
|
+
if (acked.length) {
|
|
1185
|
+
const data = Object.fromEntries(
|
|
1186
|
+
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1187
|
+
);
|
|
1188
|
+
logger3.trace(data, ">> ack");
|
|
1189
|
+
}
|
|
1190
|
+
return acked;
|
|
1191
|
+
};
|
|
1192
|
+
var withBlockTrace = (inner) => async (leases) => {
|
|
1193
|
+
const blocked = await inner(leases);
|
|
1194
|
+
if (blocked.length) {
|
|
1195
|
+
const data = Object.fromEntries(
|
|
1196
|
+
blocked.map(({ stream, at, retry, error }) => [
|
|
1197
|
+
stream,
|
|
1198
|
+
{ at, retry, error }
|
|
1199
|
+
])
|
|
1200
|
+
);
|
|
1201
|
+
logger3.trace(data, ">> block");
|
|
1202
|
+
}
|
|
1203
|
+
return blocked;
|
|
1204
|
+
};
|
|
1205
|
+
var withSubscribeTrace = (inner) => async (streams) => {
|
|
1206
|
+
const result = await inner(streams);
|
|
1207
|
+
if (result.subscribed) {
|
|
1208
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1209
|
+
logger3.trace(`>> correlate ${data}`);
|
|
1210
|
+
}
|
|
1211
|
+
return result;
|
|
1212
|
+
};
|
|
1213
|
+
function buildDrain(level) {
|
|
1214
|
+
if (level !== "trace") {
|
|
1215
|
+
return {
|
|
1216
|
+
claim,
|
|
1217
|
+
fetch,
|
|
1218
|
+
ack,
|
|
1219
|
+
block,
|
|
1220
|
+
subscribe
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
return {
|
|
1224
|
+
claim: withClaimTrace(claim),
|
|
1225
|
+
fetch: withFetchTrace(fetch),
|
|
1226
|
+
ack: withAckTrace(ack),
|
|
1227
|
+
block: withBlockTrace(block),
|
|
1228
|
+
subscribe: withSubscribeTrace(subscribe)
|
|
1229
|
+
};
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// src/act.ts
|
|
1233
|
+
var logger4 = log();
|
|
1046
1234
|
var Act = class {
|
|
1047
1235
|
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
1048
1236
|
this.registry = registry;
|
|
1049
1237
|
this._states = _states;
|
|
1050
1238
|
this._batch_handlers = batchHandlers;
|
|
1239
|
+
const level = log().level;
|
|
1240
|
+
this._es = buildEs(level);
|
|
1241
|
+
this._cd = buildDrain(level);
|
|
1051
1242
|
const statics = [];
|
|
1052
1243
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
1053
1244
|
if (register.reactions.size > 0) {
|
|
@@ -1107,6 +1298,10 @@ var Act = class {
|
|
|
1107
1298
|
_static_targets;
|
|
1108
1299
|
/** Batch handlers for static-target projections (target → handler) */
|
|
1109
1300
|
_batch_handlers;
|
|
1301
|
+
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
1302
|
+
_es;
|
|
1303
|
+
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1304
|
+
_cd;
|
|
1110
1305
|
/**
|
|
1111
1306
|
* Executes an action on a state instance, committing resulting events.
|
|
1112
1307
|
*
|
|
@@ -1189,7 +1384,7 @@ var Act = class {
|
|
|
1189
1384
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
1190
1385
|
*/
|
|
1191
1386
|
async do(action2, target, payload, reactingTo, skipValidation = false) {
|
|
1192
|
-
const snapshots = await action(
|
|
1387
|
+
const snapshots = await this._es.action(
|
|
1193
1388
|
this.registry.actions[action2],
|
|
1194
1389
|
action2,
|
|
1195
1390
|
target,
|
|
@@ -1215,7 +1410,7 @@ var Act = class {
|
|
|
1215
1410
|
} else {
|
|
1216
1411
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
1217
1412
|
}
|
|
1218
|
-
return await load(merged, stream, callback, asOf);
|
|
1413
|
+
return await this._es.load(merged, stream, callback, asOf);
|
|
1219
1414
|
}
|
|
1220
1415
|
/**
|
|
1221
1416
|
* Queries the event store for events matching a filter.
|
|
@@ -1328,7 +1523,7 @@ var Act = class {
|
|
|
1328
1523
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1329
1524
|
const stream = lease.stream;
|
|
1330
1525
|
let at = payloads.at(0).event.id, handled = 0;
|
|
1331
|
-
lease.retry > 0 &&
|
|
1526
|
+
lease.retry > 0 && logger4.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1332
1527
|
const doAction = this.do.bind(this);
|
|
1333
1528
|
const scopedApp = {
|
|
1334
1529
|
do: doAction,
|
|
@@ -1350,16 +1545,16 @@ var Act = class {
|
|
|
1350
1545
|
at = event.id;
|
|
1351
1546
|
handled++;
|
|
1352
1547
|
} catch (error) {
|
|
1353
|
-
|
|
1354
|
-
const
|
|
1355
|
-
|
|
1548
|
+
logger4.error(error);
|
|
1549
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1550
|
+
block2 && logger4.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1356
1551
|
return {
|
|
1357
1552
|
lease,
|
|
1358
1553
|
handled,
|
|
1359
1554
|
at,
|
|
1360
1555
|
// only report error when nothing was handled
|
|
1361
1556
|
error: handled === 0 ? error.message : void 0,
|
|
1362
|
-
block
|
|
1557
|
+
block: block2
|
|
1363
1558
|
};
|
|
1364
1559
|
}
|
|
1365
1560
|
}
|
|
@@ -1382,21 +1577,21 @@ var Act = class {
|
|
|
1382
1577
|
const stream = lease.stream;
|
|
1383
1578
|
const events = payloads.map((p) => p.event);
|
|
1384
1579
|
const at = events.at(-1).id;
|
|
1385
|
-
lease.retry > 0 &&
|
|
1580
|
+
lease.retry > 0 && logger4.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
1386
1581
|
try {
|
|
1387
1582
|
await batchHandler(events, stream);
|
|
1388
1583
|
return { lease, handled: events.length, at };
|
|
1389
1584
|
} catch (error) {
|
|
1390
|
-
|
|
1585
|
+
logger4.error(error);
|
|
1391
1586
|
const { options } = payloads[0];
|
|
1392
|
-
const
|
|
1393
|
-
|
|
1587
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1588
|
+
block2 && logger4.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1394
1589
|
return {
|
|
1395
1590
|
lease,
|
|
1396
1591
|
handled: 0,
|
|
1397
1592
|
at: lease.at,
|
|
1398
1593
|
error: error.message,
|
|
1399
|
-
block
|
|
1594
|
+
block: block2
|
|
1400
1595
|
};
|
|
1401
1596
|
}
|
|
1402
1597
|
}
|
|
@@ -1452,7 +1647,7 @@ var Act = class {
|
|
|
1452
1647
|
this._drain_locked = true;
|
|
1453
1648
|
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
1454
1649
|
const leading = streamLimit - lagging;
|
|
1455
|
-
const leased = await
|
|
1650
|
+
const leased = await this._cd.claim(
|
|
1456
1651
|
lagging,
|
|
1457
1652
|
leading,
|
|
1458
1653
|
(0, import_crypto2.randomUUID)(),
|
|
@@ -1462,17 +1657,7 @@ var Act = class {
|
|
|
1462
1657
|
this._needs_drain = false;
|
|
1463
1658
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1464
1659
|
}
|
|
1465
|
-
const fetched = await
|
|
1466
|
-
leased.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
1467
|
-
const events = await this.query_array({
|
|
1468
|
-
stream: source,
|
|
1469
|
-
after: at,
|
|
1470
|
-
limit: eventLimit
|
|
1471
|
-
});
|
|
1472
|
-
return { stream, source, at, lagging: lagging2, events };
|
|
1473
|
-
})
|
|
1474
|
-
);
|
|
1475
|
-
tracer.fetched(fetched);
|
|
1660
|
+
const fetched = await this._cd.fetch(leased, eventLimit);
|
|
1476
1661
|
const payloadsMap = /* @__PURE__ */ new Map();
|
|
1477
1662
|
const fetch_window_at = fetched.reduce(
|
|
1478
1663
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
@@ -1489,7 +1674,6 @@ var Act = class {
|
|
|
1489
1674
|
});
|
|
1490
1675
|
payloadsMap.set(stream, payloads);
|
|
1491
1676
|
});
|
|
1492
|
-
tracer.leased(leased);
|
|
1493
1677
|
const handled = await Promise.all(
|
|
1494
1678
|
leased.map((lease) => {
|
|
1495
1679
|
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
@@ -1513,27 +1697,21 @@ var Act = class {
|
|
|
1513
1697
|
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1514
1698
|
const total = lagging_avg + leading_avg;
|
|
1515
1699
|
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1516
|
-
const acked = await
|
|
1700
|
+
const acked = await this._cd.ack(
|
|
1517
1701
|
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1518
1702
|
);
|
|
1519
|
-
if (acked.length)
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
}
|
|
1523
|
-
const blocked = await store().block(
|
|
1524
|
-
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
1703
|
+
if (acked.length) this.emit("acked", acked);
|
|
1704
|
+
const blocked = await this._cd.block(
|
|
1705
|
+
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
1525
1706
|
);
|
|
1526
|
-
if (blocked.length)
|
|
1527
|
-
tracer.blocked(blocked);
|
|
1528
|
-
this.emit("blocked", blocked);
|
|
1529
|
-
}
|
|
1707
|
+
if (blocked.length) this.emit("blocked", blocked);
|
|
1530
1708
|
const result = { fetched, leased, acked, blocked };
|
|
1531
1709
|
const hasErrors = handled.some(({ error }) => error);
|
|
1532
1710
|
if (!acked.length && !blocked.length && !hasErrors)
|
|
1533
1711
|
this._needs_drain = false;
|
|
1534
1712
|
return result;
|
|
1535
1713
|
} catch (error) {
|
|
1536
|
-
|
|
1714
|
+
logger4.error(error);
|
|
1537
1715
|
} finally {
|
|
1538
1716
|
this._drain_locked = false;
|
|
1539
1717
|
}
|
|
@@ -1639,10 +1817,9 @@ var Act = class {
|
|
|
1639
1817
|
stream,
|
|
1640
1818
|
source
|
|
1641
1819
|
}));
|
|
1642
|
-
const { subscribed } = await
|
|
1820
|
+
const { subscribed } = await this._cd.subscribe(streams);
|
|
1643
1821
|
this._correlation_checkpoint = last_id;
|
|
1644
1822
|
if (subscribed) {
|
|
1645
|
-
tracer.correlated(streams);
|
|
1646
1823
|
for (const { stream } of streams) {
|
|
1647
1824
|
this._subscribed_statics.add(stream);
|
|
1648
1825
|
}
|
|
@@ -1907,7 +2084,7 @@ var Act = class {
|
|
|
1907
2084
|
if (mergedState) {
|
|
1908
2085
|
await Promise.all(
|
|
1909
2086
|
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
1910
|
-
const snap2 = await load(mergedState, stream);
|
|
2087
|
+
const snap2 = await this._es.load(mergedState, stream);
|
|
1911
2088
|
seedStates.set(stream, snap2.state);
|
|
1912
2089
|
})
|
|
1913
2090
|
);
|
|
@@ -2014,139 +2191,13 @@ var Act = class {
|
|
|
2014
2191
|
if (!made_progress) break;
|
|
2015
2192
|
}
|
|
2016
2193
|
if (lastDrain) this.emit("settled", lastDrain);
|
|
2017
|
-
})().catch((err) =>
|
|
2194
|
+
})().catch((err) => logger4.error(err)).finally(() => {
|
|
2018
2195
|
this._settling = false;
|
|
2019
2196
|
});
|
|
2020
2197
|
}, debounceMs);
|
|
2021
2198
|
}
|
|
2022
2199
|
};
|
|
2023
2200
|
|
|
2024
|
-
// src/merge.ts
|
|
2025
|
-
var import_zod4 = require("zod");
|
|
2026
|
-
function baseTypeName(zodType) {
|
|
2027
|
-
let t = zodType;
|
|
2028
|
-
while (typeof t.unwrap === "function") {
|
|
2029
|
-
t = t.unwrap();
|
|
2030
|
-
}
|
|
2031
|
-
return t.constructor.name;
|
|
2032
|
-
}
|
|
2033
|
-
function mergeSchemas(existing, incoming, stateName) {
|
|
2034
|
-
if (existing instanceof import_zod4.ZodObject && incoming instanceof import_zod4.ZodObject) {
|
|
2035
|
-
const existingShape = existing.shape;
|
|
2036
|
-
const incomingShape = incoming.shape;
|
|
2037
|
-
for (const key of Object.keys(incomingShape)) {
|
|
2038
|
-
if (key in existingShape) {
|
|
2039
|
-
const existingBase = baseTypeName(existingShape[key]);
|
|
2040
|
-
const incomingBase = baseTypeName(incomingShape[key]);
|
|
2041
|
-
if (existingBase !== incomingBase) {
|
|
2042
|
-
throw new Error(
|
|
2043
|
-
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
2044
|
-
);
|
|
2045
|
-
}
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
return existing.extend(incomingShape);
|
|
2049
|
-
}
|
|
2050
|
-
return existing;
|
|
2051
|
-
}
|
|
2052
|
-
function mergeInits(existing, incoming) {
|
|
2053
|
-
return () => ({ ...existing(), ...incoming() });
|
|
2054
|
-
}
|
|
2055
|
-
function registerState(state2, states, actions, events) {
|
|
2056
|
-
if (states.has(state2.name)) {
|
|
2057
|
-
const existing = states.get(state2.name);
|
|
2058
|
-
for (const name of Object.keys(state2.actions)) {
|
|
2059
|
-
if (existing.actions[name] === state2.actions[name]) continue;
|
|
2060
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
2061
|
-
}
|
|
2062
|
-
for (const name of Object.keys(state2.events)) {
|
|
2063
|
-
if (existing.events[name] === state2.events[name]) continue;
|
|
2064
|
-
if (existing.events[name]) continue;
|
|
2065
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
2066
|
-
}
|
|
2067
|
-
const mergedPatch = { ...existing.patch };
|
|
2068
|
-
for (const name of Object.keys(state2.patch)) {
|
|
2069
|
-
const existingP = existing.patch[name];
|
|
2070
|
-
const incomingP = state2.patch[name];
|
|
2071
|
-
if (!existingP) {
|
|
2072
|
-
mergedPatch[name] = incomingP;
|
|
2073
|
-
} else {
|
|
2074
|
-
const existingIsDefault = existingP._passthrough;
|
|
2075
|
-
const incomingIsDefault = incomingP._passthrough;
|
|
2076
|
-
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
2077
|
-
throw new Error(
|
|
2078
|
-
`Duplicate custom patch for event "${name}" in state "${state2.name}"`
|
|
2079
|
-
);
|
|
2080
|
-
}
|
|
2081
|
-
if (existingIsDefault && !incomingIsDefault) {
|
|
2082
|
-
mergedPatch[name] = incomingP;
|
|
2083
|
-
}
|
|
2084
|
-
}
|
|
2085
|
-
}
|
|
2086
|
-
const merged = {
|
|
2087
|
-
...existing,
|
|
2088
|
-
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
2089
|
-
init: mergeInits(existing.init, state2.init),
|
|
2090
|
-
events: { ...existing.events, ...state2.events },
|
|
2091
|
-
actions: { ...existing.actions, ...state2.actions },
|
|
2092
|
-
patch: mergedPatch,
|
|
2093
|
-
on: { ...existing.on, ...state2.on },
|
|
2094
|
-
given: { ...existing.given, ...state2.given },
|
|
2095
|
-
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
2096
|
-
throw new Error(
|
|
2097
|
-
`Duplicate snap strategy for state "${state2.name}"`
|
|
2098
|
-
);
|
|
2099
|
-
})() : state2.snap || existing.snap
|
|
2100
|
-
};
|
|
2101
|
-
states.set(state2.name, merged);
|
|
2102
|
-
for (const name of Object.keys(merged.actions)) {
|
|
2103
|
-
actions[name] = merged;
|
|
2104
|
-
}
|
|
2105
|
-
for (const name of Object.keys(state2.events)) {
|
|
2106
|
-
if (events[name]) continue;
|
|
2107
|
-
events[name] = {
|
|
2108
|
-
schema: state2.events[name],
|
|
2109
|
-
reactions: /* @__PURE__ */ new Map()
|
|
2110
|
-
};
|
|
2111
|
-
}
|
|
2112
|
-
} else {
|
|
2113
|
-
states.set(state2.name, state2);
|
|
2114
|
-
for (const name of Object.keys(state2.actions)) {
|
|
2115
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
2116
|
-
actions[name] = state2;
|
|
2117
|
-
}
|
|
2118
|
-
for (const name of Object.keys(state2.events)) {
|
|
2119
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
2120
|
-
events[name] = {
|
|
2121
|
-
schema: state2.events[name],
|
|
2122
|
-
reactions: /* @__PURE__ */ new Map()
|
|
2123
|
-
};
|
|
2124
|
-
}
|
|
2125
|
-
}
|
|
2126
|
-
}
|
|
2127
|
-
function mergeProjection(proj, events) {
|
|
2128
|
-
for (const eventName of Object.keys(proj.events)) {
|
|
2129
|
-
const projRegister = proj.events[eventName];
|
|
2130
|
-
const existing = events[eventName];
|
|
2131
|
-
if (!existing) {
|
|
2132
|
-
events[eventName] = {
|
|
2133
|
-
schema: projRegister.schema,
|
|
2134
|
-
reactions: new Map(projRegister.reactions)
|
|
2135
|
-
};
|
|
2136
|
-
} else {
|
|
2137
|
-
for (const [name, reaction] of projRegister.reactions) {
|
|
2138
|
-
let key = name;
|
|
2139
|
-
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
2140
|
-
existing.reactions.set(key, reaction);
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
var _this_ = ({ stream }) => ({
|
|
2146
|
-
source: stream,
|
|
2147
|
-
target: stream
|
|
2148
|
-
});
|
|
2149
|
-
|
|
2150
2201
|
// src/act-builder.ts
|
|
2151
2202
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
2152
2203
|
actions: {},
|
|
@@ -2490,7 +2541,6 @@ function action_builder(state2) {
|
|
|
2490
2541
|
ValidationError,
|
|
2491
2542
|
ZodEmpty,
|
|
2492
2543
|
act,
|
|
2493
|
-
build_tracer,
|
|
2494
2544
|
cache,
|
|
2495
2545
|
config,
|
|
2496
2546
|
dispose,
|