@rotorsoft/act 0.35.2 → 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/README.md +2 -0
- 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/@types/internal/merge.d.ts.map +1 -1
- package/dist/index.cjs +92 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +92 -5
- 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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/internal/merge.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,KAAK,EAAU,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAuDvD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAON;
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../../src/internal/merge.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AACpE,OAAO,KAAK,EAAU,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAuDvD;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC3B,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,EACzC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAON;AAmID;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,EAC3D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,CAAC,GAC1D,IAAI,CAQN;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,CAAC,GAAG,CAAC,EACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAC1B,IAAI,CAiBN;AAGD,eAAO,MAAM,MAAM,GAAI,YAAY;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE;;;CAGnD,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) {
|
|
@@ -1542,7 +1579,11 @@ function mergeIntoExisting(state2, existing, states, actions, events) {
|
|
|
1542
1579
|
}
|
|
1543
1580
|
for (const name of Object.keys(state2.events)) {
|
|
1544
1581
|
if (existing.events[name] === state2.events[name]) continue;
|
|
1545
|
-
if (existing.events[name])
|
|
1582
|
+
if (existing.events[name]) {
|
|
1583
|
+
throw new Error(
|
|
1584
|
+
`Event "${name}" in state "${state2.name}" is declared with different Zod schemas across slices. Cross-slice event schemas must reference the same instance \u2014 extract a shared schema (e.g. \`export const ${name} = z.object({ ... })\` in a shared module) and import it in every slice that declares it.`
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1546
1587
|
if (events[name]) throw new Error(`Duplicate event "${name}"`);
|
|
1547
1588
|
}
|
|
1548
1589
|
const mergedPatch = mergePatches(existing.patch, state2.patch, state2.name);
|
|
@@ -1893,6 +1934,20 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
|
|
|
1893
1934
|
return [snapshot];
|
|
1894
1935
|
}
|
|
1895
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
|
+
}
|
|
1896
1951
|
const emitted = tuples.map(([name, data]) => ({
|
|
1897
1952
|
name,
|
|
1898
1953
|
data: skipValidation ? data : validate(name, data, me.events[name])
|
|
@@ -2893,6 +2948,38 @@ function act() {
|
|
|
2893
2948
|
mergeProjection(proj, registry.events);
|
|
2894
2949
|
registerBatchHandler(proj, batchHandlers);
|
|
2895
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
|
+
}
|
|
2896
2983
|
return new Act(
|
|
2897
2984
|
registry,
|
|
2898
2985
|
states,
|
|
@@ -3079,10 +3166,10 @@ function action_builder(state2) {
|
|
|
3079
3166
|
function emit(handler) {
|
|
3080
3167
|
if (typeof handler === "string") {
|
|
3081
3168
|
const eventName = handler;
|
|
3082
|
-
|
|
3083
|
-
eventName
|
|
3084
|
-
|
|
3085
|
-
];
|
|
3169
|
+
const emitFn = Object.assign((payload) => [eventName, payload], {
|
|
3170
|
+
_staticEmit: eventName
|
|
3171
|
+
});
|
|
3172
|
+
internal.on[action2] = emitFn;
|
|
3086
3173
|
} else {
|
|
3087
3174
|
internal.on[action2] = handler;
|
|
3088
3175
|
}
|