@workglow/task-graph 0.0.89 → 0.0.90

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