@rotorsoft/act 0.6.33 → 0.8.0

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/index.js CHANGED
@@ -564,6 +564,9 @@ process.once("unhandledRejection", async (arg) => {
564
564
  await disposeAndExit("ERROR");
565
565
  });
566
566
 
567
+ // src/act-builder.ts
568
+ import { ZodObject as ZodObject2 } from "zod";
569
+
567
570
  // src/act.ts
568
571
  import { randomUUID as randomUUID2 } from "crypto";
569
572
  import EventEmitter from "events";
@@ -689,9 +692,11 @@ var Act = class {
689
692
  * Create a new Act orchestrator.
690
693
  *
691
694
  * @param registry The registry of state, event, and action schemas
695
+ * @param states Map of state names to their (potentially merged) state definitions
692
696
  */
693
- constructor(registry) {
697
+ constructor(registry, _states = /* @__PURE__ */ new Map()) {
694
698
  this.registry = registry;
699
+ this._states = _states;
695
700
  dispose(() => {
696
701
  this._emitter.removeAllListeners();
697
702
  this.stop_correlations();
@@ -809,47 +814,16 @@ var Act = class {
809
814
  this.emit("committed", snapshots);
810
815
  return snapshots;
811
816
  }
812
- /**
813
- * Loads the current state snapshot for a specific stream.
814
- *
815
- * Reconstructs the current state by replaying events from the event store.
816
- * Uses snapshots when available to optimize loading performance.
817
- *
818
- * @template SX - State schema type
819
- * @template EX - Event schemas type
820
- * @template AX - Action schemas type
821
- * @param state - The state definition to load
822
- * @param stream - The stream ID (state instance identifier)
823
- * @param callback - Optional callback invoked with the loaded snapshot
824
- * @returns The current state snapshot for the stream
825
- *
826
- * @example Load current state
827
- * ```typescript
828
- * const snapshot = await app.load(Counter, "counter-1");
829
- * console.log(snapshot.state.count); // Current count
830
- * console.log(snapshot.version); // Number of events applied
831
- * console.log(snapshot.patches); // Events since last snapshot
832
- * ```
833
- *
834
- * @example With callback
835
- * ```typescript
836
- * const snapshot = await app.load(User, "user-123", (snap) => {
837
- * console.log("Loaded user:", snap.state.name);
838
- * });
839
- * ```
840
- *
841
- * @example Load multiple states
842
- * ```typescript
843
- * const [user, account] = await Promise.all([
844
- * app.load(User, "user-123"),
845
- * app.load(BankAccount, "account-456")
846
- * ]);
847
- * ```
848
- *
849
- * @see {@link Snapshot} for snapshot structure
850
- */
851
- async load(state2, stream, callback) {
852
- return await load(state2, stream, callback);
817
+ async load(stateOrName, stream, callback) {
818
+ let merged;
819
+ if (typeof stateOrName === "string") {
820
+ const found = this._states.get(stateOrName);
821
+ if (!found) throw new Error(`State "${stateOrName}" not found`);
822
+ merged = found;
823
+ } else {
824
+ merged = this._states.get(stateOrName.name) || stateOrName;
825
+ }
826
+ return await load(merged, stream, callback);
853
827
  }
854
828
  /**
855
829
  * Queries the event store for events matching a filter.
@@ -1074,7 +1048,8 @@ var Act = class {
1074
1048
  );
1075
1049
  fetched.forEach(({ stream, lagging: lagging2, events }) => {
1076
1050
  const payloads = events.flatMap((event) => {
1077
- const register = this.registry.events[event.name] || [];
1051
+ const register = this.registry.events[event.name];
1052
+ if (!register) return [];
1078
1053
  return [...register.reactions.values()].filter((reaction) => {
1079
1054
  const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
1080
1055
  return resolved && resolved.target === stream;
@@ -1307,18 +1282,49 @@ var Act = class {
1307
1282
  };
1308
1283
 
1309
1284
  // src/act-builder.ts
1285
+ function baseTypeName(zodType) {
1286
+ let t = zodType;
1287
+ while (typeof t.unwrap === "function") {
1288
+ t = t.unwrap();
1289
+ }
1290
+ return t.constructor.name;
1291
+ }
1292
+ function mergeSchemas(existing, incoming, stateName) {
1293
+ if (existing instanceof ZodObject2 && incoming instanceof ZodObject2) {
1294
+ const existingShape = existing.shape;
1295
+ const incomingShape = incoming.shape;
1296
+ for (const key of Object.keys(incomingShape)) {
1297
+ if (key in existingShape) {
1298
+ const existingBase = baseTypeName(existingShape[key]);
1299
+ const incomingBase = baseTypeName(incomingShape[key]);
1300
+ if (existingBase !== incomingBase) {
1301
+ throw new Error(
1302
+ `Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
1303
+ );
1304
+ }
1305
+ }
1306
+ }
1307
+ return existing.extend(incomingShape);
1308
+ }
1309
+ return existing;
1310
+ }
1311
+ function mergeInits(existing, incoming) {
1312
+ return () => ({ ...existing(), ...incoming() });
1313
+ }
1310
1314
  var _this_ = ({ stream }) => ({
1311
1315
  source: stream,
1312
1316
  target: stream
1313
1317
  });
1314
1318
  var _void_ = () => void 0;
1315
- function act(states = /* @__PURE__ */ new Set(), registry = {
1319
+ function act(states = /* @__PURE__ */ new Map(), registry = {
1316
1320
  actions: {},
1317
1321
  events: {}
1318
1322
  }) {
1319
1323
  const builder = {
1320
1324
  /**
1321
- * Adds a state to the builder.
1325
+ * Adds a state to the builder. When a state with the same name is already
1326
+ * registered, merges the new partial's actions, events, patches, and handlers
1327
+ * into the existing state (errors on duplicate action/event names).
1322
1328
  *
1323
1329
  * @template SX The type of state
1324
1330
  * @template EX The type of events
@@ -1327,8 +1333,39 @@ function act(states = /* @__PURE__ */ new Set(), registry = {
1327
1333
  * @returns The builder
1328
1334
  */
1329
1335
  with: (state2) => {
1330
- if (!states.has(state2.name)) {
1331
- states.add(state2.name);
1336
+ if (states.has(state2.name)) {
1337
+ const existing = states.get(state2.name);
1338
+ for (const name of Object.keys(state2.actions)) {
1339
+ if (registry.actions[name])
1340
+ throw new Error(`Duplicate action "${name}"`);
1341
+ }
1342
+ for (const name of Object.keys(state2.events)) {
1343
+ if (registry.events[name])
1344
+ throw new Error(`Duplicate event "${name}"`);
1345
+ }
1346
+ const merged = {
1347
+ ...existing,
1348
+ state: mergeSchemas(existing.state, state2.state, state2.name),
1349
+ init: mergeInits(existing.init, state2.init),
1350
+ events: { ...existing.events, ...state2.events },
1351
+ actions: { ...existing.actions, ...state2.actions },
1352
+ patch: { ...existing.patch, ...state2.patch },
1353
+ on: { ...existing.on, ...state2.on },
1354
+ given: { ...existing.given, ...state2.given },
1355
+ snap: state2.snap || existing.snap
1356
+ };
1357
+ states.set(state2.name, merged);
1358
+ for (const name of Object.keys(merged.actions)) {
1359
+ registry.actions[name] = merged;
1360
+ }
1361
+ for (const name of Object.keys(state2.events)) {
1362
+ registry.events[name] = {
1363
+ schema: state2.events[name],
1364
+ reactions: /* @__PURE__ */ new Map()
1365
+ };
1366
+ }
1367
+ } else {
1368
+ states.set(state2.name, state2);
1332
1369
  for (const name of Object.keys(state2.actions)) {
1333
1370
  if (registry.actions[name])
1334
1371
  throw new Error(`Duplicate action "${name}"`);
@@ -1385,7 +1422,7 @@ function act(states = /* @__PURE__ */ new Set(), registry = {
1385
1422
  };
1386
1423
  }
1387
1424
  }),
1388
- build: () => new Act(registry),
1425
+ build: () => new Act(registry, states),
1389
1426
  events: registry.events
1390
1427
  };
1391
1428
  return builder;