@objectstack/service-automation 7.4.1 → 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 +138 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +138 -0
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
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(", ")}`
|