@rotorsoft/act 0.7.0 → 0.9.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
@@ -804,54 +804,21 @@ var Act = class {
804
804
  action2,
805
805
  target,
806
806
  payload,
807
- // @ts-expect-error type lost
808
807
  reactingTo,
809
808
  skipValidation
810
809
  );
811
810
  this.emit("committed", snapshots);
812
811
  return snapshots;
813
812
  }
814
- /**
815
- * Loads the current state snapshot for a specific stream.
816
- *
817
- * Reconstructs the current state by replaying events from the event store.
818
- * Uses snapshots when available to optimize loading performance.
819
- *
820
- * @template SX - State schema type
821
- * @template EX - Event schemas type
822
- * @template AX - Action schemas type
823
- * @param state - The state definition to load
824
- * @param stream - The stream ID (state instance identifier)
825
- * @param callback - Optional callback invoked with the loaded snapshot
826
- * @returns The current state snapshot for the stream
827
- *
828
- * @example Load current state
829
- * ```typescript
830
- * const snapshot = await app.load(Counter, "counter-1");
831
- * console.log(snapshot.state.count); // Current count
832
- * console.log(snapshot.version); // Number of events applied
833
- * console.log(snapshot.patches); // Events since last snapshot
834
- * ```
835
- *
836
- * @example With callback
837
- * ```typescript
838
- * const snapshot = await app.load(User, "user-123", (snap) => {
839
- * console.log("Loaded user:", snap.state.name);
840
- * });
841
- * ```
842
- *
843
- * @example Load multiple states
844
- * ```typescript
845
- * const [user, account] = await Promise.all([
846
- * app.load(User, "user-123"),
847
- * app.load(BankAccount, "account-456")
848
- * ]);
849
- * ```
850
- *
851
- * @see {@link Snapshot} for snapshot structure
852
- */
853
- async load(state2, stream, callback) {
854
- const merged = this._states.get(state2.name) || state2;
813
+ async load(stateOrName, stream, callback) {
814
+ let merged;
815
+ if (typeof stateOrName === "string") {
816
+ const found = this._states.get(stateOrName);
817
+ if (!found) throw new Error(`State "${stateOrName}" not found`);
818
+ merged = found;
819
+ } else {
820
+ merged = this._states.get(stateOrName.name) || stateOrName;
821
+ }
855
822
  return await load(merged, stream, callback);
856
823
  }
857
824
  /**
@@ -964,7 +931,7 @@ var Act = class {
964
931
  for (const payload of payloads) {
965
932
  const { event, handler, options } = payload;
966
933
  try {
967
- await handler(event, stream);
934
+ await handler(event, stream, this);
968
935
  at = event.id;
969
936
  handled++;
970
937
  } catch (error) {
@@ -1077,7 +1044,8 @@ var Act = class {
1077
1044
  );
1078
1045
  fetched.forEach(({ stream, lagging: lagging2, events }) => {
1079
1046
  const payloads = events.flatMap((event) => {
1080
- const register = this.registry.events[event.name] || [];
1047
+ const register = this.registry.events[event.name];
1048
+ if (!register) return [];
1081
1049
  return [...register.reactions.values()].filter((reaction) => {
1082
1050
  const resolved = typeof reaction.resolver === "function" ? reaction.resolver(event) : reaction.resolver;
1083
1051
  return resolved && resolved.target === stream;
@@ -1092,7 +1060,6 @@ var Act = class {
1092
1060
  retry: 0,
1093
1061
  lagging: lagging2
1094
1062
  },
1095
- // @ts-expect-error indexed by key
1096
1063
  payloads
1097
1064
  });
1098
1065
  });
@@ -1309,79 +1276,164 @@ var Act = class {
1309
1276
  }
1310
1277
  };
1311
1278
 
1312
- // src/act-builder.ts
1279
+ // src/merge.ts
1280
+ import { ZodObject as ZodObject2 } from "zod";
1281
+ function baseTypeName(zodType) {
1282
+ let t = zodType;
1283
+ while (typeof t.unwrap === "function") {
1284
+ t = t.unwrap();
1285
+ }
1286
+ return t.constructor.name;
1287
+ }
1288
+ function mergeSchemas(existing, incoming, stateName) {
1289
+ if (existing instanceof ZodObject2 && incoming instanceof ZodObject2) {
1290
+ const existingShape = existing.shape;
1291
+ const incomingShape = incoming.shape;
1292
+ for (const key of Object.keys(incomingShape)) {
1293
+ if (key in existingShape) {
1294
+ const existingBase = baseTypeName(existingShape[key]);
1295
+ const incomingBase = baseTypeName(incomingShape[key]);
1296
+ if (existingBase !== incomingBase) {
1297
+ throw new Error(
1298
+ `Schema conflict in "${stateName}": key "${key}" has type "${existingBase}" but incoming partial declares "${incomingBase}"`
1299
+ );
1300
+ }
1301
+ }
1302
+ }
1303
+ return existing.extend(incomingShape);
1304
+ }
1305
+ return existing;
1306
+ }
1307
+ function mergeInits(existing, incoming) {
1308
+ return () => ({ ...existing(), ...incoming() });
1309
+ }
1310
+ function registerState(state2, states, actions, events) {
1311
+ if (states.has(state2.name)) {
1312
+ const existing = states.get(state2.name);
1313
+ for (const name of Object.keys(state2.actions)) {
1314
+ if (existing.actions[name] === state2.actions[name]) continue;
1315
+ if (actions[name]) throw new Error(`Duplicate action "${name}"`);
1316
+ }
1317
+ for (const name of Object.keys(state2.events)) {
1318
+ if (existing.events[name] === state2.events[name]) continue;
1319
+ if (events[name]) throw new Error(`Duplicate event "${name}"`);
1320
+ }
1321
+ const merged = {
1322
+ ...existing,
1323
+ state: mergeSchemas(existing.state, state2.state, state2.name),
1324
+ init: mergeInits(existing.init, state2.init),
1325
+ events: { ...existing.events, ...state2.events },
1326
+ actions: { ...existing.actions, ...state2.actions },
1327
+ patch: { ...existing.patch, ...state2.patch },
1328
+ on: { ...existing.on, ...state2.on },
1329
+ given: { ...existing.given, ...state2.given },
1330
+ snap: state2.snap || existing.snap
1331
+ };
1332
+ states.set(state2.name, merged);
1333
+ for (const name of Object.keys(merged.actions)) {
1334
+ actions[name] = merged;
1335
+ }
1336
+ for (const name of Object.keys(state2.events)) {
1337
+ if (events[name]) continue;
1338
+ events[name] = {
1339
+ schema: state2.events[name],
1340
+ reactions: /* @__PURE__ */ new Map()
1341
+ };
1342
+ }
1343
+ } else {
1344
+ states.set(state2.name, state2);
1345
+ for (const name of Object.keys(state2.actions)) {
1346
+ if (actions[name]) throw new Error(`Duplicate action "${name}"`);
1347
+ actions[name] = state2;
1348
+ }
1349
+ for (const name of Object.keys(state2.events)) {
1350
+ if (events[name]) throw new Error(`Duplicate event "${name}"`);
1351
+ events[name] = {
1352
+ schema: state2.events[name],
1353
+ reactions: /* @__PURE__ */ new Map()
1354
+ };
1355
+ }
1356
+ }
1357
+ }
1313
1358
  var _this_ = ({ stream }) => ({
1314
1359
  source: stream,
1315
1360
  target: stream
1316
1361
  });
1317
1362
  var _void_ = () => void 0;
1363
+
1364
+ // src/slice-builder.ts
1365
+ function isSlice(x) {
1366
+ return x != null && x._tag === "Slice";
1367
+ }
1368
+ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}) {
1369
+ const builder = {
1370
+ with: (state2) => {
1371
+ registerState(state2, states, actions, events);
1372
+ return slice(states, actions, events);
1373
+ },
1374
+ on: (event) => ({
1375
+ do: (handler, options) => {
1376
+ const reaction = {
1377
+ handler,
1378
+ resolver: _this_,
1379
+ options: {
1380
+ blockOnError: options?.blockOnError ?? true,
1381
+ maxRetries: options?.maxRetries ?? 3
1382
+ }
1383
+ };
1384
+ const name = handler.name || `${String(event)}_${events[event].reactions.size}`;
1385
+ events[event].reactions.set(name, reaction);
1386
+ return {
1387
+ ...builder,
1388
+ to(resolver) {
1389
+ events[event].reactions.set(name, {
1390
+ ...reaction,
1391
+ resolver: typeof resolver === "string" ? { target: resolver } : resolver
1392
+ });
1393
+ return builder;
1394
+ },
1395
+ void() {
1396
+ events[event].reactions.set(name, {
1397
+ ...reaction,
1398
+ resolver: _void_
1399
+ });
1400
+ return builder;
1401
+ }
1402
+ };
1403
+ }
1404
+ }),
1405
+ build: () => ({
1406
+ _tag: "Slice",
1407
+ states,
1408
+ events
1409
+ }),
1410
+ events
1411
+ };
1412
+ return builder;
1413
+ }
1414
+
1415
+ // src/act-builder.ts
1318
1416
  function act(states = /* @__PURE__ */ new Map(), registry = {
1319
1417
  actions: {},
1320
1418
  events: {}
1321
1419
  }) {
1322
1420
  const builder = {
1323
- /**
1324
- * Adds a state to the builder. When a state with the same name is already
1325
- * registered, merges the new partial's actions, events, patches, and handlers
1326
- * into the existing state (errors on duplicate action/event names).
1327
- *
1328
- * @template SX The type of state
1329
- * @template EX The type of events
1330
- * @template AX The type of actions
1331
- * @param state The state to add
1332
- * @returns The builder
1333
- */
1334
- with: (state2) => {
1335
- if (states.has(state2.name)) {
1336
- const existing = states.get(state2.name);
1337
- for (const name of Object.keys(state2.actions)) {
1338
- if (registry.actions[name])
1339
- throw new Error(`Duplicate action "${name}"`);
1421
+ with: ((input) => {
1422
+ if (isSlice(input)) {
1423
+ for (const s of input.states.values()) {
1424
+ registerState(s, states, registry.actions, registry.events);
1340
1425
  }
1341
- for (const name of Object.keys(state2.events)) {
1342
- if (registry.events[name])
1343
- throw new Error(`Duplicate event "${name}"`);
1344
- }
1345
- const merged = {
1346
- ...existing,
1347
- events: { ...existing.events, ...state2.events },
1348
- actions: { ...existing.actions, ...state2.actions },
1349
- patch: { ...existing.patch, ...state2.patch },
1350
- on: { ...existing.on, ...state2.on },
1351
- given: { ...existing.given, ...state2.given },
1352
- snap: state2.snap || existing.snap
1353
- };
1354
- states.set(state2.name, merged);
1355
- for (const name of Object.keys(merged.actions)) {
1356
- registry.actions[name] = merged;
1357
- }
1358
- for (const name of Object.keys(state2.events)) {
1359
- registry.events[name] = {
1360
- schema: state2.events[name],
1361
- reactions: /* @__PURE__ */ new Map()
1362
- };
1363
- }
1364
- } else {
1365
- states.set(state2.name, state2);
1366
- for (const name of Object.keys(state2.actions)) {
1367
- if (registry.actions[name])
1368
- throw new Error(`Duplicate action "${name}"`);
1369
- registry.actions[name] = state2;
1370
- }
1371
- for (const name of Object.keys(state2.events)) {
1372
- if (registry.events[name])
1373
- throw new Error(`Duplicate event "${name}"`);
1374
- registry.events[name] = {
1375
- schema: state2.events[name],
1376
- reactions: /* @__PURE__ */ new Map()
1377
- };
1426
+ for (const eventName of Object.keys(input.events)) {
1427
+ const sliceRegister = input.events[eventName];
1428
+ for (const [name, reaction] of sliceRegister.reactions) {
1429
+ registry.events[eventName].reactions.set(name, reaction);
1430
+ }
1378
1431
  }
1432
+ return act(states, registry);
1379
1433
  }
1380
- return act(
1381
- states,
1382
- registry
1383
- );
1384
- },
1434
+ registerState(input, states, registry.actions, registry.events);
1435
+ return act(states, registry);
1436
+ }),
1385
1437
  /**
1386
1438
  * Adds a reaction to an event.
1387
1439
  *
@@ -1399,18 +1451,19 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1399
1451
  maxRetries: options?.maxRetries ?? 3
1400
1452
  }
1401
1453
  };
1402
- registry.events[event].reactions.set(handler.name, reaction);
1454
+ const name = handler.name || `${String(event)}_${registry.events[event].reactions.size}`;
1455
+ registry.events[event].reactions.set(name, reaction);
1403
1456
  return {
1404
1457
  ...builder,
1405
1458
  to(resolver) {
1406
- registry.events[event].reactions.set(handler.name, {
1459
+ registry.events[event].reactions.set(name, {
1407
1460
  ...reaction,
1408
1461
  resolver: typeof resolver === "string" ? { target: resolver } : resolver
1409
1462
  });
1410
1463
  return builder;
1411
1464
  },
1412
1465
  void() {
1413
- registry.events[event].reactions.set(handler.name, {
1466
+ registry.events[event].reactions.set(name, {
1414
1467
  ...reaction,
1415
1468
  resolver: _void_
1416
1469
  });
@@ -1504,10 +1557,12 @@ export {
1504
1557
  dispose,
1505
1558
  disposeAndExit,
1506
1559
  extend,
1560
+ isSlice,
1507
1561
  logger,
1508
1562
  patch,
1509
1563
  port,
1510
1564
  sleep,
1565
+ slice,
1511
1566
  state,
1512
1567
  store,
1513
1568
  validate