@rotorsoft/act 1.37.8 → 2.0.1
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/README.md +16 -3
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +5 -4
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/adapters/in-memory-store.d.ts +2 -2
- package/dist/@types/adapters/in-memory-store.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/builders/merge.d.ts.map +1 -1
- package/dist/@types/builders/state-builder.d.ts +1 -1
- package/dist/@types/internal/classify.d.ts.map +1 -1
- package/dist/@types/internal/config.d.ts +11 -0
- package/dist/@types/internal/config.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts +6 -2
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/tracing.d.ts.map +1 -1
- package/dist/@types/internal/zod.d.ts +2 -2
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/@types/types/act.d.ts +0 -9
- package/dist/@types/types/act.d.ts.map +1 -1
- package/dist/@types/types/action.d.ts +36 -32
- package/dist/@types/types/action.d.ts.map +1 -1
- package/dist/@types/types/builders.d.ts +126 -47
- package/dist/@types/types/builders.d.ts.map +1 -1
- package/dist/@types/types/errors.d.ts +11 -43
- package/dist/@types/types/errors.d.ts.map +1 -1
- package/dist/@types/types/ports.d.ts +65 -50
- package/dist/@types/types/ports.d.ts.map +1 -1
- package/dist/@types/types/reaction.d.ts +3 -3
- package/dist/@types/utils.d.ts +19 -11
- package/dist/@types/utils.d.ts.map +1 -1
- package/dist/{chunk-TSDQDT4S.js → chunk-33D5NQNQ.js} +52 -30
- package/dist/chunk-33D5NQNQ.js.map +1 -0
- package/dist/{chunk-BCA423WP.cjs → chunk-3CQWJ4UA.cjs} +8 -22
- package/dist/chunk-3CQWJ4UA.cjs.map +1 -0
- package/dist/{chunk-4UWU5FAN.cjs → chunk-S2BEPQ5Z.cjs} +63 -41
- package/dist/chunk-S2BEPQ5Z.cjs.map +1 -0
- package/dist/{chunk-I5RF2NFR.js → chunk-UO7HNLSH.js} +7 -21
- package/dist/chunk-UO7HNLSH.js.map +1 -0
- package/dist/index.cjs +451 -291
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +258 -98
- package/dist/index.js.map +1 -1
- package/dist/test/index.cjs +4 -4
- package/dist/test/index.js +2 -2
- package/dist/types/index.cjs +2 -4
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.js +1 -3
- package/package.json +3 -3
- package/dist/chunk-4UWU5FAN.cjs.map +0 -1
- package/dist/chunk-BCA423WP.cjs.map +0 -1
- package/dist/chunk-I5RF2NFR.js.map +0 -1
- 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-
|
|
47
|
+
} from "./chunk-33D5NQNQ.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-
|
|
80
|
+
} from "./chunk-UO7HNLSH.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
|
-
|
|
1201
|
-
|
|
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
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
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
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
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
|
|
1467
|
-
name
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
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
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
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(
|
|
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
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
2209
|
-
//
|
|
2210
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
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) {
|
|
@@ -4189,12 +4302,11 @@ var Act = class {
|
|
|
4189
4302
|
|
|
4190
4303
|
// src/builders/merge.ts
|
|
4191
4304
|
import { ZodObject } from "zod";
|
|
4192
|
-
function base_type_name(
|
|
4193
|
-
let
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
}
|
|
4197
|
-
return t.constructor.name;
|
|
4305
|
+
function base_type_name(schema) {
|
|
4306
|
+
let def = def_of(schema);
|
|
4307
|
+
for (let inner = inner_of(def); inner; inner = inner_of(def))
|
|
4308
|
+
def = def_of(inner);
|
|
4309
|
+
return def.type === "array" ? `array of ${base_type_name(def.element)}` : def.type;
|
|
4198
4310
|
}
|
|
4199
4311
|
function merge_schemas(existing, incoming, state_name) {
|
|
4200
4312
|
if (existing === incoming) return existing;
|
|
@@ -4286,8 +4398,8 @@ function merge_into_existing(state2, existing, states, actions, events) {
|
|
|
4286
4398
|
actions: { ...existing.actions, ...state2.actions },
|
|
4287
4399
|
patch: merged_patch,
|
|
4288
4400
|
on: { ...existing.on, ...state2.on },
|
|
4289
|
-
given: { ...existing.given, ...state2.given },
|
|
4290
4401
|
requires: { ...existing.requires, ...state2.requires },
|
|
4402
|
+
loads: existing.loads || state2.loads ? { ...existing.loads, ...state2.loads } : void 0,
|
|
4291
4403
|
snap: pick_declared(existing.snap, state2.snap, "snap strategy", state2.name),
|
|
4292
4404
|
// Per-action retry policy is keyed by action name, so the two partials'
|
|
4293
4405
|
// maps combine. Left `undefined` when neither declared one, so the
|
|
@@ -4852,6 +4964,38 @@ function act() {
|
|
|
4852
4964
|
for (const [name, gate] of built.handler_readers) _hr.set(name, gate);
|
|
4853
4965
|
finalize_deprecations();
|
|
4854
4966
|
if (built.advisory) log().warn(built.advisory);
|
|
4967
|
+
for (const [name, state2] of states) {
|
|
4968
|
+
if (!state2.loads) continue;
|
|
4969
|
+
const resolved = {};
|
|
4970
|
+
for (const [action2, loads] of Object.entries(state2.loads)) {
|
|
4971
|
+
resolved[action2] = loads.map((l) => {
|
|
4972
|
+
const registered = states.get(l.state.name);
|
|
4973
|
+
if (!registered)
|
|
4974
|
+
throw new Error(
|
|
4975
|
+
`Action "${action2}" loads "${l.state.name}", which is not registered \u2014 add it via withState/withSlice before build()`
|
|
4976
|
+
);
|
|
4977
|
+
const deprecated = l.events.find(
|
|
4978
|
+
(e) => _de.get(registered.name)?.has(e)
|
|
4979
|
+
);
|
|
4980
|
+
if (deprecated)
|
|
4981
|
+
throw new Error(
|
|
4982
|
+
`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.`
|
|
4983
|
+
);
|
|
4984
|
+
return { ...l, state: registered };
|
|
4985
|
+
});
|
|
4986
|
+
}
|
|
4987
|
+
for (const [action2, loads] of Object.entries(resolved)) {
|
|
4988
|
+
const static_target = state2.on[action2]._static_emit;
|
|
4989
|
+
if (static_target && loads.some((l) => l.events.includes(static_target)))
|
|
4990
|
+
throw new Error(
|
|
4991
|
+
`Action "${action2}" statically emits "${static_target}", which its load lists, so nothing goes to its own stream; emit one of its own events too`
|
|
4992
|
+
);
|
|
4993
|
+
}
|
|
4994
|
+
const own = { ...state2, loads: resolved };
|
|
4995
|
+
states.set(name, own);
|
|
4996
|
+
for (const action2 of Object.keys(own.actions))
|
|
4997
|
+
registry.actions[action2] = own;
|
|
4998
|
+
}
|
|
4855
4999
|
for (const state2 of states.values()) {
|
|
4856
5000
|
if (state2.disclose) _dp.set(state2.name, state2.disclose);
|
|
4857
5001
|
if (state2.autoclose) _ac.set(state2.name, state2.autoclose);
|
|
@@ -5092,9 +5236,25 @@ function action_builder(state2) {
|
|
|
5092
5236
|
internal.options ??= {};
|
|
5093
5237
|
internal.options[action2] = options;
|
|
5094
5238
|
}
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5239
|
+
const loads = [];
|
|
5240
|
+
function load2(state3, target, events = [], name = state3.name) {
|
|
5241
|
+
if (loads.some((l) => l.name === name))
|
|
5242
|
+
throw new Error(
|
|
5243
|
+
`Action "${action2}" loads "${name}" twice; name each load of the same state`
|
|
5244
|
+
);
|
|
5245
|
+
for (const event of events) {
|
|
5246
|
+
if (!(event in state3.events))
|
|
5247
|
+
throw new Error(
|
|
5248
|
+
`Action "${action2}" lists "${event}" on its load of "${state3.name}", which doesn't emit it`
|
|
5249
|
+
);
|
|
5250
|
+
if (loads.some((l) => l.events.includes(event)))
|
|
5251
|
+
throw new Error(
|
|
5252
|
+
`Action "${action2}" lists "${event}" on two loads; an event goes to one stream`
|
|
5253
|
+
);
|
|
5254
|
+
}
|
|
5255
|
+
loads.push({ state: state3, target, events, name });
|
|
5256
|
+
(internal.loads ??= {})[action2] = loads;
|
|
5257
|
+
return { loads: load2, requires, emit };
|
|
5098
5258
|
}
|
|
5099
5259
|
function requires(rules) {
|
|
5100
5260
|
(internal.requires ??= {})[action2] = rules;
|
|
@@ -5115,7 +5275,7 @@ function action_builder(state2) {
|
|
|
5115
5275
|
}
|
|
5116
5276
|
return builder;
|
|
5117
5277
|
}
|
|
5118
|
-
return {
|
|
5278
|
+
return { loads: load2, requires, emit };
|
|
5119
5279
|
},
|
|
5120
5280
|
snap(snap2) {
|
|
5121
5281
|
internal.snap = snap2;
|
|
@@ -5348,7 +5508,6 @@ export {
|
|
|
5348
5508
|
ExitCodes,
|
|
5349
5509
|
InMemoryCache,
|
|
5350
5510
|
InMemoryStore,
|
|
5351
|
-
InvariantError,
|
|
5352
5511
|
LogLevels,
|
|
5353
5512
|
NonRetryableError,
|
|
5354
5513
|
PackageSchema,
|
|
@@ -5365,6 +5524,7 @@ export {
|
|
|
5365
5524
|
ZodEmpty,
|
|
5366
5525
|
act,
|
|
5367
5526
|
cache,
|
|
5527
|
+
check_appends,
|
|
5368
5528
|
config,
|
|
5369
5529
|
dateReviver,
|
|
5370
5530
|
default_scope,
|