@rotorsoft/act 0.37.0 → 0.38.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/README.md +33 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/act.d.ts +14 -0
- package/dist/@types/act.d.ts.map +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/ports.d.ts +10 -51
- package/dist/@types/ports.d.ts.map +1 -1
- package/dist/index.cjs +150 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +149 -114
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -55,7 +55,7 @@ __export(index_exports, {
|
|
|
55
55
|
ValidationError: () => ValidationError,
|
|
56
56
|
ZodEmpty: () => ZodEmpty,
|
|
57
57
|
act: () => act,
|
|
58
|
-
cache: () =>
|
|
58
|
+
cache: () => cache2,
|
|
59
59
|
config: () => config,
|
|
60
60
|
dispose: () => dispose,
|
|
61
61
|
disposeAndExit: () => disposeAndExit,
|
|
@@ -63,14 +63,18 @@ __export(index_exports, {
|
|
|
63
63
|
log: () => log,
|
|
64
64
|
port: () => port,
|
|
65
65
|
projection: () => projection,
|
|
66
|
+
scoped: () => scoped,
|
|
66
67
|
sleep: () => sleep,
|
|
67
68
|
slice: () => slice,
|
|
68
69
|
state: () => state,
|
|
69
|
-
store: () =>
|
|
70
|
+
store: () => store2,
|
|
70
71
|
validate: () => validate
|
|
71
72
|
});
|
|
72
73
|
module.exports = __toCommonJS(index_exports);
|
|
73
74
|
|
|
75
|
+
// src/ports.ts
|
|
76
|
+
var import_node_async_hooks = require("async_hooks");
|
|
77
|
+
|
|
74
78
|
// src/adapters/console-logger.ts
|
|
75
79
|
var LEVEL_VALUES = {
|
|
76
80
|
fatal: 60,
|
|
@@ -971,6 +975,7 @@ var InMemoryStore = class {
|
|
|
971
975
|
};
|
|
972
976
|
|
|
973
977
|
// src/ports.ts
|
|
978
|
+
var scoped = new import_node_async_hooks.AsyncLocalStorage();
|
|
974
979
|
var ExitCodes = ["ERROR", "EXIT"];
|
|
975
980
|
var adapters = /* @__PURE__ */ new Map();
|
|
976
981
|
function port(injector) {
|
|
@@ -990,11 +995,17 @@ var log = port(function log2(adapter) {
|
|
|
990
995
|
pretty: cfg.env !== "production"
|
|
991
996
|
});
|
|
992
997
|
});
|
|
993
|
-
var
|
|
994
|
-
return adapter
|
|
998
|
+
var _store = port(function store(adapter) {
|
|
999
|
+
return adapter ?? new InMemoryStore();
|
|
1000
|
+
});
|
|
1001
|
+
var store2 = ((adapter) => {
|
|
1002
|
+
return scoped.getStore()?.store ?? _store(adapter);
|
|
995
1003
|
});
|
|
996
|
-
var
|
|
997
|
-
return adapter
|
|
1004
|
+
var _cache = port(function cache(adapter) {
|
|
1005
|
+
return adapter ?? new InMemoryCache();
|
|
1006
|
+
});
|
|
1007
|
+
var cache2 = ((adapter) => {
|
|
1008
|
+
return scoped.getStore()?.cache ?? _cache(adapter);
|
|
998
1009
|
});
|
|
999
1010
|
var disposers = [];
|
|
1000
1011
|
async function disposeAndExit(code = "EXIT") {
|
|
@@ -1124,7 +1135,7 @@ async function scanStreamHeads(streams) {
|
|
|
1124
1135
|
let maxId = -1;
|
|
1125
1136
|
let version = -1;
|
|
1126
1137
|
let lastEventName = "";
|
|
1127
|
-
await
|
|
1138
|
+
await store2().query(
|
|
1128
1139
|
(e) => {
|
|
1129
1140
|
if (e.name === TOMBSTONE_EVENT || maxId !== -1) return;
|
|
1130
1141
|
maxId = e.id;
|
|
@@ -1141,7 +1152,7 @@ async function scanStreamHeads(streams) {
|
|
|
1141
1152
|
async function partitionBySafety(streamInfo, reactiveEventsSize, skipped) {
|
|
1142
1153
|
if (reactiveEventsSize === 0) return [...streamInfo.keys()];
|
|
1143
1154
|
const pendingSet = /* @__PURE__ */ new Set();
|
|
1144
|
-
await
|
|
1155
|
+
await store2().query_streams((position) => {
|
|
1145
1156
|
const sourceRe = position.source ? RegExp(position.source) : void 0;
|
|
1146
1157
|
for (const [stream, info] of streamInfo) {
|
|
1147
1158
|
if ((!sourceRe || sourceRe.test(stream)) && position.at < info.maxId) {
|
|
@@ -1212,13 +1223,13 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
|
|
|
1212
1223
|
}
|
|
1213
1224
|
};
|
|
1214
1225
|
});
|
|
1215
|
-
const truncated = await
|
|
1226
|
+
const truncated = await store2().truncate(truncTargets);
|
|
1216
1227
|
await Promise.all(
|
|
1217
1228
|
guarded.map(async (stream) => {
|
|
1218
1229
|
const entry = truncated.get(stream);
|
|
1219
1230
|
const state2 = seedStates.get(stream);
|
|
1220
1231
|
if (state2 && entry) {
|
|
1221
|
-
await
|
|
1232
|
+
await cache2().set(stream, {
|
|
1222
1233
|
state: state2,
|
|
1223
1234
|
version: entry.committed.version,
|
|
1224
1235
|
event_id: entry.committed.id,
|
|
@@ -1226,7 +1237,7 @@ async function truncateAndWarmCache(guarded, seedStates, guardEvents, correlatio
|
|
|
1226
1237
|
snaps: 1
|
|
1227
1238
|
});
|
|
1228
1239
|
} else {
|
|
1229
|
-
await
|
|
1240
|
+
await cache2().invalidate(stream);
|
|
1230
1241
|
}
|
|
1231
1242
|
})
|
|
1232
1243
|
);
|
|
@@ -1261,7 +1272,7 @@ var CorrelateCycle = class {
|
|
|
1261
1272
|
async init() {
|
|
1262
1273
|
if (this._initialized) return;
|
|
1263
1274
|
this._initialized = true;
|
|
1264
|
-
const { watermark } = await
|
|
1275
|
+
const { watermark } = await store2().subscribe([...this.staticTargets]);
|
|
1265
1276
|
this._checkpoint = watermark;
|
|
1266
1277
|
this.onInit?.();
|
|
1267
1278
|
for (const { stream } of this.staticTargets) {
|
|
@@ -1280,7 +1291,7 @@ var CorrelateCycle = class {
|
|
|
1280
1291
|
const after = Math.max(this._checkpoint, query.after || -1);
|
|
1281
1292
|
const correlated = /* @__PURE__ */ new Map();
|
|
1282
1293
|
let last_id = after;
|
|
1283
|
-
await
|
|
1294
|
+
await store2().query(
|
|
1284
1295
|
(event) => {
|
|
1285
1296
|
last_id = event.id;
|
|
1286
1297
|
const register = this.registry.events[event.name];
|
|
@@ -1800,12 +1811,12 @@ var SettleLoop = class {
|
|
|
1800
1811
|
};
|
|
1801
1812
|
|
|
1802
1813
|
// src/internal/drain.ts
|
|
1803
|
-
var claim = (lagging, leading, by, millis) =>
|
|
1814
|
+
var claim = (lagging, leading, by, millis) => store2().claim(lagging, leading, by, millis);
|
|
1804
1815
|
async function fetch(leased, eventLimit) {
|
|
1805
1816
|
return Promise.all(
|
|
1806
1817
|
leased.map(async ({ stream, source, at, lagging }) => {
|
|
1807
1818
|
const events = [];
|
|
1808
|
-
await
|
|
1819
|
+
await store2().query((e) => events.push(e), {
|
|
1809
1820
|
stream: source,
|
|
1810
1821
|
after: at,
|
|
1811
1822
|
limit: eventLimit
|
|
@@ -1814,9 +1825,9 @@ async function fetch(leased, eventLimit) {
|
|
|
1814
1825
|
})
|
|
1815
1826
|
);
|
|
1816
1827
|
}
|
|
1817
|
-
var ack = (leases) =>
|
|
1818
|
-
var block = (leases) =>
|
|
1819
|
-
var subscribe = (streams) =>
|
|
1828
|
+
var ack = (leases) => store2().ack(leases);
|
|
1829
|
+
var block = (leases) => store2().block(leases);
|
|
1830
|
+
var subscribe = (streams) => store2().subscribe(streams);
|
|
1820
1831
|
|
|
1821
1832
|
// src/internal/event-sourcing.ts
|
|
1822
1833
|
var import_node_crypto3 = require("crypto");
|
|
@@ -1824,7 +1835,7 @@ var import_act_patch = require("@rotorsoft/act-patch");
|
|
|
1824
1835
|
async function snap(snapshot) {
|
|
1825
1836
|
try {
|
|
1826
1837
|
const { id, stream, name, meta, version } = snapshot.event;
|
|
1827
|
-
await
|
|
1838
|
+
await store2().commit(
|
|
1828
1839
|
stream,
|
|
1829
1840
|
[{ name: SNAP_EVENT, data: snapshot.state }],
|
|
1830
1841
|
{
|
|
@@ -1840,7 +1851,7 @@ async function snap(snapshot) {
|
|
|
1840
1851
|
}
|
|
1841
1852
|
async function tombstone(stream, expectedVersion, correlation) {
|
|
1842
1853
|
try {
|
|
1843
|
-
const [committed] = await
|
|
1854
|
+
const [committed] = await store2().commit(
|
|
1844
1855
|
stream,
|
|
1845
1856
|
[{ name: TOMBSTONE_EVENT, data: {} }],
|
|
1846
1857
|
{ correlation, causation: {} },
|
|
@@ -1854,7 +1865,7 @@ async function tombstone(stream, expectedVersion, correlation) {
|
|
|
1854
1865
|
}
|
|
1855
1866
|
async function load(me, stream, callback, asOf) {
|
|
1856
1867
|
const timeTravel = !!asOf && Object.values(asOf).some((v) => v !== void 0);
|
|
1857
|
-
const cached = timeTravel ? void 0 : await
|
|
1868
|
+
const cached = timeTravel ? void 0 : await cache2().get(stream);
|
|
1858
1869
|
const cache_hit = !!cached;
|
|
1859
1870
|
let state2 = cached?.state ?? (me.init ? me.init() : {});
|
|
1860
1871
|
let patches = cached?.patches ?? 0;
|
|
@@ -1862,7 +1873,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
1862
1873
|
let version = cached?.version ?? -1;
|
|
1863
1874
|
let replayed = 0;
|
|
1864
1875
|
let event;
|
|
1865
|
-
await
|
|
1876
|
+
await store2().query(
|
|
1866
1877
|
(e) => {
|
|
1867
1878
|
event = e;
|
|
1868
1879
|
version = e.version;
|
|
@@ -1897,7 +1908,7 @@ async function load(me, stream, callback, asOf) {
|
|
|
1897
1908
|
}
|
|
1898
1909
|
);
|
|
1899
1910
|
if (replayed > 0 && !timeTravel && event) {
|
|
1900
|
-
await
|
|
1911
|
+
await cache2().set(stream, {
|
|
1901
1912
|
state: state2,
|
|
1902
1913
|
version,
|
|
1903
1914
|
event_id: event.id,
|
|
@@ -1970,7 +1981,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1970
1981
|
};
|
|
1971
1982
|
let committed;
|
|
1972
1983
|
try {
|
|
1973
|
-
committed = await
|
|
1984
|
+
committed = await store2().commit(
|
|
1974
1985
|
stream,
|
|
1975
1986
|
emitted,
|
|
1976
1987
|
meta,
|
|
@@ -1982,7 +1993,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1982
1993
|
);
|
|
1983
1994
|
} catch (error) {
|
|
1984
1995
|
if (error instanceof ConcurrencyError) {
|
|
1985
|
-
await
|
|
1996
|
+
await cache2().invalidate(stream);
|
|
1986
1997
|
}
|
|
1987
1998
|
throw error;
|
|
1988
1999
|
}
|
|
@@ -2004,7 +2015,7 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
2004
2015
|
});
|
|
2005
2016
|
const last = snapshots.at(-1);
|
|
2006
2017
|
const snapped = me.snap?.(last);
|
|
2007
|
-
|
|
2018
|
+
cache2().set(stream, {
|
|
2008
2019
|
state: last.state,
|
|
2009
2020
|
version: last.event.version,
|
|
2010
2021
|
event_id: last.event.id,
|
|
@@ -2199,6 +2210,7 @@ var Act = class {
|
|
|
2199
2210
|
this.registry = registry;
|
|
2200
2211
|
this._states = _states;
|
|
2201
2212
|
this._batch_handlers = batchHandlers;
|
|
2213
|
+
this._scoped = options.scoped ? (fn) => scoped.run(options.scoped, fn) : (fn) => fn();
|
|
2202
2214
|
this._es = buildEs(this._logger);
|
|
2203
2215
|
this._cd = buildDrain(this._logger);
|
|
2204
2216
|
this._handle = buildHandle({
|
|
@@ -2244,7 +2256,7 @@ var Act = class {
|
|
|
2244
2256
|
},
|
|
2245
2257
|
options.settleDebounceMs ?? DEFAULT_SETTLE_DEBOUNCE_MS
|
|
2246
2258
|
);
|
|
2247
|
-
this._notify_disposer = this._wireNotify();
|
|
2259
|
+
this._notify_disposer = this._wireNotify(options.scoped?.store ?? store2());
|
|
2248
2260
|
dispose(async () => {
|
|
2249
2261
|
this._emitter.removeAllListeners();
|
|
2250
2262
|
this.stop_correlations();
|
|
@@ -2313,6 +2325,11 @@ var Act = class {
|
|
|
2313
2325
|
_event_to_state;
|
|
2314
2326
|
/** Logger resolved at construction time (after user port configuration) */
|
|
2315
2327
|
_logger = log();
|
|
2328
|
+
/** Wraps a public-method body so internal `store()`/`cache()` resolve to the
|
|
2329
|
+
* per-Act ports (ACT-501). No-op when the Act is unscoped — so the singleton
|
|
2330
|
+
* path keeps reading fresh `store()`/`cache()` per call, which matters for
|
|
2331
|
+
* tests that dispose and re-seed mid-suite. */
|
|
2332
|
+
_scoped;
|
|
2316
2333
|
/** Pre-bound IAct methods reused across drain cycles. Only `do` varies per
|
|
2317
2334
|
* payload (it captures the triggering event for reactingTo auto-inject). */
|
|
2318
2335
|
_bound_do = this.do.bind(this);
|
|
@@ -2328,9 +2345,8 @@ var Act = class {
|
|
|
2328
2345
|
* subscription was made). Errors during subscription are logged but
|
|
2329
2346
|
* never thrown — `notify` is a hint, not a contract.
|
|
2330
2347
|
*/
|
|
2331
|
-
async _wireNotify() {
|
|
2348
|
+
async _wireNotify(s) {
|
|
2332
2349
|
if (this._reactive_events.size === 0) return void 0;
|
|
2333
|
-
const s = store();
|
|
2334
2350
|
if (!s.notify) return void 0;
|
|
2335
2351
|
try {
|
|
2336
2352
|
return await s.notify((notification) => {
|
|
@@ -2434,35 +2450,39 @@ var Act = class {
|
|
|
2434
2450
|
* @see {@link ValidationError}, {@link InvariantError}, {@link ConcurrencyError}
|
|
2435
2451
|
*/
|
|
2436
2452
|
async do(action2, target, payload, reactingTo, skipValidation = false) {
|
|
2437
|
-
|
|
2438
|
-
this.
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
this.
|
|
2449
|
-
|
|
2453
|
+
return this._scoped(async () => {
|
|
2454
|
+
const snapshots = await this._es.action(
|
|
2455
|
+
this.registry.actions[action2],
|
|
2456
|
+
action2,
|
|
2457
|
+
target,
|
|
2458
|
+
payload,
|
|
2459
|
+
reactingTo,
|
|
2460
|
+
skipValidation
|
|
2461
|
+
);
|
|
2462
|
+
if (this._reactive_events.size > 0) {
|
|
2463
|
+
for (const snap2 of snapshots) {
|
|
2464
|
+
if (snap2.event?.name && this._reactive_events.has(snap2.event.name)) {
|
|
2465
|
+
this._drain.arm();
|
|
2466
|
+
break;
|
|
2467
|
+
}
|
|
2450
2468
|
}
|
|
2451
2469
|
}
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2470
|
+
this.emit("committed", snapshots);
|
|
2471
|
+
return snapshots;
|
|
2472
|
+
});
|
|
2455
2473
|
}
|
|
2456
2474
|
async load(stateOrName, stream, callback, asOf) {
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2475
|
+
return this._scoped(async () => {
|
|
2476
|
+
let merged;
|
|
2477
|
+
if (typeof stateOrName === "string") {
|
|
2478
|
+
const found = this._states.get(stateOrName);
|
|
2479
|
+
if (!found) throw new Error(`State "${stateOrName}" not found`);
|
|
2480
|
+
merged = found;
|
|
2481
|
+
} else {
|
|
2482
|
+
merged = this._states.get(stateOrName.name) || stateOrName;
|
|
2483
|
+
}
|
|
2484
|
+
return await this._es.load(merged, stream, callback, asOf);
|
|
2485
|
+
});
|
|
2466
2486
|
}
|
|
2467
2487
|
/**
|
|
2468
2488
|
* Queries the event store for events matching a filter.
|
|
@@ -2511,14 +2531,16 @@ var Act = class {
|
|
|
2511
2531
|
* @see {@link query_array} for loading events into memory
|
|
2512
2532
|
*/
|
|
2513
2533
|
async query(query, callback) {
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2534
|
+
return this._scoped(async () => {
|
|
2535
|
+
let first;
|
|
2536
|
+
let last;
|
|
2537
|
+
const count = await store2().query((e) => {
|
|
2538
|
+
if (!first) first = e;
|
|
2539
|
+
last = e;
|
|
2540
|
+
callback?.(e);
|
|
2541
|
+
}, query);
|
|
2542
|
+
return { first, last, count };
|
|
2543
|
+
});
|
|
2522
2544
|
}
|
|
2523
2545
|
/**
|
|
2524
2546
|
* Queries the event store and returns all matching events in memory.
|
|
@@ -2547,9 +2569,11 @@ var Act = class {
|
|
|
2547
2569
|
* @see {@link query} for large result sets
|
|
2548
2570
|
*/
|
|
2549
2571
|
async query_array(query) {
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2572
|
+
return this._scoped(async () => {
|
|
2573
|
+
const events = [];
|
|
2574
|
+
await store2().query((e) => events.push(e), query);
|
|
2575
|
+
return events;
|
|
2576
|
+
});
|
|
2553
2577
|
}
|
|
2554
2578
|
/**
|
|
2555
2579
|
* Processes pending reactions by draining uncommitted events from the event store.
|
|
@@ -2589,7 +2613,7 @@ var Act = class {
|
|
|
2589
2613
|
* @see {@link start_correlations} for automatic correlation
|
|
2590
2614
|
*/
|
|
2591
2615
|
async drain(options = {}) {
|
|
2592
|
-
return this._drain.drain(options);
|
|
2616
|
+
return this._scoped(() => this._drain.drain(options));
|
|
2593
2617
|
}
|
|
2594
2618
|
/**
|
|
2595
2619
|
* Discovers and registers new streams dynamically based on reaction resolvers.
|
|
@@ -2637,7 +2661,7 @@ var Act = class {
|
|
|
2637
2661
|
* @see {@link stop_correlations} to stop automatic correlation
|
|
2638
2662
|
*/
|
|
2639
2663
|
async correlate(query = { after: -1, limit: 10 }) {
|
|
2640
|
-
return this._correlate.correlate(query);
|
|
2664
|
+
return this._scoped(() => this._correlate.correlate(query));
|
|
2641
2665
|
}
|
|
2642
2666
|
/**
|
|
2643
2667
|
* Starts automatic periodic correlation worker for discovering new streams.
|
|
@@ -2758,9 +2782,11 @@ var Act = class {
|
|
|
2758
2782
|
* @see {@link settle} for the debounced full-catch-up loop
|
|
2759
2783
|
*/
|
|
2760
2784
|
async reset(streams) {
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2785
|
+
return this._scoped(async () => {
|
|
2786
|
+
const count = await store2().reset(streams);
|
|
2787
|
+
if (count > 0 && this._reactive_events.size > 0) this._drain.arm();
|
|
2788
|
+
return count;
|
|
2789
|
+
});
|
|
2764
2790
|
}
|
|
2765
2791
|
/**
|
|
2766
2792
|
* Bulk-update scheduling priority for streams matching `filter`.
|
|
@@ -2801,7 +2827,7 @@ var Act = class {
|
|
|
2801
2827
|
* @see {@link claim} for how priority biases scheduling
|
|
2802
2828
|
*/
|
|
2803
2829
|
async prioritize(filter, priority) {
|
|
2804
|
-
return
|
|
2830
|
+
return this._scoped(() => store2().prioritize(filter, priority));
|
|
2805
2831
|
}
|
|
2806
2832
|
/**
|
|
2807
2833
|
* Close the books — guard, archive, truncate, and optionally restart streams.
|
|
@@ -2838,16 +2864,18 @@ var Act = class {
|
|
|
2838
2864
|
*/
|
|
2839
2865
|
async close(targets) {
|
|
2840
2866
|
if (!targets.length) return { truncated: /* @__PURE__ */ new Map(), skipped: [] };
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2867
|
+
return this._scoped(async () => {
|
|
2868
|
+
await this.correlate({ limit: 1e3 });
|
|
2869
|
+
const result = await runCloseCycle(targets, {
|
|
2870
|
+
reactiveEventsSize: this._reactive_events.size,
|
|
2871
|
+
eventToState: this._event_to_state,
|
|
2872
|
+
load: this._es.load,
|
|
2873
|
+
tombstone: this._es.tombstone,
|
|
2874
|
+
logger: this._logger
|
|
2875
|
+
});
|
|
2876
|
+
this.emit("closed", result);
|
|
2877
|
+
return result;
|
|
2848
2878
|
});
|
|
2849
|
-
this.emit("closed", result);
|
|
2850
|
-
return result;
|
|
2851
2879
|
}
|
|
2852
2880
|
/**
|
|
2853
2881
|
* Debounced, non-blocking correlate→drain cycle.
|
|
@@ -2901,6 +2929,41 @@ function act() {
|
|
|
2901
2929
|
};
|
|
2902
2930
|
const pendingProjections = [];
|
|
2903
2931
|
const batchHandlers = /* @__PURE__ */ new Map();
|
|
2932
|
+
let _built = false;
|
|
2933
|
+
const finalizeDeprecations = () => {
|
|
2934
|
+
const deprecationSummary = [];
|
|
2935
|
+
for (const state2 of states.values()) {
|
|
2936
|
+
const eventNames = Object.keys(state2.events);
|
|
2937
|
+
const deprecated = deprecatedEventNames(eventNames);
|
|
2938
|
+
if (deprecated.size === 0) continue;
|
|
2939
|
+
state2._deprecated = deprecated;
|
|
2940
|
+
for (const name of deprecated) {
|
|
2941
|
+
const current = currentVersionOf(name, eventNames);
|
|
2942
|
+
deprecationSummary.push({
|
|
2943
|
+
stateName: state2.name,
|
|
2944
|
+
deprecated: name,
|
|
2945
|
+
current
|
|
2946
|
+
});
|
|
2947
|
+
}
|
|
2948
|
+
for (const [actionName, handler] of Object.entries(state2.on)) {
|
|
2949
|
+
const staticTarget = handler?._staticEmit;
|
|
2950
|
+
if (staticTarget && deprecated.has(staticTarget)) {
|
|
2951
|
+
const current = currentVersionOf(staticTarget, eventNames);
|
|
2952
|
+
throw new Error(
|
|
2953
|
+
`Action "${actionName}" in state "${state2.name}" emits deprecated event "${staticTarget}". A newer version exists: "${current}". Update the .emit() call to target the current version. The reducer (.patch) for "${staticTarget}" stays as-is \u2014 historical events still need it.`
|
|
2954
|
+
);
|
|
2955
|
+
}
|
|
2956
|
+
}
|
|
2957
|
+
}
|
|
2958
|
+
if (deprecationSummary.length > 0) {
|
|
2959
|
+
const list = deprecationSummary.map(
|
|
2960
|
+
(d) => `"${d.deprecated}" (current: "${d.current}", state: "${d.stateName}")`
|
|
2961
|
+
).join(", ");
|
|
2962
|
+
log().info(
|
|
2963
|
+
`Act registered ${deprecationSummary.length} deprecated event(s): ${list}. These are legacy versions kept for the read path. Consider truncating closed streams via app.close() when feasible to reduce historical event load. See docs/docs/architecture/event-schema-evolution.md.`
|
|
2964
|
+
);
|
|
2965
|
+
}
|
|
2966
|
+
};
|
|
2904
2967
|
const builder = {
|
|
2905
2968
|
withState: (state2) => {
|
|
2906
2969
|
registerState(state2, states, registry.actions, registry.events);
|
|
@@ -2944,41 +3007,13 @@ function act() {
|
|
|
2944
3007
|
}
|
|
2945
3008
|
}),
|
|
2946
3009
|
build: (options) => {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
const deprecationSummary = [];
|
|
2952
|
-
for (const state2 of states.values()) {
|
|
2953
|
-
const eventNames = Object.keys(state2.events);
|
|
2954
|
-
const deprecated = deprecatedEventNames(eventNames);
|
|
2955
|
-
if (deprecated.size === 0) continue;
|
|
2956
|
-
state2._deprecated = deprecated;
|
|
2957
|
-
for (const name of deprecated) {
|
|
2958
|
-
const current = currentVersionOf(name, eventNames);
|
|
2959
|
-
deprecationSummary.push({
|
|
2960
|
-
stateName: state2.name,
|
|
2961
|
-
deprecated: name,
|
|
2962
|
-
current
|
|
2963
|
-
});
|
|
2964
|
-
}
|
|
2965
|
-
for (const [actionName, handler] of Object.entries(state2.on)) {
|
|
2966
|
-
const staticTarget = handler?._staticEmit;
|
|
2967
|
-
if (staticTarget && deprecated.has(staticTarget)) {
|
|
2968
|
-
const current = currentVersionOf(staticTarget, eventNames);
|
|
2969
|
-
throw new Error(
|
|
2970
|
-
`Action "${actionName}" in state "${state2.name}" emits deprecated event "${staticTarget}". A newer version exists: "${current}". Update the .emit() call to target the current version. The reducer (.patch) for "${staticTarget}" stays as-is \u2014 historical events still need it.`
|
|
2971
|
-
);
|
|
2972
|
-
}
|
|
3010
|
+
if (!_built) {
|
|
3011
|
+
for (const proj of pendingProjections) {
|
|
3012
|
+
mergeProjection(proj, registry.events);
|
|
3013
|
+
registerBatchHandler(proj, batchHandlers);
|
|
2973
3014
|
}
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
const list = deprecationSummary.map(
|
|
2977
|
-
(d) => `"${d.deprecated}" (current: "${d.current}", state: "${d.stateName}")`
|
|
2978
|
-
).join(", ");
|
|
2979
|
-
log().info(
|
|
2980
|
-
`Act registered ${deprecationSummary.length} deprecated event(s): ${list}. These are legacy versions kept for the read path. Consider truncating closed streams via app.close() when feasible to reduce historical event load. See docs/docs/architecture/event-schema-evolution.md.`
|
|
2981
|
-
);
|
|
3015
|
+
finalizeDeprecations();
|
|
3016
|
+
_built = true;
|
|
2982
3017
|
}
|
|
2983
3018
|
return new Act(
|
|
2984
3019
|
registry,
|
|
@@ -3222,6 +3257,7 @@ function action_builder(state2) {
|
|
|
3222
3257
|
log,
|
|
3223
3258
|
port,
|
|
3224
3259
|
projection,
|
|
3260
|
+
scoped,
|
|
3225
3261
|
sleep,
|
|
3226
3262
|
slice,
|
|
3227
3263
|
state,
|