@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.js
CHANGED
|
@@ -154,7 +154,7 @@ var InMemoryCache = class {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
// src/utils.ts
|
|
157
|
-
import { prettifyError } from "zod";
|
|
157
|
+
import { ZodError, prettifyError } from "zod";
|
|
158
158
|
|
|
159
159
|
// src/config.ts
|
|
160
160
|
import * as fs from "fs";
|
|
@@ -181,7 +181,7 @@ var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
|
181
181
|
var env = NODE_ENV || "development";
|
|
182
182
|
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : NODE_ENV === "production" ? "info" : "trace");
|
|
183
183
|
var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
|
|
184
|
-
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
|
|
184
|
+
var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
|
|
185
185
|
var pkg = getPackage();
|
|
186
186
|
var config = () => {
|
|
187
187
|
return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
|
|
@@ -192,19 +192,15 @@ var validate = (target, payload, schema) => {
|
|
|
192
192
|
try {
|
|
193
193
|
return schema ? schema.parse(payload) : payload;
|
|
194
194
|
} catch (error) {
|
|
195
|
-
if (error instanceof
|
|
196
|
-
throw new ValidationError(
|
|
197
|
-
target,
|
|
198
|
-
payload,
|
|
199
|
-
prettifyError(error)
|
|
200
|
-
);
|
|
195
|
+
if (error instanceof ZodError) {
|
|
196
|
+
throw new ValidationError(target, payload, prettifyError(error));
|
|
201
197
|
}
|
|
202
198
|
throw new ValidationError(target, payload, error);
|
|
203
199
|
}
|
|
204
200
|
};
|
|
205
201
|
var extend = (source, schema, target) => {
|
|
206
202
|
const value = validate("config", source, schema);
|
|
207
|
-
return
|
|
203
|
+
return { ...target, ...value };
|
|
208
204
|
};
|
|
209
205
|
async function sleep(ms) {
|
|
210
206
|
return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
|
|
@@ -632,13 +628,13 @@ var cache = port(function cache2(adapter) {
|
|
|
632
628
|
var disposers = [];
|
|
633
629
|
async function disposeAndExit(code = "EXIT") {
|
|
634
630
|
if (code === "ERROR" && config().env === "production") return;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
})
|
|
641
|
-
|
|
631
|
+
for (const disposer of [...disposers].reverse()) {
|
|
632
|
+
await disposer();
|
|
633
|
+
}
|
|
634
|
+
for (const adapter of [...adapters.values()].reverse()) {
|
|
635
|
+
await adapter.dispose();
|
|
636
|
+
console.log(`[act] - ${adapter.constructor.name}`);
|
|
637
|
+
}
|
|
642
638
|
adapters.clear();
|
|
643
639
|
config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
|
|
644
640
|
}
|
|
@@ -650,21 +646,20 @@ var SNAP_EVENT = "__snapshot__";
|
|
|
650
646
|
var TOMBSTONE_EVENT = "__tombstone__";
|
|
651
647
|
|
|
652
648
|
// src/signals.ts
|
|
653
|
-
var logger = log();
|
|
654
649
|
process.once("SIGINT", async (arg) => {
|
|
655
|
-
|
|
650
|
+
log().info(arg, "SIGINT");
|
|
656
651
|
await disposeAndExit("EXIT");
|
|
657
652
|
});
|
|
658
653
|
process.once("SIGTERM", async (arg) => {
|
|
659
|
-
|
|
654
|
+
log().info(arg, "SIGTERM");
|
|
660
655
|
await disposeAndExit("EXIT");
|
|
661
656
|
});
|
|
662
657
|
process.once("uncaughtException", async (arg) => {
|
|
663
|
-
|
|
658
|
+
log().error(arg, "Uncaught Exception");
|
|
664
659
|
await disposeAndExit("ERROR");
|
|
665
660
|
});
|
|
666
661
|
process.once("unhandledRejection", async (arg) => {
|
|
667
|
-
|
|
662
|
+
log().error(arg, "Unhandled Rejection");
|
|
668
663
|
await disposeAndExit("ERROR");
|
|
669
664
|
});
|
|
670
665
|
|
|
@@ -672,6 +667,136 @@ process.once("unhandledRejection", async (arg) => {
|
|
|
672
667
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
673
668
|
import EventEmitter from "events";
|
|
674
669
|
|
|
670
|
+
// src/internal/merge.ts
|
|
671
|
+
import { ZodObject } from "zod";
|
|
672
|
+
function baseTypeName(zodType) {
|
|
673
|
+
let t = zodType;
|
|
674
|
+
while (typeof t.unwrap === "function") {
|
|
675
|
+
t = t.unwrap();
|
|
676
|
+
}
|
|
677
|
+
return t.constructor.name;
|
|
678
|
+
}
|
|
679
|
+
function mergeSchemas(existing, incoming, stateName) {
|
|
680
|
+
if (existing instanceof ZodObject && incoming instanceof ZodObject) {
|
|
681
|
+
const existingShape = existing.shape;
|
|
682
|
+
const incomingShape = incoming.shape;
|
|
683
|
+
for (const key of Object.keys(incomingShape)) {
|
|
684
|
+
if (key in existingShape) {
|
|
685
|
+
const existingBase = baseTypeName(existingShape[key]);
|
|
686
|
+
const incomingBase = baseTypeName(incomingShape[key]);
|
|
687
|
+
if (existingBase !== incomingBase) {
|
|
688
|
+
throw new Error(
|
|
689
|
+
`Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
return existing.extend(incomingShape);
|
|
695
|
+
}
|
|
696
|
+
return existing;
|
|
697
|
+
}
|
|
698
|
+
function mergeInits(existing, incoming) {
|
|
699
|
+
return () => ({ ...existing(), ...incoming() });
|
|
700
|
+
}
|
|
701
|
+
function registerState(state2, states, actions, events) {
|
|
702
|
+
const existing = states.get(state2.name);
|
|
703
|
+
if (existing) {
|
|
704
|
+
mergeIntoExisting(state2, existing, states, actions, events);
|
|
705
|
+
} else {
|
|
706
|
+
registerNewState(state2, states, actions, events);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function registerNewState(state2, states, actions, events) {
|
|
710
|
+
states.set(state2.name, state2);
|
|
711
|
+
for (const name of Object.keys(state2.actions)) {
|
|
712
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
713
|
+
actions[name] = state2;
|
|
714
|
+
}
|
|
715
|
+
for (const name of Object.keys(state2.events)) {
|
|
716
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
717
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
function mergeIntoExisting(state2, existing, states, actions, events) {
|
|
721
|
+
for (const name of Object.keys(state2.actions)) {
|
|
722
|
+
if (existing.actions[name] === state2.actions[name]) continue;
|
|
723
|
+
if (actions[name]) throw new Error(`Duplicate action "${name}"`);
|
|
724
|
+
}
|
|
725
|
+
for (const name of Object.keys(state2.events)) {
|
|
726
|
+
if (existing.events[name] === state2.events[name]) continue;
|
|
727
|
+
if (existing.events[name]) continue;
|
|
728
|
+
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
729
|
+
}
|
|
730
|
+
const mergedPatch = mergePatches(existing.patch, state2.patch, state2.name);
|
|
731
|
+
const merged = {
|
|
732
|
+
...existing,
|
|
733
|
+
state: mergeSchemas(existing.state, state2.state, state2.name),
|
|
734
|
+
init: mergeInits(existing.init, state2.init),
|
|
735
|
+
events: { ...existing.events, ...state2.events },
|
|
736
|
+
actions: { ...existing.actions, ...state2.actions },
|
|
737
|
+
patch: mergedPatch,
|
|
738
|
+
on: { ...existing.on, ...state2.on },
|
|
739
|
+
given: { ...existing.given, ...state2.given },
|
|
740
|
+
snap: state2.snap && existing.snap && state2.snap !== existing.snap ? (() => {
|
|
741
|
+
throw new Error(
|
|
742
|
+
`Duplicate snap strategy for state "${state2.name}"`
|
|
743
|
+
);
|
|
744
|
+
})() : state2.snap || existing.snap
|
|
745
|
+
};
|
|
746
|
+
states.set(state2.name, merged);
|
|
747
|
+
for (const name of Object.keys(merged.actions)) {
|
|
748
|
+
actions[name] = merged;
|
|
749
|
+
}
|
|
750
|
+
for (const name of Object.keys(state2.events)) {
|
|
751
|
+
if (events[name]) continue;
|
|
752
|
+
events[name] = { schema: state2.events[name], reactions: /* @__PURE__ */ new Map() };
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function mergePatches(existing, incoming, stateName) {
|
|
756
|
+
const merged = { ...existing };
|
|
757
|
+
for (const name of Object.keys(incoming)) {
|
|
758
|
+
const existingP = existing[name];
|
|
759
|
+
const incomingP = incoming[name];
|
|
760
|
+
if (!existingP) {
|
|
761
|
+
merged[name] = incomingP;
|
|
762
|
+
continue;
|
|
763
|
+
}
|
|
764
|
+
const existingIsDefault = existingP._passthrough;
|
|
765
|
+
const incomingIsDefault = incomingP._passthrough;
|
|
766
|
+
if (!existingIsDefault && !incomingIsDefault && existingP !== incomingP) {
|
|
767
|
+
throw new Error(
|
|
768
|
+
`Duplicate custom patch for event "${name}" in state "${stateName}"`
|
|
769
|
+
);
|
|
770
|
+
}
|
|
771
|
+
if (existingIsDefault && !incomingIsDefault) {
|
|
772
|
+
merged[name] = incomingP;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
return merged;
|
|
776
|
+
}
|
|
777
|
+
function mergeProjection(proj, events) {
|
|
778
|
+
for (const eventName of Object.keys(proj.events)) {
|
|
779
|
+
const projRegister = proj.events[eventName];
|
|
780
|
+
const existing = events[eventName];
|
|
781
|
+
if (!existing) {
|
|
782
|
+
events[eventName] = {
|
|
783
|
+
schema: projRegister.schema,
|
|
784
|
+
reactions: new Map(projRegister.reactions)
|
|
785
|
+
};
|
|
786
|
+
} else {
|
|
787
|
+
for (const [name, reaction] of projRegister.reactions) {
|
|
788
|
+
let key = name;
|
|
789
|
+
while (existing.reactions.has(key)) key = `${key}_p`;
|
|
790
|
+
existing.reactions.set(key, reaction);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
var _this_ = ({ stream }) => ({
|
|
796
|
+
source: stream,
|
|
797
|
+
target: stream
|
|
798
|
+
});
|
|
799
|
+
|
|
675
800
|
// src/internal/drain.ts
|
|
676
801
|
var claim = (lagging, leading, by, millis) => store().claim(lagging, leading, by, millis);
|
|
677
802
|
async function fetch(leased, eventLimit) {
|
|
@@ -694,7 +819,6 @@ var subscribe = (streams) => store().subscribe(streams);
|
|
|
694
819
|
// src/internal/event-sourcing.ts
|
|
695
820
|
import { patch } from "@rotorsoft/act-patch";
|
|
696
821
|
import { randomUUID } from "crypto";
|
|
697
|
-
var logger2 = log();
|
|
698
822
|
async function snap(snapshot) {
|
|
699
823
|
try {
|
|
700
824
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
@@ -709,11 +833,11 @@ async function snap(snapshot) {
|
|
|
709
833
|
// IMPORTANT! - state events are committed right after the snapshot event
|
|
710
834
|
);
|
|
711
835
|
} catch (error) {
|
|
712
|
-
|
|
836
|
+
log().error(error);
|
|
713
837
|
}
|
|
714
838
|
}
|
|
715
839
|
async function load(me, stream, callback, asOf) {
|
|
716
|
-
const timeTravel = asOf && (asOf.
|
|
840
|
+
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
717
841
|
const cached = timeTravel ? void 0 : await cache().get(stream);
|
|
718
842
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
719
843
|
let patches = cached?.patches ?? 0;
|
|
@@ -729,6 +853,10 @@ async function load(me, stream, callback, asOf) {
|
|
|
729
853
|
} else if (me.patch[e.name]) {
|
|
730
854
|
state2 = patch(state2, me.patch[e.name](event, state2));
|
|
731
855
|
patches++;
|
|
856
|
+
} else if (e.name !== TOMBSTONE_EVENT) {
|
|
857
|
+
log().warn(
|
|
858
|
+
`Skipping unknown event "${String(e.name)}" on stream "${stream}" (id=${e.id}) \u2014 no reducer in state "${me.name}"`
|
|
859
|
+
);
|
|
732
860
|
}
|
|
733
861
|
callback && callback({ event, state: state2, patches, snaps });
|
|
734
862
|
},
|
|
@@ -797,7 +925,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
797
925
|
reactingTo ? void 0 : expected
|
|
798
926
|
);
|
|
799
927
|
} catch (error) {
|
|
800
|
-
if (error
|
|
928
|
+
if (error instanceof ConcurrencyError) {
|
|
801
929
|
await cache().invalidate(stream);
|
|
802
930
|
}
|
|
803
931
|
throw error;
|
|
@@ -811,241 +939,56 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
811
939
|
});
|
|
812
940
|
const last = snapshots.at(-1);
|
|
813
941
|
const snapped = me.snap && me.snap(last);
|
|
814
|
-
|
|
942
|
+
cache().set(stream, {
|
|
815
943
|
state: last.state,
|
|
816
944
|
version: last.event.version,
|
|
817
945
|
event_id: last.event.id,
|
|
818
946
|
patches: snapped ? 0 : last.patches,
|
|
819
947
|
snaps: snapped ? last.snaps + 1 : last.snaps
|
|
820
|
-
});
|
|
948
|
+
}).catch((err) => log().error(err));
|
|
821
949
|
if (snapped) void snap(last);
|
|
822
950
|
return snapshots;
|
|
823
951
|
}
|
|
824
952
|
|
|
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
953
|
// src/internal/tracing.ts
|
|
952
|
-
var
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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") {
|
|
954
|
+
var traced = (inner, exit, entry) => (async (...args) => {
|
|
955
|
+
entry?.(...args);
|
|
956
|
+
const result = await inner(...args);
|
|
957
|
+
exit?.(result, ...args);
|
|
958
|
+
return result;
|
|
959
|
+
});
|
|
960
|
+
function buildEs(logger) {
|
|
961
|
+
if (logger.level !== "trace") {
|
|
984
962
|
return { snap, load, action };
|
|
985
963
|
}
|
|
986
964
|
return {
|
|
987
|
-
snap:
|
|
988
|
-
|
|
989
|
-
|
|
965
|
+
snap: traced(snap, void 0, (snapshot) => {
|
|
966
|
+
logger.trace(
|
|
967
|
+
`\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
|
|
968
|
+
);
|
|
969
|
+
}),
|
|
970
|
+
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
971
|
+
logger.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
|
|
972
|
+
}),
|
|
973
|
+
action: traced(
|
|
974
|
+
action,
|
|
975
|
+
(snapshots, _me, _action, target) => {
|
|
976
|
+
const committed = snapshots.filter((s) => s.event);
|
|
977
|
+
if (committed.length) {
|
|
978
|
+
logger.trace(
|
|
979
|
+
committed.map((s) => s.event.data),
|
|
980
|
+
`\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
},
|
|
984
|
+
(_me, action2, target, payload) => {
|
|
985
|
+
logger.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
|
|
986
|
+
}
|
|
987
|
+
)
|
|
990
988
|
};
|
|
991
989
|
}
|
|
992
|
-
|
|
993
|
-
|
|
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") {
|
|
990
|
+
function buildDrain(logger) {
|
|
991
|
+
if (logger.level !== "trace") {
|
|
1049
992
|
return {
|
|
1050
993
|
claim,
|
|
1051
994
|
fetch,
|
|
@@ -1055,24 +998,62 @@ function buildDrain(level) {
|
|
|
1055
998
|
};
|
|
1056
999
|
}
|
|
1057
1000
|
return {
|
|
1058
|
-
claim:
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1001
|
+
claim: traced(claim, (leased) => {
|
|
1002
|
+
if (leased.length) {
|
|
1003
|
+
const data = Object.fromEntries(
|
|
1004
|
+
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1005
|
+
);
|
|
1006
|
+
logger.trace(data, ">> lease");
|
|
1007
|
+
}
|
|
1008
|
+
}),
|
|
1009
|
+
fetch: traced(fetch, (fetched) => {
|
|
1010
|
+
const data = Object.fromEntries(
|
|
1011
|
+
fetched.map(({ stream, source, events }) => {
|
|
1012
|
+
const key = source ? `${stream}<-${source}` : stream;
|
|
1013
|
+
const value = Object.fromEntries(
|
|
1014
|
+
events.map(({ id, stream: stream2, name }) => [id, { [stream2]: name }])
|
|
1015
|
+
);
|
|
1016
|
+
return [key, value];
|
|
1017
|
+
})
|
|
1018
|
+
);
|
|
1019
|
+
logger.trace(data, ">> fetch");
|
|
1020
|
+
}),
|
|
1021
|
+
ack: traced(ack, (acked) => {
|
|
1022
|
+
if (acked.length) {
|
|
1023
|
+
const data = Object.fromEntries(
|
|
1024
|
+
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1025
|
+
);
|
|
1026
|
+
logger.trace(data, ">> ack");
|
|
1027
|
+
}
|
|
1028
|
+
}),
|
|
1029
|
+
block: traced(block, (blocked) => {
|
|
1030
|
+
if (blocked.length) {
|
|
1031
|
+
const data = Object.fromEntries(
|
|
1032
|
+
blocked.map(({ stream, at, retry, error }) => [
|
|
1033
|
+
stream,
|
|
1034
|
+
{ at, retry, error }
|
|
1035
|
+
])
|
|
1036
|
+
);
|
|
1037
|
+
logger.trace(data, ">> block");
|
|
1038
|
+
}
|
|
1039
|
+
}),
|
|
1040
|
+
subscribe: traced(subscribe, (result, streams) => {
|
|
1041
|
+
if (result.subscribed) {
|
|
1042
|
+
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1043
|
+
logger.trace(`>> correlate ${data}`);
|
|
1044
|
+
}
|
|
1045
|
+
})
|
|
1063
1046
|
};
|
|
1064
1047
|
}
|
|
1065
1048
|
|
|
1066
1049
|
// src/act.ts
|
|
1067
|
-
var logger4 = log();
|
|
1068
1050
|
var Act = class {
|
|
1069
1051
|
constructor(registry, _states = /* @__PURE__ */ new Map(), batchHandlers = /* @__PURE__ */ new Map()) {
|
|
1070
1052
|
this.registry = registry;
|
|
1071
1053
|
this._states = _states;
|
|
1072
1054
|
this._batch_handlers = batchHandlers;
|
|
1073
|
-
|
|
1074
|
-
this.
|
|
1075
|
-
this._cd = buildDrain(level);
|
|
1055
|
+
this._es = buildEs(this._logger);
|
|
1056
|
+
this._cd = buildDrain(this._logger);
|
|
1076
1057
|
const statics = [];
|
|
1077
1058
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
1078
1059
|
if (register.reactions.size > 0) {
|
|
@@ -1090,6 +1071,11 @@ var Act = class {
|
|
|
1090
1071
|
}
|
|
1091
1072
|
}
|
|
1092
1073
|
this._static_targets = statics;
|
|
1074
|
+
for (const merged of this._states.values()) {
|
|
1075
|
+
for (const eventName of Object.keys(merged.events)) {
|
|
1076
|
+
this._event_to_state.set(eventName, merged);
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1093
1079
|
dispose(() => {
|
|
1094
1080
|
this._emitter.removeAllListeners();
|
|
1095
1081
|
this.stop_correlations();
|
|
@@ -1136,6 +1122,15 @@ var Act = class {
|
|
|
1136
1122
|
_es;
|
|
1137
1123
|
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1138
1124
|
_cd;
|
|
1125
|
+
/**
|
|
1126
|
+
* Event-name → owning state, computed at build time. The duplicate-event
|
|
1127
|
+
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
1128
|
+
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
1129
|
+
* set when seeding a `restart` snapshot in multi-state apps.
|
|
1130
|
+
*/
|
|
1131
|
+
_event_to_state = /* @__PURE__ */ new Map();
|
|
1132
|
+
/** Logger resolved at construction time (after user port configuration) */
|
|
1133
|
+
_logger = log();
|
|
1139
1134
|
/**
|
|
1140
1135
|
* Executes an action on a state instance, committing resulting events.
|
|
1141
1136
|
*
|
|
@@ -1357,7 +1352,7 @@ var Act = class {
|
|
|
1357
1352
|
if (payloads.length === 0) return { lease, handled: 0, at: lease.at };
|
|
1358
1353
|
const stream = lease.stream;
|
|
1359
1354
|
let at = payloads.at(0).event.id, handled = 0;
|
|
1360
|
-
lease.retry > 0 &&
|
|
1355
|
+
lease.retry > 0 && this._logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1361
1356
|
const doAction = this.do.bind(this);
|
|
1362
1357
|
const scopedApp = {
|
|
1363
1358
|
do: doAction,
|
|
@@ -1379,9 +1374,11 @@ var Act = class {
|
|
|
1379
1374
|
at = event.id;
|
|
1380
1375
|
handled++;
|
|
1381
1376
|
} catch (error) {
|
|
1382
|
-
|
|
1377
|
+
this._logger.error(error);
|
|
1383
1378
|
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1384
|
-
block2 &&
|
|
1379
|
+
block2 && this._logger.error(
|
|
1380
|
+
`Blocking ${stream} after ${lease.retry} retries.`
|
|
1381
|
+
);
|
|
1385
1382
|
return {
|
|
1386
1383
|
lease,
|
|
1387
1384
|
handled,
|
|
@@ -1411,15 +1408,17 @@ var Act = class {
|
|
|
1411
1408
|
const stream = lease.stream;
|
|
1412
1409
|
const events = payloads.map((p) => p.event);
|
|
1413
1410
|
const at = events.at(-1).id;
|
|
1414
|
-
lease.retry > 0 &&
|
|
1411
|
+
lease.retry > 0 && this._logger.warn(
|
|
1412
|
+
`Retrying batch ${stream}@${events[0].id} (${lease.retry}).`
|
|
1413
|
+
);
|
|
1415
1414
|
try {
|
|
1416
1415
|
await batchHandler(events, stream);
|
|
1417
1416
|
return { lease, handled: events.length, at };
|
|
1418
1417
|
} catch (error) {
|
|
1419
|
-
|
|
1418
|
+
this._logger.error(error);
|
|
1420
1419
|
const { options } = payloads[0];
|
|
1421
1420
|
const block2 = lease.retry >= options.maxRetries && options.blockOnError;
|
|
1422
|
-
block2 &&
|
|
1421
|
+
block2 && this._logger.error(`Blocking ${stream} after ${lease.retry} retries.`);
|
|
1423
1422
|
return {
|
|
1424
1423
|
lease,
|
|
1425
1424
|
handled: 0,
|
|
@@ -1545,7 +1544,7 @@ var Act = class {
|
|
|
1545
1544
|
this._needs_drain = false;
|
|
1546
1545
|
return result;
|
|
1547
1546
|
} catch (error) {
|
|
1548
|
-
|
|
1547
|
+
this._logger.error(error);
|
|
1549
1548
|
} finally {
|
|
1550
1549
|
this._drain_locked = false;
|
|
1551
1550
|
}
|
|
@@ -1846,16 +1845,24 @@ var Act = class {
|
|
|
1846
1845
|
streams.map(async (s) => {
|
|
1847
1846
|
let maxId = -1;
|
|
1848
1847
|
let version = -1;
|
|
1848
|
+
let lastEventName;
|
|
1849
1849
|
await store().query(
|
|
1850
1850
|
(e) => {
|
|
1851
|
-
if (e.name
|
|
1851
|
+
if (e.name === TOMBSTONE_EVENT) return;
|
|
1852
|
+
if (maxId === -1) {
|
|
1852
1853
|
maxId = e.id;
|
|
1853
1854
|
version = e.version;
|
|
1854
1855
|
}
|
|
1856
|
+
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
1857
|
+
lastEventName = e.name;
|
|
1858
|
+
}
|
|
1855
1859
|
},
|
|
1856
|
-
|
|
1860
|
+
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
1861
|
+
// always preceded by the domain event it captured). Streams with
|
|
1862
|
+
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
1863
|
+
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
1857
1864
|
);
|
|
1858
|
-
if (maxId >= 0) streamInfo.set(s, { maxId, version });
|
|
1865
|
+
if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
|
|
1859
1866
|
})
|
|
1860
1867
|
);
|
|
1861
1868
|
const skipped = [];
|
|
@@ -1864,16 +1871,14 @@ var Act = class {
|
|
|
1864
1871
|
safe = [...streamInfo.keys()];
|
|
1865
1872
|
} else {
|
|
1866
1873
|
const pendingSet = /* @__PURE__ */ new Set();
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
for (const lease of leases) {
|
|
1870
|
-
const sourceRe = lease.source ? RegExp(lease.source) : void 0;
|
|
1874
|
+
await store().query_streams((position) => {
|
|
1875
|
+
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
1871
1876
|
for (const [stream, info] of streamInfo) {
|
|
1872
|
-
if ((!sourceRe || sourceRe.test(stream)) &&
|
|
1877
|
+
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
1873
1878
|
pendingSet.add(stream);
|
|
1874
1879
|
}
|
|
1875
1880
|
}
|
|
1876
|
-
}
|
|
1881
|
+
});
|
|
1877
1882
|
safe = [];
|
|
1878
1883
|
for (const [stream] of streamInfo) {
|
|
1879
1884
|
if (pendingSet.has(stream)) {
|
|
@@ -1913,16 +1918,21 @@ var Act = class {
|
|
|
1913
1918
|
this.emit("closed", result2);
|
|
1914
1919
|
return result2;
|
|
1915
1920
|
}
|
|
1916
|
-
const mergedState = [...this._states.values()][0];
|
|
1917
1921
|
const seedStates = /* @__PURE__ */ new Map();
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1922
|
+
await Promise.all(
|
|
1923
|
+
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
1924
|
+
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
1925
|
+
const ownerState = lastEventName ? this._event_to_state.get(lastEventName) : void 0;
|
|
1926
|
+
if (!ownerState) {
|
|
1927
|
+
this._logger.error(
|
|
1928
|
+
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
1929
|
+
);
|
|
1930
|
+
return;
|
|
1931
|
+
}
|
|
1932
|
+
const snap2 = await this._es.load(ownerState, stream);
|
|
1933
|
+
seedStates.set(stream, snap2.state);
|
|
1934
|
+
})
|
|
1935
|
+
);
|
|
1926
1936
|
for (const stream of guarded) {
|
|
1927
1937
|
const archiveFn = targetMap.get(stream)?.archive;
|
|
1928
1938
|
if (archiveFn) await archiveFn();
|
|
@@ -2025,7 +2035,7 @@ var Act = class {
|
|
|
2025
2035
|
if (!made_progress) break;
|
|
2026
2036
|
}
|
|
2027
2037
|
if (lastDrain) this.emit("settled", lastDrain);
|
|
2028
|
-
})().catch((err) =>
|
|
2038
|
+
})().catch((err) => this._logger.error(err)).finally(() => {
|
|
2029
2039
|
this._settling = false;
|
|
2030
2040
|
});
|
|
2031
2041
|
}, debounceMs);
|
|
@@ -2033,6 +2043,14 @@ var Act = class {
|
|
|
2033
2043
|
};
|
|
2034
2044
|
|
|
2035
2045
|
// src/act-builder.ts
|
|
2046
|
+
function registerBatchHandler(proj, batchHandlers) {
|
|
2047
|
+
if (!proj.batchHandler || !proj.target) return;
|
|
2048
|
+
const existing = batchHandlers.get(proj.target);
|
|
2049
|
+
if (existing && existing !== proj.batchHandler) {
|
|
2050
|
+
throw new Error(`Duplicate batch handler for target "${proj.target}"`);
|
|
2051
|
+
}
|
|
2052
|
+
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2053
|
+
}
|
|
2036
2054
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
2037
2055
|
actions: {},
|
|
2038
2056
|
events: {}
|
|
@@ -2067,9 +2085,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2067
2085
|
},
|
|
2068
2086
|
withProjection: (proj) => {
|
|
2069
2087
|
mergeProjection(proj, registry.events);
|
|
2070
|
-
|
|
2071
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2072
|
-
}
|
|
2088
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2073
2089
|
return act(
|
|
2074
2090
|
states,
|
|
2075
2091
|
registry,
|
|
@@ -2115,9 +2131,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2115
2131
|
build: () => {
|
|
2116
2132
|
for (const proj of pendingProjections) {
|
|
2117
2133
|
mergeProjection(proj, registry.events);
|
|
2118
|
-
|
|
2119
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2120
|
-
}
|
|
2134
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2121
2135
|
}
|
|
2122
2136
|
return new Act(
|
|
2123
2137
|
registry,
|
|
@@ -2272,8 +2286,9 @@ function state(entry) {
|
|
|
2272
2286
|
emits(events) {
|
|
2273
2287
|
const defaultPatch = Object.fromEntries(
|
|
2274
2288
|
Object.keys(events).map((k) => {
|
|
2275
|
-
const fn = ({ data }) => data
|
|
2276
|
-
|
|
2289
|
+
const fn = Object.assign(({ data }) => data, {
|
|
2290
|
+
_passthrough: true
|
|
2291
|
+
});
|
|
2277
2292
|
return [k, fn];
|
|
2278
2293
|
})
|
|
2279
2294
|
);
|