@objectstack/service-automation 7.9.0 → 8.0.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
@@ -145,7 +145,7 @@ var AutomationEngine = class {
145
145
  description: `Deprecated alias of '${canonicalType}' (ADR-0018 M3). Author new flows with '${canonicalType}'.`,
146
146
  category: meta?.category ?? "io",
147
147
  source: "builtin",
148
- paradigms: meta?.paradigms ?? ["flow", "workflow_rule", "approval"],
148
+ paradigms: meta?.paradigms ?? ["flow", "approval"],
149
149
  supportsRetry: true,
150
150
  needsOutbox: meta?.needsOutbox ?? false,
151
151
  deprecated: true,
@@ -987,6 +987,9 @@ ${failures.join("\n")}`
987
987
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
988
988
  durationMs: Date.now() - stepStart
989
989
  });
990
+ if (result.childSteps?.length) {
991
+ steps.push(...result.childSteps);
992
+ }
990
993
  if (result.output) {
991
994
  for (const [key, value] of Object.entries(result.output)) {
992
995
  variables.set(`${node.id}.${key}`, value);
@@ -1051,14 +1054,19 @@ ${failures.join("\n")}`
1051
1054
  * nodes/edges, so the main DAG traversal (`traverseNext`) is never aware of
1052
1055
  * scope markers — keeping the shared traversal untouched.
1053
1056
  *
1054
- * Body step logs are kept in a region-local array (not yet merged into the
1055
- * parent run log); surfacing per-iteration steps is a follow-up.
1057
+ * #1479: the executed body steps are **returned** (tagged with `grouping`)
1058
+ * so the calling container node can fold them into the parent run log via
1059
+ * `NodeExecutionResult.childSteps`. Tagging only fills fields left undefined,
1060
+ * so when regions nest, each step keeps its **innermost** container's
1061
+ * `parentNodeId` / `iteration` / `regionKind`. On failure the region throws
1062
+ * as before (preserving `try_catch` retry semantics); a failed attempt's
1063
+ * partial steps are not surfaced.
1056
1064
  *
1057
1065
  * Durable pause (`suspend`) inside a region is not supported in this
1058
1066
  * iteration — it is converted into a clear error (mirrors the `subflow`
1059
1067
  * nested-pause guard).
1060
1068
  */
1061
- async runRegion(region, variables, context) {
1069
+ async runRegion(region, variables, context, grouping) {
1062
1070
  const entryId = findRegionEntry(region);
1063
1071
  const entry = region.nodes.find((n) => n.id === entryId);
1064
1072
  if (!entry) {
@@ -1076,6 +1084,16 @@ ${failures.join("\n")}`
1076
1084
  }
1077
1085
  throw err;
1078
1086
  }
1087
+ if (grouping) {
1088
+ for (const step of regionSteps) {
1089
+ if (step.parentNodeId === void 0) {
1090
+ step.parentNodeId = grouping.parentNodeId;
1091
+ if (grouping.iteration !== void 0) step.iteration = grouping.iteration;
1092
+ if (grouping.regionKind !== void 0) step.regionKind = grouping.regionKind;
1093
+ }
1094
+ }
1095
+ }
1096
+ return regionSteps;
1079
1097
  }
1080
1098
  /**
1081
1099
  * Execute a promise with timeout using Promise.race.
@@ -1760,13 +1778,19 @@ function registerLoopNode(engine, ctx) {
1760
1778
  };
1761
1779
  }
1762
1780
  let iterations = 0;
1781
+ const childSteps = [];
1763
1782
  for (let i = 0; i < collection.length; i++) {
1764
1783
  variables.set(iteratorVariable, collection[i]);
1765
1784
  if (indexVariable) variables.set(indexVariable, i);
1766
- await engine.runRegion(body, variables, context ?? {});
1785
+ const iterSteps = await engine.runRegion(body, variables, context ?? {}, {
1786
+ parentNodeId: node.id,
1787
+ iteration: i,
1788
+ regionKind: "loop-body"
1789
+ });
1790
+ childSteps.push(...iterSteps);
1767
1791
  iterations++;
1768
1792
  }
1769
- return { success: true, output: { iterations } };
1793
+ return { success: true, output: { iterations }, childSteps };
1770
1794
  }
1771
1795
  });
1772
1796
  ctx.logger.info("[Loop Node] 1 built-in node executor registered");
@@ -1814,15 +1838,22 @@ function registerParallelNode(engine, ctx) {
1814
1838
  error: `parallel '${node.id}': config.branches must declare at least 2 branch regions`
1815
1839
  };
1816
1840
  }
1841
+ let branchSteps;
1817
1842
  try {
1818
- await Promise.all(
1819
- branches.map((branch) => engine.runRegion(branch, variables, context ?? {}))
1843
+ branchSteps = await Promise.all(
1844
+ branches.map(
1845
+ (branch, i) => engine.runRegion(branch, variables, context ?? {}, {
1846
+ parentNodeId: node.id,
1847
+ iteration: i,
1848
+ regionKind: "parallel-branch"
1849
+ })
1850
+ )
1820
1851
  );
1821
1852
  } catch (err) {
1822
1853
  const message = err instanceof Error ? err.message : String(err);
1823
1854
  return { success: false, error: `parallel '${node.id}': branch failed \u2014 ${message}` };
1824
1855
  }
1825
- return { success: true, output: { branches: branches.length } };
1856
+ return { success: true, output: { branches: branches.length }, childSteps: branchSteps.flat() };
1826
1857
  }
1827
1858
  });
1828
1859
  ctx.logger.info("[Parallel Node] 1 built-in node executor registered");
@@ -1893,8 +1924,11 @@ function registerTryCatchNode(engine, ctx) {
1893
1924
  if (delay > 0) await new Promise((r) => setTimeout(r, delay));
1894
1925
  }
1895
1926
  try {
1896
- await engine.runRegion(tryRegion, variables, ctxOrEmpty);
1897
- return { success: true, output: { attempts: attempt + 1, caught: false } };
1927
+ const trySteps = await engine.runRegion(tryRegion, variables, ctxOrEmpty, {
1928
+ parentNodeId: node.id,
1929
+ regionKind: "try"
1930
+ });
1931
+ return { success: true, output: { attempts: attempt + 1, caught: false }, childSteps: trySteps };
1898
1932
  } catch (err) {
1899
1933
  lastError = err instanceof Error ? err.message : String(err);
1900
1934
  }
@@ -1902,8 +1936,15 @@ function registerTryCatchNode(engine, ctx) {
1902
1936
  if (catchRegion != null) {
1903
1937
  variables.set(errorVariable, { nodeId: node.id, message: lastError });
1904
1938
  try {
1905
- await engine.runRegion(catchRegion, variables, ctxOrEmpty);
1906
- return { success: true, output: { attempts: maxRetries + 1, caught: true, error: lastError } };
1939
+ const catchSteps = await engine.runRegion(catchRegion, variables, ctxOrEmpty, {
1940
+ parentNodeId: node.id,
1941
+ regionKind: "catch"
1942
+ });
1943
+ return {
1944
+ success: true,
1945
+ output: { attempts: maxRetries + 1, caught: true, error: lastError },
1946
+ childSteps: catchSteps
1947
+ };
1907
1948
  } catch (catchErr) {
1908
1949
  const catchMsg = catchErr instanceof Error ? catchErr.message : String(catchErr);
1909
1950
  return { success: false, error: `try_catch '${node.id}': catch region failed \u2014 ${catchMsg}` };
@@ -2161,7 +2202,7 @@ function registerHttpNodes(engine, ctx) {
2161
2202
  // and the messaging HTTP outbox is wired).
2162
2203
  needsOutbox: true,
2163
2204
  supportsRetry: true,
2164
- paradigms: ["flow", "workflow_rule", "approval"],
2205
+ paradigms: ["flow", "approval"],
2165
2206
  configSchema: {
2166
2207
  type: "object",
2167
2208
  required: ["url"],
@@ -2271,8 +2312,9 @@ function registerConnectorNodes(engine, ctx) {
2271
2312
  category: "io",
2272
2313
  source: "builtin",
2273
2314
  supportsRetry: true,
2274
- // Present in all three authoring paradigms (ADR-0018 §registry table).
2275
- paradigms: ["flow", "workflow_rule", "approval"],
2315
+ // Present in both authoring paradigms (ADR-0018 §registry table;
2316
+ // workflow_rule retired per ADR-0019).
2317
+ paradigms: ["flow", "approval"],
2276
2318
  // Config contract — drives the Studio property form and flow validation.
2277
2319
  configSchema: {
2278
2320
  type: "object",
@@ -2347,7 +2389,7 @@ function registerNotifyNode(engine, ctx) {
2347
2389
  // Delivery is outbox-backed inside the messaging service (ADR-0030
2348
2390
  // emit → sys_notification_delivery), so it inherits retry/dead-letter.
2349
2391
  needsOutbox: true,
2350
- paradigms: ["flow", "workflow_rule", "approval"]
2392
+ paradigms: ["flow", "approval"]
2351
2393
  }),
2352
2394
  async execute(node, variables, context) {
2353
2395
  const cfg = node.config ?? {};