@rotorsoft/act 0.8.0 → 0.10.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.cjs CHANGED
@@ -53,10 +53,14 @@ __export(index_exports, {
53
53
  dispose: () => dispose,
54
54
  disposeAndExit: () => disposeAndExit,
55
55
  extend: () => extend,
56
+ isProjection: () => isProjection,
57
+ isSlice: () => isSlice,
56
58
  logger: () => logger,
57
59
  patch: () => patch,
58
60
  port: () => port,
61
+ projection: () => projection,
59
62
  sleep: () => sleep,
63
+ slice: () => slice,
60
64
  state: () => state,
61
65
  store: () => store,
62
66
  validate: () => validate
@@ -629,9 +633,6 @@ process.once("unhandledRejection", async (arg) => {
629
633
  await disposeAndExit("ERROR");
630
634
  });
631
635
 
632
- // src/act-builder.ts
633
- var import_zod4 = require("zod");
634
-
635
636
  // src/act.ts
636
637
  var import_crypto2 = require("crypto");
637
638
  var import_events = __toESM(require("events"), 1);
@@ -872,7 +873,6 @@ var Act = class {
872
873
  action2,
873
874
  target,
874
875
  payload,
875
- // @ts-expect-error type lost
876
876
  reactingTo,
877
877
  skipValidation
878
878
  );
@@ -1000,7 +1000,7 @@ var Act = class {
1000
1000
  for (const payload of payloads) {
1001
1001
  const { event, handler, options } = payload;
1002
1002
  try {
1003
- await handler(event, stream);
1003
+ await handler(event, stream, this);
1004
1004
  at = event.id;
1005
1005
  handled++;
1006
1006
  } catch (error) {
@@ -1129,7 +1129,6 @@ var Act = class {
1129
1129
  retry: 0,
1130
1130
  lagging: lagging2
1131
1131
  },
1132
- // @ts-expect-error indexed by key
1133
1132
  payloads
1134
1133
  });
1135
1134
  });
@@ -1346,7 +1345,8 @@ var Act = class {
1346
1345
  }
1347
1346
  };
1348
1347
 
1349
- // src/act-builder.ts
1348
+ // src/merge.ts
1349
+ var import_zod4 = require("zod");
1350
1350
  function baseTypeName(zodType) {
1351
1351
  let t = zodType;
1352
1352
  while (typeof t.unwrap === "function") {
@@ -1376,80 +1376,216 @@ function mergeSchemas(existing, incoming, stateName) {
1376
1376
  function mergeInits(existing, incoming) {
1377
1377
  return () => ({ ...existing(), ...incoming() });
1378
1378
  }
1379
+ function registerState(state2, states, actions, events) {
1380
+ if (states.has(state2.name)) {
1381
+ const existing = states.get(state2.name);
1382
+ for (const name of Object.keys(state2.actions)) {
1383
+ if (existing.actions[name] === state2.actions[name]) continue;
1384
+ if (actions[name]) throw new Error(`Duplicate action "${name}"`);
1385
+ }
1386
+ for (const name of Object.keys(state2.events)) {
1387
+ if (existing.events[name] === state2.events[name]) continue;
1388
+ if (events[name]) throw new Error(`Duplicate event "${name}"`);
1389
+ }
1390
+ const merged = {
1391
+ ...existing,
1392
+ state: mergeSchemas(existing.state, state2.state, state2.name),
1393
+ init: mergeInits(existing.init, state2.init),
1394
+ events: { ...existing.events, ...state2.events },
1395
+ actions: { ...existing.actions, ...state2.actions },
1396
+ patch: { ...existing.patch, ...state2.patch },
1397
+ on: { ...existing.on, ...state2.on },
1398
+ given: { ...existing.given, ...state2.given },
1399
+ snap: state2.snap || existing.snap
1400
+ };
1401
+ states.set(state2.name, merged);
1402
+ for (const name of Object.keys(merged.actions)) {
1403
+ actions[name] = merged;
1404
+ }
1405
+ for (const name of Object.keys(state2.events)) {
1406
+ if (events[name]) continue;
1407
+ events[name] = {
1408
+ schema: state2.events[name],
1409
+ reactions: /* @__PURE__ */ new Map()
1410
+ };
1411
+ }
1412
+ } else {
1413
+ states.set(state2.name, state2);
1414
+ for (const name of Object.keys(state2.actions)) {
1415
+ if (actions[name]) throw new Error(`Duplicate action "${name}"`);
1416
+ actions[name] = state2;
1417
+ }
1418
+ for (const name of Object.keys(state2.events)) {
1419
+ if (events[name]) throw new Error(`Duplicate event "${name}"`);
1420
+ events[name] = {
1421
+ schema: state2.events[name],
1422
+ reactions: /* @__PURE__ */ new Map()
1423
+ };
1424
+ }
1425
+ }
1426
+ }
1379
1427
  var _this_ = ({ stream }) => ({
1380
1428
  source: stream,
1381
1429
  target: stream
1382
1430
  });
1383
1431
  var _void_ = () => void 0;
1432
+
1433
+ // src/projection-builder.ts
1434
+ function isProjection(x) {
1435
+ return x != null && x._tag === "Projection";
1436
+ }
1437
+ function projection(target, events = {}) {
1438
+ const defaultResolver = target ? { target } : void 0;
1439
+ const builder = {
1440
+ on: (entry) => {
1441
+ const keys = Object.keys(entry);
1442
+ if (keys.length !== 1) throw new Error(".on() requires exactly one key");
1443
+ const event = keys[0];
1444
+ const schema = entry[event];
1445
+ if (!(event in events)) {
1446
+ events[event] = {
1447
+ schema,
1448
+ reactions: /* @__PURE__ */ new Map()
1449
+ };
1450
+ }
1451
+ return {
1452
+ do: (handler) => {
1453
+ const reaction = {
1454
+ handler,
1455
+ resolver: defaultResolver ?? _this_,
1456
+ options: {
1457
+ blockOnError: true,
1458
+ maxRetries: 3
1459
+ }
1460
+ };
1461
+ const register = events[event];
1462
+ const name = handler.name || `${event}_${register.reactions.size}`;
1463
+ register.reactions.set(name, reaction);
1464
+ const nextBuilder = projection(
1465
+ target,
1466
+ events
1467
+ );
1468
+ return {
1469
+ ...nextBuilder,
1470
+ to(resolver) {
1471
+ register.reactions.set(name, {
1472
+ ...reaction,
1473
+ resolver: typeof resolver === "string" ? { target: resolver } : resolver
1474
+ });
1475
+ return nextBuilder;
1476
+ },
1477
+ void() {
1478
+ register.reactions.set(name, {
1479
+ ...reaction,
1480
+ resolver: _void_
1481
+ });
1482
+ return nextBuilder;
1483
+ }
1484
+ };
1485
+ }
1486
+ };
1487
+ },
1488
+ build: () => ({
1489
+ _tag: "Projection",
1490
+ events
1491
+ }),
1492
+ events
1493
+ };
1494
+ return builder;
1495
+ }
1496
+
1497
+ // src/slice-builder.ts
1498
+ function isSlice(x) {
1499
+ return x != null && x._tag === "Slice";
1500
+ }
1501
+ function slice(states = /* @__PURE__ */ new Map(), actions = {}, events = {}) {
1502
+ const builder = {
1503
+ with: (state2) => {
1504
+ registerState(state2, states, actions, events);
1505
+ return slice(states, actions, events);
1506
+ },
1507
+ on: (event) => ({
1508
+ do: (handler, options) => {
1509
+ const reaction = {
1510
+ handler,
1511
+ resolver: _this_,
1512
+ options: {
1513
+ blockOnError: options?.blockOnError ?? true,
1514
+ maxRetries: options?.maxRetries ?? 3
1515
+ }
1516
+ };
1517
+ const name = handler.name || `${String(event)}_${events[event].reactions.size}`;
1518
+ events[event].reactions.set(name, reaction);
1519
+ return {
1520
+ ...builder,
1521
+ to(resolver) {
1522
+ events[event].reactions.set(name, {
1523
+ ...reaction,
1524
+ resolver: typeof resolver === "string" ? { target: resolver } : resolver
1525
+ });
1526
+ return builder;
1527
+ },
1528
+ void() {
1529
+ events[event].reactions.set(name, {
1530
+ ...reaction,
1531
+ resolver: _void_
1532
+ });
1533
+ return builder;
1534
+ }
1535
+ };
1536
+ }
1537
+ }),
1538
+ build: () => ({
1539
+ _tag: "Slice",
1540
+ states,
1541
+ events
1542
+ }),
1543
+ events
1544
+ };
1545
+ return builder;
1546
+ }
1547
+
1548
+ // src/act-builder.ts
1384
1549
  function act(states = /* @__PURE__ */ new Map(), registry = {
1385
1550
  actions: {},
1386
1551
  events: {}
1387
1552
  }) {
1388
1553
  const builder = {
1389
- /**
1390
- * Adds a state to the builder. When a state with the same name is already
1391
- * registered, merges the new partial's actions, events, patches, and handlers
1392
- * into the existing state (errors on duplicate action/event names).
1393
- *
1394
- * @template SX The type of state
1395
- * @template EX The type of events
1396
- * @template AX The type of actions
1397
- * @param state The state to add
1398
- * @returns The builder
1399
- */
1400
- with: (state2) => {
1401
- if (states.has(state2.name)) {
1402
- const existing = states.get(state2.name);
1403
- for (const name of Object.keys(state2.actions)) {
1404
- if (registry.actions[name])
1405
- throw new Error(`Duplicate action "${name}"`);
1406
- }
1407
- for (const name of Object.keys(state2.events)) {
1408
- if (registry.events[name])
1409
- throw new Error(`Duplicate event "${name}"`);
1410
- }
1411
- const merged = {
1412
- ...existing,
1413
- state: mergeSchemas(existing.state, state2.state, state2.name),
1414
- init: mergeInits(existing.init, state2.init),
1415
- events: { ...existing.events, ...state2.events },
1416
- actions: { ...existing.actions, ...state2.actions },
1417
- patch: { ...existing.patch, ...state2.patch },
1418
- on: { ...existing.on, ...state2.on },
1419
- given: { ...existing.given, ...state2.given },
1420
- snap: state2.snap || existing.snap
1421
- };
1422
- states.set(state2.name, merged);
1423
- for (const name of Object.keys(merged.actions)) {
1424
- registry.actions[name] = merged;
1425
- }
1426
- for (const name of Object.keys(state2.events)) {
1427
- registry.events[name] = {
1428
- schema: state2.events[name],
1429
- reactions: /* @__PURE__ */ new Map()
1430
- };
1554
+ with: ((input) => {
1555
+ if (isProjection(input)) {
1556
+ for (const eventName of Object.keys(input.events)) {
1557
+ const projRegister = input.events[eventName];
1558
+ const existing = registry.events[eventName];
1559
+ if (!existing) {
1560
+ registry.events[eventName] = {
1561
+ schema: projRegister.schema,
1562
+ reactions: new Map(projRegister.reactions)
1563
+ };
1564
+ } else {
1565
+ for (const [name, reaction] of projRegister.reactions) {
1566
+ let key = name;
1567
+ while (existing.reactions.has(key)) key = `${key}_p`;
1568
+ existing.reactions.set(key, reaction);
1569
+ }
1570
+ }
1431
1571
  }
1432
- } else {
1433
- states.set(state2.name, state2);
1434
- for (const name of Object.keys(state2.actions)) {
1435
- if (registry.actions[name])
1436
- throw new Error(`Duplicate action "${name}"`);
1437
- registry.actions[name] = state2;
1572
+ return act(states, registry);
1573
+ }
1574
+ if (isSlice(input)) {
1575
+ for (const s of input.states.values()) {
1576
+ registerState(s, states, registry.actions, registry.events);
1438
1577
  }
1439
- for (const name of Object.keys(state2.events)) {
1440
- if (registry.events[name])
1441
- throw new Error(`Duplicate event "${name}"`);
1442
- registry.events[name] = {
1443
- schema: state2.events[name],
1444
- reactions: /* @__PURE__ */ new Map()
1445
- };
1578
+ for (const eventName of Object.keys(input.events)) {
1579
+ const sliceRegister = input.events[eventName];
1580
+ for (const [name, reaction] of sliceRegister.reactions) {
1581
+ registry.events[eventName].reactions.set(name, reaction);
1582
+ }
1446
1583
  }
1584
+ return act(states, registry);
1447
1585
  }
1448
- return act(
1449
- states,
1450
- registry
1451
- );
1452
- },
1586
+ registerState(input, states, registry.actions, registry.events);
1587
+ return act(states, registry);
1588
+ }),
1453
1589
  /**
1454
1590
  * Adds a reaction to an event.
1455
1591
  *
@@ -1467,18 +1603,19 @@ function act(states = /* @__PURE__ */ new Map(), registry = {
1467
1603
  maxRetries: options?.maxRetries ?? 3
1468
1604
  }
1469
1605
  };
1470
- registry.events[event].reactions.set(handler.name, reaction);
1606
+ const name = handler.name || `${String(event)}_${registry.events[event].reactions.size}`;
1607
+ registry.events[event].reactions.set(name, reaction);
1471
1608
  return {
1472
1609
  ...builder,
1473
1610
  to(resolver) {
1474
- registry.events[event].reactions.set(handler.name, {
1611
+ registry.events[event].reactions.set(name, {
1475
1612
  ...reaction,
1476
1613
  resolver: typeof resolver === "string" ? { target: resolver } : resolver
1477
1614
  });
1478
1615
  return builder;
1479
1616
  },
1480
1617
  void() {
1481
- registry.events[event].reactions.set(handler.name, {
1618
+ registry.events[event].reactions.set(name, {
1482
1619
  ...reaction,
1483
1620
  resolver: _void_
1484
1621
  });
@@ -1519,7 +1656,11 @@ function state(name, state2) {
1519
1656
  }
1520
1657
  function action_builder(state2) {
1521
1658
  return {
1522
- on(action2, schema) {
1659
+ on(entry) {
1660
+ const keys = Object.keys(entry);
1661
+ if (keys.length !== 1) throw new Error(".on() requires exactly one key");
1662
+ const action2 = keys[0];
1663
+ const schema = entry[action2];
1523
1664
  if (action2 in state2.actions)
1524
1665
  throw new Error(`Duplicate action "${action2}"`);
1525
1666
  const actions = { ...state2.actions, [action2]: schema };
@@ -1573,10 +1714,14 @@ function action_builder(state2) {
1573
1714
  dispose,
1574
1715
  disposeAndExit,
1575
1716
  extend,
1717
+ isProjection,
1718
+ isSlice,
1576
1719
  logger,
1577
1720
  patch,
1578
1721
  port,
1722
+ projection,
1579
1723
  sleep,
1724
+ slice,
1580
1725
  state,
1581
1726
  store,
1582
1727
  validate