@objectstack/service-automation 7.4.0 → 7.5.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.cjs CHANGED
@@ -1668,6 +1668,142 @@ function registerNotifyNode(engine, ctx) {
1668
1668
  ctx.logger.info("[Notify] 1 built-in node executor registered (notify)");
1669
1669
  }
1670
1670
 
1671
+ // src/builtin/wait-node.ts
1672
+ var import_automation8 = require("@objectstack/spec/automation");
1673
+ function registerWaitNode(engine, ctx) {
1674
+ const getJobService = () => {
1675
+ try {
1676
+ return ctx.getService("job");
1677
+ } catch {
1678
+ return void 0;
1679
+ }
1680
+ };
1681
+ engine.registerNodeExecutor({
1682
+ type: "wait",
1683
+ descriptor: (0, import_automation8.defineActionDescriptor)({
1684
+ type: "wait",
1685
+ version: "1.0.0",
1686
+ name: "Wait",
1687
+ description: "Pause the flow until a timer elapses or a named signal arrives.",
1688
+ icon: "timer-reset",
1689
+ category: "logic",
1690
+ source: "builtin",
1691
+ // Durable pause — the run suspends and resumes later (timer/signal).
1692
+ supportsPause: true,
1693
+ isAsync: true
1694
+ }),
1695
+ async execute(node, variables, _context) {
1696
+ const loose = node.config ?? {};
1697
+ const wec = node.waitEventConfig ?? {};
1698
+ const eventType = String(wec.eventType ?? loose.eventType ?? "timer");
1699
+ const runId = variables.get("$runId");
1700
+ if (eventType === "timer") {
1701
+ const durationMs = parseIsoDuration(wec.timerDuration ?? loose.timerDuration ?? loose.duration) ?? (typeof wec.timeoutMs === "number" ? wec.timeoutMs : void 0) ?? (typeof loose.timeoutMs === "number" ? loose.timeoutMs : void 0);
1702
+ const job = getJobService();
1703
+ if (job && runId != null && durationMs && durationMs > 0) {
1704
+ const jobName = `flow-wait:${String(runId)}:${node.id}`;
1705
+ const at = new Date(Date.now() + durationMs).toISOString();
1706
+ try {
1707
+ await job.schedule(jobName, { type: "once", at }, async () => {
1708
+ try {
1709
+ await engine.resume(String(runId));
1710
+ } finally {
1711
+ try {
1712
+ await job.cancel?.(jobName);
1713
+ } catch {
1714
+ }
1715
+ }
1716
+ });
1717
+ return { success: true, suspend: true, correlation: jobName };
1718
+ } catch (err) {
1719
+ ctx.logger.warn(
1720
+ `[wait] node '${node.id}': failed to schedule timer resume (${err?.message ?? err}); suspending without auto-resume (resume it via resume(runId))`
1721
+ );
1722
+ }
1723
+ } else if (!job) {
1724
+ ctx.logger.warn(
1725
+ `[wait] node '${node.id}': no job service registered \u2014 suspending without an auto-resume timer (resume it via resume(runId), or install the job service for durable timers)`
1726
+ );
1727
+ }
1728
+ return { success: true, suspend: true, correlation: `timer:${node.id}` };
1729
+ }
1730
+ const signal = String(wec.signalName ?? loose.signalName ?? loose.signal ?? `wait:${node.id}`);
1731
+ return { success: true, suspend: true, correlation: signal };
1732
+ }
1733
+ });
1734
+ ctx.logger.info("[Wait Node] 1 built-in node executor registered");
1735
+ }
1736
+ function parseIsoDuration(input) {
1737
+ if (typeof input === "number" && Number.isFinite(input)) return input > 0 ? input : void 0;
1738
+ if (typeof input !== "string") return void 0;
1739
+ const s = input.trim();
1740
+ if (!s) return void 0;
1741
+ if (/^\d+(?:\.\d+)?$/.test(s)) {
1742
+ const n = Number(s);
1743
+ return n > 0 ? n : void 0;
1744
+ }
1745
+ const m = /^P(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+(?:\.\d+)?)S)?)?$/.exec(s);
1746
+ if (!m) return void 0;
1747
+ const [, w, d, h, min, sec] = m;
1748
+ if (!w && !d && !h && !min && !sec) return void 0;
1749
+ const totalSec = Number(w ?? 0) * 7 * 86400 + Number(d ?? 0) * 86400 + Number(h ?? 0) * 3600 + Number(min ?? 0) * 60 + Number(sec ?? 0);
1750
+ const ms = totalSec * 1e3;
1751
+ return ms > 0 ? ms : void 0;
1752
+ }
1753
+
1754
+ // src/builtin/subflow-node.ts
1755
+ var import_automation9 = require("@objectstack/spec/automation");
1756
+ var MAX_SUBFLOW_DEPTH = 16;
1757
+ function registerSubflowNode(engine, ctx) {
1758
+ engine.registerNodeExecutor({
1759
+ type: "subflow",
1760
+ descriptor: (0, import_automation9.defineActionDescriptor)({
1761
+ type: "subflow",
1762
+ version: "1.0.0",
1763
+ name: "Subflow",
1764
+ description: "Invoke another flow as a reusable step and capture its output.",
1765
+ icon: "workflow",
1766
+ category: "logic",
1767
+ source: "builtin"
1768
+ }),
1769
+ async execute(node, variables, context) {
1770
+ const cfg = node.config ?? {};
1771
+ const flowName = typeof cfg.flowName === "string" ? cfg.flowName : typeof cfg.flow === "string" ? cfg.flow : void 0;
1772
+ if (!flowName) {
1773
+ return { success: false, error: `subflow '${node.id}': config.flowName is required` };
1774
+ }
1775
+ const depth = Number(context?.$subflowDepth ?? 0);
1776
+ if (depth >= MAX_SUBFLOW_DEPTH) {
1777
+ return {
1778
+ success: false,
1779
+ error: `subflow '${flowName}': max nesting depth (${MAX_SUBFLOW_DEPTH}) exceeded \u2014 recursive subflow?`
1780
+ };
1781
+ }
1782
+ const rawInput = cfg.input && typeof cfg.input === "object" ? cfg.input : {};
1783
+ const params = interpolate(rawInput, variables, context ?? {});
1784
+ const childContext = {
1785
+ ...context ?? {},
1786
+ $subflowDepth: depth + 1,
1787
+ params
1788
+ };
1789
+ const child = await engine.execute(flowName, childContext);
1790
+ if (child.status === "paused") {
1791
+ return {
1792
+ success: false,
1793
+ error: `subflow '${flowName}' suspended at a pausing node \u2014 a nested approval/screen/wait pause from a subflow is not yet supported`
1794
+ };
1795
+ }
1796
+ if (!child.success) {
1797
+ return { success: false, error: `subflow '${flowName}' failed: ${child.error ?? "unknown error"}` };
1798
+ }
1799
+ const outVar = typeof cfg.outputVariable === "string" && cfg.outputVariable ? cfg.outputVariable : void 0;
1800
+ if (outVar) variables.set(outVar, child.output ?? null);
1801
+ return { success: true, output: { output: child.output ?? null } };
1802
+ }
1803
+ });
1804
+ ctx.logger.info("[Subflow Node] 1 built-in node executor registered");
1805
+ }
1806
+
1671
1807
  // src/builtin/index.ts
1672
1808
  function installBuiltinNodes(engine, ctx) {
1673
1809
  registerLogicNodes(engine, ctx);
@@ -1676,6 +1812,8 @@ function installBuiltinNodes(engine, ctx) {
1676
1812
  registerHttpNodes(engine, ctx);
1677
1813
  registerConnectorNodes(engine, ctx);
1678
1814
  registerNotifyNode(engine, ctx);
1815
+ registerWaitNode(engine, ctx);
1816
+ registerSubflowNode(engine, ctx);
1679
1817
  const types = engine.getRegisteredNodeTypes();
1680
1818
  ctx.logger.info(
1681
1819
  `[Automation] ${types.length} built-in node executors installed: ${types.join(", ")}`