@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/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) {
@@ -1374,7 +1411,11 @@ function mergeIntoExisting(state2, existing, states, actions, events) {
1374
1411
  }
1375
1412
  for (const name of Object.keys(state2.events)) {
1376
1413
  if (existing.events[name] === state2.events[name]) continue;
1377
- if (existing.events[name]) continue;
1414
+ if (existing.events[name]) {
1415
+ throw new Error(
1416
+ `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.`
1417
+ );
1418
+ }
1378
1419
  if (events[name]) throw new Error(`Duplicate event "${name}"`);
1379
1420
  }
1380
1421
  const mergedPatch = mergePatches(existing.patch, state2.patch, state2.name);
@@ -1725,6 +1766,20 @@ async function action(me, action2, target, payload, reactingTo, skipValidation =
1725
1766
  return [snapshot];
1726
1767
  }
1727
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
+ }
1728
1783
  const emitted = tuples.map(([name, data]) => ({
1729
1784
  name,
1730
1785
  data: skipValidation ? data : validate(name, data, me.events[name])
@@ -2725,6 +2780,38 @@ function act() {
2725
2780
  mergeProjection(proj, registry.events);
2726
2781
  registerBatchHandler(proj, batchHandlers);
2727
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
+ }
2728
2815
  return new Act(
2729
2816
  registry,
2730
2817
  states,
@@ -2911,10 +2998,10 @@ function action_builder(state2) {
2911
2998
  function emit(handler) {
2912
2999
  if (typeof handler === "string") {
2913
3000
  const eventName = handler;
2914
- internal.on[action2] = (payload) => [
2915
- eventName,
2916
- payload
2917
- ];
3001
+ const emitFn = Object.assign((payload) => [eventName, payload], {
3002
+ _staticEmit: eventName
3003
+ });
3004
+ internal.on[action2] = emitFn;
2918
3005
  } else {
2919
3006
  internal.on[action2] = handler;
2920
3007
  }