@rotorsoft/act 0.32.1 → 0.32.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-builder.d.ts.map +1 -1
- package/dist/@types/act.d.ts +13 -10
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/InMemoryStore.d.ts +2 -4
- package/dist/@types/adapters/InMemoryStore.d.ts.map +1 -1
- package/dist/@types/internal/drain.d.ts +2 -6
- package/dist/@types/internal/drain.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/index.d.ts +9 -4
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/@types/internal/merge.d.ts +1 -25
- package/dist/@types/internal/merge.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +9 -10
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/state-builder.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +13 -0
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +2 -6
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +9 -3
- package/dist/@types/types/reaction.d.ts.map +1 -1
- package/dist/@types/utils.d.ts.map +1 -1
- package/dist/index.cjs +302 -287
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +303 -288
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -347,7 +347,7 @@ var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
|
347
347
|
var env = NODE_ENV || "development";
|
|
348
348
|
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : NODE_ENV === "production" ? "info" : "trace");
|
|
349
349
|
var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
|
|
350
|
-
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
|
|
350
|
+
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
|
|
351
351
|
var pkg = getPackage();
|
|
352
352
|
var config = () => {
|
|
353
353
|
return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
|
|
@@ -358,19 +358,15 @@ var validate = (target, payload, schema) => {
|
|
|
358
358
|
try {
|
|
359
359
|
return schema ? schema.parse(payload) : payload;
|
|
360
360
|
} catch (error) {
|
|
361
|
-
if (error instanceof
|
|
362
|
-
throw new ValidationError(
|
|
363
|
-
target,
|
|
364
|
-
payload,
|
|
365
|
-
(0, import_zod3.prettifyError)(error)
|
|
366
|
-
);
|
|
361
|
+
if (error instanceof import_zod3.ZodError) {
|
|
362
|
+
throw new ValidationError(target, payload, (0, import_zod3.prettifyError)(error));
|
|
367
363
|
}
|
|
368
364
|
throw new ValidationError(target, payload, error);
|
|
369
365
|
}
|
|
370
366
|
};
|
|
371
367
|
var extend = (source, schema, target) => {
|
|
372
368
|
const value = validate("config", source, schema);
|
|
373
|
-
return
|
|
369
|
+
return { ...target, ...value };
|
|
374
370
|
};
|
|
375
371
|
async function sleep(ms) {
|
|
376
372
|
return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
|
|
@@ -798,13 +794,13 @@ var cache = port(function cache2(adapter) {
|
|
|
798
794
|
var disposers = [];
|
|
799
795
|
async function disposeAndExit(code = "EXIT") {
|
|
800
796
|
if (code === "ERROR" && config().env === "production") return;
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
})
|
|
807
|
-
|
|
797
|
+
for (const disposer of [...disposers].reverse()) {
|
|
798
|
+
await disposer();
|
|
799
|
+
}
|
|
800
|
+
for (const adapter of [...adapters.values()].reverse()) {
|
|
801
|
+
await adapter.dispose();
|
|
802
|
+
console.log(`[act] - ${adapter.constructor.name}`);
|
|
803
|
+
}
|
|
808
804
|
adapters.clear();
|
|
809
805
|
config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
|
|
810
806
|
}
|
|
@@ -816,21 +812,20 @@ var SNAP_EVENT = "__snapshot__";
|
|
|
816
812
|
var TOMBSTONE_EVENT = "__tombstone__";
|
|
817
813
|
|
|
818
814
|
// src/signals.ts
|
|
819
|
-
var logger = log();
|
|
820
815
|
process.once("SIGINT", async (arg) => {
|
|
821
|
-
|
|
816
|
+
log().info(arg, "SIGINT");
|
|
822
817
|
await disposeAndExit("EXIT");
|
|
823
818
|
});
|
|
824
819
|
process.once("SIGTERM", async (arg) => {
|
|
825
|
-
|
|
820
|
+
log().info(arg, "SIGTERM");
|
|
826
821
|
await disposeAndExit("EXIT");
|
|
827
822
|
});
|
|
828
823
|
process.once("uncaughtException", async (arg) => {
|
|
829
|
-
|
|
824
|
+
log().error(arg, "Uncaught Exception");
|
|
830
825
|
await disposeAndExit("ERROR");
|
|
831
826
|
});
|
|
832
827
|
process.once("unhandledRejection", async (arg) => {
|
|
833
|
-
|
|
828
|
+
log().error(arg, "Unhandled Rejection");
|
|
834
829
|
await disposeAndExit("ERROR");
|
|
835
830
|
});
|
|
836
831
|
|
|
@@ -838,6 +833,136 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
838
833
|
var import_crypto2 = require("crypto");
|
|
839
834
|
var import_events = __toESM(require("events"), 1);
|
|
840
835
|
|
|
836
|
+
// src/internal/merge.ts
|
|
837
|
+
var import_zod4 = require("zod");
|
|
838
|
+
function baseTypeName(zodType) {
|
|
839
|
+
let t = zodType;
|
|
840
|
+
while (typeof t.unwrap === "function") {
|
|
841
|
+
t = t.unwrap();
|
|
842
|
+
}
|
|
843
|
+
return t.constructor.name;
|
|
844
|
+
}
|
|
845
|
+
function mergeSchemas(existing, incoming, stateName) {
|
|
846
|
+
if (existing instanceof import_zod4.ZodObject && incoming instanceof import_zod4.ZodObject) {
|
|
847
|
+
const existingShape = existing.shape;
|
|
848
|
+
const incomingShape = incoming.shape;
|
|
849
|
+
for (const key of Object.keys(incomingShape)) {
|
|
850
|
+
if (key in existingShape) {
|
|
851
|
+
const existingBase = baseTypeName(existingShape[key]);
|
|
852
|
+
const incomingBase = baseTypeName(incomingShape[key]);
|
|
853
|
+
if (existingBase !== incomingBase) {
|
|
854
|
+
throw new Error(
|
|
855
|
+
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return existing.extend(incomingShape);
|
|
861
|
+
}
|
|
862
|
+
return existing;
|
|
863
|
+
}
|
|
864
|
+
function mergeInits(existing, incoming) {
|
|
865
|
+
return () => ({ ...existing(), ...incoming() });
|
|
866
|
+
}
|
|
867
|
+
function registerState(state2, states, actions, events) {
|
|
868
|
+
const existing = states.get(state2.name);
|
|
869
|
+
if (existing) {
|
|
870
|
+
mergeIntoExisting(state2, existing, states, actions, events);
|
|
871
|
+
} else {
|
|
872
|
+
registerNewState(state2, states, actions, events);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
function registerNewState(state2, states, actions, events) {
|
|
876
|
+
states.set(state2.name, state2);
|
|
877
|
+
for (const name of Object.keys(state2.actions)) {
|
|
878
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
879
|
+
actions[name] = state2;
|
|
880
|
+
}
|
|
881
|
+
for (const name of Object.keys(state2.events)) {
|
|
882
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
883
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
function mergeIntoExisting(state2, existing, states, actions, events) {
|
|
887
|
+
for (const name of Object.keys(state2.actions)) {
|
|
888
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
889
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
890
|
+
}
|
|
891
|
+
for (const name of Object.keys(state2.events)) {
|
|
892
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
893
|
+
if (existing.events[name]) continue;
|
|
894
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
895
|
+
}
|
|
896
|
+
const mergedPatch = mergePatches(existing.patch, state2.patch, state2.name);
|
|
897
|
+
const merged = {
|
|
898
|
+
...existing,
|
|
899
|
+
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
900
|
+
init: mergeInits(existing.init, state2.init),
|
|
901
|
+
events: { ...existing.events, ...state2.events },
|
|
902
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
903
|
+
patch: mergedPatch,
|
|
904
|
+
on: { ...existing.on, ...state2.on },
|
|
905
|
+
given: { ...existing.given, ...state2.given },
|
|
906
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
907
|
+
throw new Error(
|
|
908
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
909
|
+
);
|
|
910
|
+
})() : state2.snap || existing.snap
|
|
911
|
+
};
|
|
912
|
+
states.set(state2.name, merged);
|
|
913
|
+
for (const name of Object.keys(merged.actions)) {
|
|
914
|
+
actions[name] = merged;
|
|
915
|
+
}
|
|
916
|
+
for (const name of Object.keys(state2.events)) {
|
|
917
|
+
if (events[name]) continue;
|
|
918
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
function mergePatches(existing, incoming, stateName) {
|
|
922
|
+
const merged = { ...existing };
|
|
923
|
+
for (const name of Object.keys(incoming)) {
|
|
924
|
+
const existingP = existing[name];
|
|
925
|
+
const incomingP = incoming[name];
|
|
926
|
+
if (!existingP) {
|
|
927
|
+
merged[name] = incomingP;
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
const existingIsDefault = existingP._passthrough;
|
|
931
|
+
const incomingIsDefault = incomingP._passthrough;
|
|
932
|
+
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
933
|
+
throw new Error(
|
|
934
|
+
`Duplicate custom patch for event "${name}" in state "${stateName}"`
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
if (existingIsDefault && !incomingIsDefault) {
|
|
938
|
+
merged[name] = incomingP;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
return merged;
|
|
942
|
+
}
|
|
943
|
+
function mergeProjection(proj, events) {
|
|
944
|
+
for (const eventName of Object.keys(proj.events)) {
|
|
945
|
+
const projRegister = proj.events[eventName];
|
|
946
|
+
const existing = events[eventName];
|
|
947
|
+
if (!existing) {
|
|
948
|
+
events[eventName] = {
|
|
949
|
+
schema: projRegister.schema,
|
|
950
|
+
reactions: new Map(projRegister.reactions)
|
|
951
|
+
};
|
|
952
|
+
} else {
|
|
953
|
+
for (const [name, reaction] of projRegister.reactions) {
|
|
954
|
+
let key = name;
|
|
955
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
956
|
+
existing.reactions.set(key, reaction);
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
var _this_ = ({ stream }) => ({
|
|
962
|
+
source: stream,
|
|
963
|
+
target: stream
|
|
964
|
+
});
|
|
965
|
+
|
|
841
966
|
// src/internal/drain.ts
|
|
842
967
|
var claim = (lagging, leading, by, millis) => store().claim(lagging, leading, by, millis);
|
|
843
968
|
async function fetch(leased, eventLimit) {
|
|
@@ -860,7 +985,6 @@ var subscribe = (streams) => store().subscribe(streams);
|
|
|
860
985
|
// src/internal/event-sourcing.ts
|
|
861
986
|
var import_act_patch = require("@rotorsoft/act-patch");
|
|
862
987
|
var import_crypto = require("crypto");
|
|
863
|
-
var logger2 = log();
|
|
864
988
|
async function snap(snapshot) {
|
|
865
989
|
try {
|
|
866
990
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -875,11 +999,11 @@ async function snap(snapshot) {
|
|
|
875
999
|
// IMPORTANT! - state events are committed right after the snapshot event
|
|
876
1000
|
);
|
|
877
1001
|
} catch (error) {
|
|
878
|
-
|
|
1002
|
+
log().error(error);
|
|
879
1003
|
}
|
|
880
1004
|
}
|
|
881
1005
|
async function load(me, stream, callback, asOf) {
|
|
882
|
-
const timeTravel = asOf && (asOf.
|
|
1006
|
+
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
883
1007
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
884
1008
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
885
1009
|
let patches = cached?.patches ?? 0;
|
|
@@ -895,6 +1019,10 @@ async function load(me, stream, callback, asOf) {
|
|
|
895
1019
|
} else if (me.patch[e.name]) {
|
|
896
1020
|
state2 = (0, import_act_patch.patch)(state2, me.patch[e.name](event, state2));
|
|
897
1021
|
patches++;
|
|
1022
|
+
} else if (e.name !== TOMBSTONE_EVENT) {
|
|
1023
|
+
log().warn(
|
|
1024
|
+
`Skipping unknown event "${String(e.name)}" on stream "${stream}" (id=${e.id}) \u2014 no reducer in state "${me.name}"`
|
|
1025
|
+
);
|
|
898
1026
|
}
|
|
899
1027
|
callback && callback({ event, state: state2, patches, snaps });
|
|
900
1028
|
},
|
|
@@ -963,7 +1091,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
963
1091
|
reactingTo ? void 0 : expected
|
|
964
1092
|
);
|
|
965
1093
|
} catch (error) {
|
|
966
|
-
if (error
|
|
1094
|
+
if (error instanceof ConcurrencyError) {
|
|
967
1095
|
await cache().invalidate(stream);
|
|
968
1096
|
}
|
|
969
1097
|
throw error;
|
|
@@ -977,241 +1105,56 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
977
1105
|
});
|
|
978
1106
|
const last = snapshots.at(-1);
|
|
979
1107
|
const snapped = me.snap && me.snap(last);
|
|
980
|
-
|
|
1108
|
+
cache().set(stream, {
|
|
981
1109
|
state: last.state,
|
|
982
1110
|
version: last.event.version,
|
|
983
1111
|
event_id: last.event.id,
|
|
984
1112
|
patches: snapped ? 0 : last.patches,
|
|
985
1113
|
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
986
|
-
});
|
|
1114
|
+
}).catch((err) => log().error(err));
|
|
987
1115
|
if (snapped) void snap(last);
|
|
988
1116
|
return snapshots;
|
|
989
1117
|
}
|
|
990
1118
|
|
|
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
1119
|
// src/internal/tracing.ts
|
|
1118
|
-
var
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
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") {
|
|
1120
|
+
var traced = (inner, exit, entry) => (async (...args) => {
|
|
1121
|
+
entry?.(...args);
|
|
1122
|
+
const result = await inner(...args);
|
|
1123
|
+
exit?.(result, ...args);
|
|
1124
|
+
return result;
|
|
1125
|
+
});
|
|
1126
|
+
function buildEs(logger) {
|
|
1127
|
+
if (logger.level !== "trace") {
|
|
1150
1128
|
return { snap, load, action };
|
|
1151
1129
|
}
|
|
1152
1130
|
return {
|
|
1153
|
-
snap:
|
|
1154
|
-
|
|
1155
|
-
|
|
1131
|
+
snap: traced(snap, void 0, (snapshot) => {
|
|
1132
|
+
logger.trace(
|
|
1133
|
+
`\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
|
|
1134
|
+
);
|
|
1135
|
+
}),
|
|
1136
|
+
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
1137
|
+
logger.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
|
|
1138
|
+
}),
|
|
1139
|
+
action: traced(
|
|
1140
|
+
action,
|
|
1141
|
+
(snapshots, _me, _action, target) => {
|
|
1142
|
+
const committed = snapshots.filter((s) => s.event);
|
|
1143
|
+
if (committed.length) {
|
|
1144
|
+
logger.trace(
|
|
1145
|
+
committed.map((s) => s.event.data),
|
|
1146
|
+
`\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
(_me, action2, target, payload) => {
|
|
1151
|
+
logger.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
|
|
1152
|
+
}
|
|
1153
|
+
)
|
|
1156
1154
|
};
|
|
1157
1155
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
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") {
|
|
1156
|
+
function buildDrain(logger) {
|
|
1157
|
+
if (logger.level !== "trace") {
|
|
1215
1158
|
return {
|
|
1216
1159
|
claim,
|
|
1217
1160
|
fetch,
|
|
@@ -1221,24 +1164,62 @@ function buildDrain(level) {
|
|
|
1221
1164
|
};
|
|
1222
1165
|
}
|
|
1223
1166
|
return {
|
|
1224
|
-
claim:
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1167
|
+
claim: traced(claim, (leased) => {
|
|
1168
|
+
if (leased.length) {
|
|
1169
|
+
const data = Object.fromEntries(
|
|
1170
|
+
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1171
|
+
);
|
|
1172
|
+
logger.trace(data, ">> lease");
|
|
1173
|
+
}
|
|
1174
|
+
}),
|
|
1175
|
+
fetch: traced(fetch, (fetched) => {
|
|
1176
|
+
const data = Object.fromEntries(
|
|
1177
|
+
fetched.map(({ stream, source, events }) => {
|
|
1178
|
+
const key = source ? `${stream}<-${source}` : stream;
|
|
1179
|
+
const value = Object.fromEntries(
|
|
1180
|
+
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
1181
|
+
);
|
|
1182
|
+
return [key, value];
|
|
1183
|
+
})
|
|
1184
|
+
);
|
|
1185
|
+
logger.trace(data, ">> fetch");
|
|
1186
|
+
}),
|
|
1187
|
+
ack: traced(ack, (acked) => {
|
|
1188
|
+
if (acked.length) {
|
|
1189
|
+
const data = Object.fromEntries(
|
|
1190
|
+
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1191
|
+
);
|
|
1192
|
+
logger.trace(data, ">> ack");
|
|
1193
|
+
}
|
|
1194
|
+
}),
|
|
1195
|
+
block: traced(block, (blocked) => {
|
|
1196
|
+
if (blocked.length) {
|
|
1197
|
+
const data = Object.fromEntries(
|
|
1198
|
+
blocked.map(({ stream, at, retry, error }) => [
|
|
1199
|
+
stream,
|
|
1200
|
+
{ at, retry, error }
|
|
1201
|
+
])
|
|
1202
|
+
);
|
|
1203
|
+
logger.trace(data, ">> block");
|
|
1204
|
+
}
|
|
1205
|
+
}),
|
|
1206
|
+
subscribe: traced(subscribe, (result, streams) => {
|
|
1207
|
+
if (result.subscribed) {
|
|
1208
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1209
|
+
logger.trace(`>> correlate ${data}`);
|
|
1210
|
+
}
|
|
1211
|
+
})
|
|
1229
1212
|
};
|
|
1230
1213
|
}
|
|
1231
1214
|
|
|
1232
1215
|
// src/act.ts
|
|
1233
|
-
var logger4 = log();
|
|
1234
1216
|
var Act = class {
|
|
1235
1217
|
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
1236
1218
|
this.registry = registry;
|
|
1237
1219
|
this._states = _states;
|
|
1238
1220
|
this._batch_handlers = batchHandlers;
|
|
1239
|
-
|
|
1240
|
-
this.
|
|
1241
|
-
this._cd = buildDrain(level);
|
|
1221
|
+
this._es = buildEs(this._logger);
|
|
1222
|
+
this._cd = buildDrain(this._logger);
|
|
1242
1223
|
const statics = [];
|
|
1243
1224
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
1244
1225
|
if (register.reactions.size > 0) {
|
|
@@ -1256,6 +1237,11 @@ var Act = class {
|
|
|
1256
1237
|
}
|
|
1257
1238
|
}
|
|
1258
1239
|
this._static_targets = statics;
|
|
1240
|
+
for (const merged of this._states.values()) {
|
|
1241
|
+
for (const eventName of Object.keys(merged.events)) {
|
|
1242
|
+
this._event_to_state.set(eventName, merged);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1259
1245
|
dispose(() => {
|
|
1260
1246
|
this._emitter.removeAllListeners();
|
|
1261
1247
|
this.stop_correlations();
|
|
@@ -1302,6 +1288,15 @@ var Act = class {
|
|
|
1302
1288
|
_es;
|
|
1303
1289
|
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1304
1290
|
_cd;
|
|
1291
|
+
/**
|
|
1292
|
+
* Event-name → owning state, computed at build time. The duplicate-event
|
|
1293
|
+
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
1294
|
+
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
1295
|
+
* set when seeding a `restart` snapshot in multi-state apps.
|
|
1296
|
+
*/
|
|
1297
|
+
_event_to_state = /* @__PURE__ */ new Map();
|
|
1298
|
+
/** Logger resolved at construction time (after user port configuration) */
|
|
1299
|
+
_logger = log();
|
|
1305
1300
|
/**
|
|
1306
1301
|
* Executes an action on a state instance, committing resulting events.
|
|
1307
1302
|
*
|
|
@@ -1523,7 +1518,7 @@ var Act = class {
|
|
|
1523
1518
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1524
1519
|
const stream = lease.stream;
|
|
1525
1520
|
let at = payloads.at(0).event.id, handled = 0;
|
|
1526
|
-
lease.retry > 0 &&
|
|
1521
|
+
lease.retry > 0 && this._logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1527
1522
|
const doAction = this.do.bind(this);
|
|
1528
1523
|
const scopedApp = {
|
|
1529
1524
|
do: doAction,
|
|
@@ -1545,9 +1540,11 @@ var Act = class {
|
|
|
1545
1540
|
at = event.id;
|
|
1546
1541
|
handled++;
|
|
1547
1542
|
} catch (error) {
|
|
1548
|
-
|
|
1543
|
+
this._logger.error(error);
|
|
1549
1544
|
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1550
|
-
block2 &&
|
|
1545
|
+
block2 && this._logger.error(
|
|
1546
|
+
`Blocking ${stream} after ${lease.retry} retries.`
|
|
1547
|
+
);
|
|
1551
1548
|
return {
|
|
1552
1549
|
lease,
|
|
1553
1550
|
handled,
|
|
@@ -1577,15 +1574,17 @@ var Act = class {
|
|
|
1577
1574
|
const stream = lease.stream;
|
|
1578
1575
|
const events = payloads.map((p) => p.event);
|
|
1579
1576
|
const at = events.at(-1).id;
|
|
1580
|
-
lease.retry > 0 &&
|
|
1577
|
+
lease.retry > 0 && this._logger.warn(
|
|
1578
|
+
`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`
|
|
1579
|
+
);
|
|
1581
1580
|
try {
|
|
1582
1581
|
await batchHandler(events, stream);
|
|
1583
1582
|
return { lease, handled: events.length, at };
|
|
1584
1583
|
} catch (error) {
|
|
1585
|
-
|
|
1584
|
+
this._logger.error(error);
|
|
1586
1585
|
const { options } = payloads[0];
|
|
1587
1586
|
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1588
|
-
block2 &&
|
|
1587
|
+
block2 && this._logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1589
1588
|
return {
|
|
1590
1589
|
lease,
|
|
1591
1590
|
handled: 0,
|
|
@@ -1711,7 +1710,7 @@ var Act = class {
|
|
|
1711
1710
|
this._needs_drain = false;
|
|
1712
1711
|
return result;
|
|
1713
1712
|
} catch (error) {
|
|
1714
|
-
|
|
1713
|
+
this._logger.error(error);
|
|
1715
1714
|
} finally {
|
|
1716
1715
|
this._drain_locked = false;
|
|
1717
1716
|
}
|
|
@@ -2012,16 +2011,24 @@ var Act = class {
|
|
|
2012
2011
|
streams.map(async (s) => {
|
|
2013
2012
|
let maxId = -1;
|
|
2014
2013
|
let version = -1;
|
|
2014
|
+
let lastEventName;
|
|
2015
2015
|
await store().query(
|
|
2016
2016
|
(e) => {
|
|
2017
|
-
if (e.name
|
|
2017
|
+
if (e.name === TOMBSTONE_EVENT) return;
|
|
2018
|
+
if (maxId === -1) {
|
|
2018
2019
|
maxId = e.id;
|
|
2019
2020
|
version = e.version;
|
|
2020
2021
|
}
|
|
2022
|
+
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
2023
|
+
lastEventName = e.name;
|
|
2024
|
+
}
|
|
2021
2025
|
},
|
|
2022
|
-
|
|
2026
|
+
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
2027
|
+
// always preceded by the domain event it captured). Streams with
|
|
2028
|
+
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
2029
|
+
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
2023
2030
|
);
|
|
2024
|
-
if (maxId >= 0) streamInfo.set(s, { maxId, version });
|
|
2031
|
+
if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
|
|
2025
2032
|
})
|
|
2026
2033
|
);
|
|
2027
2034
|
const skipped = [];
|
|
@@ -2030,16 +2037,14 @@ var Act = class {
|
|
|
2030
2037
|
safe = [...streamInfo.keys()];
|
|
2031
2038
|
} else {
|
|
2032
2039
|
const pendingSet = /* @__PURE__ */ new Set();
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
for (const lease of leases) {
|
|
2036
|
-
const sourceRe = lease.source ? RegExp(lease.source) : void 0;
|
|
2040
|
+
await store().query_streams((position) => {
|
|
2041
|
+
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
2037
2042
|
for (const [stream, info] of streamInfo) {
|
|
2038
|
-
if ((!sourceRe || sourceRe.test(stream)) &&
|
|
2043
|
+
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
2039
2044
|
pendingSet.add(stream);
|
|
2040
2045
|
}
|
|
2041
2046
|
}
|
|
2042
|
-
}
|
|
2047
|
+
});
|
|
2043
2048
|
safe = [];
|
|
2044
2049
|
for (const [stream] of streamInfo) {
|
|
2045
2050
|
if (pendingSet.has(stream)) {
|
|
@@ -2079,16 +2084,21 @@ var Act = class {
|
|
|
2079
2084
|
this.emit("closed", result2);
|
|
2080
2085
|
return result2;
|
|
2081
2086
|
}
|
|
2082
|
-
const mergedState = [...this._states.values()][0];
|
|
2083
2087
|
const seedStates = /* @__PURE__ */ new Map();
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2088
|
+
await Promise.all(
|
|
2089
|
+
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
2090
|
+
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
2091
|
+
const ownerState = lastEventName ? this._event_to_state.get(lastEventName) : void 0;
|
|
2092
|
+
if (!ownerState) {
|
|
2093
|
+
this._logger.error(
|
|
2094
|
+
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
2095
|
+
);
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
const snap2 = await this._es.load(ownerState, stream);
|
|
2099
|
+
seedStates.set(stream, snap2.state);
|
|
2100
|
+
})
|
|
2101
|
+
);
|
|
2092
2102
|
for (const stream of guarded) {
|
|
2093
2103
|
const archiveFn = targetMap.get(stream)?.archive;
|
|
2094
2104
|
if (archiveFn) await archiveFn();
|
|
@@ -2191,7 +2201,7 @@ var Act = class {
|
|
|
2191
2201
|
if (!made_progress) break;
|
|
2192
2202
|
}
|
|
2193
2203
|
if (lastDrain) this.emit("settled", lastDrain);
|
|
2194
|
-
})().catch((err) =>
|
|
2204
|
+
})().catch((err) => this._logger.error(err)).finally(() => {
|
|
2195
2205
|
this._settling = false;
|
|
2196
2206
|
});
|
|
2197
2207
|
}, debounceMs);
|
|
@@ -2199,6 +2209,14 @@ var Act = class {
|
|
|
2199
2209
|
};
|
|
2200
2210
|
|
|
2201
2211
|
// src/act-builder.ts
|
|
2212
|
+
function registerBatchHandler(proj, batchHandlers) {
|
|
2213
|
+
if (!proj.batchHandler || !proj.target) return;
|
|
2214
|
+
const existing = batchHandlers.get(proj.target);
|
|
2215
|
+
if (existing && existing !== proj.batchHandler) {
|
|
2216
|
+
throw new Error(`Duplicate batch handler for target "${proj.target}"`);
|
|
2217
|
+
}
|
|
2218
|
+
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2219
|
+
}
|
|
2202
2220
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
2203
2221
|
actions: {},
|
|
2204
2222
|
events: {}
|
|
@@ -2233,9 +2251,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2233
2251
|
},
|
|
2234
2252
|
withProjection: (proj) => {
|
|
2235
2253
|
mergeProjection(proj, registry.events);
|
|
2236
|
-
|
|
2237
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2238
|
-
}
|
|
2254
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2239
2255
|
return act(
|
|
2240
2256
|
states,
|
|
2241
2257
|
registry,
|
|
@@ -2281,9 +2297,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2281
2297
|
build: () => {
|
|
2282
2298
|
for (const proj of pendingProjections) {
|
|
2283
2299
|
mergeProjection(proj, registry.events);
|
|
2284
|
-
|
|
2285
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2286
|
-
}
|
|
2300
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2287
2301
|
}
|
|
2288
2302
|
return new Act(
|
|
2289
2303
|
registry,
|
|
@@ -2438,8 +2452,9 @@ function state(entry) {
|
|
|
2438
2452
|
emits(events) {
|
|
2439
2453
|
const defaultPatch = Object.fromEntries(
|
|
2440
2454
|
Object.keys(events).map((k) => {
|
|
2441
|
-
const fn = ({ data }) => data
|
|
2442
|
-
|
|
2455
|
+
const fn = Object.assign(({ data }) => data, {
|
|
2456
|
+
_passthrough: true
|
|
2457
|
+
});
|
|
2443
2458
|
return [k, fn];
|
|
2444
2459
|
})
|
|
2445
2460
|
);
|