@workglow/task-graph 0.0.84 → 0.0.86

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 (57) hide show
  1. package/README.md +1 -53
  2. package/dist/browser.js +281 -273
  3. package/dist/browser.js.map +19 -20
  4. package/dist/bun.js +281 -273
  5. package/dist/bun.js.map +19 -20
  6. package/dist/common.d.ts +1 -16
  7. package/dist/common.d.ts.map +1 -1
  8. package/dist/node.js +281 -273
  9. package/dist/node.js.map +19 -20
  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/ConditionalTask.d.ts +1 -0
  15. package/dist/task/ConditionalTask.d.ts.map +1 -1
  16. package/dist/task/GraphAsTask.d.ts +2 -0
  17. package/dist/task/GraphAsTask.d.ts.map +1 -1
  18. package/dist/task/GraphAsTaskRunner.d.ts +0 -1
  19. package/dist/task/GraphAsTaskRunner.d.ts.map +1 -1
  20. package/dist/task/ITask.d.ts +5 -6
  21. package/dist/task/ITask.d.ts.map +1 -1
  22. package/dist/task/InputResolver.d.ts +34 -0
  23. package/dist/task/InputResolver.d.ts.map +1 -0
  24. package/dist/task/JobQueueTask.d.ts +3 -3
  25. package/dist/task/JobQueueTask.d.ts.map +1 -1
  26. package/dist/task/Task.d.ts +22 -11
  27. package/dist/task/Task.d.ts.map +1 -1
  28. package/dist/task/TaskEvents.d.ts +1 -1
  29. package/dist/task/TaskEvents.d.ts.map +1 -1
  30. package/dist/task/TaskJSON.d.ts +1 -4
  31. package/dist/task/TaskJSON.d.ts.map +1 -1
  32. package/dist/task/TaskRunner.d.ts +6 -5
  33. package/dist/task/TaskRunner.d.ts.map +1 -1
  34. package/dist/task/TaskTypes.d.ts +0 -4
  35. package/dist/task/TaskTypes.d.ts.map +1 -1
  36. package/dist/task/index.d.ts +23 -0
  37. package/dist/task/index.d.ts.map +1 -0
  38. package/dist/task-graph/Dataflow.d.ts +2 -3
  39. package/dist/task-graph/Dataflow.d.ts.map +1 -1
  40. package/dist/task-graph/ITaskGraph.d.ts +1 -1
  41. package/dist/task-graph/ITaskGraph.d.ts.map +1 -1
  42. package/dist/task-graph/TaskGraph.d.ts +4 -4
  43. package/dist/task-graph/TaskGraph.d.ts.map +1 -1
  44. package/dist/task-graph/TaskGraphRunner.d.ts +11 -18
  45. package/dist/task-graph/TaskGraphRunner.d.ts.map +1 -1
  46. package/dist/task-graph/Workflow.d.ts +1 -1
  47. package/dist/task-graph/Workflow.d.ts.map +1 -1
  48. package/package.json +7 -7
  49. package/src/storage/README.md +1 -1
  50. package/src/task/README.md +91 -15
  51. package/src/task-graph/README.md +0 -2
  52. package/dist/task/ArrayTask.d.ts +0 -77
  53. package/dist/task/ArrayTask.d.ts.map +0 -1
  54. package/dist/task/InputTask.d.ts +0 -27
  55. package/dist/task/InputTask.d.ts.map +0 -1
  56. package/dist/task/OutputTask.d.ts +0 -27
  57. package/dist/task/OutputTask.d.ts.map +0 -1
package/dist/node.js CHANGED
@@ -1,12 +1,3 @@
1
- // src/task/ArrayTask.ts
2
- import { uuid4 as uuid44 } from "@workglow/util";
3
-
4
- // src/task-graph/TaskGraph.ts
5
- import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid43 } from "@workglow/util";
6
-
7
- // src/task/GraphAsTask.ts
8
- import { compileSchema as compileSchema2 } from "@workglow/util";
9
-
10
1
  // src/task-graph/Dataflow.ts
11
2
  import { areSemanticallyCompatible, EventEmitter } from "@workglow/util";
12
3
 
@@ -42,14 +33,12 @@ class Dataflow {
42
33
  return Dataflow.createId(this.sourceTaskId, this.sourceTaskPortId, this.targetTaskId, this.targetTaskPortId);
43
34
  }
44
35
  value = undefined;
45
- provenance = {};
46
36
  status = TaskStatus.PENDING;
47
37
  error;
48
38
  reset() {
49
39
  this.status = TaskStatus.PENDING;
50
40
  this.error = undefined;
51
41
  this.value = undefined;
52
- this.provenance = {};
53
42
  this.emit("reset");
54
43
  this.emit("status", this.status);
55
44
  }
@@ -79,7 +68,7 @@ class Dataflow {
79
68
  }
80
69
  this.emit("status", this.status);
81
70
  }
82
- setPortData(entireDataBlock, nodeProvenance) {
71
+ setPortData(entireDataBlock) {
83
72
  if (this.sourceTaskPortId === DATAFLOW_ALL_PORTS) {
84
73
  this.value = entireDataBlock;
85
74
  } else if (this.sourceTaskPortId === DATAFLOW_ERROR_PORT) {
@@ -87,8 +76,6 @@ class Dataflow {
87
76
  } else {
88
77
  this.value = entireDataBlock[this.sourceTaskPortId];
89
78
  }
90
- if (nodeProvenance)
91
- this.provenance = nodeProvenance;
92
79
  }
93
80
  getPortData() {
94
81
  if (this.targetTaskPortId === DATAFLOW_ALL_PORTS) {
@@ -171,11 +158,17 @@ class DataflowArrow extends Dataflow {
171
158
  super(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
172
159
  }
173
160
  }
161
+ // src/task-graph/TaskGraph.ts
162
+ import { DirectedAcyclicGraph, EventEmitter as EventEmitter5, uuid4 as uuid43 } from "@workglow/util";
163
+
164
+ // src/task/GraphAsTask.ts
165
+ import { compileSchema as compileSchema2 } from "@workglow/util";
174
166
 
175
167
  // src/task-graph/TaskGraphRunner.ts
176
168
  import {
177
169
  collectPropertyValues,
178
170
  globalServiceRegistry as globalServiceRegistry2,
171
+ ServiceRegistry as ServiceRegistry2,
179
172
  uuid4 as uuid42
180
173
  } from "@workglow/util";
181
174
 
@@ -280,13 +273,69 @@ class TaskInvalidInputError extends TaskError {
280
273
 
281
274
  // src/task/TaskRunner.ts
282
275
  import { globalServiceRegistry } from "@workglow/util";
276
+
277
+ // src/task/InputResolver.ts
278
+ import { getInputResolvers } from "@workglow/util";
279
+ function getSchemaFormat(schema) {
280
+ if (typeof schema !== "object" || schema === null)
281
+ return;
282
+ const s = schema;
283
+ if (typeof s.format === "string")
284
+ return s.format;
285
+ const variants = s.oneOf ?? s.anyOf;
286
+ if (Array.isArray(variants)) {
287
+ for (const variant of variants) {
288
+ if (typeof variant === "object" && variant !== null) {
289
+ const v = variant;
290
+ if (typeof v.format === "string")
291
+ return v.format;
292
+ }
293
+ }
294
+ }
295
+ return;
296
+ }
297
+ function getFormatPrefix(format) {
298
+ const colonIndex = format.indexOf(":");
299
+ return colonIndex >= 0 ? format.substring(0, colonIndex) : format;
300
+ }
301
+ async function resolveSchemaInputs(input, schema, config) {
302
+ if (typeof schema === "boolean")
303
+ return input;
304
+ const properties = schema.properties;
305
+ if (!properties || typeof properties !== "object")
306
+ return input;
307
+ const resolvers = getInputResolvers();
308
+ const resolved = { ...input };
309
+ for (const [key, propSchema] of Object.entries(properties)) {
310
+ const value = resolved[key];
311
+ const format = getSchemaFormat(propSchema);
312
+ if (!format)
313
+ continue;
314
+ let resolver = resolvers.get(format);
315
+ if (!resolver) {
316
+ const prefix = getFormatPrefix(format);
317
+ resolver = resolvers.get(prefix);
318
+ }
319
+ if (!resolver)
320
+ continue;
321
+ if (typeof value === "string") {
322
+ resolved[key] = await resolver(value, format, config.registry);
323
+ } else if (Array.isArray(value) && value.every((item) => typeof item === "string")) {
324
+ const results = await Promise.all(value.map((item) => resolver(item, format, config.registry)));
325
+ resolved[key] = results.filter((result) => result !== undefined);
326
+ }
327
+ }
328
+ return resolved;
329
+ }
330
+
331
+ // src/task/TaskRunner.ts
283
332
  class TaskRunner {
284
333
  running = false;
285
334
  reactiveRunning = false;
286
- nodeProvenance = {};
287
335
  task;
288
336
  abortController;
289
337
  outputCache;
338
+ registry = globalServiceRegistry;
290
339
  constructor(task) {
291
340
  this.task = task;
292
341
  this.own = this.own.bind(this);
@@ -296,6 +345,8 @@ class TaskRunner {
296
345
  await this.handleStart(config);
297
346
  try {
298
347
  this.task.setInput(overrides);
348
+ const schema = this.task.constructor.inputSchema();
349
+ this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
299
350
  const isValid = await this.task.validateInput(this.task.runInputData);
300
351
  if (!isValid) {
301
352
  throw new TaskInvalidInputError("Invalid input data");
@@ -331,6 +382,8 @@ class TaskRunner {
331
382
  return this.task.runOutputData;
332
383
  }
333
384
  this.task.setInput(overrides);
385
+ const schema = this.task.constructor.inputSchema();
386
+ this.task.runInputData = await resolveSchemaInputs(this.task.runInputData, schema, { registry: this.registry });
334
387
  await this.handleStartReactive();
335
388
  try {
336
389
  const isValid = await this.task.validateInput(this.task.runInputData);
@@ -361,8 +414,8 @@ class TaskRunner {
361
414
  const result = await this.task.execute(input, {
362
415
  signal: this.abortController.signal,
363
416
  updateProgress: this.handleProgress.bind(this),
364
- nodeProvenance: this.nodeProvenance,
365
- own: this.own
417
+ own: this.own,
418
+ registry: this.registry
366
419
  });
367
420
  return await this.executeTaskReactive(input, result || {});
368
421
  }
@@ -373,7 +426,6 @@ class TaskRunner {
373
426
  async handleStart(config = {}) {
374
427
  if (this.task.status === TaskStatus.PROCESSING)
375
428
  return;
376
- this.nodeProvenance = {};
377
429
  this.running = true;
378
430
  this.task.startedAt = new Date;
379
431
  this.task.progress = 0;
@@ -382,7 +434,6 @@ class TaskRunner {
382
434
  this.abortController.signal.addEventListener("abort", () => {
383
435
  this.handleAbort();
384
436
  });
385
- this.nodeProvenance = config.nodeProvenance ?? {};
386
437
  const cache = this.task.config.outputCache ?? config.outputCache;
387
438
  if (cache === true) {
388
439
  let instance = globalServiceRegistry.get(TASK_OUTPUT_REPOSITORY);
@@ -395,6 +446,9 @@ class TaskRunner {
395
446
  if (config.updateProgress) {
396
447
  this.updateProgress = config.updateProgress;
397
448
  }
449
+ if (config.registry) {
450
+ this.registry = config.registry;
451
+ }
398
452
  this.task.emit("start");
399
453
  this.task.emit("status", this.task.status);
400
454
  }
@@ -421,7 +475,6 @@ class TaskRunner {
421
475
  this.task.progress = 100;
422
476
  this.task.status = TaskStatus.COMPLETED;
423
477
  this.abortController = undefined;
424
- this.nodeProvenance = {};
425
478
  this.task.emit("complete");
426
479
  this.task.emit("status", this.task.status);
427
480
  }
@@ -435,7 +488,6 @@ class TaskRunner {
435
488
  this.task.progress = 100;
436
489
  this.task.completedAt = new Date;
437
490
  this.abortController = undefined;
438
- this.nodeProvenance = {};
439
491
  this.task.emit("disabled");
440
492
  this.task.emit("status", this.task.status);
441
493
  }
@@ -455,7 +507,6 @@ class TaskRunner {
455
507
  this.task.status = TaskStatus.FAILED;
456
508
  this.task.error = err instanceof TaskError ? err : new TaskFailedError(err?.message || "Task failed");
457
509
  this.abortController = undefined;
458
- this.nodeProvenance = {};
459
510
  this.task.emit("error", this.task.error);
460
511
  this.task.emit("status", this.task.status);
461
512
  }
@@ -553,7 +604,6 @@ class Task {
553
604
  return this._events;
554
605
  }
555
606
  _events;
556
- nodeProvenance = {};
557
607
  constructor(callerDefaultInputs = {}, config = {}) {
558
608
  const inputDefaults = this.getDefaultInputsFromStaticInputDefinitions();
559
609
  const mergedDefaults = Object.assign(inputDefaults, callerDefaultInputs);
@@ -590,10 +640,45 @@ class Task {
590
640
  }
591
641
  }
592
642
  resetInputData() {
643
+ this.runInputData = this.smartClone(this.defaults);
644
+ }
645
+ smartClone(obj, visited = new WeakSet) {
646
+ if (obj === null || obj === undefined) {
647
+ return obj;
648
+ }
649
+ if (typeof obj !== "object") {
650
+ return obj;
651
+ }
652
+ if (visited.has(obj)) {
653
+ throw new Error("Circular reference detected in input data. " + "Cannot clone objects with circular references.");
654
+ }
655
+ if (ArrayBuffer.isView(obj)) {
656
+ if (typeof DataView !== "undefined" && obj instanceof DataView) {
657
+ return obj;
658
+ }
659
+ const typedArray = obj;
660
+ return new typedArray.constructor(typedArray);
661
+ }
662
+ if (!Array.isArray(obj)) {
663
+ const proto = Object.getPrototypeOf(obj);
664
+ if (proto !== Object.prototype && proto !== null) {
665
+ return obj;
666
+ }
667
+ }
668
+ visited.add(obj);
593
669
  try {
594
- this.runInputData = structuredClone(this.defaults);
595
- } catch (err) {
596
- this.runInputData = JSON.parse(JSON.stringify(this.defaults));
670
+ if (Array.isArray(obj)) {
671
+ return obj.map((item) => this.smartClone(item, visited));
672
+ }
673
+ const result = {};
674
+ for (const key in obj) {
675
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
676
+ result[key] = this.smartClone(obj[key], visited);
677
+ }
678
+ }
679
+ return result;
680
+ } finally {
681
+ visited.delete(obj);
597
682
  }
598
683
  }
599
684
  setDefaults(defaults) {
@@ -621,7 +706,7 @@ class Task {
621
706
  }
622
707
  if (schema.additionalProperties === true) {
623
708
  for (const [inputId, value] of Object.entries(input)) {
624
- if (value !== undefined && !(inputId in properties)) {
709
+ if (!(inputId in properties)) {
625
710
  this.runInputData[inputId] = value;
626
711
  }
627
712
  }
@@ -674,7 +759,7 @@ class Task {
674
759
  }
675
760
  if (inputSchema.additionalProperties === true) {
676
761
  for (const [inputId, value] of Object.entries(overrides)) {
677
- if (value !== undefined && !(inputId in properties)) {
762
+ if (!(inputId in properties)) {
678
763
  if (!deepEqual(this.runInputData[inputId], value)) {
679
764
  this.runInputData[inputId] = value;
680
765
  changed = true;
@@ -684,7 +769,7 @@ class Task {
684
769
  }
685
770
  return changed;
686
771
  }
687
- async narrowInput(input) {
772
+ async narrowInput(input, _registry) {
688
773
  return input;
689
774
  }
690
775
  subscribe(name, fn) {
@@ -744,20 +829,20 @@ class Task {
744
829
  const path = e.data.pointer || "";
745
830
  return `${e.message}${path ? ` (${path})` : ""}`;
746
831
  });
747
- throw new TaskInvalidInputError(`Input ${JSON.stringify(input)} does not match schema: ${errorMessages.join(", ")}`);
832
+ throw new TaskInvalidInputError(`Input ${JSON.stringify(Object.keys(input))} does not match schema: ${errorMessages.join(", ")}`);
748
833
  }
749
834
  return true;
750
835
  }
751
836
  id() {
752
837
  return this.config.id;
753
838
  }
754
- getProvenance() {
755
- return this.config.provenance ?? {};
756
- }
757
839
  stripSymbols(obj) {
758
840
  if (obj === null || obj === undefined) {
759
841
  return obj;
760
842
  }
843
+ if (ArrayBuffer.isView(obj)) {
844
+ return obj;
845
+ }
761
846
  if (Array.isArray(obj)) {
762
847
  return obj.map((item) => this.stripSymbols(item));
763
848
  }
@@ -773,14 +858,12 @@ class Task {
773
858
  return obj;
774
859
  }
775
860
  toJSON() {
776
- const provenance = this.getProvenance();
777
861
  const extras = this.config.extras;
778
862
  let json = this.stripSymbols({
779
863
  id: this.config.id,
780
864
  type: this.type,
781
865
  ...this.config.name ? { name: this.config.name } : {},
782
866
  defaults: this.defaults,
783
- ...Object.keys(provenance).length ? { provenance } : {},
784
867
  ...extras && Object.keys(extras).length ? { extras } : {}
785
868
  });
786
869
  return json;
@@ -826,8 +909,9 @@ class Task {
826
909
  // src/task/ConditionalTask.ts
827
910
  class ConditionalTask extends Task {
828
911
  static type = "ConditionalTask";
829
- static category = "Flow Control";
912
+ static category = "Hidden";
830
913
  static title = "Conditional Task";
914
+ static description = "Evaluates conditions to determine which dataflows are active";
831
915
  static hasDynamicSchemas = true;
832
916
  activeBranches = new Set;
833
917
  async execute(input, context) {
@@ -1052,9 +1136,9 @@ class TaskGraphRunner {
1052
1136
  reactiveScheduler;
1053
1137
  running = false;
1054
1138
  reactiveRunning = false;
1055
- provenanceInput;
1056
1139
  graph;
1057
1140
  outputCache;
1141
+ registry = globalServiceRegistry2;
1058
1142
  abortController;
1059
1143
  inProgressTasks = new Map;
1060
1144
  inProgressFunctions = new Map;
@@ -1063,7 +1147,6 @@ class TaskGraphRunner {
1063
1147
  this.processScheduler = processScheduler;
1064
1148
  this.reactiveScheduler = reactiveScheduler;
1065
1149
  this.graph = graph;
1066
- this.provenanceInput = new Map;
1067
1150
  graph.outputCache = outputCache;
1068
1151
  this.handleProgress = this.handleProgress.bind(this);
1069
1152
  }
@@ -1083,7 +1166,7 @@ class TaskGraphRunner {
1083
1166
  const runAsync = async () => {
1084
1167
  try {
1085
1168
  const taskInput = isRootTask ? input : this.filterInputForTask(task, input);
1086
- const taskPromise = this.runTaskWithProvenance(task, taskInput, config?.parentProvenance || {});
1169
+ const taskPromise = this.runTask(task, taskInput);
1087
1170
  this.inProgressTasks.set(task.config.id, taskPromise);
1088
1171
  const taskResult = await taskPromise;
1089
1172
  if (this.graph.getTargetDataflows(task.config.id).length === 0) {
@@ -1193,23 +1276,16 @@ class TaskGraphRunner {
1193
1276
  this.addInputData(task, dataflow.getPortData());
1194
1277
  }
1195
1278
  }
1196
- getInputProvenance(node) {
1197
- const nodeProvenance = {};
1198
- this.graph.getSourceDataflows(node.config.id).forEach((dataflow) => {
1199
- Object.assign(nodeProvenance, dataflow.provenance);
1200
- });
1201
- return nodeProvenance;
1202
- }
1203
- async pushOutputFromNodeToEdges(node, results, nodeProvenance) {
1279
+ async pushOutputFromNodeToEdges(node, results) {
1204
1280
  const dataflows = this.graph.getTargetDataflows(node.config.id);
1205
1281
  for (const dataflow of dataflows) {
1206
1282
  const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow);
1207
1283
  if (compatibility === "static") {
1208
- dataflow.setPortData(results, nodeProvenance);
1284
+ dataflow.setPortData(results);
1209
1285
  } else if (compatibility === "runtime") {
1210
1286
  const task = this.graph.getTask(dataflow.targetTaskId);
1211
- const narrowed = await task.narrowInput({ ...results });
1212
- dataflow.setPortData(narrowed, nodeProvenance);
1287
+ const narrowed = await task.narrowInput({ ...results }, this.registry);
1288
+ dataflow.setPortData(narrowed);
1213
1289
  } else {}
1214
1290
  }
1215
1291
  }
@@ -1279,20 +1355,14 @@ class TaskGraphRunner {
1279
1355
  }
1280
1356
  }
1281
1357
  }
1282
- async runTaskWithProvenance(task, input, parentProvenance) {
1283
- const nodeProvenance = {
1284
- ...parentProvenance,
1285
- ...this.getInputProvenance(task),
1286
- ...task.getProvenance()
1287
- };
1288
- this.provenanceInput.set(task.config.id, nodeProvenance);
1358
+ async runTask(task, input) {
1289
1359
  this.copyInputFromEdgesToNode(task);
1290
1360
  const results = await task.runner.run(input, {
1291
- nodeProvenance,
1292
1361
  outputCache: this.outputCache,
1293
- updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args)
1362
+ updateProgress: async (task2, progress, message, ...args) => await this.handleProgress(task2, progress, message, ...args),
1363
+ registry: this.registry
1294
1364
  });
1295
- await this.pushOutputFromNodeToEdges(task, results, nodeProvenance);
1365
+ await this.pushOutputFromNodeToEdges(task, results);
1296
1366
  return {
1297
1367
  id: task.config.id,
1298
1368
  type: task.constructor.runtype || task.constructor.type,
@@ -1326,10 +1396,15 @@ class TaskGraphRunner {
1326
1396
  });
1327
1397
  }
1328
1398
  async handleStart(config) {
1399
+ if (config?.registry !== undefined) {
1400
+ this.registry = config.registry;
1401
+ } else {
1402
+ this.registry = new ServiceRegistry2(globalServiceRegistry2.container.createChildContainer());
1403
+ }
1329
1404
  if (config?.outputCache !== undefined) {
1330
1405
  if (typeof config.outputCache === "boolean") {
1331
1406
  if (config.outputCache === true) {
1332
- this.outputCache = globalServiceRegistry2.get(TASK_OUTPUT_REPOSITORY);
1407
+ this.outputCache = this.registry.get(TASK_OUTPUT_REPOSITORY);
1333
1408
  } else {
1334
1409
  this.outputCache = undefined;
1335
1410
  }
@@ -1415,7 +1490,7 @@ class TaskGraphRunner {
1415
1490
  progress = Math.round(completed / total);
1416
1491
  }
1417
1492
  this.pushStatusFromNodeToEdges(this.graph, task);
1418
- await this.pushOutputFromNodeToEdges(task, task.runOutputData, task.getProvenance());
1493
+ await this.pushOutputFromNodeToEdges(task, task.runOutputData);
1419
1494
  this.graph.emit("graph_progress", progress, message, args);
1420
1495
  }
1421
1496
  }
@@ -1427,7 +1502,6 @@ class GraphAsTaskRunner extends TaskRunner {
1427
1502
  this.task.emit("progress", progress, message, ...args);
1428
1503
  });
1429
1504
  const results = await this.task.subGraph.run(input, {
1430
- parentProvenance: this.nodeProvenance || {},
1431
1505
  parentSignal: this.abortController?.signal,
1432
1506
  outputCache: this.outputCache
1433
1507
  });
@@ -1443,27 +1517,12 @@ class GraphAsTaskRunner extends TaskRunner {
1443
1517
  }
1444
1518
  super.handleDisable();
1445
1519
  }
1446
- fixInput(input) {
1447
- const inputSchema = this.task.inputSchema();
1448
- if (typeof inputSchema === "boolean") {
1449
- return input;
1450
- }
1451
- const flattenedInput = Object.entries(input).reduce((acc, [key, value]) => {
1452
- const inputDef = inputSchema.properties?.[key];
1453
- const shouldFlatten = Array.isArray(value) && typeof inputDef === "object" && inputDef !== null && "x-replicate" in inputDef && inputDef["x-replicate"] === true;
1454
- if (shouldFlatten) {
1455
- return { ...acc, [key]: value[0] };
1456
- }
1457
- return { ...acc, [key]: value };
1458
- }, {});
1459
- return flattenedInput;
1460
- }
1461
1520
  async executeTask(input) {
1462
1521
  if (this.task.hasChildren()) {
1463
1522
  const runExecuteOutputData = await this.executeTaskChildren(input);
1464
1523
  this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(runExecuteOutputData, this.task.compoundMerge);
1465
1524
  } else {
1466
- const result = await super.executeTask(this.fixInput(input));
1525
+ const result = await super.executeTask(input);
1467
1526
  this.task.runOutputData = result ?? {};
1468
1527
  }
1469
1528
  return this.task.runOutputData;
@@ -1473,7 +1532,7 @@ class GraphAsTaskRunner extends TaskRunner {
1473
1532
  const reactiveResults = await this.executeTaskChildrenReactive();
1474
1533
  this.task.runOutputData = this.task.subGraph.mergeExecuteOutputsToRunOutput(reactiveResults, this.task.compoundMerge);
1475
1534
  } else {
1476
- const reactiveResults = await super.executeTaskReactive(this.fixInput(input), output);
1535
+ const reactiveResults = await super.executeTaskReactive(input, output);
1477
1536
  this.task.runOutputData = Object.assign({}, output, reactiveResults ?? {});
1478
1537
  }
1479
1538
  return this.task.runOutputData;
@@ -1483,6 +1542,8 @@ class GraphAsTaskRunner extends TaskRunner {
1483
1542
  // src/task/GraphAsTask.ts
1484
1543
  class GraphAsTask extends Task {
1485
1544
  static type = "GraphAsTask";
1545
+ static title = "Graph as Task";
1546
+ static description = "A task that contains a subgraph of tasks";
1486
1547
  static category = "Hidden";
1487
1548
  static compoundMerge = PROPERTY_ARRAY;
1488
1549
  static hasDynamicSchemas = true;
@@ -1709,6 +1770,15 @@ class Workflow {
1709
1770
  const sourceSchema = parent.outputSchema();
1710
1771
  const targetSchema = task.inputSchema();
1711
1772
  const makeMatch = (comparator) => {
1773
+ if (typeof sourceSchema === "object") {
1774
+ if (targetSchema === true || typeof targetSchema === "object" && targetSchema.additionalProperties === true) {
1775
+ for (const fromOutputPortId of Object.keys(sourceSchema.properties || {})) {
1776
+ matches.set(fromOutputPortId, fromOutputPortId);
1777
+ this.connect(parent.config.id, fromOutputPortId, task.config.id, fromOutputPortId);
1778
+ }
1779
+ return matches;
1780
+ }
1781
+ }
1712
1782
  if (typeof sourceSchema === "boolean" || typeof targetSchema === "boolean") {
1713
1783
  return matches;
1714
1784
  }
@@ -1722,25 +1792,135 @@ class Workflow {
1722
1792
  }
1723
1793
  return matches;
1724
1794
  };
1725
- makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1795
+ const getSpecificTypeIdentifiers = (schema) => {
1796
+ const formats = new Set;
1797
+ const ids = new Set;
1798
+ if (typeof schema === "boolean") {
1799
+ return { formats, ids };
1800
+ }
1801
+ const extractFromSchema = (s) => {
1802
+ if (!s || typeof s !== "object" || Array.isArray(s))
1803
+ return;
1804
+ if (s.format)
1805
+ formats.add(s.format);
1806
+ if (s.$id)
1807
+ ids.add(s.$id);
1808
+ };
1809
+ extractFromSchema(schema);
1810
+ const checkUnion = (schemas) => {
1811
+ if (!schemas)
1812
+ return;
1813
+ for (const s of schemas) {
1814
+ if (typeof s === "boolean")
1815
+ continue;
1816
+ extractFromSchema(s);
1817
+ if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
1818
+ extractFromSchema(s.items);
1819
+ }
1820
+ }
1821
+ };
1822
+ checkUnion(schema.oneOf);
1823
+ checkUnion(schema.anyOf);
1824
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
1825
+ extractFromSchema(schema.items);
1826
+ }
1827
+ return { formats, ids };
1828
+ };
1829
+ const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
1726
1830
  if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
1727
- if (fromPortOutputSchema === true && toPortInputSchema === true) {
1831
+ return fromPortOutputSchema === true && toPortInputSchema === true;
1832
+ }
1833
+ const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
1834
+ const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
1835
+ for (const format of outputIds.formats) {
1836
+ if (inputIds.formats.has(format)) {
1837
+ return true;
1838
+ }
1839
+ }
1840
+ for (const id of outputIds.ids) {
1841
+ if (inputIds.ids.has(id)) {
1728
1842
  return true;
1729
1843
  }
1844
+ }
1845
+ if (requireSpecificType) {
1730
1846
  return false;
1731
1847
  }
1732
- const idTypeMatch = fromPortOutputSchema.$id !== undefined && fromPortOutputSchema.$id === toPortInputSchema.$id;
1733
1848
  const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
1734
- const typeMatch = idTypeBlank && (fromPortOutputSchema.type === toPortInputSchema.type || (toPortInputSchema.oneOf?.some((i) => i.type == fromPortOutputSchema.type) ?? false));
1849
+ if (!idTypeBlank)
1850
+ return false;
1851
+ if (fromPortOutputSchema.type === toPortInputSchema.type)
1852
+ return true;
1853
+ const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
1854
+ if (typeof schema === "boolean")
1855
+ return schema;
1856
+ return schema.type === fromPortOutputSchema.type;
1857
+ }) ?? false;
1858
+ const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
1859
+ if (typeof schema === "boolean")
1860
+ return schema;
1861
+ return schema.type === fromPortOutputSchema.type;
1862
+ }) ?? false;
1863
+ return matchesOneOf || matchesAnyOf;
1864
+ };
1865
+ makeMatch(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1735
1866
  const outputPortIdMatch = fromOutputPortId === toInputPortId;
1736
1867
  const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1737
1868
  const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1738
- return (idTypeMatch || typeMatch) && portIdsCompatible;
1869
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
1739
1870
  });
1740
- if (matches.size === 0) {
1741
- this._error = `Could not find a match between the outputs of ${parent.type} and the inputs of ${task.type}. ` + `You now need to connect the outputs to the inputs via connect() manually before adding this task. Task not added.`;
1871
+ makeMatch(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
1872
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
1873
+ });
1874
+ const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
1875
+ const providedInputKeys = new Set(Object.keys(input || {}));
1876
+ const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r));
1877
+ let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
1878
+ if (unmatchedRequired.length > 0) {
1879
+ const nodes = this._graph.getTasks();
1880
+ const parentIndex = nodes.findIndex((n) => n.config.id === parent.config.id);
1881
+ for (let i = parentIndex - 1;i >= 0 && unmatchedRequired.length > 0; i--) {
1882
+ const earlierTask = nodes[i];
1883
+ const earlierOutputSchema = earlierTask.outputSchema();
1884
+ const makeMatchFromEarlier = (comparator) => {
1885
+ if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
1886
+ return;
1887
+ }
1888
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
1889
+ for (const requiredInputId of unmatchedRequired) {
1890
+ const toPortInputSchema = targetSchema.properties?.[requiredInputId];
1891
+ if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
1892
+ matches.set(requiredInputId, fromOutputPortId);
1893
+ this.connect(earlierTask.config.id, fromOutputPortId, task.config.id, requiredInputId);
1894
+ }
1895
+ }
1896
+ }
1897
+ };
1898
+ makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
1899
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
1900
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
1901
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
1902
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
1903
+ });
1904
+ makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
1905
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
1906
+ });
1907
+ unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
1908
+ }
1909
+ }
1910
+ const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
1911
+ if (stillUnmatchedRequired.length > 0) {
1912
+ this._error = `Could not find matches for required inputs [${stillUnmatchedRequired.join(", ")}] of ${task.type}. ` + `Attempted to match from ${parent.type} and earlier tasks. Task not added.`;
1742
1913
  console.error(this._error);
1743
1914
  this.graph.removeTask(task.config.id);
1915
+ } else if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
1916
+ const hasRequiredInputs = requiredInputs.size > 0;
1917
+ const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
1918
+ const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
1919
+ if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
1920
+ this._error = `Could not find a match between the outputs of ${parent.type} and the inputs of ${task.type}. ` + `You now need to connect the outputs to the inputs via connect() manually before adding this task. Task not added.`;
1921
+ console.error(this._error);
1922
+ this.graph.removeTask(task.config.id);
1923
+ }
1744
1924
  }
1745
1925
  }
1746
1926
  return this;
@@ -1785,7 +1965,6 @@ class Workflow {
1785
1965
  try {
1786
1966
  const output = await this.graph.run(input, {
1787
1967
  parentSignal: this._abortController.signal,
1788
- parentProvenance: {},
1789
1968
  outputCache: this._repository
1790
1969
  });
1791
1970
  const results = this.graph.mergeExecuteOutputsToRunOutput(output, PROPERTY_ARRAY);
@@ -1917,7 +2096,8 @@ class Workflow {
1917
2096
  if (targetSchema === false) {
1918
2097
  throw new WorkflowError(`Target task has schema 'false' and accepts no inputs`);
1919
2098
  }
1920
- } else if (!targetSchema.properties?.[targetTaskPortId]) {
2099
+ if (targetSchema === true) {}
2100
+ } else if (targetSchema.additionalProperties === true) {} else if (!targetSchema.properties?.[targetTaskPortId]) {
1921
2101
  throw new WorkflowError(`Input ${targetTaskPortId} not found on target task`);
1922
2102
  }
1923
2103
  const dataflow = new Dataflow(sourceTaskId, sourceTaskPortId, targetTaskId, targetTaskPortId);
@@ -2097,7 +2277,6 @@ class TaskGraph {
2097
2277
  run(input = {}, config = {}) {
2098
2278
  return this.runner.runGraph(input, {
2099
2279
  outputCache: config?.outputCache || this.outputCache,
2100
- parentProvenance: config?.parentProvenance || {},
2101
2280
  parentSignal: config?.parentSignal || undefined
2102
2281
  });
2103
2282
  }
@@ -2338,142 +2517,6 @@ function serialGraph(tasks, inputHandle, outputHandle) {
2338
2517
  graph.addDataflows(serialGraphEdges(tasks, inputHandle, outputHandle));
2339
2518
  return graph;
2340
2519
  }
2341
-
2342
- // src/task/ArrayTask.ts
2343
- class ArrayTask extends GraphAsTask {
2344
- static type = "ArrayTask";
2345
- static compoundMerge = PROPERTY_ARRAY;
2346
- inputSchema() {
2347
- return this.constructor.inputSchema();
2348
- }
2349
- outputSchema() {
2350
- return this.constructor.outputSchema();
2351
- }
2352
- regenerateGraph() {
2353
- const arrayInputs = new Map;
2354
- let hasArrayInputs = false;
2355
- const inputSchema = this.inputSchema();
2356
- if (typeof inputSchema !== "boolean") {
2357
- const keys = Object.keys(inputSchema.properties || {});
2358
- for (const inputId of keys) {
2359
- const inputValue = this.runInputData[inputId];
2360
- const inputDef = inputSchema.properties?.[inputId];
2361
- if (typeof inputDef === "object" && inputDef !== null && "x-replicate" in inputDef && inputDef["x-replicate"] === true && Array.isArray(inputValue) && inputValue.length > 1) {
2362
- arrayInputs.set(inputId, inputValue);
2363
- hasArrayInputs = true;
2364
- }
2365
- }
2366
- }
2367
- this.subGraph = new TaskGraph;
2368
- if (!hasArrayInputs) {
2369
- super.regenerateGraph();
2370
- return;
2371
- }
2372
- const inputIds = Array.from(arrayInputs.keys());
2373
- const inputObject = Object.fromEntries(arrayInputs);
2374
- const combinations = this.generateCombinations(inputObject, inputIds);
2375
- const tasks = combinations.map((combination) => {
2376
- const { id, name, ...rest } = this.config;
2377
- const task = new this.constructor({ ...this.defaults, ...this.runInputData, ...combination }, { ...rest, id: `${id}_${uuid44()}` });
2378
- return task;
2379
- });
2380
- this.subGraph.addTasks(tasks);
2381
- super.regenerateGraph();
2382
- }
2383
- generateCombinations(input, inputMakeArray) {
2384
- const arraysToCombine = inputMakeArray.map((key) => Array.isArray(input[key]) ? input[key] : []);
2385
- const indices = arraysToCombine.map(() => 0);
2386
- const combinations = [];
2387
- let done = false;
2388
- while (!done) {
2389
- combinations.push([...indices]);
2390
- for (let i = indices.length - 1;i >= 0; i--) {
2391
- if (++indices[i] < arraysToCombine[i].length)
2392
- break;
2393
- if (i === 0)
2394
- done = true;
2395
- else
2396
- indices[i] = 0;
2397
- }
2398
- }
2399
- const combos = combinations.map((combination) => {
2400
- const result = { ...input };
2401
- combination.forEach((valueIndex, arrayIndex) => {
2402
- const key = inputMakeArray[arrayIndex];
2403
- if (Array.isArray(input[key]))
2404
- result[key] = input[key][valueIndex];
2405
- });
2406
- return result;
2407
- });
2408
- return combos;
2409
- }
2410
- toJSON() {
2411
- const { subgraph, ...result } = super.toJSON();
2412
- return result;
2413
- }
2414
- toDependencyJSON() {
2415
- const { subtasks, ...result } = super.toDependencyJSON();
2416
- return result;
2417
- }
2418
- get runner() {
2419
- if (!this._runner) {
2420
- this._runner = new ArrayTaskRunner(this);
2421
- }
2422
- return this._runner;
2423
- }
2424
- }
2425
-
2426
- class ArrayTaskRunner extends GraphAsTaskRunner {
2427
- async executeTaskChildren(_input) {
2428
- return super.executeTaskChildren({});
2429
- }
2430
- async executeTaskReactive(input, output) {
2431
- const reactiveResults = await super.executeTaskReactive(input, output);
2432
- if (this.task.hasChildren()) {
2433
- return reactiveResults;
2434
- } else {
2435
- this.task.runOutputData = Object.assign({}, output, reactiveResults ?? {});
2436
- return this.task.runOutputData;
2437
- }
2438
- }
2439
- }
2440
- // src/task/InputTask.ts
2441
- import { Task as Task2 } from "@workglow/task-graph";
2442
-
2443
- class InputTask extends Task2 {
2444
- static type = "InputTask";
2445
- static category = "Flow Control";
2446
- static title = "Input";
2447
- static description = "Starts the workflow";
2448
- static hasDynamicSchemas = true;
2449
- static cacheable = false;
2450
- static inputSchema() {
2451
- return {
2452
- type: "object",
2453
- properties: {},
2454
- additionalProperties: true
2455
- };
2456
- }
2457
- static outputSchema() {
2458
- return {
2459
- type: "object",
2460
- properties: {},
2461
- additionalProperties: true
2462
- };
2463
- }
2464
- inputSchema() {
2465
- return this.config?.extras?.inputSchema ?? this.constructor.inputSchema();
2466
- }
2467
- outputSchema() {
2468
- return this.config?.extras?.outputSchema ?? this.constructor.outputSchema();
2469
- }
2470
- async execute(input, _context) {
2471
- return input;
2472
- }
2473
- async executeReactive(input, _output, _context) {
2474
- return input;
2475
- }
2476
- }
2477
2520
  // src/task/JobQueueFactory.ts
2478
2521
  import {
2479
2522
  JobQueueClient,
@@ -2602,7 +2645,7 @@ function setTaskQueueRegistry(registry) {
2602
2645
  }
2603
2646
 
2604
2647
  // src/task/JobQueueTask.ts
2605
- class JobQueueTask extends ArrayTask {
2648
+ class JobQueueTask extends GraphAsTask {
2606
2649
  static type = "JobQueueTask";
2607
2650
  static canRunDirectly = true;
2608
2651
  currentQueueName;
@@ -2734,43 +2777,6 @@ class JobQueueTask extends ArrayTask {
2734
2777
  super.abort();
2735
2778
  }
2736
2779
  }
2737
- // src/task/OutputTask.ts
2738
- import { Task as Task3 } from "@workglow/task-graph";
2739
-
2740
- class OutputTask extends Task3 {
2741
- static type = "OutputTask";
2742
- static category = "Flow Control";
2743
- static title = "Output";
2744
- static description = "Ends the workflow";
2745
- static hasDynamicSchemas = true;
2746
- static cacheable = false;
2747
- static inputSchema() {
2748
- return {
2749
- type: "object",
2750
- properties: {},
2751
- additionalProperties: true
2752
- };
2753
- }
2754
- static outputSchema() {
2755
- return {
2756
- type: "object",
2757
- properties: {},
2758
- additionalProperties: true
2759
- };
2760
- }
2761
- inputSchema() {
2762
- return this.config?.extras?.inputSchema ?? this.constructor.inputSchema();
2763
- }
2764
- outputSchema() {
2765
- return this.config?.extras?.outputSchema ?? this.constructor.outputSchema();
2766
- }
2767
- async execute(input, _context) {
2768
- return input;
2769
- }
2770
- async executeReactive(input, _output, _context) {
2771
- return input;
2772
- }
2773
- }
2774
2780
  // src/task/TaskRegistry.ts
2775
2781
  var taskConstructors = new Map;
2776
2782
  function registerTask(baseClass) {
@@ -2788,17 +2794,14 @@ var createSingleTaskFromJSON = (item) => {
2788
2794
  throw new TaskJSONError("Task id required");
2789
2795
  if (!item.type)
2790
2796
  throw new TaskJSONError("Task type required");
2791
- if (item.defaults && (Array.isArray(item.defaults) || Array.isArray(item.provenance)))
2797
+ if (item.defaults && Array.isArray(item.defaults))
2792
2798
  throw new TaskJSONError("Task defaults must be an object");
2793
- if (item.provenance && (Array.isArray(item.provenance) || typeof item.provenance !== "object"))
2794
- throw new TaskJSONError("Task provenance must be an object");
2795
2799
  const taskClass = TaskRegistry.all.get(item.type);
2796
2800
  if (!taskClass)
2797
2801
  throw new TaskJSONError(`Task type ${item.type} not found, perhaps not registered?`);
2798
2802
  const taskConfig = {
2799
2803
  id: item.id,
2800
2804
  name: item.name,
2801
- provenance: item.provenance ?? {},
2802
2805
  extras: item.extras
2803
2806
  };
2804
2807
  const task = new taskClass(item.defaults ?? {}, taskConfig);
@@ -2841,6 +2844,12 @@ var createGraphFromGraphJSON = (graphJsonObj) => {
2841
2844
  }
2842
2845
  return subGraph;
2843
2846
  };
2847
+ // src/task/index.ts
2848
+ var registerBaseTasks = () => {
2849
+ const tasks = [ConditionalTask, GraphAsTask];
2850
+ tasks.map(TaskRegistry.registerTask);
2851
+ return tasks;
2852
+ };
2844
2853
  // src/storage/TaskGraphRepository.ts
2845
2854
  import { createServiceToken as createServiceToken3, EventEmitter as EventEmitter6 } from "@workglow/util";
2846
2855
  var TASK_GRAPH_REPOSITORY = createServiceToken3("taskgraph.taskGraphRepository");
@@ -3000,7 +3009,9 @@ class TaskOutputTabularRepository extends TaskOutputRepository {
3000
3009
  export {
3001
3010
  setTaskQueueRegistry,
3002
3011
  serialGraph,
3012
+ resolveSchemaInputs,
3003
3013
  registerJobQueueFactory,
3014
+ registerBaseTasks,
3004
3015
  pipe,
3005
3016
  parallel,
3006
3017
  getTaskQueueRegistry,
@@ -3038,11 +3049,9 @@ export {
3038
3049
  TASK_OUTPUT_REPOSITORY,
3039
3050
  TASK_GRAPH_REPOSITORY,
3040
3051
  PROPERTY_ARRAY,
3041
- OutputTask,
3042
3052
  JobTaskFailedError,
3043
3053
  JobQueueTask,
3044
3054
  JOB_QUEUE_FACTORY,
3045
- InputTask,
3046
3055
  GraphAsTaskRunner,
3047
3056
  GraphAsTask,
3048
3057
  GRAPH_RESULT_ARRAY,
@@ -3053,8 +3062,7 @@ export {
3053
3062
  DATAFLOW_ERROR_PORT,
3054
3063
  DATAFLOW_ALL_PORTS,
3055
3064
  CreateWorkflow,
3056
- ConditionalTask,
3057
- ArrayTask
3065
+ ConditionalTask
3058
3066
  };
3059
3067
 
3060
- //# debugId=1AE09A7FA617090D64756E2164756E21
3068
+ //# debugId=0CAE14A95DE5D28564756E2164756E21