@workglow/task-graph 0.0.89 → 0.0.90

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.js 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 uuid43 } from "@workglow/util";
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(input, context) {
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(input, output, context) {
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 = "Hidden";
915
- static title = "Conditional Task";
916
- static description = "Evaluates conditions to determine which dataflows are active";
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.config.branches ?? [];
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 && this.config.defaultBranch) {
940
- const defaultBranchExists = branches.some((b) => b.id === this.config.defaultBranch);
1050
+ if (this.activeBranches.size === 0 && defaultBranch) {
1051
+ const defaultBranchExists = branches.some((b) => b.id === defaultBranch);
941
1052
  if (defaultBranchExists) {
942
- this.activeBranches.add(this.config.defaultBranch);
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)
@@ -1546,9 +1693,9 @@ class GraphAsTaskRunner extends TaskRunner {
1546
1693
  // src/task/GraphAsTask.ts
1547
1694
  class GraphAsTask extends Task {
1548
1695
  static type = "GraphAsTask";
1549
- static title = "Graph as Task";
1550
- static description = "A task that contains a subgraph of tasks";
1551
- static category = "Hidden";
1696
+ static title = "Group";
1697
+ static description = "A group of tasks that are executed together";
1698
+ static category = "Flow Control";
1552
1699
  static compoundMerge = PROPERTY_ARRAY;
1553
1700
  static hasDynamicSchemas = true;
1554
1701
  constructor(input = {}, config = {}) {
@@ -1728,38 +1875,64 @@ class GraphAsTask extends Task {
1728
1875
  }
1729
1876
 
1730
1877
  // src/task-graph/Workflow.ts
1731
- 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
+
1732
1896
  class WorkflowTask extends GraphAsTask {
1733
1897
  static type = "Workflow";
1734
1898
  static compoundMerge = PROPERTY_ARRAY;
1735
1899
  }
1736
- var taskIdCounter = 0;
1737
1900
 
1738
1901
  class Workflow {
1739
- constructor(repository) {
1740
- this._repository = repository;
1741
- this._graph = new TaskGraph({
1742
- outputCache: this._repository
1743
- });
1744
- this._onChanged = this._onChanged.bind(this);
1745
- this.setupEvents();
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
+ }
1746
1911
  }
1747
1912
  _graph;
1748
1913
  _dataFlows = [];
1749
1914
  _error = "";
1750
- _repository;
1915
+ _outputCache;
1751
1916
  _abortController;
1917
+ _parentWorkflow;
1918
+ _iteratorTask;
1919
+ _pendingLoopConnect;
1920
+ outputCache() {
1921
+ return this._outputCache;
1922
+ }
1923
+ get isLoopBuilder() {
1924
+ return this._parentWorkflow !== undefined;
1925
+ }
1752
1926
  events = new EventEmitter4;
1753
1927
  static createWorkflow(taskClass) {
1754
1928
  const helper = function(input = {}, config = {}) {
1755
1929
  this._error = "";
1756
1930
  const parent = getLastTask(this);
1757
- taskIdCounter++;
1758
- const task = this.addTask(taskClass, input, { id: String(taskIdCounter), ...config });
1931
+ const task = this.addTaskToGraph(taskClass, input, { id: uuid43(), ...config });
1759
1932
  if (this._dataFlows.length > 0) {
1760
1933
  this._dataFlows.forEach((dataflow) => {
1761
1934
  const taskSchema = task.inputSchema();
1762
- 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) {
1763
1936
  this._error = `Input ${dataflow.targetTaskPortId} not found on task ${task.config.id}`;
1764
1937
  console.error(this._error);
1765
1938
  return;
@@ -1770,158 +1943,23 @@ class Workflow {
1770
1943
  this._dataFlows = [];
1771
1944
  }
1772
1945
  if (parent && this.graph.getTargetDataflows(parent.config.id).length === 0) {
1773
- const matches = new Map;
1774
- const sourceSchema = parent.outputSchema();
1775
- const targetSchema = task.inputSchema();
1776
- const makeMatch = (comparator) => {
1777
- if (typeof sourceSchema === "object") {
1778
- if (targetSchema === true || typeof targetSchema === "object" && targetSchema.additionalProperties === true) {
1779
- for (const fromOutputPortId of Object.keys(sourceSchema.properties || {})) {
1780
- matches.set(fromOutputPortId, fromOutputPortId);
1781
- this.connect(parent.config.id, fromOutputPortId, task.config.id, fromOutputPortId);
1782
- }
1783
- return matches;
1784
- }
1785
- }
1786
- if (typeof sourceSchema === "boolean" || typeof targetSchema === "boolean") {
1787
- return matches;
1788
- }
1789
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(sourceSchema.properties || {})) {
1790
- for (const [toInputPortId, toPortInputSchema] of Object.entries(targetSchema.properties || {})) {
1791
- if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
1792
- matches.set(toInputPortId, fromOutputPortId);
1793
- this.connect(parent.config.id, fromOutputPortId, task.config.id, toInputPortId);
1794
- }
1795
- }
1796
- }
1797
- return matches;
1798
- };
1799
- const getSpecificTypeIdentifiers = (schema) => {
1800
- const formats = new Set;
1801
- const ids = new Set;
1802
- if (typeof schema === "boolean") {
1803
- return { formats, ids };
1804
- }
1805
- const extractFromSchema = (s) => {
1806
- if (!s || typeof s !== "object" || Array.isArray(s))
1807
- return;
1808
- if (s.format)
1809
- formats.add(s.format);
1810
- if (s.$id)
1811
- ids.add(s.$id);
1812
- };
1813
- extractFromSchema(schema);
1814
- const checkUnion = (schemas) => {
1815
- if (!schemas)
1816
- return;
1817
- for (const s of schemas) {
1818
- if (typeof s === "boolean")
1819
- continue;
1820
- extractFromSchema(s);
1821
- if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
1822
- extractFromSchema(s.items);
1823
- }
1824
- }
1825
- };
1826
- checkUnion(schema.oneOf);
1827
- checkUnion(schema.anyOf);
1828
- if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
1829
- extractFromSchema(schema.items);
1830
- }
1831
- return { formats, ids };
1832
- };
1833
- const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
1834
- if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
1835
- return fromPortOutputSchema === true && toPortInputSchema === true;
1836
- }
1837
- const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
1838
- const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
1839
- for (const format of outputIds.formats) {
1840
- if (inputIds.formats.has(format)) {
1841
- return true;
1842
- }
1843
- }
1844
- for (const id of outputIds.ids) {
1845
- if (inputIds.ids.has(id)) {
1846
- return true;
1847
- }
1848
- }
1849
- if (requireSpecificType) {
1850
- return false;
1851
- }
1852
- const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
1853
- if (!idTypeBlank)
1854
- return false;
1855
- if (fromPortOutputSchema.type === toPortInputSchema.type)
1856
- return true;
1857
- const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
1858
- if (typeof schema === "boolean")
1859
- return schema;
1860
- return schema.type === fromPortOutputSchema.type;
1861
- }) ?? false;
1862
- const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
1863
- if (typeof schema === "boolean")
1864
- return schema;
1865
- return schema.type === fromPortOutputSchema.type;
1866
- }) ?? false;
1867
- return matchesOneOf || matchesAnyOf;
1868
- };
1869
- makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1870
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
1871
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1872
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1873
- return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
1874
- });
1875
- makeMatch(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
1876
- return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
1877
- });
1878
- const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
1879
- const providedInputKeys = new Set(Object.keys(input || {}));
1880
- const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
1881
- let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
1882
- if (unmatchedRequired.length > 0) {
1883
- const nodes = this._graph.getTasks();
1884
- const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
1885
- for (let i = parentIndex - 1;i >= 0 && unmatchedRequired.length > 0; i--) {
1886
- const earlierTask = nodes[i];
1887
- const earlierOutputSchema = earlierTask.outputSchema();
1888
- const makeMatchFromEarlier = (comparator) => {
1889
- if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
1890
- return;
1891
- }
1892
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
1893
- for (const requiredInputId of unmatchedRequired) {
1894
- const toPortInputSchema = targetSchema.properties?.[requiredInputId];
1895
- if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
1896
- matches.set(requiredInputId, fromOutputPortId);
1897
- this.connect(earlierTask.config.id, fromOutputPortId, task.config.id, requiredInputId);
1898
- }
1899
- }
1900
- }
1901
- };
1902
- makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1903
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
1904
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1905
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1906
- return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
1907
- });
1908
- makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
1909
- return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
1910
- });
1911
- unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
1912
- }
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]);
1913
1951
  }
1914
- const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
1915
- if (stillUnmatchedRequired.length > 0) {
1916
- 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.`;
1917
- console.error(this._error);
1918
- this.graph.removeTask(task.config.id);
1919
- } else if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
1920
- const hasRequiredInputs = requiredInputs.size > 0;
1921
- const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
1922
- const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
1923
- if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
1924
- 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.`;
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.";
1925
1963
  console.error(this._error);
1926
1964
  this.graph.removeTask(task.config.id);
1927
1965
  }
@@ -1964,12 +2002,20 @@ class Workflow {
1964
2002
  return this.events.waitOn(name);
1965
2003
  }
1966
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
+ }
1967
2013
  this.events.emit("start");
1968
2014
  this._abortController = new AbortController;
1969
2015
  try {
1970
2016
  const output = await this.graph.run(input, {
1971
2017
  parentSignal: this._abortController.signal,
1972
- outputCache: this._repository
2018
+ outputCache: this._outputCache
1973
2019
  });
1974
2020
  const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
1975
2021
  this.events.emit("complete");
@@ -1982,6 +2028,9 @@ class Workflow {
1982
2028
  }
1983
2029
  }
1984
2030
  async abort() {
2031
+ if (this._parentWorkflow) {
2032
+ return this._parentWorkflow.abort();
2033
+ }
1985
2034
  this._abortController?.abort();
1986
2035
  }
1987
2036
  pop() {
@@ -2050,10 +2099,12 @@ class Workflow {
2050
2099
  return task;
2051
2100
  }
2052
2101
  reset() {
2053
- taskIdCounter = 0;
2102
+ if (this._parentWorkflow) {
2103
+ throw new WorkflowError("Cannot reset a loop workflow. Call reset() on the parent workflow.");
2104
+ }
2054
2105
  this.clearEvents();
2055
2106
  this._graph = new TaskGraph({
2056
- outputCache: this._repository
2107
+ outputCache: this._outputCache
2057
2108
  });
2058
2109
  this._dataFlows = [];
2059
2110
  this._error = "";
@@ -2108,15 +2159,243 @@ class Workflow {
2108
2159
  this.graph.addDataflow(dataflow);
2109
2160
  return this;
2110
2161
  }
2111
- addTask(taskClass, input, config) {
2162
+ addTaskToGraph(taskClass, input, config) {
2112
2163
  const task = new taskClass(input, config);
2113
2164
  const id = this.graph.addTask(task);
2114
2165
  this.events.emit("changed", id);
2115
2166
  return task;
2116
2167
  }
2117
- }
2118
- function CreateWorkflow(taskClass) {
2119
- return Workflow.createWorkflow(taskClass);
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
+ }
2120
2399
  }
2121
2400
 
2122
2401
  // src/task-graph/Conversions.ts
@@ -2356,7 +2635,7 @@ class TaskGraph {
2356
2635
  return this._dag.removeNode(taskId);
2357
2636
  }
2358
2637
  resetGraph() {
2359
- this.runner.resetGraph(this, uuid43());
2638
+ this.runner.resetGraph(this, uuid44());
2360
2639
  }
2361
2640
  toJSON() {
2362
2641
  const tasks = this.getTasks().map((node) => node.toJSON());
@@ -2521,81 +2800,1145 @@ function serialGraph(tasks, inputHandle, outputHandle) {
2521
2800
  graph.addDataflows(serialGraphEdges(tasks, inputHandle, outputHandle));
2522
2801
  return graph;
2523
2802
  }
2524
- // src/task/JobQueueFactory.ts
2525
- import {
2526
- JobQueueClient,
2527
- JobQueueServer
2528
- } from "@workglow/job-queue";
2529
- import { InMemoryQueueStorage } from "@workglow/storage";
2530
- import { createServiceToken as createServiceToken2, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
2531
- var JOB_QUEUE_FACTORY = createServiceToken2("taskgraph.jobQueueFactory");
2532
- var defaultJobQueueFactory = async ({
2533
- queueName,
2534
- jobClass,
2535
- options
2536
- }) => {
2537
- const storage = options?.storage ?? new InMemoryQueueStorage(queueName);
2538
- await storage.setupDatabase();
2539
- const server = new JobQueueServer(jobClass, {
2540
- storage,
2541
- queueName,
2542
- limiter: options?.limiter,
2543
- workerCount: options?.workerCount,
2544
- pollIntervalMs: options?.pollIntervalMs,
2545
- deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
2546
- deleteAfterFailureMs: options?.deleteAfterFailureMs,
2547
- deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
2548
- cleanupIntervalMs: options?.cleanupIntervalMs
2549
- });
2550
- const client = new JobQueueClient({
2551
- storage,
2552
- queueName
2553
- });
2554
- client.attach(server);
2555
- return { server, client, storage };
2556
- };
2557
- function registerJobQueueFactory(factory) {
2558
- globalServiceRegistry3.registerInstance(JOB_QUEUE_FACTORY, factory);
2559
- }
2560
- function createJobQueueFactoryWithOptions(defaultOptions = {}) {
2561
- return async ({
2562
- queueName,
2563
- jobClass,
2564
- options
2565
- }) => {
2566
- const mergedOptions = {
2567
- ...defaultOptions,
2568
- ...options ?? {}
2569
- };
2570
- const storage = mergedOptions.storage ?? new InMemoryQueueStorage(queueName);
2571
- await storage.setupDatabase();
2572
- const server = new JobQueueServer(jobClass, {
2573
- storage,
2574
- queueName,
2575
- limiter: mergedOptions.limiter,
2576
- workerCount: mergedOptions.workerCount,
2577
- pollIntervalMs: mergedOptions.pollIntervalMs,
2578
- deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
2579
- deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
2580
- deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
2581
- cleanupIntervalMs: mergedOptions.cleanupIntervalMs
2582
- });
2583
- const client = new JobQueueClient({
2584
- storage,
2585
- queueName
2586
- });
2587
- client.attach(server);
2588
- return { server, client, storage };
2589
- };
2590
- }
2591
- function getJobQueueFactory() {
2592
- if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
2593
- 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);
2594
2814
  }
2595
- return globalServiceRegistry3.get(JOB_QUEUE_FACTORY);
2596
- }
2597
- if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
2598
- registerJobQueueFactory(defaultJobQueueFactory);
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)) {
3941
+ registerJobQueueFactory(defaultJobQueueFactory);
2599
3942
  }
2600
3943
  // src/task/JobQueueTask.ts
2601
3944
  import { Job as Job2 } from "@workglow/job-queue";
@@ -2781,6 +4124,155 @@ class JobQueueTask extends GraphAsTask {
2781
4124
  super.abort();
2782
4125
  }
2783
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
+ });
2784
4276
  // src/task/TaskRegistry.ts
2785
4277
  var taskConstructors = new Map;
2786
4278
  function registerTask(baseClass) {
@@ -2850,7 +4342,7 @@ var createGraphFromGraphJSON = (graphJsonObj) => {
2850
4342
  };
2851
4343
  // src/task/index.ts
2852
4344
  var registerBaseTasks = () => {
2853
- const tasks = [ConditionalTask, GraphAsTask];
4345
+ const tasks = [GraphAsTask, ConditionalTask, MapTask, WhileTask, ReduceTask];
2854
4346
  tasks.map(TaskRegistry.registerTask);
2855
4347
  return tasks;
2856
4348
  };
@@ -3011,25 +4503,47 @@ class TaskOutputTabularRepository extends TaskOutputRepository {
3011
4503
  }
3012
4504
  }
3013
4505
  export {
4506
+ wrapSchemaInArray,
3014
4507
  setTaskQueueRegistry,
3015
4508
  serialGraph,
4509
+ schemaAcceptsArray,
3016
4510
  resolveSchemaInputs,
4511
+ removeIterationProperties,
3017
4512
  registerJobQueueFactory,
3018
4513
  registerBaseTasks,
3019
4514
  pipe,
3020
4515
  parallel,
4516
+ mergeChainedOutputToInput,
4517
+ isStrictArraySchema,
4518
+ isIterationProperty,
4519
+ isFlexibleSchema,
3021
4520
  getTaskQueueRegistry,
4521
+ getNestedValue,
3022
4522
  getLastTask,
3023
4523
  getJobQueueFactory,
4524
+ getIterationContextSchemaForType,
4525
+ getInputModeFromSchema,
4526
+ findArrayPorts,
4527
+ filterIterationProperties,
4528
+ extractIterationProperties,
4529
+ extractBaseSchema,
4530
+ evaluateCondition,
3024
4531
  ensureTask,
3025
4532
  createTaskFromGraphJSON,
3026
4533
  createTaskFromDependencyJSON,
3027
4534
  createJobQueueFactoryWithOptions,
3028
4535
  createGraphFromGraphJSON,
3029
4536
  createGraphFromDependencyJSON,
4537
+ createFlexibleSchema,
4538
+ createArraySchema,
3030
4539
  connect,
4540
+ buildIterationInputSchema,
4541
+ addIterationContextToSchema,
3031
4542
  WorkflowError,
3032
4543
  Workflow,
4544
+ WhileTaskRunner,
4545
+ WhileTask,
4546
+ WHILE_CONTEXT_SCHEMA,
3033
4547
  TaskStatus,
3034
4548
  TaskRegistry,
3035
4549
  TaskQueueRegistry,
@@ -3052,10 +4566,15 @@ export {
3052
4566
  Task,
3053
4567
  TASK_OUTPUT_REPOSITORY,
3054
4568
  TASK_GRAPH_REPOSITORY,
4569
+ ReduceTask,
3055
4570
  PROPERTY_ARRAY,
4571
+ MapTask,
3056
4572
  JobTaskFailedError,
3057
4573
  JobQueueTask,
3058
4574
  JOB_QUEUE_FACTORY,
4575
+ IteratorTaskRunner,
4576
+ IteratorTask,
4577
+ ITERATOR_CONTEXT_SCHEMA,
3059
4578
  GraphAsTaskRunner,
3060
4579
  GraphAsTask,
3061
4580
  GRAPH_RESULT_ARRAY,
@@ -3066,7 +4585,9 @@ export {
3066
4585
  DATAFLOW_ERROR_PORT,
3067
4586
  DATAFLOW_ALL_PORTS,
3068
4587
  CreateWorkflow,
4588
+ CreateLoopWorkflow,
4589
+ CreateEndLoopWorkflow,
3069
4590
  ConditionalTask
3070
4591
  };
3071
4592
 
3072
- //# debugId=262FD218D0D53F5964756E2164756E21
4593
+ //# debugId=8CD145E1258087DF64756E2164756E21