@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/index.js CHANGED
@@ -1317,6 +1317,43 @@ var DrainController = class {
1317
1317
  }
1318
1318
  };
1319
1319
 
1320
+ // src/internal/event-versions.ts
1321
+ var VERSION_SUFFIX = /^(.+?)_v(\d+)$/;
1322
+ function parse(name) {
1323
+ const m = name.match(VERSION_SUFFIX);
1324
+ if (m) {
1325
+ const v = Number.parseInt(m[2], 10);
1326
+ if (v >= 2) return { base: m[1], version: v };
1327
+ }
1328
+ return { base: name, version: 1 };
1329
+ }
1330
+ function deprecatedEventNames(names) {
1331
+ const groups = /* @__PURE__ */ new Map();
1332
+ for (const name of names) {
1333
+ const { base, version } = parse(name);
1334
+ const list = groups.get(base);
1335
+ if (list) list.push({ version, name });
1336
+ else groups.set(base, [{ version, name }]);
1337
+ }
1338
+ const deprecated = /* @__PURE__ */ new Set();
1339
+ for (const list of groups.values()) {
1340
+ if (list.length < 2) continue;
1341
+ list.sort((a, b) => b.version - a.version);
1342
+ for (let i = 1; i < list.length; i++) deprecated.add(list[i].name);
1343
+ }
1344
+ return deprecated;
1345
+ }
1346
+ function currentVersionOf(deprecatedName, allNames) {
1347
+ const target = parse(deprecatedName);
1348
+ let highest;
1349
+ for (const name of allNames) {
1350
+ const { base, version } = parse(name);
1351
+ if (base !== target.base) continue;
1352
+ if (!highest || version > highest.version) highest = { version, name };
1353
+ }
1354
+ return highest && highest.version > target.version ? highest.name : void 0;
1355
+ }
1356
+
1320
1357
  // src/internal/merge.ts
1321
1358
  import { ZodObject } from "zod";
1322
1359
  function baseTypeName(zodType) {
@@ -1729,6 +1766,20 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
1729
1766
  return [snapshot];
1730
1767
  }
1731
1768
  const tuples = Array.isArray(result[0]) ? result : [result];
1769
+ const deprecated = me._deprecated;
1770
+ if (deprecated && deprecated.size > 0) {
1771
+ const me_ = me;
1772
+ const warned = me_._warned ?? (me_._warned = /* @__PURE__ */ new Set());
1773
+ for (const [name] of tuples) {
1774
+ const evt = name;
1775
+ if (deprecated.has(evt) && !warned.has(evt)) {
1776
+ warned.add(evt);
1777
+ log().warn(
1778
+ `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)`
1779
+ );
1780
+ }
1781
+ }
1782
+ }
1732
1783
  const emitted = tuples.map(([name, data]) => ({
1733
1784
  name,
1734
1785
  data: skipValidation ? data : validate(name, data, me.events[name])
@@ -2729,6 +2780,38 @@ function act() {
2729
2780
  mergeProjection(proj, registry.events);
2730
2781
  registerBatchHandler(proj, batchHandlers);
2731
2782
  }
2783
+ const deprecationSummary = [];
2784
+ for (const state2 of states.values()) {
2785
+ const eventNames = Object.keys(state2.events);
2786
+ const deprecated = deprecatedEventNames(eventNames);
2787
+ if (deprecated.size === 0) continue;
2788
+ state2._deprecated = deprecated;
2789
+ for (const name of deprecated) {
2790
+ const current = currentVersionOf(name, eventNames);
2791
+ deprecationSummary.push({
2792
+ stateName: state2.name,
2793
+ deprecated: name,
2794
+ current
2795
+ });
2796
+ }
2797
+ for (const [actionName, handler] of Object.entries(state2.on)) {
2798
+ const staticTarget = handler?._staticEmit;
2799
+ if (staticTarget && deprecated.has(staticTarget)) {
2800
+ const current = currentVersionOf(staticTarget, eventNames);
2801
+ throw new Error(
2802
+ `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.`
2803
+ );
2804
+ }
2805
+ }
2806
+ }
2807
+ if (deprecationSummary.length > 0) {
2808
+ const list = deprecationSummary.map(
2809
+ (d) => `"${d.deprecated}" (current: "${d.current}", state: "${d.stateName}")`
2810
+ ).join(", ");
2811
+ log().info(
2812
+ `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.`
2813
+ );
2814
+ }
2732
2815
  return new Act(
2733
2816
  registry,
2734
2817
  states,
@@ -2915,10 +2998,10 @@ function action_builder(state2) {
2915
2998
  function emit(handler) {
2916
2999
  if (typeof handler === "string") {
2917
3000
  const eventName = handler;
2918
- internal.on[action2] = (payload) => [
2919
- eventName,
2920
- payload
2921
- ];
3001
+ const emitFn = Object.assign((payload) => [eventName, payload], {
3002
+ _staticEmit: eventName
3003
+ });
3004
+ internal.on[action2] = emitFn;
2922
3005
  } else {
2923
3006
  internal.on[action2] = handler;
2924
3007
  }