@rotorsoft/act 0.32.2 → 0.32.4
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 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/config.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts +9 -2
- package/dist/@types/internal/tracing.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 +143 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +144 -80
- 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";
|
|
@@ -179,12 +179,19 @@ var BaseSchema = PackageSchema.extend({
|
|
|
179
179
|
});
|
|
180
180
|
var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
|
|
181
181
|
var env = NODE_ENV || "development";
|
|
182
|
-
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "
|
|
182
|
+
var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "fatal" : 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
|
+
var _validated;
|
|
186
187
|
var config = () => {
|
|
187
|
-
|
|
188
|
+
if (!_validated) {
|
|
189
|
+
_validated = extend(
|
|
190
|
+
{ ...pkg, env, logLevel, logSingleLine, sleepMs },
|
|
191
|
+
BaseSchema
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
return _validated;
|
|
188
195
|
};
|
|
189
196
|
|
|
190
197
|
// src/utils.ts
|
|
@@ -192,19 +199,15 @@ var validate = (target, payload, schema) => {
|
|
|
192
199
|
try {
|
|
193
200
|
return schema ? schema.parse(payload) : payload;
|
|
194
201
|
} catch (error) {
|
|
195
|
-
if (error instanceof
|
|
196
|
-
throw new ValidationError(
|
|
197
|
-
target,
|
|
198
|
-
payload,
|
|
199
|
-
prettifyError(error)
|
|
200
|
-
);
|
|
202
|
+
if (error instanceof ZodError) {
|
|
203
|
+
throw new ValidationError(target, payload, prettifyError(error));
|
|
201
204
|
}
|
|
202
205
|
throw new ValidationError(target, payload, error);
|
|
203
206
|
}
|
|
204
207
|
};
|
|
205
208
|
var extend = (source, schema, target) => {
|
|
206
209
|
const value = validate("config", source, schema);
|
|
207
|
-
return
|
|
210
|
+
return { ...target, ...value };
|
|
208
211
|
};
|
|
209
212
|
async function sleep(ms) {
|
|
210
213
|
return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
|
|
@@ -611,7 +614,7 @@ function port(injector) {
|
|
|
611
614
|
if (!adapters.has(injector.name)) {
|
|
612
615
|
const injected = injector(adapter);
|
|
613
616
|
adapters.set(injector.name, injected);
|
|
614
|
-
|
|
617
|
+
log().info(`[act] + ${injector.name}:${injected.constructor.name}`);
|
|
615
618
|
}
|
|
616
619
|
return adapters.get(injector.name);
|
|
617
620
|
};
|
|
@@ -632,13 +635,13 @@ var cache = port(function cache2(adapter) {
|
|
|
632
635
|
var disposers = [];
|
|
633
636
|
async function disposeAndExit(code = "EXIT") {
|
|
634
637
|
if (code === "ERROR" && config().env === "production") return;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
})
|
|
641
|
-
|
|
638
|
+
for (const disposer of [...disposers].reverse()) {
|
|
639
|
+
await disposer();
|
|
640
|
+
}
|
|
641
|
+
for (const adapter of [...adapters.values()].reverse()) {
|
|
642
|
+
await adapter.dispose();
|
|
643
|
+
log().info(`[act] - ${adapter.constructor.name}`);
|
|
644
|
+
}
|
|
642
645
|
adapters.clear();
|
|
643
646
|
config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
|
|
644
647
|
}
|
|
@@ -650,21 +653,20 @@ var SNAP_EVENT = "__snapshot__";
|
|
|
650
653
|
var TOMBSTONE_EVENT = "__tombstone__";
|
|
651
654
|
|
|
652
655
|
// src/signals.ts
|
|
653
|
-
var logger = log();
|
|
654
656
|
process.once("SIGINT", async (arg) => {
|
|
655
|
-
|
|
657
|
+
log().info(arg, "SIGINT");
|
|
656
658
|
await disposeAndExit("EXIT");
|
|
657
659
|
});
|
|
658
660
|
process.once("SIGTERM", async (arg) => {
|
|
659
|
-
|
|
661
|
+
log().info(arg, "SIGTERM");
|
|
660
662
|
await disposeAndExit("EXIT");
|
|
661
663
|
});
|
|
662
664
|
process.once("uncaughtException", async (arg) => {
|
|
663
|
-
|
|
665
|
+
log().error(arg, "Uncaught Exception");
|
|
664
666
|
await disposeAndExit("ERROR");
|
|
665
667
|
});
|
|
666
668
|
process.once("unhandledRejection", async (arg) => {
|
|
667
|
-
|
|
669
|
+
log().error(arg, "Unhandled Rejection");
|
|
668
670
|
await disposeAndExit("ERROR");
|
|
669
671
|
});
|
|
670
672
|
|
|
@@ -858,6 +860,10 @@ async function load(me, stream, callback, asOf) {
|
|
|
858
860
|
} else if (me.patch[e.name]) {
|
|
859
861
|
state2 = patch(state2, me.patch[e.name](event, state2));
|
|
860
862
|
patches++;
|
|
863
|
+
} else if (e.name !== TOMBSTONE_EVENT) {
|
|
864
|
+
log().warn(
|
|
865
|
+
`Skipping unknown event "${String(e.name)}" on stream "${stream}" (id=${e.id}) \u2014 no reducer in state "${me.name}"`
|
|
866
|
+
);
|
|
861
867
|
}
|
|
862
868
|
callback && callback({ event, state: state2, patches, snaps });
|
|
863
869
|
},
|
|
@@ -952,44 +958,69 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
952
958
|
}
|
|
953
959
|
|
|
954
960
|
// src/internal/tracing.ts
|
|
961
|
+
var PRETTY = config().env !== "production";
|
|
962
|
+
var C_BLUE = "\x1B[38;5;39m";
|
|
963
|
+
var C_ORANGE = "\x1B[38;5;208m";
|
|
964
|
+
var C_GREEN = "\x1B[38;5;42m";
|
|
965
|
+
var C_MAGENTA = "\x1B[38;5;165m";
|
|
966
|
+
var C_DRAIN = "\x1B[38;5;244m";
|
|
967
|
+
var C_RESET = "\x1B[0m";
|
|
968
|
+
var es_caption = (caption, color, body) => PRETTY ? `${color}${body}${C_RESET}` : `${caption}: ${body}`;
|
|
969
|
+
var drain_caption = (caption) => {
|
|
970
|
+
const tag = `>> ${caption}`;
|
|
971
|
+
return PRETTY ? `${C_DRAIN}${tag}${C_RESET}` : tag;
|
|
972
|
+
};
|
|
955
973
|
var traced = (inner, exit, entry) => (async (...args) => {
|
|
956
974
|
entry?.(...args);
|
|
957
975
|
const result = await inner(...args);
|
|
958
976
|
exit?.(result, ...args);
|
|
959
977
|
return result;
|
|
960
978
|
});
|
|
961
|
-
function buildEs(
|
|
962
|
-
if (
|
|
979
|
+
function buildEs(logger) {
|
|
980
|
+
if (logger.level !== "trace") {
|
|
963
981
|
return { snap, load, action };
|
|
964
982
|
}
|
|
965
983
|
return {
|
|
966
984
|
snap: traced(snap, void 0, (snapshot) => {
|
|
967
|
-
|
|
968
|
-
|
|
985
|
+
logger.trace(
|
|
986
|
+
es_caption(
|
|
987
|
+
"snap",
|
|
988
|
+
C_MAGENTA,
|
|
989
|
+
`${snapshot.event.stream}@${snapshot.event.version}`
|
|
990
|
+
)
|
|
969
991
|
);
|
|
970
992
|
}),
|
|
971
993
|
load: traced(load, void 0, (_me, stream, _cb, asOf) => {
|
|
972
|
-
|
|
994
|
+
logger.trace(
|
|
995
|
+
es_caption("load", C_GREEN, `${stream}${asOf ? " (as-of)" : ""}`)
|
|
996
|
+
);
|
|
973
997
|
}),
|
|
974
998
|
action: traced(
|
|
975
999
|
action,
|
|
976
1000
|
(snapshots, _me, _action, target) => {
|
|
977
1001
|
const committed = snapshots.filter((s) => s.event);
|
|
978
1002
|
if (committed.length) {
|
|
979
|
-
|
|
1003
|
+
logger.trace(
|
|
980
1004
|
committed.map((s) => s.event.data),
|
|
981
|
-
|
|
1005
|
+
es_caption(
|
|
1006
|
+
"committed",
|
|
1007
|
+
C_ORANGE,
|
|
1008
|
+
`${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
|
|
1009
|
+
)
|
|
982
1010
|
);
|
|
983
1011
|
}
|
|
984
1012
|
},
|
|
985
1013
|
(_me, action2, target, payload) => {
|
|
986
|
-
|
|
1014
|
+
logger.trace(
|
|
1015
|
+
payload,
|
|
1016
|
+
es_caption("action", C_BLUE, `${target.stream}.${action2}`)
|
|
1017
|
+
);
|
|
987
1018
|
}
|
|
988
1019
|
)
|
|
989
1020
|
};
|
|
990
1021
|
}
|
|
991
|
-
function buildDrain(
|
|
992
|
-
if (
|
|
1022
|
+
function buildDrain(logger) {
|
|
1023
|
+
if (logger.level !== "trace") {
|
|
993
1024
|
return {
|
|
994
1025
|
claim,
|
|
995
1026
|
fetch,
|
|
@@ -1004,7 +1035,7 @@ function buildDrain(logger2) {
|
|
|
1004
1035
|
const data = Object.fromEntries(
|
|
1005
1036
|
leased.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1006
1037
|
);
|
|
1007
|
-
|
|
1038
|
+
logger.trace(data, drain_caption("claimed"));
|
|
1008
1039
|
}
|
|
1009
1040
|
}),
|
|
1010
1041
|
fetch: traced(fetch, (fetched) => {
|
|
@@ -1017,14 +1048,14 @@ function buildDrain(logger2) {
|
|
|
1017
1048
|
return [key, value];
|
|
1018
1049
|
})
|
|
1019
1050
|
);
|
|
1020
|
-
|
|
1051
|
+
logger.trace(data, drain_caption("fetched"));
|
|
1021
1052
|
}),
|
|
1022
1053
|
ack: traced(ack, (acked) => {
|
|
1023
1054
|
if (acked.length) {
|
|
1024
1055
|
const data = Object.fromEntries(
|
|
1025
1056
|
acked.map(({ stream, at, retry }) => [stream, { at, retry }])
|
|
1026
1057
|
);
|
|
1027
|
-
|
|
1058
|
+
logger.trace(data, drain_caption("acked"));
|
|
1028
1059
|
}
|
|
1029
1060
|
}),
|
|
1030
1061
|
block: traced(block, (blocked) => {
|
|
@@ -1035,13 +1066,13 @@ function buildDrain(logger2) {
|
|
|
1035
1066
|
{ at, retry, error }
|
|
1036
1067
|
])
|
|
1037
1068
|
);
|
|
1038
|
-
|
|
1069
|
+
logger.trace(data, drain_caption("blocked"));
|
|
1039
1070
|
}
|
|
1040
1071
|
}),
|
|
1041
1072
|
subscribe: traced(subscribe, (result, streams) => {
|
|
1042
1073
|
if (result.subscribed) {
|
|
1043
1074
|
const data = streams.map(({ stream }) => stream).join(" ");
|
|
1044
|
-
|
|
1075
|
+
logger.trace(`${drain_caption("correlated")} ${data}`);
|
|
1045
1076
|
}
|
|
1046
1077
|
})
|
|
1047
1078
|
};
|
|
@@ -1055,7 +1086,7 @@ var Act = class {
|
|
|
1055
1086
|
this._batch_handlers = batchHandlers;
|
|
1056
1087
|
this._es = buildEs(this._logger);
|
|
1057
1088
|
this._cd = buildDrain(this._logger);
|
|
1058
|
-
const statics =
|
|
1089
|
+
const statics = /* @__PURE__ */ new Map();
|
|
1059
1090
|
for (const [name, register] of Object.entries(this.registry.events)) {
|
|
1060
1091
|
if (register.reactions.size > 0) {
|
|
1061
1092
|
this._reactive_events.add(name);
|
|
@@ -1064,14 +1095,18 @@ var Act = class {
|
|
|
1064
1095
|
if (typeof reaction.resolver === "function") {
|
|
1065
1096
|
this._has_dynamic_resolvers = true;
|
|
1066
1097
|
} else {
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
});
|
|
1098
|
+
const { target, source } = reaction.resolver;
|
|
1099
|
+
const key = `${target}|${source ?? ""}`;
|
|
1100
|
+
if (!statics.has(key)) statics.set(key, { stream: target, source });
|
|
1071
1101
|
}
|
|
1072
1102
|
}
|
|
1073
1103
|
}
|
|
1074
|
-
this._static_targets = statics;
|
|
1104
|
+
this._static_targets = [...statics.values()];
|
|
1105
|
+
for (const merged of this._states.values()) {
|
|
1106
|
+
for (const eventName of Object.keys(merged.events)) {
|
|
1107
|
+
this._event_to_state.set(eventName, merged);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1075
1110
|
dispose(() => {
|
|
1076
1111
|
this._emitter.removeAllListeners();
|
|
1077
1112
|
this.stop_correlations();
|
|
@@ -1118,8 +1153,21 @@ var Act = class {
|
|
|
1118
1153
|
_es;
|
|
1119
1154
|
/** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
|
|
1120
1155
|
_cd;
|
|
1156
|
+
/**
|
|
1157
|
+
* Event-name → owning state, computed at build time. The duplicate-event
|
|
1158
|
+
* guard in merge.ts ensures one event name maps to at most one state, so
|
|
1159
|
+
* this lookup is unambiguous. Used by `close()` to pick the right reducer
|
|
1160
|
+
* set when seeding a `restart` snapshot in multi-state apps.
|
|
1161
|
+
*/
|
|
1162
|
+
_event_to_state = /* @__PURE__ */ new Map();
|
|
1121
1163
|
/** Logger resolved at construction time (after user port configuration) */
|
|
1122
1164
|
_logger = log();
|
|
1165
|
+
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
1166
|
+
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
1167
|
+
_bound_do = this.do.bind(this);
|
|
1168
|
+
_bound_load = this.load.bind(this);
|
|
1169
|
+
_bound_query = this.query.bind(this);
|
|
1170
|
+
_bound_query_array = this.query_array.bind(this);
|
|
1123
1171
|
/**
|
|
1124
1172
|
* Executes an action on a state instance, committing resulting events.
|
|
1125
1173
|
*
|
|
@@ -1342,12 +1390,12 @@ var Act = class {
|
|
|
1342
1390
|
const stream = lease.stream;
|
|
1343
1391
|
let at = payloads.at(0).event.id, handled = 0;
|
|
1344
1392
|
lease.retry > 0 && this._logger.warn(`Retrying ${stream}@${at} (${lease.retry}).`);
|
|
1345
|
-
const doAction = this.
|
|
1393
|
+
const doAction = this._bound_do;
|
|
1346
1394
|
const scopedApp = {
|
|
1347
1395
|
do: doAction,
|
|
1348
|
-
load: this.
|
|
1349
|
-
query: this.
|
|
1350
|
-
query_array: this.
|
|
1396
|
+
load: this._bound_load,
|
|
1397
|
+
query: this._bound_query,
|
|
1398
|
+
query_array: this._bound_query_array
|
|
1351
1399
|
};
|
|
1352
1400
|
for (const payload of payloads) {
|
|
1353
1401
|
const { event, handler, options } = payload;
|
|
@@ -1480,12 +1528,13 @@ var Act = class {
|
|
|
1480
1528
|
return { fetched: [], leased: [], acked: [], blocked: [] };
|
|
1481
1529
|
}
|
|
1482
1530
|
const fetched = await this._cd.fetch(leased, eventLimit);
|
|
1483
|
-
const
|
|
1531
|
+
const fetchMap = /* @__PURE__ */ new Map();
|
|
1484
1532
|
const fetch_window_at = fetched.reduce(
|
|
1485
1533
|
(max, { at, events }) => Math.max(max, events.at(-1)?.id || at),
|
|
1486
1534
|
0
|
|
1487
1535
|
);
|
|
1488
|
-
|
|
1536
|
+
for (const f of fetched) {
|
|
1537
|
+
const { stream, events } = f;
|
|
1489
1538
|
const payloads = events.flatMap((event) => {
|
|
1490
1539
|
const register = this.registry.events[event.name];
|
|
1491
1540
|
if (!register) return [];
|
|
@@ -1494,13 +1543,13 @@ var Act = class {
|
|
|
1494
1543
|
return resolved && resolved.target === stream;
|
|
1495
1544
|
}).map((reaction) => ({ ...reaction, event }));
|
|
1496
1545
|
});
|
|
1497
|
-
|
|
1498
|
-
}
|
|
1546
|
+
fetchMap.set(stream, { fetch: f, payloads });
|
|
1547
|
+
}
|
|
1499
1548
|
const handled = await Promise.all(
|
|
1500
1549
|
leased.map((lease) => {
|
|
1501
|
-
const
|
|
1502
|
-
const at =
|
|
1503
|
-
const payloads =
|
|
1550
|
+
const entry = fetchMap.get(lease.stream);
|
|
1551
|
+
const at = entry?.fetch.events.at(-1)?.id || fetch_window_at;
|
|
1552
|
+
const payloads = entry?.payloads ?? [];
|
|
1504
1553
|
const batchHandler = this._batch_handlers.get(lease.stream);
|
|
1505
1554
|
if (batchHandler && payloads.length > 0) {
|
|
1506
1555
|
return this.handleBatch({ ...lease, at }, payloads, batchHandler);
|
|
@@ -1834,16 +1883,24 @@ var Act = class {
|
|
|
1834
1883
|
streams.map(async (s) => {
|
|
1835
1884
|
let maxId = -1;
|
|
1836
1885
|
let version = -1;
|
|
1886
|
+
let lastEventName;
|
|
1837
1887
|
await store().query(
|
|
1838
1888
|
(e) => {
|
|
1839
|
-
if (e.name
|
|
1889
|
+
if (e.name === TOMBSTONE_EVENT) return;
|
|
1890
|
+
if (maxId === -1) {
|
|
1840
1891
|
maxId = e.id;
|
|
1841
1892
|
version = e.version;
|
|
1842
1893
|
}
|
|
1894
|
+
if (e.name !== SNAP_EVENT && lastEventName === void 0) {
|
|
1895
|
+
lastEventName = e.name;
|
|
1896
|
+
}
|
|
1843
1897
|
},
|
|
1844
|
-
|
|
1898
|
+
// limit: 2 covers the typical snapshot-at-head case (snapshot is
|
|
1899
|
+
// always preceded by the domain event it captured). Streams with
|
|
1900
|
+
// unusual layouts fall back to no-seed via the lookup miss path.
|
|
1901
|
+
{ stream: s, stream_exact: true, backward: true, limit: 2 }
|
|
1845
1902
|
);
|
|
1846
|
-
if (maxId >= 0) streamInfo.set(s, { maxId, version });
|
|
1903
|
+
if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
|
|
1847
1904
|
})
|
|
1848
1905
|
);
|
|
1849
1906
|
const skipped = [];
|
|
@@ -1852,16 +1909,14 @@ var Act = class {
|
|
|
1852
1909
|
safe = [...streamInfo.keys()];
|
|
1853
1910
|
} else {
|
|
1854
1911
|
const pendingSet = /* @__PURE__ */ new Set();
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
for (const lease of leases) {
|
|
1858
|
-
const sourceRe = lease.source ? RegExp(lease.source) : void 0;
|
|
1912
|
+
await store().query_streams((position) => {
|
|
1913
|
+
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
1859
1914
|
for (const [stream, info] of streamInfo) {
|
|
1860
|
-
if ((!sourceRe || sourceRe.test(stream)) &&
|
|
1915
|
+
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
1861
1916
|
pendingSet.add(stream);
|
|
1862
1917
|
}
|
|
1863
1918
|
}
|
|
1864
|
-
}
|
|
1919
|
+
});
|
|
1865
1920
|
safe = [];
|
|
1866
1921
|
for (const [stream] of streamInfo) {
|
|
1867
1922
|
if (pendingSet.has(stream)) {
|
|
@@ -1901,16 +1956,21 @@ var Act = class {
|
|
|
1901
1956
|
this.emit("closed", result2);
|
|
1902
1957
|
return result2;
|
|
1903
1958
|
}
|
|
1904
|
-
const mergedState = [...this._states.values()][0];
|
|
1905
1959
|
const seedStates = /* @__PURE__ */ new Map();
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1960
|
+
await Promise.all(
|
|
1961
|
+
guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
|
|
1962
|
+
const lastEventName = streamInfo.get(stream)?.lastEventName;
|
|
1963
|
+
const ownerState = lastEventName ? this._event_to_state.get(lastEventName) : void 0;
|
|
1964
|
+
if (!ownerState) {
|
|
1965
|
+
this._logger.error(
|
|
1966
|
+
`Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
|
|
1967
|
+
);
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
const snap2 = await this._es.load(ownerState, stream);
|
|
1971
|
+
seedStates.set(stream, snap2.state);
|
|
1972
|
+
})
|
|
1973
|
+
);
|
|
1914
1974
|
for (const stream of guarded) {
|
|
1915
1975
|
const archiveFn = targetMap.get(stream)?.archive;
|
|
1916
1976
|
if (archiveFn) await archiveFn();
|
|
@@ -2021,6 +2081,14 @@ var Act = class {
|
|
|
2021
2081
|
};
|
|
2022
2082
|
|
|
2023
2083
|
// src/act-builder.ts
|
|
2084
|
+
function registerBatchHandler(proj, batchHandlers) {
|
|
2085
|
+
if (!proj.batchHandler || !proj.target) return;
|
|
2086
|
+
const existing = batchHandlers.get(proj.target);
|
|
2087
|
+
if (existing && existing !== proj.batchHandler) {
|
|
2088
|
+
throw new Error(`Duplicate batch handler for target "${proj.target}"`);
|
|
2089
|
+
}
|
|
2090
|
+
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2091
|
+
}
|
|
2024
2092
|
function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
2025
2093
|
actions: {},
|
|
2026
2094
|
events: {}
|
|
@@ -2055,9 +2123,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2055
2123
|
},
|
|
2056
2124
|
withProjection: (proj) => {
|
|
2057
2125
|
mergeProjection(proj, registry.events);
|
|
2058
|
-
|
|
2059
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2060
|
-
}
|
|
2126
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2061
2127
|
return act(
|
|
2062
2128
|
states,
|
|
2063
2129
|
registry,
|
|
@@ -2103,9 +2169,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
|
|
|
2103
2169
|
build: () => {
|
|
2104
2170
|
for (const proj of pendingProjections) {
|
|
2105
2171
|
mergeProjection(proj, registry.events);
|
|
2106
|
-
|
|
2107
|
-
batchHandlers.set(proj.target, proj.batchHandler);
|
|
2108
|
-
}
|
|
2172
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2109
2173
|
}
|
|
2110
2174
|
return new Act(
|
|
2111
2175
|
registry,
|