@rotorsoft/act 0.32.2 → 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 +7 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/utils.d.ts.map +1 -1
- package/dist/index.cjs +83 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +84 -58
- 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
|
|
|
@@ -858,6 +853,10 @@ async function load(me, stream, callback, asOf) {
|
|
|
858
853
|
} else if (me.patch[e.name]) {
|
|
859
854
|
state2 = patch(state2, me.patch[e.name](event, state2));
|
|
860
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
|
+
);
|
|
861
860
|
}
|
|
862
861
|
callback && callback({ event, state: state2, patches, snaps });
|
|
863
862
|
},
|
|
@@ -958,38 +957,38 @@ var traced = (inner, exit, entry) => (async (...args) => {
|
|
|
958
957
|
exit?.(result, ...args);
|
|
959
958
|
return result;
|
|
960
959
|
});
|
|
961
|
-
function buildEs(
|
|
962
|
-
if (
|
|
960
|
+
function buildEs(logger) {
|
|
961
|
+
if (logger.level !== "trace") {
|
|
963
962
|
return { snap, load, action };
|
|
964
963
|
}
|
|
965
964
|
return {
|
|
966
965
|
snap: traced(snap, void 0, (snapshot) => {
|
|
967
|
-
|
|
966
|
+
logger.trace(
|
|
968
967
|
`\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
|
|
969
968
|
);
|
|
970
969
|
}),
|
|
971
970
|
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
972
|
-
|
|
971
|
+
logger.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
|
|
973
972
|
}),
|
|
974
973
|
action: traced(
|
|
975
974
|
action,
|
|
976
975
|
(snapshots, _me, _action, target) => {
|
|
977
976
|
const committed = snapshots.filter((s) => s.event);
|
|
978
977
|
if (committed.length) {
|
|
979
|
-
|
|
978
|
+
logger.trace(
|
|
980
979
|
committed.map((s) => s.event.data),
|
|
981
980
|
`\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
982
981
|
);
|
|
983
982
|
}
|
|
984
983
|
},
|
|
985
984
|
(_me, action2, target, payload) => {
|
|
986
|
-
|
|
985
|
+
logger.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
|
|
987
986
|
}
|
|
988
987
|
)
|
|
989
988
|
};
|
|
990
989
|
}
|
|
991
|
-
function buildDrain(
|
|
992
|
-
if (
|
|
990
|
+
function buildDrain(logger) {
|
|
991
|
+
if (logger.level !== "trace") {
|
|
993
992
|
return {
|
|
994
993
|
claim,
|
|
995
994
|
fetch,
|
|
@@ -1004,7 +1003,7 @@ function buildDrain(logger2) {
|
|
|
1004
1003
|
const data = Object.fromEntries(
|
|
1005
1004
|
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1006
1005
|
);
|
|
1007
|
-
|
|
1006
|
+
logger.trace(data, ">> lease");
|
|
1008
1007
|
}
|
|
1009
1008
|
}),
|
|
1010
1009
|
fetch: traced(fetch, (fetched) => {
|
|
@@ -1017,14 +1016,14 @@ function buildDrain(logger2) {
|
|
|
1017
1016
|
return [key, value];
|
|
1018
1017
|
})
|
|
1019
1018
|
);
|
|
1020
|
-
|
|
1019
|
+
logger.trace(data, ">> fetch");
|
|
1021
1020
|
}),
|
|
1022
1021
|
ack: traced(ack, (acked) => {
|
|
1023
1022
|
if (acked.length) {
|
|
1024
1023
|
const data = Object.fromEntries(
|
|
1025
1024
|
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1026
1025
|
);
|
|
1027
|
-
|
|
1026
|
+
logger.trace(data, ">> ack");
|
|
1028
1027
|
}
|
|
1029
1028
|
}),
|
|
1030
1029
|
block: traced(block, (blocked) => {
|
|
@@ -1035,13 +1034,13 @@ function buildDrain(logger2) {
|
|
|
1035
1034
|
{ at, retry, error }
|
|
1036
1035
|
])
|
|
1037
1036
|
);
|
|
1038
|
-
|
|
1037
|
+
logger.trace(data, ">> block");
|
|
1039
1038
|
}
|
|
1040
1039
|
}),
|
|
1041
1040
|
subscribe: traced(subscribe, (result, streams) => {
|
|
1042
1041
|
if (result.subscribed) {
|
|
1043
1042
|
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1044
|
-
|
|
1043
|
+
logger.trace(`>> correlate ${data}`);
|
|
1045
1044
|
}
|
|
1046
1045
|
})
|
|
1047
1046
|
};
|
|
@@ -1072,6 +1071,11 @@ var Act = class {
|
|
|
1072
1071
|
}
|
|
1073
1072
|
}
|
|
1074
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
|
+
}
|
|
1075
1079
|
dispose(() => {
|
|
1076
1080
|
this._emitter.removeAllListeners();
|
|
1077
1081
|
this.stop_correlations();
|
|
@@ -1118,6 +1122,13 @@ var Act = class {
|
|
|
1118
1122
|
_es;
|
|
1119
1123
|
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1120
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();
|
|
1121
1132
|
/** Logger resolved at construction time (after user port configuration) */
|
|
1122
1133
|
_logger = log();
|
|
1123
1134
|
/**
|
|
@@ -1834,16 +1845,24 @@ var Act = class {
|
|
|
1834
1845
|
streams.map(async (s) => {
|
|
1835
1846
|
let maxId = -1;
|
|
1836
1847
|
let version = -1;
|
|
1848
|
+
let lastEventName;
|
|
1837
1849
|
await store().query(
|
|
1838
1850
|
(e) => {
|
|
1839
|
-
if (e.name
|
|
1851
|
+
if (e.name === TOMBSTONE_EVENT) return;
|
|
1852
|
+
if (maxId === -1) {
|
|
1840
1853
|
maxId = e.id;
|
|
1841
1854
|
version = e.version;
|
|
1842
1855
|
}
|
|
1856
|
+
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
1857
|
+
lastEventName = e.name;
|
|
1858
|
+
}
|
|
1843
1859
|
},
|
|
1844
|
-
|
|
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 }
|
|
1845
1864
|
);
|
|
1846
|
-
if (maxId >= 0) streamInfo.set(s, { maxId, version });
|
|
1865
|
+
if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
|
|
1847
1866
|
})
|
|
1848
1867
|
);
|
|
1849
1868
|
const skipped = [];
|
|
@@ -1852,16 +1871,14 @@ var Act = class {
|
|
|
1852
1871
|
safe = [...streamInfo.keys()];
|
|
1853
1872
|
} else {
|
|
1854
1873
|
const pendingSet = /* @__PURE__ */ new Set();
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
for (const lease of leases) {
|
|
1858
|
-
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;
|
|
1859
1876
|
for (const [stream, info] of streamInfo) {
|
|
1860
|
-
if ((!sourceRe || sourceRe.test(stream)) &&
|
|
1877
|
+
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
1861
1878
|
pendingSet.add(stream);
|
|
1862
1879
|
}
|
|
1863
1880
|
}
|
|
1864
|
-
}
|
|
1881
|
+
});
|
|
1865
1882
|
safe = [];
|
|
1866
1883
|
for (const [stream] of streamInfo) {
|
|
1867
1884
|
if (pendingSet.has(stream)) {
|
|
@@ -1901,16 +1918,21 @@ var Act = class {
|
|
|
1901
1918
|
this.emit("closed", result2);
|
|
1902
1919
|
return result2;
|
|
1903
1920
|
}
|
|
1904
|
-
const mergedState = [...this._states.values()][0];
|
|
1905
1921
|
const seedStates = /* @__PURE__ */ new Map();
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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
|
+
);
|
|
1914
1936
|
for (const stream of guarded) {
|
|
1915
1937
|
const archiveFn = targetMap.get(stream)?.archive;
|
|
1916
1938
|
if (archiveFn) await archiveFn();
|
|
@@ -2021,6 +2043,14 @@ var Act = class {
|
|
|
2021
2043
|
};
|
|
2022
2044
|
|
|
2023
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
|
+
}
|
|
2024
2054
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
2025
2055
|
actions: {},
|
|
2026
2056
|
events: {}
|
|
@@ -2055,9 +2085,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2055
2085
|
},
|
|
2056
2086
|
withProjection: (proj) => {
|
|
2057
2087
|
mergeProjection(proj, registry.events);
|
|
2058
|
-
|
|
2059
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2060
|
-
}
|
|
2088
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2061
2089
|
return act(
|
|
2062
2090
|
states,
|
|
2063
2091
|
registry,
|
|
@@ -2103,9 +2131,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2103
2131
|
build: () => {
|
|
2104
2132
|
for (const proj of pendingProjections) {
|
|
2105
2133
|
mergeProjection(proj, registry.events);
|
|
2106
|
-
|
|
2107
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2108
|
-
}
|
|
2134
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2109
2135
|
}
|
|
2110
2136
|
return new Act(
|
|
2111
2137
|
registry,
|