@workglow/task-graph 0.0.85 → 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.
Files changed (71) hide show
  1. package/README.md +1 -53
  2. package/dist/browser.js +1451 -355
  3. package/dist/browser.js.map +28 -21
  4. package/dist/bun.js +1451 -355
  5. package/dist/bun.js.map +28 -21
  6. package/dist/common.d.ts +1 -16
  7. package/dist/common.d.ts.map +1 -1
  8. package/dist/node.js +1451 -355
  9. package/dist/node.js.map +28 -21
  10. package/dist/storage/TaskGraphTabularRepository.d.ts +2 -2
  11. package/dist/storage/TaskGraphTabularRepository.d.ts.map +1 -1
  12. package/dist/storage/TaskOutputTabularRepository.d.ts +2 -2
  13. package/dist/storage/TaskOutputTabularRepository.d.ts.map +1 -1
  14. package/dist/task/BatchTask.d.ts +154 -0
  15. package/dist/task/BatchTask.d.ts.map +1 -0
  16. package/dist/task/ConditionalTask.d.ts +1 -0
  17. package/dist/task/ConditionalTask.d.ts.map +1 -1
  18. package/dist/task/ForEachTask.d.ts +120 -0
  19. package/dist/task/ForEachTask.d.ts.map +1 -0
  20. package/dist/task/GraphAsTask.d.ts +2 -0
  21. package/dist/task/GraphAsTask.d.ts.map +1 -1
  22. package/dist/task/GraphAsTaskRunner.d.ts +0 -1
  23. package/dist/task/GraphAsTaskRunner.d.ts.map +1 -1
  24. package/dist/task/ITask.d.ts +5 -6
  25. package/dist/task/ITask.d.ts.map +1 -1
  26. package/dist/task/InputResolver.d.ts +34 -0
  27. package/dist/task/InputResolver.d.ts.map +1 -0
  28. package/dist/task/IteratorTask.d.ts +197 -0
  29. package/dist/task/IteratorTask.d.ts.map +1 -0
  30. package/dist/task/IteratorTaskRunner.d.ts +63 -0
  31. package/dist/task/IteratorTaskRunner.d.ts.map +1 -0
  32. package/dist/task/JobQueueTask.d.ts +3 -3
  33. package/dist/task/JobQueueTask.d.ts.map +1 -1
  34. package/dist/task/MapTask.d.ts +134 -0
  35. package/dist/task/MapTask.d.ts.map +1 -0
  36. package/dist/task/ReduceTask.d.ts +155 -0
  37. package/dist/task/ReduceTask.d.ts.map +1 -0
  38. package/dist/task/Task.d.ts +22 -11
  39. package/dist/task/Task.d.ts.map +1 -1
  40. package/dist/task/TaskEvents.d.ts +1 -1
  41. package/dist/task/TaskEvents.d.ts.map +1 -1
  42. package/dist/task/TaskJSON.d.ts +1 -4
  43. package/dist/task/TaskJSON.d.ts.map +1 -1
  44. package/dist/task/TaskRunner.d.ts +6 -5
  45. package/dist/task/TaskRunner.d.ts.map +1 -1
  46. package/dist/task/TaskTypes.d.ts +0 -4
  47. package/dist/task/TaskTypes.d.ts.map +1 -1
  48. package/dist/task/WhileTask.d.ts +176 -0
  49. package/dist/task/WhileTask.d.ts.map +1 -0
  50. package/dist/task/index.d.ts +35 -0
  51. package/dist/task/index.d.ts.map +1 -0
  52. package/dist/task-graph/Dataflow.d.ts +2 -3
  53. package/dist/task-graph/Dataflow.d.ts.map +1 -1
  54. package/dist/task-graph/ITaskGraph.d.ts +1 -1
  55. package/dist/task-graph/ITaskGraph.d.ts.map +1 -1
  56. package/dist/task-graph/TaskGraph.d.ts +4 -4
  57. package/dist/task-graph/TaskGraph.d.ts.map +1 -1
  58. package/dist/task-graph/TaskGraphRunner.d.ts +11 -18
  59. package/dist/task-graph/TaskGraphRunner.d.ts.map +1 -1
  60. package/dist/task-graph/Workflow.d.ts +95 -9
  61. package/dist/task-graph/Workflow.d.ts.map +1 -1
  62. package/package.json +7 -7
  63. package/src/storage/README.md +1 -1
  64. package/src/task/README.md +91 -15
  65. package/src/task-graph/README.md +0 -2
  66. package/dist/task/ArrayTask.d.ts +0 -77
  67. package/dist/task/ArrayTask.d.ts.map +0 -1
  68. package/dist/task/InputTask.d.ts +0 -27
  69. package/dist/task/InputTask.d.ts.map +0 -1
  70. package/dist/task/OutputTask.d.ts +0 -27
  71. package/dist/task/OutputTask.d.ts.map +0 -1
package/dist/browser.js CHANGED
@@ -1,28 +1,54 @@
1
- // src/task/ArrayTask.ts
2
- import { uuid4 as uuid44 } from "@workglow/util";
3
-
4
- // src/task-graph/TaskGraph.ts
5
- import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid43 } from "@workglow/util";
6
-
7
- // src/task/GraphAsTask.ts
8
- import { compileSchema as compileSchema2 } from "@workglow/util";
9
-
10
- // src/task-graph/Dataflow.ts
11
- 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);
12
29
 
13
30
  // src/task/TaskTypes.ts
14
- var TaskStatus = {
15
- PENDING: "PENDING",
16
- DISABLED: "DISABLED",
17
- PROCESSING: "PROCESSING",
18
- COMPLETED: "COMPLETED",
19
- ABORTING: "ABORTING",
20
- FAILED: "FAILED"
21
- };
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
+ });
22
42
 
23
43
  // src/task-graph/Dataflow.ts
24
- var DATAFLOW_ALL_PORTS = "*";
25
- 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";
26
52
 
27
53
  class Dataflow {
28
54
  sourceTaskId;
@@ -42,14 +68,12 @@ class Dataflow {
42
68
  return Dataflow.createId(this.sourceTaskId, this.sourceTaskPortId, this.targetTaskId, this.targetTaskPortId);
43
69
  }
44
70
  value = undefined;
45
- provenance = {};
46
71
  status = TaskStatus.PENDING;
47
72
  error;
48
73
  reset() {
49
74
  this.status = TaskStatus.PENDING;
50
75
  this.error = undefined;
51
76
  this.value = undefined;
52
- this.provenance = {};
53
77
  this.emit("reset");
54
78
  this.emit("status", this.status);
55
79
  }
@@ -79,7 +103,7 @@ class Dataflow {
79
103
  }
80
104
  this.emit("status", this.status);
81
105
  }
82
- setPortData(entireDataBlock, nodeProvenance) {
106
+ setPortData(entireDataBlock) {
83
107
  if (this.sourceTaskPortId === DATAFLOW_ALL_PORTS) {
84
108
  this.value = entireDataBlock;
85
109
  } else if (this.sourceTaskPortId === DATAFLOW_ERROR_PORT) {
@@ -87,17 +111,17 @@ class Dataflow {
87
111
  } else {
88
112
  this.value = entireDataBlock[this.sourceTaskPortId];
89
113
  }
90
- if (nodeProvenance)
91
- this.provenance = nodeProvenance;
92
114
  }
93
115
  getPortData() {
116
+ let result;
94
117
  if (this.targetTaskPortId === DATAFLOW_ALL_PORTS) {
95
- return this.value;
118
+ result = this.value;
96
119
  } else if (this.targetTaskPortId === DATAFLOW_ERROR_PORT) {
97
- return { [DATAFLOW_ERROR_PORT]: this.error };
120
+ result = { [DATAFLOW_ERROR_PORT]: this.error };
98
121
  } else {
99
- return { [this.targetTaskPortId]: this.value };
122
+ result = { [this.targetTaskPortId]: this.value };
100
123
  }
124
+ return result;
101
125
  }
102
126
  toJSON() {
103
127
  return {
@@ -159,23 +183,37 @@ class Dataflow {
159
183
  this._events?.emit(name, ...args);
160
184
  }
161
185
  }
162
-
163
- class DataflowArrow extends Dataflow {
164
- constructor(dataflow) {
165
- const pattern = /^([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\] ==> ([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\]$/;
166
- const match = dataflow.match(pattern);
167
- if (!match) {
168
- 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);
169
198
  }
170
- const [, sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId] = match;
171
- super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
172
- }
173
- }
199
+ };
200
+ });
201
+
202
+ // src/common.ts
203
+ init_Dataflow();
204
+
205
+ // src/task-graph/TaskGraph.ts
206
+ import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid44 } from "@workglow/util";
207
+
208
+ // src/task/GraphAsTask.ts
209
+ init_Dataflow();
210
+ import { compileSchema as compileSchema2 } from "@workglow/util";
174
211
 
175
212
  // src/task-graph/TaskGraphRunner.ts
176
213
  import {
177
214
  collectPropertyValues,
178
215
  globalServiceRegistry as globalServiceRegistry2,
216
+ ServiceRegistry as ServiceRegistry2,
179
217
  uuid4 as uuid42
180
218
  } from "@workglow/util";
181
219
 
@@ -210,6 +248,7 @@ class TaskOutputRepository {
210
248
  }
211
249
 
212
250
  // src/task/Task.ts
251
+ init_Dataflow();
213
252
  import {
214
253
  compileSchema,
215
254
  deepEqual,
@@ -280,13 +319,71 @@ class TaskInvalidInputError extends TaskError {
280
319
 
281
320
  // src/task/TaskRunner.ts
282
321
  import { globalServiceRegistry } from "@workglow/util";
322
+
323
+ // src/task/InputResolver.ts
324
+ import { getInputResolvers } from "@workglow/util";
325
+ function getSchemaFormat(schema) {
326
+ if (typeof schema !== "object" || schema === null)
327
+ return;
328
+ const s = schema;
329
+ if (typeof s.format === "string")
330
+ return s.format;
331
+ const variants = s.oneOf ?? s.anyOf;
332
+ if (Array.isArray(variants)) {
333
+ for (const variant of variants) {
334
+ if (typeof variant === "object" && variant !== null) {
335
+ const v = variant;
336
+ if (typeof v.format === "string")
337
+ return v.format;
338
+ }
339
+ }
340
+ }
341
+ return;
342
+ }
343
+ function getFormatPrefix(format) {
344
+ const colonIndex = format.indexOf(":");
345
+ return colonIndex >= 0 ? format.substring(0, colonIndex) : format;
346
+ }
347
+ async function resolveSchemaInputs(input, schema, config) {
348
+ if (typeof schema === "boolean")
349
+ return input;
350
+ const properties = schema.properties;
351
+ if (!properties || typeof properties !== "object")
352
+ return input;
353
+ const resolvers = getInputResolvers();
354
+ const resolved = { ...input };
355
+ for (const [key, propSchema] of Object.entries(properties)) {
356
+ const value = resolved[key];
357
+ const format = getSchemaFormat(propSchema);
358
+ if (!format)
359
+ continue;
360
+ let resolver = resolvers.get(format);
361
+ if (!resolver) {
362
+ const prefix = getFormatPrefix(format);
363
+ resolver = resolvers.get(prefix);
364
+ }
365
+ if (!resolver)
366
+ continue;
367
+ if (typeof value === "string") {
368
+ resolved[key] = await resolver(value, format, config.registry);
369
+ } else if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
370
+ const results = await Promise.all(value.map((item) => resolver(item, format, config.registry)));
371
+ resolved[key] = results.filter((result) => result !== undefined);
372
+ }
373
+ }
374
+ return resolved;
375
+ }
376
+
377
+ // src/task/TaskRunner.ts
378
+ init_TaskTypes();
379
+
283
380
  class TaskRunner {
284
381
  running = false;
285
382
  reactiveRunning = false;
286
- nodeProvenance = {};
287
383
  task;
288
384
  abortController;
289
385
  outputCache;
386
+ registry = globalServiceRegistry;
290
387
  constructor(task) {
291
388
  this.task = task;
292
389
  this.own = this.own.bind(this);
@@ -296,6 +393,8 @@ class TaskRunner {
296
393
  await this.handleStart(config);
297
394
  try {
298
395
  this.task.setInput(overrides);
396
+ const schema = this.task.constructor.inputSchema();
397
+ this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
299
398
  const isValid = await this.task.validateInput(this.task.runInputData);
300
399
  if (!isValid) {
301
400
  throw new TaskInvalidInputError("Invalid input data");
@@ -331,6 +430,8 @@ class TaskRunner {
331
430
  return this.task.runOutputData;
332
431
  }
333
432
  this.task.setInput(overrides);
433
+ const schema = this.task.constructor.inputSchema();
434
+ this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
334
435
  await this.handleStartReactive();
335
436
  try {
336
437
  const isValid = await this.task.validateInput(this.task.runInputData);
@@ -361,8 +462,8 @@ class TaskRunner {
361
462
  const result = await this.task.execute(input, {
362
463
  signal: this.abortController.signal,
363
464
  updateProgress: this.handleProgress.bind(this),
364
- nodeProvenance: this.nodeProvenance,
365
- own: this.own
465
+ own: this.own,
466
+ registry: this.registry
366
467
  });
367
468
  return await this.executeTaskReactive(input, result || {});
368
469
  }
@@ -373,7 +474,6 @@ class TaskRunner {
373
474
  async handleStart(config = {}) {
374
475
  if (this.task.status === TaskStatus.PROCESSING)
375
476
  return;
376
- this.nodeProvenance = {};
377
477
  this.running = true;
378
478
  this.task.startedAt = new Date;
379
479
  this.task.progress = 0;
@@ -382,7 +482,6 @@ class TaskRunner {
382
482
  this.abortController.signal.addEventListener("abort", () => {
383
483
  this.handleAbort();
384
484
  });
385
- this.nodeProvenance = config.nodeProvenance ?? {};
386
485
  const cache = this.task.config.outputCache ?? config.outputCache;
387
486
  if (cache === true) {
388
487
  let instance = globalServiceRegistry.get(TASK_OUTPUT_REPOSITORY);
@@ -395,6 +494,9 @@ class TaskRunner {
395
494
  if (config.updateProgress) {
396
495
  this.updateProgress = config.updateProgress;
397
496
  }
497
+ if (config.registry) {
498
+ this.registry = config.registry;
499
+ }
398
500
  this.task.emit("start");
399
501
  this.task.emit("status", this.task.status);
400
502
  }
@@ -421,7 +523,6 @@ class TaskRunner {
421
523
  this.task.progress = 100;
422
524
  this.task.status = TaskStatus.COMPLETED;
423
525
  this.abortController = undefined;
424
- this.nodeProvenance = {};
425
526
  this.task.emit("complete");
426
527
  this.task.emit("status", this.task.status);
427
528
  }
@@ -435,7 +536,6 @@ class TaskRunner {
435
536
  this.task.progress = 100;
436
537
  this.task.completedAt = new Date;
437
538
  this.abortController = undefined;
438
- this.nodeProvenance = {};
439
539
  this.task.emit("disabled");
440
540
  this.task.emit("status", this.task.status);
441
541
  }
@@ -455,7 +555,6 @@ class TaskRunner {
455
555
  this.task.status = TaskStatus.FAILED;
456
556
  this.task.error = err instanceof TaskError ? err : new TaskFailedError(err?.message || "Task failed");
457
557
  this.abortController = undefined;
458
- this.nodeProvenance = {};
459
558
  this.task.emit("error", this.task.error);
460
559
  this.task.emit("status", this.task.status);
461
560
  }
@@ -470,6 +569,8 @@ class TaskRunner {
470
569
  }
471
570
 
472
571
  // src/task/Task.ts
572
+ init_TaskTypes();
573
+
473
574
  class Task {
474
575
  static type = "Task";
475
576
  static category = "Hidden";
@@ -553,7 +654,6 @@ class Task {
553
654
  return this._events;
554
655
  }
555
656
  _events;
556
- nodeProvenance = {};
557
657
  constructor(callerDefaultInputs = {}, config = {}) {
558
658
  const inputDefaults = this.getDefaultInputsFromStaticInputDefinitions();
559
659
  const mergedDefaults = Object.assign(inputDefaults, callerDefaultInputs);
@@ -590,10 +690,45 @@ class Task {
590
690
  }
591
691
  }
592
692
  resetInputData() {
693
+ this.runInputData = this.smartClone(this.defaults);
694
+ }
695
+ smartClone(obj, visited = new WeakSet) {
696
+ if (obj === null || obj === undefined) {
697
+ return obj;
698
+ }
699
+ if (typeof obj !== "object") {
700
+ return obj;
701
+ }
702
+ if (visited.has(obj)) {
703
+ throw new Error("Circular reference detected in input data. " + "Cannot clone objects with circular references.");
704
+ }
705
+ if (ArrayBuffer.isView(obj)) {
706
+ if (typeof DataView !== "undefined" && obj instanceof DataView) {
707
+ return obj;
708
+ }
709
+ const typedArray = obj;
710
+ return new typedArray.constructor(typedArray);
711
+ }
712
+ if (!Array.isArray(obj)) {
713
+ const proto = Object.getPrototypeOf(obj);
714
+ if (proto !== Object.prototype && proto !== null) {
715
+ return obj;
716
+ }
717
+ }
718
+ visited.add(obj);
593
719
  try {
594
- this.runInputData = structuredClone(this.defaults);
595
- } catch (err) {
596
- this.runInputData = JSON.parse(JSON.stringify(this.defaults));
720
+ if (Array.isArray(obj)) {
721
+ return obj.map((item) => this.smartClone(item, visited));
722
+ }
723
+ const result = {};
724
+ for (const key in obj) {
725
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
726
+ result[key] = this.smartClone(obj[key], visited);
727
+ }
728
+ }
729
+ return result;
730
+ } finally {
731
+ visited.delete(obj);
597
732
  }
598
733
  }
599
734
  setDefaults(defaults) {
@@ -621,7 +756,7 @@ class Task {
621
756
  }
622
757
  if (schema.additionalProperties === true) {
623
758
  for (const [inputId, value] of Object.entries(input)) {
624
- if (value !== undefined && !(inputId in properties)) {
759
+ if (!(inputId in properties)) {
625
760
  this.runInputData[inputId] = value;
626
761
  }
627
762
  }
@@ -674,7 +809,7 @@ class Task {
674
809
  }
675
810
  if (inputSchema.additionalProperties === true) {
676
811
  for (const [inputId, value] of Object.entries(overrides)) {
677
- if (value !== undefined && !(inputId in properties)) {
812
+ if (!(inputId in properties)) {
678
813
  if (!deepEqual(this.runInputData[inputId], value)) {
679
814
  this.runInputData[inputId] = value;
680
815
  changed = true;
@@ -684,7 +819,7 @@ class Task {
684
819
  }
685
820
  return changed;
686
821
  }
687
- async narrowInput(input) {
822
+ async narrowInput(input, _registry) {
688
823
  return input;
689
824
  }
690
825
  subscribe(name, fn) {
@@ -744,20 +879,20 @@ class Task {
744
879
  const path = e.data.pointer || "";
745
880
  return `${e.message}${path ? ` (${path})` : ""}`;
746
881
  });
747
- throw new TaskInvalidInputError(`Input ${JSON.stringify(input)} does not match schema: ${errorMessages.join(", ")}`);
882
+ throw new TaskInvalidInputError(`Input ${JSON.stringify(Object.keys(input))} does not match schema: ${errorMessages.join(", ")}`);
748
883
  }
749
884
  return true;
750
885
  }
751
886
  id() {
752
887
  return this.config.id;
753
888
  }
754
- getProvenance() {
755
- return this.config.provenance ?? {};
756
- }
757
889
  stripSymbols(obj) {
758
890
  if (obj === null || obj === undefined) {
759
891
  return obj;
760
892
  }
893
+ if (ArrayBuffer.isView(obj)) {
894
+ return obj;
895
+ }
761
896
  if (Array.isArray(obj)) {
762
897
  return obj.map((item) => this.stripSymbols(item));
763
898
  }
@@ -773,14 +908,12 @@ class Task {
773
908
  return obj;
774
909
  }
775
910
  toJSON() {
776
- const provenance = this.getProvenance();
777
911
  const extras = this.config.extras;
778
912
  let json = this.stripSymbols({
779
913
  id: this.config.id,
780
914
  type: this.type,
781
915
  ...this.config.name ? { name: this.config.name } : {},
782
916
  defaults: this.defaults,
783
- ...Object.keys(provenance).length ? { provenance } : {},
784
917
  ...extras && Object.keys(extras).length ? { extras } : {}
785
918
  });
786
919
  return json;
@@ -826,8 +959,9 @@ class Task {
826
959
  // src/task/ConditionalTask.ts
827
960
  class ConditionalTask extends Task {
828
961
  static type = "ConditionalTask";
829
- static category = "Flow Control";
962
+ static category = "Hidden";
830
963
  static title = "Conditional Task";
964
+ static description = "Evaluates conditions to determine which dataflows are active";
831
965
  static hasDynamicSchemas = true;
832
966
  activeBranches = new Set;
833
967
  async execute(input, context) {
@@ -935,7 +1069,13 @@ class ConditionalTask extends Task {
935
1069
  }
936
1070
  }
937
1071
 
1072
+ // src/task-graph/TaskGraphRunner.ts
1073
+ init_TaskTypes();
1074
+ init_Dataflow();
1075
+
938
1076
  // src/task-graph/TaskGraphScheduler.ts
1077
+ init_TaskTypes();
1078
+
939
1079
  class TopologicalScheduler {
940
1080
  dag;
941
1081
  sortedNodes;
@@ -1052,9 +1192,9 @@ class TaskGraphRunner {
1052
1192
  reactiveScheduler;
1053
1193
  running = false;
1054
1194
  reactiveRunning = false;
1055
- provenanceInput;
1056
1195
  graph;
1057
1196
  outputCache;
1197
+ registry = globalServiceRegistry2;
1058
1198
  abortController;
1059
1199
  inProgressTasks = new Map;
1060
1200
  inProgressFunctions = new Map;
@@ -1063,7 +1203,6 @@ class TaskGraphRunner {
1063
1203
  this.processScheduler = processScheduler;
1064
1204
  this.reactiveScheduler = reactiveScheduler;
1065
1205
  this.graph = graph;
1066
- this.provenanceInput = new Map;
1067
1206
  graph.outputCache = outputCache;
1068
1207
  this.handleProgress = this.handleProgress.bind(this);
1069
1208
  }
@@ -1083,7 +1222,7 @@ class TaskGraphRunner {
1083
1222
  const runAsync = async () => {
1084
1223
  try {
1085
1224
  const taskInput = isRootTask ? input : this.filterInputForTask(task, input);
1086
- const taskPromise = this.runTaskWithProvenance(task, taskInput, config?.parentProvenance || {});
1225
+ const taskPromise = this.runTask(task, taskInput);
1087
1226
  this.inProgressTasks.set(task.config.id, taskPromise);
1088
1227
  const taskResult = await taskPromise;
1089
1228
  if (this.graph.getTargetDataflows(task.config.id).length === 0) {
@@ -1193,23 +1332,16 @@ class TaskGraphRunner {
1193
1332
  this.addInputData(task, dataflow.getPortData());
1194
1333
  }
1195
1334
  }
1196
- getInputProvenance(node) {
1197
- const nodeProvenance = {};
1198
- this.graph.getSourceDataflows(node.config.id).forEach((dataflow) => {
1199
- Object.assign(nodeProvenance, dataflow.provenance);
1200
- });
1201
- return nodeProvenance;
1202
- }
1203
- async pushOutputFromNodeToEdges(node, results, nodeProvenance) {
1335
+ async pushOutputFromNodeToEdges(node, results) {
1204
1336
  const dataflows = this.graph.getTargetDataflows(node.config.id);
1205
1337
  for (const dataflow of dataflows) {
1206
1338
  const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow);
1207
1339
  if (compatibility === "static") {
1208
- dataflow.setPortData(results, nodeProvenance);
1340
+ dataflow.setPortData(results);
1209
1341
  } else if (compatibility === "runtime") {
1210
1342
  const task = this.graph.getTask(dataflow.targetTaskId);
1211
- const narrowed = await task.narrowInput({ ...results });
1212
- dataflow.setPortData(narrowed, nodeProvenance);
1343
+ const narrowed = await task.narrowInput({ ...results }, this.registry);
1344
+ dataflow.setPortData(narrowed);
1213
1345
  } else {}
1214
1346
  }
1215
1347
  }
@@ -1279,20 +1411,14 @@ class TaskGraphRunner {
1279
1411
  }
1280
1412
  }
1281
1413
  }
1282
- async runTaskWithProvenance(task, input, parentProvenance) {
1283
- const nodeProvenance = {
1284
- ...parentProvenance,
1285
- ...this.getInputProvenance(task),
1286
- ...task.getProvenance()
1287
- };
1288
- this.provenanceInput.set(task.config.id, nodeProvenance);
1414
+ async runTask(task, input) {
1289
1415
  this.copyInputFromEdgesToNode(task);
1290
1416
  const results = await task.runner.run(input, {
1291
- nodeProvenance,
1292
1417
  outputCache: this.outputCache,
1293
- updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args)
1418
+ updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args),
1419
+ registry: this.registry
1294
1420
  });
1295
- await this.pushOutputFromNodeToEdges(task, results, nodeProvenance);
1421
+ await this.pushOutputFromNodeToEdges(task, results);
1296
1422
  return {
1297
1423
  id: task.config.id,
1298
1424
  type: task.constructor.runtype || task.constructor.type,
@@ -1326,10 +1452,15 @@ class TaskGraphRunner {
1326
1452
  });
1327
1453
  }
1328
1454
  async handleStart(config) {
1455
+ if (config?.registry !== undefined) {
1456
+ this.registry = config.registry;
1457
+ } else {
1458
+ this.registry = new ServiceRegistry2(globalServiceRegistry2.container.createChildContainer());
1459
+ }
1329
1460
  if (config?.outputCache !== undefined) {
1330
1461
  if (typeof config.outputCache === "boolean") {
1331
1462
  if (config.outputCache === true) {
1332
- this.outputCache = globalServiceRegistry2.get(TASK_OUTPUT_REPOSITORY);
1463
+ this.outputCache = this.registry.get(TASK_OUTPUT_REPOSITORY);
1333
1464
  } else {
1334
1465
  this.outputCache = undefined;
1335
1466
  }
@@ -1415,7 +1546,7 @@ class TaskGraphRunner {
1415
1546
  progress = Math.round(completed / total);
1416
1547
  }
1417
1548
  this.pushStatusFromNodeToEdges(this.graph, task);
1418
- await this.pushOutputFromNodeToEdges(task, task.runOutputData, task.getProvenance());
1549
+ await this.pushOutputFromNodeToEdges(task, task.runOutputData);
1419
1550
  this.graph.emit("graph_progress", progress, message, args);
1420
1551
  }
1421
1552
  }
@@ -1427,7 +1558,6 @@ class GraphAsTaskRunner extends TaskRunner {
1427
1558
  this.task.emit("progress", progress, message, ...args);
1428
1559
  });
1429
1560
  const results = await this.task.subGraph.run(input, {
1430
- parentProvenance: this.nodeProvenance || {},
1431
1561
  parentSignal: this.abortController?.signal,
1432
1562
  outputCache: this.outputCache
1433
1563
  });
@@ -1443,27 +1573,12 @@ class GraphAsTaskRunner extends TaskRunner {
1443
1573
  }
1444
1574
  super.handleDisable();
1445
1575
  }
1446
- fixInput(input) {
1447
- const inputSchema = this.task.inputSchema();
1448
- if (typeof inputSchema === "boolean") {
1449
- return input;
1450
- }
1451
- const flattenedInput = Object.entries(input).reduce((acc, [key, value]) => {
1452
- const inputDef = inputSchema.properties?.[key];
1453
- const shouldFlatten = Array.isArray(value) && typeof inputDef === "object" && inputDef !== null && "x-replicate" in inputDef && inputDef["x-replicate"] === true;
1454
- if (shouldFlatten) {
1455
- return { ...acc, [key]: value[0] };
1456
- }
1457
- return { ...acc, [key]: value };
1458
- }, {});
1459
- return flattenedInput;
1460
- }
1461
1576
  async executeTask(input) {
1462
1577
  if (this.task.hasChildren()) {
1463
1578
  const runExecuteOutputData = await this.executeTaskChildren(input);
1464
1579
  this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(runExecuteOutputData, this.task.compoundMerge);
1465
1580
  } else {
1466
- const result = await super.executeTask(this.fixInput(input));
1581
+ const result = await super.executeTask(input);
1467
1582
  this.task.runOutputData = result ?? {};
1468
1583
  }
1469
1584
  return this.task.runOutputData;
@@ -1473,7 +1588,7 @@ class GraphAsTaskRunner extends TaskRunner {
1473
1588
  const reactiveResults = await this.executeTaskChildrenReactive();
1474
1589
  this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(reactiveResults, this.task.compoundMerge);
1475
1590
  } else {
1476
- const reactiveResults = await super.executeTaskReactive(this.fixInput(input), output);
1591
+ const reactiveResults = await super.executeTaskReactive(input, output);
1477
1592
  this.task.runOutputData = Object.assign({}, output, reactiveResults ?? {});
1478
1593
  }
1479
1594
  return this.task.runOutputData;
@@ -1483,6 +1598,8 @@ class GraphAsTaskRunner extends TaskRunner {
1483
1598
  // src/task/GraphAsTask.ts
1484
1599
  class GraphAsTask extends Task {
1485
1600
  static type = "GraphAsTask";
1601
+ static title = "Graph as Task";
1602
+ static description = "A task that contains a subgraph of tasks";
1486
1603
  static category = "Hidden";
1487
1604
  static compoundMerge = PROPERTY_ARRAY;
1488
1605
  static hasDynamicSchemas = true;
@@ -1662,35 +1779,70 @@ class GraphAsTask extends Task {
1662
1779
  }
1663
1780
  }
1664
1781
 
1782
+ // src/task-graph/Conversions.ts
1783
+ init_Dataflow();
1784
+
1665
1785
  // src/task-graph/Workflow.ts
1666
- 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
+
1667
1811
  class WorkflowTask extends GraphAsTask {
1668
1812
  static type = "Workflow";
1669
1813
  static compoundMerge = PROPERTY_ARRAY;
1670
1814
  }
1671
- var taskIdCounter = 0;
1672
1815
 
1673
1816
  class Workflow {
1674
- constructor(repository) {
1675
- this._repository = repository;
1676
- this._graph = new TaskGraph({
1677
- outputCache: this._repository
1678
- });
1679
- this._onChanged = this._onChanged.bind(this);
1680
- 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
+ }
1681
1826
  }
1682
1827
  _graph;
1683
1828
  _dataFlows = [];
1684
1829
  _error = "";
1685
- _repository;
1830
+ _outputCache;
1686
1831
  _abortController;
1832
+ _parentWorkflow;
1833
+ _iteratorTask;
1834
+ outputCache() {
1835
+ return this._outputCache;
1836
+ }
1837
+ get isLoopBuilder() {
1838
+ return this._parentWorkflow !== undefined;
1839
+ }
1687
1840
  events = new EventEmitter4;
1688
1841
  static createWorkflow(taskClass) {
1689
1842
  const helper = function(input = {}, config = {}) {
1690
1843
  this._error = "";
1691
1844
  const parent = getLastTask(this);
1692
- taskIdCounter++;
1693
- const task = this.addTask(taskClass, input, { id: String(taskIdCounter), ...config });
1845
+ const task = this.addTaskToGraph(taskClass, input, { id: uuid43(), ...config });
1694
1846
  if (this._dataFlows.length > 0) {
1695
1847
  this._dataFlows.forEach((dataflow) => {
1696
1848
  const taskSchema = task.inputSchema();
@@ -1705,42 +1857,26 @@ class Workflow {
1705
1857
  this._dataFlows = [];
1706
1858
  }
1707
1859
  if (parent && this.graph.getTargetDataflows(parent.config.id).length === 0) {
1708
- const matches = new Map;
1709
- const sourceSchema = parent.outputSchema();
1710
- const targetSchema = task.inputSchema();
1711
- const makeMatch = (comparator) => {
1712
- if (typeof sourceSchema === "boolean" || typeof targetSchema === "boolean") {
1713
- return matches;
1714
- }
1715
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(sourceSchema.properties || {})) {
1716
- for (const [toInputPortId, toPortInputSchema] of Object.entries(targetSchema.properties || {})) {
1717
- if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
1718
- matches.set(toInputPortId, fromOutputPortId);
1719
- this.connect(parent.config.id, fromOutputPortId, task.config.id, toInputPortId);
1720
- }
1721
- }
1722
- }
1723
- return matches;
1724
- };
1725
- makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1726
- if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
1727
- if (fromPortOutputSchema === true && toPortInputSchema === true) {
1728
- return true;
1729
- }
1730
- return false;
1731
- }
1732
- const idTypeMatch = fromPortOutputSchema.$id !== undefined && fromPortOutputSchema.$id === toPortInputSchema.$id;
1733
- const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
1734
- const typeMatch = idTypeBlank && (fromPortOutputSchema.type === toPortInputSchema.type || (toPortInputSchema.oneOf?.some((i) => i.type == fromPortOutputSchema.type) ?? false));
1735
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
1736
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1737
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1738
- return (idTypeMatch || typeMatch) && portIdsCompatible;
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]);
1865
+ }
1866
+ const providedInputKeys = new Set(Object.keys(input || {}));
1867
+ const result = Workflow.autoConnect(this.graph, parent, task, {
1868
+ providedInputKeys,
1869
+ earlierTasks
1739
1870
  });
1740
- if (matches.size === 0) {
1741
- 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.`;
1742
- console.error(this._error);
1743
- this.graph.removeTask(task.config.id);
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.";
1877
+ console.error(this._error);
1878
+ this.graph.removeTask(task.config.id);
1879
+ }
1744
1880
  }
1745
1881
  }
1746
1882
  return this;
@@ -1780,13 +1916,16 @@ class Workflow {
1780
1916
  return this.events.waitOn(name);
1781
1917
  }
1782
1918
  async run(input = {}) {
1919
+ if (this.isLoopBuilder) {
1920
+ this.finalizeTemplate();
1921
+ return this._parentWorkflow.run(input);
1922
+ }
1783
1923
  this.events.emit("start");
1784
1924
  this._abortController = new AbortController;
1785
1925
  try {
1786
1926
  const output = await this.graph.run(input, {
1787
1927
  parentSignal: this._abortController.signal,
1788
- parentProvenance: {},
1789
- outputCache: this._repository
1928
+ outputCache: this._outputCache
1790
1929
  });
1791
1930
  const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
1792
1931
  this.events.emit("complete");
@@ -1799,6 +1938,9 @@ class Workflow {
1799
1938
  }
1800
1939
  }
1801
1940
  async abort() {
1941
+ if (this._parentWorkflow) {
1942
+ return this._parentWorkflow.abort();
1943
+ }
1802
1944
  this._abortController?.abort();
1803
1945
  }
1804
1946
  pop() {
@@ -1867,10 +2009,12 @@ class Workflow {
1867
2009
  return task;
1868
2010
  }
1869
2011
  reset() {
1870
- taskIdCounter = 0;
2012
+ if (this._parentWorkflow) {
2013
+ throw new WorkflowError("Cannot reset a loop workflow. Call reset() on the parent workflow.");
2014
+ }
1871
2015
  this.clearEvents();
1872
2016
  this._graph = new TaskGraph({
1873
- outputCache: this._repository
2017
+ outputCache: this._outputCache
1874
2018
  });
1875
2019
  this._dataFlows = [];
1876
2020
  this._error = "";
@@ -1917,22 +2061,202 @@ class Workflow {
1917
2061
  if (targetSchema === false) {
1918
2062
  throw new WorkflowError(`Target task has schema 'false' and accepts no inputs`);
1919
2063
  }
1920
- } else if (!targetSchema.properties?.[targetTaskPortId]) {
2064
+ if (targetSchema === true) {}
2065
+ } else if (targetSchema.additionalProperties === true) {} else if (!targetSchema.properties?.[targetTaskPortId]) {
1921
2066
  throw new WorkflowError(`Input ${targetTaskPortId} not found on target task`);
1922
2067
  }
1923
2068
  const dataflow = new Dataflow(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
1924
2069
  this.graph.addDataflow(dataflow);
1925
2070
  return this;
1926
2071
  }
1927
- addTask(taskClass, input, config) {
2072
+ addTaskToGraph(taskClass, input, config) {
1928
2073
  const task = new taskClass(input, config);
1929
2074
  const id = this.graph.addTask(task);
1930
2075
  this.events.emit("changed", id);
1931
2076
  return task;
1932
2077
  }
1933
- }
1934
- function CreateWorkflow(taskClass) {
1935
- 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
+ }
1936
2260
  }
1937
2261
 
1938
2262
  // src/task-graph/Conversions.ts
@@ -2055,6 +2379,9 @@ function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
2055
2379
  return workflow;
2056
2380
  }
2057
2381
 
2382
+ // src/task-graph/TaskGraph.ts
2383
+ init_Dataflow();
2384
+
2058
2385
  // src/task-graph/TaskGraphEvents.ts
2059
2386
  var EventDagToTaskGraphMapping = {
2060
2387
  "node-added": "task_added",
@@ -2097,7 +2424,6 @@ class TaskGraph {
2097
2424
  run(input = {}, config = {}) {
2098
2425
  return this.runner.runGraph(input, {
2099
2426
  outputCache: config?.outputCache || this.outputCache,
2100
- parentProvenance: config?.parentProvenance || {},
2101
2427
  parentSignal: config?.parentSignal || undefined
2102
2428
  });
2103
2429
  }
@@ -2173,7 +2499,7 @@ class TaskGraph {
2173
2499
  return this._dag.removeNode(taskId);
2174
2500
  }
2175
2501
  resetGraph() {
2176
- this.runner.resetGraph(this, uuid43());
2502
+ this.runner.resetGraph(this, uuid44());
2177
2503
  }
2178
2504
  toJSON() {
2179
2505
  const tasks = this.getTasks().map((node) => node.toJSON());
@@ -2338,117 +2664,456 @@ function serialGraph(tasks, inputHandle, outputHandle) {
2338
2664
  graph.addDataflows(serialGraphEdges(tasks, inputHandle, outputHandle));
2339
2665
  return graph;
2340
2666
  }
2667
+ // src/task/IteratorTaskRunner.ts
2668
+ import { Job, JobQueueClient, JobQueueServer } from "@workglow/job-queue";
2669
+ import { InMemoryQueueStorage } from "@workglow/storage";
2670
+ import { uuid4 as uuid45 } from "@workglow/util";
2341
2671
 
2342
- // src/task/ArrayTask.ts
2343
- class ArrayTask extends GraphAsTask {
2344
- static type = "ArrayTask";
2345
- static compoundMerge = PROPERTY_ARRAY;
2346
- inputSchema() {
2347
- return this.constructor.inputSchema();
2348
- }
2349
- outputSchema() {
2350
- return this.constructor.outputSchema();
2351
- }
2352
- regenerateGraph() {
2353
- const arrayInputs = new Map;
2354
- let hasArrayInputs = false;
2355
- const inputSchema = this.inputSchema();
2356
- if (typeof inputSchema !== "boolean") {
2357
- const keys = Object.keys(inputSchema.properties || {});
2358
- for (const inputId of keys) {
2359
- const inputValue = this.runInputData[inputId];
2360
- const inputDef = inputSchema.properties?.[inputId];
2361
- if (typeof inputDef === "object" && inputDef !== null && "x-replicate" in inputDef && inputDef["x-replicate"] === true && Array.isArray(inputValue) && inputValue.length > 1) {
2362
- arrayInputs.set(inputId, inputValue);
2363
- hasArrayInputs = true;
2364
- }
2365
- }
2366
- }
2367
- this.subGraph = new TaskGraph;
2368
- if (!hasArrayInputs) {
2369
- super.regenerateGraph();
2370
- return;
2672
+ // src/task/TaskQueueRegistry.ts
2673
+ var taskQueueRegistry = null;
2674
+
2675
+ class TaskQueueRegistry {
2676
+ queues = new Map;
2677
+ registerQueue(queue) {
2678
+ const queueName = queue.server.queueName;
2679
+ if (this.queues.has(queueName)) {
2680
+ throw new Error(`Queue with name ${queueName} already exists`);
2371
2681
  }
2372
- const inputIds = Array.from(arrayInputs.keys());
2373
- const inputObject = Object.fromEntries(arrayInputs);
2374
- const combinations = this.generateCombinations(inputObject, inputIds);
2375
- const tasks = combinations.map((combination) => {
2376
- const { id, name, ...rest } = this.config;
2377
- const task = new this.constructor({ ...this.defaults, ...this.runInputData, ...combination }, { ...rest, id: `${id}_${uuid44()}` });
2378
- return task;
2379
- });
2380
- this.subGraph.addTasks(tasks);
2381
- super.regenerateGraph();
2682
+ this.queues.set(queueName, queue);
2382
2683
  }
2383
- generateCombinations(input, inputMakeArray) {
2384
- const arraysToCombine = inputMakeArray.map((key) => Array.isArray(input[key]) ? input[key] : []);
2385
- const indices = arraysToCombine.map(() => 0);
2386
- const combinations = [];
2387
- let done = false;
2388
- while (!done) {
2389
- combinations.push([...indices]);
2390
- for (let i = indices.length - 1;i >= 0; i--) {
2391
- if (++indices[i] < arraysToCombine[i].length)
2392
- break;
2393
- if (i === 0)
2394
- done = true;
2395
- else
2396
- indices[i] = 0;
2397
- }
2398
- }
2399
- const combos = combinations.map((combination) => {
2400
- const result = { ...input };
2401
- combination.forEach((valueIndex, arrayIndex) => {
2402
- const key = inputMakeArray[arrayIndex];
2403
- if (Array.isArray(input[key]))
2404
- result[key] = input[key][valueIndex];
2405
- });
2406
- return result;
2407
- });
2408
- return combos;
2684
+ getQueue(queueName) {
2685
+ return this.queues.get(queueName);
2409
2686
  }
2410
- toJSON() {
2411
- const { subgraph, ...result } = super.toJSON();
2412
- return result;
2687
+ startQueues() {
2688
+ for (const queue of this.queues.values()) {
2689
+ queue.server.start();
2690
+ }
2691
+ return this;
2413
2692
  }
2414
- toDependencyJSON() {
2415
- const { subtasks, ...result } = super.toDependencyJSON();
2416
- return result;
2693
+ stopQueues() {
2694
+ for (const queue of this.queues.values()) {
2695
+ queue.server.stop();
2696
+ }
2697
+ return this;
2417
2698
  }
2418
- get runner() {
2419
- if (!this._runner) {
2420
- this._runner = new ArrayTaskRunner(this);
2699
+ clearQueues() {
2700
+ for (const queue of this.queues.values()) {
2701
+ queue.storage.deleteAll();
2421
2702
  }
2422
- return this._runner;
2703
+ return this;
2423
2704
  }
2424
2705
  }
2425
-
2426
- class ArrayTaskRunner extends GraphAsTaskRunner {
2427
- async executeTaskChildren(_input) {
2428
- return super.executeTaskChildren({});
2706
+ function getTaskQueueRegistry() {
2707
+ if (!taskQueueRegistry) {
2708
+ taskQueueRegistry = new TaskQueueRegistry;
2429
2709
  }
2430
- async executeTaskReactive(input, output) {
2431
- const reactiveResults = await super.executeTaskReactive(input, output);
2432
- if (this.task.hasChildren()) {
2433
- return reactiveResults;
2434
- } else {
2435
- this.task.runOutputData = Object.assign({}, output, reactiveResults ?? {});
2436
- return this.task.runOutputData;
2437
- }
2710
+ return taskQueueRegistry;
2711
+ }
2712
+ function setTaskQueueRegistry(registry) {
2713
+ if (taskQueueRegistry) {
2714
+ taskQueueRegistry.stopQueues();
2715
+ taskQueueRegistry.clearQueues();
2438
2716
  }
2717
+ taskQueueRegistry = registry;
2439
2718
  }
2440
- // src/task/InputTask.ts
2441
- import { Task as Task2 } from "@workglow/task-graph";
2442
2719
 
2443
- class InputTask extends Task2 {
2444
- static type = "InputTask";
2445
- static category = "Flow Control";
2446
- static title = "Input";
2447
- static description = "Starts the workflow";
2448
- static hasDynamicSchemas = true;
2449
- static cacheable = false;
2450
- static inputSchema() {
2451
- return {
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
+ };
2779
+ try {
2780
+ getTaskQueueRegistry().registerQueue(queue);
2781
+ } catch (err) {
2782
+ const existing = getTaskQueueRegistry().getQueue(queueName);
2783
+ if (existing) {
2784
+ return existing;
2785
+ }
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 {
2452
3117
  type: "object",
2453
3118
  properties: {},
2454
3119
  additionalProperties: true
@@ -2461,25 +3126,161 @@ class InputTask extends Task2 {
2461
3126
  additionalProperties: true
2462
3127
  };
2463
3128
  }
2464
- inputSchema() {
2465
- return this.config?.extras?.inputSchema ?? this.constructor.inputSchema();
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;
2466
3181
  }
2467
3182
  outputSchema() {
2468
- return this.config?.extras?.outputSchema ?? this.constructor.outputSchema();
3183
+ if (!this.hasChildren() && !this._templateGraph) {
3184
+ return this.constructor.outputSchema();
3185
+ }
3186
+ return this.getWrappedOutputSchema();
2469
3187
  }
2470
- async execute(input, _context) {
2471
- return input;
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;
2472
3202
  }
2473
- async executeReactive(input, _output, _context) {
2474
- return input;
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
+ };
2475
3274
  }
2476
3275
  }
3276
+ Workflow.prototype.forEach = CreateLoopWorkflow(ForEachTask);
3277
+ Workflow.prototype.endForEach = CreateEndLoopWorkflow("endForEach");
2477
3278
  // src/task/JobQueueFactory.ts
2478
3279
  import {
2479
- JobQueueClient,
2480
- JobQueueServer
3280
+ JobQueueClient as JobQueueClient2,
3281
+ JobQueueServer as JobQueueServer2
2481
3282
  } from "@workglow/job-queue";
2482
- import { InMemoryQueueStorage } from "@workglow/storage";
3283
+ import { InMemoryQueueStorage as InMemoryQueueStorage2 } from "@workglow/storage";
2483
3284
  import { createServiceToken as createServiceToken2, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
2484
3285
  var JOB_QUEUE_FACTORY = createServiceToken2("taskgraph.jobQueueFactory");
2485
3286
  var defaultJobQueueFactory = async ({
@@ -2487,9 +3288,9 @@ var defaultJobQueueFactory = async ({
2487
3288
  jobClass,
2488
3289
  options
2489
3290
  }) => {
2490
- const storage = options?.storage ?? new InMemoryQueueStorage(queueName);
3291
+ const storage = options?.storage ?? new InMemoryQueueStorage2(queueName);
2491
3292
  await storage.setupDatabase();
2492
- const server = new JobQueueServer(jobClass, {
3293
+ const server = new JobQueueServer2(jobClass, {
2493
3294
  storage,
2494
3295
  queueName,
2495
3296
  limiter: options?.limiter,
@@ -2500,7 +3301,7 @@ var defaultJobQueueFactory = async ({
2500
3301
  deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
2501
3302
  cleanupIntervalMs: options?.cleanupIntervalMs
2502
3303
  });
2503
- const client = new JobQueueClient({
3304
+ const client = new JobQueueClient2({
2504
3305
  storage,
2505
3306
  queueName
2506
3307
  });
@@ -2520,9 +3321,9 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
2520
3321
  ...defaultOptions,
2521
3322
  ...options ?? {}
2522
3323
  };
2523
- const storage = mergedOptions.storage ?? new InMemoryQueueStorage(queueName);
3324
+ const storage = mergedOptions.storage ?? new InMemoryQueueStorage2(queueName);
2524
3325
  await storage.setupDatabase();
2525
- const server = new JobQueueServer(jobClass, {
3326
+ const server = new JobQueueServer2(jobClass, {
2526
3327
  storage,
2527
3328
  queueName,
2528
3329
  limiter: mergedOptions.limiter,
@@ -2533,7 +3334,7 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
2533
3334
  deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
2534
3335
  cleanupIntervalMs: mergedOptions.cleanupIntervalMs
2535
3336
  });
2536
- const client = new JobQueueClient({
3337
+ const client = new JobQueueClient2({
2537
3338
  storage,
2538
3339
  queueName
2539
3340
  });
@@ -2551,58 +3352,8 @@ if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
2551
3352
  registerJobQueueFactory(defaultJobQueueFactory);
2552
3353
  }
2553
3354
  // src/task/JobQueueTask.ts
2554
- import { Job as Job2 } from "@workglow/job-queue";
2555
-
2556
- // src/task/TaskQueueRegistry.ts
2557
- var taskQueueRegistry = null;
2558
-
2559
- class TaskQueueRegistry {
2560
- queues = new Map;
2561
- registerQueue(queue) {
2562
- const queueName = queue.server.queueName;
2563
- if (this.queues.has(queueName)) {
2564
- throw new Error(`Queue with name ${queueName} already exists`);
2565
- }
2566
- this.queues.set(queueName, queue);
2567
- }
2568
- getQueue(queueName) {
2569
- return this.queues.get(queueName);
2570
- }
2571
- startQueues() {
2572
- for (const queue of this.queues.values()) {
2573
- queue.server.start();
2574
- }
2575
- return this;
2576
- }
2577
- stopQueues() {
2578
- for (const queue of this.queues.values()) {
2579
- queue.server.stop();
2580
- }
2581
- return this;
2582
- }
2583
- clearQueues() {
2584
- for (const queue of this.queues.values()) {
2585
- queue.storage.deleteAll();
2586
- }
2587
- return this;
2588
- }
2589
- }
2590
- function getTaskQueueRegistry() {
2591
- if (!taskQueueRegistry) {
2592
- taskQueueRegistry = new TaskQueueRegistry;
2593
- }
2594
- return taskQueueRegistry;
2595
- }
2596
- function setTaskQueueRegistry(registry) {
2597
- if (taskQueueRegistry) {
2598
- taskQueueRegistry.stopQueues();
2599
- taskQueueRegistry.clearQueues();
2600
- }
2601
- taskQueueRegistry = registry;
2602
- }
2603
-
2604
- // src/task/JobQueueTask.ts
2605
- class JobQueueTask extends ArrayTask {
3355
+ import { Job as Job3 } from "@workglow/job-queue";
3356
+ class JobQueueTask extends GraphAsTask {
2606
3357
  static type = "JobQueueTask";
2607
3358
  static canRunDirectly = true;
2608
3359
  currentQueueName;
@@ -2612,7 +3363,7 @@ class JobQueueTask extends ArrayTask {
2612
3363
  constructor(input = {}, config = {}) {
2613
3364
  config.queue ??= true;
2614
3365
  super(input, config);
2615
- this.jobClass = Job2;
3366
+ this.jobClass = Job3;
2616
3367
  }
2617
3368
  async execute(input, executeContext) {
2618
3369
  let cleanup = () => {};
@@ -2734,16 +3485,13 @@ class JobQueueTask extends ArrayTask {
2734
3485
  super.abort();
2735
3486
  }
2736
3487
  }
2737
- // src/task/OutputTask.ts
2738
- import { Task as Task3 } from "@workglow/task-graph";
2739
-
2740
- class OutputTask extends Task3 {
2741
- static type = "OutputTask";
3488
+ // src/task/MapTask.ts
3489
+ class MapTask extends IteratorTask {
3490
+ static type = "MapTask";
2742
3491
  static category = "Flow Control";
2743
- static title = "Output";
2744
- static description = "Ends the workflow";
2745
- static hasDynamicSchemas = true;
2746
- static cacheable = false;
3492
+ static title = "Map";
3493
+ static description = "Transforms an array by running a workflow for each element";
3494
+ static compoundMerge = PROPERTY_ARRAY;
2747
3495
  static inputSchema() {
2748
3496
  return {
2749
3497
  type: "object",
@@ -2758,19 +3506,198 @@ class OutputTask extends Task3 {
2758
3506
  additionalProperties: true
2759
3507
  };
2760
3508
  }
2761
- inputSchema() {
2762
- return this.config?.extras?.inputSchema ?? this.constructor.inputSchema();
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;
2763
3525
  }
2764
3526
  outputSchema() {
2765
- return this.config?.extras?.outputSchema ?? this.constructor.outputSchema();
3527
+ if (!this.hasChildren() && !this._templateGraph) {
3528
+ return this.constructor.outputSchema();
3529
+ }
3530
+ return this.getWrappedOutputSchema();
2766
3531
  }
2767
- async execute(input, _context) {
2768
- return input;
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;
2769
3546
  }
2770
- async executeReactive(input, _output, _context) {
2771
- return input;
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");
2772
3694
  }
2773
3695
  }
3696
+ Workflow.prototype.reduce = CreateLoopWorkflow(ReduceTask);
3697
+ Workflow.prototype.endReduce = CreateEndLoopWorkflow("endReduce");
3698
+ // src/task/TaskJSON.ts
3699
+ init_Dataflow();
3700
+
2774
3701
  // src/task/TaskRegistry.ts
2775
3702
  var taskConstructors = new Map;
2776
3703
  function registerTask(baseClass) {
@@ -2788,17 +3715,14 @@ var createSingleTaskFromJSON = (item) => {
2788
3715
  throw new TaskJSONError("Task id required");
2789
3716
  if (!item.type)
2790
3717
  throw new TaskJSONError("Task type required");
2791
- if (item.defaults && (Array.isArray(item.defaults) || Array.isArray(item.provenance)))
3718
+ if (item.defaults && Array.isArray(item.defaults))
2792
3719
  throw new TaskJSONError("Task defaults must be an object");
2793
- if (item.provenance && (Array.isArray(item.provenance) || typeof item.provenance !== "object"))
2794
- throw new TaskJSONError("Task provenance must be an object");
2795
3720
  const taskClass = TaskRegistry.all.get(item.type);
2796
3721
  if (!taskClass)
2797
3722
  throw new TaskJSONError(`Task type ${item.type} not found, perhaps not registered?`);
2798
3723
  const taskConfig = {
2799
3724
  id: item.id,
2800
3725
  name: item.name,
2801
- provenance: item.provenance ?? {},
2802
3726
  extras: item.extras
2803
3727
  };
2804
3728
  const task = new taskClass(item.defaults ?? {}, taskConfig);
@@ -2841,6 +3765,170 @@ var createGraphFromGraphJSON = (graphJsonObj) => {
2841
3765
  }
2842
3766
  return subGraph;
2843
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");
3918
+ // src/task/index.ts
3919
+ var registerBaseTasks = () => {
3920
+ const tasks = [
3921
+ ConditionalTask,
3922
+ GraphAsTask,
3923
+ ForEachTask,
3924
+ MapTask,
3925
+ BatchTask,
3926
+ WhileTask,
3927
+ ReduceTask
3928
+ ];
3929
+ tasks.map(TaskRegistry.registerTask);
3930
+ return tasks;
3931
+ };
2844
3932
  // src/storage/TaskGraphRepository.ts
2845
3933
  import { createServiceToken as createServiceToken3, EventEmitter as EventEmitter6 } from "@workglow/util";
2846
3934
  var TASK_GRAPH_REPOSITORY = createServiceToken3("taskgraph.taskGraphRepository");
@@ -3000,7 +4088,9 @@ class TaskOutputTabularRepository extends TaskOutputRepository {
3000
4088
  export {
3001
4089
  setTaskQueueRegistry,
3002
4090
  serialGraph,
4091
+ resolveSchemaInputs,
3003
4092
  registerJobQueueFactory,
4093
+ registerBaseTasks,
3004
4094
  pipe,
3005
4095
  parallel,
3006
4096
  getTaskQueueRegistry,
@@ -3015,6 +4105,7 @@ export {
3015
4105
  connect,
3016
4106
  WorkflowError,
3017
4107
  Workflow,
4108
+ WhileTask,
3018
4109
  TaskStatus,
3019
4110
  TaskRegistry,
3020
4111
  TaskQueueRegistry,
@@ -3037,15 +4128,18 @@ export {
3037
4128
  Task,
3038
4129
  TASK_OUTPUT_REPOSITORY,
3039
4130
  TASK_GRAPH_REPOSITORY,
4131
+ ReduceTask,
3040
4132
  PROPERTY_ARRAY,
3041
- OutputTask,
4133
+ MapTask,
3042
4134
  JobTaskFailedError,
3043
4135
  JobQueueTask,
3044
4136
  JOB_QUEUE_FACTORY,
3045
- InputTask,
4137
+ IteratorTaskRunner,
4138
+ IteratorTask,
3046
4139
  GraphAsTaskRunner,
3047
4140
  GraphAsTask,
3048
4141
  GRAPH_RESULT_ARRAY,
4142
+ ForEachTask,
3049
4143
  EventTaskGraphToDagMapping,
3050
4144
  EventDagToTaskGraphMapping,
3051
4145
  DataflowArrow,
@@ -3053,8 +4147,10 @@ export {
3053
4147
  DATAFLOW_ERROR_PORT,
3054
4148
  DATAFLOW_ALL_PORTS,
3055
4149
  CreateWorkflow,
4150
+ CreateLoopWorkflow,
4151
+ CreateEndLoopWorkflow,
3056
4152
  ConditionalTask,
3057
- ArrayTask
4153
+ BatchTask
3058
4154
  };
3059
4155
 
3060
- //# debugId=E59B822A237BFEFE64756E2164756E21
4156
+ //# debugId=BB6441A3D3B3FDA564756E2164756E21