@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/bun.js CHANGED
@@ -1,29 +1,55 @@
1
1
  // @bun
2
- // src/task/ArrayTask.ts
3
- import { uuid4 as uuid44 } from "@workglow/util";
4
-
5
- // src/task-graph/TaskGraph.ts
6
- import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid43 } from "@workglow/util";
7
-
8
- // src/task/GraphAsTask.ts
9
- import { compileSchema as compileSchema2 } from "@workglow/util";
10
-
11
- // src/task-graph/Dataflow.ts
12
- import { areSemanticallyCompatible, EventEmitter } from "@workglow/util";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
13
30
 
14
31
  // src/task/TaskTypes.ts
15
- var TaskStatus = {
16
- PENDING: "PENDING",
17
- DISABLED: "DISABLED",
18
- PROCESSING: "PROCESSING",
19
- COMPLETED: "COMPLETED",
20
- ABORTING: "ABORTING",
21
- FAILED: "FAILED"
22
- };
32
+ var TaskStatus;
33
+ var init_TaskTypes = __esm(() => {
34
+ TaskStatus = {
35
+ PENDING: "PENDING",
36
+ DISABLED: "DISABLED",
37
+ PROCESSING: "PROCESSING",
38
+ COMPLETED: "COMPLETED",
39
+ ABORTING: "ABORTING",
40
+ FAILED: "FAILED"
41
+ };
42
+ });
23
43
 
24
44
  // src/task-graph/Dataflow.ts
25
- var DATAFLOW_ALL_PORTS = "*";
26
- var DATAFLOW_ERROR_PORT = "[error]";
45
+ var exports_Dataflow = {};
46
+ __export(exports_Dataflow, {
47
+ DataflowArrow: () => DataflowArrow,
48
+ Dataflow: () => Dataflow,
49
+ DATAFLOW_ERROR_PORT: () => DATAFLOW_ERROR_PORT,
50
+ DATAFLOW_ALL_PORTS: () => DATAFLOW_ALL_PORTS
51
+ });
52
+ import { areSemanticallyCompatible, EventEmitter } from "@workglow/util";
27
53
 
28
54
  class Dataflow {
29
55
  sourceTaskId;
@@ -43,14 +69,12 @@ class Dataflow {
43
69
  return Dataflow.createId(this.sourceTaskId, this.sourceTaskPortId, this.targetTaskId, this.targetTaskPortId);
44
70
  }
45
71
  value = undefined;
46
- provenance = {};
47
72
  status = TaskStatus.PENDING;
48
73
  error;
49
74
  reset() {
50
75
  this.status = TaskStatus.PENDING;
51
76
  this.error = undefined;
52
77
  this.value = undefined;
53
- this.provenance = {};
54
78
  this.emit("reset");
55
79
  this.emit("status", this.status);
56
80
  }
@@ -80,7 +104,7 @@ class Dataflow {
80
104
  }
81
105
  this.emit("status", this.status);
82
106
  }
83
- setPortData(entireDataBlock, nodeProvenance) {
107
+ setPortData(entireDataBlock) {
84
108
  if (this.sourceTaskPortId === DATAFLOW_ALL_PORTS) {
85
109
  this.value = entireDataBlock;
86
110
  } else if (this.sourceTaskPortId === DATAFLOW_ERROR_PORT) {
@@ -88,17 +112,17 @@ class Dataflow {
88
112
  } else {
89
113
  this.value = entireDataBlock[this.sourceTaskPortId];
90
114
  }
91
- if (nodeProvenance)
92
- this.provenance = nodeProvenance;
93
115
  }
94
116
  getPortData() {
117
+ let result;
95
118
  if (this.targetTaskPortId === DATAFLOW_ALL_PORTS) {
96
- return this.value;
119
+ result = this.value;
97
120
  } else if (this.targetTaskPortId === DATAFLOW_ERROR_PORT) {
98
- return { [DATAFLOW_ERROR_PORT]: this.error };
121
+ result = { [DATAFLOW_ERROR_PORT]: this.error };
99
122
  } else {
100
- return { [this.targetTaskPortId]: this.value };
123
+ result = { [this.targetTaskPortId]: this.value };
101
124
  }
125
+ return result;
102
126
  }
103
127
  toJSON() {
104
128
  return {
@@ -160,23 +184,37 @@ class Dataflow {
160
184
  this._events?.emit(name, ...args);
161
185
  }
162
186
  }
163
-
164
- class DataflowArrow extends Dataflow {
165
- constructor(dataflow) {
166
- const pattern = /^([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\] ==> ([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\]$/;
167
- const match = dataflow.match(pattern);
168
- if (!match) {
169
- throw new Error(`Invalid dataflow format: ${dataflow}`);
187
+ var DATAFLOW_ALL_PORTS = "*", DATAFLOW_ERROR_PORT = "[error]", DataflowArrow;
188
+ var init_Dataflow = __esm(() => {
189
+ init_TaskTypes();
190
+ DataflowArrow = class DataflowArrow extends Dataflow {
191
+ constructor(dataflow) {
192
+ const pattern = /^([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\] ==> ([a-zA-Z0-9-]+?)\[([a-zA-Z0-9-]+?)\]$/;
193
+ const match = dataflow.match(pattern);
194
+ if (!match) {
195
+ throw new Error(`Invalid dataflow format: ${dataflow}`);
196
+ }
197
+ const [, sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId] = match;
198
+ super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
170
199
  }
171
- const [, sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId] = match;
172
- super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
173
- }
174
- }
200
+ };
201
+ });
202
+
203
+ // src/common.ts
204
+ init_Dataflow();
205
+
206
+ // src/task-graph/TaskGraph.ts
207
+ import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid44 } from "@workglow/util";
208
+
209
+ // src/task/GraphAsTask.ts
210
+ init_Dataflow();
211
+ import { compileSchema as compileSchema2 } from "@workglow/util";
175
212
 
176
213
  // src/task-graph/TaskGraphRunner.ts
177
214
  import {
178
215
  collectPropertyValues,
179
216
  globalServiceRegistry as globalServiceRegistry2,
217
+ ServiceRegistry as ServiceRegistry2,
180
218
  uuid4 as uuid42
181
219
  } from "@workglow/util";
182
220
 
@@ -211,6 +249,7 @@ class TaskOutputRepository {
211
249
  }
212
250
 
213
251
  // src/task/Task.ts
252
+ init_Dataflow();
214
253
  import {
215
254
  compileSchema,
216
255
  deepEqual,
@@ -281,13 +320,71 @@ class TaskInvalidInputError extends TaskError {
281
320
 
282
321
  // src/task/TaskRunner.ts
283
322
  import { globalServiceRegistry } from "@workglow/util";
323
+
324
+ // src/task/InputResolver.ts
325
+ import { getInputResolvers } from "@workglow/util";
326
+ function getSchemaFormat(schema) {
327
+ if (typeof schema !== "object" || schema === null)
328
+ return;
329
+ const s = schema;
330
+ if (typeof s.format === "string")
331
+ return s.format;
332
+ const variants = s.oneOf ?? s.anyOf;
333
+ if (Array.isArray(variants)) {
334
+ for (const variant of variants) {
335
+ if (typeof variant === "object" && variant !== null) {
336
+ const v = variant;
337
+ if (typeof v.format === "string")
338
+ return v.format;
339
+ }
340
+ }
341
+ }
342
+ return;
343
+ }
344
+ function getFormatPrefix(format) {
345
+ const colonIndex = format.indexOf(":");
346
+ return colonIndex >= 0 ? format.substring(0, colonIndex) : format;
347
+ }
348
+ async function resolveSchemaInputs(input, schema, config) {
349
+ if (typeof schema === "boolean")
350
+ return input;
351
+ const properties = schema.properties;
352
+ if (!properties || typeof properties !== "object")
353
+ return input;
354
+ const resolvers = getInputResolvers();
355
+ const resolved = { ...input };
356
+ for (const [key, propSchema] of Object.entries(properties)) {
357
+ const value = resolved[key];
358
+ const format = getSchemaFormat(propSchema);
359
+ if (!format)
360
+ continue;
361
+ let resolver = resolvers.get(format);
362
+ if (!resolver) {
363
+ const prefix = getFormatPrefix(format);
364
+ resolver = resolvers.get(prefix);
365
+ }
366
+ if (!resolver)
367
+ continue;
368
+ if (typeof value === "string") {
369
+ resolved[key] = await resolver(value, format, config.registry);
370
+ } else if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
371
+ const results = await Promise.all(value.map((item) => resolver(item, format, config.registry)));
372
+ resolved[key] = results.filter((result) => result !== undefined);
373
+ }
374
+ }
375
+ return resolved;
376
+ }
377
+
378
+ // src/task/TaskRunner.ts
379
+ init_TaskTypes();
380
+
284
381
  class TaskRunner {
285
382
  running = false;
286
383
  reactiveRunning = false;
287
- nodeProvenance = {};
288
384
  task;
289
385
  abortController;
290
386
  outputCache;
387
+ registry = globalServiceRegistry;
291
388
  constructor(task) {
292
389
  this.task = task;
293
390
  this.own = this.own.bind(this);
@@ -297,6 +394,8 @@ class TaskRunner {
297
394
  await this.handleStart(config);
298
395
  try {
299
396
  this.task.setInput(overrides);
397
+ const schema = this.task.constructor.inputSchema();
398
+ this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
300
399
  const isValid = await this.task.validateInput(this.task.runInputData);
301
400
  if (!isValid) {
302
401
  throw new TaskInvalidInputError("Invalid input data");
@@ -332,6 +431,8 @@ class TaskRunner {
332
431
  return this.task.runOutputData;
333
432
  }
334
433
  this.task.setInput(overrides);
434
+ const schema = this.task.constructor.inputSchema();
435
+ this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
335
436
  await this.handleStartReactive();
336
437
  try {
337
438
  const isValid = await this.task.validateInput(this.task.runInputData);
@@ -362,8 +463,8 @@ class TaskRunner {
362
463
  const result = await this.task.execute(input, {
363
464
  signal: this.abortController.signal,
364
465
  updateProgress: this.handleProgress.bind(this),
365
- nodeProvenance: this.nodeProvenance,
366
- own: this.own
466
+ own: this.own,
467
+ registry: this.registry
367
468
  });
368
469
  return await this.executeTaskReactive(input, result || {});
369
470
  }
@@ -374,7 +475,6 @@ class TaskRunner {
374
475
  async handleStart(config = {}) {
375
476
  if (this.task.status === TaskStatus.PROCESSING)
376
477
  return;
377
- this.nodeProvenance = {};
378
478
  this.running = true;
379
479
  this.task.startedAt = new Date;
380
480
  this.task.progress = 0;
@@ -383,7 +483,6 @@ class TaskRunner {
383
483
  this.abortController.signal.addEventListener("abort", () => {
384
484
  this.handleAbort();
385
485
  });
386
- this.nodeProvenance = config.nodeProvenance ?? {};
387
486
  const cache = this.task.config.outputCache ?? config.outputCache;
388
487
  if (cache === true) {
389
488
  let instance = globalServiceRegistry.get(TASK_OUTPUT_REPOSITORY);
@@ -396,6 +495,9 @@ class TaskRunner {
396
495
  if (config.updateProgress) {
397
496
  this.updateProgress = config.updateProgress;
398
497
  }
498
+ if (config.registry) {
499
+ this.registry = config.registry;
500
+ }
399
501
  this.task.emit("start");
400
502
  this.task.emit("status", this.task.status);
401
503
  }
@@ -422,7 +524,6 @@ class TaskRunner {
422
524
  this.task.progress = 100;
423
525
  this.task.status = TaskStatus.COMPLETED;
424
526
  this.abortController = undefined;
425
- this.nodeProvenance = {};
426
527
  this.task.emit("complete");
427
528
  this.task.emit("status", this.task.status);
428
529
  }
@@ -436,7 +537,6 @@ class TaskRunner {
436
537
  this.task.progress = 100;
437
538
  this.task.completedAt = new Date;
438
539
  this.abortController = undefined;
439
- this.nodeProvenance = {};
440
540
  this.task.emit("disabled");
441
541
  this.task.emit("status", this.task.status);
442
542
  }
@@ -456,7 +556,6 @@ class TaskRunner {
456
556
  this.task.status = TaskStatus.FAILED;
457
557
  this.task.error = err instanceof TaskError ? err : new TaskFailedError(err?.message || "Task failed");
458
558
  this.abortController = undefined;
459
- this.nodeProvenance = {};
460
559
  this.task.emit("error", this.task.error);
461
560
  this.task.emit("status", this.task.status);
462
561
  }
@@ -471,6 +570,8 @@ class TaskRunner {
471
570
  }
472
571
 
473
572
  // src/task/Task.ts
573
+ init_TaskTypes();
574
+
474
575
  class Task {
475
576
  static type = "Task";
476
577
  static category = "Hidden";
@@ -554,7 +655,6 @@ class Task {
554
655
  return this._events;
555
656
  }
556
657
  _events;
557
- nodeProvenance = {};
558
658
  constructor(callerDefaultInputs = {}, config = {}) {
559
659
  const inputDefaults = this.getDefaultInputsFromStaticInputDefinitions();
560
660
  const mergedDefaults = Object.assign(inputDefaults, callerDefaultInputs);
@@ -591,10 +691,45 @@ class Task {
591
691
  }
592
692
  }
593
693
  resetInputData() {
694
+ this.runInputData = this.smartClone(this.defaults);
695
+ }
696
+ smartClone(obj, visited = new WeakSet) {
697
+ if (obj === null || obj === undefined) {
698
+ return obj;
699
+ }
700
+ if (typeof obj !== "object") {
701
+ return obj;
702
+ }
703
+ if (visited.has(obj)) {
704
+ throw new Error("Circular reference detected in input data. " + "Cannot clone objects with circular references.");
705
+ }
706
+ if (ArrayBuffer.isView(obj)) {
707
+ if (typeof DataView !== "undefined" && obj instanceof DataView) {
708
+ return obj;
709
+ }
710
+ const typedArray = obj;
711
+ return new typedArray.constructor(typedArray);
712
+ }
713
+ if (!Array.isArray(obj)) {
714
+ const proto = Object.getPrototypeOf(obj);
715
+ if (proto !== Object.prototype && proto !== null) {
716
+ return obj;
717
+ }
718
+ }
719
+ visited.add(obj);
594
720
  try {
595
- this.runInputData = structuredClone(this.defaults);
596
- } catch (err) {
597
- this.runInputData = JSON.parse(JSON.stringify(this.defaults));
721
+ if (Array.isArray(obj)) {
722
+ return obj.map((item) => this.smartClone(item, visited));
723
+ }
724
+ const result = {};
725
+ for (const key in obj) {
726
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
727
+ result[key] = this.smartClone(obj[key], visited);
728
+ }
729
+ }
730
+ return result;
731
+ } finally {
732
+ visited.delete(obj);
598
733
  }
599
734
  }
600
735
  setDefaults(defaults) {
@@ -622,7 +757,7 @@ class Task {
622
757
  }
623
758
  if (schema.additionalProperties === true) {
624
759
  for (const [inputId, value] of Object.entries(input)) {
625
- if (value !== undefined && !(inputId in properties)) {
760
+ if (!(inputId in properties)) {
626
761
  this.runInputData[inputId] = value;
627
762
  }
628
763
  }
@@ -675,7 +810,7 @@ class Task {
675
810
  }
676
811
  if (inputSchema.additionalProperties === true) {
677
812
  for (const [inputId, value] of Object.entries(overrides)) {
678
- if (value !== undefined && !(inputId in properties)) {
813
+ if (!(inputId in properties)) {
679
814
  if (!deepEqual(this.runInputData[inputId], value)) {
680
815
  this.runInputData[inputId] = value;
681
816
  changed = true;
@@ -685,7 +820,7 @@ class Task {
685
820
  }
686
821
  return changed;
687
822
  }
688
- async narrowInput(input) {
823
+ async narrowInput(input, _registry) {
689
824
  return input;
690
825
  }
691
826
  subscribe(name, fn) {
@@ -745,20 +880,20 @@ class Task {
745
880
  const path = e.data.pointer || "";
746
881
  return `${e.message}${path ? ` (${path})` : ""}`;
747
882
  });
748
- throw new TaskInvalidInputError(`Input ${JSON.stringify(input)} does not match schema: ${errorMessages.join(", ")}`);
883
+ throw new TaskInvalidInputError(`Input ${JSON.stringify(Object.keys(input))} does not match schema: ${errorMessages.join(", ")}`);
749
884
  }
750
885
  return true;
751
886
  }
752
887
  id() {
753
888
  return this.config.id;
754
889
  }
755
- getProvenance() {
756
- return this.config.provenance ?? {};
757
- }
758
890
  stripSymbols(obj) {
759
891
  if (obj === null || obj === undefined) {
760
892
  return obj;
761
893
  }
894
+ if (ArrayBuffer.isView(obj)) {
895
+ return obj;
896
+ }
762
897
  if (Array.isArray(obj)) {
763
898
  return obj.map((item) => this.stripSymbols(item));
764
899
  }
@@ -774,14 +909,12 @@ class Task {
774
909
  return obj;
775
910
  }
776
911
  toJSON() {
777
- const provenance = this.getProvenance();
778
912
  const extras = this.config.extras;
779
913
  let json = this.stripSymbols({
780
914
  id: this.config.id,
781
915
  type: this.type,
782
916
  ...this.config.name ? { name: this.config.name } : {},
783
917
  defaults: this.defaults,
784
- ...Object.keys(provenance).length ? { provenance } : {},
785
918
  ...extras && Object.keys(extras).length ? { extras } : {}
786
919
  });
787
920
  return json;
@@ -827,8 +960,9 @@ class Task {
827
960
  // src/task/ConditionalTask.ts
828
961
  class ConditionalTask extends Task {
829
962
  static type = "ConditionalTask";
830
- static category = "Flow Control";
963
+ static category = "Hidden";
831
964
  static title = "Conditional Task";
965
+ static description = "Evaluates conditions to determine which dataflows are active";
832
966
  static hasDynamicSchemas = true;
833
967
  activeBranches = new Set;
834
968
  async execute(input, context) {
@@ -936,7 +1070,13 @@ class ConditionalTask extends Task {
936
1070
  }
937
1071
  }
938
1072
 
1073
+ // src/task-graph/TaskGraphRunner.ts
1074
+ init_TaskTypes();
1075
+ init_Dataflow();
1076
+
939
1077
  // src/task-graph/TaskGraphScheduler.ts
1078
+ init_TaskTypes();
1079
+
940
1080
  class TopologicalScheduler {
941
1081
  dag;
942
1082
  sortedNodes;
@@ -1053,9 +1193,9 @@ class TaskGraphRunner {
1053
1193
  reactiveScheduler;
1054
1194
  running = false;
1055
1195
  reactiveRunning = false;
1056
- provenanceInput;
1057
1196
  graph;
1058
1197
  outputCache;
1198
+ registry = globalServiceRegistry2;
1059
1199
  abortController;
1060
1200
  inProgressTasks = new Map;
1061
1201
  inProgressFunctions = new Map;
@@ -1064,7 +1204,6 @@ class TaskGraphRunner {
1064
1204
  this.processScheduler = processScheduler;
1065
1205
  this.reactiveScheduler = reactiveScheduler;
1066
1206
  this.graph = graph;
1067
- this.provenanceInput = new Map;
1068
1207
  graph.outputCache = outputCache;
1069
1208
  this.handleProgress = this.handleProgress.bind(this);
1070
1209
  }
@@ -1084,7 +1223,7 @@ class TaskGraphRunner {
1084
1223
  const runAsync = async () => {
1085
1224
  try {
1086
1225
  const taskInput = isRootTask ? input : this.filterInputForTask(task, input);
1087
- const taskPromise = this.runTaskWithProvenance(task, taskInput, config?.parentProvenance || {});
1226
+ const taskPromise = this.runTask(task, taskInput);
1088
1227
  this.inProgressTasks.set(task.config.id, taskPromise);
1089
1228
  const taskResult = await taskPromise;
1090
1229
  if (this.graph.getTargetDataflows(task.config.id).length === 0) {
@@ -1194,23 +1333,16 @@ class TaskGraphRunner {
1194
1333
  this.addInputData(task, dataflow.getPortData());
1195
1334
  }
1196
1335
  }
1197
- getInputProvenance(node) {
1198
- const nodeProvenance = {};
1199
- this.graph.getSourceDataflows(node.config.id).forEach((dataflow) => {
1200
- Object.assign(nodeProvenance, dataflow.provenance);
1201
- });
1202
- return nodeProvenance;
1203
- }
1204
- async pushOutputFromNodeToEdges(node, results, nodeProvenance) {
1336
+ async pushOutputFromNodeToEdges(node, results) {
1205
1337
  const dataflows = this.graph.getTargetDataflows(node.config.id);
1206
1338
  for (const dataflow of dataflows) {
1207
1339
  const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow);
1208
1340
  if (compatibility === "static") {
1209
- dataflow.setPortData(results, nodeProvenance);
1341
+ dataflow.setPortData(results);
1210
1342
  } else if (compatibility === "runtime") {
1211
1343
  const task = this.graph.getTask(dataflow.targetTaskId);
1212
- const narrowed = await task.narrowInput({ ...results });
1213
- dataflow.setPortData(narrowed, nodeProvenance);
1344
+ const narrowed = await task.narrowInput({ ...results }, this.registry);
1345
+ dataflow.setPortData(narrowed);
1214
1346
  } else {}
1215
1347
  }
1216
1348
  }
@@ -1280,20 +1412,14 @@ class TaskGraphRunner {
1280
1412
  }
1281
1413
  }
1282
1414
  }
1283
- async runTaskWithProvenance(task, input, parentProvenance) {
1284
- const nodeProvenance = {
1285
- ...parentProvenance,
1286
- ...this.getInputProvenance(task),
1287
- ...task.getProvenance()
1288
- };
1289
- this.provenanceInput.set(task.config.id, nodeProvenance);
1415
+ async runTask(task, input) {
1290
1416
  this.copyInputFromEdgesToNode(task);
1291
1417
  const results = await task.runner.run(input, {
1292
- nodeProvenance,
1293
1418
  outputCache: this.outputCache,
1294
- updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args)
1419
+ updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args),
1420
+ registry: this.registry
1295
1421
  });
1296
- await this.pushOutputFromNodeToEdges(task, results, nodeProvenance);
1422
+ await this.pushOutputFromNodeToEdges(task, results);
1297
1423
  return {
1298
1424
  id: task.config.id,
1299
1425
  type: task.constructor.runtype || task.constructor.type,
@@ -1327,10 +1453,15 @@ class TaskGraphRunner {
1327
1453
  });
1328
1454
  }
1329
1455
  async handleStart(config) {
1456
+ if (config?.registry !== undefined) {
1457
+ this.registry = config.registry;
1458
+ } else {
1459
+ this.registry = new ServiceRegistry2(globalServiceRegistry2.container.createChildContainer());
1460
+ }
1330
1461
  if (config?.outputCache !== undefined) {
1331
1462
  if (typeof config.outputCache === "boolean") {
1332
1463
  if (config.outputCache === true) {
1333
- this.outputCache = globalServiceRegistry2.get(TASK_OUTPUT_REPOSITORY);
1464
+ this.outputCache = this.registry.get(TASK_OUTPUT_REPOSITORY);
1334
1465
  } else {
1335
1466
  this.outputCache = undefined;
1336
1467
  }
@@ -1416,7 +1547,7 @@ class TaskGraphRunner {
1416
1547
  progress = Math.round(completed / total);
1417
1548
  }
1418
1549
  this.pushStatusFromNodeToEdges(this.graph, task);
1419
- await this.pushOutputFromNodeToEdges(task, task.runOutputData, task.getProvenance());
1550
+ await this.pushOutputFromNodeToEdges(task, task.runOutputData);
1420
1551
  this.graph.emit("graph_progress", progress, message, args);
1421
1552
  }
1422
1553
  }
@@ -1428,7 +1559,6 @@ class GraphAsTaskRunner extends TaskRunner {
1428
1559
  this.task.emit("progress", progress, message, ...args);
1429
1560
  });
1430
1561
  const results = await this.task.subGraph.run(input, {
1431
- parentProvenance: this.nodeProvenance || {},
1432
1562
  parentSignal: this.abortController?.signal,
1433
1563
  outputCache: this.outputCache
1434
1564
  });
@@ -1444,27 +1574,12 @@ class GraphAsTaskRunner extends TaskRunner {
1444
1574
  }
1445
1575
  super.handleDisable();
1446
1576
  }
1447
- fixInput(input) {
1448
- const inputSchema = this.task.inputSchema();
1449
- if (typeof inputSchema === "boolean") {
1450
- return input;
1451
- }
1452
- const flattenedInput = Object.entries(input).reduce((acc, [key, value]) => {
1453
- const inputDef = inputSchema.properties?.[key];
1454
- const shouldFlatten = Array.isArray(value) && typeof inputDef === "object" && inputDef !== null && "x-replicate" in inputDef && inputDef["x-replicate"] === true;
1455
- if (shouldFlatten) {
1456
- return { ...acc, [key]: value[0] };
1457
- }
1458
- return { ...acc, [key]: value };
1459
- }, {});
1460
- return flattenedInput;
1461
- }
1462
1577
  async executeTask(input) {
1463
1578
  if (this.task.hasChildren()) {
1464
1579
  const runExecuteOutputData = await this.executeTaskChildren(input);
1465
1580
  this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(runExecuteOutputData, this.task.compoundMerge);
1466
1581
  } else {
1467
- const result = await super.executeTask(this.fixInput(input));
1582
+ const result = await super.executeTask(input);
1468
1583
  this.task.runOutputData = result ?? {};
1469
1584
  }
1470
1585
  return this.task.runOutputData;
@@ -1474,7 +1589,7 @@ class GraphAsTaskRunner extends TaskRunner {
1474
1589
  const reactiveResults = await this.executeTaskChildrenReactive();
1475
1590
  this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(reactiveResults, this.task.compoundMerge);
1476
1591
  } else {
1477
- const reactiveResults = await super.executeTaskReactive(this.fixInput(input), output);
1592
+ const reactiveResults = await super.executeTaskReactive(input, output);
1478
1593
  this.task.runOutputData = Object.assign({}, output, reactiveResults ?? {});
1479
1594
  }
1480
1595
  return this.task.runOutputData;
@@ -1484,6 +1599,8 @@ class GraphAsTaskRunner extends TaskRunner {
1484
1599
  // src/task/GraphAsTask.ts
1485
1600
  class GraphAsTask extends Task {
1486
1601
  static type = "GraphAsTask";
1602
+ static title = "Graph as Task";
1603
+ static description = "A task that contains a subgraph of tasks";
1487
1604
  static category = "Hidden";
1488
1605
  static compoundMerge = PROPERTY_ARRAY;
1489
1606
  static hasDynamicSchemas = true;
@@ -1663,35 +1780,70 @@ class GraphAsTask extends Task {
1663
1780
  }
1664
1781
  }
1665
1782
 
1783
+ // src/task-graph/Conversions.ts
1784
+ init_Dataflow();
1785
+
1666
1786
  // src/task-graph/Workflow.ts
1667
- import { EventEmitter as EventEmitter4 } from "@workglow/util";
1787
+ import { EventEmitter as EventEmitter4, uuid4 as uuid43 } from "@workglow/util";
1788
+ init_Dataflow();
1789
+ function CreateWorkflow(taskClass) {
1790
+ return Workflow.createWorkflow(taskClass);
1791
+ }
1792
+ function CreateLoopWorkflow(taskClass) {
1793
+ return function(config = {}) {
1794
+ const task = new taskClass({}, config);
1795
+ this.graph.addTask(task);
1796
+ const previousTask = getLastTask(this);
1797
+ if (previousTask && previousTask !== task) {
1798
+ this.graph.addDataflow(new Dataflow(previousTask.config.id, "*", task.config.id, "*"));
1799
+ }
1800
+ return new Workflow(this.outputCache(), this, task);
1801
+ };
1802
+ }
1803
+ function CreateEndLoopWorkflow(methodName) {
1804
+ return function() {
1805
+ if (!this.isLoopBuilder) {
1806
+ throw new Error(`${methodName}() can only be called on loop workflows`);
1807
+ }
1808
+ return this.finalizeAndReturn();
1809
+ };
1810
+ }
1811
+
1668
1812
  class WorkflowTask extends GraphAsTask {
1669
1813
  static type = "Workflow";
1670
1814
  static compoundMerge = PROPERTY_ARRAY;
1671
1815
  }
1672
- var taskIdCounter = 0;
1673
1816
 
1674
1817
  class Workflow {
1675
- constructor(repository) {
1676
- this._repository = repository;
1677
- this._graph = new TaskGraph({
1678
- outputCache: this._repository
1679
- });
1680
- this._onChanged = this._onChanged.bind(this);
1681
- this.setupEvents();
1818
+ constructor(cache, parent, iteratorTask) {
1819
+ this._outputCache = cache;
1820
+ this._parentWorkflow = parent;
1821
+ this._iteratorTask = iteratorTask;
1822
+ this._graph = new TaskGraph({ outputCache: this._outputCache });
1823
+ if (!parent) {
1824
+ this._onChanged = this._onChanged.bind(this);
1825
+ this.setupEvents();
1826
+ }
1682
1827
  }
1683
1828
  _graph;
1684
1829
  _dataFlows = [];
1685
1830
  _error = "";
1686
- _repository;
1831
+ _outputCache;
1687
1832
  _abortController;
1833
+ _parentWorkflow;
1834
+ _iteratorTask;
1835
+ outputCache() {
1836
+ return this._outputCache;
1837
+ }
1838
+ get isLoopBuilder() {
1839
+ return this._parentWorkflow !== undefined;
1840
+ }
1688
1841
  events = new EventEmitter4;
1689
1842
  static createWorkflow(taskClass) {
1690
1843
  const helper = function(input = {}, config = {}) {
1691
1844
  this._error = "";
1692
1845
  const parent = getLastTask(this);
1693
- taskIdCounter++;
1694
- const task = this.addTask(taskClass, input, { id: String(taskIdCounter), ...config });
1846
+ const task = this.addTaskToGraph(taskClass, input, { id: uuid43(), ...config });
1695
1847
  if (this._dataFlows.length > 0) {
1696
1848
  this._dataFlows.forEach((dataflow) => {
1697
1849
  const taskSchema = task.inputSchema();
@@ -1706,42 +1858,26 @@ class Workflow {
1706
1858
  this._dataFlows = [];
1707
1859
  }
1708
1860
  if (parent && this.graph.getTargetDataflows(parent.config.id).length === 0) {
1709
- const matches = new Map;
1710
- const sourceSchema = parent.outputSchema();
1711
- const targetSchema = task.inputSchema();
1712
- const makeMatch = (comparator) => {
1713
- if (typeof sourceSchema === "boolean" || typeof targetSchema === "boolean") {
1714
- return matches;
1715
- }
1716
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(sourceSchema.properties || {})) {
1717
- for (const [toInputPortId, toPortInputSchema] of Object.entries(targetSchema.properties || {})) {
1718
- if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
1719
- matches.set(toInputPortId, fromOutputPortId);
1720
- this.connect(parent.config.id, fromOutputPortId, task.config.id, toInputPortId);
1721
- }
1722
- }
1723
- }
1724
- return matches;
1725
- };
1726
- makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1727
- if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
1728
- if (fromPortOutputSchema === true && toPortInputSchema === true) {
1729
- return true;
1730
- }
1731
- return false;
1732
- }
1733
- const idTypeMatch = fromPortOutputSchema.$id !== undefined && fromPortOutputSchema.$id === toPortInputSchema.$id;
1734
- const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
1735
- const typeMatch = idTypeBlank && (fromPortOutputSchema.type === toPortInputSchema.type || (toPortInputSchema.oneOf?.some((i) => i.type == fromPortOutputSchema.type) ?? false));
1736
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
1737
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1738
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1739
- return (idTypeMatch || typeMatch) && portIdsCompatible;
1861
+ const nodes = this._graph.getTasks();
1862
+ const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
1863
+ const earlierTasks = [];
1864
+ for (let i = parentIndex - 1;i >= 0; i--) {
1865
+ earlierTasks.push(nodes[i]);
1866
+ }
1867
+ const providedInputKeys = new Set(Object.keys(input || {}));
1868
+ const result = Workflow.autoConnect(this.graph, parent, task, {
1869
+ providedInputKeys,
1870
+ earlierTasks
1740
1871
  });
1741
- if (matches.size === 0) {
1742
- 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.`;
1743
- console.error(this._error);
1744
- this.graph.removeTask(task.config.id);
1872
+ if (result.error) {
1873
+ if (this.isLoopBuilder) {
1874
+ this._error = result.error;
1875
+ console.warn(this._error);
1876
+ } else {
1877
+ this._error = result.error + " Task not added.";
1878
+ console.error(this._error);
1879
+ this.graph.removeTask(task.config.id);
1880
+ }
1745
1881
  }
1746
1882
  }
1747
1883
  return this;
@@ -1781,13 +1917,16 @@ class Workflow {
1781
1917
  return this.events.waitOn(name);
1782
1918
  }
1783
1919
  async run(input = {}) {
1920
+ if (this.isLoopBuilder) {
1921
+ this.finalizeTemplate();
1922
+ return this._parentWorkflow.run(input);
1923
+ }
1784
1924
  this.events.emit("start");
1785
1925
  this._abortController = new AbortController;
1786
1926
  try {
1787
1927
  const output = await this.graph.run(input, {
1788
1928
  parentSignal: this._abortController.signal,
1789
- parentProvenance: {},
1790
- outputCache: this._repository
1929
+ outputCache: this._outputCache
1791
1930
  });
1792
1931
  const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
1793
1932
  this.events.emit("complete");
@@ -1800,6 +1939,9 @@ class Workflow {
1800
1939
  }
1801
1940
  }
1802
1941
  async abort() {
1942
+ if (this._parentWorkflow) {
1943
+ return this._parentWorkflow.abort();
1944
+ }
1803
1945
  this._abortController?.abort();
1804
1946
  }
1805
1947
  pop() {
@@ -1868,10 +2010,12 @@ class Workflow {
1868
2010
  return task;
1869
2011
  }
1870
2012
  reset() {
1871
- taskIdCounter = 0;
2013
+ if (this._parentWorkflow) {
2014
+ throw new WorkflowError("Cannot reset a loop workflow. Call reset() on the parent workflow.");
2015
+ }
1872
2016
  this.clearEvents();
1873
2017
  this._graph = new TaskGraph({
1874
- outputCache: this._repository
2018
+ outputCache: this._outputCache
1875
2019
  });
1876
2020
  this._dataFlows = [];
1877
2021
  this._error = "";
@@ -1918,22 +2062,202 @@ class Workflow {
1918
2062
  if (targetSchema === false) {
1919
2063
  throw new WorkflowError(`Target task has schema 'false' and accepts no inputs`);
1920
2064
  }
1921
- } else if (!targetSchema.properties?.[targetTaskPortId]) {
2065
+ if (targetSchema === true) {}
2066
+ } else if (targetSchema.additionalProperties === true) {} else if (!targetSchema.properties?.[targetTaskPortId]) {
1922
2067
  throw new WorkflowError(`Input ${targetTaskPortId} not found on target task`);
1923
2068
  }
1924
2069
  const dataflow = new Dataflow(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
1925
2070
  this.graph.addDataflow(dataflow);
1926
2071
  return this;
1927
2072
  }
1928
- addTask(taskClass, input, config) {
2073
+ addTaskToGraph(taskClass, input, config) {
1929
2074
  const task = new taskClass(input, config);
1930
2075
  const id = this.graph.addTask(task);
1931
2076
  this.events.emit("changed", id);
1932
2077
  return task;
1933
2078
  }
1934
- }
1935
- function CreateWorkflow(taskClass) {
1936
- return Workflow.createWorkflow(taskClass);
2079
+ addTask(taskClass, input, config) {
2080
+ const helper = Workflow.createWorkflow(taskClass);
2081
+ return helper.call(this, input, config);
2082
+ }
2083
+ static AutoConnectOptions = Symbol("AutoConnectOptions");
2084
+ static autoConnect(graph, sourceTask, targetTask, options) {
2085
+ const matches = new Map;
2086
+ const sourceSchema = sourceTask.outputSchema();
2087
+ const targetSchema = targetTask.inputSchema();
2088
+ const providedInputKeys = options?.providedInputKeys ?? new Set;
2089
+ const earlierTasks = options?.earlierTasks ?? [];
2090
+ const getSpecificTypeIdentifiers = (schema) => {
2091
+ const formats = new Set;
2092
+ const ids = new Set;
2093
+ if (typeof schema === "boolean") {
2094
+ return { formats, ids };
2095
+ }
2096
+ const extractFromSchema = (s) => {
2097
+ if (!s || typeof s !== "object" || Array.isArray(s))
2098
+ return;
2099
+ if (s.format)
2100
+ formats.add(s.format);
2101
+ if (s.$id)
2102
+ ids.add(s.$id);
2103
+ };
2104
+ extractFromSchema(schema);
2105
+ const checkUnion = (schemas) => {
2106
+ if (!schemas)
2107
+ return;
2108
+ for (const s of schemas) {
2109
+ if (typeof s === "boolean")
2110
+ continue;
2111
+ extractFromSchema(s);
2112
+ if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
2113
+ extractFromSchema(s.items);
2114
+ }
2115
+ }
2116
+ };
2117
+ checkUnion(schema.oneOf);
2118
+ checkUnion(schema.anyOf);
2119
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
2120
+ extractFromSchema(schema.items);
2121
+ }
2122
+ return { formats, ids };
2123
+ };
2124
+ const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
2125
+ if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
2126
+ return fromPortOutputSchema === true && toPortInputSchema === true;
2127
+ }
2128
+ const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
2129
+ const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
2130
+ for (const format of outputIds.formats) {
2131
+ if (inputIds.formats.has(format)) {
2132
+ return true;
2133
+ }
2134
+ }
2135
+ for (const id of outputIds.ids) {
2136
+ if (inputIds.ids.has(id)) {
2137
+ return true;
2138
+ }
2139
+ }
2140
+ if (requireSpecificType) {
2141
+ return false;
2142
+ }
2143
+ const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
2144
+ if (!idTypeBlank)
2145
+ return false;
2146
+ if (fromPortOutputSchema.type === toPortInputSchema.type)
2147
+ return true;
2148
+ const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
2149
+ if (typeof schema === "boolean")
2150
+ return schema;
2151
+ return schema.type === fromPortOutputSchema.type;
2152
+ }) ?? false;
2153
+ const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
2154
+ if (typeof schema === "boolean")
2155
+ return schema;
2156
+ return schema.type === fromPortOutputSchema.type;
2157
+ }) ?? false;
2158
+ return matchesOneOf || matchesAnyOf;
2159
+ };
2160
+ const makeMatch = (fromSchema, toSchema, fromTaskId, toTaskId, comparator) => {
2161
+ if (typeof fromSchema === "object") {
2162
+ if (toSchema === true || typeof toSchema === "object" && toSchema.additionalProperties === true) {
2163
+ for (const fromOutputPortId of Object.keys(fromSchema.properties || {})) {
2164
+ matches.set(fromOutputPortId, fromOutputPortId);
2165
+ graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, fromOutputPortId));
2166
+ }
2167
+ return;
2168
+ }
2169
+ }
2170
+ if (typeof fromSchema === "boolean" || typeof toSchema === "boolean") {
2171
+ return;
2172
+ }
2173
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(fromSchema.properties || {})) {
2174
+ for (const [toInputPortId, toPortInputSchema] of Object.entries(toSchema.properties || {})) {
2175
+ if (!matches.has(toInputPortId) && comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
2176
+ matches.set(toInputPortId, fromOutputPortId);
2177
+ graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, toInputPortId));
2178
+ }
2179
+ }
2180
+ }
2181
+ };
2182
+ makeMatch(sourceSchema, targetSchema, sourceTask.config.id, targetTask.config.id, ([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
2183
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
2184
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
2185
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
2186
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
2187
+ });
2188
+ makeMatch(sourceSchema, targetSchema, sourceTask.config.id, targetTask.config.id, ([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
2189
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
2190
+ });
2191
+ const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
2192
+ const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
2193
+ let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
2194
+ if (unmatchedRequired.length > 0 && earlierTasks.length > 0) {
2195
+ for (let i = 0;i < earlierTasks.length && unmatchedRequired.length > 0; i++) {
2196
+ const earlierTask = earlierTasks[i];
2197
+ const earlierOutputSchema = earlierTask.outputSchema();
2198
+ const makeMatchFromEarlier = (comparator) => {
2199
+ if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
2200
+ return;
2201
+ }
2202
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
2203
+ for (const requiredInputId of unmatchedRequired) {
2204
+ const toPortInputSchema = targetSchema.properties?.[requiredInputId];
2205
+ if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
2206
+ matches.set(requiredInputId, fromOutputPortId);
2207
+ graph.addDataflow(new Dataflow(earlierTask.config.id, fromOutputPortId, targetTask.config.id, requiredInputId));
2208
+ }
2209
+ }
2210
+ }
2211
+ };
2212
+ makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
2213
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
2214
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
2215
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
2216
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
2217
+ });
2218
+ makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
2219
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
2220
+ });
2221
+ unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
2222
+ }
2223
+ }
2224
+ const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
2225
+ if (stillUnmatchedRequired.length > 0) {
2226
+ return {
2227
+ matches,
2228
+ error: `Could not find matches for required inputs [${stillUnmatchedRequired.join(", ")}] of ${targetTask.type}. ` + `Attempted to match from ${sourceTask.type} and earlier tasks.`,
2229
+ unmatchedRequired: stillUnmatchedRequired
2230
+ };
2231
+ }
2232
+ if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
2233
+ const hasRequiredInputs = requiredInputs.size > 0;
2234
+ const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
2235
+ const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
2236
+ if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
2237
+ return {
2238
+ matches,
2239
+ 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.`,
2240
+ unmatchedRequired: []
2241
+ };
2242
+ }
2243
+ }
2244
+ return {
2245
+ matches,
2246
+ unmatchedRequired: []
2247
+ };
2248
+ }
2249
+ finalizeTemplate() {
2250
+ if (this._iteratorTask && this.graph.getTasks().length > 0) {
2251
+ this._iteratorTask.setTemplateGraph(this.graph);
2252
+ }
2253
+ }
2254
+ finalizeAndReturn() {
2255
+ if (!this._parentWorkflow) {
2256
+ throw new WorkflowError("finalizeAndReturn() can only be called on loop workflows");
2257
+ }
2258
+ this.finalizeTemplate();
2259
+ return this._parentWorkflow;
2260
+ }
1937
2261
  }
1938
2262
 
1939
2263
  // src/task-graph/Conversions.ts
@@ -2056,6 +2380,9 @@ function parallel(args, mergeFn = PROPERTY_ARRAY, workflow = new Workflow) {
2056
2380
  return workflow;
2057
2381
  }
2058
2382
 
2383
+ // src/task-graph/TaskGraph.ts
2384
+ init_Dataflow();
2385
+
2059
2386
  // src/task-graph/TaskGraphEvents.ts
2060
2387
  var EventDagToTaskGraphMapping = {
2061
2388
  "node-added": "task_added",
@@ -2098,7 +2425,6 @@ class TaskGraph {
2098
2425
  run(input = {}, config = {}) {
2099
2426
  return this.runner.runGraph(input, {
2100
2427
  outputCache: config?.outputCache || this.outputCache,
2101
- parentProvenance: config?.parentProvenance || {},
2102
2428
  parentSignal: config?.parentSignal || undefined
2103
2429
  });
2104
2430
  }
@@ -2174,7 +2500,7 @@ class TaskGraph {
2174
2500
  return this._dag.removeNode(taskId);
2175
2501
  }
2176
2502
  resetGraph() {
2177
- this.runner.resetGraph(this, uuid43());
2503
+ this.runner.resetGraph(this, uuid44());
2178
2504
  }
2179
2505
  toJSON() {
2180
2506
  const tasks = this.getTasks().map((node) => node.toJSON());
@@ -2339,117 +2665,456 @@ function serialGraph(tasks, inputHandle, outputHandle) {
2339
2665
  graph.addDataflows(serialGraphEdges(tasks, inputHandle, outputHandle));
2340
2666
  return graph;
2341
2667
  }
2668
+ // src/task/IteratorTaskRunner.ts
2669
+ import { Job, JobQueueClient, JobQueueServer } from "@workglow/job-queue";
2670
+ import { InMemoryQueueStorage } from "@workglow/storage";
2671
+ import { uuid4 as uuid45 } from "@workglow/util";
2342
2672
 
2343
- // src/task/ArrayTask.ts
2344
- class ArrayTask extends GraphAsTask {
2345
- static type = "ArrayTask";
2346
- static compoundMerge = PROPERTY_ARRAY;
2347
- inputSchema() {
2348
- return this.constructor.inputSchema();
2349
- }
2350
- outputSchema() {
2351
- return this.constructor.outputSchema();
2352
- }
2353
- regenerateGraph() {
2354
- const arrayInputs = new Map;
2355
- let hasArrayInputs = false;
2356
- const inputSchema = this.inputSchema();
2357
- if (typeof inputSchema !== "boolean") {
2358
- const keys = Object.keys(inputSchema.properties || {});
2359
- for (const inputId of keys) {
2360
- const inputValue = this.runInputData[inputId];
2361
- const inputDef = inputSchema.properties?.[inputId];
2362
- if (typeof inputDef === "object" && inputDef !== null && "x-replicate" in inputDef && inputDef["x-replicate"] === true && Array.isArray(inputValue) && inputValue.length > 1) {
2363
- arrayInputs.set(inputId, inputValue);
2364
- hasArrayInputs = true;
2365
- }
2366
- }
2367
- }
2368
- this.subGraph = new TaskGraph;
2369
- if (!hasArrayInputs) {
2370
- super.regenerateGraph();
2371
- return;
2673
+ // src/task/TaskQueueRegistry.ts
2674
+ var taskQueueRegistry = null;
2675
+
2676
+ class TaskQueueRegistry {
2677
+ queues = new Map;
2678
+ registerQueue(queue) {
2679
+ const queueName = queue.server.queueName;
2680
+ if (this.queues.has(queueName)) {
2681
+ throw new Error(`Queue with name ${queueName} already exists`);
2372
2682
  }
2373
- const inputIds = Array.from(arrayInputs.keys());
2374
- const inputObject = Object.fromEntries(arrayInputs);
2375
- const combinations = this.generateCombinations(inputObject, inputIds);
2376
- const tasks = combinations.map((combination) => {
2377
- const { id, name, ...rest } = this.config;
2378
- const task = new this.constructor({ ...this.defaults, ...this.runInputData, ...combination }, { ...rest, id: `${id}_${uuid44()}` });
2379
- return task;
2380
- });
2381
- this.subGraph.addTasks(tasks);
2382
- super.regenerateGraph();
2683
+ this.queues.set(queueName, queue);
2383
2684
  }
2384
- generateCombinations(input, inputMakeArray) {
2385
- const arraysToCombine = inputMakeArray.map((key) => Array.isArray(input[key]) ? input[key] : []);
2386
- const indices = arraysToCombine.map(() => 0);
2387
- const combinations = [];
2388
- let done = false;
2389
- while (!done) {
2390
- combinations.push([...indices]);
2391
- for (let i = indices.length - 1;i >= 0; i--) {
2392
- if (++indices[i] < arraysToCombine[i].length)
2393
- break;
2394
- if (i === 0)
2395
- done = true;
2396
- else
2397
- indices[i] = 0;
2398
- }
2399
- }
2400
- const combos = combinations.map((combination) => {
2401
- const result = { ...input };
2402
- combination.forEach((valueIndex, arrayIndex) => {
2403
- const key = inputMakeArray[arrayIndex];
2404
- if (Array.isArray(input[key]))
2405
- result[key] = input[key][valueIndex];
2406
- });
2407
- return result;
2408
- });
2409
- return combos;
2685
+ getQueue(queueName) {
2686
+ return this.queues.get(queueName);
2410
2687
  }
2411
- toJSON() {
2412
- const { subgraph, ...result } = super.toJSON();
2413
- return result;
2688
+ startQueues() {
2689
+ for (const queue of this.queues.values()) {
2690
+ queue.server.start();
2691
+ }
2692
+ return this;
2414
2693
  }
2415
- toDependencyJSON() {
2416
- const { subtasks, ...result } = super.toDependencyJSON();
2417
- return result;
2694
+ stopQueues() {
2695
+ for (const queue of this.queues.values()) {
2696
+ queue.server.stop();
2697
+ }
2698
+ return this;
2418
2699
  }
2419
- get runner() {
2420
- if (!this._runner) {
2421
- this._runner = new ArrayTaskRunner(this);
2700
+ clearQueues() {
2701
+ for (const queue of this.queues.values()) {
2702
+ queue.storage.deleteAll();
2422
2703
  }
2423
- return this._runner;
2704
+ return this;
2424
2705
  }
2425
2706
  }
2426
-
2427
- class ArrayTaskRunner extends GraphAsTaskRunner {
2428
- async executeTaskChildren(_input) {
2429
- return super.executeTaskChildren({});
2707
+ function getTaskQueueRegistry() {
2708
+ if (!taskQueueRegistry) {
2709
+ taskQueueRegistry = new TaskQueueRegistry;
2430
2710
  }
2431
- async executeTaskReactive(input, output) {
2432
- const reactiveResults = await super.executeTaskReactive(input, output);
2433
- if (this.task.hasChildren()) {
2434
- return reactiveResults;
2435
- } else {
2436
- this.task.runOutputData = Object.assign({}, output, reactiveResults ?? {});
2437
- return this.task.runOutputData;
2438
- }
2711
+ return taskQueueRegistry;
2712
+ }
2713
+ function setTaskQueueRegistry(registry) {
2714
+ if (taskQueueRegistry) {
2715
+ taskQueueRegistry.stopQueues();
2716
+ taskQueueRegistry.clearQueues();
2439
2717
  }
2718
+ taskQueueRegistry = registry;
2440
2719
  }
2441
- // src/task/InputTask.ts
2442
- import { Task as Task2 } from "@workglow/task-graph";
2443
2720
 
2444
- class InputTask extends Task2 {
2445
- static type = "InputTask";
2446
- static category = "Flow Control";
2447
- static title = "Input";
2448
- static description = "Starts the workflow";
2449
- static hasDynamicSchemas = true;
2450
- static cacheable = false;
2451
- static inputSchema() {
2452
- return {
2721
+ // src/task/IteratorTaskRunner.ts
2722
+ class IteratorTaskRunner extends GraphAsTaskRunner {
2723
+ iteratorQueue;
2724
+ iteratorQueueName;
2725
+ async getOrCreateIteratorQueue() {
2726
+ const executionMode = this.task.executionMode;
2727
+ if (executionMode === "parallel") {
2728
+ return;
2729
+ }
2730
+ if (this.iteratorQueue) {
2731
+ return this.iteratorQueue;
2732
+ }
2733
+ const queueName = this.task.config.queueName ?? `iterator-${this.task.config.id}-${uuid45().slice(0, 8)}`;
2734
+ this.iteratorQueueName = queueName;
2735
+ const existingQueue = getTaskQueueRegistry().getQueue(queueName);
2736
+ if (existingQueue) {
2737
+ this.iteratorQueue = existingQueue;
2738
+ return existingQueue;
2739
+ }
2740
+ const concurrency = this.getConcurrencyForMode(executionMode);
2741
+ this.iteratorQueue = await this.createIteratorQueue(queueName, concurrency);
2742
+ return this.iteratorQueue;
2743
+ }
2744
+ getConcurrencyForMode(mode) {
2745
+ switch (mode) {
2746
+ case "sequential":
2747
+ return 1;
2748
+ case "parallel-limited":
2749
+ return this.task.concurrencyLimit;
2750
+ case "batched":
2751
+ return this.task.batchSize;
2752
+ case "parallel":
2753
+ default:
2754
+ return Infinity;
2755
+ }
2756
+ }
2757
+ async createIteratorQueue(queueName, concurrency) {
2758
+ const storage = new InMemoryQueueStorage(queueName);
2759
+ await storage.setupDatabase();
2760
+ const JobClass = class extends Job {
2761
+ async execute(input) {
2762
+ return input;
2763
+ }
2764
+ };
2765
+ const server = new JobQueueServer(JobClass, {
2766
+ storage,
2767
+ queueName,
2768
+ workerCount: Math.min(concurrency, 10)
2769
+ });
2770
+ const client = new JobQueueClient({
2771
+ storage,
2772
+ queueName
2773
+ });
2774
+ client.attach(server);
2775
+ const queue = {
2776
+ server,
2777
+ client,
2778
+ storage
2779
+ };
2780
+ try {
2781
+ getTaskQueueRegistry().registerQueue(queue);
2782
+ } catch (err) {
2783
+ const existing = getTaskQueueRegistry().getQueue(queueName);
2784
+ if (existing) {
2785
+ return existing;
2786
+ }
2787
+ throw err;
2788
+ }
2789
+ await server.start();
2790
+ return queue;
2791
+ }
2792
+ async executeTaskChildren(input) {
2793
+ const executionMode = this.task.executionMode;
2794
+ switch (executionMode) {
2795
+ case "sequential":
2796
+ return this.executeSequential(input);
2797
+ case "parallel-limited":
2798
+ return this.executeParallelLimited(input);
2799
+ case "batched":
2800
+ return this.executeBatched(input);
2801
+ case "parallel":
2802
+ default:
2803
+ return super.executeTaskChildren(input);
2804
+ }
2805
+ }
2806
+ async executeSequential(input) {
2807
+ const tasks = this.task.subGraph.getTasks();
2808
+ const results = [];
2809
+ for (const task of tasks) {
2810
+ if (this.abortController?.signal.aborted) {
2811
+ break;
2812
+ }
2813
+ const taskResult = await task.run(input);
2814
+ results.push({
2815
+ id: task.config.id,
2816
+ type: task.type,
2817
+ data: taskResult
2818
+ });
2819
+ }
2820
+ return results;
2821
+ }
2822
+ async executeParallelLimited(input) {
2823
+ const tasks = this.task.subGraph.getTasks();
2824
+ const results = [];
2825
+ const limit = this.task.concurrencyLimit;
2826
+ for (let i = 0;i < tasks.length; i += limit) {
2827
+ if (this.abortController?.signal.aborted) {
2828
+ break;
2829
+ }
2830
+ const chunk = tasks.slice(i, i + limit);
2831
+ const chunkPromises = chunk.map(async (task) => {
2832
+ const taskResult = await task.run(input);
2833
+ return {
2834
+ id: task.config.id,
2835
+ type: task.type,
2836
+ data: taskResult
2837
+ };
2838
+ });
2839
+ const chunkResults = await Promise.all(chunkPromises);
2840
+ results.push(...chunkResults);
2841
+ }
2842
+ return results;
2843
+ }
2844
+ async executeBatched(input) {
2845
+ const tasks = this.task.subGraph.getTasks();
2846
+ const results = [];
2847
+ const batchSize = this.task.batchSize;
2848
+ for (let i = 0;i < tasks.length; i += batchSize) {
2849
+ if (this.abortController?.signal.aborted) {
2850
+ break;
2851
+ }
2852
+ const batch = tasks.slice(i, i + batchSize);
2853
+ const batchPromises = batch.map(async (task) => {
2854
+ const taskResult = await task.run(input);
2855
+ return {
2856
+ id: task.config.id,
2857
+ type: task.type,
2858
+ data: taskResult
2859
+ };
2860
+ });
2861
+ const batchResults = await Promise.all(batchPromises);
2862
+ results.push(...batchResults);
2863
+ const progress = Math.round((i + batch.length) / tasks.length * 100);
2864
+ this.task.emit("progress", progress, `Completed batch ${Math.ceil((i + 1) / batchSize)}`);
2865
+ }
2866
+ return results;
2867
+ }
2868
+ async cleanup() {
2869
+ if (this.iteratorQueue && this.iteratorQueueName) {
2870
+ try {
2871
+ this.iteratorQueue.server.stop();
2872
+ } catch (err) {}
2873
+ }
2874
+ }
2875
+ }
2876
+
2877
+ // src/task/IteratorTask.ts
2878
+ class IteratorTask extends GraphAsTask {
2879
+ static type = "IteratorTask";
2880
+ static category = "Flow Control";
2881
+ static title = "Iterator";
2882
+ static description = "Base class for loop-type tasks";
2883
+ static hasDynamicSchemas = true;
2884
+ _templateGraph;
2885
+ _iteratorPortInfo;
2886
+ constructor(input = {}, config = {}) {
2887
+ super(input, config);
2888
+ }
2889
+ get runner() {
2890
+ if (!this._runner) {
2891
+ this._runner = new IteratorTaskRunner(this);
2892
+ }
2893
+ return this._runner;
2894
+ }
2895
+ get executionMode() {
2896
+ return this.config.executionMode ?? "parallel";
2897
+ }
2898
+ get concurrencyLimit() {
2899
+ return this.config.concurrencyLimit ?? 5;
2900
+ }
2901
+ get batchSize() {
2902
+ return this.config.batchSize ?? 10;
2903
+ }
2904
+ detectIteratorPort() {
2905
+ if (this._iteratorPortInfo) {
2906
+ return this._iteratorPortInfo;
2907
+ }
2908
+ if (this.config.iteratorPort) {
2909
+ const schema2 = this.inputSchema();
2910
+ if (typeof schema2 === "boolean")
2911
+ return;
2912
+ const portSchema = schema2.properties?.[this.config.iteratorPort];
2913
+ if (portSchema && typeof portSchema === "object") {
2914
+ const itemSchema = portSchema.items ?? { type: "object" };
2915
+ this._iteratorPortInfo = {
2916
+ portName: this.config.iteratorPort,
2917
+ itemSchema
2918
+ };
2919
+ return this._iteratorPortInfo;
2920
+ }
2921
+ }
2922
+ const schema = this.inputSchema();
2923
+ if (typeof schema === "boolean")
2924
+ return;
2925
+ const properties = schema.properties || {};
2926
+ for (const [portName, portSchema] of Object.entries(properties)) {
2927
+ if (typeof portSchema !== "object" || portSchema === null)
2928
+ continue;
2929
+ const ps = portSchema;
2930
+ if (ps.type === "array" || ps.items !== undefined) {
2931
+ const itemSchema = ps.items ?? {
2932
+ type: "object",
2933
+ properties: {},
2934
+ additionalProperties: true
2935
+ };
2936
+ this._iteratorPortInfo = { portName, itemSchema };
2937
+ return this._iteratorPortInfo;
2938
+ }
2939
+ const variants = ps.oneOf ?? ps.anyOf;
2940
+ if (Array.isArray(variants)) {
2941
+ for (const variant of variants) {
2942
+ if (typeof variant === "object" && variant !== null) {
2943
+ const v = variant;
2944
+ if (v.type === "array" || v.items !== undefined) {
2945
+ const itemSchema = v.items ?? {
2946
+ type: "object",
2947
+ properties: {},
2948
+ additionalProperties: true
2949
+ };
2950
+ this._iteratorPortInfo = { portName, itemSchema };
2951
+ return this._iteratorPortInfo;
2952
+ }
2953
+ }
2954
+ }
2955
+ }
2956
+ }
2957
+ return;
2958
+ }
2959
+ getIteratorPortName() {
2960
+ return this.detectIteratorPort()?.portName;
2961
+ }
2962
+ getItemSchema() {
2963
+ return this.detectIteratorPort()?.itemSchema ?? {
2964
+ type: "object",
2965
+ properties: {},
2966
+ additionalProperties: true
2967
+ };
2968
+ }
2969
+ getIterableItems(input) {
2970
+ const portName = this.getIteratorPortName();
2971
+ if (!portName) {
2972
+ throw new TaskConfigurationError(`${this.type}: No array port found in input schema. ` + `Specify 'iteratorPort' in config or ensure input has an array-typed property.`);
2973
+ }
2974
+ const items = input[portName];
2975
+ if (items === undefined || items === null) {
2976
+ return [];
2977
+ }
2978
+ if (Array.isArray(items)) {
2979
+ return items;
2980
+ }
2981
+ return [items];
2982
+ }
2983
+ setTemplateGraph(graph) {
2984
+ this._templateGraph = graph;
2985
+ this.events.emit("regenerate");
2986
+ }
2987
+ getTemplateGraph() {
2988
+ return this._templateGraph;
2989
+ }
2990
+ regenerateGraph() {
2991
+ this.subGraph = new TaskGraph;
2992
+ if (!this._templateGraph || !this._templateGraph.getTasks().length) {
2993
+ super.regenerateGraph();
2994
+ return;
2995
+ }
2996
+ const items = this.getIterableItems(this.runInputData);
2997
+ if (items.length === 0) {
2998
+ super.regenerateGraph();
2999
+ return;
3000
+ }
3001
+ this.createIterationTasks(items);
3002
+ super.regenerateGraph();
3003
+ }
3004
+ createIterationTasks(items) {
3005
+ const portName = this.getIteratorPortName();
3006
+ if (!portName)
3007
+ return;
3008
+ const baseInput = {};
3009
+ for (const [key, value] of Object.entries(this.runInputData)) {
3010
+ if (key !== portName) {
3011
+ baseInput[key] = value;
3012
+ }
3013
+ }
3014
+ for (let i = 0;i < items.length; i++) {
3015
+ const item = items[i];
3016
+ const iterationInput = {
3017
+ ...baseInput,
3018
+ [portName]: item,
3019
+ _iterationIndex: i,
3020
+ _iterationItem: item
3021
+ };
3022
+ this.cloneTemplateForIteration(iterationInput, i);
3023
+ }
3024
+ }
3025
+ cloneTemplateForIteration(iterationInput, index) {
3026
+ if (!this._templateGraph)
3027
+ return;
3028
+ const templateTasks = this._templateGraph.getTasks();
3029
+ const templateDataflows = this._templateGraph.getDataflows();
3030
+ const idMap = new Map;
3031
+ for (const templateTask of templateTasks) {
3032
+ const TaskClass = templateTask.constructor;
3033
+ const clonedTask = new TaskClass({ ...templateTask.defaults, ...iterationInput }, {
3034
+ ...templateTask.config,
3035
+ id: `${templateTask.config.id}_iter${index}`,
3036
+ name: `${templateTask.config.name || templateTask.type} [${index}]`
3037
+ });
3038
+ this.subGraph.addTask(clonedTask);
3039
+ idMap.set(templateTask.config.id, clonedTask.config.id);
3040
+ }
3041
+ for (const templateDataflow of templateDataflows) {
3042
+ const sourceId = idMap.get(templateDataflow.sourceTaskId);
3043
+ const targetId = idMap.get(templateDataflow.targetTaskId);
3044
+ if (sourceId !== undefined && targetId !== undefined) {
3045
+ const { Dataflow: Dataflow2 } = (init_Dataflow(), __toCommonJS(exports_Dataflow));
3046
+ const clonedDataflow = new Dataflow2(sourceId, templateDataflow.sourceTaskPortId, targetId, templateDataflow.targetTaskPortId);
3047
+ this.subGraph.addDataflow(clonedDataflow);
3048
+ }
3049
+ }
3050
+ }
3051
+ async execute(input, context) {
3052
+ const items = this.getIterableItems(input);
3053
+ if (items.length === 0) {
3054
+ return this.getEmptyResult();
3055
+ }
3056
+ this.runInputData = { ...this.defaults, ...input };
3057
+ this.regenerateGraph();
3058
+ return super.execute(input, context);
3059
+ }
3060
+ getEmptyResult() {
3061
+ return {};
3062
+ }
3063
+ collectResults(results) {
3064
+ return results;
3065
+ }
3066
+ inputSchema() {
3067
+ if (this.hasChildren() || this._templateGraph) {
3068
+ return super.inputSchema();
3069
+ }
3070
+ return this.constructor.inputSchema();
3071
+ }
3072
+ outputSchema() {
3073
+ if (!this.hasChildren() && !this._templateGraph) {
3074
+ return this.constructor.outputSchema();
3075
+ }
3076
+ return this.getWrappedOutputSchema();
3077
+ }
3078
+ getWrappedOutputSchema() {
3079
+ const templateGraph = this._templateGraph ?? this.subGraph;
3080
+ if (!templateGraph) {
3081
+ return { type: "object", properties: {}, additionalProperties: false };
3082
+ }
3083
+ const tasks = templateGraph.getTasks();
3084
+ const endingNodes = tasks.filter((task) => templateGraph.getTargetDataflows(task.config.id).length === 0);
3085
+ if (endingNodes.length === 0) {
3086
+ return { type: "object", properties: {}, additionalProperties: false };
3087
+ }
3088
+ const properties = {};
3089
+ for (const task of endingNodes) {
3090
+ const taskOutputSchema = task.outputSchema();
3091
+ if (typeof taskOutputSchema === "boolean")
3092
+ continue;
3093
+ const taskProperties = taskOutputSchema.properties || {};
3094
+ for (const [key, schema] of Object.entries(taskProperties)) {
3095
+ properties[key] = {
3096
+ type: "array",
3097
+ items: schema
3098
+ };
3099
+ }
3100
+ }
3101
+ return {
3102
+ type: "object",
3103
+ properties,
3104
+ additionalProperties: false
3105
+ };
3106
+ }
3107
+ }
3108
+
3109
+ // src/task/BatchTask.ts
3110
+ class BatchTask extends IteratorTask {
3111
+ static type = "BatchTask";
3112
+ static category = "Flow Control";
3113
+ static title = "Batch";
3114
+ static description = "Processes an array in configurable batches";
3115
+ static compoundMerge = PROPERTY_ARRAY;
3116
+ static inputSchema() {
3117
+ return {
2453
3118
  type: "object",
2454
3119
  properties: {},
2455
3120
  additionalProperties: true
@@ -2462,25 +3127,161 @@ class InputTask extends Task2 {
2462
3127
  additionalProperties: true
2463
3128
  };
2464
3129
  }
2465
- inputSchema() {
2466
- return this.config?.extras?.inputSchema ?? this.constructor.inputSchema();
3130
+ get batchSize() {
3131
+ return this.config.batchSize ?? 10;
3132
+ }
3133
+ get flattenResults() {
3134
+ return this.config.flattenResults ?? true;
3135
+ }
3136
+ get batchExecutionMode() {
3137
+ return this.config.batchExecutionMode ?? "sequential";
3138
+ }
3139
+ getIterableItems(input) {
3140
+ const items = super.getIterableItems(input);
3141
+ return this.groupIntoBatches(items);
3142
+ }
3143
+ groupIntoBatches(items) {
3144
+ const batches = [];
3145
+ const size = this.batchSize;
3146
+ for (let i = 0;i < items.length; i += size) {
3147
+ batches.push(items.slice(i, i + size));
3148
+ }
3149
+ return batches;
3150
+ }
3151
+ createIterationTasks(batches) {
3152
+ const portName = this.getIteratorPortName();
3153
+ if (!portName)
3154
+ return;
3155
+ const baseInput = {};
3156
+ for (const [key, value] of Object.entries(this.runInputData)) {
3157
+ if (key !== portName) {
3158
+ baseInput[key] = value;
3159
+ }
3160
+ }
3161
+ for (let i = 0;i < batches.length; i++) {
3162
+ const batch = batches[i];
3163
+ const batchInput = {
3164
+ ...baseInput,
3165
+ [portName]: batch,
3166
+ _batchIndex: i,
3167
+ _batchItems: batch
3168
+ };
3169
+ this.cloneTemplateForIteration(batchInput, i);
3170
+ }
3171
+ }
3172
+ getEmptyResult() {
3173
+ const schema = this.outputSchema();
3174
+ if (typeof schema === "boolean") {
3175
+ return {};
3176
+ }
3177
+ const result = {};
3178
+ for (const key of Object.keys(schema.properties || {})) {
3179
+ result[key] = [];
3180
+ }
3181
+ return result;
2467
3182
  }
2468
3183
  outputSchema() {
2469
- return this.config?.extras?.outputSchema ?? this.constructor.outputSchema();
3184
+ if (!this.hasChildren() && !this._templateGraph) {
3185
+ return this.constructor.outputSchema();
3186
+ }
3187
+ return this.getWrappedOutputSchema();
2470
3188
  }
2471
- async execute(input, _context) {
2472
- return input;
3189
+ collectResults(results) {
3190
+ const collected = super.collectResults(results);
3191
+ if (!this.flattenResults || typeof collected !== "object" || collected === null) {
3192
+ return collected;
3193
+ }
3194
+ const flattened = {};
3195
+ for (const [key, value] of Object.entries(collected)) {
3196
+ if (Array.isArray(value)) {
3197
+ flattened[key] = value.flat(2);
3198
+ } else {
3199
+ flattened[key] = value;
3200
+ }
3201
+ }
3202
+ return flattened;
2473
3203
  }
2474
- async executeReactive(input, _output, _context) {
2475
- return input;
3204
+ regenerateGraph() {
3205
+ this.subGraph = new TaskGraph;
3206
+ if (!this._templateGraph || !this._templateGraph.getTasks().length) {
3207
+ super.regenerateGraph();
3208
+ return;
3209
+ }
3210
+ const batches = this.getIterableItems(this.runInputData);
3211
+ if (batches.length === 0) {
3212
+ super.regenerateGraph();
3213
+ return;
3214
+ }
3215
+ this.createIterationTasks(batches);
3216
+ this.events.emit("regenerate");
3217
+ }
3218
+ }
3219
+ Workflow.prototype.batch = CreateLoopWorkflow(BatchTask);
3220
+ Workflow.prototype.endBatch = CreateEndLoopWorkflow("endBatch");
3221
+ // src/task/ForEachTask.ts
3222
+ class ForEachTask extends IteratorTask {
3223
+ static type = "ForEachTask";
3224
+ static category = "Flow Control";
3225
+ static title = "For Each";
3226
+ static description = "Iterates over an array and runs a workflow for each element";
3227
+ static inputSchema() {
3228
+ return {
3229
+ type: "object",
3230
+ properties: {},
3231
+ additionalProperties: true
3232
+ };
3233
+ }
3234
+ static outputSchema() {
3235
+ return {
3236
+ type: "object",
3237
+ properties: {
3238
+ completed: {
3239
+ type: "boolean",
3240
+ title: "Completed",
3241
+ description: "Whether all iterations completed successfully"
3242
+ },
3243
+ count: {
3244
+ type: "number",
3245
+ title: "Count",
3246
+ description: "Number of items processed"
3247
+ }
3248
+ },
3249
+ additionalProperties: false
3250
+ };
3251
+ }
3252
+ get shouldCollectResults() {
3253
+ return this.config.shouldCollectResults ?? false;
3254
+ }
3255
+ getEmptyResult() {
3256
+ return {
3257
+ completed: true,
3258
+ count: 0
3259
+ };
3260
+ }
3261
+ outputSchema() {
3262
+ if (this.shouldCollectResults && (this.hasChildren() || this._templateGraph)) {
3263
+ return this.getWrappedOutputSchema();
3264
+ }
3265
+ return this.constructor.outputSchema();
3266
+ }
3267
+ collectResults(results) {
3268
+ if (this.config.shouldCollectResults) {
3269
+ return super.collectResults(results);
3270
+ }
3271
+ return {
3272
+ completed: true,
3273
+ count: results.length
3274
+ };
2476
3275
  }
2477
3276
  }
3277
+ Workflow.prototype.forEach = CreateLoopWorkflow(ForEachTask);
3278
+ Workflow.prototype.endForEach = CreateEndLoopWorkflow("endForEach");
2478
3279
  // src/task/JobQueueFactory.ts
2479
3280
  import {
2480
- JobQueueClient,
2481
- JobQueueServer
3281
+ JobQueueClient as JobQueueClient2,
3282
+ JobQueueServer as JobQueueServer2
2482
3283
  } from "@workglow/job-queue";
2483
- import { InMemoryQueueStorage } from "@workglow/storage";
3284
+ import { InMemoryQueueStorage as InMemoryQueueStorage2 } from "@workglow/storage";
2484
3285
  import { createServiceToken as createServiceToken2, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
2485
3286
  var JOB_QUEUE_FACTORY = createServiceToken2("taskgraph.jobQueueFactory");
2486
3287
  var defaultJobQueueFactory = async ({
@@ -2488,9 +3289,9 @@ var defaultJobQueueFactory = async ({
2488
3289
  jobClass,
2489
3290
  options
2490
3291
  }) => {
2491
- const storage = options?.storage ?? new InMemoryQueueStorage(queueName);
3292
+ const storage = options?.storage ?? new InMemoryQueueStorage2(queueName);
2492
3293
  await storage.setupDatabase();
2493
- const server = new JobQueueServer(jobClass, {
3294
+ const server = new JobQueueServer2(jobClass, {
2494
3295
  storage,
2495
3296
  queueName,
2496
3297
  limiter: options?.limiter,
@@ -2501,7 +3302,7 @@ var defaultJobQueueFactory = async ({
2501
3302
  deleteAfterDisabledMs: options?.deleteAfterDisabledMs,
2502
3303
  cleanupIntervalMs: options?.cleanupIntervalMs
2503
3304
  });
2504
- const client = new JobQueueClient({
3305
+ const client = new JobQueueClient2({
2505
3306
  storage,
2506
3307
  queueName
2507
3308
  });
@@ -2521,9 +3322,9 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
2521
3322
  ...defaultOptions,
2522
3323
  ...options ?? {}
2523
3324
  };
2524
- const storage = mergedOptions.storage ?? new InMemoryQueueStorage(queueName);
3325
+ const storage = mergedOptions.storage ?? new InMemoryQueueStorage2(queueName);
2525
3326
  await storage.setupDatabase();
2526
- const server = new JobQueueServer(jobClass, {
3327
+ const server = new JobQueueServer2(jobClass, {
2527
3328
  storage,
2528
3329
  queueName,
2529
3330
  limiter: mergedOptions.limiter,
@@ -2534,7 +3335,7 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
2534
3335
  deleteAfterDisabledMs: mergedOptions.deleteAfterDisabledMs,
2535
3336
  cleanupIntervalMs: mergedOptions.cleanupIntervalMs
2536
3337
  });
2537
- const client = new JobQueueClient({
3338
+ const client = new JobQueueClient2({
2538
3339
  storage,
2539
3340
  queueName
2540
3341
  });
@@ -2552,58 +3353,8 @@ if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
2552
3353
  registerJobQueueFactory(defaultJobQueueFactory);
2553
3354
  }
2554
3355
  // src/task/JobQueueTask.ts
2555
- import { Job as Job2 } from "@workglow/job-queue";
2556
-
2557
- // src/task/TaskQueueRegistry.ts
2558
- var taskQueueRegistry = null;
2559
-
2560
- class TaskQueueRegistry {
2561
- queues = new Map;
2562
- registerQueue(queue) {
2563
- const queueName = queue.server.queueName;
2564
- if (this.queues.has(queueName)) {
2565
- throw new Error(`Queue with name ${queueName} already exists`);
2566
- }
2567
- this.queues.set(queueName, queue);
2568
- }
2569
- getQueue(queueName) {
2570
- return this.queues.get(queueName);
2571
- }
2572
- startQueues() {
2573
- for (const queue of this.queues.values()) {
2574
- queue.server.start();
2575
- }
2576
- return this;
2577
- }
2578
- stopQueues() {
2579
- for (const queue of this.queues.values()) {
2580
- queue.server.stop();
2581
- }
2582
- return this;
2583
- }
2584
- clearQueues() {
2585
- for (const queue of this.queues.values()) {
2586
- queue.storage.deleteAll();
2587
- }
2588
- return this;
2589
- }
2590
- }
2591
- function getTaskQueueRegistry() {
2592
- if (!taskQueueRegistry) {
2593
- taskQueueRegistry = new TaskQueueRegistry;
2594
- }
2595
- return taskQueueRegistry;
2596
- }
2597
- function setTaskQueueRegistry(registry) {
2598
- if (taskQueueRegistry) {
2599
- taskQueueRegistry.stopQueues();
2600
- taskQueueRegistry.clearQueues();
2601
- }
2602
- taskQueueRegistry = registry;
2603
- }
2604
-
2605
- // src/task/JobQueueTask.ts
2606
- class JobQueueTask extends ArrayTask {
3356
+ import { Job as Job3 } from "@workglow/job-queue";
3357
+ class JobQueueTask extends GraphAsTask {
2607
3358
  static type = "JobQueueTask";
2608
3359
  static canRunDirectly = true;
2609
3360
  currentQueueName;
@@ -2613,7 +3364,7 @@ class JobQueueTask extends ArrayTask {
2613
3364
  constructor(input = {}, config = {}) {
2614
3365
  config.queue ??= true;
2615
3366
  super(input, config);
2616
- this.jobClass = Job2;
3367
+ this.jobClass = Job3;
2617
3368
  }
2618
3369
  async execute(input, executeContext) {
2619
3370
  let cleanup = () => {};
@@ -2735,16 +3486,13 @@ class JobQueueTask extends ArrayTask {
2735
3486
  super.abort();
2736
3487
  }
2737
3488
  }
2738
- // src/task/OutputTask.ts
2739
- import { Task as Task3 } from "@workglow/task-graph";
2740
-
2741
- class OutputTask extends Task3 {
2742
- static type = "OutputTask";
3489
+ // src/task/MapTask.ts
3490
+ class MapTask extends IteratorTask {
3491
+ static type = "MapTask";
2743
3492
  static category = "Flow Control";
2744
- static title = "Output";
2745
- static description = "Ends the workflow";
2746
- static hasDynamicSchemas = true;
2747
- static cacheable = false;
3493
+ static title = "Map";
3494
+ static description = "Transforms an array by running a workflow for each element";
3495
+ static compoundMerge = PROPERTY_ARRAY;
2748
3496
  static inputSchema() {
2749
3497
  return {
2750
3498
  type: "object",
@@ -2759,19 +3507,198 @@ class OutputTask extends Task3 {
2759
3507
  additionalProperties: true
2760
3508
  };
2761
3509
  }
2762
- inputSchema() {
2763
- return this.config?.extras?.inputSchema ?? this.constructor.inputSchema();
3510
+ get preserveOrder() {
3511
+ return this.config.preserveOrder ?? true;
3512
+ }
3513
+ get flatten() {
3514
+ return this.config.flatten ?? false;
3515
+ }
3516
+ getEmptyResult() {
3517
+ const schema = this.outputSchema();
3518
+ if (typeof schema === "boolean") {
3519
+ return {};
3520
+ }
3521
+ const result = {};
3522
+ for (const key of Object.keys(schema.properties || {})) {
3523
+ result[key] = [];
3524
+ }
3525
+ return result;
2764
3526
  }
2765
3527
  outputSchema() {
2766
- return this.config?.extras?.outputSchema ?? this.constructor.outputSchema();
3528
+ if (!this.hasChildren() && !this._templateGraph) {
3529
+ return this.constructor.outputSchema();
3530
+ }
3531
+ return this.getWrappedOutputSchema();
2767
3532
  }
2768
- async execute(input, _context) {
2769
- return input;
3533
+ collectResults(results) {
3534
+ const collected = super.collectResults(results);
3535
+ if (!this.flatten || typeof collected !== "object" || collected === null) {
3536
+ return collected;
3537
+ }
3538
+ const flattened = {};
3539
+ for (const [key, value] of Object.entries(collected)) {
3540
+ if (Array.isArray(value)) {
3541
+ flattened[key] = value.flat();
3542
+ } else {
3543
+ flattened[key] = value;
3544
+ }
3545
+ }
3546
+ return flattened;
2770
3547
  }
2771
- async executeReactive(input, _output, _context) {
2772
- return input;
3548
+ }
3549
+ Workflow.prototype.map = CreateLoopWorkflow(MapTask);
3550
+ Workflow.prototype.endMap = CreateEndLoopWorkflow("endMap");
3551
+ // src/task/ReduceTask.ts
3552
+ class ReduceTask extends IteratorTask {
3553
+ static type = "ReduceTask";
3554
+ static category = "Flow Control";
3555
+ static title = "Reduce";
3556
+ static description = "Processes array elements sequentially with an accumulator (fold)";
3557
+ constructor(input = {}, config = {}) {
3558
+ const reduceConfig = {
3559
+ ...config,
3560
+ executionMode: "sequential"
3561
+ };
3562
+ super(input, reduceConfig);
3563
+ }
3564
+ get initialValue() {
3565
+ return this.config.initialValue ?? {};
3566
+ }
3567
+ get accumulatorPort() {
3568
+ return this.config.accumulatorPort ?? "accumulator";
3569
+ }
3570
+ get currentItemPort() {
3571
+ return this.config.currentItemPort ?? "currentItem";
3572
+ }
3573
+ get indexPort() {
3574
+ return this.config.indexPort ?? "index";
3575
+ }
3576
+ async execute(input, context) {
3577
+ if (!this._templateGraph || this._templateGraph.getTasks().length === 0) {
3578
+ return this.initialValue;
3579
+ }
3580
+ const items = this.getIterableItems(input);
3581
+ if (items.length === 0) {
3582
+ return this.initialValue;
3583
+ }
3584
+ let accumulator = { ...this.initialValue };
3585
+ for (let index = 0;index < items.length; index++) {
3586
+ if (context.signal?.aborted) {
3587
+ break;
3588
+ }
3589
+ const currentItem = items[index];
3590
+ const stepInput = {
3591
+ ...input,
3592
+ [this.accumulatorPort]: accumulator,
3593
+ [this.currentItemPort]: currentItem,
3594
+ [this.indexPort]: index
3595
+ };
3596
+ this.subGraph = this.cloneTemplateForStep(index);
3597
+ const results = await this.subGraph.run(stepInput, {
3598
+ parentSignal: context.signal
3599
+ });
3600
+ accumulator = this.subGraph.mergeExecuteOutputsToRunOutput(results, this.compoundMerge);
3601
+ const progress = Math.round((index + 1) / items.length * 100);
3602
+ await context.updateProgress(progress, `Processing item ${index + 1}/${items.length}`);
3603
+ }
3604
+ return accumulator;
3605
+ }
3606
+ getEmptyResult() {
3607
+ return this.initialValue;
3608
+ }
3609
+ cloneTemplateForStep(stepIndex) {
3610
+ const clonedGraph = new TaskGraph;
3611
+ if (!this._templateGraph) {
3612
+ return clonedGraph;
3613
+ }
3614
+ const templateTasks = this._templateGraph.getTasks();
3615
+ const templateDataflows = this._templateGraph.getDataflows();
3616
+ const idMap = new Map;
3617
+ for (const templateTask of templateTasks) {
3618
+ const TaskClass = templateTask.constructor;
3619
+ const clonedTask = new TaskClass({ ...templateTask.defaults }, {
3620
+ ...templateTask.config,
3621
+ id: `${templateTask.config.id}_step${stepIndex}`,
3622
+ name: `${templateTask.config.name || templateTask.type} [${stepIndex}]`
3623
+ });
3624
+ clonedGraph.addTask(clonedTask);
3625
+ idMap.set(templateTask.config.id, clonedTask.config.id);
3626
+ }
3627
+ for (const templateDataflow of templateDataflows) {
3628
+ const sourceId = idMap.get(templateDataflow.sourceTaskId);
3629
+ const targetId = idMap.get(templateDataflow.targetTaskId);
3630
+ if (sourceId !== undefined && targetId !== undefined) {
3631
+ const { Dataflow: Dataflow2 } = (init_Dataflow(), __toCommonJS(exports_Dataflow));
3632
+ const clonedDataflow = new Dataflow2(sourceId, templateDataflow.sourceTaskPortId, targetId, templateDataflow.targetTaskPortId);
3633
+ clonedGraph.addDataflow(clonedDataflow);
3634
+ }
3635
+ }
3636
+ return clonedGraph;
3637
+ }
3638
+ static inputSchema() {
3639
+ return {
3640
+ type: "object",
3641
+ properties: {
3642
+ accumulator: {
3643
+ title: "Accumulator",
3644
+ description: "The current accumulator value"
3645
+ },
3646
+ currentItem: {
3647
+ title: "Current Item",
3648
+ description: "The current item being processed"
3649
+ },
3650
+ index: {
3651
+ type: "number",
3652
+ title: "Index",
3653
+ description: "The current item index"
3654
+ }
3655
+ },
3656
+ additionalProperties: true
3657
+ };
3658
+ }
3659
+ static outputSchema() {
3660
+ return {
3661
+ type: "object",
3662
+ properties: {},
3663
+ additionalProperties: true
3664
+ };
3665
+ }
3666
+ outputSchema() {
3667
+ if (!this._templateGraph) {
3668
+ return this.constructor.outputSchema();
3669
+ }
3670
+ const tasks = this._templateGraph.getTasks();
3671
+ const endingNodes = tasks.filter((task) => this._templateGraph.getTargetDataflows(task.config.id).length === 0);
3672
+ if (endingNodes.length === 0) {
3673
+ return this.constructor.outputSchema();
3674
+ }
3675
+ const properties = {};
3676
+ for (const task of endingNodes) {
3677
+ const taskOutputSchema = task.outputSchema();
3678
+ if (typeof taskOutputSchema === "boolean")
3679
+ continue;
3680
+ const taskProperties = taskOutputSchema.properties || {};
3681
+ for (const [key, schema] of Object.entries(taskProperties)) {
3682
+ if (!properties[key]) {
3683
+ properties[key] = schema;
3684
+ }
3685
+ }
3686
+ }
3687
+ return {
3688
+ type: "object",
3689
+ properties,
3690
+ additionalProperties: false
3691
+ };
3692
+ }
3693
+ regenerateGraph() {
3694
+ this.events.emit("regenerate");
2773
3695
  }
2774
3696
  }
3697
+ Workflow.prototype.reduce = CreateLoopWorkflow(ReduceTask);
3698
+ Workflow.prototype.endReduce = CreateEndLoopWorkflow("endReduce");
3699
+ // src/task/TaskJSON.ts
3700
+ init_Dataflow();
3701
+
2775
3702
  // src/task/TaskRegistry.ts
2776
3703
  var taskConstructors = new Map;
2777
3704
  function registerTask(baseClass) {
@@ -2789,17 +3716,14 @@ var createSingleTaskFromJSON = (item) => {
2789
3716
  throw new TaskJSONError("Task id required");
2790
3717
  if (!item.type)
2791
3718
  throw new TaskJSONError("Task type required");
2792
- if (item.defaults && (Array.isArray(item.defaults) || Array.isArray(item.provenance)))
3719
+ if (item.defaults && Array.isArray(item.defaults))
2793
3720
  throw new TaskJSONError("Task defaults must be an object");
2794
- if (item.provenance && (Array.isArray(item.provenance) || typeof item.provenance !== "object"))
2795
- throw new TaskJSONError("Task provenance must be an object");
2796
3721
  const taskClass = TaskRegistry.all.get(item.type);
2797
3722
  if (!taskClass)
2798
3723
  throw new TaskJSONError(`Task type ${item.type} not found, perhaps not registered?`);
2799
3724
  const taskConfig = {
2800
3725
  id: item.id,
2801
3726
  name: item.name,
2802
- provenance: item.provenance ?? {},
2803
3727
  extras: item.extras
2804
3728
  };
2805
3729
  const task = new taskClass(item.defaults ?? {}, taskConfig);
@@ -2842,6 +3766,170 @@ var createGraphFromGraphJSON = (graphJsonObj) => {
2842
3766
  }
2843
3767
  return subGraph;
2844
3768
  };
3769
+
3770
+ // src/task/index.ts
3771
+ init_TaskTypes();
3772
+
3773
+ // src/task/WhileTask.ts
3774
+ class WhileTask extends GraphAsTask {
3775
+ static type = "WhileTask";
3776
+ static category = "Flow Control";
3777
+ static title = "While Loop";
3778
+ static description = "Loops until a condition function returns false";
3779
+ static hasDynamicSchemas = true;
3780
+ _templateGraph;
3781
+ _currentIteration = 0;
3782
+ constructor(input = {}, config = {}) {
3783
+ super(input, config);
3784
+ }
3785
+ get condition() {
3786
+ return this.config.condition;
3787
+ }
3788
+ get maxIterations() {
3789
+ return this.config.maxIterations ?? 100;
3790
+ }
3791
+ get chainIterations() {
3792
+ return this.config.chainIterations ?? true;
3793
+ }
3794
+ get currentIteration() {
3795
+ return this._currentIteration;
3796
+ }
3797
+ setTemplateGraph(graph) {
3798
+ this._templateGraph = graph;
3799
+ }
3800
+ getTemplateGraph() {
3801
+ return this._templateGraph;
3802
+ }
3803
+ async execute(input, context) {
3804
+ if (!this._templateGraph || this._templateGraph.getTasks().length === 0) {
3805
+ throw new TaskConfigurationError(`${this.type}: No template graph set for while loop`);
3806
+ }
3807
+ if (!this.condition) {
3808
+ throw new TaskConfigurationError(`${this.type}: No condition function provided`);
3809
+ }
3810
+ this._currentIteration = 0;
3811
+ let currentInput = { ...input };
3812
+ let currentOutput = {};
3813
+ while (this._currentIteration < this.maxIterations) {
3814
+ if (context.signal?.aborted) {
3815
+ break;
3816
+ }
3817
+ this.subGraph = this.cloneTemplateGraph(this._currentIteration);
3818
+ const results = await this.subGraph.run(currentInput, {
3819
+ parentSignal: context.signal
3820
+ });
3821
+ currentOutput = this.subGraph.mergeExecuteOutputsToRunOutput(results, this.compoundMerge);
3822
+ if (!this.condition(currentOutput, this._currentIteration)) {
3823
+ break;
3824
+ }
3825
+ if (this.chainIterations) {
3826
+ currentInput = { ...currentInput, ...currentOutput };
3827
+ }
3828
+ this._currentIteration++;
3829
+ const progress = Math.min(this._currentIteration / this.maxIterations * 100, 99);
3830
+ await context.updateProgress(progress, `Iteration ${this._currentIteration}`);
3831
+ }
3832
+ return currentOutput;
3833
+ }
3834
+ cloneTemplateGraph(iteration) {
3835
+ const clonedGraph = new TaskGraph;
3836
+ if (!this._templateGraph) {
3837
+ return clonedGraph;
3838
+ }
3839
+ const templateTasks = this._templateGraph.getTasks();
3840
+ const templateDataflows = this._templateGraph.getDataflows();
3841
+ const idMap = new Map;
3842
+ for (const templateTask of templateTasks) {
3843
+ const TaskClass = templateTask.constructor;
3844
+ const clonedTask = new TaskClass({ ...templateTask.defaults }, {
3845
+ ...templateTask.config,
3846
+ id: `${templateTask.config.id}_iter${iteration}`,
3847
+ name: `${templateTask.config.name || templateTask.type} [${iteration}]`
3848
+ });
3849
+ clonedGraph.addTask(clonedTask);
3850
+ idMap.set(templateTask.config.id, clonedTask.config.id);
3851
+ }
3852
+ for (const templateDataflow of templateDataflows) {
3853
+ const sourceId = idMap.get(templateDataflow.sourceTaskId);
3854
+ const targetId = idMap.get(templateDataflow.targetTaskId);
3855
+ if (sourceId !== undefined && targetId !== undefined) {
3856
+ const { Dataflow: Dataflow2 } = (init_Dataflow(), __toCommonJS(exports_Dataflow));
3857
+ const clonedDataflow = new Dataflow2(sourceId, templateDataflow.sourceTaskPortId, targetId, templateDataflow.targetTaskPortId);
3858
+ clonedGraph.addDataflow(clonedDataflow);
3859
+ }
3860
+ }
3861
+ return clonedGraph;
3862
+ }
3863
+ static inputSchema() {
3864
+ return {
3865
+ type: "object",
3866
+ properties: {},
3867
+ additionalProperties: true
3868
+ };
3869
+ }
3870
+ static outputSchema() {
3871
+ return {
3872
+ type: "object",
3873
+ properties: {
3874
+ _iterations: {
3875
+ type: "number",
3876
+ title: "Iterations",
3877
+ description: "Number of iterations executed"
3878
+ }
3879
+ },
3880
+ additionalProperties: true
3881
+ };
3882
+ }
3883
+ outputSchema() {
3884
+ if (!this._templateGraph) {
3885
+ return this.constructor.outputSchema();
3886
+ }
3887
+ const tasks = this._templateGraph.getTasks();
3888
+ const endingNodes = tasks.filter((task) => this._templateGraph.getTargetDataflows(task.config.id).length === 0);
3889
+ if (endingNodes.length === 0) {
3890
+ return this.constructor.outputSchema();
3891
+ }
3892
+ const properties = {
3893
+ _iterations: {
3894
+ type: "number",
3895
+ title: "Iterations",
3896
+ description: "Number of iterations executed"
3897
+ }
3898
+ };
3899
+ for (const task of endingNodes) {
3900
+ const taskOutputSchema = task.outputSchema();
3901
+ if (typeof taskOutputSchema === "boolean")
3902
+ continue;
3903
+ const taskProperties = taskOutputSchema.properties || {};
3904
+ for (const [key, schema] of Object.entries(taskProperties)) {
3905
+ if (!properties[key]) {
3906
+ properties[key] = schema;
3907
+ }
3908
+ }
3909
+ }
3910
+ return {
3911
+ type: "object",
3912
+ properties,
3913
+ additionalProperties: false
3914
+ };
3915
+ }
3916
+ }
3917
+ Workflow.prototype.while = CreateLoopWorkflow(WhileTask);
3918
+ Workflow.prototype.endWhile = CreateEndLoopWorkflow("endWhile");
3919
+ // src/task/index.ts
3920
+ var registerBaseTasks = () => {
3921
+ const tasks = [
3922
+ ConditionalTask,
3923
+ GraphAsTask,
3924
+ ForEachTask,
3925
+ MapTask,
3926
+ BatchTask,
3927
+ WhileTask,
3928
+ ReduceTask
3929
+ ];
3930
+ tasks.map(TaskRegistry.registerTask);
3931
+ return tasks;
3932
+ };
2845
3933
  // src/storage/TaskGraphRepository.ts
2846
3934
  import { createServiceToken as createServiceToken3, EventEmitter as EventEmitter6 } from "@workglow/util";
2847
3935
  var TASK_GRAPH_REPOSITORY = createServiceToken3("taskgraph.taskGraphRepository");
@@ -3001,7 +4089,9 @@ class TaskOutputTabularRepository extends TaskOutputRepository {
3001
4089
  export {
3002
4090
  setTaskQueueRegistry,
3003
4091
  serialGraph,
4092
+ resolveSchemaInputs,
3004
4093
  registerJobQueueFactory,
4094
+ registerBaseTasks,
3005
4095
  pipe,
3006
4096
  parallel,
3007
4097
  getTaskQueueRegistry,
@@ -3016,6 +4106,7 @@ export {
3016
4106
  connect,
3017
4107
  WorkflowError,
3018
4108
  Workflow,
4109
+ WhileTask,
3019
4110
  TaskStatus,
3020
4111
  TaskRegistry,
3021
4112
  TaskQueueRegistry,
@@ -3038,15 +4129,18 @@ export {
3038
4129
  Task,
3039
4130
  TASK_OUTPUT_REPOSITORY,
3040
4131
  TASK_GRAPH_REPOSITORY,
4132
+ ReduceTask,
3041
4133
  PROPERTY_ARRAY,
3042
- OutputTask,
4134
+ MapTask,
3043
4135
  JobTaskFailedError,
3044
4136
  JobQueueTask,
3045
4137
  JOB_QUEUE_FACTORY,
3046
- InputTask,
4138
+ IteratorTaskRunner,
4139
+ IteratorTask,
3047
4140
  GraphAsTaskRunner,
3048
4141
  GraphAsTask,
3049
4142
  GRAPH_RESULT_ARRAY,
4143
+ ForEachTask,
3050
4144
  EventTaskGraphToDagMapping,
3051
4145
  EventDagToTaskGraphMapping,
3052
4146
  DataflowArrow,
@@ -3054,8 +4148,10 @@ export {
3054
4148
  DATAFLOW_ERROR_PORT,
3055
4149
  DATAFLOW_ALL_PORTS,
3056
4150
  CreateWorkflow,
4151
+ CreateLoopWorkflow,
4152
+ CreateEndLoopWorkflow,
3057
4153
  ConditionalTask,
3058
- ArrayTask
4154
+ BatchTask
3059
4155
  };
3060
4156
 
3061
- //# debugId=B04A540E23B60ABE64756E2164756E21
4157
+ //# debugId=F4A17E03C9237E9864756E2164756E21