@workglow/task-graph 0.0.89 → 0.0.90
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/browser.js +1783 -262
- package/dist/browser.js.map +16 -8
- package/dist/bun.js +1783 -262
- package/dist/bun.js.map +16 -8
- package/dist/node.js +1783 -262
- package/dist/node.js.map +16 -8
- package/dist/task/ConditionUtils.d.ts +47 -0
- package/dist/task/ConditionUtils.d.ts.map +1 -0
- package/dist/task/ConditionalTask.d.ts +15 -0
- package/dist/task/ConditionalTask.d.ts.map +1 -1
- package/dist/task/GraphAsTask.d.ts.map +1 -1
- package/dist/task/IteratorTask.d.ts +177 -0
- package/dist/task/IteratorTask.d.ts.map +1 -0
- package/dist/task/IteratorTaskRunner.d.ts +36 -0
- package/dist/task/IteratorTaskRunner.d.ts.map +1 -0
- package/dist/task/MapTask.d.ts +82 -0
- package/dist/task/MapTask.d.ts.map +1 -0
- package/dist/task/ReduceTask.d.ts +61 -0
- package/dist/task/ReduceTask.d.ts.map +1 -0
- package/dist/task/Task.d.ts +2 -2
- package/dist/task/Task.d.ts.map +1 -1
- package/dist/task/WhileTask.d.ts +214 -0
- package/dist/task/WhileTask.d.ts.map +1 -0
- package/dist/task/WhileTaskRunner.d.ts +29 -0
- package/dist/task/WhileTaskRunner.d.ts.map +1 -0
- package/dist/task/index.d.ts +12 -1
- package/dist/task/index.d.ts.map +1 -1
- package/dist/task/iterationSchema.d.ts +70 -0
- package/dist/task/iterationSchema.d.ts.map +1 -0
- package/dist/task-graph/Workflow.d.ts +112 -8
- package/dist/task-graph/Workflow.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/task/TaskJSON.test.d.ts +0 -7
- package/dist/task/TaskJSON.test.d.ts.map +0 -1
package/dist/bun.js
CHANGED
|
@@ -162,7 +162,7 @@ class DataflowArrow extends Dataflow {
|
|
|
162
162
|
}
|
|
163
163
|
}
|
|
164
164
|
// src/task-graph/TaskGraph.ts
|
|
165
|
-
import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as
|
|
165
|
+
import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid44 } from "@workglow/util";
|
|
166
166
|
|
|
167
167
|
// src/task/GraphAsTask.ts
|
|
168
168
|
import { compileSchema as compileSchema2 } from "@workglow/util";
|
|
@@ -205,6 +205,73 @@ class TaskOutputRepository {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// src/task/ConditionUtils.ts
|
|
209
|
+
function evaluateCondition(fieldValue, operator, compareValue) {
|
|
210
|
+
if (fieldValue === null || fieldValue === undefined) {
|
|
211
|
+
switch (operator) {
|
|
212
|
+
case "is_empty":
|
|
213
|
+
return true;
|
|
214
|
+
case "is_not_empty":
|
|
215
|
+
return false;
|
|
216
|
+
case "is_true":
|
|
217
|
+
return false;
|
|
218
|
+
case "is_false":
|
|
219
|
+
return true;
|
|
220
|
+
default:
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const strValue = String(fieldValue);
|
|
225
|
+
const numValue = Number(fieldValue);
|
|
226
|
+
switch (operator) {
|
|
227
|
+
case "equals":
|
|
228
|
+
if (!isNaN(numValue) && !isNaN(Number(compareValue))) {
|
|
229
|
+
return numValue === Number(compareValue);
|
|
230
|
+
}
|
|
231
|
+
return strValue === compareValue;
|
|
232
|
+
case "not_equals":
|
|
233
|
+
if (!isNaN(numValue) && !isNaN(Number(compareValue))) {
|
|
234
|
+
return numValue !== Number(compareValue);
|
|
235
|
+
}
|
|
236
|
+
return strValue !== compareValue;
|
|
237
|
+
case "greater_than":
|
|
238
|
+
return numValue > Number(compareValue);
|
|
239
|
+
case "greater_or_equal":
|
|
240
|
+
return numValue >= Number(compareValue);
|
|
241
|
+
case "less_than":
|
|
242
|
+
return numValue < Number(compareValue);
|
|
243
|
+
case "less_or_equal":
|
|
244
|
+
return numValue <= Number(compareValue);
|
|
245
|
+
case "contains":
|
|
246
|
+
return strValue.toLowerCase().includes(compareValue.toLowerCase());
|
|
247
|
+
case "starts_with":
|
|
248
|
+
return strValue.toLowerCase().startsWith(compareValue.toLowerCase());
|
|
249
|
+
case "ends_with":
|
|
250
|
+
return strValue.toLowerCase().endsWith(compareValue.toLowerCase());
|
|
251
|
+
case "is_empty":
|
|
252
|
+
return strValue === "" || Array.isArray(fieldValue) && fieldValue.length === 0;
|
|
253
|
+
case "is_not_empty":
|
|
254
|
+
return strValue !== "" && !(Array.isArray(fieldValue) && fieldValue.length === 0);
|
|
255
|
+
case "is_true":
|
|
256
|
+
return Boolean(fieldValue) === true;
|
|
257
|
+
case "is_false":
|
|
258
|
+
return Boolean(fieldValue) === false;
|
|
259
|
+
default:
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function getNestedValue(obj, path) {
|
|
264
|
+
const parts = path.split(".");
|
|
265
|
+
let current = obj;
|
|
266
|
+
for (const part of parts) {
|
|
267
|
+
if (current === null || current === undefined || typeof current !== "object") {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
current = current[part];
|
|
271
|
+
}
|
|
272
|
+
return current;
|
|
273
|
+
}
|
|
274
|
+
|
|
208
275
|
// src/task/Task.ts
|
|
209
276
|
import {
|
|
210
277
|
compileSchema,
|
|
@@ -544,13 +611,13 @@ class Task {
|
|
|
544
611
|
additionalProperties: false
|
|
545
612
|
};
|
|
546
613
|
}
|
|
547
|
-
async execute(
|
|
614
|
+
async execute(_input, context) {
|
|
548
615
|
if (context.signal?.aborted) {
|
|
549
616
|
throw new TaskAbortedError("Task aborted");
|
|
550
617
|
}
|
|
551
618
|
return;
|
|
552
619
|
}
|
|
553
|
-
async executeReactive(
|
|
620
|
+
async executeReactive(_input, output, _context) {
|
|
554
621
|
return output;
|
|
555
622
|
}
|
|
556
623
|
_runner;
|
|
@@ -912,18 +979,62 @@ class Task {
|
|
|
912
979
|
// src/task/ConditionalTask.ts
|
|
913
980
|
class ConditionalTask extends Task {
|
|
914
981
|
static type = "ConditionalTask";
|
|
915
|
-
static category = "
|
|
916
|
-
static title = "
|
|
917
|
-
static description = "
|
|
982
|
+
static category = "Flow Control";
|
|
983
|
+
static title = "Condition";
|
|
984
|
+
static description = "Route data based on conditions";
|
|
918
985
|
static hasDynamicSchemas = true;
|
|
919
986
|
activeBranches = new Set;
|
|
987
|
+
buildBranchesFromConditionConfig(conditionConfig) {
|
|
988
|
+
if (!conditionConfig?.branches || conditionConfig.branches.length === 0) {
|
|
989
|
+
return [
|
|
990
|
+
{
|
|
991
|
+
id: "default",
|
|
992
|
+
condition: () => true,
|
|
993
|
+
outputPort: "1"
|
|
994
|
+
}
|
|
995
|
+
];
|
|
996
|
+
}
|
|
997
|
+
return conditionConfig.branches.map((branch, index) => ({
|
|
998
|
+
id: branch.id,
|
|
999
|
+
outputPort: String(index + 1),
|
|
1000
|
+
condition: (inputData) => {
|
|
1001
|
+
const fieldValue = getNestedValue(inputData, branch.field);
|
|
1002
|
+
return evaluateCondition(fieldValue, branch.operator, branch.value);
|
|
1003
|
+
}
|
|
1004
|
+
}));
|
|
1005
|
+
}
|
|
1006
|
+
resolveBranches(input) {
|
|
1007
|
+
const configBranches = this.config.branches ?? [];
|
|
1008
|
+
if (configBranches.length > 0 && typeof configBranches[0].condition === "function") {
|
|
1009
|
+
return {
|
|
1010
|
+
branches: configBranches,
|
|
1011
|
+
isExclusive: this.config.exclusive ?? true,
|
|
1012
|
+
defaultBranch: this.config.defaultBranch,
|
|
1013
|
+
fromConditionConfig: false
|
|
1014
|
+
};
|
|
1015
|
+
}
|
|
1016
|
+
const conditionConfig = input.conditionConfig ?? this.config.extras?.conditionConfig;
|
|
1017
|
+
if (conditionConfig) {
|
|
1018
|
+
return {
|
|
1019
|
+
branches: this.buildBranchesFromConditionConfig(conditionConfig),
|
|
1020
|
+
isExclusive: conditionConfig.exclusive ?? true,
|
|
1021
|
+
defaultBranch: conditionConfig.defaultBranch,
|
|
1022
|
+
fromConditionConfig: true
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
return {
|
|
1026
|
+
branches: configBranches,
|
|
1027
|
+
isExclusive: this.config.exclusive ?? true,
|
|
1028
|
+
defaultBranch: this.config.defaultBranch,
|
|
1029
|
+
fromConditionConfig: false
|
|
1030
|
+
};
|
|
1031
|
+
}
|
|
920
1032
|
async execute(input, context) {
|
|
921
1033
|
if (context.signal?.aborted) {
|
|
922
1034
|
return;
|
|
923
1035
|
}
|
|
924
1036
|
this.activeBranches.clear();
|
|
925
|
-
const branches = this.
|
|
926
|
-
const isExclusive = this.config.exclusive ?? true;
|
|
1037
|
+
const { branches, isExclusive, defaultBranch, fromConditionConfig } = this.resolveBranches(input);
|
|
927
1038
|
for (const branch of branches) {
|
|
928
1039
|
try {
|
|
929
1040
|
const isActive = branch.condition(input);
|
|
@@ -937,14 +1048,50 @@ class ConditionalTask extends Task {
|
|
|
937
1048
|
console.warn(`Condition evaluation failed for branch "${branch.id}":`, error);
|
|
938
1049
|
}
|
|
939
1050
|
}
|
|
940
|
-
if (this.activeBranches.size === 0 &&
|
|
941
|
-
const defaultBranchExists = branches.some((b) => b.id ===
|
|
1051
|
+
if (this.activeBranches.size === 0 && defaultBranch) {
|
|
1052
|
+
const defaultBranchExists = branches.some((b) => b.id === defaultBranch);
|
|
942
1053
|
if (defaultBranchExists) {
|
|
943
|
-
this.activeBranches.add(
|
|
1054
|
+
this.activeBranches.add(defaultBranch);
|
|
944
1055
|
}
|
|
945
1056
|
}
|
|
1057
|
+
if (fromConditionConfig) {
|
|
1058
|
+
return this.buildConditionConfigOutput(input, branches, isExclusive);
|
|
1059
|
+
}
|
|
946
1060
|
return this.buildOutput(input);
|
|
947
1061
|
}
|
|
1062
|
+
buildConditionConfigOutput(input, branches, isExclusive) {
|
|
1063
|
+
const output = {};
|
|
1064
|
+
const { conditionConfig, ...passThrough } = input;
|
|
1065
|
+
const inputKeys = Object.keys(passThrough);
|
|
1066
|
+
let matchedBranchNumber = null;
|
|
1067
|
+
for (let i = 0;i < branches.length; i++) {
|
|
1068
|
+
if (this.activeBranches.has(branches[i].id)) {
|
|
1069
|
+
if (matchedBranchNumber === null) {
|
|
1070
|
+
matchedBranchNumber = i + 1;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
if (isExclusive) {
|
|
1075
|
+
if (matchedBranchNumber !== null) {
|
|
1076
|
+
for (const key of inputKeys) {
|
|
1077
|
+
output[`${key}_${matchedBranchNumber}`] = passThrough[key];
|
|
1078
|
+
}
|
|
1079
|
+
} else {
|
|
1080
|
+
for (const key of inputKeys) {
|
|
1081
|
+
output[`${key}_else`] = passThrough[key];
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
} else {
|
|
1085
|
+
for (let i = 0;i < branches.length; i++) {
|
|
1086
|
+
if (this.activeBranches.has(branches[i].id)) {
|
|
1087
|
+
for (const key of inputKeys) {
|
|
1088
|
+
output[`${key}_${i + 1}`] = passThrough[key];
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
return output;
|
|
1094
|
+
}
|
|
948
1095
|
buildOutput(input) {
|
|
949
1096
|
const output = {
|
|
950
1097
|
_activeBranches: Array.from(this.activeBranches)
|
|
@@ -1547,9 +1694,9 @@ class GraphAsTaskRunner extends TaskRunner {
|
|
|
1547
1694
|
// src/task/GraphAsTask.ts
|
|
1548
1695
|
class GraphAsTask extends Task {
|
|
1549
1696
|
static type = "GraphAsTask";
|
|
1550
|
-
static title = "
|
|
1551
|
-
static description = "A
|
|
1552
|
-
static category = "
|
|
1697
|
+
static title = "Group";
|
|
1698
|
+
static description = "A group of tasks that are executed together";
|
|
1699
|
+
static category = "Flow Control";
|
|
1553
1700
|
static compoundMerge = PROPERTY_ARRAY;
|
|
1554
1701
|
static hasDynamicSchemas = true;
|
|
1555
1702
|
constructor(input = {}, config = {}) {
|
|
@@ -1729,38 +1876,64 @@ class GraphAsTask extends Task {
|
|
|
1729
1876
|
}
|
|
1730
1877
|
|
|
1731
1878
|
// src/task-graph/Workflow.ts
|
|
1732
|
-
import { EventEmitter as EventEmitter4 } from "@workglow/util";
|
|
1879
|
+
import { EventEmitter as EventEmitter4, uuid4 as uuid43 } from "@workglow/util";
|
|
1880
|
+
function CreateWorkflow(taskClass) {
|
|
1881
|
+
return Workflow.createWorkflow(taskClass);
|
|
1882
|
+
}
|
|
1883
|
+
function CreateLoopWorkflow(taskClass) {
|
|
1884
|
+
return function(config = {}) {
|
|
1885
|
+
return this.addLoopTask(taskClass, config);
|
|
1886
|
+
};
|
|
1887
|
+
}
|
|
1888
|
+
function CreateEndLoopWorkflow(methodName) {
|
|
1889
|
+
return function() {
|
|
1890
|
+
if (!this.isLoopBuilder) {
|
|
1891
|
+
throw new Error(`${methodName}() can only be called on loop workflows`);
|
|
1892
|
+
}
|
|
1893
|
+
return this.finalizeAndReturn();
|
|
1894
|
+
};
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1733
1897
|
class WorkflowTask extends GraphAsTask {
|
|
1734
1898
|
static type = "Workflow";
|
|
1735
1899
|
static compoundMerge = PROPERTY_ARRAY;
|
|
1736
1900
|
}
|
|
1737
|
-
var taskIdCounter = 0;
|
|
1738
1901
|
|
|
1739
1902
|
class Workflow {
|
|
1740
|
-
constructor(
|
|
1741
|
-
this.
|
|
1742
|
-
this.
|
|
1743
|
-
|
|
1744
|
-
});
|
|
1745
|
-
|
|
1746
|
-
|
|
1903
|
+
constructor(cache, parent, iteratorTask) {
|
|
1904
|
+
this._outputCache = cache;
|
|
1905
|
+
this._parentWorkflow = parent;
|
|
1906
|
+
this._iteratorTask = iteratorTask;
|
|
1907
|
+
this._graph = new TaskGraph({ outputCache: this._outputCache });
|
|
1908
|
+
if (!parent) {
|
|
1909
|
+
this._onChanged = this._onChanged.bind(this);
|
|
1910
|
+
this.setupEvents();
|
|
1911
|
+
}
|
|
1747
1912
|
}
|
|
1748
1913
|
_graph;
|
|
1749
1914
|
_dataFlows = [];
|
|
1750
1915
|
_error = "";
|
|
1751
|
-
|
|
1916
|
+
_outputCache;
|
|
1752
1917
|
_abortController;
|
|
1918
|
+
_parentWorkflow;
|
|
1919
|
+
_iteratorTask;
|
|
1920
|
+
_pendingLoopConnect;
|
|
1921
|
+
outputCache() {
|
|
1922
|
+
return this._outputCache;
|
|
1923
|
+
}
|
|
1924
|
+
get isLoopBuilder() {
|
|
1925
|
+
return this._parentWorkflow !== undefined;
|
|
1926
|
+
}
|
|
1753
1927
|
events = new EventEmitter4;
|
|
1754
1928
|
static createWorkflow(taskClass) {
|
|
1755
1929
|
const helper = function(input = {}, config = {}) {
|
|
1756
1930
|
this._error = "";
|
|
1757
1931
|
const parent = getLastTask(this);
|
|
1758
|
-
|
|
1759
|
-
const task = this.addTask(taskClass, input, { id: String(taskIdCounter), ...config });
|
|
1932
|
+
const task = this.addTaskToGraph(taskClass, input, { id: uuid43(), ...config });
|
|
1760
1933
|
if (this._dataFlows.length > 0) {
|
|
1761
1934
|
this._dataFlows.forEach((dataflow) => {
|
|
1762
1935
|
const taskSchema = task.inputSchema();
|
|
1763
|
-
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
1936
|
+
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined && taskSchema.additionalProperties !== true || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
1764
1937
|
this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.config.id}`;
|
|
1765
1938
|
console.error(this._error);
|
|
1766
1939
|
return;
|
|
@@ -1771,158 +1944,23 @@ class Workflow {
|
|
|
1771
1944
|
this._dataFlows = [];
|
|
1772
1945
|
}
|
|
1773
1946
|
if (parent && this.graph.getTargetDataflows(parent.config.id).length === 0) {
|
|
1774
|
-
const
|
|
1775
|
-
const
|
|
1776
|
-
const
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
if (targetSchema === true || typeof targetSchema === "object" && targetSchema.additionalProperties === true) {
|
|
1780
|
-
for (const fromOutputPortId of Object.keys(sourceSchema.properties || {})) {
|
|
1781
|
-
matches.set(fromOutputPortId, fromOutputPortId);
|
|
1782
|
-
this.connect(parent.config.id, fromOutputPortId, task.config.id, fromOutputPortId);
|
|
1783
|
-
}
|
|
1784
|
-
return matches;
|
|
1785
|
-
}
|
|
1786
|
-
}
|
|
1787
|
-
if (typeof sourceSchema === "boolean" || typeof targetSchema === "boolean") {
|
|
1788
|
-
return matches;
|
|
1789
|
-
}
|
|
1790
|
-
for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(sourceSchema.properties || {})) {
|
|
1791
|
-
for (const [toInputPortId, toPortInputSchema] of Object.entries(targetSchema.properties || {})) {
|
|
1792
|
-
if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
|
|
1793
|
-
matches.set(toInputPortId, fromOutputPortId);
|
|
1794
|
-
this.connect(parent.config.id, fromOutputPortId, task.config.id, toInputPortId);
|
|
1795
|
-
}
|
|
1796
|
-
}
|
|
1797
|
-
}
|
|
1798
|
-
return matches;
|
|
1799
|
-
};
|
|
1800
|
-
const getSpecificTypeIdentifiers = (schema) => {
|
|
1801
|
-
const formats = new Set;
|
|
1802
|
-
const ids = new Set;
|
|
1803
|
-
if (typeof schema === "boolean") {
|
|
1804
|
-
return { formats, ids };
|
|
1805
|
-
}
|
|
1806
|
-
const extractFromSchema = (s) => {
|
|
1807
|
-
if (!s || typeof s !== "object" || Array.isArray(s))
|
|
1808
|
-
return;
|
|
1809
|
-
if (s.format)
|
|
1810
|
-
formats.add(s.format);
|
|
1811
|
-
if (s.$id)
|
|
1812
|
-
ids.add(s.$id);
|
|
1813
|
-
};
|
|
1814
|
-
extractFromSchema(schema);
|
|
1815
|
-
const checkUnion = (schemas) => {
|
|
1816
|
-
if (!schemas)
|
|
1817
|
-
return;
|
|
1818
|
-
for (const s of schemas) {
|
|
1819
|
-
if (typeof s === "boolean")
|
|
1820
|
-
continue;
|
|
1821
|
-
extractFromSchema(s);
|
|
1822
|
-
if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
|
|
1823
|
-
extractFromSchema(s.items);
|
|
1824
|
-
}
|
|
1825
|
-
}
|
|
1826
|
-
};
|
|
1827
|
-
checkUnion(schema.oneOf);
|
|
1828
|
-
checkUnion(schema.anyOf);
|
|
1829
|
-
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
1830
|
-
extractFromSchema(schema.items);
|
|
1831
|
-
}
|
|
1832
|
-
return { formats, ids };
|
|
1833
|
-
};
|
|
1834
|
-
const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
|
|
1835
|
-
if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
|
|
1836
|
-
return fromPortOutputSchema === true && toPortInputSchema === true;
|
|
1837
|
-
}
|
|
1838
|
-
const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
|
|
1839
|
-
const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
|
|
1840
|
-
for (const format of outputIds.formats) {
|
|
1841
|
-
if (inputIds.formats.has(format)) {
|
|
1842
|
-
return true;
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
for (const id of outputIds.ids) {
|
|
1846
|
-
if (inputIds.ids.has(id)) {
|
|
1847
|
-
return true;
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
if (requireSpecificType) {
|
|
1851
|
-
return false;
|
|
1852
|
-
}
|
|
1853
|
-
const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
|
|
1854
|
-
if (!idTypeBlank)
|
|
1855
|
-
return false;
|
|
1856
|
-
if (fromPortOutputSchema.type === toPortInputSchema.type)
|
|
1857
|
-
return true;
|
|
1858
|
-
const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
|
|
1859
|
-
if (typeof schema === "boolean")
|
|
1860
|
-
return schema;
|
|
1861
|
-
return schema.type === fromPortOutputSchema.type;
|
|
1862
|
-
}) ?? false;
|
|
1863
|
-
const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
|
|
1864
|
-
if (typeof schema === "boolean")
|
|
1865
|
-
return schema;
|
|
1866
|
-
return schema.type === fromPortOutputSchema.type;
|
|
1867
|
-
}) ?? false;
|
|
1868
|
-
return matchesOneOf || matchesAnyOf;
|
|
1869
|
-
};
|
|
1870
|
-
makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
|
|
1871
|
-
const outputPortIdMatch = fromOutputPortId === toInputPortId;
|
|
1872
|
-
const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
|
|
1873
|
-
const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
|
|
1874
|
-
return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
|
|
1875
|
-
});
|
|
1876
|
-
makeMatch(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
|
|
1877
|
-
return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
|
|
1878
|
-
});
|
|
1879
|
-
const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
|
|
1880
|
-
const providedInputKeys = new Set(Object.keys(input || {}));
|
|
1881
|
-
const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
|
|
1882
|
-
let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
|
|
1883
|
-
if (unmatchedRequired.length > 0) {
|
|
1884
|
-
const nodes = this._graph.getTasks();
|
|
1885
|
-
const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
|
|
1886
|
-
for (let i = parentIndex - 1;i >= 0 && unmatchedRequired.length > 0; i--) {
|
|
1887
|
-
const earlierTask = nodes[i];
|
|
1888
|
-
const earlierOutputSchema = earlierTask.outputSchema();
|
|
1889
|
-
const makeMatchFromEarlier = (comparator) => {
|
|
1890
|
-
if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
|
|
1891
|
-
return;
|
|
1892
|
-
}
|
|
1893
|
-
for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
|
|
1894
|
-
for (const requiredInputId of unmatchedRequired) {
|
|
1895
|
-
const toPortInputSchema = targetSchema.properties?.[requiredInputId];
|
|
1896
|
-
if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
|
|
1897
|
-
matches.set(requiredInputId, fromOutputPortId);
|
|
1898
|
-
this.connect(earlierTask.config.id, fromOutputPortId, task.config.id, requiredInputId);
|
|
1899
|
-
}
|
|
1900
|
-
}
|
|
1901
|
-
}
|
|
1902
|
-
};
|
|
1903
|
-
makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
|
|
1904
|
-
const outputPortIdMatch = fromOutputPortId === toInputPortId;
|
|
1905
|
-
const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
|
|
1906
|
-
const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
|
|
1907
|
-
return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
|
|
1908
|
-
});
|
|
1909
|
-
makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
|
|
1910
|
-
return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
|
|
1911
|
-
});
|
|
1912
|
-
unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
|
|
1913
|
-
}
|
|
1947
|
+
const nodes = this._graph.getTasks();
|
|
1948
|
+
const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
|
|
1949
|
+
const earlierTasks = [];
|
|
1950
|
+
for (let i = parentIndex - 1;i >= 0; i--) {
|
|
1951
|
+
earlierTasks.push(nodes[i]);
|
|
1914
1952
|
}
|
|
1915
|
-
const
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
this._error =
|
|
1953
|
+
const providedInputKeys = new Set(Object.keys(input || {}));
|
|
1954
|
+
const result = Workflow.autoConnect(this.graph, parent, task, {
|
|
1955
|
+
providedInputKeys,
|
|
1956
|
+
earlierTasks
|
|
1957
|
+
});
|
|
1958
|
+
if (result.error) {
|
|
1959
|
+
if (this.isLoopBuilder) {
|
|
1960
|
+
this._error = result.error;
|
|
1961
|
+
console.warn(this._error);
|
|
1962
|
+
} else {
|
|
1963
|
+
this._error = result.error + " Task not added.";
|
|
1926
1964
|
console.error(this._error);
|
|
1927
1965
|
this.graph.removeTask(task.config.id);
|
|
1928
1966
|
}
|
|
@@ -1965,12 +2003,20 @@ class Workflow {
|
|
|
1965
2003
|
return this.events.waitOn(name);
|
|
1966
2004
|
}
|
|
1967
2005
|
async run(input = {}) {
|
|
2006
|
+
if (this.isLoopBuilder) {
|
|
2007
|
+
this.finalizeTemplate();
|
|
2008
|
+
if (this._pendingLoopConnect) {
|
|
2009
|
+
this._parentWorkflow.autoConnectLoopTask(this._pendingLoopConnect);
|
|
2010
|
+
this._pendingLoopConnect = undefined;
|
|
2011
|
+
}
|
|
2012
|
+
return this._parentWorkflow.run(input);
|
|
2013
|
+
}
|
|
1968
2014
|
this.events.emit("start");
|
|
1969
2015
|
this._abortController = new AbortController;
|
|
1970
2016
|
try {
|
|
1971
2017
|
const output = await this.graph.run(input, {
|
|
1972
2018
|
parentSignal: this._abortController.signal,
|
|
1973
|
-
outputCache: this.
|
|
2019
|
+
outputCache: this._outputCache
|
|
1974
2020
|
});
|
|
1975
2021
|
const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
|
|
1976
2022
|
this.events.emit("complete");
|
|
@@ -1983,6 +2029,9 @@ class Workflow {
|
|
|
1983
2029
|
}
|
|
1984
2030
|
}
|
|
1985
2031
|
async abort() {
|
|
2032
|
+
if (this._parentWorkflow) {
|
|
2033
|
+
return this._parentWorkflow.abort();
|
|
2034
|
+
}
|
|
1986
2035
|
this._abortController?.abort();
|
|
1987
2036
|
}
|
|
1988
2037
|
pop() {
|
|
@@ -2051,10 +2100,12 @@ class Workflow {
|
|
|
2051
2100
|
return task;
|
|
2052
2101
|
}
|
|
2053
2102
|
reset() {
|
|
2054
|
-
|
|
2103
|
+
if (this._parentWorkflow) {
|
|
2104
|
+
throw new WorkflowError("Cannot reset a loop workflow. Call reset() on the parent workflow.");
|
|
2105
|
+
}
|
|
2055
2106
|
this.clearEvents();
|
|
2056
2107
|
this._graph = new TaskGraph({
|
|
2057
|
-
outputCache: this.
|
|
2108
|
+
outputCache: this._outputCache
|
|
2058
2109
|
});
|
|
2059
2110
|
this._dataFlows = [];
|
|
2060
2111
|
this._error = "";
|
|
@@ -2109,15 +2160,243 @@ class Workflow {
|
|
|
2109
2160
|
this.graph.addDataflow(dataflow);
|
|
2110
2161
|
return this;
|
|
2111
2162
|
}
|
|
2112
|
-
|
|
2163
|
+
addTaskToGraph(taskClass, input, config) {
|
|
2113
2164
|
const task = new taskClass(input, config);
|
|
2114
2165
|
const id = this.graph.addTask(task);
|
|
2115
2166
|
this.events.emit("changed", id);
|
|
2116
2167
|
return task;
|
|
2117
2168
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2169
|
+
addTask(taskClass, input, config) {
|
|
2170
|
+
const helper = Workflow.createWorkflow(taskClass);
|
|
2171
|
+
return helper.call(this, input, config);
|
|
2172
|
+
}
|
|
2173
|
+
addLoopTask(taskClass, config = {}) {
|
|
2174
|
+
this._error = "";
|
|
2175
|
+
const parent = getLastTask(this);
|
|
2176
|
+
const task = this.addTaskToGraph(taskClass, {}, { id: uuid43(), ...config });
|
|
2177
|
+
if (this._dataFlows.length > 0) {
|
|
2178
|
+
this._dataFlows.forEach((dataflow) => {
|
|
2179
|
+
const taskSchema = task.inputSchema();
|
|
2180
|
+
if (typeof taskSchema !== "boolean" && taskSchema.properties?.[dataflow.targetTaskPortId] === undefined && taskSchema.additionalProperties !== true || taskSchema === true && dataflow.targetTaskPortId !== DATAFLOW_ALL_PORTS) {
|
|
2181
|
+
this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.config.id}`;
|
|
2182
|
+
console.error(this._error);
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
dataflow.targetTaskId = task.config.id;
|
|
2186
|
+
this.graph.addDataflow(dataflow);
|
|
2187
|
+
});
|
|
2188
|
+
this._dataFlows = [];
|
|
2189
|
+
}
|
|
2190
|
+
const loopBuilder = new Workflow(this.outputCache(), this, task);
|
|
2191
|
+
if (parent) {
|
|
2192
|
+
loopBuilder._pendingLoopConnect = { parent, iteratorTask: task };
|
|
2193
|
+
}
|
|
2194
|
+
return loopBuilder;
|
|
2195
|
+
}
|
|
2196
|
+
autoConnectLoopTask(pending) {
|
|
2197
|
+
if (!pending)
|
|
2198
|
+
return;
|
|
2199
|
+
const { parent, iteratorTask } = pending;
|
|
2200
|
+
if (this.graph.getTargetDataflows(parent.config.id).length === 0) {
|
|
2201
|
+
const nodes = this._graph.getTasks();
|
|
2202
|
+
const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
|
|
2203
|
+
const earlierTasks = [];
|
|
2204
|
+
for (let i = parentIndex - 1;i >= 0; i--) {
|
|
2205
|
+
earlierTasks.push(nodes[i]);
|
|
2206
|
+
}
|
|
2207
|
+
const result = Workflow.autoConnect(this.graph, parent, iteratorTask, {
|
|
2208
|
+
earlierTasks
|
|
2209
|
+
});
|
|
2210
|
+
if (result.error) {
|
|
2211
|
+
this._error = result.error + " Task not added.";
|
|
2212
|
+
console.error(this._error);
|
|
2213
|
+
this.graph.removeTask(iteratorTask.config.id);
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
static AutoConnectOptions = Symbol("AutoConnectOptions");
|
|
2218
|
+
static autoConnect(graph, sourceTask, targetTask, options) {
|
|
2219
|
+
const matches = new Map;
|
|
2220
|
+
const sourceSchema = sourceTask.outputSchema();
|
|
2221
|
+
const targetSchema = targetTask.inputSchema();
|
|
2222
|
+
const providedInputKeys = options?.providedInputKeys ?? new Set;
|
|
2223
|
+
const earlierTasks = options?.earlierTasks ?? [];
|
|
2224
|
+
const getSpecificTypeIdentifiers = (schema) => {
|
|
2225
|
+
const formats = new Set;
|
|
2226
|
+
const ids = new Set;
|
|
2227
|
+
if (typeof schema === "boolean") {
|
|
2228
|
+
return { formats, ids };
|
|
2229
|
+
}
|
|
2230
|
+
const extractFromSchema = (s) => {
|
|
2231
|
+
if (!s || typeof s !== "object" || Array.isArray(s))
|
|
2232
|
+
return;
|
|
2233
|
+
if (s.format)
|
|
2234
|
+
formats.add(s.format);
|
|
2235
|
+
if (s.$id)
|
|
2236
|
+
ids.add(s.$id);
|
|
2237
|
+
};
|
|
2238
|
+
extractFromSchema(schema);
|
|
2239
|
+
const checkUnion = (schemas) => {
|
|
2240
|
+
if (!schemas)
|
|
2241
|
+
return;
|
|
2242
|
+
for (const s of schemas) {
|
|
2243
|
+
if (typeof s === "boolean")
|
|
2244
|
+
continue;
|
|
2245
|
+
extractFromSchema(s);
|
|
2246
|
+
if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
|
|
2247
|
+
extractFromSchema(s.items);
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
checkUnion(schema.oneOf);
|
|
2252
|
+
checkUnion(schema.anyOf);
|
|
2253
|
+
if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
|
|
2254
|
+
extractFromSchema(schema.items);
|
|
2255
|
+
}
|
|
2256
|
+
return { formats, ids };
|
|
2257
|
+
};
|
|
2258
|
+
const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
|
|
2259
|
+
if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
|
|
2260
|
+
return fromPortOutputSchema === true && toPortInputSchema === true;
|
|
2261
|
+
}
|
|
2262
|
+
const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
|
|
2263
|
+
const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
|
|
2264
|
+
for (const format of outputIds.formats) {
|
|
2265
|
+
if (inputIds.formats.has(format)) {
|
|
2266
|
+
return true;
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
for (const id of outputIds.ids) {
|
|
2270
|
+
if (inputIds.ids.has(id)) {
|
|
2271
|
+
return true;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
if (requireSpecificType) {
|
|
2275
|
+
return false;
|
|
2276
|
+
}
|
|
2277
|
+
const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
|
|
2278
|
+
if (!idTypeBlank)
|
|
2279
|
+
return false;
|
|
2280
|
+
if (fromPortOutputSchema.type === toPortInputSchema.type)
|
|
2281
|
+
return true;
|
|
2282
|
+
const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
|
|
2283
|
+
if (typeof schema === "boolean")
|
|
2284
|
+
return schema;
|
|
2285
|
+
return schema.type === fromPortOutputSchema.type;
|
|
2286
|
+
}) ?? false;
|
|
2287
|
+
const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
|
|
2288
|
+
if (typeof schema === "boolean")
|
|
2289
|
+
return schema;
|
|
2290
|
+
return schema.type === fromPortOutputSchema.type;
|
|
2291
|
+
}) ?? false;
|
|
2292
|
+
return matchesOneOf || matchesAnyOf;
|
|
2293
|
+
};
|
|
2294
|
+
const makeMatch = (fromSchema, toSchema, fromTaskId, toTaskId, comparator) => {
|
|
2295
|
+
if (typeof fromSchema === "object") {
|
|
2296
|
+
if (toSchema === true || typeof toSchema === "object" && toSchema.additionalProperties === true) {
|
|
2297
|
+
for (const fromOutputPortId of Object.keys(fromSchema.properties || {})) {
|
|
2298
|
+
matches.set(fromOutputPortId, fromOutputPortId);
|
|
2299
|
+
graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, fromOutputPortId));
|
|
2300
|
+
}
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
if (typeof fromSchema === "boolean" || typeof toSchema === "boolean") {
|
|
2305
|
+
return;
|
|
2306
|
+
}
|
|
2307
|
+
for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(fromSchema.properties || {})) {
|
|
2308
|
+
for (const [toInputPortId, toPortInputSchema] of Object.entries(toSchema.properties || {})) {
|
|
2309
|
+
if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
|
|
2310
|
+
matches.set(toInputPortId, fromOutputPortId);
|
|
2311
|
+
graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, toInputPortId));
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
makeMatch(sourceSchema, targetSchema, sourceTask.config.id, targetTask.config.id, ([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
|
|
2317
|
+
const outputPortIdMatch = fromOutputPortId === toInputPortId;
|
|
2318
|
+
const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
|
|
2319
|
+
const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
|
|
2320
|
+
return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
|
|
2321
|
+
});
|
|
2322
|
+
makeMatch(sourceSchema, targetSchema, sourceTask.config.id, targetTask.config.id, ([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
|
|
2323
|
+
return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
|
|
2324
|
+
});
|
|
2325
|
+
const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
|
|
2326
|
+
const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
|
|
2327
|
+
let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
|
|
2328
|
+
if (unmatchedRequired.length > 0 && earlierTasks.length > 0) {
|
|
2329
|
+
for (let i = 0;i < earlierTasks.length && unmatchedRequired.length > 0; i++) {
|
|
2330
|
+
const earlierTask = earlierTasks[i];
|
|
2331
|
+
const earlierOutputSchema = earlierTask.outputSchema();
|
|
2332
|
+
const makeMatchFromEarlier = (comparator) => {
|
|
2333
|
+
if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
|
|
2334
|
+
return;
|
|
2335
|
+
}
|
|
2336
|
+
for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
|
|
2337
|
+
for (const requiredInputId of unmatchedRequired) {
|
|
2338
|
+
const toPortInputSchema = targetSchema.properties?.[requiredInputId];
|
|
2339
|
+
if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
|
|
2340
|
+
matches.set(requiredInputId, fromOutputPortId);
|
|
2341
|
+
graph.addDataflow(new Dataflow(earlierTask.config.id, fromOutputPortId, targetTask.config.id, requiredInputId));
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
};
|
|
2346
|
+
makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
|
|
2347
|
+
const outputPortIdMatch = fromOutputPortId === toInputPortId;
|
|
2348
|
+
const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
|
|
2349
|
+
const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
|
|
2350
|
+
return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
|
|
2351
|
+
});
|
|
2352
|
+
makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
|
|
2353
|
+
return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
|
|
2354
|
+
});
|
|
2355
|
+
unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
|
|
2356
|
+
}
|
|
2357
|
+
}
|
|
2358
|
+
const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
|
|
2359
|
+
if (stillUnmatchedRequired.length > 0) {
|
|
2360
|
+
return {
|
|
2361
|
+
matches,
|
|
2362
|
+
error: `Could not find matches for required inputs [${stillUnmatchedRequired.join(", ")}] of ${targetTask.type}. ` + `Attempted to match from ${sourceTask.type} and earlier tasks.`,
|
|
2363
|
+
unmatchedRequired: stillUnmatchedRequired
|
|
2364
|
+
};
|
|
2365
|
+
}
|
|
2366
|
+
if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
|
|
2367
|
+
const hasRequiredInputs = requiredInputs.size > 0;
|
|
2368
|
+
const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
|
|
2369
|
+
const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
|
|
2370
|
+
if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
|
|
2371
|
+
return {
|
|
2372
|
+
matches,
|
|
2373
|
+
error: `Could not find a match between the outputs of ${sourceTask.type} and the inputs of ${targetTask.type}. ` + `You may need to connect the outputs to the inputs via connect() manually.`,
|
|
2374
|
+
unmatchedRequired: []
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
return {
|
|
2379
|
+
matches,
|
|
2380
|
+
unmatchedRequired: []
|
|
2381
|
+
};
|
|
2382
|
+
}
|
|
2383
|
+
finalizeTemplate() {
|
|
2384
|
+
if (!this._iteratorTask || this.graph.getTasks().length === 0) {
|
|
2385
|
+
return;
|
|
2386
|
+
}
|
|
2387
|
+
this._iteratorTask.subGraph = this.graph;
|
|
2388
|
+
}
|
|
2389
|
+
finalizeAndReturn() {
|
|
2390
|
+
if (!this._parentWorkflow) {
|
|
2391
|
+
throw new WorkflowError("finalizeAndReturn() can only be called on loop workflows");
|
|
2392
|
+
}
|
|
2393
|
+
this.finalizeTemplate();
|
|
2394
|
+
if (this._pendingLoopConnect) {
|
|
2395
|
+
this._parentWorkflow.autoConnectLoopTask(this._pendingLoopConnect);
|
|
2396
|
+
this._pendingLoopConnect = undefined;
|
|
2397
|
+
}
|
|
2398
|
+
return this._parentWorkflow;
|
|
2399
|
+
}
|
|
2121
2400
|
}
|
|
2122
2401
|
|
|
2123
2402
|
// src/task-graph/Conversions.ts
|
|
@@ -2357,7 +2636,7 @@ class TaskGraph {
|
|
|
2357
2636
|
return this._dag.removeNode(taskId);
|
|
2358
2637
|
}
|
|
2359
2638
|
resetGraph() {
|
|
2360
|
-
this.runner.resetGraph(this,
|
|
2639
|
+
this.runner.resetGraph(this, uuid44());
|
|
2361
2640
|
}
|
|
2362
2641
|
toJSON() {
|
|
2363
2642
|
const tasks = this.getTasks().map((node) => node.toJSON());
|
|
@@ -2522,81 +2801,1145 @@ function serialGraph(tasks, inputHandle, outputHandle) {
|
|
|
2522
2801
|
graph.addDataflows(serialGraphEdges(tasks, inputHandle, outputHandle));
|
|
2523
2802
|
return graph;
|
|
2524
2803
|
}
|
|
2525
|
-
// src/task/
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
options
|
|
2537
|
-
}) => {
|
|
2538
|
-
const storage = options?.storage ?? new InMemoryQueueStorage(queueName);
|
|
2539
|
-
await storage.setupDatabase();
|
|
2540
|
-
const server = new JobQueueServer(jobClass, {
|
|
2541
|
-
storage,
|
|
2542
|
-
queueName,
|
|
2543
|
-
limiter: options?.limiter,
|
|
2544
|
-
workerCount: options?.workerCount,
|
|
2545
|
-
pollIntervalMs: options?.pollIntervalMs,
|
|
2546
|
-
deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
|
|
2547
|
-
deleteAfterFailureMs: options?.deleteAfterFailureMs,
|
|
2548
|
-
deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
|
|
2549
|
-
cleanupIntervalMs: options?.cleanupIntervalMs
|
|
2550
|
-
});
|
|
2551
|
-
const client = new JobQueueClient({
|
|
2552
|
-
storage,
|
|
2553
|
-
queueName
|
|
2554
|
-
});
|
|
2555
|
-
client.attach(server);
|
|
2556
|
-
return { server, client, storage };
|
|
2557
|
-
};
|
|
2558
|
-
function registerJobQueueFactory(factory) {
|
|
2559
|
-
globalServiceRegistry3.registerInstance(JOB_QUEUE_FACTORY, factory);
|
|
2560
|
-
}
|
|
2561
|
-
function createJobQueueFactoryWithOptions(defaultOptions = {}) {
|
|
2562
|
-
return async ({
|
|
2563
|
-
queueName,
|
|
2564
|
-
jobClass,
|
|
2565
|
-
options
|
|
2566
|
-
}) => {
|
|
2567
|
-
const mergedOptions = {
|
|
2568
|
-
...defaultOptions,
|
|
2569
|
-
...options ?? {}
|
|
2570
|
-
};
|
|
2571
|
-
const storage = mergedOptions.storage ?? new InMemoryQueueStorage(queueName);
|
|
2572
|
-
await storage.setupDatabase();
|
|
2573
|
-
const server = new JobQueueServer(jobClass, {
|
|
2574
|
-
storage,
|
|
2575
|
-
queueName,
|
|
2576
|
-
limiter: mergedOptions.limiter,
|
|
2577
|
-
workerCount: mergedOptions.workerCount,
|
|
2578
|
-
pollIntervalMs: mergedOptions.pollIntervalMs,
|
|
2579
|
-
deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
|
|
2580
|
-
deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
|
|
2581
|
-
deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
|
|
2582
|
-
cleanupIntervalMs: mergedOptions.cleanupIntervalMs
|
|
2583
|
-
});
|
|
2584
|
-
const client = new JobQueueClient({
|
|
2585
|
-
storage,
|
|
2586
|
-
queueName
|
|
2587
|
-
});
|
|
2588
|
-
client.attach(server);
|
|
2589
|
-
return { server, client, storage };
|
|
2590
|
-
};
|
|
2591
|
-
}
|
|
2592
|
-
function getJobQueueFactory() {
|
|
2593
|
-
if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
|
|
2594
|
-
registerJobQueueFactory(defaultJobQueueFactory);
|
|
2804
|
+
// src/task/IteratorTaskRunner.ts
|
|
2805
|
+
class IteratorTaskRunner extends GraphAsTaskRunner {
|
|
2806
|
+
subGraphRunChain = Promise.resolve();
|
|
2807
|
+
async executeTask(input) {
|
|
2808
|
+
const analysis = this.task.analyzeIterationInput(input);
|
|
2809
|
+
if (analysis.iterationCount === 0) {
|
|
2810
|
+
const emptyResult = this.task.getEmptyResult();
|
|
2811
|
+
return this.executeTaskReactive(input, emptyResult);
|
|
2812
|
+
}
|
|
2813
|
+
const result = this.task.isReduceTask() ? await this.executeReduceIterations(analysis) : await this.executeCollectIterations(analysis);
|
|
2814
|
+
return this.executeTaskReactive(input, result);
|
|
2595
2815
|
}
|
|
2596
|
-
|
|
2597
|
-
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2816
|
+
async executeTaskReactive(input, output) {
|
|
2817
|
+
const reactiveResult = await this.task.executeReactive(input, output, { own: this.own });
|
|
2818
|
+
return Object.assign({}, output, reactiveResult ?? {});
|
|
2819
|
+
}
|
|
2820
|
+
async executeCollectIterations(analysis) {
|
|
2821
|
+
const iterationCount = analysis.iterationCount;
|
|
2822
|
+
const preserveOrder = this.task.preserveIterationOrder();
|
|
2823
|
+
const batchSize = this.task.batchSize !== undefined && this.task.batchSize > 0 ? this.task.batchSize : iterationCount;
|
|
2824
|
+
const requestedConcurrency = this.task.concurrencyLimit ?? iterationCount;
|
|
2825
|
+
const concurrency = Math.max(1, Math.min(requestedConcurrency, iterationCount));
|
|
2826
|
+
const orderedResults = preserveOrder ? new Array(iterationCount) : [];
|
|
2827
|
+
const completionOrderResults = [];
|
|
2828
|
+
for (let batchStart = 0;batchStart < iterationCount; batchStart += batchSize) {
|
|
2829
|
+
if (this.abortController?.signal.aborted) {
|
|
2830
|
+
break;
|
|
2831
|
+
}
|
|
2832
|
+
const batchEnd = Math.min(batchStart + batchSize, iterationCount);
|
|
2833
|
+
const batchIndices = Array.from({ length: batchEnd - batchStart }, (_, i) => batchStart + i);
|
|
2834
|
+
const batchResults = await this.executeBatch(batchIndices, analysis, iterationCount, concurrency);
|
|
2835
|
+
for (const { index, result } of batchResults) {
|
|
2836
|
+
if (result === undefined)
|
|
2837
|
+
continue;
|
|
2838
|
+
if (preserveOrder) {
|
|
2839
|
+
orderedResults[index] = result;
|
|
2840
|
+
} else {
|
|
2841
|
+
completionOrderResults.push(result);
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
const progress = Math.round(batchEnd / iterationCount * 100);
|
|
2845
|
+
await this.handleProgress(progress, `Completed ${batchEnd}/${iterationCount} iterations`);
|
|
2846
|
+
}
|
|
2847
|
+
const collected = preserveOrder ? orderedResults.filter((result) => result !== undefined) : completionOrderResults;
|
|
2848
|
+
return this.task.collectResults(collected);
|
|
2849
|
+
}
|
|
2850
|
+
async executeReduceIterations(analysis) {
|
|
2851
|
+
const iterationCount = analysis.iterationCount;
|
|
2852
|
+
let accumulator = this.task.getInitialAccumulator();
|
|
2853
|
+
for (let index = 0;index < iterationCount; index++) {
|
|
2854
|
+
if (this.abortController?.signal.aborted) {
|
|
2855
|
+
break;
|
|
2856
|
+
}
|
|
2857
|
+
const iterationInput = this.task.buildIterationRunInput(analysis, index, iterationCount, {
|
|
2858
|
+
accumulator
|
|
2859
|
+
});
|
|
2860
|
+
const iterationResult = await this.executeSubgraphIteration(iterationInput);
|
|
2861
|
+
accumulator = this.task.mergeIterationIntoAccumulator(accumulator, iterationResult, index);
|
|
2862
|
+
const progress = Math.round((index + 1) / iterationCount * 100);
|
|
2863
|
+
await this.handleProgress(progress, `Completed ${index + 1}/${iterationCount} iterations`);
|
|
2864
|
+
}
|
|
2865
|
+
return accumulator;
|
|
2866
|
+
}
|
|
2867
|
+
async executeBatch(indices, analysis, iterationCount, concurrency) {
|
|
2868
|
+
const results = [];
|
|
2869
|
+
let cursor = 0;
|
|
2870
|
+
const workerCount = Math.max(1, Math.min(concurrency, indices.length));
|
|
2871
|
+
const workers = Array.from({ length: workerCount }, async () => {
|
|
2872
|
+
while (true) {
|
|
2873
|
+
if (this.abortController?.signal.aborted) {
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
const position = cursor;
|
|
2877
|
+
cursor += 1;
|
|
2878
|
+
if (position >= indices.length) {
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const index = indices[position];
|
|
2882
|
+
const iterationInput = this.task.buildIterationRunInput(analysis, index, iterationCount);
|
|
2883
|
+
const result = await this.executeSubgraphIteration(iterationInput);
|
|
2884
|
+
results.push({ index, result });
|
|
2885
|
+
}
|
|
2886
|
+
});
|
|
2887
|
+
await Promise.all(workers);
|
|
2888
|
+
return results;
|
|
2889
|
+
}
|
|
2890
|
+
async executeSubgraphIteration(input) {
|
|
2891
|
+
let releaseTurn;
|
|
2892
|
+
const waitForPreviousRun = this.subGraphRunChain;
|
|
2893
|
+
this.subGraphRunChain = new Promise((resolve) => {
|
|
2894
|
+
releaseTurn = resolve;
|
|
2895
|
+
});
|
|
2896
|
+
await waitForPreviousRun;
|
|
2897
|
+
try {
|
|
2898
|
+
if (this.abortController?.signal.aborted) {
|
|
2899
|
+
return;
|
|
2900
|
+
}
|
|
2901
|
+
const results = await this.task.subGraph.run(input, {
|
|
2902
|
+
parentSignal: this.abortController?.signal,
|
|
2903
|
+
outputCache: this.outputCache
|
|
2904
|
+
});
|
|
2905
|
+
if (results.length === 0) {
|
|
2906
|
+
return;
|
|
2907
|
+
}
|
|
2908
|
+
return this.task.subGraph.mergeExecuteOutputsToRunOutput(results, this.task.compoundMerge);
|
|
2909
|
+
} finally {
|
|
2910
|
+
releaseTurn?.();
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
// src/task/IteratorTask.ts
|
|
2916
|
+
var ITERATOR_CONTEXT_SCHEMA = {
|
|
2917
|
+
type: "object",
|
|
2918
|
+
properties: {
|
|
2919
|
+
_iterationIndex: {
|
|
2920
|
+
type: "integer",
|
|
2921
|
+
minimum: 0,
|
|
2922
|
+
title: "Iteration Index",
|
|
2923
|
+
description: "Current iteration index (0-based)",
|
|
2924
|
+
"x-ui-iteration": true
|
|
2925
|
+
},
|
|
2926
|
+
_iterationCount: {
|
|
2927
|
+
type: "integer",
|
|
2928
|
+
minimum: 0,
|
|
2929
|
+
title: "Iteration Count",
|
|
2930
|
+
description: "Total number of iterations",
|
|
2931
|
+
"x-ui-iteration": true
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
};
|
|
2935
|
+
function isArrayVariant(schema) {
|
|
2936
|
+
if (!schema || typeof schema !== "object")
|
|
2937
|
+
return false;
|
|
2938
|
+
const record = schema;
|
|
2939
|
+
return record.type === "array" || record.items !== undefined;
|
|
2940
|
+
}
|
|
2941
|
+
function getExplicitIterationFlag(schema) {
|
|
2942
|
+
if (!schema || typeof schema !== "object")
|
|
2943
|
+
return;
|
|
2944
|
+
const record = schema;
|
|
2945
|
+
const flag = record["x-ui-iteration"];
|
|
2946
|
+
if (flag === true)
|
|
2947
|
+
return true;
|
|
2948
|
+
if (flag === false)
|
|
2949
|
+
return false;
|
|
2950
|
+
return;
|
|
2951
|
+
}
|
|
2952
|
+
function inferIterationFromSchema(schema) {
|
|
2953
|
+
if (!schema || typeof schema !== "object")
|
|
2954
|
+
return;
|
|
2955
|
+
const record = schema;
|
|
2956
|
+
if (record.type === "array" || record.items !== undefined) {
|
|
2957
|
+
return true;
|
|
2958
|
+
}
|
|
2959
|
+
const variants = record.oneOf ?? record.anyOf;
|
|
2960
|
+
if (!Array.isArray(variants) || variants.length === 0) {
|
|
2961
|
+
if (record.type !== undefined) {
|
|
2962
|
+
return false;
|
|
2963
|
+
}
|
|
2964
|
+
return;
|
|
2965
|
+
}
|
|
2966
|
+
let hasArrayVariant = false;
|
|
2967
|
+
let hasNonArrayVariant = false;
|
|
2968
|
+
for (const variant of variants) {
|
|
2969
|
+
if (isArrayVariant(variant)) {
|
|
2970
|
+
hasArrayVariant = true;
|
|
2971
|
+
} else {
|
|
2972
|
+
hasNonArrayVariant = true;
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
if (hasArrayVariant && hasNonArrayVariant)
|
|
2976
|
+
return;
|
|
2977
|
+
if (hasArrayVariant)
|
|
2978
|
+
return true;
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
function createFlexibleSchema(baseSchema) {
|
|
2982
|
+
if (typeof baseSchema === "boolean")
|
|
2983
|
+
return baseSchema;
|
|
2984
|
+
return {
|
|
2985
|
+
anyOf: [baseSchema, { type: "array", items: baseSchema }]
|
|
2986
|
+
};
|
|
2987
|
+
}
|
|
2988
|
+
function createArraySchema(baseSchema) {
|
|
2989
|
+
if (typeof baseSchema === "boolean")
|
|
2990
|
+
return baseSchema;
|
|
2991
|
+
return {
|
|
2992
|
+
type: "array",
|
|
2993
|
+
items: baseSchema
|
|
2994
|
+
};
|
|
2995
|
+
}
|
|
2996
|
+
function extractBaseSchema(schema) {
|
|
2997
|
+
if (typeof schema === "boolean")
|
|
2998
|
+
return schema;
|
|
2999
|
+
const schemaType = schema.type;
|
|
3000
|
+
if (schemaType === "array" && schema.items) {
|
|
3001
|
+
return schema.items;
|
|
3002
|
+
}
|
|
3003
|
+
const variants = schema.oneOf ?? schema.anyOf;
|
|
3004
|
+
if (Array.isArray(variants)) {
|
|
3005
|
+
for (const variant of variants) {
|
|
3006
|
+
if (typeof variant === "object") {
|
|
3007
|
+
const variantType = variant.type;
|
|
3008
|
+
if (variantType !== "array") {
|
|
3009
|
+
return variant;
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
for (const variant of variants) {
|
|
3014
|
+
if (typeof variant === "object") {
|
|
3015
|
+
const variantType = variant.type;
|
|
3016
|
+
if (variantType === "array" && variant.items) {
|
|
3017
|
+
return variant.items;
|
|
3018
|
+
}
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
}
|
|
3022
|
+
return schema;
|
|
3023
|
+
}
|
|
3024
|
+
function schemaAcceptsArray(schema) {
|
|
3025
|
+
if (typeof schema === "boolean")
|
|
3026
|
+
return false;
|
|
3027
|
+
const schemaType = schema.type;
|
|
3028
|
+
if (schemaType === "array")
|
|
3029
|
+
return true;
|
|
3030
|
+
const variants = schema.oneOf ?? schema.anyOf;
|
|
3031
|
+
if (Array.isArray(variants)) {
|
|
3032
|
+
return variants.some((variant) => isArrayVariant(variant));
|
|
3033
|
+
}
|
|
3034
|
+
return false;
|
|
3035
|
+
}
|
|
3036
|
+
|
|
3037
|
+
class IteratorTask extends GraphAsTask {
|
|
3038
|
+
static type = "IteratorTask";
|
|
3039
|
+
static category = "Flow Control";
|
|
3040
|
+
static title = "Iterator";
|
|
3041
|
+
static description = "Base class for loop-type tasks";
|
|
3042
|
+
static hasDynamicSchemas = true;
|
|
3043
|
+
static getIterationContextSchema() {
|
|
3044
|
+
return ITERATOR_CONTEXT_SCHEMA;
|
|
3045
|
+
}
|
|
3046
|
+
_iteratorPortInfo;
|
|
3047
|
+
_iterationInputSchema;
|
|
3048
|
+
constructor(input = {}, config = {}) {
|
|
3049
|
+
super(input, config);
|
|
3050
|
+
}
|
|
3051
|
+
get runner() {
|
|
3052
|
+
if (!this._runner) {
|
|
3053
|
+
this._runner = new IteratorTaskRunner(this);
|
|
3054
|
+
}
|
|
3055
|
+
return this._runner;
|
|
3056
|
+
}
|
|
3057
|
+
set subGraph(subGraph) {
|
|
3058
|
+
super.subGraph = subGraph;
|
|
3059
|
+
this.invalidateIterationInputSchema();
|
|
3060
|
+
this.events.emit("regenerate");
|
|
3061
|
+
}
|
|
3062
|
+
get subGraph() {
|
|
3063
|
+
return super.subGraph;
|
|
3064
|
+
}
|
|
3065
|
+
regenerateGraph() {
|
|
3066
|
+
this.invalidateIterationInputSchema();
|
|
3067
|
+
super.regenerateGraph();
|
|
3068
|
+
}
|
|
3069
|
+
preserveIterationOrder() {
|
|
3070
|
+
return true;
|
|
3071
|
+
}
|
|
3072
|
+
isReduceTask() {
|
|
3073
|
+
return false;
|
|
3074
|
+
}
|
|
3075
|
+
getInitialAccumulator() {
|
|
3076
|
+
return {};
|
|
3077
|
+
}
|
|
3078
|
+
buildIterationRunInput(analysis, index, iterationCount, extraInput = {}) {
|
|
3079
|
+
return {
|
|
3080
|
+
...analysis.getIterationInput(index),
|
|
3081
|
+
...extraInput,
|
|
3082
|
+
_iterationIndex: index,
|
|
3083
|
+
_iterationCount: iterationCount
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
mergeIterationIntoAccumulator(accumulator, iterationResult, _index) {
|
|
3087
|
+
return iterationResult ?? accumulator;
|
|
3088
|
+
}
|
|
3089
|
+
getEmptyResult() {
|
|
3090
|
+
return {};
|
|
3091
|
+
}
|
|
3092
|
+
collectResults(results) {
|
|
3093
|
+
if (results.length === 0) {
|
|
3094
|
+
return {};
|
|
3095
|
+
}
|
|
3096
|
+
const merged = {};
|
|
3097
|
+
for (const result of results) {
|
|
3098
|
+
if (!result || typeof result !== "object")
|
|
3099
|
+
continue;
|
|
3100
|
+
for (const [key, value] of Object.entries(result)) {
|
|
3101
|
+
if (!merged[key]) {
|
|
3102
|
+
merged[key] = [];
|
|
3103
|
+
}
|
|
3104
|
+
merged[key].push(value);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
return merged;
|
|
3108
|
+
}
|
|
3109
|
+
get concurrencyLimit() {
|
|
3110
|
+
return this.config.concurrencyLimit;
|
|
3111
|
+
}
|
|
3112
|
+
get batchSize() {
|
|
3113
|
+
return this.config.batchSize;
|
|
3114
|
+
}
|
|
3115
|
+
get iterationInputConfig() {
|
|
3116
|
+
return this.config.iterationInputConfig;
|
|
3117
|
+
}
|
|
3118
|
+
buildDefaultIterationInputSchema() {
|
|
3119
|
+
const innerSchema = this.getInnerInputSchema();
|
|
3120
|
+
if (!innerSchema || typeof innerSchema === "boolean") {
|
|
3121
|
+
return { type: "object", properties: {}, additionalProperties: true };
|
|
3122
|
+
}
|
|
3123
|
+
const properties = {};
|
|
3124
|
+
const innerProps = innerSchema.properties || {};
|
|
3125
|
+
for (const [key, propSchema] of Object.entries(innerProps)) {
|
|
3126
|
+
if (typeof propSchema === "boolean")
|
|
3127
|
+
continue;
|
|
3128
|
+
if (propSchema["x-ui-iteration"]) {
|
|
3129
|
+
continue;
|
|
3130
|
+
}
|
|
3131
|
+
const baseSchema = propSchema;
|
|
3132
|
+
properties[key] = createFlexibleSchema(baseSchema);
|
|
3133
|
+
}
|
|
3134
|
+
return {
|
|
3135
|
+
type: "object",
|
|
3136
|
+
properties,
|
|
3137
|
+
additionalProperties: innerSchema.additionalProperties ?? true
|
|
3138
|
+
};
|
|
3139
|
+
}
|
|
3140
|
+
buildConfiguredIterationInputSchema() {
|
|
3141
|
+
const innerSchema = this.getInnerInputSchema();
|
|
3142
|
+
if (!innerSchema || typeof innerSchema === "boolean") {
|
|
3143
|
+
return { type: "object", properties: {}, additionalProperties: true };
|
|
3144
|
+
}
|
|
3145
|
+
const config = this.iterationInputConfig || {};
|
|
3146
|
+
const properties = {};
|
|
3147
|
+
const innerProps = innerSchema.properties || {};
|
|
3148
|
+
for (const [key, propSchema] of Object.entries(innerProps)) {
|
|
3149
|
+
if (typeof propSchema === "boolean")
|
|
3150
|
+
continue;
|
|
3151
|
+
if (propSchema["x-ui-iteration"]) {
|
|
3152
|
+
continue;
|
|
3153
|
+
}
|
|
3154
|
+
const baseSchema = propSchema;
|
|
3155
|
+
const propConfig = config[key];
|
|
3156
|
+
if (!propConfig) {
|
|
3157
|
+
properties[key] = createFlexibleSchema(baseSchema);
|
|
3158
|
+
continue;
|
|
3159
|
+
}
|
|
3160
|
+
switch (propConfig.mode) {
|
|
3161
|
+
case "array":
|
|
3162
|
+
properties[key] = createArraySchema(propConfig.baseSchema);
|
|
3163
|
+
break;
|
|
3164
|
+
case "scalar":
|
|
3165
|
+
properties[key] = propConfig.baseSchema;
|
|
3166
|
+
break;
|
|
3167
|
+
case "flexible":
|
|
3168
|
+
default:
|
|
3169
|
+
properties[key] = createFlexibleSchema(propConfig.baseSchema);
|
|
3170
|
+
break;
|
|
3171
|
+
}
|
|
3172
|
+
}
|
|
3173
|
+
return {
|
|
3174
|
+
type: "object",
|
|
3175
|
+
properties,
|
|
3176
|
+
additionalProperties: innerSchema.additionalProperties ?? true
|
|
3177
|
+
};
|
|
3178
|
+
}
|
|
3179
|
+
getInnerInputSchema() {
|
|
3180
|
+
if (!this.hasChildren())
|
|
3181
|
+
return;
|
|
3182
|
+
const tasks = this.subGraph.getTasks();
|
|
3183
|
+
if (tasks.length === 0)
|
|
3184
|
+
return;
|
|
3185
|
+
const startingNodes = tasks.filter((task) => this.subGraph.getSourceDataflows(task.config.id).length === 0);
|
|
3186
|
+
const sources = startingNodes.length > 0 ? startingNodes : tasks;
|
|
3187
|
+
const properties = {};
|
|
3188
|
+
const required = [];
|
|
3189
|
+
let additionalProperties = false;
|
|
3190
|
+
for (const task of sources) {
|
|
3191
|
+
const inputSchema = task.inputSchema();
|
|
3192
|
+
if (typeof inputSchema === "boolean") {
|
|
3193
|
+
if (inputSchema === true) {
|
|
3194
|
+
additionalProperties = true;
|
|
3195
|
+
}
|
|
3196
|
+
continue;
|
|
3197
|
+
}
|
|
3198
|
+
additionalProperties = additionalProperties || inputSchema.additionalProperties === true;
|
|
3199
|
+
for (const [key, prop] of Object.entries(inputSchema.properties || {})) {
|
|
3200
|
+
if (typeof prop === "boolean")
|
|
3201
|
+
continue;
|
|
3202
|
+
if (!properties[key]) {
|
|
3203
|
+
properties[key] = prop;
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
for (const key of inputSchema.required || []) {
|
|
3207
|
+
if (!required.includes(key)) {
|
|
3208
|
+
required.push(key);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
return {
|
|
3213
|
+
type: "object",
|
|
3214
|
+
properties,
|
|
3215
|
+
...required.length > 0 ? { required } : {},
|
|
3216
|
+
additionalProperties
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
getIterationInputSchema() {
|
|
3220
|
+
if (this._iterationInputSchema) {
|
|
3221
|
+
return this._iterationInputSchema;
|
|
3222
|
+
}
|
|
3223
|
+
this._iterationInputSchema = this.iterationInputConfig ? this.buildConfiguredIterationInputSchema() : this.buildDefaultIterationInputSchema();
|
|
3224
|
+
return this._iterationInputSchema;
|
|
3225
|
+
}
|
|
3226
|
+
setIterationInputSchema(schema) {
|
|
3227
|
+
this._iterationInputSchema = schema;
|
|
3228
|
+
this._inputSchemaNode = undefined;
|
|
3229
|
+
this.events.emit("regenerate");
|
|
3230
|
+
}
|
|
3231
|
+
setPropertyInputMode(propertyName, mode, baseSchema) {
|
|
3232
|
+
const currentSchema = this.getIterationInputSchema();
|
|
3233
|
+
if (typeof currentSchema === "boolean")
|
|
3234
|
+
return;
|
|
3235
|
+
const currentProps = currentSchema.properties || {};
|
|
3236
|
+
const existingProp = currentProps[propertyName];
|
|
3237
|
+
const base = baseSchema ?? (existingProp ? extractBaseSchema(existingProp) : { type: "string" });
|
|
3238
|
+
let newPropSchema;
|
|
3239
|
+
switch (mode) {
|
|
3240
|
+
case "array":
|
|
3241
|
+
newPropSchema = createArraySchema(base);
|
|
3242
|
+
break;
|
|
3243
|
+
case "scalar":
|
|
3244
|
+
newPropSchema = base;
|
|
3245
|
+
break;
|
|
3246
|
+
case "flexible":
|
|
3247
|
+
default:
|
|
3248
|
+
newPropSchema = createFlexibleSchema(base);
|
|
3249
|
+
break;
|
|
3250
|
+
}
|
|
3251
|
+
this._iterationInputSchema = {
|
|
3252
|
+
...currentSchema,
|
|
3253
|
+
properties: {
|
|
3254
|
+
...currentProps,
|
|
3255
|
+
[propertyName]: newPropSchema
|
|
3256
|
+
}
|
|
3257
|
+
};
|
|
3258
|
+
this._inputSchemaNode = undefined;
|
|
3259
|
+
this.events.emit("regenerate");
|
|
3260
|
+
}
|
|
3261
|
+
invalidateIterationInputSchema() {
|
|
3262
|
+
this._iterationInputSchema = undefined;
|
|
3263
|
+
this._iteratorPortInfo = undefined;
|
|
3264
|
+
this._inputSchemaNode = undefined;
|
|
3265
|
+
}
|
|
3266
|
+
analyzeIterationInput(input) {
|
|
3267
|
+
const inputData = input;
|
|
3268
|
+
const schema = this.hasChildren() ? this.getIterationInputSchema() : this.inputSchema();
|
|
3269
|
+
const schemaProps = typeof schema === "object" && schema.properties ? schema.properties : {};
|
|
3270
|
+
const keys = new Set([...Object.keys(schemaProps), ...Object.keys(inputData)]);
|
|
3271
|
+
const arrayPorts = [];
|
|
3272
|
+
const scalarPorts = [];
|
|
3273
|
+
const iteratedValues = {};
|
|
3274
|
+
const arrayLengths = [];
|
|
3275
|
+
for (const key of keys) {
|
|
3276
|
+
if (key.startsWith("_iteration"))
|
|
3277
|
+
continue;
|
|
3278
|
+
const value = inputData[key];
|
|
3279
|
+
const portSchema = schemaProps[key];
|
|
3280
|
+
let shouldIterate;
|
|
3281
|
+
const explicitFlag = getExplicitIterationFlag(portSchema);
|
|
3282
|
+
if (explicitFlag !== undefined) {
|
|
3283
|
+
shouldIterate = explicitFlag;
|
|
3284
|
+
} else {
|
|
3285
|
+
const schemaInference = inferIterationFromSchema(portSchema);
|
|
3286
|
+
shouldIterate = schemaInference ?? Array.isArray(value);
|
|
3287
|
+
}
|
|
3288
|
+
if (!shouldIterate) {
|
|
3289
|
+
scalarPorts.push(key);
|
|
3290
|
+
continue;
|
|
3291
|
+
}
|
|
3292
|
+
if (!Array.isArray(value)) {
|
|
3293
|
+
throw new TaskConfigurationError(`${this.type}: Input '${key}' is configured for iteration but value is not an array.`);
|
|
3294
|
+
}
|
|
3295
|
+
iteratedValues[key] = value;
|
|
3296
|
+
arrayPorts.push(key);
|
|
3297
|
+
arrayLengths.push(value.length);
|
|
3298
|
+
}
|
|
3299
|
+
if (arrayPorts.length === 0) {
|
|
3300
|
+
throw new TaskConfigurationError(`${this.type}: At least one array input is required for iteration. ` + `Mark a port with x-ui-iteration=true, provide array-typed schema, or pass array values at runtime.`);
|
|
3301
|
+
}
|
|
3302
|
+
const uniqueLengths = new Set(arrayLengths);
|
|
3303
|
+
if (uniqueLengths.size > 1) {
|
|
3304
|
+
const lengthInfo = arrayPorts.map((port, index) => `${port}=${arrayLengths[index]}`).join(", ");
|
|
3305
|
+
throw new TaskConfigurationError(`${this.type}: All iterated array inputs must have the same length (zip semantics). ` + `Found different lengths: ${lengthInfo}`);
|
|
3306
|
+
}
|
|
3307
|
+
const iterationCount = arrayLengths[0] ?? 0;
|
|
3308
|
+
const getIterationInput = (index) => {
|
|
3309
|
+
const iterInput = {};
|
|
3310
|
+
for (const key of arrayPorts) {
|
|
3311
|
+
iterInput[key] = iteratedValues[key][index];
|
|
3312
|
+
}
|
|
3313
|
+
for (const key of scalarPorts) {
|
|
3314
|
+
if (key in inputData) {
|
|
3315
|
+
iterInput[key] = inputData[key];
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
return iterInput;
|
|
3319
|
+
};
|
|
3320
|
+
return {
|
|
3321
|
+
iterationCount,
|
|
3322
|
+
arrayPorts,
|
|
3323
|
+
scalarPorts,
|
|
3324
|
+
getIterationInput
|
|
3325
|
+
};
|
|
3326
|
+
}
|
|
3327
|
+
getIterationContextSchema() {
|
|
3328
|
+
return this.constructor.getIterationContextSchema();
|
|
3329
|
+
}
|
|
3330
|
+
inputSchema() {
|
|
3331
|
+
if (this.hasChildren()) {
|
|
3332
|
+
return this.getIterationInputSchema();
|
|
3333
|
+
}
|
|
3334
|
+
return this.constructor.inputSchema();
|
|
3335
|
+
}
|
|
3336
|
+
outputSchema() {
|
|
3337
|
+
if (!this.hasChildren()) {
|
|
3338
|
+
return this.constructor.outputSchema();
|
|
3339
|
+
}
|
|
3340
|
+
return this.getWrappedOutputSchema();
|
|
3341
|
+
}
|
|
3342
|
+
getWrappedOutputSchema() {
|
|
3343
|
+
if (!this.hasChildren()) {
|
|
3344
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
3345
|
+
}
|
|
3346
|
+
const endingNodes = this.subGraph.getTasks().filter((task) => this.subGraph.getTargetDataflows(task.config.id).length === 0);
|
|
3347
|
+
if (endingNodes.length === 0) {
|
|
3348
|
+
return { type: "object", properties: {}, additionalProperties: false };
|
|
3349
|
+
}
|
|
3350
|
+
const properties = {};
|
|
3351
|
+
for (const task of endingNodes) {
|
|
3352
|
+
const taskOutputSchema = task.outputSchema();
|
|
3353
|
+
if (typeof taskOutputSchema === "boolean")
|
|
3354
|
+
continue;
|
|
3355
|
+
for (const [key, schema] of Object.entries(taskOutputSchema.properties || {})) {
|
|
3356
|
+
properties[key] = {
|
|
3357
|
+
type: "array",
|
|
3358
|
+
items: schema
|
|
3359
|
+
};
|
|
3360
|
+
}
|
|
3361
|
+
}
|
|
3362
|
+
return {
|
|
3363
|
+
type: "object",
|
|
3364
|
+
properties,
|
|
3365
|
+
additionalProperties: false
|
|
3366
|
+
};
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3370
|
+
// src/task/WhileTaskRunner.ts
|
|
3371
|
+
class WhileTaskRunner extends GraphAsTaskRunner {
|
|
3372
|
+
async executeTask(input) {
|
|
3373
|
+
const result = await this.task.execute(input, {
|
|
3374
|
+
signal: this.abortController.signal,
|
|
3375
|
+
updateProgress: this.handleProgress.bind(this),
|
|
3376
|
+
own: this.own,
|
|
3377
|
+
registry: this.registry
|
|
3378
|
+
});
|
|
3379
|
+
return result;
|
|
3380
|
+
}
|
|
3381
|
+
async executeTaskReactive(input, output) {
|
|
3382
|
+
const reactiveResult = await this.task.executeReactive(input, output, { own: this.own });
|
|
3383
|
+
return Object.assign({}, output, reactiveResult ?? {});
|
|
3384
|
+
}
|
|
3385
|
+
}
|
|
3386
|
+
|
|
3387
|
+
// src/task/WhileTask.ts
|
|
3388
|
+
var WHILE_CONTEXT_SCHEMA = {
|
|
3389
|
+
type: "object",
|
|
3390
|
+
properties: {
|
|
3391
|
+
_iterationIndex: {
|
|
3392
|
+
type: "integer",
|
|
3393
|
+
minimum: 0,
|
|
3394
|
+
title: "Iteration Number",
|
|
3395
|
+
description: "Current iteration number (0-based)",
|
|
3396
|
+
"x-ui-iteration": true
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
};
|
|
3400
|
+
|
|
3401
|
+
class WhileTask extends GraphAsTask {
|
|
3402
|
+
static type = "WhileTask";
|
|
3403
|
+
static category = "Flow Control";
|
|
3404
|
+
static title = "While Loop";
|
|
3405
|
+
static description = "Loops until a condition function returns false";
|
|
3406
|
+
static hasDynamicSchemas = true;
|
|
3407
|
+
static getIterationContextSchema() {
|
|
3408
|
+
return WHILE_CONTEXT_SCHEMA;
|
|
3409
|
+
}
|
|
3410
|
+
_currentIteration = 0;
|
|
3411
|
+
constructor(input = {}, config = {}) {
|
|
3412
|
+
super(input, config);
|
|
3413
|
+
}
|
|
3414
|
+
get runner() {
|
|
3415
|
+
if (!this._runner) {
|
|
3416
|
+
this._runner = new WhileTaskRunner(this);
|
|
3417
|
+
}
|
|
3418
|
+
return this._runner;
|
|
3419
|
+
}
|
|
3420
|
+
get condition() {
|
|
3421
|
+
return this.config.condition;
|
|
3422
|
+
}
|
|
3423
|
+
get maxIterations() {
|
|
3424
|
+
if (this.config.maxIterations !== undefined)
|
|
3425
|
+
return this.config.maxIterations;
|
|
3426
|
+
const wc = this.config.extras?.whileConfig;
|
|
3427
|
+
return wc?.maxIterations ?? 100;
|
|
3428
|
+
}
|
|
3429
|
+
get chainIterations() {
|
|
3430
|
+
if (this.config.chainIterations !== undefined)
|
|
3431
|
+
return this.config.chainIterations;
|
|
3432
|
+
const wc = this.config.extras?.whileConfig;
|
|
3433
|
+
return wc?.chainIterations ?? true;
|
|
3434
|
+
}
|
|
3435
|
+
get currentIteration() {
|
|
3436
|
+
return this._currentIteration;
|
|
3437
|
+
}
|
|
3438
|
+
buildConditionFromExtras() {
|
|
3439
|
+
const wc = this.config.extras?.whileConfig;
|
|
3440
|
+
if (!wc?.conditionOperator) {
|
|
3441
|
+
return;
|
|
3442
|
+
}
|
|
3443
|
+
const { conditionField, conditionOperator, conditionValue } = wc;
|
|
3444
|
+
return (output) => {
|
|
3445
|
+
const fieldValue = conditionField ? getNestedValue(output, conditionField) : output;
|
|
3446
|
+
return evaluateCondition(fieldValue, conditionOperator, conditionValue ?? "");
|
|
3447
|
+
};
|
|
3448
|
+
}
|
|
3449
|
+
analyzeArrayInputs(input) {
|
|
3450
|
+
const wc = this.config.extras?.whileConfig;
|
|
3451
|
+
if (!wc?.iterationInputConfig) {
|
|
3452
|
+
return null;
|
|
3453
|
+
}
|
|
3454
|
+
const inputData = input;
|
|
3455
|
+
const config = wc.iterationInputConfig;
|
|
3456
|
+
const arrayPorts = [];
|
|
3457
|
+
const scalarPorts = [];
|
|
3458
|
+
const iteratedValues = {};
|
|
3459
|
+
const arrayLengths = [];
|
|
3460
|
+
for (const [key, propConfig] of Object.entries(config)) {
|
|
3461
|
+
const value = inputData[key];
|
|
3462
|
+
if (propConfig.mode === "array") {
|
|
3463
|
+
if (!Array.isArray(value)) {
|
|
3464
|
+
scalarPorts.push(key);
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3467
|
+
iteratedValues[key] = value;
|
|
3468
|
+
arrayPorts.push(key);
|
|
3469
|
+
arrayLengths.push(value.length);
|
|
3470
|
+
} else {
|
|
3471
|
+
scalarPorts.push(key);
|
|
3472
|
+
}
|
|
3473
|
+
}
|
|
3474
|
+
for (const key of Object.keys(inputData)) {
|
|
3475
|
+
if (!config[key] && !key.startsWith("_iteration")) {
|
|
3476
|
+
scalarPorts.push(key);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
if (arrayPorts.length === 0) {
|
|
3480
|
+
return null;
|
|
3481
|
+
}
|
|
3482
|
+
const uniqueLengths = new Set(arrayLengths);
|
|
3483
|
+
if (uniqueLengths.size > 1) {
|
|
3484
|
+
const lengthInfo = arrayPorts.map((port, index) => `${port}=${arrayLengths[index]}`).join(", ");
|
|
3485
|
+
throw new TaskConfigurationError(`${this.type}: All iterated array inputs must have the same length. ` + `Found different lengths: ${lengthInfo}`);
|
|
3486
|
+
}
|
|
3487
|
+
return {
|
|
3488
|
+
arrayPorts,
|
|
3489
|
+
scalarPorts,
|
|
3490
|
+
iteratedValues,
|
|
3491
|
+
iterationCount: arrayLengths[0] ?? 0
|
|
3492
|
+
};
|
|
3493
|
+
}
|
|
3494
|
+
buildIterationInput(input, analysis, index) {
|
|
3495
|
+
const inputData = input;
|
|
3496
|
+
const iterInput = {};
|
|
3497
|
+
for (const key of analysis.arrayPorts) {
|
|
3498
|
+
iterInput[key] = analysis.iteratedValues[key][index];
|
|
3499
|
+
}
|
|
3500
|
+
for (const key of analysis.scalarPorts) {
|
|
3501
|
+
if (key in inputData) {
|
|
3502
|
+
iterInput[key] = inputData[key];
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
return iterInput;
|
|
3506
|
+
}
|
|
3507
|
+
async execute(input, context) {
|
|
3508
|
+
if (!this.hasChildren()) {
|
|
3509
|
+
throw new TaskConfigurationError(`${this.type}: No subgraph set for while loop`);
|
|
3510
|
+
}
|
|
3511
|
+
const condition = this.condition ?? this.buildConditionFromExtras();
|
|
3512
|
+
if (!condition) {
|
|
3513
|
+
throw new TaskConfigurationError(`${this.type}: No condition function provided`);
|
|
3514
|
+
}
|
|
3515
|
+
const arrayAnalysis = this.analyzeArrayInputs(input);
|
|
3516
|
+
this._currentIteration = 0;
|
|
3517
|
+
let currentInput = { ...input };
|
|
3518
|
+
let currentOutput = {};
|
|
3519
|
+
const effectiveMax = arrayAnalysis ? Math.min(this.maxIterations, arrayAnalysis.iterationCount) : this.maxIterations;
|
|
3520
|
+
while (this._currentIteration < effectiveMax) {
|
|
3521
|
+
if (context.signal?.aborted) {
|
|
3522
|
+
break;
|
|
3523
|
+
}
|
|
3524
|
+
let iterationInput;
|
|
3525
|
+
if (arrayAnalysis) {
|
|
3526
|
+
iterationInput = {
|
|
3527
|
+
...this.buildIterationInput(currentInput, arrayAnalysis, this._currentIteration),
|
|
3528
|
+
_iterationIndex: this._currentIteration
|
|
3529
|
+
};
|
|
3530
|
+
} else {
|
|
3531
|
+
iterationInput = {
|
|
3532
|
+
...currentInput,
|
|
3533
|
+
_iterationIndex: this._currentIteration
|
|
3534
|
+
};
|
|
3535
|
+
}
|
|
3536
|
+
const results = await this.subGraph.run(iterationInput, {
|
|
3537
|
+
parentSignal: context.signal
|
|
3538
|
+
});
|
|
3539
|
+
currentOutput = this.subGraph.mergeExecuteOutputsToRunOutput(results, this.compoundMerge);
|
|
3540
|
+
if (!condition(currentOutput, this._currentIteration)) {
|
|
3541
|
+
break;
|
|
3542
|
+
}
|
|
3543
|
+
if (this.chainIterations) {
|
|
3544
|
+
currentInput = { ...currentInput, ...currentOutput };
|
|
3545
|
+
}
|
|
3546
|
+
this._currentIteration++;
|
|
3547
|
+
const progress = Math.min(this._currentIteration / effectiveMax * 100, 99);
|
|
3548
|
+
await context.updateProgress(progress, `Iteration ${this._currentIteration}`);
|
|
3549
|
+
}
|
|
3550
|
+
return currentOutput;
|
|
3551
|
+
}
|
|
3552
|
+
getIterationContextSchema() {
|
|
3553
|
+
return this.constructor.getIterationContextSchema();
|
|
3554
|
+
}
|
|
3555
|
+
getChainedOutputSchema() {
|
|
3556
|
+
if (!this.chainIterations)
|
|
3557
|
+
return;
|
|
3558
|
+
const outputSchema = this.outputSchema();
|
|
3559
|
+
if (typeof outputSchema === "boolean")
|
|
3560
|
+
return;
|
|
3561
|
+
const properties = {};
|
|
3562
|
+
if (outputSchema.properties && typeof outputSchema.properties === "object") {
|
|
3563
|
+
for (const [key, schema] of Object.entries(outputSchema.properties)) {
|
|
3564
|
+
if (key === "_iterations")
|
|
3565
|
+
continue;
|
|
3566
|
+
if (typeof schema === "object" && schema !== null) {
|
|
3567
|
+
properties[key] = { ...schema, "x-ui-iteration": true };
|
|
3568
|
+
}
|
|
3569
|
+
}
|
|
3570
|
+
}
|
|
3571
|
+
if (Object.keys(properties).length === 0)
|
|
3572
|
+
return;
|
|
3573
|
+
return { type: "object", properties };
|
|
3574
|
+
}
|
|
3575
|
+
inputSchema() {
|
|
3576
|
+
if (!this.hasChildren()) {
|
|
3577
|
+
return this.constructor.inputSchema();
|
|
3578
|
+
}
|
|
3579
|
+
const baseSchema = super.inputSchema();
|
|
3580
|
+
if (typeof baseSchema === "boolean")
|
|
3581
|
+
return baseSchema;
|
|
3582
|
+
const wc = this.config.extras?.whileConfig;
|
|
3583
|
+
if (!wc?.iterationInputConfig) {
|
|
3584
|
+
return baseSchema;
|
|
3585
|
+
}
|
|
3586
|
+
const properties = { ...baseSchema.properties || {} };
|
|
3587
|
+
for (const [key, propConfig] of Object.entries(wc.iterationInputConfig)) {
|
|
3588
|
+
if (propConfig.mode === "array" && properties[key]) {
|
|
3589
|
+
const scalarSchema = properties[key];
|
|
3590
|
+
properties[key] = {
|
|
3591
|
+
anyOf: [scalarSchema, { type: "array", items: scalarSchema }]
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return {
|
|
3596
|
+
...baseSchema,
|
|
3597
|
+
properties
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
static inputSchema() {
|
|
3601
|
+
return {
|
|
3602
|
+
type: "object",
|
|
3603
|
+
properties: {},
|
|
3604
|
+
additionalProperties: true
|
|
3605
|
+
};
|
|
3606
|
+
}
|
|
3607
|
+
static outputSchema() {
|
|
3608
|
+
return {
|
|
3609
|
+
type: "object",
|
|
3610
|
+
properties: {
|
|
3611
|
+
_iterations: {
|
|
3612
|
+
type: "number",
|
|
3613
|
+
title: "Iterations",
|
|
3614
|
+
description: "Number of iterations executed"
|
|
3615
|
+
}
|
|
3616
|
+
},
|
|
3617
|
+
additionalProperties: true
|
|
3618
|
+
};
|
|
3619
|
+
}
|
|
3620
|
+
outputSchema() {
|
|
3621
|
+
if (!this.hasChildren()) {
|
|
3622
|
+
return this.constructor.outputSchema();
|
|
3623
|
+
}
|
|
3624
|
+
const tasks = this.subGraph.getTasks();
|
|
3625
|
+
const endingNodes = tasks.filter((task) => this.subGraph.getTargetDataflows(task.config.id).length === 0);
|
|
3626
|
+
if (endingNodes.length === 0) {
|
|
3627
|
+
return this.constructor.outputSchema();
|
|
3628
|
+
}
|
|
3629
|
+
const properties = {
|
|
3630
|
+
_iterations: {
|
|
3631
|
+
type: "number",
|
|
3632
|
+
title: "Iterations",
|
|
3633
|
+
description: "Number of iterations executed"
|
|
3634
|
+
}
|
|
3635
|
+
};
|
|
3636
|
+
for (const task of endingNodes) {
|
|
3637
|
+
const taskOutputSchema = task.outputSchema();
|
|
3638
|
+
if (typeof taskOutputSchema === "boolean")
|
|
3639
|
+
continue;
|
|
3640
|
+
const taskProperties = taskOutputSchema.properties || {};
|
|
3641
|
+
for (const [key, schema] of Object.entries(taskProperties)) {
|
|
3642
|
+
if (!properties[key]) {
|
|
3643
|
+
properties[key] = schema;
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3647
|
+
return {
|
|
3648
|
+
type: "object",
|
|
3649
|
+
properties,
|
|
3650
|
+
additionalProperties: false
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
}
|
|
3654
|
+
Workflow.prototype.while = CreateLoopWorkflow(WhileTask);
|
|
3655
|
+
Workflow.prototype.endWhile = CreateEndLoopWorkflow("endWhile");
|
|
3656
|
+
|
|
3657
|
+
// src/task/iterationSchema.ts
|
|
3658
|
+
function isFlexibleSchema(schema) {
|
|
3659
|
+
if (typeof schema === "boolean")
|
|
3660
|
+
return false;
|
|
3661
|
+
const variants = schema.oneOf ?? schema.anyOf;
|
|
3662
|
+
const arr = Array.isArray(variants) ? variants : undefined;
|
|
3663
|
+
if (!arr || arr.length !== 2)
|
|
3664
|
+
return false;
|
|
3665
|
+
let hasScalar = false;
|
|
3666
|
+
let hasArray = false;
|
|
3667
|
+
for (const variant of arr) {
|
|
3668
|
+
if (typeof variant !== "object")
|
|
3669
|
+
continue;
|
|
3670
|
+
const v = variant;
|
|
3671
|
+
if (v.type === "array" || "items" in v) {
|
|
3672
|
+
hasArray = true;
|
|
3673
|
+
} else {
|
|
3674
|
+
hasScalar = true;
|
|
3675
|
+
}
|
|
3676
|
+
}
|
|
3677
|
+
return hasScalar && hasArray;
|
|
3678
|
+
}
|
|
3679
|
+
function isStrictArraySchema(schema) {
|
|
3680
|
+
if (typeof schema === "boolean")
|
|
3681
|
+
return false;
|
|
3682
|
+
const s = schema;
|
|
3683
|
+
return s.type === "array" && !isFlexibleSchema(schema);
|
|
3684
|
+
}
|
|
3685
|
+
function getInputModeFromSchema(schema) {
|
|
3686
|
+
if (isFlexibleSchema(schema))
|
|
3687
|
+
return "flexible";
|
|
3688
|
+
if (isStrictArraySchema(schema))
|
|
3689
|
+
return "array";
|
|
3690
|
+
return "scalar";
|
|
3691
|
+
}
|
|
3692
|
+
function getIterationContextSchemaForType(taskType) {
|
|
3693
|
+
if (taskType === "MapTask" || taskType === "ReduceTask") {
|
|
3694
|
+
return ITERATOR_CONTEXT_SCHEMA;
|
|
3695
|
+
}
|
|
3696
|
+
if (taskType === "WhileTask") {
|
|
3697
|
+
return WHILE_CONTEXT_SCHEMA;
|
|
3698
|
+
}
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
function addIterationContextToSchema(existingSchema, parentTaskType) {
|
|
3702
|
+
const contextSchema = getIterationContextSchemaForType(parentTaskType);
|
|
3703
|
+
if (!contextSchema) {
|
|
3704
|
+
return existingSchema ?? { type: "object", properties: {} };
|
|
3705
|
+
}
|
|
3706
|
+
const baseProperties = existingSchema && typeof existingSchema !== "boolean" && existingSchema.properties && typeof existingSchema.properties !== "boolean" ? existingSchema.properties : {};
|
|
3707
|
+
const contextProperties = typeof contextSchema !== "boolean" && contextSchema.properties && typeof contextSchema.properties !== "boolean" ? contextSchema.properties : {};
|
|
3708
|
+
return {
|
|
3709
|
+
type: "object",
|
|
3710
|
+
properties: {
|
|
3711
|
+
...baseProperties,
|
|
3712
|
+
...contextProperties
|
|
3713
|
+
}
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
function isIterationProperty(schema) {
|
|
3717
|
+
if (!schema || typeof schema === "boolean")
|
|
3718
|
+
return false;
|
|
3719
|
+
return schema["x-ui-iteration"] === true;
|
|
3720
|
+
}
|
|
3721
|
+
function filterIterationProperties(schema) {
|
|
3722
|
+
if (!schema || typeof schema === "boolean")
|
|
3723
|
+
return schema;
|
|
3724
|
+
const props = schema.properties;
|
|
3725
|
+
if (!props || typeof props === "boolean")
|
|
3726
|
+
return schema;
|
|
3727
|
+
const filteredProps = {};
|
|
3728
|
+
for (const [key, propSchema] of Object.entries(props)) {
|
|
3729
|
+
if (!isIterationProperty(propSchema)) {
|
|
3730
|
+
filteredProps[key] = propSchema;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
if (Object.keys(filteredProps).length === 0) {
|
|
3734
|
+
return { type: "object", properties: {} };
|
|
3735
|
+
}
|
|
3736
|
+
return { ...schema, properties: filteredProps };
|
|
3737
|
+
}
|
|
3738
|
+
function extractIterationProperties(schema) {
|
|
3739
|
+
if (!schema || typeof schema === "boolean")
|
|
3740
|
+
return;
|
|
3741
|
+
const props = schema.properties;
|
|
3742
|
+
if (!props || typeof props === "boolean")
|
|
3743
|
+
return;
|
|
3744
|
+
const iterProps = {};
|
|
3745
|
+
for (const [key, propSchema] of Object.entries(props)) {
|
|
3746
|
+
if (isIterationProperty(propSchema)) {
|
|
3747
|
+
iterProps[key] = propSchema;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
if (Object.keys(iterProps).length === 0)
|
|
3751
|
+
return;
|
|
3752
|
+
return { type: "object", properties: iterProps };
|
|
3753
|
+
}
|
|
3754
|
+
function removeIterationProperties(schema) {
|
|
3755
|
+
return filterIterationProperties(schema);
|
|
3756
|
+
}
|
|
3757
|
+
function mergeChainedOutputToInput(inputSchema, outputSchema) {
|
|
3758
|
+
const baseSchema = filterIterationProperties(inputSchema) ?? {
|
|
3759
|
+
type: "object",
|
|
3760
|
+
properties: {}
|
|
3761
|
+
};
|
|
3762
|
+
if (!outputSchema || typeof outputSchema === "boolean") {
|
|
3763
|
+
return baseSchema;
|
|
3764
|
+
}
|
|
3765
|
+
const outProps = outputSchema.properties;
|
|
3766
|
+
if (!outProps || typeof outProps === "boolean") {
|
|
3767
|
+
return baseSchema;
|
|
3768
|
+
}
|
|
3769
|
+
const baseProps = typeof baseSchema !== "boolean" && baseSchema.properties && typeof baseSchema.properties !== "boolean" ? baseSchema.properties : {};
|
|
3770
|
+
const mergedProperties = { ...baseProps };
|
|
3771
|
+
for (const [key, propSchema] of Object.entries(outProps)) {
|
|
3772
|
+
if (typeof propSchema === "object" && propSchema !== null) {
|
|
3773
|
+
mergedProperties[key] = { ...propSchema, "x-ui-iteration": true };
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
return {
|
|
3777
|
+
type: "object",
|
|
3778
|
+
properties: mergedProperties
|
|
3779
|
+
};
|
|
3780
|
+
}
|
|
3781
|
+
function buildIterationInputSchema(innerSchema, config) {
|
|
3782
|
+
if (!innerSchema || typeof innerSchema === "boolean") {
|
|
3783
|
+
return { type: "object", properties: {} };
|
|
3784
|
+
}
|
|
3785
|
+
const innerProps = innerSchema.properties;
|
|
3786
|
+
if (!innerProps || typeof innerProps === "boolean") {
|
|
3787
|
+
return { type: "object", properties: {} };
|
|
3788
|
+
}
|
|
3789
|
+
const properties = {};
|
|
3790
|
+
const propsRecord = innerProps;
|
|
3791
|
+
for (const [key, propSchema] of Object.entries(propsRecord)) {
|
|
3792
|
+
if (typeof propSchema === "boolean")
|
|
3793
|
+
continue;
|
|
3794
|
+
if (propSchema["x-ui-iteration"]) {
|
|
3795
|
+
continue;
|
|
3796
|
+
}
|
|
3797
|
+
const originalProps = propSchema;
|
|
3798
|
+
const metadata = {};
|
|
3799
|
+
for (const metaKey of Object.keys(originalProps)) {
|
|
3800
|
+
if (metaKey === "title" || metaKey === "description" || metaKey.startsWith("x-")) {
|
|
3801
|
+
metadata[metaKey] = originalProps[metaKey];
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
const baseSchema = extractBaseSchema(propSchema);
|
|
3805
|
+
const propConfig = config?.[key];
|
|
3806
|
+
const mode = propConfig?.mode ?? "flexible";
|
|
3807
|
+
const base = propConfig?.baseSchema ?? baseSchema;
|
|
3808
|
+
let wrappedSchema;
|
|
3809
|
+
switch (mode) {
|
|
3810
|
+
case "array":
|
|
3811
|
+
wrappedSchema = createArraySchema(base);
|
|
3812
|
+
break;
|
|
3813
|
+
case "scalar":
|
|
3814
|
+
wrappedSchema = base;
|
|
3815
|
+
break;
|
|
3816
|
+
case "flexible":
|
|
3817
|
+
default:
|
|
3818
|
+
wrappedSchema = createFlexibleSchema(base);
|
|
3819
|
+
break;
|
|
3820
|
+
}
|
|
3821
|
+
if (Object.keys(metadata).length > 0 && typeof wrappedSchema === "object") {
|
|
3822
|
+
properties[key] = { ...metadata, ...wrappedSchema };
|
|
3823
|
+
} else {
|
|
3824
|
+
properties[key] = wrappedSchema;
|
|
3825
|
+
}
|
|
3826
|
+
}
|
|
3827
|
+
return {
|
|
3828
|
+
type: "object",
|
|
3829
|
+
properties
|
|
3830
|
+
};
|
|
3831
|
+
}
|
|
3832
|
+
function findArrayPorts(schema) {
|
|
3833
|
+
if (!schema || typeof schema === "boolean")
|
|
3834
|
+
return [];
|
|
3835
|
+
const props = schema.properties;
|
|
3836
|
+
if (!props || typeof props === "boolean")
|
|
3837
|
+
return [];
|
|
3838
|
+
const arrayPorts = [];
|
|
3839
|
+
const propsRecord = props;
|
|
3840
|
+
for (const [key, propSchema] of Object.entries(propsRecord)) {
|
|
3841
|
+
if (typeof propSchema === "boolean")
|
|
3842
|
+
continue;
|
|
3843
|
+
if (propSchema.type === "array") {
|
|
3844
|
+
arrayPorts.push(key);
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
return arrayPorts;
|
|
3848
|
+
}
|
|
3849
|
+
function wrapSchemaInArray(schema) {
|
|
3850
|
+
if (!schema || typeof schema === "boolean")
|
|
3851
|
+
return schema;
|
|
3852
|
+
const props = schema.properties;
|
|
3853
|
+
if (!props || typeof props === "boolean")
|
|
3854
|
+
return schema;
|
|
3855
|
+
const propsRecord = props;
|
|
3856
|
+
const wrappedProperties = {};
|
|
3857
|
+
for (const [key, propSchema] of Object.entries(propsRecord)) {
|
|
3858
|
+
wrappedProperties[key] = {
|
|
3859
|
+
type: "array",
|
|
3860
|
+
items: propSchema
|
|
3861
|
+
};
|
|
3862
|
+
}
|
|
3863
|
+
return {
|
|
3864
|
+
type: "object",
|
|
3865
|
+
properties: wrappedProperties
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3868
|
+
// src/task/JobQueueFactory.ts
|
|
3869
|
+
import {
|
|
3870
|
+
JobQueueClient,
|
|
3871
|
+
JobQueueServer
|
|
3872
|
+
} from "@workglow/job-queue";
|
|
3873
|
+
import { InMemoryQueueStorage } from "@workglow/storage";
|
|
3874
|
+
import { createServiceToken as createServiceToken2, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
|
|
3875
|
+
var JOB_QUEUE_FACTORY = createServiceToken2("taskgraph.jobQueueFactory");
|
|
3876
|
+
var defaultJobQueueFactory = async ({
|
|
3877
|
+
queueName,
|
|
3878
|
+
jobClass,
|
|
3879
|
+
options
|
|
3880
|
+
}) => {
|
|
3881
|
+
const storage = options?.storage ?? new InMemoryQueueStorage(queueName);
|
|
3882
|
+
await storage.setupDatabase();
|
|
3883
|
+
const server = new JobQueueServer(jobClass, {
|
|
3884
|
+
storage,
|
|
3885
|
+
queueName,
|
|
3886
|
+
limiter: options?.limiter,
|
|
3887
|
+
workerCount: options?.workerCount,
|
|
3888
|
+
pollIntervalMs: options?.pollIntervalMs,
|
|
3889
|
+
deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
|
|
3890
|
+
deleteAfterFailureMs: options?.deleteAfterFailureMs,
|
|
3891
|
+
deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
|
|
3892
|
+
cleanupIntervalMs: options?.cleanupIntervalMs
|
|
3893
|
+
});
|
|
3894
|
+
const client = new JobQueueClient({
|
|
3895
|
+
storage,
|
|
3896
|
+
queueName
|
|
3897
|
+
});
|
|
3898
|
+
client.attach(server);
|
|
3899
|
+
return { server, client, storage };
|
|
3900
|
+
};
|
|
3901
|
+
function registerJobQueueFactory(factory) {
|
|
3902
|
+
globalServiceRegistry3.registerInstance(JOB_QUEUE_FACTORY, factory);
|
|
3903
|
+
}
|
|
3904
|
+
function createJobQueueFactoryWithOptions(defaultOptions = {}) {
|
|
3905
|
+
return async ({
|
|
3906
|
+
queueName,
|
|
3907
|
+
jobClass,
|
|
3908
|
+
options
|
|
3909
|
+
}) => {
|
|
3910
|
+
const mergedOptions = {
|
|
3911
|
+
...defaultOptions,
|
|
3912
|
+
...options ?? {}
|
|
3913
|
+
};
|
|
3914
|
+
const storage = mergedOptions.storage ?? new InMemoryQueueStorage(queueName);
|
|
3915
|
+
await storage.setupDatabase();
|
|
3916
|
+
const server = new JobQueueServer(jobClass, {
|
|
3917
|
+
storage,
|
|
3918
|
+
queueName,
|
|
3919
|
+
limiter: mergedOptions.limiter,
|
|
3920
|
+
workerCount: mergedOptions.workerCount,
|
|
3921
|
+
pollIntervalMs: mergedOptions.pollIntervalMs,
|
|
3922
|
+
deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
|
|
3923
|
+
deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
|
|
3924
|
+
deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
|
|
3925
|
+
cleanupIntervalMs: mergedOptions.cleanupIntervalMs
|
|
3926
|
+
});
|
|
3927
|
+
const client = new JobQueueClient({
|
|
3928
|
+
storage,
|
|
3929
|
+
queueName
|
|
3930
|
+
});
|
|
3931
|
+
client.attach(server);
|
|
3932
|
+
return { server, client, storage };
|
|
3933
|
+
};
|
|
3934
|
+
}
|
|
3935
|
+
function getJobQueueFactory() {
|
|
3936
|
+
if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
|
|
3937
|
+
registerJobQueueFactory(defaultJobQueueFactory);
|
|
3938
|
+
}
|
|
3939
|
+
return globalServiceRegistry3.get(JOB_QUEUE_FACTORY);
|
|
3940
|
+
}
|
|
3941
|
+
if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
|
|
3942
|
+
registerJobQueueFactory(defaultJobQueueFactory);
|
|
2600
3943
|
}
|
|
2601
3944
|
// src/task/JobQueueTask.ts
|
|
2602
3945
|
import { Job as Job2 } from "@workglow/job-queue";
|
|
@@ -2782,6 +4125,155 @@ class JobQueueTask extends GraphAsTask {
|
|
|
2782
4125
|
super.abort();
|
|
2783
4126
|
}
|
|
2784
4127
|
}
|
|
4128
|
+
// src/task/MapTask.ts
|
|
4129
|
+
class MapTask extends IteratorTask {
|
|
4130
|
+
static type = "MapTask";
|
|
4131
|
+
static category = "Flow Control";
|
|
4132
|
+
static title = "Map";
|
|
4133
|
+
static description = "Transforms array inputs by running a workflow per item";
|
|
4134
|
+
static compoundMerge = PROPERTY_ARRAY;
|
|
4135
|
+
static inputSchema() {
|
|
4136
|
+
return {
|
|
4137
|
+
type: "object",
|
|
4138
|
+
properties: {},
|
|
4139
|
+
additionalProperties: true
|
|
4140
|
+
};
|
|
4141
|
+
}
|
|
4142
|
+
static outputSchema() {
|
|
4143
|
+
return {
|
|
4144
|
+
type: "object",
|
|
4145
|
+
properties: {},
|
|
4146
|
+
additionalProperties: true
|
|
4147
|
+
};
|
|
4148
|
+
}
|
|
4149
|
+
get preserveOrder() {
|
|
4150
|
+
return this.config.preserveOrder ?? true;
|
|
4151
|
+
}
|
|
4152
|
+
get flatten() {
|
|
4153
|
+
return this.config.flatten ?? false;
|
|
4154
|
+
}
|
|
4155
|
+
preserveIterationOrder() {
|
|
4156
|
+
return this.preserveOrder;
|
|
4157
|
+
}
|
|
4158
|
+
getEmptyResult() {
|
|
4159
|
+
const schema = this.outputSchema();
|
|
4160
|
+
if (typeof schema === "boolean") {
|
|
4161
|
+
return {};
|
|
4162
|
+
}
|
|
4163
|
+
const result = {};
|
|
4164
|
+
for (const key of Object.keys(schema.properties || {})) {
|
|
4165
|
+
result[key] = [];
|
|
4166
|
+
}
|
|
4167
|
+
return result;
|
|
4168
|
+
}
|
|
4169
|
+
outputSchema() {
|
|
4170
|
+
if (!this.hasChildren()) {
|
|
4171
|
+
return this.constructor.outputSchema();
|
|
4172
|
+
}
|
|
4173
|
+
return this.getWrappedOutputSchema();
|
|
4174
|
+
}
|
|
4175
|
+
collectResults(results) {
|
|
4176
|
+
const collected = super.collectResults(results);
|
|
4177
|
+
if (!this.flatten || typeof collected !== "object" || collected === null) {
|
|
4178
|
+
return collected;
|
|
4179
|
+
}
|
|
4180
|
+
const flattened = {};
|
|
4181
|
+
for (const [key, value] of Object.entries(collected)) {
|
|
4182
|
+
if (Array.isArray(value)) {
|
|
4183
|
+
flattened[key] = value.flat();
|
|
4184
|
+
} else {
|
|
4185
|
+
flattened[key] = value;
|
|
4186
|
+
}
|
|
4187
|
+
}
|
|
4188
|
+
return flattened;
|
|
4189
|
+
}
|
|
4190
|
+
}
|
|
4191
|
+
queueMicrotask(() => {
|
|
4192
|
+
Workflow.prototype.map = CreateLoopWorkflow(MapTask);
|
|
4193
|
+
Workflow.prototype.endMap = CreateEndLoopWorkflow("endMap");
|
|
4194
|
+
});
|
|
4195
|
+
// src/task/ReduceTask.ts
|
|
4196
|
+
class ReduceTask extends IteratorTask {
|
|
4197
|
+
static type = "ReduceTask";
|
|
4198
|
+
static category = "Flow Control";
|
|
4199
|
+
static title = "Reduce";
|
|
4200
|
+
static description = "Processes iterated inputs sequentially with an accumulator (fold)";
|
|
4201
|
+
constructor(input = {}, config = {}) {
|
|
4202
|
+
const reduceConfig = {
|
|
4203
|
+
...config,
|
|
4204
|
+
concurrencyLimit: 1,
|
|
4205
|
+
batchSize: 1
|
|
4206
|
+
};
|
|
4207
|
+
super(input, reduceConfig);
|
|
4208
|
+
}
|
|
4209
|
+
get initialValue() {
|
|
4210
|
+
return this.config.initialValue ?? {};
|
|
4211
|
+
}
|
|
4212
|
+
isReduceTask() {
|
|
4213
|
+
return true;
|
|
4214
|
+
}
|
|
4215
|
+
getInitialAccumulator() {
|
|
4216
|
+
const value = this.initialValue;
|
|
4217
|
+
if (Array.isArray(value)) {
|
|
4218
|
+
return [...value];
|
|
4219
|
+
}
|
|
4220
|
+
if (value && typeof value === "object") {
|
|
4221
|
+
return { ...value };
|
|
4222
|
+
}
|
|
4223
|
+
return value;
|
|
4224
|
+
}
|
|
4225
|
+
buildIterationRunInput(analysis, index, iterationCount, extraInput = {}) {
|
|
4226
|
+
return super.buildIterationRunInput(analysis, index, iterationCount, {
|
|
4227
|
+
accumulator: extraInput.accumulator
|
|
4228
|
+
});
|
|
4229
|
+
}
|
|
4230
|
+
getEmptyResult() {
|
|
4231
|
+
return this.getInitialAccumulator();
|
|
4232
|
+
}
|
|
4233
|
+
static inputSchema() {
|
|
4234
|
+
return {
|
|
4235
|
+
type: "object",
|
|
4236
|
+
properties: {},
|
|
4237
|
+
additionalProperties: true
|
|
4238
|
+
};
|
|
4239
|
+
}
|
|
4240
|
+
static outputSchema() {
|
|
4241
|
+
return {
|
|
4242
|
+
type: "object",
|
|
4243
|
+
properties: {},
|
|
4244
|
+
additionalProperties: true
|
|
4245
|
+
};
|
|
4246
|
+
}
|
|
4247
|
+
outputSchema() {
|
|
4248
|
+
if (!this.hasChildren()) {
|
|
4249
|
+
return this.constructor.outputSchema();
|
|
4250
|
+
}
|
|
4251
|
+
const endingNodes = this.subGraph.getTasks().filter((task) => this.subGraph.getTargetDataflows(task.config.id).length === 0);
|
|
4252
|
+
if (endingNodes.length === 0) {
|
|
4253
|
+
return this.constructor.outputSchema();
|
|
4254
|
+
}
|
|
4255
|
+
const properties = {};
|
|
4256
|
+
for (const task of endingNodes) {
|
|
4257
|
+
const taskOutputSchema = task.outputSchema();
|
|
4258
|
+
if (typeof taskOutputSchema === "boolean")
|
|
4259
|
+
continue;
|
|
4260
|
+
for (const [key, schema] of Object.entries(taskOutputSchema.properties || {})) {
|
|
4261
|
+
if (!properties[key]) {
|
|
4262
|
+
properties[key] = schema;
|
|
4263
|
+
}
|
|
4264
|
+
}
|
|
4265
|
+
}
|
|
4266
|
+
return {
|
|
4267
|
+
type: "object",
|
|
4268
|
+
properties,
|
|
4269
|
+
additionalProperties: false
|
|
4270
|
+
};
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
queueMicrotask(() => {
|
|
4274
|
+
Workflow.prototype.reduce = CreateLoopWorkflow(ReduceTask);
|
|
4275
|
+
Workflow.prototype.endReduce = CreateEndLoopWorkflow("endReduce");
|
|
4276
|
+
});
|
|
2785
4277
|
// src/task/TaskRegistry.ts
|
|
2786
4278
|
var taskConstructors = new Map;
|
|
2787
4279
|
function registerTask(baseClass) {
|
|
@@ -2851,7 +4343,7 @@ var createGraphFromGraphJSON = (graphJsonObj) => {
|
|
|
2851
4343
|
};
|
|
2852
4344
|
// src/task/index.ts
|
|
2853
4345
|
var registerBaseTasks = () => {
|
|
2854
|
-
const tasks = [ConditionalTask,
|
|
4346
|
+
const tasks = [GraphAsTask, ConditionalTask, MapTask, WhileTask, ReduceTask];
|
|
2855
4347
|
tasks.map(TaskRegistry.registerTask);
|
|
2856
4348
|
return tasks;
|
|
2857
4349
|
};
|
|
@@ -3012,25 +4504,47 @@ class TaskOutputTabularRepository extends TaskOutputRepository {
|
|
|
3012
4504
|
}
|
|
3013
4505
|
}
|
|
3014
4506
|
export {
|
|
4507
|
+
wrapSchemaInArray,
|
|
3015
4508
|
setTaskQueueRegistry,
|
|
3016
4509
|
serialGraph,
|
|
4510
|
+
schemaAcceptsArray,
|
|
3017
4511
|
resolveSchemaInputs,
|
|
4512
|
+
removeIterationProperties,
|
|
3018
4513
|
registerJobQueueFactory,
|
|
3019
4514
|
registerBaseTasks,
|
|
3020
4515
|
pipe,
|
|
3021
4516
|
parallel,
|
|
4517
|
+
mergeChainedOutputToInput,
|
|
4518
|
+
isStrictArraySchema,
|
|
4519
|
+
isIterationProperty,
|
|
4520
|
+
isFlexibleSchema,
|
|
3022
4521
|
getTaskQueueRegistry,
|
|
4522
|
+
getNestedValue,
|
|
3023
4523
|
getLastTask,
|
|
3024
4524
|
getJobQueueFactory,
|
|
4525
|
+
getIterationContextSchemaForType,
|
|
4526
|
+
getInputModeFromSchema,
|
|
4527
|
+
findArrayPorts,
|
|
4528
|
+
filterIterationProperties,
|
|
4529
|
+
extractIterationProperties,
|
|
4530
|
+
extractBaseSchema,
|
|
4531
|
+
evaluateCondition,
|
|
3025
4532
|
ensureTask,
|
|
3026
4533
|
createTaskFromGraphJSON,
|
|
3027
4534
|
createTaskFromDependencyJSON,
|
|
3028
4535
|
createJobQueueFactoryWithOptions,
|
|
3029
4536
|
createGraphFromGraphJSON,
|
|
3030
4537
|
createGraphFromDependencyJSON,
|
|
4538
|
+
createFlexibleSchema,
|
|
4539
|
+
createArraySchema,
|
|
3031
4540
|
connect,
|
|
4541
|
+
buildIterationInputSchema,
|
|
4542
|
+
addIterationContextToSchema,
|
|
3032
4543
|
WorkflowError,
|
|
3033
4544
|
Workflow,
|
|
4545
|
+
WhileTaskRunner,
|
|
4546
|
+
WhileTask,
|
|
4547
|
+
WHILE_CONTEXT_SCHEMA,
|
|
3034
4548
|
TaskStatus,
|
|
3035
4549
|
TaskRegistry,
|
|
3036
4550
|
TaskQueueRegistry,
|
|
@@ -3053,10 +4567,15 @@ export {
|
|
|
3053
4567
|
Task,
|
|
3054
4568
|
TASK_OUTPUT_REPOSITORY,
|
|
3055
4569
|
TASK_GRAPH_REPOSITORY,
|
|
4570
|
+
ReduceTask,
|
|
3056
4571
|
PROPERTY_ARRAY,
|
|
4572
|
+
MapTask,
|
|
3057
4573
|
JobTaskFailedError,
|
|
3058
4574
|
JobQueueTask,
|
|
3059
4575
|
JOB_QUEUE_FACTORY,
|
|
4576
|
+
IteratorTaskRunner,
|
|
4577
|
+
IteratorTask,
|
|
4578
|
+
ITERATOR_CONTEXT_SCHEMA,
|
|
3060
4579
|
GraphAsTaskRunner,
|
|
3061
4580
|
GraphAsTask,
|
|
3062
4581
|
GRAPH_RESULT_ARRAY,
|
|
@@ -3067,7 +4586,9 @@ export {
|
|
|
3067
4586
|
DATAFLOW_ERROR_PORT,
|
|
3068
4587
|
DATAFLOW_ALL_PORTS,
|
|
3069
4588
|
CreateWorkflow,
|
|
4589
|
+
CreateLoopWorkflow,
|
|
4590
|
+
CreateEndLoopWorkflow,
|
|
3070
4591
|
ConditionalTask
|
|
3071
4592
|
};
|
|
3072
4593
|
|
|
3073
|
-
//# debugId=
|
|
4594
|
+
//# debugId=4D22DE67A27A853E64756E2164756E21
|