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