@workglow/task-graph 0.0.86 → 0.0.87

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/node.js CHANGED
@@ -1,19 +1,54 @@
1
- // src/task-graph/Dataflow.ts
2
- import { areSemanticallyCompatible, EventEmitter } from "@workglow/util";
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
6
+ var __toCommonJS = (from) => {
7
+ var entry = __moduleCache.get(from), desc;
8
+ if (entry)
9
+ return entry;
10
+ entry = __defProp({}, "__esModule", { value: true });
11
+ if (from && typeof from === "object" || typeof from === "function")
12
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
13
+ get: () => from[key],
14
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
15
+ }));
16
+ __moduleCache.set(from, entry);
17
+ return entry;
18
+ };
19
+ var __export = (target, all) => {
20
+ for (var name in all)
21
+ __defProp(target, name, {
22
+ get: all[name],
23
+ enumerable: true,
24
+ configurable: true,
25
+ set: (newValue) => all[name] = () => newValue
26
+ });
27
+ };
28
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
3
29
 
4
30
  // src/task/TaskTypes.ts
5
- var TaskStatus = {
6
- PENDING: "PENDING",
7
- DISABLED: "DISABLED",
8
- PROCESSING: "PROCESSING",
9
- COMPLETED: "COMPLETED",
10
- ABORTING: "ABORTING",
11
- FAILED: "FAILED"
12
- };
31
+ var TaskStatus;
32
+ var init_TaskTypes = __esm(() => {
33
+ TaskStatus = {
34
+ PENDING: "PENDING",
35
+ DISABLED: "DISABLED",
36
+ PROCESSING: "PROCESSING",
37
+ COMPLETED: "COMPLETED",
38
+ ABORTING: "ABORTING",
39
+ FAILED: "FAILED"
40
+ };
41
+ });
13
42
 
14
43
  // src/task-graph/Dataflow.ts
15
- var DATAFLOW_ALL_PORTS = "*";
16
- var DATAFLOW_ERROR_PORT = "[error]";
44
+ var exports_Dataflow = {};
45
+ __export(exports_Dataflow, {
46
+ DataflowArrow: () => DataflowArrow,
47
+ Dataflow: () => Dataflow,
48
+ DATAFLOW_ERROR_PORT: () => DATAFLOW_ERROR_PORT,
49
+ DATAFLOW_ALL_PORTS: () => DATAFLOW_ALL_PORTS
50
+ });
51
+ import { areSemanticallyCompatible, EventEmitter } from "@workglow/util";
17
52
 
18
53
  class Dataflow {
19
54
  sourceTaskId;
@@ -78,13 +113,15 @@ class Dataflow {
78
113
  }
79
114
  }
80
115
  getPortData() {
116
+ let result;
81
117
  if (this.targetTaskPortId === DATAFLOW_ALL_PORTS) {
82
- return this.value;
118
+ result = this.value;
83
119
  } else if (this.targetTaskPortId === DATAFLOW_ERROR_PORT) {
84
- return { [DATAFLOW_ERROR_PORT]: this.error };
120
+ result = { [DATAFLOW_ERROR_PORT]: this.error };
85
121
  } else {
86
- return { [this.targetTaskPortId]: this.value };
122
+ result = { [this.targetTaskPortId]: this.value };
87
123
  }
124
+ return result;
88
125
  }
89
126
  toJSON() {
90
127
  return {
@@ -146,22 +183,30 @@ class Dataflow {
146
183
  this._events?.emit(name, ...args);
147
184
  }
148
185
  }
149
-
150
- class DataflowArrow extends Dataflow {
151
- constructor(dataflow) {
152
- const pattern = /^([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\] ==> ([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\]$/;
153
- const match = dataflow.match(pattern);
154
- if (!match) {
155
- throw new Error(`Invalid dataflow format: ${dataflow}`);
186
+ var DATAFLOW_ALL_PORTS = "*", DATAFLOW_ERROR_PORT = "[error]", DataflowArrow;
187
+ var init_Dataflow = __esm(() => {
188
+ init_TaskTypes();
189
+ DataflowArrow = class DataflowArrow extends Dataflow {
190
+ constructor(dataflow) {
191
+ const pattern = /^([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\] ==> ([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\]$/;
192
+ const match = dataflow.match(pattern);
193
+ if (!match) {
194
+ throw new Error(`Invalid dataflow format: ${dataflow}`);
195
+ }
196
+ const [, sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId] = match;
197
+ super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
156
198
  }
157
- const [, sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId] = match;
158
- super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
159
- }
160
- }
199
+ };
200
+ });
201
+
202
+ // src/common.ts
203
+ init_Dataflow();
204
+
161
205
  // src/task-graph/TaskGraph.ts
162
- import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid43 } from "@workglow/util";
206
+ import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid44 } from "@workglow/util";
163
207
 
164
208
  // src/task/GraphAsTask.ts
209
+ init_Dataflow();
165
210
  import { compileSchema as compileSchema2 } from "@workglow/util";
166
211
 
167
212
  // src/task-graph/TaskGraphRunner.ts
@@ -203,6 +248,7 @@ class TaskOutputRepository {
203
248
  }
204
249
 
205
250
  // src/task/Task.ts
251
+ init_Dataflow();
206
252
  import {
207
253
  compileSchema,
208
254
  deepEqual,
@@ -329,6 +375,8 @@ async function resolveSchemaInputs(input, schema, config) {
329
375
  }
330
376
 
331
377
  // src/task/TaskRunner.ts
378
+ init_TaskTypes();
379
+
332
380
  class TaskRunner {
333
381
  running = false;
334
382
  reactiveRunning = false;
@@ -521,6 +569,8 @@ class TaskRunner {
521
569
  }
522
570
 
523
571
  // src/task/Task.ts
572
+ init_TaskTypes();
573
+
524
574
  class Task {
525
575
  static type = "Task";
526
576
  static category = "Hidden";
@@ -1019,7 +1069,13 @@ class ConditionalTask extends Task {
1019
1069
  }
1020
1070
  }
1021
1071
 
1072
+ // src/task-graph/TaskGraphRunner.ts
1073
+ init_TaskTypes();
1074
+ init_Dataflow();
1075
+
1022
1076
  // src/task-graph/TaskGraphScheduler.ts
1077
+ init_TaskTypes();
1078
+
1023
1079
  class TopologicalScheduler {
1024
1080
  dag;
1025
1081
  sortedNodes;
@@ -1723,35 +1779,70 @@ class GraphAsTask extends Task {
1723
1779
  }
1724
1780
  }
1725
1781
 
1782
+ // src/task-graph/Conversions.ts
1783
+ init_Dataflow();
1784
+
1726
1785
  // src/task-graph/Workflow.ts
1727
- import { EventEmitter as EventEmitter4 } from "@workglow/util";
1786
+ import { EventEmitter as EventEmitter4, uuid4 as uuid43 } from "@workglow/util";
1787
+ init_Dataflow();
1788
+ function CreateWorkflow(taskClass) {
1789
+ return Workflow.createWorkflow(taskClass);
1790
+ }
1791
+ function CreateLoopWorkflow(taskClass) {
1792
+ return function(config = {}) {
1793
+ const task = new taskClass({}, config);
1794
+ this.graph.addTask(task);
1795
+ const previousTask = getLastTask(this);
1796
+ if (previousTask && previousTask !== task) {
1797
+ this.graph.addDataflow(new Dataflow(previousTask.config.id, "*", task.config.id, "*"));
1798
+ }
1799
+ return new Workflow(this.outputCache(), this, task);
1800
+ };
1801
+ }
1802
+ function CreateEndLoopWorkflow(methodName) {
1803
+ return function() {
1804
+ if (!this.isLoopBuilder) {
1805
+ throw new Error(`${methodName}() can only be called on loop workflows`);
1806
+ }
1807
+ return this.finalizeAndReturn();
1808
+ };
1809
+ }
1810
+
1728
1811
  class WorkflowTask extends GraphAsTask {
1729
1812
  static type = "Workflow";
1730
1813
  static compoundMerge = PROPERTY_ARRAY;
1731
1814
  }
1732
- var taskIdCounter = 0;
1733
1815
 
1734
1816
  class Workflow {
1735
- constructor(repository) {
1736
- this._repository = repository;
1737
- this._graph = new TaskGraph({
1738
- outputCache: this._repository
1739
- });
1740
- this._onChanged = this._onChanged.bind(this);
1741
- this.setupEvents();
1817
+ constructor(cache, parent, iteratorTask) {
1818
+ this._outputCache = cache;
1819
+ this._parentWorkflow = parent;
1820
+ this._iteratorTask = iteratorTask;
1821
+ this._graph = new TaskGraph({ outputCache: this._outputCache });
1822
+ if (!parent) {
1823
+ this._onChanged = this._onChanged.bind(this);
1824
+ this.setupEvents();
1825
+ }
1742
1826
  }
1743
1827
  _graph;
1744
1828
  _dataFlows = [];
1745
1829
  _error = "";
1746
- _repository;
1830
+ _outputCache;
1747
1831
  _abortController;
1832
+ _parentWorkflow;
1833
+ _iteratorTask;
1834
+ outputCache() {
1835
+ return this._outputCache;
1836
+ }
1837
+ get isLoopBuilder() {
1838
+ return this._parentWorkflow !== undefined;
1839
+ }
1748
1840
  events = new EventEmitter4;
1749
1841
  static createWorkflow(taskClass) {
1750
1842
  const helper = function(input = {}, config = {}) {
1751
1843
  this._error = "";
1752
1844
  const parent = getLastTask(this);
1753
- taskIdCounter++;
1754
- const task = this.addTask(taskClass, input, { id: String(taskIdCounter), ...config });
1845
+ const task = this.addTaskToGraph(taskClass, input, { id: uuid43(), ...config });
1755
1846
  if (this._dataFlows.length > 0) {
1756
1847
  this._dataFlows.forEach((dataflow) => {
1757
1848
  const taskSchema = task.inputSchema();
@@ -1766,158 +1857,23 @@ class Workflow {
1766
1857
  this._dataFlows = [];
1767
1858
  }
1768
1859
  if (parent && this.graph.getTargetDataflows(parent.config.id).length === 0) {
1769
- const matches = new Map;
1770
- const sourceSchema = parent.outputSchema();
1771
- const targetSchema = task.inputSchema();
1772
- const makeMatch = (comparator) => {
1773
- if (typeof sourceSchema === "object") {
1774
- if (targetSchema === true || typeof targetSchema === "object" && targetSchema.additionalProperties === true) {
1775
- for (const fromOutputPortId of Object.keys(sourceSchema.properties || {})) {
1776
- matches.set(fromOutputPortId, fromOutputPortId);
1777
- this.connect(parent.config.id, fromOutputPortId, task.config.id, fromOutputPortId);
1778
- }
1779
- return matches;
1780
- }
1781
- }
1782
- if (typeof sourceSchema === "boolean" || typeof targetSchema === "boolean") {
1783
- return matches;
1784
- }
1785
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(sourceSchema.properties || {})) {
1786
- for (const [toInputPortId, toPortInputSchema] of Object.entries(targetSchema.properties || {})) {
1787
- if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
1788
- matches.set(toInputPortId, fromOutputPortId);
1789
- this.connect(parent.config.id, fromOutputPortId, task.config.id, toInputPortId);
1790
- }
1791
- }
1792
- }
1793
- return matches;
1794
- };
1795
- const getSpecificTypeIdentifiers = (schema) => {
1796
- const formats = new Set;
1797
- const ids = new Set;
1798
- if (typeof schema === "boolean") {
1799
- return { formats, ids };
1800
- }
1801
- const extractFromSchema = (s) => {
1802
- if (!s || typeof s !== "object" || Array.isArray(s))
1803
- return;
1804
- if (s.format)
1805
- formats.add(s.format);
1806
- if (s.$id)
1807
- ids.add(s.$id);
1808
- };
1809
- extractFromSchema(schema);
1810
- const checkUnion = (schemas) => {
1811
- if (!schemas)
1812
- return;
1813
- for (const s of schemas) {
1814
- if (typeof s === "boolean")
1815
- continue;
1816
- extractFromSchema(s);
1817
- if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
1818
- extractFromSchema(s.items);
1819
- }
1820
- }
1821
- };
1822
- checkUnion(schema.oneOf);
1823
- checkUnion(schema.anyOf);
1824
- if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
1825
- extractFromSchema(schema.items);
1826
- }
1827
- return { formats, ids };
1828
- };
1829
- const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
1830
- if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
1831
- return fromPortOutputSchema === true && toPortInputSchema === true;
1832
- }
1833
- const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
1834
- const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
1835
- for (const format of outputIds.formats) {
1836
- if (inputIds.formats.has(format)) {
1837
- return true;
1838
- }
1839
- }
1840
- for (const id of outputIds.ids) {
1841
- if (inputIds.ids.has(id)) {
1842
- return true;
1843
- }
1844
- }
1845
- if (requireSpecificType) {
1846
- return false;
1847
- }
1848
- const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
1849
- if (!idTypeBlank)
1850
- return false;
1851
- if (fromPortOutputSchema.type === toPortInputSchema.type)
1852
- return true;
1853
- const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
1854
- if (typeof schema === "boolean")
1855
- return schema;
1856
- return schema.type === fromPortOutputSchema.type;
1857
- }) ?? false;
1858
- const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
1859
- if (typeof schema === "boolean")
1860
- return schema;
1861
- return schema.type === fromPortOutputSchema.type;
1862
- }) ?? false;
1863
- return matchesOneOf || matchesAnyOf;
1864
- };
1865
- makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1866
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
1867
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1868
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1869
- return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
1870
- });
1871
- makeMatch(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
1872
- return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
1873
- });
1874
- const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
1875
- const providedInputKeys = new Set(Object.keys(input || {}));
1876
- const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
1877
- let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
1878
- if (unmatchedRequired.length > 0) {
1879
- const nodes = this._graph.getTasks();
1880
- const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
1881
- for (let i = parentIndex - 1;i >= 0 && unmatchedRequired.length > 0; i--) {
1882
- const earlierTask = nodes[i];
1883
- const earlierOutputSchema = earlierTask.outputSchema();
1884
- const makeMatchFromEarlier = (comparator) => {
1885
- if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
1886
- return;
1887
- }
1888
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
1889
- for (const requiredInputId of unmatchedRequired) {
1890
- const toPortInputSchema = targetSchema.properties?.[requiredInputId];
1891
- if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
1892
- matches.set(requiredInputId, fromOutputPortId);
1893
- this.connect(earlierTask.config.id, fromOutputPortId, task.config.id, requiredInputId);
1894
- }
1895
- }
1896
- }
1897
- };
1898
- makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1899
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
1900
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1901
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1902
- return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
1903
- });
1904
- makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
1905
- return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
1906
- });
1907
- unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
1908
- }
1860
+ const nodes = this._graph.getTasks();
1861
+ const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
1862
+ const earlierTasks = [];
1863
+ for (let i = parentIndex - 1;i >= 0; i--) {
1864
+ earlierTasks.push(nodes[i]);
1909
1865
  }
1910
- const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
1911
- if (stillUnmatchedRequired.length > 0) {
1912
- 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.`;
1913
- console.error(this._error);
1914
- this.graph.removeTask(task.config.id);
1915
- } else if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
1916
- const hasRequiredInputs = requiredInputs.size > 0;
1917
- const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
1918
- const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
1919
- if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
1920
- 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.`;
1866
+ const providedInputKeys = new Set(Object.keys(input || {}));
1867
+ const result = Workflow.autoConnect(this.graph, parent, task, {
1868
+ providedInputKeys,
1869
+ earlierTasks
1870
+ });
1871
+ if (result.error) {
1872
+ if (this.isLoopBuilder) {
1873
+ this._error = result.error;
1874
+ console.warn(this._error);
1875
+ } else {
1876
+ this._error = result.error + " Task not added.";
1921
1877
  console.error(this._error);
1922
1878
  this.graph.removeTask(task.config.id);
1923
1879
  }
@@ -1960,12 +1916,16 @@ class Workflow {
1960
1916
  return this.events.waitOn(name);
1961
1917
  }
1962
1918
  async run(input = {}) {
1919
+ if (this.isLoopBuilder) {
1920
+ this.finalizeTemplate();
1921
+ return this._parentWorkflow.run(input);
1922
+ }
1963
1923
  this.events.emit("start");
1964
1924
  this._abortController = new AbortController;
1965
1925
  try {
1966
1926
  const output = await this.graph.run(input, {
1967
1927
  parentSignal: this._abortController.signal,
1968
- outputCache: this._repository
1928
+ outputCache: this._outputCache
1969
1929
  });
1970
1930
  const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
1971
1931
  this.events.emit("complete");
@@ -1978,6 +1938,9 @@ class Workflow {
1978
1938
  }
1979
1939
  }
1980
1940
  async abort() {
1941
+ if (this._parentWorkflow) {
1942
+ return this._parentWorkflow.abort();
1943
+ }
1981
1944
  this._abortController?.abort();
1982
1945
  }
1983
1946
  pop() {
@@ -2046,10 +2009,12 @@ class Workflow {
2046
2009
  return task;
2047
2010
  }
2048
2011
  reset() {
2049
- taskIdCounter = 0;
2012
+ if (this._parentWorkflow) {
2013
+ throw new WorkflowError("Cannot reset a loop workflow. Call reset() on the parent workflow.");
2014
+ }
2050
2015
  this.clearEvents();
2051
2016
  this._graph = new TaskGraph({
2052
- outputCache: this._repository
2017
+ outputCache: this._outputCache
2053
2018
  });
2054
2019
  this._dataFlows = [];
2055
2020
  this._error = "";
@@ -2104,15 +2069,194 @@ class Workflow {
2104
2069
  this.graph.addDataflow(dataflow);
2105
2070
  return this;
2106
2071
  }
2107
- addTask(taskClass, input, config) {
2072
+ addTaskToGraph(taskClass, input, config) {
2108
2073
  const task = new taskClass(input, config);
2109
2074
  const id = this.graph.addTask(task);
2110
2075
  this.events.emit("changed", id);
2111
2076
  return task;
2112
2077
  }
2113
- }
2114
- function CreateWorkflow(taskClass) {
2115
- return Workflow.createWorkflow(taskClass);
2078
+ addTask(taskClass, input, config) {
2079
+ const helper = Workflow.createWorkflow(taskClass);
2080
+ return helper.call(this, input, config);
2081
+ }
2082
+ static AutoConnectOptions = Symbol("AutoConnectOptions");
2083
+ static autoConnect(graph, sourceTask, targetTask, options) {
2084
+ const matches = new Map;
2085
+ const sourceSchema = sourceTask.outputSchema();
2086
+ const targetSchema = targetTask.inputSchema();
2087
+ const providedInputKeys = options?.providedInputKeys ?? new Set;
2088
+ const earlierTasks = options?.earlierTasks ?? [];
2089
+ const getSpecificTypeIdentifiers = (schema) => {
2090
+ const formats = new Set;
2091
+ const ids = new Set;
2092
+ if (typeof schema === "boolean") {
2093
+ return { formats, ids };
2094
+ }
2095
+ const extractFromSchema = (s) => {
2096
+ if (!s || typeof s !== "object" || Array.isArray(s))
2097
+ return;
2098
+ if (s.format)
2099
+ formats.add(s.format);
2100
+ if (s.$id)
2101
+ ids.add(s.$id);
2102
+ };
2103
+ extractFromSchema(schema);
2104
+ const checkUnion = (schemas) => {
2105
+ if (!schemas)
2106
+ return;
2107
+ for (const s of schemas) {
2108
+ if (typeof s === "boolean")
2109
+ continue;
2110
+ extractFromSchema(s);
2111
+ if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
2112
+ extractFromSchema(s.items);
2113
+ }
2114
+ }
2115
+ };
2116
+ checkUnion(schema.oneOf);
2117
+ checkUnion(schema.anyOf);
2118
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
2119
+ extractFromSchema(schema.items);
2120
+ }
2121
+ return { formats, ids };
2122
+ };
2123
+ const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
2124
+ if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
2125
+ return fromPortOutputSchema === true && toPortInputSchema === true;
2126
+ }
2127
+ const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
2128
+ const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
2129
+ for (const format of outputIds.formats) {
2130
+ if (inputIds.formats.has(format)) {
2131
+ return true;
2132
+ }
2133
+ }
2134
+ for (const id of outputIds.ids) {
2135
+ if (inputIds.ids.has(id)) {
2136
+ return true;
2137
+ }
2138
+ }
2139
+ if (requireSpecificType) {
2140
+ return false;
2141
+ }
2142
+ const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
2143
+ if (!idTypeBlank)
2144
+ return false;
2145
+ if (fromPortOutputSchema.type === toPortInputSchema.type)
2146
+ return true;
2147
+ const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
2148
+ if (typeof schema === "boolean")
2149
+ return schema;
2150
+ return schema.type === fromPortOutputSchema.type;
2151
+ }) ?? false;
2152
+ const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
2153
+ if (typeof schema === "boolean")
2154
+ return schema;
2155
+ return schema.type === fromPortOutputSchema.type;
2156
+ }) ?? false;
2157
+ return matchesOneOf || matchesAnyOf;
2158
+ };
2159
+ const makeMatch = (fromSchema, toSchema, fromTaskId, toTaskId, comparator) => {
2160
+ if (typeof fromSchema === "object") {
2161
+ if (toSchema === true || typeof toSchema === "object" && toSchema.additionalProperties === true) {
2162
+ for (const fromOutputPortId of Object.keys(fromSchema.properties || {})) {
2163
+ matches.set(fromOutputPortId, fromOutputPortId);
2164
+ graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, fromOutputPortId));
2165
+ }
2166
+ return;
2167
+ }
2168
+ }
2169
+ if (typeof fromSchema === "boolean" || typeof toSchema === "boolean") {
2170
+ return;
2171
+ }
2172
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(fromSchema.properties || {})) {
2173
+ for (const [toInputPortId, toPortInputSchema] of Object.entries(toSchema.properties || {})) {
2174
+ if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
2175
+ matches.set(toInputPortId, fromOutputPortId);
2176
+ graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, toInputPortId));
2177
+ }
2178
+ }
2179
+ }
2180
+ };
2181
+ makeMatch(sourceSchema, targetSchema, sourceTask.config.id, targetTask.config.id, ([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
2182
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
2183
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
2184
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
2185
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
2186
+ });
2187
+ makeMatch(sourceSchema, targetSchema, sourceTask.config.id, targetTask.config.id, ([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
2188
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
2189
+ });
2190
+ const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
2191
+ const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
2192
+ let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
2193
+ if (unmatchedRequired.length > 0 && earlierTasks.length > 0) {
2194
+ for (let i = 0;i < earlierTasks.length && unmatchedRequired.length > 0; i++) {
2195
+ const earlierTask = earlierTasks[i];
2196
+ const earlierOutputSchema = earlierTask.outputSchema();
2197
+ const makeMatchFromEarlier = (comparator) => {
2198
+ if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
2199
+ return;
2200
+ }
2201
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
2202
+ for (const requiredInputId of unmatchedRequired) {
2203
+ const toPortInputSchema = targetSchema.properties?.[requiredInputId];
2204
+ if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
2205
+ matches.set(requiredInputId, fromOutputPortId);
2206
+ graph.addDataflow(new Dataflow(earlierTask.config.id, fromOutputPortId, targetTask.config.id, requiredInputId));
2207
+ }
2208
+ }
2209
+ }
2210
+ };
2211
+ makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
2212
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
2213
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
2214
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
2215
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
2216
+ });
2217
+ makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
2218
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
2219
+ });
2220
+ unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
2221
+ }
2222
+ }
2223
+ const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
2224
+ if (stillUnmatchedRequired.length > 0) {
2225
+ return {
2226
+ matches,
2227
+ error: `Could not find matches for required inputs [${stillUnmatchedRequired.join(", ")}] of ${targetTask.type}. ` + `Attempted to match from ${sourceTask.type} and earlier tasks.`,
2228
+ unmatchedRequired: stillUnmatchedRequired
2229
+ };
2230
+ }
2231
+ if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
2232
+ const hasRequiredInputs = requiredInputs.size > 0;
2233
+ const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
2234
+ const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
2235
+ if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
2236
+ return {
2237
+ matches,
2238
+ 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.`,
2239
+ unmatchedRequired: []
2240
+ };
2241
+ }
2242
+ }
2243
+ return {
2244
+ matches,
2245
+ unmatchedRequired: []
2246
+ };
2247
+ }
2248
+ finalizeTemplate() {
2249
+ if (this._iteratorTask && this.graph.getTasks().length > 0) {
2250
+ this._iteratorTask.setTemplateGraph(this.graph);
2251
+ }
2252
+ }
2253
+ finalizeAndReturn() {
2254
+ if (!this._parentWorkflow) {
2255
+ throw new WorkflowError("finalizeAndReturn() can only be called on loop workflows");
2256
+ }
2257
+ this.finalizeTemplate();
2258
+ return this._parentWorkflow;
2259
+ }
2116
2260
  }
2117
2261
 
2118
2262
  // src/task-graph/Conversions.ts
@@ -2235,6 +2379,9 @@ function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
2235
2379
  return workflow;
2236
2380
  }
2237
2381
 
2382
+ // src/task-graph/TaskGraph.ts
2383
+ init_Dataflow();
2384
+
2238
2385
  // src/task-graph/TaskGraphEvents.ts
2239
2386
  var EventDagToTaskGraphMapping = {
2240
2387
  "node-added": "task_added",
@@ -2352,7 +2499,7 @@ class TaskGraph {
2352
2499
  return this._dag.removeNode(taskId);
2353
2500
  }
2354
2501
  resetGraph() {
2355
- this.runner.resetGraph(this, uuid43());
2502
+ this.runner.resetGraph(this, uuid44());
2356
2503
  }
2357
2504
  toJSON() {
2358
2505
  const tasks = this.getTasks().map((node) => node.toJSON());
@@ -2517,84 +2664,10 @@ function serialGraph(tasks, inputHandle, outputHandle) {
2517
2664
  graph.addDataflows(serialGraphEdges(tasks, inputHandle, outputHandle));
2518
2665
  return graph;
2519
2666
  }
2520
- // src/task/JobQueueFactory.ts
2521
- import {
2522
- JobQueueClient,
2523
- JobQueueServer
2524
- } from "@workglow/job-queue";
2667
+ // src/task/IteratorTaskRunner.ts
2668
+ import { Job, JobQueueClient, JobQueueServer } from "@workglow/job-queue";
2525
2669
  import { InMemoryQueueStorage } from "@workglow/storage";
2526
- import { createServiceToken as createServiceToken2, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
2527
- var JOB_QUEUE_FACTORY = createServiceToken2("taskgraph.jobQueueFactory");
2528
- var defaultJobQueueFactory = async ({
2529
- queueName,
2530
- jobClass,
2531
- options
2532
- }) => {
2533
- const storage = options?.storage ?? new InMemoryQueueStorage(queueName);
2534
- await storage.setupDatabase();
2535
- const server = new JobQueueServer(jobClass, {
2536
- storage,
2537
- queueName,
2538
- limiter: options?.limiter,
2539
- workerCount: options?.workerCount,
2540
- pollIntervalMs: options?.pollIntervalMs,
2541
- deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
2542
- deleteAfterFailureMs: options?.deleteAfterFailureMs,
2543
- deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
2544
- cleanupIntervalMs: options?.cleanupIntervalMs
2545
- });
2546
- const client = new JobQueueClient({
2547
- storage,
2548
- queueName
2549
- });
2550
- client.attach(server);
2551
- return { server, client, storage };
2552
- };
2553
- function registerJobQueueFactory(factory) {
2554
- globalServiceRegistry3.registerInstance(JOB_QUEUE_FACTORY, factory);
2555
- }
2556
- function createJobQueueFactoryWithOptions(defaultOptions = {}) {
2557
- return async ({
2558
- queueName,
2559
- jobClass,
2560
- options
2561
- }) => {
2562
- const mergedOptions = {
2563
- ...defaultOptions,
2564
- ...options ?? {}
2565
- };
2566
- const storage = mergedOptions.storage ?? new InMemoryQueueStorage(queueName);
2567
- await storage.setupDatabase();
2568
- const server = new JobQueueServer(jobClass, {
2569
- storage,
2570
- queueName,
2571
- limiter: mergedOptions.limiter,
2572
- workerCount: mergedOptions.workerCount,
2573
- pollIntervalMs: mergedOptions.pollIntervalMs,
2574
- deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
2575
- deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
2576
- deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
2577
- cleanupIntervalMs: mergedOptions.cleanupIntervalMs
2578
- });
2579
- const client = new JobQueueClient({
2580
- storage,
2581
- queueName
2582
- });
2583
- client.attach(server);
2584
- return { server, client, storage };
2585
- };
2586
- }
2587
- function getJobQueueFactory() {
2588
- if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
2589
- registerJobQueueFactory(defaultJobQueueFactory);
2590
- }
2591
- return globalServiceRegistry3.get(JOB_QUEUE_FACTORY);
2592
- }
2593
- if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
2594
- registerJobQueueFactory(defaultJobQueueFactory);
2595
- }
2596
- // src/task/JobQueueTask.ts
2597
- import { Job as Job2 } from "@workglow/job-queue";
2670
+ import { uuid4 as uuid45 } from "@workglow/util";
2598
2671
 
2599
2672
  // src/task/TaskQueueRegistry.ts
2600
2673
  var taskQueueRegistry = null;
@@ -2644,41 +2717,676 @@ function setTaskQueueRegistry(registry) {
2644
2717
  taskQueueRegistry = registry;
2645
2718
  }
2646
2719
 
2647
- // src/task/JobQueueTask.ts
2648
- class JobQueueTask extends GraphAsTask {
2649
- static type = "JobQueueTask";
2650
- static canRunDirectly = true;
2651
- currentQueueName;
2652
- currentJobId;
2653
- currentRunnerId;
2654
- jobClass;
2655
- constructor(input = {}, config = {}) {
2656
- config.queue ??= true;
2657
- super(input, config);
2658
- this.jobClass = Job2;
2659
- }
2660
- async execute(input, executeContext) {
2661
- let cleanup = () => {};
2720
+ // src/task/IteratorTaskRunner.ts
2721
+ class IteratorTaskRunner extends GraphAsTaskRunner {
2722
+ iteratorQueue;
2723
+ iteratorQueueName;
2724
+ async getOrCreateIteratorQueue() {
2725
+ const executionMode = this.task.executionMode;
2726
+ if (executionMode === "parallel") {
2727
+ return;
2728
+ }
2729
+ if (this.iteratorQueue) {
2730
+ return this.iteratorQueue;
2731
+ }
2732
+ const queueName = this.task.config.queueName ?? `iterator-${this.task.config.id}-${uuid45().slice(0, 8)}`;
2733
+ this.iteratorQueueName = queueName;
2734
+ const existingQueue = getTaskQueueRegistry().getQueue(queueName);
2735
+ if (existingQueue) {
2736
+ this.iteratorQueue = existingQueue;
2737
+ return existingQueue;
2738
+ }
2739
+ const concurrency = this.getConcurrencyForMode(executionMode);
2740
+ this.iteratorQueue = await this.createIteratorQueue(queueName, concurrency);
2741
+ return this.iteratorQueue;
2742
+ }
2743
+ getConcurrencyForMode(mode) {
2744
+ switch (mode) {
2745
+ case "sequential":
2746
+ return 1;
2747
+ case "parallel-limited":
2748
+ return this.task.concurrencyLimit;
2749
+ case "batched":
2750
+ return this.task.batchSize;
2751
+ case "parallel":
2752
+ default:
2753
+ return Infinity;
2754
+ }
2755
+ }
2756
+ async createIteratorQueue(queueName, concurrency) {
2757
+ const storage = new InMemoryQueueStorage(queueName);
2758
+ await storage.setupDatabase();
2759
+ const JobClass = class extends Job {
2760
+ async execute(input) {
2761
+ return input;
2762
+ }
2763
+ };
2764
+ const server = new JobQueueServer(JobClass, {
2765
+ storage,
2766
+ queueName,
2767
+ workerCount: Math.min(concurrency, 10)
2768
+ });
2769
+ const client = new JobQueueClient({
2770
+ storage,
2771
+ queueName
2772
+ });
2773
+ client.attach(server);
2774
+ const queue = {
2775
+ server,
2776
+ client,
2777
+ storage
2778
+ };
2662
2779
  try {
2663
- if (this.config.queue === false && !this.constructor.canRunDirectly) {
2664
- throw new TaskConfigurationError(`${this.type} cannot run directly without a queue`);
2780
+ getTaskQueueRegistry().registerQueue(queue);
2781
+ } catch (err) {
2782
+ const existing = getTaskQueueRegistry().getQueue(queueName);
2783
+ if (existing) {
2784
+ return existing;
2665
2785
  }
2666
- const registeredQueue = await this.resolveQueue(input);
2667
- if (!registeredQueue) {
2668
- if (!this.constructor.canRunDirectly) {
2669
- const queueLabel = typeof this.config.queue === "string" ? this.config.queue : this.currentQueueName ?? this.type;
2670
- throw new TaskConfigurationError(`Queue ${queueLabel} not found, and ${this.type} cannot run directly`);
2671
- }
2672
- this.currentJobId = undefined;
2673
- const job = await this.createJob(input, this.currentQueueName);
2674
- cleanup = job.onJobProgress((progress, message, details) => {
2675
- executeContext.updateProgress(progress, message, details);
2676
- });
2677
- const output2 = await job.execute(job.input, {
2678
- signal: executeContext.signal,
2679
- updateProgress: executeContext.updateProgress.bind(this)
2680
- });
2681
- return output2;
2786
+ throw err;
2787
+ }
2788
+ await server.start();
2789
+ return queue;
2790
+ }
2791
+ async executeTaskChildren(input) {
2792
+ const executionMode = this.task.executionMode;
2793
+ switch (executionMode) {
2794
+ case "sequential":
2795
+ return this.executeSequential(input);
2796
+ case "parallel-limited":
2797
+ return this.executeParallelLimited(input);
2798
+ case "batched":
2799
+ return this.executeBatched(input);
2800
+ case "parallel":
2801
+ default:
2802
+ return super.executeTaskChildren(input);
2803
+ }
2804
+ }
2805
+ async executeSequential(input) {
2806
+ const tasks = this.task.subGraph.getTasks();
2807
+ const results = [];
2808
+ for (const task of tasks) {
2809
+ if (this.abortController?.signal.aborted) {
2810
+ break;
2811
+ }
2812
+ const taskResult = await task.run(input);
2813
+ results.push({
2814
+ id: task.config.id,
2815
+ type: task.type,
2816
+ data: taskResult
2817
+ });
2818
+ }
2819
+ return results;
2820
+ }
2821
+ async executeParallelLimited(input) {
2822
+ const tasks = this.task.subGraph.getTasks();
2823
+ const results = [];
2824
+ const limit = this.task.concurrencyLimit;
2825
+ for (let i = 0;i < tasks.length; i += limit) {
2826
+ if (this.abortController?.signal.aborted) {
2827
+ break;
2828
+ }
2829
+ const chunk = tasks.slice(i, i + limit);
2830
+ const chunkPromises = chunk.map(async (task) => {
2831
+ const taskResult = await task.run(input);
2832
+ return {
2833
+ id: task.config.id,
2834
+ type: task.type,
2835
+ data: taskResult
2836
+ };
2837
+ });
2838
+ const chunkResults = await Promise.all(chunkPromises);
2839
+ results.push(...chunkResults);
2840
+ }
2841
+ return results;
2842
+ }
2843
+ async executeBatched(input) {
2844
+ const tasks = this.task.subGraph.getTasks();
2845
+ const results = [];
2846
+ const batchSize = this.task.batchSize;
2847
+ for (let i = 0;i < tasks.length; i += batchSize) {
2848
+ if (this.abortController?.signal.aborted) {
2849
+ break;
2850
+ }
2851
+ const batch = tasks.slice(i, i + batchSize);
2852
+ const batchPromises = batch.map(async (task) => {
2853
+ const taskResult = await task.run(input);
2854
+ return {
2855
+ id: task.config.id,
2856
+ type: task.type,
2857
+ data: taskResult
2858
+ };
2859
+ });
2860
+ const batchResults = await Promise.all(batchPromises);
2861
+ results.push(...batchResults);
2862
+ const progress = Math.round((i + batch.length) / tasks.length * 100);
2863
+ this.task.emit("progress", progress, `Completed batch ${Math.ceil((i + 1) / batchSize)}`);
2864
+ }
2865
+ return results;
2866
+ }
2867
+ async cleanup() {
2868
+ if (this.iteratorQueue && this.iteratorQueueName) {
2869
+ try {
2870
+ this.iteratorQueue.server.stop();
2871
+ } catch (err) {}
2872
+ }
2873
+ }
2874
+ }
2875
+
2876
+ // src/task/IteratorTask.ts
2877
+ class IteratorTask extends GraphAsTask {
2878
+ static type = "IteratorTask";
2879
+ static category = "Flow Control";
2880
+ static title = "Iterator";
2881
+ static description = "Base class for loop-type tasks";
2882
+ static hasDynamicSchemas = true;
2883
+ _templateGraph;
2884
+ _iteratorPortInfo;
2885
+ constructor(input = {}, config = {}) {
2886
+ super(input, config);
2887
+ }
2888
+ get runner() {
2889
+ if (!this._runner) {
2890
+ this._runner = new IteratorTaskRunner(this);
2891
+ }
2892
+ return this._runner;
2893
+ }
2894
+ get executionMode() {
2895
+ return this.config.executionMode ?? "parallel";
2896
+ }
2897
+ get concurrencyLimit() {
2898
+ return this.config.concurrencyLimit ?? 5;
2899
+ }
2900
+ get batchSize() {
2901
+ return this.config.batchSize ?? 10;
2902
+ }
2903
+ detectIteratorPort() {
2904
+ if (this._iteratorPortInfo) {
2905
+ return this._iteratorPortInfo;
2906
+ }
2907
+ if (this.config.iteratorPort) {
2908
+ const schema2 = this.inputSchema();
2909
+ if (typeof schema2 === "boolean")
2910
+ return;
2911
+ const portSchema = schema2.properties?.[this.config.iteratorPort];
2912
+ if (portSchema && typeof portSchema === "object") {
2913
+ const itemSchema = portSchema.items ?? { type: "object" };
2914
+ this._iteratorPortInfo = {
2915
+ portName: this.config.iteratorPort,
2916
+ itemSchema
2917
+ };
2918
+ return this._iteratorPortInfo;
2919
+ }
2920
+ }
2921
+ const schema = this.inputSchema();
2922
+ if (typeof schema === "boolean")
2923
+ return;
2924
+ const properties = schema.properties || {};
2925
+ for (const [portName, portSchema] of Object.entries(properties)) {
2926
+ if (typeof portSchema !== "object" || portSchema === null)
2927
+ continue;
2928
+ const ps = portSchema;
2929
+ if (ps.type === "array" || ps.items !== undefined) {
2930
+ const itemSchema = ps.items ?? {
2931
+ type: "object",
2932
+ properties: {},
2933
+ additionalProperties: true
2934
+ };
2935
+ this._iteratorPortInfo = { portName, itemSchema };
2936
+ return this._iteratorPortInfo;
2937
+ }
2938
+ const variants = ps.oneOf ?? ps.anyOf;
2939
+ if (Array.isArray(variants)) {
2940
+ for (const variant of variants) {
2941
+ if (typeof variant === "object" && variant !== null) {
2942
+ const v = variant;
2943
+ if (v.type === "array" || v.items !== undefined) {
2944
+ const itemSchema = v.items ?? {
2945
+ type: "object",
2946
+ properties: {},
2947
+ additionalProperties: true
2948
+ };
2949
+ this._iteratorPortInfo = { portName, itemSchema };
2950
+ return this._iteratorPortInfo;
2951
+ }
2952
+ }
2953
+ }
2954
+ }
2955
+ }
2956
+ return;
2957
+ }
2958
+ getIteratorPortName() {
2959
+ return this.detectIteratorPort()?.portName;
2960
+ }
2961
+ getItemSchema() {
2962
+ return this.detectIteratorPort()?.itemSchema ?? {
2963
+ type: "object",
2964
+ properties: {},
2965
+ additionalProperties: true
2966
+ };
2967
+ }
2968
+ getIterableItems(input) {
2969
+ const portName = this.getIteratorPortName();
2970
+ if (!portName) {
2971
+ throw new TaskConfigurationError(`${this.type}: No array port found in input schema. ` + `Specify 'iteratorPort' in config or ensure input has an array-typed property.`);
2972
+ }
2973
+ const items = input[portName];
2974
+ if (items === undefined || items === null) {
2975
+ return [];
2976
+ }
2977
+ if (Array.isArray(items)) {
2978
+ return items;
2979
+ }
2980
+ return [items];
2981
+ }
2982
+ setTemplateGraph(graph) {
2983
+ this._templateGraph = graph;
2984
+ this.events.emit("regenerate");
2985
+ }
2986
+ getTemplateGraph() {
2987
+ return this._templateGraph;
2988
+ }
2989
+ regenerateGraph() {
2990
+ this.subGraph = new TaskGraph;
2991
+ if (!this._templateGraph || !this._templateGraph.getTasks().length) {
2992
+ super.regenerateGraph();
2993
+ return;
2994
+ }
2995
+ const items = this.getIterableItems(this.runInputData);
2996
+ if (items.length === 0) {
2997
+ super.regenerateGraph();
2998
+ return;
2999
+ }
3000
+ this.createIterationTasks(items);
3001
+ super.regenerateGraph();
3002
+ }
3003
+ createIterationTasks(items) {
3004
+ const portName = this.getIteratorPortName();
3005
+ if (!portName)
3006
+ return;
3007
+ const baseInput = {};
3008
+ for (const [key, value] of Object.entries(this.runInputData)) {
3009
+ if (key !== portName) {
3010
+ baseInput[key] = value;
3011
+ }
3012
+ }
3013
+ for (let i = 0;i < items.length; i++) {
3014
+ const item = items[i];
3015
+ const iterationInput = {
3016
+ ...baseInput,
3017
+ [portName]: item,
3018
+ _iterationIndex: i,
3019
+ _iterationItem: item
3020
+ };
3021
+ this.cloneTemplateForIteration(iterationInput, i);
3022
+ }
3023
+ }
3024
+ cloneTemplateForIteration(iterationInput, index) {
3025
+ if (!this._templateGraph)
3026
+ return;
3027
+ const templateTasks = this._templateGraph.getTasks();
3028
+ const templateDataflows = this._templateGraph.getDataflows();
3029
+ const idMap = new Map;
3030
+ for (const templateTask of templateTasks) {
3031
+ const TaskClass = templateTask.constructor;
3032
+ const clonedTask = new TaskClass({ ...templateTask.defaults, ...iterationInput }, {
3033
+ ...templateTask.config,
3034
+ id: `${templateTask.config.id}_iter${index}`,
3035
+ name: `${templateTask.config.name || templateTask.type} [${index}]`
3036
+ });
3037
+ this.subGraph.addTask(clonedTask);
3038
+ idMap.set(templateTask.config.id, clonedTask.config.id);
3039
+ }
3040
+ for (const templateDataflow of templateDataflows) {
3041
+ const sourceId = idMap.get(templateDataflow.sourceTaskId);
3042
+ const targetId = idMap.get(templateDataflow.targetTaskId);
3043
+ if (sourceId !== undefined && targetId !== undefined) {
3044
+ const { Dataflow: Dataflow2 } = (init_Dataflow(), __toCommonJS(exports_Dataflow));
3045
+ const clonedDataflow = new Dataflow2(sourceId, templateDataflow.sourceTaskPortId, targetId, templateDataflow.targetTaskPortId);
3046
+ this.subGraph.addDataflow(clonedDataflow);
3047
+ }
3048
+ }
3049
+ }
3050
+ async execute(input, context) {
3051
+ const items = this.getIterableItems(input);
3052
+ if (items.length === 0) {
3053
+ return this.getEmptyResult();
3054
+ }
3055
+ this.runInputData = { ...this.defaults, ...input };
3056
+ this.regenerateGraph();
3057
+ return super.execute(input, context);
3058
+ }
3059
+ getEmptyResult() {
3060
+ return {};
3061
+ }
3062
+ collectResults(results) {
3063
+ return results;
3064
+ }
3065
+ inputSchema() {
3066
+ if (this.hasChildren() || this._templateGraph) {
3067
+ return super.inputSchema();
3068
+ }
3069
+ return this.constructor.inputSchema();
3070
+ }
3071
+ outputSchema() {
3072
+ if (!this.hasChildren() && !this._templateGraph) {
3073
+ return this.constructor.outputSchema();
3074
+ }
3075
+ return this.getWrappedOutputSchema();
3076
+ }
3077
+ getWrappedOutputSchema() {
3078
+ const templateGraph = this._templateGraph ?? this.subGraph;
3079
+ if (!templateGraph) {
3080
+ return { type: "object", properties: {}, additionalProperties: false };
3081
+ }
3082
+ const tasks = templateGraph.getTasks();
3083
+ const endingNodes = tasks.filter((task) => templateGraph.getTargetDataflows(task.config.id).length === 0);
3084
+ if (endingNodes.length === 0) {
3085
+ return { type: "object", properties: {}, additionalProperties: false };
3086
+ }
3087
+ const properties = {};
3088
+ for (const task of endingNodes) {
3089
+ const taskOutputSchema = task.outputSchema();
3090
+ if (typeof taskOutputSchema === "boolean")
3091
+ continue;
3092
+ const taskProperties = taskOutputSchema.properties || {};
3093
+ for (const [key, schema] of Object.entries(taskProperties)) {
3094
+ properties[key] = {
3095
+ type: "array",
3096
+ items: schema
3097
+ };
3098
+ }
3099
+ }
3100
+ return {
3101
+ type: "object",
3102
+ properties,
3103
+ additionalProperties: false
3104
+ };
3105
+ }
3106
+ }
3107
+
3108
+ // src/task/BatchTask.ts
3109
+ class BatchTask extends IteratorTask {
3110
+ static type = "BatchTask";
3111
+ static category = "Flow Control";
3112
+ static title = "Batch";
3113
+ static description = "Processes an array in configurable batches";
3114
+ static compoundMerge = PROPERTY_ARRAY;
3115
+ static inputSchema() {
3116
+ return {
3117
+ type: "object",
3118
+ properties: {},
3119
+ additionalProperties: true
3120
+ };
3121
+ }
3122
+ static outputSchema() {
3123
+ return {
3124
+ type: "object",
3125
+ properties: {},
3126
+ additionalProperties: true
3127
+ };
3128
+ }
3129
+ get batchSize() {
3130
+ return this.config.batchSize ?? 10;
3131
+ }
3132
+ get flattenResults() {
3133
+ return this.config.flattenResults ?? true;
3134
+ }
3135
+ get batchExecutionMode() {
3136
+ return this.config.batchExecutionMode ?? "sequential";
3137
+ }
3138
+ getIterableItems(input) {
3139
+ const items = super.getIterableItems(input);
3140
+ return this.groupIntoBatches(items);
3141
+ }
3142
+ groupIntoBatches(items) {
3143
+ const batches = [];
3144
+ const size = this.batchSize;
3145
+ for (let i = 0;i < items.length; i += size) {
3146
+ batches.push(items.slice(i, i + size));
3147
+ }
3148
+ return batches;
3149
+ }
3150
+ createIterationTasks(batches) {
3151
+ const portName = this.getIteratorPortName();
3152
+ if (!portName)
3153
+ return;
3154
+ const baseInput = {};
3155
+ for (const [key, value] of Object.entries(this.runInputData)) {
3156
+ if (key !== portName) {
3157
+ baseInput[key] = value;
3158
+ }
3159
+ }
3160
+ for (let i = 0;i < batches.length; i++) {
3161
+ const batch = batches[i];
3162
+ const batchInput = {
3163
+ ...baseInput,
3164
+ [portName]: batch,
3165
+ _batchIndex: i,
3166
+ _batchItems: batch
3167
+ };
3168
+ this.cloneTemplateForIteration(batchInput, i);
3169
+ }
3170
+ }
3171
+ getEmptyResult() {
3172
+ const schema = this.outputSchema();
3173
+ if (typeof schema === "boolean") {
3174
+ return {};
3175
+ }
3176
+ const result = {};
3177
+ for (const key of Object.keys(schema.properties || {})) {
3178
+ result[key] = [];
3179
+ }
3180
+ return result;
3181
+ }
3182
+ outputSchema() {
3183
+ if (!this.hasChildren() && !this._templateGraph) {
3184
+ return this.constructor.outputSchema();
3185
+ }
3186
+ return this.getWrappedOutputSchema();
3187
+ }
3188
+ collectResults(results) {
3189
+ const collected = super.collectResults(results);
3190
+ if (!this.flattenResults || typeof collected !== "object" || collected === null) {
3191
+ return collected;
3192
+ }
3193
+ const flattened = {};
3194
+ for (const [key, value] of Object.entries(collected)) {
3195
+ if (Array.isArray(value)) {
3196
+ flattened[key] = value.flat(2);
3197
+ } else {
3198
+ flattened[key] = value;
3199
+ }
3200
+ }
3201
+ return flattened;
3202
+ }
3203
+ regenerateGraph() {
3204
+ this.subGraph = new TaskGraph;
3205
+ if (!this._templateGraph || !this._templateGraph.getTasks().length) {
3206
+ super.regenerateGraph();
3207
+ return;
3208
+ }
3209
+ const batches = this.getIterableItems(this.runInputData);
3210
+ if (batches.length === 0) {
3211
+ super.regenerateGraph();
3212
+ return;
3213
+ }
3214
+ this.createIterationTasks(batches);
3215
+ this.events.emit("regenerate");
3216
+ }
3217
+ }
3218
+ Workflow.prototype.batch = CreateLoopWorkflow(BatchTask);
3219
+ Workflow.prototype.endBatch = CreateEndLoopWorkflow("endBatch");
3220
+ // src/task/ForEachTask.ts
3221
+ class ForEachTask extends IteratorTask {
3222
+ static type = "ForEachTask";
3223
+ static category = "Flow Control";
3224
+ static title = "For Each";
3225
+ static description = "Iterates over an array and runs a workflow for each element";
3226
+ static inputSchema() {
3227
+ return {
3228
+ type: "object",
3229
+ properties: {},
3230
+ additionalProperties: true
3231
+ };
3232
+ }
3233
+ static outputSchema() {
3234
+ return {
3235
+ type: "object",
3236
+ properties: {
3237
+ completed: {
3238
+ type: "boolean",
3239
+ title: "Completed",
3240
+ description: "Whether all iterations completed successfully"
3241
+ },
3242
+ count: {
3243
+ type: "number",
3244
+ title: "Count",
3245
+ description: "Number of items processed"
3246
+ }
3247
+ },
3248
+ additionalProperties: false
3249
+ };
3250
+ }
3251
+ get shouldCollectResults() {
3252
+ return this.config.shouldCollectResults ?? false;
3253
+ }
3254
+ getEmptyResult() {
3255
+ return {
3256
+ completed: true,
3257
+ count: 0
3258
+ };
3259
+ }
3260
+ outputSchema() {
3261
+ if (this.shouldCollectResults && (this.hasChildren() || this._templateGraph)) {
3262
+ return this.getWrappedOutputSchema();
3263
+ }
3264
+ return this.constructor.outputSchema();
3265
+ }
3266
+ collectResults(results) {
3267
+ if (this.config.shouldCollectResults) {
3268
+ return super.collectResults(results);
3269
+ }
3270
+ return {
3271
+ completed: true,
3272
+ count: results.length
3273
+ };
3274
+ }
3275
+ }
3276
+ Workflow.prototype.forEach = CreateLoopWorkflow(ForEachTask);
3277
+ Workflow.prototype.endForEach = CreateEndLoopWorkflow("endForEach");
3278
+ // src/task/JobQueueFactory.ts
3279
+ import {
3280
+ JobQueueClient as JobQueueClient2,
3281
+ JobQueueServer as JobQueueServer2
3282
+ } from "@workglow/job-queue";
3283
+ import { InMemoryQueueStorage as InMemoryQueueStorage2 } from "@workglow/storage";
3284
+ import { createServiceToken as createServiceToken2, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
3285
+ var JOB_QUEUE_FACTORY = createServiceToken2("taskgraph.jobQueueFactory");
3286
+ var defaultJobQueueFactory = async ({
3287
+ queueName,
3288
+ jobClass,
3289
+ options
3290
+ }) => {
3291
+ const storage = options?.storage ?? new InMemoryQueueStorage2(queueName);
3292
+ await storage.setupDatabase();
3293
+ const server = new JobQueueServer2(jobClass, {
3294
+ storage,
3295
+ queueName,
3296
+ limiter: options?.limiter,
3297
+ workerCount: options?.workerCount,
3298
+ pollIntervalMs: options?.pollIntervalMs,
3299
+ deleteAfterCompletionMs: options?.deleteAfterCompletionMs,
3300
+ deleteAfterFailureMs: options?.deleteAfterFailureMs,
3301
+ deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
3302
+ cleanupIntervalMs: options?.cleanupIntervalMs
3303
+ });
3304
+ const client = new JobQueueClient2({
3305
+ storage,
3306
+ queueName
3307
+ });
3308
+ client.attach(server);
3309
+ return { server, client, storage };
3310
+ };
3311
+ function registerJobQueueFactory(factory) {
3312
+ globalServiceRegistry3.registerInstance(JOB_QUEUE_FACTORY, factory);
3313
+ }
3314
+ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
3315
+ return async ({
3316
+ queueName,
3317
+ jobClass,
3318
+ options
3319
+ }) => {
3320
+ const mergedOptions = {
3321
+ ...defaultOptions,
3322
+ ...options ?? {}
3323
+ };
3324
+ const storage = mergedOptions.storage ?? new InMemoryQueueStorage2(queueName);
3325
+ await storage.setupDatabase();
3326
+ const server = new JobQueueServer2(jobClass, {
3327
+ storage,
3328
+ queueName,
3329
+ limiter: mergedOptions.limiter,
3330
+ workerCount: mergedOptions.workerCount,
3331
+ pollIntervalMs: mergedOptions.pollIntervalMs,
3332
+ deleteAfterCompletionMs: mergedOptions.deleteAfterCompletionMs,
3333
+ deleteAfterFailureMs: mergedOptions.deleteAfterFailureMs,
3334
+ deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
3335
+ cleanupIntervalMs: mergedOptions.cleanupIntervalMs
3336
+ });
3337
+ const client = new JobQueueClient2({
3338
+ storage,
3339
+ queueName
3340
+ });
3341
+ client.attach(server);
3342
+ return { server, client, storage };
3343
+ };
3344
+ }
3345
+ function getJobQueueFactory() {
3346
+ if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
3347
+ registerJobQueueFactory(defaultJobQueueFactory);
3348
+ }
3349
+ return globalServiceRegistry3.get(JOB_QUEUE_FACTORY);
3350
+ }
3351
+ if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
3352
+ registerJobQueueFactory(defaultJobQueueFactory);
3353
+ }
3354
+ // src/task/JobQueueTask.ts
3355
+ import { Job as Job3 } from "@workglow/job-queue";
3356
+ class JobQueueTask extends GraphAsTask {
3357
+ static type = "JobQueueTask";
3358
+ static canRunDirectly = true;
3359
+ currentQueueName;
3360
+ currentJobId;
3361
+ currentRunnerId;
3362
+ jobClass;
3363
+ constructor(input = {}, config = {}) {
3364
+ config.queue ??= true;
3365
+ super(input, config);
3366
+ this.jobClass = Job3;
3367
+ }
3368
+ async execute(input, executeContext) {
3369
+ let cleanup = () => {};
3370
+ try {
3371
+ if (this.config.queue === false && !this.constructor.canRunDirectly) {
3372
+ throw new TaskConfigurationError(`${this.type} cannot run directly without a queue`);
3373
+ }
3374
+ const registeredQueue = await this.resolveQueue(input);
3375
+ if (!registeredQueue) {
3376
+ if (!this.constructor.canRunDirectly) {
3377
+ const queueLabel = typeof this.config.queue === "string" ? this.config.queue : this.currentQueueName ?? this.type;
3378
+ throw new TaskConfigurationError(`Queue ${queueLabel} not found, and ${this.type} cannot run directly`);
3379
+ }
3380
+ this.currentJobId = undefined;
3381
+ const job = await this.createJob(input, this.currentQueueName);
3382
+ cleanup = job.onJobProgress((progress, message, details) => {
3383
+ executeContext.updateProgress(progress, message, details);
3384
+ });
3385
+ const output2 = await job.execute(job.input, {
3386
+ signal: executeContext.signal,
3387
+ updateProgress: executeContext.updateProgress.bind(this)
3388
+ });
3389
+ return output2;
2682
3390
  }
2683
3391
  const { client } = registeredQueue;
2684
3392
  const jobInput = await this.getJobInput(input);
@@ -2777,6 +3485,219 @@ class JobQueueTask extends GraphAsTask {
2777
3485
  super.abort();
2778
3486
  }
2779
3487
  }
3488
+ // src/task/MapTask.ts
3489
+ class MapTask extends IteratorTask {
3490
+ static type = "MapTask";
3491
+ static category = "Flow Control";
3492
+ static title = "Map";
3493
+ static description = "Transforms an array by running a workflow for each element";
3494
+ static compoundMerge = PROPERTY_ARRAY;
3495
+ static inputSchema() {
3496
+ return {
3497
+ type: "object",
3498
+ properties: {},
3499
+ additionalProperties: true
3500
+ };
3501
+ }
3502
+ static outputSchema() {
3503
+ return {
3504
+ type: "object",
3505
+ properties: {},
3506
+ additionalProperties: true
3507
+ };
3508
+ }
3509
+ get preserveOrder() {
3510
+ return this.config.preserveOrder ?? true;
3511
+ }
3512
+ get flatten() {
3513
+ return this.config.flatten ?? false;
3514
+ }
3515
+ getEmptyResult() {
3516
+ const schema = this.outputSchema();
3517
+ if (typeof schema === "boolean") {
3518
+ return {};
3519
+ }
3520
+ const result = {};
3521
+ for (const key of Object.keys(schema.properties || {})) {
3522
+ result[key] = [];
3523
+ }
3524
+ return result;
3525
+ }
3526
+ outputSchema() {
3527
+ if (!this.hasChildren() && !this._templateGraph) {
3528
+ return this.constructor.outputSchema();
3529
+ }
3530
+ return this.getWrappedOutputSchema();
3531
+ }
3532
+ collectResults(results) {
3533
+ const collected = super.collectResults(results);
3534
+ if (!this.flatten || typeof collected !== "object" || collected === null) {
3535
+ return collected;
3536
+ }
3537
+ const flattened = {};
3538
+ for (const [key, value] of Object.entries(collected)) {
3539
+ if (Array.isArray(value)) {
3540
+ flattened[key] = value.flat();
3541
+ } else {
3542
+ flattened[key] = value;
3543
+ }
3544
+ }
3545
+ return flattened;
3546
+ }
3547
+ }
3548
+ Workflow.prototype.map = CreateLoopWorkflow(MapTask);
3549
+ Workflow.prototype.endMap = CreateEndLoopWorkflow("endMap");
3550
+ // src/task/ReduceTask.ts
3551
+ class ReduceTask extends IteratorTask {
3552
+ static type = "ReduceTask";
3553
+ static category = "Flow Control";
3554
+ static title = "Reduce";
3555
+ static description = "Processes array elements sequentially with an accumulator (fold)";
3556
+ constructor(input = {}, config = {}) {
3557
+ const reduceConfig = {
3558
+ ...config,
3559
+ executionMode: "sequential"
3560
+ };
3561
+ super(input, reduceConfig);
3562
+ }
3563
+ get initialValue() {
3564
+ return this.config.initialValue ?? {};
3565
+ }
3566
+ get accumulatorPort() {
3567
+ return this.config.accumulatorPort ?? "accumulator";
3568
+ }
3569
+ get currentItemPort() {
3570
+ return this.config.currentItemPort ?? "currentItem";
3571
+ }
3572
+ get indexPort() {
3573
+ return this.config.indexPort ?? "index";
3574
+ }
3575
+ async execute(input, context) {
3576
+ if (!this._templateGraph || this._templateGraph.getTasks().length === 0) {
3577
+ return this.initialValue;
3578
+ }
3579
+ const items = this.getIterableItems(input);
3580
+ if (items.length === 0) {
3581
+ return this.initialValue;
3582
+ }
3583
+ let accumulator = { ...this.initialValue };
3584
+ for (let index = 0;index < items.length; index++) {
3585
+ if (context.signal?.aborted) {
3586
+ break;
3587
+ }
3588
+ const currentItem = items[index];
3589
+ const stepInput = {
3590
+ ...input,
3591
+ [this.accumulatorPort]: accumulator,
3592
+ [this.currentItemPort]: currentItem,
3593
+ [this.indexPort]: index
3594
+ };
3595
+ this.subGraph = this.cloneTemplateForStep(index);
3596
+ const results = await this.subGraph.run(stepInput, {
3597
+ parentSignal: context.signal
3598
+ });
3599
+ accumulator = this.subGraph.mergeExecuteOutputsToRunOutput(results, this.compoundMerge);
3600
+ const progress = Math.round((index + 1) / items.length * 100);
3601
+ await context.updateProgress(progress, `Processing item ${index + 1}/${items.length}`);
3602
+ }
3603
+ return accumulator;
3604
+ }
3605
+ getEmptyResult() {
3606
+ return this.initialValue;
3607
+ }
3608
+ cloneTemplateForStep(stepIndex) {
3609
+ const clonedGraph = new TaskGraph;
3610
+ if (!this._templateGraph) {
3611
+ return clonedGraph;
3612
+ }
3613
+ const templateTasks = this._templateGraph.getTasks();
3614
+ const templateDataflows = this._templateGraph.getDataflows();
3615
+ const idMap = new Map;
3616
+ for (const templateTask of templateTasks) {
3617
+ const TaskClass = templateTask.constructor;
3618
+ const clonedTask = new TaskClass({ ...templateTask.defaults }, {
3619
+ ...templateTask.config,
3620
+ id: `${templateTask.config.id}_step${stepIndex}`,
3621
+ name: `${templateTask.config.name || templateTask.type} [${stepIndex}]`
3622
+ });
3623
+ clonedGraph.addTask(clonedTask);
3624
+ idMap.set(templateTask.config.id, clonedTask.config.id);
3625
+ }
3626
+ for (const templateDataflow of templateDataflows) {
3627
+ const sourceId = idMap.get(templateDataflow.sourceTaskId);
3628
+ const targetId = idMap.get(templateDataflow.targetTaskId);
3629
+ if (sourceId !== undefined && targetId !== undefined) {
3630
+ const { Dataflow: Dataflow2 } = (init_Dataflow(), __toCommonJS(exports_Dataflow));
3631
+ const clonedDataflow = new Dataflow2(sourceId, templateDataflow.sourceTaskPortId, targetId, templateDataflow.targetTaskPortId);
3632
+ clonedGraph.addDataflow(clonedDataflow);
3633
+ }
3634
+ }
3635
+ return clonedGraph;
3636
+ }
3637
+ static inputSchema() {
3638
+ return {
3639
+ type: "object",
3640
+ properties: {
3641
+ accumulator: {
3642
+ title: "Accumulator",
3643
+ description: "The current accumulator value"
3644
+ },
3645
+ currentItem: {
3646
+ title: "Current Item",
3647
+ description: "The current item being processed"
3648
+ },
3649
+ index: {
3650
+ type: "number",
3651
+ title: "Index",
3652
+ description: "The current item index"
3653
+ }
3654
+ },
3655
+ additionalProperties: true
3656
+ };
3657
+ }
3658
+ static outputSchema() {
3659
+ return {
3660
+ type: "object",
3661
+ properties: {},
3662
+ additionalProperties: true
3663
+ };
3664
+ }
3665
+ outputSchema() {
3666
+ if (!this._templateGraph) {
3667
+ return this.constructor.outputSchema();
3668
+ }
3669
+ const tasks = this._templateGraph.getTasks();
3670
+ const endingNodes = tasks.filter((task) => this._templateGraph.getTargetDataflows(task.config.id).length === 0);
3671
+ if (endingNodes.length === 0) {
3672
+ return this.constructor.outputSchema();
3673
+ }
3674
+ const properties = {};
3675
+ for (const task of endingNodes) {
3676
+ const taskOutputSchema = task.outputSchema();
3677
+ if (typeof taskOutputSchema === "boolean")
3678
+ continue;
3679
+ const taskProperties = taskOutputSchema.properties || {};
3680
+ for (const [key, schema] of Object.entries(taskProperties)) {
3681
+ if (!properties[key]) {
3682
+ properties[key] = schema;
3683
+ }
3684
+ }
3685
+ }
3686
+ return {
3687
+ type: "object",
3688
+ properties,
3689
+ additionalProperties: false
3690
+ };
3691
+ }
3692
+ regenerateGraph() {
3693
+ this.events.emit("regenerate");
3694
+ }
3695
+ }
3696
+ Workflow.prototype.reduce = CreateLoopWorkflow(ReduceTask);
3697
+ Workflow.prototype.endReduce = CreateEndLoopWorkflow("endReduce");
3698
+ // src/task/TaskJSON.ts
3699
+ init_Dataflow();
3700
+
2780
3701
  // src/task/TaskRegistry.ts
2781
3702
  var taskConstructors = new Map;
2782
3703
  function registerTask(baseClass) {
@@ -2844,9 +3765,167 @@ var createGraphFromGraphJSON = (graphJsonObj) => {
2844
3765
  }
2845
3766
  return subGraph;
2846
3767
  };
3768
+
3769
+ // src/task/index.ts
3770
+ init_TaskTypes();
3771
+
3772
+ // src/task/WhileTask.ts
3773
+ class WhileTask extends GraphAsTask {
3774
+ static type = "WhileTask";
3775
+ static category = "Flow Control";
3776
+ static title = "While Loop";
3777
+ static description = "Loops until a condition function returns false";
3778
+ static hasDynamicSchemas = true;
3779
+ _templateGraph;
3780
+ _currentIteration = 0;
3781
+ constructor(input = {}, config = {}) {
3782
+ super(input, config);
3783
+ }
3784
+ get condition() {
3785
+ return this.config.condition;
3786
+ }
3787
+ get maxIterations() {
3788
+ return this.config.maxIterations ?? 100;
3789
+ }
3790
+ get chainIterations() {
3791
+ return this.config.chainIterations ?? true;
3792
+ }
3793
+ get currentIteration() {
3794
+ return this._currentIteration;
3795
+ }
3796
+ setTemplateGraph(graph) {
3797
+ this._templateGraph = graph;
3798
+ }
3799
+ getTemplateGraph() {
3800
+ return this._templateGraph;
3801
+ }
3802
+ async execute(input, context) {
3803
+ if (!this._templateGraph || this._templateGraph.getTasks().length === 0) {
3804
+ throw new TaskConfigurationError(`${this.type}: No template graph set for while loop`);
3805
+ }
3806
+ if (!this.condition) {
3807
+ throw new TaskConfigurationError(`${this.type}: No condition function provided`);
3808
+ }
3809
+ this._currentIteration = 0;
3810
+ let currentInput = { ...input };
3811
+ let currentOutput = {};
3812
+ while (this._currentIteration < this.maxIterations) {
3813
+ if (context.signal?.aborted) {
3814
+ break;
3815
+ }
3816
+ this.subGraph = this.cloneTemplateGraph(this._currentIteration);
3817
+ const results = await this.subGraph.run(currentInput, {
3818
+ parentSignal: context.signal
3819
+ });
3820
+ currentOutput = this.subGraph.mergeExecuteOutputsToRunOutput(results, this.compoundMerge);
3821
+ if (!this.condition(currentOutput, this._currentIteration)) {
3822
+ break;
3823
+ }
3824
+ if (this.chainIterations) {
3825
+ currentInput = { ...currentInput, ...currentOutput };
3826
+ }
3827
+ this._currentIteration++;
3828
+ const progress = Math.min(this._currentIteration / this.maxIterations * 100, 99);
3829
+ await context.updateProgress(progress, `Iteration ${this._currentIteration}`);
3830
+ }
3831
+ return currentOutput;
3832
+ }
3833
+ cloneTemplateGraph(iteration) {
3834
+ const clonedGraph = new TaskGraph;
3835
+ if (!this._templateGraph) {
3836
+ return clonedGraph;
3837
+ }
3838
+ const templateTasks = this._templateGraph.getTasks();
3839
+ const templateDataflows = this._templateGraph.getDataflows();
3840
+ const idMap = new Map;
3841
+ for (const templateTask of templateTasks) {
3842
+ const TaskClass = templateTask.constructor;
3843
+ const clonedTask = new TaskClass({ ...templateTask.defaults }, {
3844
+ ...templateTask.config,
3845
+ id: `${templateTask.config.id}_iter${iteration}`,
3846
+ name: `${templateTask.config.name || templateTask.type} [${iteration}]`
3847
+ });
3848
+ clonedGraph.addTask(clonedTask);
3849
+ idMap.set(templateTask.config.id, clonedTask.config.id);
3850
+ }
3851
+ for (const templateDataflow of templateDataflows) {
3852
+ const sourceId = idMap.get(templateDataflow.sourceTaskId);
3853
+ const targetId = idMap.get(templateDataflow.targetTaskId);
3854
+ if (sourceId !== undefined && targetId !== undefined) {
3855
+ const { Dataflow: Dataflow2 } = (init_Dataflow(), __toCommonJS(exports_Dataflow));
3856
+ const clonedDataflow = new Dataflow2(sourceId, templateDataflow.sourceTaskPortId, targetId, templateDataflow.targetTaskPortId);
3857
+ clonedGraph.addDataflow(clonedDataflow);
3858
+ }
3859
+ }
3860
+ return clonedGraph;
3861
+ }
3862
+ static inputSchema() {
3863
+ return {
3864
+ type: "object",
3865
+ properties: {},
3866
+ additionalProperties: true
3867
+ };
3868
+ }
3869
+ static outputSchema() {
3870
+ return {
3871
+ type: "object",
3872
+ properties: {
3873
+ _iterations: {
3874
+ type: "number",
3875
+ title: "Iterations",
3876
+ description: "Number of iterations executed"
3877
+ }
3878
+ },
3879
+ additionalProperties: true
3880
+ };
3881
+ }
3882
+ outputSchema() {
3883
+ if (!this._templateGraph) {
3884
+ return this.constructor.outputSchema();
3885
+ }
3886
+ const tasks = this._templateGraph.getTasks();
3887
+ const endingNodes = tasks.filter((task) => this._templateGraph.getTargetDataflows(task.config.id).length === 0);
3888
+ if (endingNodes.length === 0) {
3889
+ return this.constructor.outputSchema();
3890
+ }
3891
+ const properties = {
3892
+ _iterations: {
3893
+ type: "number",
3894
+ title: "Iterations",
3895
+ description: "Number of iterations executed"
3896
+ }
3897
+ };
3898
+ for (const task of endingNodes) {
3899
+ const taskOutputSchema = task.outputSchema();
3900
+ if (typeof taskOutputSchema === "boolean")
3901
+ continue;
3902
+ const taskProperties = taskOutputSchema.properties || {};
3903
+ for (const [key, schema] of Object.entries(taskProperties)) {
3904
+ if (!properties[key]) {
3905
+ properties[key] = schema;
3906
+ }
3907
+ }
3908
+ }
3909
+ return {
3910
+ type: "object",
3911
+ properties,
3912
+ additionalProperties: false
3913
+ };
3914
+ }
3915
+ }
3916
+ Workflow.prototype.while = CreateLoopWorkflow(WhileTask);
3917
+ Workflow.prototype.endWhile = CreateEndLoopWorkflow("endWhile");
2847
3918
  // src/task/index.ts
2848
3919
  var registerBaseTasks = () => {
2849
- const tasks = [ConditionalTask, GraphAsTask];
3920
+ const tasks = [
3921
+ ConditionalTask,
3922
+ GraphAsTask,
3923
+ ForEachTask,
3924
+ MapTask,
3925
+ BatchTask,
3926
+ WhileTask,
3927
+ ReduceTask
3928
+ ];
2850
3929
  tasks.map(TaskRegistry.registerTask);
2851
3930
  return tasks;
2852
3931
  };
@@ -3026,6 +4105,7 @@ export {
3026
4105
  connect,
3027
4106
  WorkflowError,
3028
4107
  Workflow,
4108
+ WhileTask,
3029
4109
  TaskStatus,
3030
4110
  TaskRegistry,
3031
4111
  TaskQueueRegistry,
@@ -3048,13 +4128,18 @@ export {
3048
4128
  Task,
3049
4129
  TASK_OUTPUT_REPOSITORY,
3050
4130
  TASK_GRAPH_REPOSITORY,
4131
+ ReduceTask,
3051
4132
  PROPERTY_ARRAY,
4133
+ MapTask,
3052
4134
  JobTaskFailedError,
3053
4135
  JobQueueTask,
3054
4136
  JOB_QUEUE_FACTORY,
4137
+ IteratorTaskRunner,
4138
+ IteratorTask,
3055
4139
  GraphAsTaskRunner,
3056
4140
  GraphAsTask,
3057
4141
  GRAPH_RESULT_ARRAY,
4142
+ ForEachTask,
3058
4143
  EventTaskGraphToDagMapping,
3059
4144
  EventDagToTaskGraphMapping,
3060
4145
  DataflowArrow,
@@ -3062,7 +4147,10 @@ export {
3062
4147
  DATAFLOW_ERROR_PORT,
3063
4148
  DATAFLOW_ALL_PORTS,
3064
4149
  CreateWorkflow,
3065
- ConditionalTask
4150
+ CreateLoopWorkflow,
4151
+ CreateEndLoopWorkflow,
4152
+ ConditionalTask,
4153
+ BatchTask
3066
4154
  };
3067
4155
 
3068
- //# debugId=0CAE14A95DE5D28564756E2164756E21
4156
+ //# debugId=D3CEB14CCE2BF61564756E2164756E21