@rotorsoft/act 0.36.0 → 0.37.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/.tsbuildinfo +1 -1
- package/dist/@types/builders/act-builder.d.ts.map +1 -1
- package/dist/@types/internal/event-sourcing.d.ts.map +1 -1
- package/dist/@types/internal/event-versions.d.ts +44 -0
- package/dist/@types/internal/event-versions.d.ts.map +1 -0
- package/dist/@types/internal/index.d.ts +1 -0
- package/dist/@types/internal/index.d.ts.map +1 -1
- package/dist/index.cjs +87 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +87 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module event-versions
|
|
3
|
+
* @category Internal
|
|
4
|
+
*
|
|
5
|
+
* Auto-deprecation of legacy event versions via the `_v<digits>` naming
|
|
6
|
+
* convention (ACT-403).
|
|
7
|
+
*
|
|
8
|
+
* Act's schema-evolution pattern keeps the old and new event names alive
|
|
9
|
+
* forever — the old name on the read path (reducers), the new on the write
|
|
10
|
+
* path (emissions). This module reads the convention to identify legacy
|
|
11
|
+
* versions automatically; the framework then enforces "emit only the
|
|
12
|
+
* current version" at build time and warns at runtime for dynamic emits.
|
|
13
|
+
*
|
|
14
|
+
* Convention pin: only `_v<digits>` with digits ≥ 2 counts as a version
|
|
15
|
+
* suffix. `Foo_v1` is just a literal event name (the base `Foo` is
|
|
16
|
+
* implicitly v1). Pinning here keeps the contract surface small.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Returns the set of event names that are deprecated by virtue of having
|
|
22
|
+
* a higher-numbered sibling in the registry. The highest version in each
|
|
23
|
+
* group is the current version; every lower version is deprecated.
|
|
24
|
+
*
|
|
25
|
+
* Gaps are allowed: `{Foo, Foo_v3}` → `Foo` is deprecated, `Foo_v3` is
|
|
26
|
+
* current. The framework picks the max regardless of contiguity.
|
|
27
|
+
*
|
|
28
|
+
* Single-version groups (no siblings) yield no deprecations.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
export declare function deprecatedEventNames(names: Iterable<string>): Set<string>;
|
|
33
|
+
/**
|
|
34
|
+
* Given a deprecated event name and the full set of event names in its
|
|
35
|
+
* registry, returns the current (highest-version) sibling. Used to build
|
|
36
|
+
* actionable error messages — "use `Foo_v3` instead."
|
|
37
|
+
*
|
|
38
|
+
* Returns `undefined` if the event has no higher-versioned sibling (which
|
|
39
|
+
* means the caller's classification is stale or wrong).
|
|
40
|
+
*
|
|
41
|
+
* @internal
|
|
42
|
+
*/
|
|
43
|
+
export declare function currentVersionOf(deprecatedName: string, allNames: Iterable<string>): string | undefined;
|
|
44
|
+
//# sourceMappingURL=event-versions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"event-versions.d.ts","sourceRoot":"","sources":["../../../src/internal/event-versions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAmBH;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,cAAc,EAAE,MAAM,EACtB,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GACzB,MAAM,GAAG,SAAS,CASpB"}
|
|
@@ -22,6 +22,7 @@ export { CorrelateCycle } from "./correlate-cycle.js";
|
|
|
22
22
|
export type { DrainOps } from "./drain.js";
|
|
23
23
|
export { DrainController, type Handle, type HandleBatch, } from "./drain-cycle.js";
|
|
24
24
|
export type { EsOps } from "./event-sourcing.js";
|
|
25
|
+
export { currentVersionOf, deprecatedEventNames, } from "./event-versions.js";
|
|
25
26
|
export { _this_, mergeEventRegister, mergeProjection, registerState, } from "./merge.js";
|
|
26
27
|
export { buildHandle, buildHandleBatch } from "./reactions.js";
|
|
27
28
|
export { SettleLoop } from "./settle.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/internal/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,MAAM,EACN,kBAAkB,EAClB,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/internal/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EACL,eAAe,EACf,KAAK,MAAM,EACX,KAAK,WAAW,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,MAAM,EACN,kBAAkB,EAClB,eAAe,EACf,aAAa,GACd,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.cjs
CHANGED
|
@@ -1485,6 +1485,43 @@ var DrainController = class {
|
|
|
1485
1485
|
}
|
|
1486
1486
|
};
|
|
1487
1487
|
|
|
1488
|
+
// src/internal/event-versions.ts
|
|
1489
|
+
var VERSION_SUFFIX = /^(.+?)_v(\d+)$/;
|
|
1490
|
+
function parse(name) {
|
|
1491
|
+
const m = name.match(VERSION_SUFFIX);
|
|
1492
|
+
if (m) {
|
|
1493
|
+
const v = Number.parseInt(m[2], 10);
|
|
1494
|
+
if (v >= 2) return { base: m[1], version: v };
|
|
1495
|
+
}
|
|
1496
|
+
return { base: name, version: 1 };
|
|
1497
|
+
}
|
|
1498
|
+
function deprecatedEventNames(names) {
|
|
1499
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1500
|
+
for (const name of names) {
|
|
1501
|
+
const { base, version } = parse(name);
|
|
1502
|
+
const list = groups.get(base);
|
|
1503
|
+
if (list) list.push({ version, name });
|
|
1504
|
+
else groups.set(base, [{ version, name }]);
|
|
1505
|
+
}
|
|
1506
|
+
const deprecated = /* @__PURE__ */ new Set();
|
|
1507
|
+
for (const list of groups.values()) {
|
|
1508
|
+
if (list.length < 2) continue;
|
|
1509
|
+
list.sort((a, b) => b.version - a.version);
|
|
1510
|
+
for (let i = 1; i < list.length; i++) deprecated.add(list[i].name);
|
|
1511
|
+
}
|
|
1512
|
+
return deprecated;
|
|
1513
|
+
}
|
|
1514
|
+
function currentVersionOf(deprecatedName, allNames) {
|
|
1515
|
+
const target = parse(deprecatedName);
|
|
1516
|
+
let highest;
|
|
1517
|
+
for (const name of allNames) {
|
|
1518
|
+
const { base, version } = parse(name);
|
|
1519
|
+
if (base !== target.base) continue;
|
|
1520
|
+
if (!highest || version > highest.version) highest = { version, name };
|
|
1521
|
+
}
|
|
1522
|
+
return highest && highest.version > target.version ? highest.name : void 0;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1488
1525
|
// src/internal/merge.ts
|
|
1489
1526
|
var import_zod4 = require("zod");
|
|
1490
1527
|
function baseTypeName(zodType) {
|
|
@@ -1897,6 +1934,20 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1897
1934
|
return [snapshot];
|
|
1898
1935
|
}
|
|
1899
1936
|
const tuples = Array.isArray(result[0]) ? result : [result];
|
|
1937
|
+
const deprecated = me._deprecated;
|
|
1938
|
+
if (deprecated && deprecated.size > 0) {
|
|
1939
|
+
const me_ = me;
|
|
1940
|
+
const warned = me_._warned ?? (me_._warned = /* @__PURE__ */ new Set());
|
|
1941
|
+
for (const [name] of tuples) {
|
|
1942
|
+
const evt = name;
|
|
1943
|
+
if (deprecated.has(evt) && !warned.has(evt)) {
|
|
1944
|
+
warned.add(evt);
|
|
1945
|
+
log().warn(
|
|
1946
|
+
`Action "${String(action2)}" emitted deprecated event "${evt}". A newer version exists in the registry \u2014 update the action's .emit() to target the current version. (warned once per process)`
|
|
1947
|
+
);
|
|
1948
|
+
}
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1900
1951
|
const emitted = tuples.map(([name, data]) => ({
|
|
1901
1952
|
name,
|
|
1902
1953
|
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
@@ -2897,6 +2948,38 @@ function act() {
|
|
|
2897
2948
|
mergeProjection(proj, registry.events);
|
|
2898
2949
|
registerBatchHandler(proj, batchHandlers);
|
|
2899
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
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
if (deprecationSummary.length > 0) {
|
|
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
|
+
);
|
|
2982
|
+
}
|
|
2900
2983
|
return new Act(
|
|
2901
2984
|
registry,
|
|
2902
2985
|
states,
|
|
@@ -3083,10 +3166,10 @@ function action_builder(state2) {
|
|
|
3083
3166
|
function emit(handler) {
|
|
3084
3167
|
if (typeof handler === "string") {
|
|
3085
3168
|
const eventName = handler;
|
|
3086
|
-
|
|
3087
|
-
eventName
|
|
3088
|
-
|
|
3089
|
-
];
|
|
3169
|
+
const emitFn = Object.assign((payload) => [eventName, payload], {
|
|
3170
|
+
_staticEmit: eventName
|
|
3171
|
+
});
|
|
3172
|
+
internal.on[action2] = emitFn;
|
|
3090
3173
|
} else {
|
|
3091
3174
|
internal.on[action2] = handler;
|
|
3092
3175
|
}
|