@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.
@@ -1 +1 @@
1
- {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/ports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAI5D,OAAO,KAAK,EACV,KAAK,EACL,UAAU,EACV,QAAQ,EACR,MAAM,EACN,KAAK,EACN,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,4BAA6B,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAMlD;;;GAGG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,UAAU,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC;AAKlE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,IAAI,CAAC,IAAI,SAAS,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IACnD,UAAU,IAAI,KAAG,IAAI,CAQvC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,GAAG,0EASd,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,KAAK,wCAEhB,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,KAAK,wCAEhB,CAAC;AASH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,QAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3E;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CACrB,QAAQ,CAAC,EAAE,QAAQ,GAClB,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAGpC;AAMD;;;;GAIG;AACH,eAAO,MAAM,UAAU,iBAAiB,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"ports.d.ts","sourceRoot":"","sources":["../../src/ports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAI5D,OAAO,KAAK,EACV,KAAK,EACL,UAAU,EACV,QAAQ,EACR,MAAM,EACN,KAAK,EACN,MAAM,kBAAkB,CAAC;AAE1B;;;;;;;;;;;;;GAaG;AAEH;;GAEG;AACH,eAAO,MAAM,SAAS,4BAA6B,CAAC;AAEpD;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC;AAMlD;;;GAGG;AACH,KAAK,QAAQ,CAAC,IAAI,SAAS,UAAU,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,KAAK,IAAI,CAAC;AAKlE;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,IAAI,CAAC,IAAI,SAAS,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,IAAI,CAAC,IACnD,UAAU,IAAI,KAAG,IAAI,CAQvC;AAMD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,eAAO,MAAM,GAAG,0EASd,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,KAAK,wCAEhB,CAAC;AAEH;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,KAAK,wCAEhB,CAAC;AASH;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,IAAI,GAAE,QAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3E;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,OAAO,CACrB,QAAQ,CAAC,EAAE,QAAQ,GAClB,CAAC,IAAI,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAGpC;AAMD;;;;GAIG;AACH,eAAO,MAAM,UAAU,iBAAiB,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,kBAAkB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,OAAO,EAAiB,MAAM,KAAK,CAAC;AAIjE;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,QAAQ,MAAM,EACd,SAAS,QAAQ,CAAC,CAAC,CAAC,EACpB,SAAS,OAAO,CAAC,CAAC,CAAC,KAClB,QAAQ,CAAC,CAAC,CAaZ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwHG;AACH,eAAO,MAAM,MAAM,GACjB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,QAAQ,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,OAAO,CAAC,CAAC,CAAC,EAClB,SAAS,QAAQ,CAAC,CAAC,CAAC,KACnB,QAAQ,CAAC,CAAC,GAAG,CAAC,CAGhB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,wBAAsB,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,oBAEtC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAiB,MAAM,KAAK,CAAC;AAI5D;;;;;;;;GAQG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,eAAO,MAAM,QAAQ,GAAI,CAAC,EACxB,QAAQ,MAAM,EACd,SAAS,QAAQ,CAAC,CAAC,CAAC,EACpB,SAAS,OAAO,CAAC,CAAC,CAAC,KAClB,QAAQ,CAAC,CAAC,CASZ,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwHG;AACH,eAAO,MAAM,MAAM,GACjB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAEjC,QAAQ,QAAQ,CAAC,CAAC,CAAC,EACnB,QAAQ,OAAO,CAAC,CAAC,CAAC,EAClB,SAAS,QAAQ,CAAC,CAAC,CAAC,KACnB,QAAQ,CAAC,CAAC,GAAG,CAAC,CAGhB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuFG;AACH,wBAAsB,KAAK,CAAC,EAAE,CAAC,EAAE,MAAM,oBAEtC"}
package/dist/index.cjs CHANGED
@@ -347,7 +347,7 @@ var { NODE_ENV, LOG_LEVEL, LOG_SINGLE_LINE, SLEEP_MS } = process.env;
347
347
  var env = NODE_ENV || "development";
348
348
  var logLevel = LOG_LEVEL || (NODE_ENV === "test" ? "error" : NODE_ENV === "production" ? "info" : "trace");
349
349
  var logSingleLine = (LOG_SINGLE_LINE || "true") === "true";
350
- var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100");
350
+ var sleepMs = parseInt(NODE_ENV === "test" ? "0" : SLEEP_MS ?? "100", 10);
351
351
  var pkg = getPackage();
352
352
  var config = () => {
353
353
  return extend({ ...pkg, env, logLevel, logSingleLine, sleepMs }, BaseSchema);
@@ -358,19 +358,15 @@ var validate = (target, payload, schema) => {
358
358
  try {
359
359
  return schema ? schema.parse(payload) : payload;
360
360
  } catch (error) {
361
- if (error instanceof Error && error.name === "ZodError") {
362
- throw new ValidationError(
363
- target,
364
- payload,
365
- (0, import_zod3.prettifyError)(error)
366
- );
361
+ if (error instanceof import_zod3.ZodError) {
362
+ throw new ValidationError(target, payload, (0, import_zod3.prettifyError)(error));
367
363
  }
368
364
  throw new ValidationError(target, payload, error);
369
365
  }
370
366
  };
371
367
  var extend = (source, schema, target) => {
372
368
  const value = validate("config", source, schema);
373
- return Object.assign(target || {}, value);
369
+ return { ...target, ...value };
374
370
  };
375
371
  async function sleep(ms) {
376
372
  return new Promise((resolve) => setTimeout(resolve, ms ?? config().sleepMs));
@@ -798,13 +794,13 @@ var cache = port(function cache2(adapter) {
798
794
  var disposers = [];
799
795
  async function disposeAndExit(code = "EXIT") {
800
796
  if (code === "ERROR" && config().env === "production") return;
801
- await Promise.all(disposers.map((disposer) => disposer()));
802
- await Promise.all(
803
- [...adapters.values()].reverse().map(async (adapter) => {
804
- await adapter.dispose();
805
- console.log(`[act] - ${adapter.constructor.name}`);
806
- })
807
- );
797
+ for (const disposer of [...disposers].reverse()) {
798
+ await disposer();
799
+ }
800
+ for (const adapter of [...adapters.values()].reverse()) {
801
+ await adapter.dispose();
802
+ console.log(`[act] - ${adapter.constructor.name}`);
803
+ }
808
804
  adapters.clear();
809
805
  config().env !== "test" && process.exit(code === "ERROR" ? 1 : 0);
810
806
  }
@@ -816,21 +812,20 @@ var SNAP_EVENT = "__snapshot__";
816
812
  var TOMBSTONE_EVENT = "__tombstone__";
817
813
 
818
814
  // src/signals.ts
819
- var logger = log();
820
815
  process.once("SIGINT", async (arg) => {
821
- logger.info(arg, "SIGINT");
816
+ log().info(arg, "SIGINT");
822
817
  await disposeAndExit("EXIT");
823
818
  });
824
819
  process.once("SIGTERM", async (arg) => {
825
- logger.info(arg, "SIGTERM");
820
+ log().info(arg, "SIGTERM");
826
821
  await disposeAndExit("EXIT");
827
822
  });
828
823
  process.once("uncaughtException", async (arg) => {
829
- logger.error(arg, "Uncaught Exception");
824
+ log().error(arg, "Uncaught Exception");
830
825
  await disposeAndExit("ERROR");
831
826
  });
832
827
  process.once("unhandledRejection", async (arg) => {
833
- logger.error(arg, "Unhandled Rejection");
828
+ log().error(arg, "Unhandled Rejection");
834
829
  await disposeAndExit("ERROR");
835
830
  });
836
831
 
@@ -1024,6 +1019,10 @@ async function load(me, stream, callback, asOf) {
1024
1019
  } else if (me.patch[e.name]) {
1025
1020
  state2 = (0, import_act_patch.patch)(state2, me.patch[e.name](event, state2));
1026
1021
  patches++;
1022
+ } else if (e.name !== TOMBSTONE_EVENT) {
1023
+ log().warn(
1024
+ `Skipping unknown event "${String(e.name)}" on stream "${stream}" (id=${e.id}) \u2014 no reducer in state "${me.name}"`
1025
+ );
1027
1026
  }
1028
1027
  callback && callback({ event, state: state2, patches, snaps });
1029
1028
  },
@@ -1124,38 +1123,38 @@ var traced = (inner, exit, entry) => (async (...args) => {
1124
1123
  exit?.(result, ...args);
1125
1124
  return result;
1126
1125
  });
1127
- function buildEs(logger2) {
1128
- if (logger2.level !== "trace") {
1126
+ function buildEs(logger) {
1127
+ if (logger.level !== "trace") {
1129
1128
  return { snap, load, action };
1130
1129
  }
1131
1130
  return {
1132
1131
  snap: traced(snap, void 0, (snapshot) => {
1133
- logger2.trace(
1132
+ logger.trace(
1134
1133
  `\u{1F7E0} snap ${snapshot.event.stream}@${snapshot.event.version}`
1135
1134
  );
1136
1135
  }),
1137
1136
  load: traced(load, void 0, (_me, stream, _cb, asOf) => {
1138
- logger2.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
1137
+ logger.trace(`\u{1F7E2} load ${stream}${asOf ? " (as-of)" : ""}`);
1139
1138
  }),
1140
1139
  action: traced(
1141
1140
  action,
1142
1141
  (snapshots, _me, _action, target) => {
1143
1142
  const committed = snapshots.filter((s) => s.event);
1144
1143
  if (committed.length) {
1145
- logger2.trace(
1144
+ logger.trace(
1146
1145
  committed.map((s) => s.event.data),
1147
1146
  `\u{1F534} commit ${target.stream}.${committed.map((s) => s.event.name).join(", ")}`
1148
1147
  );
1149
1148
  }
1150
1149
  },
1151
1150
  (_me, action2, target, payload) => {
1152
- logger2.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
1151
+ logger.trace(payload, `\u{1F535} ${target.stream}.${action2}`);
1153
1152
  }
1154
1153
  )
1155
1154
  };
1156
1155
  }
1157
- function buildDrain(logger2) {
1158
- if (logger2.level !== "trace") {
1156
+ function buildDrain(logger) {
1157
+ if (logger.level !== "trace") {
1159
1158
  return {
1160
1159
  claim,
1161
1160
  fetch,
@@ -1170,7 +1169,7 @@ function buildDrain(logger2) {
1170
1169
  const data = Object.fromEntries(
1171
1170
  leased.map(({ stream, at, retry }) => [stream, { at, retry }])
1172
1171
  );
1173
- logger2.trace(data, ">> lease");
1172
+ logger.trace(data, ">> lease");
1174
1173
  }
1175
1174
  }),
1176
1175
  fetch: traced(fetch, (fetched) => {
@@ -1183,14 +1182,14 @@ function buildDrain(logger2) {
1183
1182
  return [key, value];
1184
1183
  })
1185
1184
  );
1186
- logger2.trace(data, ">> fetch");
1185
+ logger.trace(data, ">> fetch");
1187
1186
  }),
1188
1187
  ack: traced(ack, (acked) => {
1189
1188
  if (acked.length) {
1190
1189
  const data = Object.fromEntries(
1191
1190
  acked.map(({ stream, at, retry }) => [stream, { at, retry }])
1192
1191
  );
1193
- logger2.trace(data, ">> ack");
1192
+ logger.trace(data, ">> ack");
1194
1193
  }
1195
1194
  }),
1196
1195
  block: traced(block, (blocked) => {
@@ -1201,13 +1200,13 @@ function buildDrain(logger2) {
1201
1200
  { at, retry, error }
1202
1201
  ])
1203
1202
  );
1204
- logger2.trace(data, ">> block");
1203
+ logger.trace(data, ">> block");
1205
1204
  }
1206
1205
  }),
1207
1206
  subscribe: traced(subscribe, (result, streams) => {
1208
1207
  if (result.subscribed) {
1209
1208
  const data = streams.map(({ stream }) => stream).join(" ");
1210
- logger2.trace(`>> correlate ${data}`);
1209
+ logger.trace(`>> correlate ${data}`);
1211
1210
  }
1212
1211
  })
1213
1212
  };
@@ -1238,6 +1237,11 @@ var Act = class {
1238
1237
  }
1239
1238
  }
1240
1239
  this._static_targets = statics;
1240
+ for (const merged of this._states.values()) {
1241
+ for (const eventName of Object.keys(merged.events)) {
1242
+ this._event_to_state.set(eventName, merged);
1243
+ }
1244
+ }
1241
1245
  dispose(() => {
1242
1246
  this._emitter.removeAllListeners();
1243
1247
  this.stop_correlations();
@@ -1284,6 +1288,13 @@ var Act = class {
1284
1288
  _es;
1285
1289
  /** Correlate/drain pipeline ops, optionally wrapped with trace decorators */
1286
1290
  _cd;
1291
+ /**
1292
+ * Event-name → owning state, computed at build time. The duplicate-event
1293
+ * guard in merge.ts ensures one event name maps to at most one state, so
1294
+ * this lookup is unambiguous. Used by `close()` to pick the right reducer
1295
+ * set when seeding a `restart` snapshot in multi-state apps.
1296
+ */
1297
+ _event_to_state = /* @__PURE__ */ new Map();
1287
1298
  /** Logger resolved at construction time (after user port configuration) */
1288
1299
  _logger = log();
1289
1300
  /**
@@ -2000,16 +2011,24 @@ var Act = class {
2000
2011
  streams.map(async (s) => {
2001
2012
  let maxId = -1;
2002
2013
  let version = -1;
2014
+ let lastEventName;
2003
2015
  await store().query(
2004
2016
  (e) => {
2005
- if (e.name !== TOMBSTONE_EVENT) {
2017
+ if (e.name === TOMBSTONE_EVENT) return;
2018
+ if (maxId === -1) {
2006
2019
  maxId = e.id;
2007
2020
  version = e.version;
2008
2021
  }
2022
+ if (e.name !== SNAP_EVENT && lastEventName === void 0) {
2023
+ lastEventName = e.name;
2024
+ }
2009
2025
  },
2010
- { stream: s, stream_exact: true, backward: true, limit: 1 }
2026
+ // limit: 2 covers the typical snapshot-at-head case (snapshot is
2027
+ // always preceded by the domain event it captured). Streams with
2028
+ // unusual layouts fall back to no-seed via the lookup miss path.
2029
+ { stream: s, stream_exact: true, backward: true, limit: 2 }
2011
2030
  );
2012
- if (maxId >= 0) streamInfo.set(s, { maxId, version });
2031
+ if (maxId >= 0) streamInfo.set(s, { maxId, version, lastEventName });
2013
2032
  })
2014
2033
  );
2015
2034
  const skipped = [];
@@ -2018,16 +2037,14 @@ var Act = class {
2018
2037
  safe = [...streamInfo.keys()];
2019
2038
  } else {
2020
2039
  const pendingSet = /* @__PURE__ */ new Set();
2021
- const leases = await store().claim(1e3, 1e3, (0, import_crypto2.randomUUID)(), 1);
2022
- if (leases.length) await store().ack(leases);
2023
- for (const lease of leases) {
2024
- const sourceRe = lease.source ? RegExp(lease.source) : void 0;
2040
+ await store().query_streams((position) => {
2041
+ const sourceRe = position.source ? RegExp(position.source) : void 0;
2025
2042
  for (const [stream, info] of streamInfo) {
2026
- if ((!sourceRe || sourceRe.test(stream)) && lease.at < info.maxId) {
2043
+ if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
2027
2044
  pendingSet.add(stream);
2028
2045
  }
2029
2046
  }
2030
- }
2047
+ });
2031
2048
  safe = [];
2032
2049
  for (const [stream] of streamInfo) {
2033
2050
  if (pendingSet.has(stream)) {
@@ -2067,16 +2084,21 @@ var Act = class {
2067
2084
  this.emit("closed", result2);
2068
2085
  return result2;
2069
2086
  }
2070
- const mergedState = [...this._states.values()][0];
2071
2087
  const seedStates = /* @__PURE__ */ new Map();
2072
- if (mergedState) {
2073
- await Promise.all(
2074
- guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
2075
- const snap2 = await this._es.load(mergedState, stream);
2076
- seedStates.set(stream, snap2.state);
2077
- })
2078
- );
2079
- }
2088
+ await Promise.all(
2089
+ guarded.filter((s) => targetMap.get(s)?.restart).map(async (stream) => {
2090
+ const lastEventName = streamInfo.get(stream)?.lastEventName;
2091
+ const ownerState = lastEventName ? this._event_to_state.get(lastEventName) : void 0;
2092
+ if (!ownerState) {
2093
+ this._logger.error(
2094
+ `Cannot seed restart for "${stream}": no registered state owns event "${lastEventName ?? "<none>"}". Stream will be tombstoned instead.`
2095
+ );
2096
+ return;
2097
+ }
2098
+ const snap2 = await this._es.load(ownerState, stream);
2099
+ seedStates.set(stream, snap2.state);
2100
+ })
2101
+ );
2080
2102
  for (const stream of guarded) {
2081
2103
  const archiveFn = targetMap.get(stream)?.archive;
2082
2104
  if (archiveFn) await archiveFn();
@@ -2187,6 +2209,14 @@ var Act = class {
2187
2209
  };
2188
2210
 
2189
2211
  // src/act-builder.ts
2212
+ function registerBatchHandler(proj, batchHandlers) {
2213
+ if (!proj.batchHandler || !proj.target) return;
2214
+ const existing = batchHandlers.get(proj.target);
2215
+ if (existing && existing !== proj.batchHandler) {
2216
+ throw new Error(`Duplicate batch handler for target "${proj.target}"`);
2217
+ }
2218
+ batchHandlers.set(proj.target, proj.batchHandler);
2219
+ }
2190
2220
  function act(states = /* @__PURE__ */ new Map(), registry = {
2191
2221
  actions: {},
2192
2222
  events: {}
@@ -2221,9 +2251,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
2221
2251
  },
2222
2252
  withProjection: (proj) => {
2223
2253
  mergeProjection(proj, registry.events);
2224
- if (proj.batchHandler && proj.target) {
2225
- batchHandlers.set(proj.target, proj.batchHandler);
2226
- }
2254
+ registerBatchHandler(proj, batchHandlers);
2227
2255
  return act(
2228
2256
  states,
2229
2257
  registry,
@@ -2269,9 +2297,7 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
2269
2297
  build: () => {
2270
2298
  for (const proj of pendingProjections) {
2271
2299
  mergeProjection(proj, registry.events);
2272
- if (proj.batchHandler && proj.target) {
2273
- batchHandlers.set(proj.target, proj.batchHandler);
2274
- }
2300
+ registerBatchHandler(proj, batchHandlers);
2275
2301
  }
2276
2302
  return new Act(
2277
2303
  registry,