@rotorsoft/act 0.32.0 → 0.32.2
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 +10 -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 +39 -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 +22 -0
- package/dist/@types/internal/index.d.ts.map +1 -0
- package/dist/@types/internal/merge.d.ts +19 -0
- package/dist/@types/internal/merge.d.ts.map +1 -0
- package/dist/@types/internal/tracing.d.ts +36 -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/@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/index.cjs +291 -252
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +291 -251
- 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 +0 -43
- 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,162 @@ 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/merge.ts
|
|
676
|
+
import { ZodObject } from "zod";
|
|
677
|
+
function baseTypeName(zodType) {
|
|
678
|
+
let t = zodType;
|
|
679
|
+
while (typeof t.unwrap === "function") {
|
|
680
|
+
t = t.unwrap();
|
|
681
|
+
}
|
|
682
|
+
return t.constructor.name;
|
|
683
|
+
}
|
|
684
|
+
function mergeSchemas(existing, incoming, stateName) {
|
|
685
|
+
if (existing instanceof ZodObject && incoming instanceof ZodObject) {
|
|
686
|
+
const existingShape = existing.shape;
|
|
687
|
+
const incomingShape = incoming.shape;
|
|
688
|
+
for (const key of Object.keys(incomingShape)) {
|
|
689
|
+
if (key in existingShape) {
|
|
690
|
+
const existingBase = baseTypeName(existingShape[key]);
|
|
691
|
+
const incomingBase = baseTypeName(incomingShape[key]);
|
|
692
|
+
if (existingBase !== incomingBase) {
|
|
693
|
+
throw new Error(
|
|
694
|
+
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return existing.extend(incomingShape);
|
|
700
|
+
}
|
|
701
|
+
return existing;
|
|
702
|
+
}
|
|
703
|
+
function mergeInits(existing, incoming) {
|
|
704
|
+
return () => ({ ...existing(), ...incoming() });
|
|
705
|
+
}
|
|
706
|
+
function registerState(state2, states, actions, events) {
|
|
707
|
+
const existing = states.get(state2.name);
|
|
708
|
+
if (existing) {
|
|
709
|
+
mergeIntoExisting(state2, existing, states, actions, events);
|
|
710
|
+
} else {
|
|
711
|
+
registerNewState(state2, states, actions, events);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
function registerNewState(state2, states, actions, events) {
|
|
715
|
+
states.set(state2.name, state2);
|
|
716
|
+
for (const name of Object.keys(state2.actions)) {
|
|
717
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
718
|
+
actions[name] = state2;
|
|
719
|
+
}
|
|
720
|
+
for (const name of Object.keys(state2.events)) {
|
|
721
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
722
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
function mergeIntoExisting(state2, existing, states, actions, events) {
|
|
726
|
+
for (const name of Object.keys(state2.actions)) {
|
|
727
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
728
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
729
|
+
}
|
|
730
|
+
for (const name of Object.keys(state2.events)) {
|
|
731
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
732
|
+
if (existing.events[name]) continue;
|
|
733
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
734
|
+
}
|
|
735
|
+
const mergedPatch = mergePatches(existing.patch, state2.patch, state2.name);
|
|
736
|
+
const merged = {
|
|
737
|
+
...existing,
|
|
738
|
+
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
739
|
+
init: mergeInits(existing.init, state2.init),
|
|
740
|
+
events: { ...existing.events, ...state2.events },
|
|
741
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
742
|
+
patch: mergedPatch,
|
|
743
|
+
on: { ...existing.on, ...state2.on },
|
|
744
|
+
given: { ...existing.given, ...state2.given },
|
|
745
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
746
|
+
throw new Error(
|
|
747
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
748
|
+
);
|
|
749
|
+
})() : state2.snap || existing.snap
|
|
750
|
+
};
|
|
751
|
+
states.set(state2.name, merged);
|
|
752
|
+
for (const name of Object.keys(merged.actions)) {
|
|
753
|
+
actions[name] = merged;
|
|
754
|
+
}
|
|
755
|
+
for (const name of Object.keys(state2.events)) {
|
|
756
|
+
if (events[name]) continue;
|
|
757
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
function mergePatches(existing, incoming, stateName) {
|
|
761
|
+
const merged = { ...existing };
|
|
762
|
+
for (const name of Object.keys(incoming)) {
|
|
763
|
+
const existingP = existing[name];
|
|
764
|
+
const incomingP = incoming[name];
|
|
765
|
+
if (!existingP) {
|
|
766
|
+
merged[name] = incomingP;
|
|
767
|
+
continue;
|
|
768
|
+
}
|
|
769
|
+
const existingIsDefault = existingP._passthrough;
|
|
770
|
+
const incomingIsDefault = incomingP._passthrough;
|
|
771
|
+
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
772
|
+
throw new Error(
|
|
773
|
+
`Duplicate custom patch for event "${name}" in state "${stateName}"`
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
if (existingIsDefault && !incomingIsDefault) {
|
|
777
|
+
merged[name] = incomingP;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
return merged;
|
|
781
|
+
}
|
|
782
|
+
function mergeProjection(proj, events) {
|
|
783
|
+
for (const eventName of Object.keys(proj.events)) {
|
|
784
|
+
const projRegister = proj.events[eventName];
|
|
785
|
+
const existing = events[eventName];
|
|
786
|
+
if (!existing) {
|
|
787
|
+
events[eventName] = {
|
|
788
|
+
schema: projRegister.schema,
|
|
789
|
+
reactions: new Map(projRegister.reactions)
|
|
790
|
+
};
|
|
791
|
+
} else {
|
|
792
|
+
for (const [name, reaction] of projRegister.reactions) {
|
|
793
|
+
let key = name;
|
|
794
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
795
|
+
existing.reactions.set(key, reaction);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
var _this_ = ({ stream }) => ({
|
|
801
|
+
source: stream,
|
|
802
|
+
target: stream
|
|
803
|
+
});
|
|
804
|
+
|
|
805
|
+
// src/internal/drain.ts
|
|
806
|
+
var claim = (lagging, leading, by, millis) => store().claim(lagging, leading, by, millis);
|
|
807
|
+
async function fetch(leased, eventLimit) {
|
|
808
|
+
return Promise.all(
|
|
809
|
+
leased.map(async ({ stream, source, at, lagging }) => {
|
|
810
|
+
const events = [];
|
|
811
|
+
await store().query((e) => events.push(e), {
|
|
812
|
+
stream: source,
|
|
813
|
+
after: at,
|
|
814
|
+
limit: eventLimit
|
|
815
|
+
});
|
|
816
|
+
return { stream, source, at, lagging, events };
|
|
817
|
+
})
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
var ack = (leases) => store().ack(leases);
|
|
821
|
+
var block = (leases) => store().block(leases);
|
|
822
|
+
var subscribe = (streams) => store().subscribe(streams);
|
|
823
|
+
|
|
824
|
+
// src/internal/event-sourcing.ts
|
|
733
825
|
import { patch } from "@rotorsoft/act-patch";
|
|
734
826
|
import { randomUUID } from "crypto";
|
|
735
|
-
var logger2 = log();
|
|
736
827
|
async function snap(snapshot) {
|
|
737
828
|
try {
|
|
738
829
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
739
|
-
|
|
830
|
+
await store().commit(
|
|
740
831
|
stream,
|
|
741
832
|
[{ name: SNAP_EVENT, data: snapshot.state }],
|
|
742
833
|
{
|
|
@@ -746,19 +837,18 @@ async function snap(snapshot) {
|
|
|
746
837
|
version
|
|
747
838
|
// IMPORTANT! - state events are committed right after the snapshot event
|
|
748
839
|
);
|
|
749
|
-
logger2.trace(snapped, "\u{1F7E0} snap");
|
|
750
840
|
} catch (error) {
|
|
751
|
-
|
|
841
|
+
log().error(error);
|
|
752
842
|
}
|
|
753
843
|
}
|
|
754
844
|
async function load(me, stream, callback, asOf) {
|
|
755
|
-
const timeTravel = asOf && (asOf.
|
|
845
|
+
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
756
846
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
757
847
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
758
848
|
let patches = cached?.patches ?? 0;
|
|
759
849
|
let snaps = cached?.snaps ?? 0;
|
|
760
850
|
let event;
|
|
761
|
-
|
|
851
|
+
await store().query(
|
|
762
852
|
(e) => {
|
|
763
853
|
event = e;
|
|
764
854
|
if (e.name === SNAP_EVENT) {
|
|
@@ -777,10 +867,6 @@ async function load(me, stream, callback, asOf) {
|
|
|
777
867
|
...cached ? { after: cached.event_id } : { with_snaps: true, ...asOf }
|
|
778
868
|
}
|
|
779
869
|
);
|
|
780
|
-
logger2.trace(
|
|
781
|
-
state2,
|
|
782
|
-
`\u{1F7E2} load ${stream}${cached && count === 0 ? " (cached)" : ""}${timeTravel ? " (as-of)" : ""}`
|
|
783
|
-
);
|
|
784
870
|
return { event, state: state2, patches, snaps };
|
|
785
871
|
}
|
|
786
872
|
async function action(me, action2, target, payload, reactingTo, skipValidation = false) {
|
|
@@ -791,10 +877,6 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
791
877
|
if (snapshot.event?.name === TOMBSTONE_EVENT)
|
|
792
878
|
throw new StreamClosedError(stream);
|
|
793
879
|
const expected = expectedVersion ?? snapshot.event?.version;
|
|
794
|
-
logger2.trace(
|
|
795
|
-
payload,
|
|
796
|
-
`\u{1F535} ${stream}.${action2}${typeof expected === "number" ? `.${expected}` : ""}`
|
|
797
|
-
);
|
|
798
880
|
if (me.given) {
|
|
799
881
|
const invariants = me.given[action2] || [];
|
|
800
882
|
invariants.forEach(({ valid, description }) => {
|
|
@@ -834,10 +916,6 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
834
916
|
} : void 0
|
|
835
917
|
}
|
|
836
918
|
};
|
|
837
|
-
logger2.trace(
|
|
838
|
-
emitted.map((e) => e.data),
|
|
839
|
-
`\u{1F534} commit ${stream}.${emitted.map((e) => e.name).join(", ")}`
|
|
840
|
-
);
|
|
841
919
|
let committed;
|
|
842
920
|
try {
|
|
843
921
|
committed = await store().commit(
|
|
@@ -848,7 +926,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
848
926
|
reactingTo ? void 0 : expected
|
|
849
927
|
);
|
|
850
928
|
} catch (error) {
|
|
851
|
-
if (error
|
|
929
|
+
if (error instanceof ConcurrencyError) {
|
|
852
930
|
await cache().invalidate(stream);
|
|
853
931
|
}
|
|
854
932
|
throw error;
|
|
@@ -862,25 +940,121 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
862
940
|
});
|
|
863
941
|
const last = snapshots.at(-1);
|
|
864
942
|
const snapped = me.snap && me.snap(last);
|
|
865
|
-
|
|
943
|
+
cache().set(stream, {
|
|
866
944
|
state: last.state,
|
|
867
945
|
version: last.event.version,
|
|
868
946
|
event_id: last.event.id,
|
|
869
947
|
patches: snapped ? 0 : last.patches,
|
|
870
948
|
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
871
|
-
});
|
|
949
|
+
}).catch((err) => log().error(err));
|
|
872
950
|
if (snapped) void snap(last);
|
|
873
951
|
return snapshots;
|
|
874
952
|
}
|
|
875
953
|
|
|
954
|
+
// src/internal/tracing.ts
|
|
955
|
+
var traced = (inner, exit, entry) => (async (...args) => {
|
|
956
|
+
entry?.(...args);
|
|
957
|
+
const result = await inner(...args);
|
|
958
|
+
exit?.(result, ...args);
|
|
959
|
+
return result;
|
|
960
|
+
});
|
|
961
|
+
function buildEs(logger2) {
|
|
962
|
+
if (logger2.level !== "trace") {
|
|
963
|
+
return { snap, load, action };
|
|
964
|
+
}
|
|
965
|
+
return {
|
|
966
|
+
snap: traced(snap, void 0, (snapshot) => {
|
|
967
|
+
logger2.trace(
|
|
968
|
+
`\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
|
|
969
|
+
);
|
|
970
|
+
}),
|
|
971
|
+
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
972
|
+
logger2.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
|
|
973
|
+
}),
|
|
974
|
+
action: traced(
|
|
975
|
+
action,
|
|
976
|
+
(snapshots, _me, _action, target) => {
|
|
977
|
+
const committed = snapshots.filter((s) => s.event);
|
|
978
|
+
if (committed.length) {
|
|
979
|
+
logger2.trace(
|
|
980
|
+
committed.map((s) => s.event.data),
|
|
981
|
+
`\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
},
|
|
985
|
+
(_me, action2, target, payload) => {
|
|
986
|
+
logger2.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
|
|
987
|
+
}
|
|
988
|
+
)
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
function buildDrain(logger2) {
|
|
992
|
+
if (logger2.level !== "trace") {
|
|
993
|
+
return {
|
|
994
|
+
claim,
|
|
995
|
+
fetch,
|
|
996
|
+
ack,
|
|
997
|
+
block,
|
|
998
|
+
subscribe
|
|
999
|
+
};
|
|
1000
|
+
}
|
|
1001
|
+
return {
|
|
1002
|
+
claim: traced(claim, (leased) => {
|
|
1003
|
+
if (leased.length) {
|
|
1004
|
+
const data = Object.fromEntries(
|
|
1005
|
+
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1006
|
+
);
|
|
1007
|
+
logger2.trace(data, ">> lease");
|
|
1008
|
+
}
|
|
1009
|
+
}),
|
|
1010
|
+
fetch: traced(fetch, (fetched) => {
|
|
1011
|
+
const data = Object.fromEntries(
|
|
1012
|
+
fetched.map(({ stream, source, events }) => {
|
|
1013
|
+
const key = source ? `${stream}<-${source}` : stream;
|
|
1014
|
+
const value = Object.fromEntries(
|
|
1015
|
+
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
1016
|
+
);
|
|
1017
|
+
return [key, value];
|
|
1018
|
+
})
|
|
1019
|
+
);
|
|
1020
|
+
logger2.trace(data, ">> fetch");
|
|
1021
|
+
}),
|
|
1022
|
+
ack: traced(ack, (acked) => {
|
|
1023
|
+
if (acked.length) {
|
|
1024
|
+
const data = Object.fromEntries(
|
|
1025
|
+
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1026
|
+
);
|
|
1027
|
+
logger2.trace(data, ">> ack");
|
|
1028
|
+
}
|
|
1029
|
+
}),
|
|
1030
|
+
block: traced(block, (blocked) => {
|
|
1031
|
+
if (blocked.length) {
|
|
1032
|
+
const data = Object.fromEntries(
|
|
1033
|
+
blocked.map(({ stream, at, retry, error }) => [
|
|
1034
|
+
stream,
|
|
1035
|
+
{ at, retry, error }
|
|
1036
|
+
])
|
|
1037
|
+
);
|
|
1038
|
+
logger2.trace(data, ">> block");
|
|
1039
|
+
}
|
|
1040
|
+
}),
|
|
1041
|
+
subscribe: traced(subscribe, (result, streams) => {
|
|
1042
|
+
if (result.subscribed) {
|
|
1043
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1044
|
+
logger2.trace(`>> correlate ${data}`);
|
|
1045
|
+
}
|
|
1046
|
+
})
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
|
|
876
1050
|
// src/act.ts
|
|
877
|
-
var logger3 = log();
|
|
878
|
-
var tracer = build_tracer(config().logLevel);
|
|
879
1051
|
var Act = class {
|
|
880
1052
|
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
881
1053
|
this.registry = registry;
|
|
882
1054
|
this._states = _states;
|
|
883
1055
|
this._batch_handlers = batchHandlers;
|
|
1056
|
+
this._es = buildEs(this._logger);
|
|
1057
|
+
this._cd = buildDrain(this._logger);
|
|
884
1058
|
const statics = [];
|
|
885
1059
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
886
1060
|
if (register.reactions.size > 0) {
|
|
@@ -940,6 +1114,12 @@ var Act = class {
|
|
|
940
1114
|
_static_targets;
|
|
941
1115
|
/** Batch handlers for static-target projections (target → handler) */
|
|
942
1116
|
_batch_handlers;
|
|
1117
|
+
/** Event-sourcing handlers, optionally wrapped with trace decorators */
|
|
1118
|
+
_es;
|
|
1119
|
+
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1120
|
+
_cd;
|
|
1121
|
+
/** Logger resolved at construction time (after user port configuration) */
|
|
1122
|
+
_logger = log();
|
|
943
1123
|
/**
|
|
944
1124
|
* Executes an action on a state instance, committing resulting events.
|
|
945
1125
|
*
|
|
@@ -1022,7 +1202,7 @@ var Act = class {
|
|
|
1022
1202
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
1023
1203
|
*/
|
|
1024
1204
|
async do(action2, target, payload, reactingTo, skipValidation = false) {
|
|
1025
|
-
const snapshots = await action(
|
|
1205
|
+
const snapshots = await this._es.action(
|
|
1026
1206
|
this.registry.actions[action2],
|
|
1027
1207
|
action2,
|
|
1028
1208
|
target,
|
|
@@ -1048,7 +1228,7 @@ var Act = class {
|
|
|
1048
1228
|
} else {
|
|
1049
1229
|
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
1050
1230
|
}
|
|
1051
|
-
return await load(merged, stream, callback, asOf);
|
|
1231
|
+
return await this._es.load(merged, stream, callback, asOf);
|
|
1052
1232
|
}
|
|
1053
1233
|
/**
|
|
1054
1234
|
* Queries the event store for events matching a filter.
|
|
@@ -1161,7 +1341,7 @@ var Act = class {
|
|
|
1161
1341
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1162
1342
|
const stream = lease.stream;
|
|
1163
1343
|
let at = payloads.at(0).event.id, handled = 0;
|
|
1164
|
-
lease.retry > 0 &&
|
|
1344
|
+
lease.retry > 0 && this._logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1165
1345
|
const doAction = this.do.bind(this);
|
|
1166
1346
|
const scopedApp = {
|
|
1167
1347
|
do: doAction,
|
|
@@ -1183,16 +1363,18 @@ var Act = class {
|
|
|
1183
1363
|
at = event.id;
|
|
1184
1364
|
handled++;
|
|
1185
1365
|
} catch (error) {
|
|
1186
|
-
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1366
|
+
this._logger.error(error);
|
|
1367
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1368
|
+
block2 && this._logger.error(
|
|
1369
|
+
`Blocking ${stream} after ${lease.retry} retries.`
|
|
1370
|
+
);
|
|
1189
1371
|
return {
|
|
1190
1372
|
lease,
|
|
1191
1373
|
handled,
|
|
1192
1374
|
at,
|
|
1193
1375
|
// only report error when nothing was handled
|
|
1194
1376
|
error: handled === 0 ? error.message : void 0,
|
|
1195
|
-
block
|
|
1377
|
+
block: block2
|
|
1196
1378
|
};
|
|
1197
1379
|
}
|
|
1198
1380
|
}
|
|
@@ -1215,21 +1397,23 @@ var Act = class {
|
|
|
1215
1397
|
const stream = lease.stream;
|
|
1216
1398
|
const events = payloads.map((p) => p.event);
|
|
1217
1399
|
const at = events.at(-1).id;
|
|
1218
|
-
lease.retry > 0 &&
|
|
1400
|
+
lease.retry > 0 && this._logger.warn(
|
|
1401
|
+
`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`
|
|
1402
|
+
);
|
|
1219
1403
|
try {
|
|
1220
1404
|
await batchHandler(events, stream);
|
|
1221
1405
|
return { lease, handled: events.length, at };
|
|
1222
1406
|
} catch (error) {
|
|
1223
|
-
|
|
1407
|
+
this._logger.error(error);
|
|
1224
1408
|
const { options } = payloads[0];
|
|
1225
|
-
const
|
|
1226
|
-
|
|
1409
|
+
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1410
|
+
block2 && this._logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1227
1411
|
return {
|
|
1228
1412
|
lease,
|
|
1229
1413
|
handled: 0,
|
|
1230
1414
|
at: lease.at,
|
|
1231
1415
|
error: error.message,
|
|
1232
|
-
block
|
|
1416
|
+
block: block2
|
|
1233
1417
|
};
|
|
1234
1418
|
}
|
|
1235
1419
|
}
|
|
@@ -1285,7 +1469,7 @@ var Act = class {
|
|
|
1285
1469
|
this._drain_locked = true;
|
|
1286
1470
|
const lagging = Math.ceil(streamLimit * this._drain_lag2lead_ratio);
|
|
1287
1471
|
const leading = streamLimit - lagging;
|
|
1288
|
-
const leased = await
|
|
1472
|
+
const leased = await this._cd.claim(
|
|
1289
1473
|
lagging,
|
|
1290
1474
|
leading,
|
|
1291
1475
|
randomUUID2(),
|
|
@@ -1295,17 +1479,7 @@ var Act = class {
|
|
|
1295
1479
|
this._needs_drain = false;
|
|
1296
1480
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1297
1481
|
}
|
|
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);
|
|
1482
|
+
const fetched = await this._cd.fetch(leased, eventLimit);
|
|
1309
1483
|
const payloadsMap = /* @__PURE__ */ new Map();
|
|
1310
1484
|
const fetch_window_at = fetched.reduce(
|
|
1311
1485
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
@@ -1322,7 +1496,6 @@ var Act = class {
|
|
|
1322
1496
|
});
|
|
1323
1497
|
payloadsMap.set(stream, payloads);
|
|
1324
1498
|
});
|
|
1325
|
-
tracer.leased(leased);
|
|
1326
1499
|
const handled = await Promise.all(
|
|
1327
1500
|
leased.map((lease) => {
|
|
1328
1501
|
const streamFetch = fetched.find((f) => f.stream === lease.stream);
|
|
@@ -1346,27 +1519,21 @@ var Act = class {
|
|
|
1346
1519
|
const leading_avg = leading > 0 ? leading_handled / leading : 0;
|
|
1347
1520
|
const total = lagging_avg + leading_avg;
|
|
1348
1521
|
this._drain_lag2lead_ratio = total > 0 ? Math.max(0.2, Math.min(0.8, lagging_avg / total)) : 0.5;
|
|
1349
|
-
const acked = await
|
|
1522
|
+
const acked = await this._cd.ack(
|
|
1350
1523
|
handled.filter(({ error }) => !error).map(({ at, lease }) => ({ ...lease, at }))
|
|
1351
1524
|
);
|
|
1352
|
-
if (acked.length)
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
}
|
|
1356
|
-
const blocked = await store().block(
|
|
1357
|
-
handled.filter(({ block }) => block).map(({ lease, error }) => ({ ...lease, error }))
|
|
1525
|
+
if (acked.length) this.emit("acked", acked);
|
|
1526
|
+
const blocked = await this._cd.block(
|
|
1527
|
+
handled.filter(({ block: block2 }) => block2).map(({ lease, error }) => ({ ...lease, error }))
|
|
1358
1528
|
);
|
|
1359
|
-
if (blocked.length)
|
|
1360
|
-
tracer.blocked(blocked);
|
|
1361
|
-
this.emit("blocked", blocked);
|
|
1362
|
-
}
|
|
1529
|
+
if (blocked.length) this.emit("blocked", blocked);
|
|
1363
1530
|
const result = { fetched, leased, acked, blocked };
|
|
1364
1531
|
const hasErrors = handled.some(({ error }) => error);
|
|
1365
1532
|
if (!acked.length && !blocked.length && !hasErrors)
|
|
1366
1533
|
this._needs_drain = false;
|
|
1367
1534
|
return result;
|
|
1368
1535
|
} catch (error) {
|
|
1369
|
-
|
|
1536
|
+
this._logger.error(error);
|
|
1370
1537
|
} finally {
|
|
1371
1538
|
this._drain_locked = false;
|
|
1372
1539
|
}
|
|
@@ -1472,10 +1639,9 @@ var Act = class {
|
|
|
1472
1639
|
stream,
|
|
1473
1640
|
source
|
|
1474
1641
|
}));
|
|
1475
|
-
const { subscribed } = await
|
|
1642
|
+
const { subscribed } = await this._cd.subscribe(streams);
|
|
1476
1643
|
this._correlation_checkpoint = last_id;
|
|
1477
1644
|
if (subscribed) {
|
|
1478
|
-
tracer.correlated(streams);
|
|
1479
1645
|
for (const { stream } of streams) {
|
|
1480
1646
|
this._subscribed_statics.add(stream);
|
|
1481
1647
|
}
|
|
@@ -1740,7 +1906,7 @@ var Act = class {
|
|
|
1740
1906
|
if (mergedState) {
|
|
1741
1907
|
await Promise.all(
|
|
1742
1908
|
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
1743
|
-
const snap2 = await load(mergedState, stream);
|
|
1909
|
+
const snap2 = await this._es.load(mergedState, stream);
|
|
1744
1910
|
seedStates.set(stream, snap2.state);
|
|
1745
1911
|
})
|
|
1746
1912
|
);
|
|
@@ -1847,139 +2013,13 @@ var Act = class {
|
|
|
1847
2013
|
if (!made_progress) break;
|
|
1848
2014
|
}
|
|
1849
2015
|
if (lastDrain) this.emit("settled", lastDrain);
|
|
1850
|
-
})().catch((err) =>
|
|
2016
|
+
})().catch((err) => this._logger.error(err)).finally(() => {
|
|
1851
2017
|
this._settling = false;
|
|
1852
2018
|
});
|
|
1853
2019
|
}, debounceMs);
|
|
1854
2020
|
}
|
|
1855
2021
|
};
|
|
1856
2022
|
|
|
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
2023
|
// src/act-builder.ts
|
|
1984
2024
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
1985
2025
|
actions: {},
|
|
@@ -2220,8 +2260,9 @@ function state(entry) {
|
|
|
2220
2260
|
emits(events) {
|
|
2221
2261
|
const defaultPatch = Object.fromEntries(
|
|
2222
2262
|
Object.keys(events).map((k) => {
|
|
2223
|
-
const fn = ({ data }) => data
|
|
2224
|
-
|
|
2263
|
+
const fn = Object.assign(({ data }) => data, {
|
|
2264
|
+
_passthrough: true
|
|
2265
|
+
});
|
|
2225
2266
|
return [k, fn];
|
|
2226
2267
|
})
|
|
2227
2268
|
);
|
|
@@ -2322,7 +2363,6 @@ export {
|
|
|
2322
2363
|
ValidationError,
|
|
2323
2364
|
ZodEmpty,
|
|
2324
2365
|
act,
|
|
2325
|
-
build_tracer,
|
|
2326
2366
|
cache,
|
|
2327
2367
|
config,
|
|
2328
2368
|
dispose,
|