@rotorsoft/act 0.32.0 → 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/index.cjs +294 -244
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +294 -243
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/@types/event-sourcing.d.ts.map +0 -1
- package/dist/@types/merge.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -648,63 +648,6 @@ function dispose(disposer) {
|
|
|
648
648
|
}
|
|
649
649
|
var SNAP_EVENT = "__snapshot__";
|
|
650
650
|
var TOMBSTONE_EVENT = "__tombstone__";
|
|
651
|
-
function build_tracer(logLevel2) {
|
|
652
|
-
if (logLevel2 === "trace") {
|
|
653
|
-
const logger4 = log();
|
|
654
|
-
return {
|
|
655
|
-
fetched: (fetched) => {
|
|
656
|
-
const data = Object.fromEntries(
|
|
657
|
-
fetched.map(({ stream, source, events }) => {
|
|
658
|
-
const key = source ? `${stream}<-${source}` : stream;
|
|
659
|
-
const value = Object.fromEntries(
|
|
660
|
-
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
661
|
-
);
|
|
662
|
-
return [key, value];
|
|
663
|
-
})
|
|
664
|
-
);
|
|
665
|
-
logger4.trace(data, ">> fetch");
|
|
666
|
-
},
|
|
667
|
-
correlated: (streams) => {
|
|
668
|
-
const data = streams.map(({ stream }) => stream).join(" ");
|
|
669
|
-
logger4.trace(`>> correlate ${data}`);
|
|
670
|
-
},
|
|
671
|
-
leased: (leases) => {
|
|
672
|
-
const data = Object.fromEntries(
|
|
673
|
-
leases.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
674
|
-
);
|
|
675
|
-
logger4.trace(data, ">> lease");
|
|
676
|
-
},
|
|
677
|
-
acked: (leases) => {
|
|
678
|
-
const data = Object.fromEntries(
|
|
679
|
-
leases.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
680
|
-
);
|
|
681
|
-
logger4.trace(data, ">> ack");
|
|
682
|
-
},
|
|
683
|
-
blocked: (leases) => {
|
|
684
|
-
const data = Object.fromEntries(
|
|
685
|
-
leases.map(({ stream, at, retry, error }) => [
|
|
686
|
-
stream,
|
|
687
|
-
{ at, retry, error }
|
|
688
|
-
])
|
|
689
|
-
);
|
|
690
|
-
logger4.trace(data, ">> block");
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
} else {
|
|
694
|
-
return {
|
|
695
|
-
fetched: () => {
|
|
696
|
-
},
|
|
697
|
-
correlated: () => {
|
|
698
|
-
},
|
|
699
|
-
leased: () => {
|
|
700
|
-
},
|
|
701
|
-
acked: () => {
|
|
702
|
-
},
|
|
703
|
-
blocked: () => {
|
|
704
|
-
}
|
|
705
|
-
};
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
651
|
|
|
709
652
|
// src/signals.ts
|
|
710
653
|
var logger = log();
|
|
@@ -729,14 +672,33 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
729
672
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
730
673
|
import EventEmitter from "events";
|
|
731
674
|
|
|
732
|
-
// src/
|
|
675
|
+
// src/internal/drain.ts
|
|
676
|
+
var claim = (lagging, leading, by, millis) => store().claim(lagging, leading, by, millis);
|
|
677
|
+
async function fetch(leased, eventLimit) {
|
|
678
|
+
return Promise.all(
|
|
679
|
+
leased.map(async ({ stream, source, at, lagging }) => {
|
|
680
|
+
const events = [];
|
|
681
|
+
await store().query((e) => events.push(e), {
|
|
682
|
+
stream: source,
|
|
683
|
+
after: at,
|
|
684
|
+
limit: eventLimit
|
|
685
|
+
});
|
|
686
|
+
return { stream, source, at, lagging, events };
|
|
687
|
+
})
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
var ack = (leases) => store().ack(leases);
|
|
691
|
+
var block = (leases) => store().block(leases);
|
|
692
|
+
var subscribe = (streams) => store().subscribe(streams);
|
|
693
|
+
|
|
694
|
+
// src/internal/event-sourcing.ts
|
|
733
695
|
import { patch } from "@rotorsoft/act-patch";
|
|
734
696
|
import { randomUUID } from "crypto";
|
|
735
697
|
var logger2 = log();
|
|
736
698
|
async function snap(snapshot) {
|
|
737
699
|
try {
|
|
738
700
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
739
|
-
|
|
701
|
+
await store().commit(
|
|
740
702
|
stream,
|
|
741
703
|
[{ name: SNAP_EVENT, data: snapshot.state }],
|
|
742
704
|
{
|
|
@@ -746,7 +708,6 @@ async function snap(snapshot) {
|
|
|
746
708
|
version
|
|
747
709
|
// IMPORTANT! - state events are committed right after the snapshot event
|
|
748
710
|
);
|
|
749
|
-
logger2.trace(snapped, "\u{1F7E0} snap");
|
|
750
711
|
} catch (error) {
|
|
751
712
|
logger2.error(error);
|
|
752
713
|
}
|
|
@@ -758,7 +719,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
758
719
|
let patches = cached?.patches ?? 0;
|
|
759
720
|
let snaps = cached?.snaps ?? 0;
|
|
760
721
|
let event;
|
|
761
|
-
|
|
722
|
+
await store().query(
|
|
762
723
|
(e) => {
|
|
763
724
|
event = e;
|
|
764
725
|
if (e.name === SNAP_EVENT) {
|
|
@@ -777,10 +738,6 @@ async function load(me, stream, callback, asOf) {
|
|
|
777
738
|
...cached ? { after: cached.event_id } : { with_snaps: true, ...asOf }
|
|
778
739
|
}
|
|
779
740
|
);
|
|
780
|
-
logger2.trace(
|
|
781
|
-
state2,
|
|
782
|
-
`\u{1F7E2} load ${stream}${cached && count === 0 ? " (cached)" : ""}${timeTravel ? " (as-of)" : ""}`
|
|
783
|
-
);
|
|
784
741
|
return { event, state: state2, patches, snaps };
|
|
785
742
|
}
|
|
786
743
|
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
@@ -791,10 +748,6 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
791
748
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
792
749
|
throw new StreamClosedError(stream);
|
|
793
750
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
794
|
-
logger2.trace(
|
|
795
|
-
payload,
|
|
796
|
-
`\u{1F535} ${stream}.${action2}${typeof expected === "number" ? `.${expected}` : ""}`
|
|
797
|
-
);
|
|
798
751
|
if (me.given) {
|
|
799
752
|
const invariants = me.given[action2] || [];
|
|
800
753
|
invariants.forEach(({ valid, description }) => {
|
|
@@ -834,10 +787,6 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
834
787
|
} : void 0
|
|
835
788
|
}
|
|
836
789
|
};
|
|
837
|
-
logger2.trace(
|
|
838
|
-
emitted.map((e) => e.data),
|
|
839
|
-
`\u{1F534} commit ${stream}.${emitted.map((e) => e.name).join(", ")}`
|
|
840
|
-
);
|
|
841
790
|
let committed;
|
|
842
791
|
try {
|
|
843
792
|
committed = await store().commit(
|
|
@@ -873,14 +822,257 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
873
822
|
return snapshots;
|
|
874
823
|
}
|
|
875
824
|
|
|
876
|
-
// src/
|
|
825
|
+
// src/internal/merge.ts
|
|
826
|
+
import { ZodObject } from "zod";
|
|
827
|
+
function baseTypeName(zodType) {
|
|
828
|
+
let t = zodType;
|
|
829
|
+
while (typeof t.unwrap === "function") {
|
|
830
|
+
t = t.unwrap();
|
|
831
|
+
}
|
|
832
|
+
return t.constructor.name;
|
|
833
|
+
}
|
|
834
|
+
function mergeSchemas(existing, incoming, stateName) {
|
|
835
|
+
if (existing instanceof ZodObject && incoming instanceof ZodObject) {
|
|
836
|
+
const existingShape = existing.shape;
|
|
837
|
+
const incomingShape = incoming.shape;
|
|
838
|
+
for (const key of Object.keys(incomingShape)) {
|
|
839
|
+
if (key in existingShape) {
|
|
840
|
+
const existingBase = baseTypeName(existingShape[key]);
|
|
841
|
+
const incomingBase = baseTypeName(incomingShape[key]);
|
|
842
|
+
if (existingBase !== incomingBase) {
|
|
843
|
+
throw new Error(
|
|
844
|
+
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
return existing.extend(incomingShape);
|
|
850
|
+
}
|
|
851
|
+
return existing;
|
|
852
|
+
}
|
|
853
|
+
function mergeInits(existing, incoming) {
|
|
854
|
+
return () => ({ ...existing(), ...incoming() });
|
|
855
|
+
}
|
|
856
|
+
function registerState(state2, states, actions, events) {
|
|
857
|
+
if (states.has(state2.name)) {
|
|
858
|
+
const existing = states.get(state2.name);
|
|
859
|
+
for (const name of Object.keys(state2.actions)) {
|
|
860
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
861
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
862
|
+
}
|
|
863
|
+
for (const name of Object.keys(state2.events)) {
|
|
864
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
865
|
+
if (existing.events[name]) continue;
|
|
866
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
867
|
+
}
|
|
868
|
+
const mergedPatch = { ...existing.patch };
|
|
869
|
+
for (const name of Object.keys(state2.patch)) {
|
|
870
|
+
const existingP = existing.patch[name];
|
|
871
|
+
const incomingP = state2.patch[name];
|
|
872
|
+
if (!existingP) {
|
|
873
|
+
mergedPatch[name] = incomingP;
|
|
874
|
+
} else {
|
|
875
|
+
const existingIsDefault = existingP._passthrough;
|
|
876
|
+
const incomingIsDefault = incomingP._passthrough;
|
|
877
|
+
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
878
|
+
throw new Error(
|
|
879
|
+
`Duplicate custom patch for event "${name}" in state "${state2.name}"`
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
if (existingIsDefault && !incomingIsDefault) {
|
|
883
|
+
mergedPatch[name] = incomingP;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
const merged = {
|
|
888
|
+
...existing,
|
|
889
|
+
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
890
|
+
init: mergeInits(existing.init, state2.init),
|
|
891
|
+
events: { ...existing.events, ...state2.events },
|
|
892
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
893
|
+
patch: mergedPatch,
|
|
894
|
+
on: { ...existing.on, ...state2.on },
|
|
895
|
+
given: { ...existing.given, ...state2.given },
|
|
896
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
897
|
+
throw new Error(
|
|
898
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
899
|
+
);
|
|
900
|
+
})() : state2.snap || existing.snap
|
|
901
|
+
};
|
|
902
|
+
states.set(state2.name, merged);
|
|
903
|
+
for (const name of Object.keys(merged.actions)) {
|
|
904
|
+
actions[name] = merged;
|
|
905
|
+
}
|
|
906
|
+
for (const name of Object.keys(state2.events)) {
|
|
907
|
+
if (events[name]) continue;
|
|
908
|
+
events[name] = {
|
|
909
|
+
schema: state2.events[name],
|
|
910
|
+
reactions: /* @__PURE__ */ new Map()
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
} else {
|
|
914
|
+
states.set(state2.name, state2);
|
|
915
|
+
for (const name of Object.keys(state2.actions)) {
|
|
916
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
917
|
+
actions[name] = state2;
|
|
918
|
+
}
|
|
919
|
+
for (const name of Object.keys(state2.events)) {
|
|
920
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
921
|
+
events[name] = {
|
|
922
|
+
schema: state2.events[name],
|
|
923
|
+
reactions: /* @__PURE__ */ new Map()
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
function mergeProjection(proj, events) {
|
|
929
|
+
for (const eventName of Object.keys(proj.events)) {
|
|
930
|
+
const projRegister = proj.events[eventName];
|
|
931
|
+
const existing = events[eventName];
|
|
932
|
+
if (!existing) {
|
|
933
|
+
events[eventName] = {
|
|
934
|
+
schema: projRegister.schema,
|
|
935
|
+
reactions: new Map(projRegister.reactions)
|
|
936
|
+
};
|
|
937
|
+
} else {
|
|
938
|
+
for (const [name, reaction] of projRegister.reactions) {
|
|
939
|
+
let key = name;
|
|
940
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
941
|
+
existing.reactions.set(key, reaction);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
var _this_ = ({ stream }) => ({
|
|
947
|
+
source: stream,
|
|
948
|
+
target: stream
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
// src/internal/tracing.ts
|
|
877
952
|
var logger3 = log();
|
|
878
|
-
var
|
|
953
|
+
var withSnapTrace = (inner) => async (snapshot) => {
|
|
954
|
+
logger3.trace(
|
|
955
|
+
`\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
|
|
956
|
+
);
|
|
957
|
+
return inner(snapshot);
|
|
958
|
+
};
|
|
959
|
+
var withLoadTrace = (inner) => async (me, stream, callback, asOf) => {
|
|
960
|
+
logger3.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
|
|
961
|
+
return inner(me, stream, callback, asOf);
|
|
962
|
+
};
|
|
963
|
+
var withActionTrace = (inner) => async (me, action2, target, payload, reactingTo, skipValidation) => {
|
|
964
|
+
logger3.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
|
|
965
|
+
const snapshots = await inner(
|
|
966
|
+
me,
|
|
967
|
+
action2,
|
|
968
|
+
target,
|
|
969
|
+
payload,
|
|
970
|
+
reactingTo,
|
|
971
|
+
skipValidation
|
|
972
|
+
);
|
|
973
|
+
const committed = snapshots.filter((s) => s.event);
|
|
974
|
+
if (committed.length) {
|
|
975
|
+
logger3.trace(
|
|
976
|
+
committed.map((s) => s.event.data),
|
|
977
|
+
`\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
978
|
+
);
|
|
979
|
+
}
|
|
980
|
+
return snapshots;
|
|
981
|
+
};
|
|
982
|
+
function buildEs(level) {
|
|
983
|
+
if (level !== "trace") {
|
|
984
|
+
return { snap, load, action };
|
|
985
|
+
}
|
|
986
|
+
return {
|
|
987
|
+
snap: withSnapTrace(snap),
|
|
988
|
+
load: withLoadTrace(load),
|
|
989
|
+
action: withActionTrace(action)
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
var withClaimTrace = (inner) => async (lagging, leading, by, millis) => {
|
|
993
|
+
const leased = await inner(lagging, leading, by, millis);
|
|
994
|
+
if (leased.length) {
|
|
995
|
+
const data = Object.fromEntries(
|
|
996
|
+
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
997
|
+
);
|
|
998
|
+
logger3.trace(data, ">> lease");
|
|
999
|
+
}
|
|
1000
|
+
return leased;
|
|
1001
|
+
};
|
|
1002
|
+
var withFetchTrace = (inner) => async (leased, eventLimit) => {
|
|
1003
|
+
const fetched = await inner(leased, eventLimit);
|
|
1004
|
+
const data = Object.fromEntries(
|
|
1005
|
+
fetched.map(({ stream, source, events }) => {
|
|
1006
|
+
const key = source ? `${stream}<-${source}` : stream;
|
|
1007
|
+
const value = Object.fromEntries(
|
|
1008
|
+
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
1009
|
+
);
|
|
1010
|
+
return [key, value];
|
|
1011
|
+
})
|
|
1012
|
+
);
|
|
1013
|
+
logger3.trace(data, ">> fetch");
|
|
1014
|
+
return fetched;
|
|
1015
|
+
};
|
|
1016
|
+
var withAckTrace = (inner) => async (leases) => {
|
|
1017
|
+
const acked = await inner(leases);
|
|
1018
|
+
if (acked.length) {
|
|
1019
|
+
const data = Object.fromEntries(
|
|
1020
|
+
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1021
|
+
);
|
|
1022
|
+
logger3.trace(data, ">> ack");
|
|
1023
|
+
}
|
|
1024
|
+
return acked;
|
|
1025
|
+
};
|
|
1026
|
+
var withBlockTrace = (inner) => async (leases) => {
|
|
1027
|
+
const blocked = await inner(leases);
|
|
1028
|
+
if (blocked.length) {
|
|
1029
|
+
const data = Object.fromEntries(
|
|
1030
|
+
blocked.map(({ stream, at, retry, error }) => [
|
|
1031
|
+
stream,
|
|
1032
|
+
{ at, retry, error }
|
|
1033
|
+
])
|
|
1034
|
+
);
|
|
1035
|
+
logger3.trace(data, ">> block");
|
|
1036
|
+
}
|
|
1037
|
+
return blocked;
|
|
1038
|
+
};
|
|
1039
|
+
var withSubscribeTrace = (inner) => async (streams) => {
|
|
1040
|
+
const result = await inner(streams);
|
|
1041
|
+
if (result.subscribed) {
|
|
1042
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1043
|
+
logger3.trace(`>> correlate ${data}`);
|
|
1044
|
+
}
|
|
1045
|
+
return result;
|
|
1046
|
+
};
|
|
1047
|
+
function buildDrain(level) {
|
|
1048
|
+
if (level !== "trace") {
|
|
1049
|
+
return {
|
|
1050
|
+
claim,
|
|
1051
|
+
fetch,
|
|
1052
|
+
ack,
|
|
1053
|
+
block,
|
|
1054
|
+
subscribe
|
|
1055
|
+
};
|
|
1056
|
+
}
|
|
1057
|
+
return {
|
|
1058
|
+
claim: withClaimTrace(claim),
|
|
1059
|
+
fetch: withFetchTrace(fetch),
|
|
1060
|
+
ack: withAckTrace(ack),
|
|
1061
|
+
block: withBlockTrace(block),
|
|
1062
|
+
subscribe: withSubscribeTrace(subscribe)
|
|
1063
|
+
};
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// src/act.ts
|
|
1067
|
+
var logger4 = log();
|
|
879
1068
|
var Act = class {
|
|
880
1069
|
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
881
1070
|
this.registry = registry;
|
|
882
1071
|
this._states = _states;
|
|
883
1072
|
this._batch_handlers = batchHandlers;
|
|
1073
|
+
const level = log().level;
|
|
1074
|
+
this._es = buildEs(level);
|
|
1075
|
+
this._cd = buildDrain(level);
|
|
884
1076
|
const statics = [];
|
|
885
1077
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
886
1078
|
if (register.reactions.size > 0) {
|
|
@@ -940,6 +1132,10 @@ var Act = class {
|
|
|
940
1132
|
_static_targets;
|
|
941
1133
|
/** Batch handlers for static-target projections (target → handler) */
|
|
942
1134
|
_batch_handlers;
|
|
1135
|
+
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
1136
|
+
_es;
|
|
1137
|
+
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1138
|
+
_cd;
|
|
943
1139
|
/**
|
|
944
1140
|
* Executes an action on a state instance, committing resulting events.
|
|
945
1141
|
*
|
|
@@ -1022,7 +1218,7 @@ var Act = class {
|
|
|
1022
1218
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
1023
1219
|
*/
|
|
1024
1220
|
async do(action2, target, payload, reactingTo, skipValidation = false) {
|
|
1025
|
-
const snapshots = await action(
|
|
1221
|
+
const snapshots = await this._es.action(
|
|
1026
1222
|
this.registry.actions[action2],
|
|
1027
1223
|
action2,
|
|
1028
1224
|
target,
|
|
@@ -1048,7 +1244,7 @@ var Act = class {
|
|
|
1048
1244
|
} else {
|
|
1049
1245
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
1050
1246
|
}
|
|
1051
|
-
return await load(merged, stream, callback, asOf);
|
|
1247
|
+
return await this._es.load(merged, stream, callback, asOf);
|
|
1052
1248
|
}
|
|
1053
1249
|
/**
|
|
1054
1250
|
* Queries the event store for events matching a filter.
|
|
@@ -1161,7 +1357,7 @@ var Act = class {
|
|
|
1161
1357
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1162
1358
|
const stream = lease.stream;
|
|
1163
1359
|
let at = payloads.at(0).event.id, handled = 0;
|
|
1164
|
-
lease.retry > 0 &&
|
|
1360
|
+
lease.retry > 0 && logger4.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1165
1361
|
const doAction = this.do.bind(this);
|
|
1166
1362
|
const scopedApp = {
|
|
1167
1363
|
do: doAction,
|
|
@@ -1183,16 +1379,16 @@ var Act = class {
|
|
|
1183
1379
|
at = event.id;
|
|
1184
1380
|
handled++;
|
|
1185
1381
|
} catch (error) {
|
|
1186
|
-
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1382
|
+
logger4.error(error);
|
|
1383
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1384
|
+
block2 && logger4.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1189
1385
|
return {
|
|
1190
1386
|
lease,
|
|
1191
1387
|
handled,
|
|
1192
1388
|
at,
|
|
1193
1389
|
// only report error when nothing was handled
|
|
1194
1390
|
error: handled === 0 ? error.message : void 0,
|
|
1195
|
-
block
|
|
1391
|
+
block: block2
|
|
1196
1392
|
};
|
|
1197
1393
|
}
|
|
1198
1394
|
}
|
|
@@ -1215,21 +1411,21 @@ var Act = class {
|
|
|
1215
1411
|
const stream = lease.stream;
|
|
1216
1412
|
const events = payloads.map((p) => p.event);
|
|
1217
1413
|
const at = events.at(-1).id;
|
|
1218
|
-
lease.retry > 0 &&
|
|
1414
|
+
lease.retry > 0 && logger4.warn(`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`);
|
|
1219
1415
|
try {
|
|
1220
1416
|
await batchHandler(events, stream);
|
|
1221
1417
|
return { lease, handled: events.length, at };
|
|
1222
1418
|
} catch (error) {
|
|
1223
|
-
|
|
1419
|
+
logger4.error(error);
|
|
1224
1420
|
const { options } = payloads[0];
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1421
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1422
|
+
block2 && logger4.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1227
1423
|
return {
|
|
1228
1424
|
lease,
|
|
1229
1425
|
handled: 0,
|
|
1230
1426
|
at: lease.at,
|
|
1231
1427
|
error: error.message,
|
|
1232
|
-
block
|
|
1428
|
+
block: block2
|
|
1233
1429
|
};
|
|
1234
1430
|
}
|
|
1235
1431
|
}
|
|
@@ -1285,7 +1481,7 @@ var Act = class {
|
|
|
1285
1481
|
this._drain_locked = true;
|
|
1286
1482
|
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
1287
1483
|
const leading = streamLimit - lagging;
|
|
1288
|
-
const leased = await
|
|
1484
|
+
const leased = await this._cd.claim(
|
|
1289
1485
|
lagging,
|
|
1290
1486
|
leading,
|
|
1291
1487
|
randomUUID2(),
|
|
@@ -1295,17 +1491,7 @@ var Act = class {
|
|
|
1295
1491
|
this._needs_drain = false;
|
|
1296
1492
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1297
1493
|
}
|
|
1298
|
-
const fetched = await
|
|
1299
|
-
leased.map(async ({ stream, source, at, lagging: lagging2 }) => {
|
|
1300
|
-
const events = await this.query_array({
|
|
1301
|
-
stream: source,
|
|
1302
|
-
after: at,
|
|
1303
|
-
limit: eventLimit
|
|
1304
|
-
});
|
|
1305
|
-
return { stream, source, at, lagging: lagging2, events };
|
|
1306
|
-
})
|
|
1307
|
-
);
|
|
1308
|
-
tracer.fetched(fetched);
|
|
1494
|
+
const fetched = await this._cd.fetch(leased, eventLimit);
|
|
1309
1495
|
const payloadsMap = /* @__PURE__ */ new Map();
|
|
1310
1496
|
const fetch_window_at = fetched.reduce(
|
|
1311
1497
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
@@ -1322,7 +1508,6 @@ var Act = class {
|
|
|
1322
1508
|
});
|
|
1323
1509
|
payloadsMap.set(stream, payloads);
|
|
1324
1510
|
});
|
|
1325
|
-
tracer.leased(leased);
|
|
1326
1511
|
const handled = await Promise.all(
|
|
1327
1512
|
leased.map((lease) => {
|
|
1328
1513
|
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
@@ -1346,27 +1531,21 @@ var Act = class {
|
|
|
1346
1531
|
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1347
1532
|
const total = lagging_avg + leading_avg;
|
|
1348
1533
|
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1349
|
-
const acked = await
|
|
1534
|
+
const acked = await this._cd.ack(
|
|
1350
1535
|
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1351
1536
|
);
|
|
1352
|
-
if (acked.length)
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1356
|
-
const blocked = await store().block(
|
|
1357
|
-
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
1537
|
+
if (acked.length) this.emit("acked", acked);
|
|
1538
|
+
const blocked = await this._cd.block(
|
|
1539
|
+
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
1358
1540
|
);
|
|
1359
|
-
if (blocked.length)
|
|
1360
|
-
tracer.blocked(blocked);
|
|
1361
|
-
this.emit("blocked", blocked);
|
|
1362
|
-
}
|
|
1541
|
+
if (blocked.length) this.emit("blocked", blocked);
|
|
1363
1542
|
const result = { fetched, leased, acked, blocked };
|
|
1364
1543
|
const hasErrors = handled.some(({ error }) => error);
|
|
1365
1544
|
if (!acked.length && !blocked.length && !hasErrors)
|
|
1366
1545
|
this._needs_drain = false;
|
|
1367
1546
|
return result;
|
|
1368
1547
|
} catch (error) {
|
|
1369
|
-
|
|
1548
|
+
logger4.error(error);
|
|
1370
1549
|
} finally {
|
|
1371
1550
|
this._drain_locked = false;
|
|
1372
1551
|
}
|
|
@@ -1472,10 +1651,9 @@ var Act = class {
|
|
|
1472
1651
|
stream,
|
|
1473
1652
|
source
|
|
1474
1653
|
}));
|
|
1475
|
-
const { subscribed } = await
|
|
1654
|
+
const { subscribed } = await this._cd.subscribe(streams);
|
|
1476
1655
|
this._correlation_checkpoint = last_id;
|
|
1477
1656
|
if (subscribed) {
|
|
1478
|
-
tracer.correlated(streams);
|
|
1479
1657
|
for (const { stream } of streams) {
|
|
1480
1658
|
this._subscribed_statics.add(stream);
|
|
1481
1659
|
}
|
|
@@ -1740,7 +1918,7 @@ var Act = class {
|
|
|
1740
1918
|
if (mergedState) {
|
|
1741
1919
|
await Promise.all(
|
|
1742
1920
|
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
1743
|
-
const snap2 = await load(mergedState, stream);
|
|
1921
|
+
const snap2 = await this._es.load(mergedState, stream);
|
|
1744
1922
|
seedStates.set(stream, snap2.state);
|
|
1745
1923
|
})
|
|
1746
1924
|
);
|
|
@@ -1847,139 +2025,13 @@ var Act = class {
|
|
|
1847
2025
|
if (!made_progress) break;
|
|
1848
2026
|
}
|
|
1849
2027
|
if (lastDrain) this.emit("settled", lastDrain);
|
|
1850
|
-
})().catch((err) =>
|
|
2028
|
+
})().catch((err) => logger4.error(err)).finally(() => {
|
|
1851
2029
|
this._settling = false;
|
|
1852
2030
|
});
|
|
1853
2031
|
}, debounceMs);
|
|
1854
2032
|
}
|
|
1855
2033
|
};
|
|
1856
2034
|
|
|
1857
|
-
// src/merge.ts
|
|
1858
|
-
import { ZodObject } from "zod";
|
|
1859
|
-
function baseTypeName(zodType) {
|
|
1860
|
-
let t = zodType;
|
|
1861
|
-
while (typeof t.unwrap === "function") {
|
|
1862
|
-
t = t.unwrap();
|
|
1863
|
-
}
|
|
1864
|
-
return t.constructor.name;
|
|
1865
|
-
}
|
|
1866
|
-
function mergeSchemas(existing, incoming, stateName) {
|
|
1867
|
-
if (existing instanceof ZodObject && incoming instanceof ZodObject) {
|
|
1868
|
-
const existingShape = existing.shape;
|
|
1869
|
-
const incomingShape = incoming.shape;
|
|
1870
|
-
for (const key of Object.keys(incomingShape)) {
|
|
1871
|
-
if (key in existingShape) {
|
|
1872
|
-
const existingBase = baseTypeName(existingShape[key]);
|
|
1873
|
-
const incomingBase = baseTypeName(incomingShape[key]);
|
|
1874
|
-
if (existingBase !== incomingBase) {
|
|
1875
|
-
throw new Error(
|
|
1876
|
-
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
1877
|
-
);
|
|
1878
|
-
}
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
return existing.extend(incomingShape);
|
|
1882
|
-
}
|
|
1883
|
-
return existing;
|
|
1884
|
-
}
|
|
1885
|
-
function mergeInits(existing, incoming) {
|
|
1886
|
-
return () => ({ ...existing(), ...incoming() });
|
|
1887
|
-
}
|
|
1888
|
-
function registerState(state2, states, actions, events) {
|
|
1889
|
-
if (states.has(state2.name)) {
|
|
1890
|
-
const existing = states.get(state2.name);
|
|
1891
|
-
for (const name of Object.keys(state2.actions)) {
|
|
1892
|
-
if (existing.actions[name] === state2.actions[name]) continue;
|
|
1893
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
1894
|
-
}
|
|
1895
|
-
for (const name of Object.keys(state2.events)) {
|
|
1896
|
-
if (existing.events[name] === state2.events[name]) continue;
|
|
1897
|
-
if (existing.events[name]) continue;
|
|
1898
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1899
|
-
}
|
|
1900
|
-
const mergedPatch = { ...existing.patch };
|
|
1901
|
-
for (const name of Object.keys(state2.patch)) {
|
|
1902
|
-
const existingP = existing.patch[name];
|
|
1903
|
-
const incomingP = state2.patch[name];
|
|
1904
|
-
if (!existingP) {
|
|
1905
|
-
mergedPatch[name] = incomingP;
|
|
1906
|
-
} else {
|
|
1907
|
-
const existingIsDefault = existingP._passthrough;
|
|
1908
|
-
const incomingIsDefault = incomingP._passthrough;
|
|
1909
|
-
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
1910
|
-
throw new Error(
|
|
1911
|
-
`Duplicate custom patch for event "${name}" in state "${state2.name}"`
|
|
1912
|
-
);
|
|
1913
|
-
}
|
|
1914
|
-
if (existingIsDefault && !incomingIsDefault) {
|
|
1915
|
-
mergedPatch[name] = incomingP;
|
|
1916
|
-
}
|
|
1917
|
-
}
|
|
1918
|
-
}
|
|
1919
|
-
const merged = {
|
|
1920
|
-
...existing,
|
|
1921
|
-
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
1922
|
-
init: mergeInits(existing.init, state2.init),
|
|
1923
|
-
events: { ...existing.events, ...state2.events },
|
|
1924
|
-
actions: { ...existing.actions, ...state2.actions },
|
|
1925
|
-
patch: mergedPatch,
|
|
1926
|
-
on: { ...existing.on, ...state2.on },
|
|
1927
|
-
given: { ...existing.given, ...state2.given },
|
|
1928
|
-
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
1929
|
-
throw new Error(
|
|
1930
|
-
`Duplicate snap strategy for state "${state2.name}"`
|
|
1931
|
-
);
|
|
1932
|
-
})() : state2.snap || existing.snap
|
|
1933
|
-
};
|
|
1934
|
-
states.set(state2.name, merged);
|
|
1935
|
-
for (const name of Object.keys(merged.actions)) {
|
|
1936
|
-
actions[name] = merged;
|
|
1937
|
-
}
|
|
1938
|
-
for (const name of Object.keys(state2.events)) {
|
|
1939
|
-
if (events[name]) continue;
|
|
1940
|
-
events[name] = {
|
|
1941
|
-
schema: state2.events[name],
|
|
1942
|
-
reactions: /* @__PURE__ */ new Map()
|
|
1943
|
-
};
|
|
1944
|
-
}
|
|
1945
|
-
} else {
|
|
1946
|
-
states.set(state2.name, state2);
|
|
1947
|
-
for (const name of Object.keys(state2.actions)) {
|
|
1948
|
-
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
1949
|
-
actions[name] = state2;
|
|
1950
|
-
}
|
|
1951
|
-
for (const name of Object.keys(state2.events)) {
|
|
1952
|
-
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1953
|
-
events[name] = {
|
|
1954
|
-
schema: state2.events[name],
|
|
1955
|
-
reactions: /* @__PURE__ */ new Map()
|
|
1956
|
-
};
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
function mergeProjection(proj, events) {
|
|
1961
|
-
for (const eventName of Object.keys(proj.events)) {
|
|
1962
|
-
const projRegister = proj.events[eventName];
|
|
1963
|
-
const existing = events[eventName];
|
|
1964
|
-
if (!existing) {
|
|
1965
|
-
events[eventName] = {
|
|
1966
|
-
schema: projRegister.schema,
|
|
1967
|
-
reactions: new Map(projRegister.reactions)
|
|
1968
|
-
};
|
|
1969
|
-
} else {
|
|
1970
|
-
for (const [name, reaction] of projRegister.reactions) {
|
|
1971
|
-
let key = name;
|
|
1972
|
-
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
1973
|
-
existing.reactions.set(key, reaction);
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
var _this_ = ({ stream }) => ({
|
|
1979
|
-
source: stream,
|
|
1980
|
-
target: stream
|
|
1981
|
-
});
|
|
1982
|
-
|
|
1983
2035
|
// src/act-builder.ts
|
|
1984
2036
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
1985
2037
|
actions: {},
|
|
@@ -2322,7 +2374,6 @@ export {
|
|
|
2322
2374
|
ValidationError,
|
|
2323
2375
|
ZodEmpty,
|
|
2324
2376
|
act,
|
|
2325
|
-
build_tracer,
|
|
2326
2377
|
cache,
|
|
2327
2378
|
config,
|
|
2328
2379
|
dispose,
|