@rotorsoft/act 1.37.8 → 2.0.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.
Files changed (51) hide show
  1. package/README.md +16 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/@types/act.d.ts +5 -4
  4. package/dist/@types/act.d.ts.map +1 -1
  5. package/dist/@types/adapters/in-memory-store.d.ts +2 -2
  6. package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
  7. package/dist/@types/builders/act-builder.d.ts.map +1 -1
  8. package/dist/@types/builders/merge.d.ts.map +1 -1
  9. package/dist/@types/builders/state-builder.d.ts +1 -1
  10. package/dist/@types/internal/classify.d.ts.map +1 -1
  11. package/dist/@types/internal/config.d.ts +11 -0
  12. package/dist/@types/internal/config.d.ts.map +1 -1
  13. package/dist/@types/internal/event-sourcing.d.ts +6 -2
  14. package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
  15. package/dist/@types/internal/tracing.d.ts.map +1 -1
  16. package/dist/@types/ports.d.ts.map +1 -1
  17. package/dist/@types/types/act.d.ts +0 -9
  18. package/dist/@types/types/act.d.ts.map +1 -1
  19. package/dist/@types/types/action.d.ts +36 -32
  20. package/dist/@types/types/action.d.ts.map +1 -1
  21. package/dist/@types/types/builders.d.ts +126 -47
  22. package/dist/@types/types/builders.d.ts.map +1 -1
  23. package/dist/@types/types/errors.d.ts +11 -43
  24. package/dist/@types/types/errors.d.ts.map +1 -1
  25. package/dist/@types/types/ports.d.ts +46 -28
  26. package/dist/@types/types/ports.d.ts.map +1 -1
  27. package/dist/@types/types/reaction.d.ts +3 -3
  28. package/dist/@types/utils.d.ts +19 -11
  29. package/dist/@types/utils.d.ts.map +1 -1
  30. package/dist/{chunk-4UWU5FAN.cjs → chunk-LUHPSF3C.cjs} +63 -41
  31. package/dist/chunk-LUHPSF3C.cjs.map +1 -0
  32. package/dist/{chunk-TSDQDT4S.js → chunk-MWHTNPUY.js} +52 -30
  33. package/dist/chunk-MWHTNPUY.js.map +1 -0
  34. package/dist/{chunk-I5RF2NFR.js → chunk-SNPEQW6Z.js} +7 -21
  35. package/dist/chunk-SNPEQW6Z.js.map +1 -0
  36. package/dist/{chunk-BCA423WP.cjs → chunk-YFFWCVXW.cjs} +8 -22
  37. package/dist/chunk-YFFWCVXW.cjs.map +1 -0
  38. package/dist/index.cjs +446 -285
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.js +253 -92
  41. package/dist/index.js.map +1 -1
  42. package/dist/test/index.cjs +4 -4
  43. package/dist/test/index.js +2 -2
  44. package/dist/types/index.cjs +2 -4
  45. package/dist/types/index.cjs.map +1 -1
  46. package/dist/types/index.js +1 -3
  47. package/package.json +2 -2
  48. package/dist/chunk-4UWU5FAN.cjs.map +0 -1
  49. package/dist/chunk-BCA423WP.cjs.map +0 -1
  50. package/dist/chunk-I5RF2NFR.js.map +0 -1
  51. package/dist/chunk-TSDQDT4S.js.map +0 -1
package/dist/index.js CHANGED
@@ -13,6 +13,8 @@ import {
13
13
  MAX_SHUTDOWN_GRACE_MS,
14
14
  PackageSchema,
15
15
  cache,
16
+ check_appends,
17
+ check_store,
16
18
  config,
17
19
  current_autoclose_window,
18
20
  current_reacting,
@@ -42,7 +44,7 @@ import {
42
44
  sleep,
43
45
  store,
44
46
  validate
45
- } from "./chunk-TSDQDT4S.js";
47
+ } from "./chunk-MWHTNPUY.js";
46
48
  import {
47
49
  ActorSchema,
48
50
  CausationEventSchema,
@@ -56,7 +58,6 @@ import {
56
58
  Errors,
57
59
  EventMetaSchema,
58
60
  ExitCodes,
59
- InvariantError,
60
61
  LogLevels,
61
62
  NonRetryableError,
62
63
  PreconditionError,
@@ -76,7 +77,7 @@ import {
76
77
  pii_fields,
77
78
  pii_schemas,
78
79
  sensitive
79
- } from "./chunk-I5RF2NFR.js";
80
+ } from "./chunk-SNPEQW6Z.js";
80
81
 
81
82
  // src/signals.ts
82
83
  process.once("SIGINT", async (arg) => {
@@ -752,6 +753,7 @@ var CircuitBreaker = class {
752
753
 
753
754
  // src/internal/classify.ts
754
755
  var ALL_LANES = /* @__PURE__ */ Symbol("act/all-lanes");
756
+ var describe_source = (source) => source === void 0 ? "no source (every stream)" : `"${source}"`;
755
757
  function classify_registry(registry, states) {
756
758
  const statics = /* @__PURE__ */ new Map();
757
759
  const target_lanes = /* @__PURE__ */ new Map();
@@ -778,6 +780,10 @@ function classify_registry(registry, states) {
778
780
  );
779
781
  }
780
782
  const existing = statics.get(target);
783
+ if (existing && existing.source !== source)
784
+ throw new Error(
785
+ `Target "${target}" has reactions on two sources, ${describe_source(existing.source)} and ${describe_source(source)}. A target reads one source, so the second would miss events or read too many. Give it its own target.`
786
+ );
781
787
  if (!existing) {
782
788
  statics.set(target, { stream: target, source, priority, lane });
783
789
  } else if (priority > existing.priority) {
@@ -1197,14 +1203,18 @@ async function snap(snapshot) {
1197
1203
  const { id, stream, name, meta, version } = snapshot.event;
1198
1204
  try {
1199
1205
  const [committed] = await store().commit(
1200
- stream,
1201
- [{ name: SNAP_EVENT, data: snapshot.state }],
1206
+ [
1207
+ {
1208
+ stream,
1209
+ msgs: [{ name: SNAP_EVENT, data: snapshot.state }],
1210
+ // IMPORTANT! - state events are committed right after the snapshot event
1211
+ expectedVersion: version
1212
+ }
1213
+ ],
1202
1214
  {
1203
1215
  correlation: meta.correlation,
1204
1216
  causation: { event: { id, name, stream } }
1205
- },
1206
- version
1207
- // IMPORTANT! - state events are committed right after the snapshot event
1217
+ }
1208
1218
  );
1209
1219
  return committed;
1210
1220
  } catch (error) {
@@ -1217,10 +1227,14 @@ async function snap(snapshot) {
1217
1227
  async function tombstone(stream, expectedVersion, correlation) {
1218
1228
  try {
1219
1229
  const [committed] = await store().commit(
1220
- stream,
1221
- [{ name: TOMBSTONE_EVENT, data: {} }],
1222
- { correlation, causation: {} },
1223
- expectedVersion
1230
+ [
1231
+ {
1232
+ stream,
1233
+ msgs: [{ name: TOMBSTONE_EVENT, data: {} }],
1234
+ expectedVersion
1235
+ }
1236
+ ],
1237
+ { correlation, causation: {} }
1224
1238
  );
1225
1239
  return committed;
1226
1240
  } catch (error) {
@@ -1422,6 +1436,38 @@ async function load(me, target, callback, patch_fn = bare_patch) {
1422
1436
  replayed
1423
1437
  };
1424
1438
  }
1439
+ async function load_others(loads, payload, own, actor, patch_fn, refuse) {
1440
+ const streams = loads.map((l) => l.target(payload, own.state));
1441
+ streams.forEach((stream, i) => {
1442
+ if (stream === void 0) return;
1443
+ if (typeof stream !== "string" || !stream)
1444
+ throw refuse(`loads "${loads[i].name}" from an empty stream name`);
1445
+ if (stream === own.stream) throw refuse(`loads its own stream "${stream}"`);
1446
+ if (streams.indexOf(stream) !== i)
1447
+ throw refuse(`loads stream "${stream}" twice`);
1448
+ });
1449
+ return Promise.all(
1450
+ loads.map(async (l, i) => {
1451
+ const stream = streams[i];
1452
+ if (stream === void 0) return void 0;
1453
+ const snapshot = await load(
1454
+ l.state,
1455
+ { stream, actor },
1456
+ void 0,
1457
+ patch_fn
1458
+ );
1459
+ if (snapshot.event?.name === TOMBSTONE_EVENT)
1460
+ throw new StreamClosedError(stream);
1461
+ return {
1462
+ state: l.state,
1463
+ stream,
1464
+ snapshot,
1465
+ expected: snapshot.version,
1466
+ events: []
1467
+ };
1468
+ })
1469
+ );
1470
+ }
1425
1471
  async function action(me, action2, target, payload, options, patch_fn = bare_patch) {
1426
1472
  const { stream, expectedVersion, actor } = target;
1427
1473
  if (!stream) throw new Error("Missing target stream");
@@ -1430,6 +1476,7 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1430
1476
  const validated = validate(action2, payload, me.actions[action2]);
1431
1477
  const opts = me.options?.[action2];
1432
1478
  const max_retries = opts?.maxRetries ?? 0;
1479
+ const loads = me.loads?.[action2] ?? [];
1433
1480
  for (let attempt = 0; ; attempt++) {
1434
1481
  try {
1435
1482
  const snapshot = await load(
@@ -1440,18 +1487,32 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1440
1487
  );
1441
1488
  if (snapshot.event?.name === TOMBSTONE_EVENT)
1442
1489
  throw new StreamClosedError(stream);
1443
- const expected = expectedVersion ?? snapshot.version;
1444
- for (const { valid, description } of me.given?.[action2] ?? [])
1445
- if (!valid(snapshot.state, actor))
1446
- throw new InvariantError(
1447
- action2,
1448
- validated,
1449
- target,
1450
- snapshot,
1451
- description
1452
- );
1490
+ const own = {
1491
+ state: me,
1492
+ stream,
1493
+ snapshot,
1494
+ expected: expectedVersion ?? snapshot.version,
1495
+ events: []
1496
+ };
1497
+ const others = loads.length ? await load_others(
1498
+ loads,
1499
+ validated,
1500
+ { stream, state: snapshot.state },
1501
+ target.actor,
1502
+ patch_fn,
1503
+ (description) => new PreconditionError(
1504
+ action2,
1505
+ validated,
1506
+ target,
1507
+ snapshot,
1508
+ description
1509
+ )
1510
+ ) : [];
1511
+ const loaded = Object.fromEntries(
1512
+ others.map((p, i) => [loads[i].name, p?.snapshot.state])
1513
+ );
1453
1514
  for (const { holds, description } of me.requires?.[action2] ?? [])
1454
- if (!holds(validated, snapshot.state, actor))
1515
+ if (!holds(validated, snapshot.state, actor, loaded))
1455
1516
  throw new PreconditionError(
1456
1517
  action2,
1457
1518
  validated,
@@ -1459,15 +1520,31 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1459
1520
  snapshot,
1460
1521
  description
1461
1522
  );
1462
- const result = me.on[action2](validated, snapshot, target);
1523
+ const result = me.on[action2](validated, snapshot, target, loaded);
1463
1524
  if (!result || Array.isArray(result) && !result.length)
1464
1525
  return [snapshot];
1465
1526
  const tuples = Array.isArray(result[0]) ? result : [result];
1466
- const events = tuples.map(([name, data]) => ({
1467
- name,
1468
- data: validate(name, data, me.events[name])
1469
- }));
1470
- const emitted = events.map((e) => me.message(e));
1527
+ for (const [name, data] of tuples) {
1528
+ const event = name;
1529
+ const i = loads.findIndex((l) => l.events.includes(event));
1530
+ if (i >= 0 && !others[i]) continue;
1531
+ const to = i >= 0 ? others[i] : name in me.events ? own : void 0;
1532
+ if (!to)
1533
+ throw new Error(
1534
+ `Action "${action2}" emitted "${event}", which isn't an event of "${me.name}" and isn't listed on any of its loads`
1535
+ );
1536
+ to.events.push({
1537
+ name: event,
1538
+ data: validate(event, data, to.state.events[event])
1539
+ });
1540
+ }
1541
+ if (!own.events.length)
1542
+ throw new Error(
1543
+ `Action "${action2}" appends only to the streams it loads; it must append an event to its own stream too`
1544
+ );
1545
+ const appends = [own, ...others].filter(
1546
+ (p) => !!p?.events.length
1547
+ );
1471
1548
  const meta = {
1472
1549
  correlation: reactingTo?.meta.correlation || correlator({
1473
1550
  action: action2,
@@ -1488,61 +1565,86 @@ async function action(me, action2, target, payload, options, patch_fn = bare_pat
1488
1565
  } : void 0
1489
1566
  }
1490
1567
  };
1491
- let { state: state2, patches } = snapshot;
1492
1568
  const created = /* @__PURE__ */ new Date();
1493
- const folded = events.map((e, i) => {
1494
- const event = {
1495
- ...e,
1496
- id: -1,
1497
- stream,
1498
- version: snapshot.version + i + 1,
1499
- created,
1500
- meta
1501
- };
1502
- const patch2 = me.patch[event.name](event, state2);
1503
- state2 = patch_fn(me, state2, patch2, event);
1504
- patches++;
1505
- return { state: state2, patch: patch2, patches };
1569
+ const folds = appends.map((p) => {
1570
+ let { state: state2, patches } = p.snapshot;
1571
+ const folded = p.events.map((e, i) => {
1572
+ const event = {
1573
+ ...e,
1574
+ id: -1,
1575
+ stream: p.stream,
1576
+ version: p.snapshot.version + i + 1,
1577
+ created,
1578
+ meta
1579
+ };
1580
+ const patch2 = p.state.patch[event.name](event, state2);
1581
+ state2 = patch_fn(p.state, state2, patch2, event);
1582
+ patches++;
1583
+ return { state: state2, patch: patch2, patches };
1584
+ });
1585
+ check_invariants(
1586
+ `${p.state.name}.${action2}`,
1587
+ state2,
1588
+ p.state.state,
1589
+ p.state.pii_aware
1590
+ );
1591
+ return folded;
1506
1592
  });
1507
- check_invariants(
1508
- `${me.name}.${action2}`,
1509
- state2,
1510
- me.state,
1511
- me.pii_aware
1512
- );
1513
1593
  let committed;
1514
1594
  try {
1515
- committed = await store().commit(stream, emitted, meta, expected);
1595
+ committed = await store().commit(
1596
+ appends.map((p) => ({
1597
+ stream: p.stream,
1598
+ msgs: p.events.map((e) => p.state.message(e)),
1599
+ expectedVersion: p.expected
1600
+ })),
1601
+ meta
1602
+ );
1516
1603
  } catch (error) {
1517
- if (error instanceof ConcurrencyError) void cache_drop(stream);
1604
+ if (error instanceof ConcurrencyError) void cache_drop(error.stream);
1518
1605
  throw error;
1519
1606
  }
1520
- const snapshots = committed.map((row, i) => ({
1521
- event: { ...row, data: events[i].data },
1522
- state: folded[i].state,
1523
- version: row.version,
1524
- id: row.id,
1525
- patches: folded[i].patches,
1526
- snaps: snapshot.snaps,
1527
- patch: folded[i].patch,
1528
- cache_hit: snapshot.cache_hit,
1529
- replayed: snapshot.replayed
1530
- }));
1531
- const last = snapshots.at(-1);
1532
- const snap_event = me.snap?.(last) ? await snap(last) : void 0;
1533
- if (!me.pii_aware)
1534
- void cache_put(stream, {
1535
- stream,
1536
- state: last.state,
1537
- version: snap_event?.version ?? last.event.version,
1538
- event_id: snap_event?.id ?? last.event.id,
1539
- patches: snap_event ? 0 : last.patches,
1540
- snaps: snap_event ? last.snaps + 1 : last.snaps
1607
+ let row = 0;
1608
+ const snapshots = [];
1609
+ for (const [a, append] of appends.entries()) {
1610
+ const { snapshot: base, state: owner } = append;
1611
+ const appended = append.events.map((e, i) => {
1612
+ const stored = committed[row++];
1613
+ const fold = folds[a][i];
1614
+ return {
1615
+ event: { ...stored, data: e.data },
1616
+ state: fold.state,
1617
+ version: stored.version,
1618
+ id: stored.id,
1619
+ patches: fold.patches,
1620
+ snaps: base.snaps,
1621
+ patch: fold.patch,
1622
+ cache_hit: base.cache_hit,
1623
+ replayed: base.replayed
1624
+ };
1541
1625
  });
1626
+ const last = appended.at(-1);
1627
+ const snap_event = owner.snap?.(last) ? await snap(last) : void 0;
1628
+ if (!owner.pii_aware)
1629
+ void cache_put(append.stream, {
1630
+ stream: append.stream,
1631
+ state: last.state,
1632
+ version: snap_event?.version ?? last.event.version,
1633
+ event_id: snap_event?.id ?? last.event.id,
1634
+ patches: snap_event ? 0 : last.patches,
1635
+ snaps: snap_event ? last.snaps + 1 : last.snaps
1636
+ });
1637
+ if (snap_event)
1638
+ appended[appended.length - 1] = {
1639
+ ...last,
1640
+ version: snap_event.version
1641
+ };
1642
+ snapshots.push(...appended);
1643
+ }
1542
1644
  return snapshots;
1543
1645
  } catch (error) {
1544
1646
  if (!(error instanceof ConcurrencyError)) throw error;
1545
- if (expectedVersion !== void 0) throw error;
1647
+ if (expectedVersion !== void 0 && error.stream === stream) throw error;
1546
1648
  if (attempt >= max_retries) throw error;
1547
1649
  if (opts?.backoff) {
1548
1650
  const delay_ms = compute_backoff_delay(attempt, opts.backoff);
@@ -1649,14 +1751,14 @@ function build_es(logger, correlator = default_correlator, patch_fn = bare_patch
1649
1751
  }),
1650
1752
  action: traced(
1651
1753
  bound_action,
1652
- (snapshots, _me, _action, target) => {
1754
+ (snapshots) => {
1653
1755
  if (snapshots[0].patch)
1654
1756
  logger.trace(
1655
1757
  snapshots.map((s) => s.event.data),
1656
1758
  es_caption(
1657
1759
  "committed",
1658
1760
  C_ORANGE,
1659
- `${target.stream}.${snapshots.map((s) => s.event.name).join(", ")}`
1761
+ snapshots.map((s) => `${s.event.stream}.${s.event.name}`).join(", ")
1660
1762
  )
1661
1763
  );
1662
1764
  },
@@ -2205,9 +2307,9 @@ function synthesize_autoclose_reactions(registry, states, read_window) {
2205
2307
  // watermark would let autoclose's defer short-circuit the aggregate's
2206
2308
  // other reactions (the "a defer affects all reactions on a stream"
2207
2309
  // hazard). The close still targets the aggregate (`source`).
2208
- // A stream name with a regex metacharacter would read as a pattern, and a
2209
- // store may refuse it at subscribe, stopping every reaction. Such a stream
2210
- // has no autoclose: `app.close` still closes it.
2310
+ // An anchored stream name would read as a pattern, and a store may refuse
2311
+ // it at subscribe, stopping every reaction. Such a stream has no
2312
+ // autoclose: `app.close` still closes it.
2211
2313
  resolver: (e) => is_literal_source(e.stream) ? {
2212
2314
  target: `${AUTOCLOSE_TARGET_PREFIX}${e.stream}`,
2213
2315
  source: e.stream
@@ -3285,6 +3387,7 @@ var Act = class {
3285
3387
  this._lanes = lanes;
3286
3388
  this._declared_lanes = ["default", ...lanes.map((l) => l.name)];
3287
3389
  validate_only_lanes(options.onlyLanes, this._declared_lanes);
3390
+ if (options.scoped) check_store(options.scoped.store);
3288
3391
  this._ports = options.scoped ?? default_scope();
3289
3392
  const ports = this._ports;
3290
3393
  this._scoped = make_run_scoped({
@@ -3540,7 +3643,7 @@ var Act = class {
3540
3643
  * Executes an action: validates the payload, loads the state, checks its
3541
3644
  * preconditions, runs the handler, folds the events it emits, checks the
3542
3645
  * state's invariants (its schema) on the result, and commits them with
3543
- * optimistic concurrency on the stream. The reactions to those events
3646
+ * optimistic concurrency on every stream it appends to. The reactions to those events
3544
3647
  * settle on their own (unless `autoSettle: false`).
3545
3648
  *
3546
3649
  * @template TKey - Action name from registered actions
@@ -3550,7 +3653,8 @@ var Act = class {
3550
3653
  * @param options - Per-call dispatch options ({@link DoOptions}) —
3551
3654
  * `reactingTo` to thread correlation, `correlator` to override the
3552
3655
  * framework or orchestrator-level correlator for this call only.
3553
- * @returns Array of snapshots for all affected states (usually one)
3656
+ * @returns The snapshots of the action's own stream, one per event appended
3657
+ * to it; the loaded snapshot alone when the action commits nothing
3554
3658
  *
3555
3659
  * @throws {ValidationError} If the payload doesn't match the action schema,
3556
3660
  * or the state the action leaves breaks the state schema
@@ -3587,13 +3691,22 @@ var Act = class {
3587
3691
  do_options
3588
3692
  );
3589
3693
  if (!snapshots[0].patch) return snapshots;
3694
+ const me = this.registry.actions[action2];
3590
3695
  this._wake(snapshots.map((s) => s.event.name));
3591
- this.emit("committed", snapshots);
3592
- this._notify(
3593
- `committed:${this.registry.actions[action2].name}`,
3594
- snapshots
3595
- );
3596
- return snapshots;
3696
+ const by_stream = /* @__PURE__ */ new Map();
3697
+ for (const s of snapshots) {
3698
+ const of_stream = by_stream.get(s.event.stream);
3699
+ if (of_stream) of_stream.push(s);
3700
+ else by_stream.set(s.event.stream, [s]);
3701
+ }
3702
+ for (const [stream, of_stream] of by_stream) {
3703
+ const owner = stream === target.stream ? me : me.loads[action2].find(
3704
+ (l) => l.events.includes(of_stream[0].event.name)
3705
+ ).state;
3706
+ this.emit("committed", of_stream);
3707
+ this._notify(`committed:${owner.name}`, of_stream);
3708
+ }
3709
+ return snapshots.filter((s) => s.event.stream === target.stream);
3597
3710
  });
3598
3711
  }
3599
3712
  async load(stateOrName, streamOrTarget, callback, asOf) {
@@ -4286,8 +4399,8 @@ function merge_into_existing(state2, existing, states, actions, events) {
4286
4399
  actions: { ...existing.actions, ...state2.actions },
4287
4400
  patch: merged_patch,
4288
4401
  on: { ...existing.on, ...state2.on },
4289
- given: { ...existing.given, ...state2.given },
4290
4402
  requires: { ...existing.requires, ...state2.requires },
4403
+ loads: existing.loads || state2.loads ? { ...existing.loads, ...state2.loads } : void 0,
4291
4404
  snap: pick_declared(existing.snap, state2.snap, "snap strategy", state2.name),
4292
4405
  // Per-action retry policy is keyed by action name, so the two partials'
4293
4406
  // maps combine. Left `undefined` when neither declared one, so the
@@ -4852,6 +4965,38 @@ function act() {
4852
4965
  for (const [name, gate] of built.handler_readers) _hr.set(name, gate);
4853
4966
  finalize_deprecations();
4854
4967
  if (built.advisory) log().warn(built.advisory);
4968
+ for (const [name, state2] of states) {
4969
+ if (!state2.loads) continue;
4970
+ const resolved = {};
4971
+ for (const [action2, loads] of Object.entries(state2.loads)) {
4972
+ resolved[action2] = loads.map((l) => {
4973
+ const registered = states.get(l.state.name);
4974
+ if (!registered)
4975
+ throw new Error(
4976
+ `Action "${action2}" loads "${l.state.name}", which is not registered \u2014 add it via withState/withSlice before build()`
4977
+ );
4978
+ const deprecated = l.events.find(
4979
+ (e) => _de.get(registered.name)?.has(e)
4980
+ );
4981
+ if (deprecated)
4982
+ throw new Error(
4983
+ `Action "${action2}" lists deprecated event "${deprecated}" on its load of "${registered.name}". A newer version exists: "${current_version_of(deprecated, Object.keys(registered.events))}". List the current version instead.`
4984
+ );
4985
+ return { ...l, state: registered };
4986
+ });
4987
+ }
4988
+ for (const [action2, loads] of Object.entries(resolved)) {
4989
+ const static_target = state2.on[action2]._static_emit;
4990
+ if (static_target && loads.some((l) => l.events.includes(static_target)))
4991
+ throw new Error(
4992
+ `Action "${action2}" statically emits "${static_target}", which its load lists, so nothing goes to its own stream; emit one of its own events too`
4993
+ );
4994
+ }
4995
+ const own = { ...state2, loads: resolved };
4996
+ states.set(name, own);
4997
+ for (const action2 of Object.keys(own.actions))
4998
+ registry.actions[action2] = own;
4999
+ }
4855
5000
  for (const state2 of states.values()) {
4856
5001
  if (state2.disclose) _dp.set(state2.name, state2.disclose);
4857
5002
  if (state2.autoclose) _ac.set(state2.name, state2.autoclose);
@@ -5092,9 +5237,25 @@ function action_builder(state2) {
5092
5237
  internal.options ??= {};
5093
5238
  internal.options[action2] = options;
5094
5239
  }
5095
- function given(rules) {
5096
- (internal.given ??= {})[action2] = rules;
5097
- return { requires, emit };
5240
+ const loads = [];
5241
+ function load2(state3, target, events = [], name = state3.name) {
5242
+ if (loads.some((l) => l.name === name))
5243
+ throw new Error(
5244
+ `Action "${action2}" loads "${name}" twice; name each load of the same state`
5245
+ );
5246
+ for (const event of events) {
5247
+ if (!(event in state3.events))
5248
+ throw new Error(
5249
+ `Action "${action2}" lists "${event}" on its load of "${state3.name}", which doesn't emit it`
5250
+ );
5251
+ if (loads.some((l) => l.events.includes(event)))
5252
+ throw new Error(
5253
+ `Action "${action2}" lists "${event}" on two loads; an event goes to one stream`
5254
+ );
5255
+ }
5256
+ loads.push({ state: state3, target, events, name });
5257
+ (internal.loads ??= {})[action2] = loads;
5258
+ return { loads: load2, requires, emit };
5098
5259
  }
5099
5260
  function requires(rules) {
5100
5261
  (internal.requires ??= {})[action2] = rules;
@@ -5115,7 +5276,7 @@ function action_builder(state2) {
5115
5276
  }
5116
5277
  return builder;
5117
5278
  }
5118
- return { given, requires, emit };
5279
+ return { loads: load2, requires, emit };
5119
5280
  },
5120
5281
  snap(snap2) {
5121
5282
  internal.snap = snap2;
@@ -5348,7 +5509,6 @@ export {
5348
5509
  ExitCodes,
5349
5510
  InMemoryCache,
5350
5511
  InMemoryStore,
5351
- InvariantError,
5352
5512
  LogLevels,
5353
5513
  NonRetryableError,
5354
5514
  PackageSchema,
@@ -5365,6 +5525,7 @@ export {
5365
5525
  ZodEmpty,
5366
5526
  act,
5367
5527
  cache,
5528
+ check_appends,
5368
5529
  config,
5369
5530
  dateReviver,
5370
5531
  default_scope,