@workglow/task-graph 0.2.13 → 0.2.15

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 (89) hide show
  1. package/dist/__tests__/public-exports.test.d.ts +7 -0
  2. package/dist/__tests__/public-exports.test.d.ts.map +1 -0
  3. package/dist/browser.js +1068 -404
  4. package/dist/browser.js.map +30 -20
  5. package/dist/bun.js +1063 -399
  6. package/dist/bun.js.map +30 -20
  7. package/dist/common.d.ts +4 -0
  8. package/dist/common.d.ts.map +1 -1
  9. package/dist/node.js +1063 -399
  10. package/dist/node.js.map +30 -20
  11. package/dist/task/EntitlementEnforcer.d.ts +37 -4
  12. package/dist/task/EntitlementEnforcer.d.ts.map +1 -1
  13. package/dist/task/EntitlementPolicy.d.ts +10 -0
  14. package/dist/task/EntitlementPolicy.d.ts.map +1 -1
  15. package/dist/task/FallbackTask.d.ts +2 -1
  16. package/dist/task/FallbackTask.d.ts.map +1 -1
  17. package/dist/task/GraphAsTask.d.ts +8 -0
  18. package/dist/task/GraphAsTask.d.ts.map +1 -1
  19. package/dist/task/IteratorTask.d.ts +26 -7
  20. package/dist/task/IteratorTask.d.ts.map +1 -1
  21. package/dist/task/IteratorTaskRunner.d.ts +1 -1
  22. package/dist/task/IteratorTaskRunner.d.ts.map +1 -1
  23. package/dist/task/MapTask.d.ts +47 -3
  24. package/dist/task/MapTask.d.ts.map +1 -1
  25. package/dist/task/ReduceTask.d.ts +10 -3
  26. package/dist/task/ReduceTask.d.ts.map +1 -1
  27. package/dist/task/Task.d.ts +4 -1
  28. package/dist/task/Task.d.ts.map +1 -1
  29. package/dist/task/TaskJSON.d.ts +10 -1
  30. package/dist/task/TaskJSON.d.ts.map +1 -1
  31. package/dist/task/WhileTask.d.ts +20 -7
  32. package/dist/task/WhileTask.d.ts.map +1 -1
  33. package/dist/task/__tests__/DataflowJson.transforms.test.d.ts +7 -0
  34. package/dist/task/__tests__/DataflowJson.transforms.test.d.ts.map +1 -0
  35. package/dist/task-graph/ConditionalBuilder.d.ts +49 -0
  36. package/dist/task-graph/ConditionalBuilder.d.ts.map +1 -0
  37. package/dist/task-graph/Conversions.d.ts.map +1 -1
  38. package/dist/task-graph/Dataflow.d.ts +41 -1
  39. package/dist/task-graph/Dataflow.d.ts.map +1 -1
  40. package/dist/task-graph/GraphSchemaUtils.d.ts +2 -2
  41. package/dist/task-graph/GraphSchemaUtils.d.ts.map +1 -1
  42. package/dist/task-graph/GraphToWorkflowCode.d.ts.map +1 -1
  43. package/dist/task-graph/TaskGraph.d.ts +6 -0
  44. package/dist/task-graph/TaskGraph.d.ts.map +1 -1
  45. package/dist/task-graph/TaskGraphRunner.d.ts.map +1 -1
  46. package/dist/task-graph/TransformRegistry.d.ts +14 -0
  47. package/dist/task-graph/TransformRegistry.d.ts.map +1 -0
  48. package/dist/task-graph/TransformTypes.d.ts +51 -0
  49. package/dist/task-graph/TransformTypes.d.ts.map +1 -0
  50. package/dist/task-graph/Workflow.d.ts +31 -3
  51. package/dist/task-graph/Workflow.d.ts.map +1 -1
  52. package/dist/task-graph/__tests__/Dataflow.streaming.test.d.ts +7 -0
  53. package/dist/task-graph/__tests__/Dataflow.streaming.test.d.ts.map +1 -0
  54. package/dist/task-graph/__tests__/Dataflow.transforms.test.d.ts +7 -0
  55. package/dist/task-graph/__tests__/Dataflow.transforms.test.d.ts.map +1 -0
  56. package/dist/task-graph/__tests__/TaskGraphRunner.transforms.test.d.ts +7 -0
  57. package/dist/task-graph/__tests__/TaskGraphRunner.transforms.test.d.ts.map +1 -0
  58. package/dist/task-graph/__tests__/TransformRegistry.test.d.ts +6 -0
  59. package/dist/task-graph/__tests__/TransformRegistry.test.d.ts.map +1 -0
  60. package/dist/task-graph/__tests__/transforms/coalesce.test.d.ts +6 -0
  61. package/dist/task-graph/__tests__/transforms/coalesce.test.d.ts.map +1 -0
  62. package/dist/task-graph/__tests__/transforms/date-conversions.test.d.ts +6 -0
  63. package/dist/task-graph/__tests__/transforms/date-conversions.test.d.ts.map +1 -0
  64. package/dist/task-graph/__tests__/transforms/index-access.test.d.ts +6 -0
  65. package/dist/task-graph/__tests__/transforms/index-access.test.d.ts.map +1 -0
  66. package/dist/task-graph/__tests__/transforms/pick.test.d.ts +6 -0
  67. package/dist/task-graph/__tests__/transforms/pick.test.d.ts.map +1 -0
  68. package/dist/task-graph/__tests__/transforms/scalar-conversions.test.d.ts +6 -0
  69. package/dist/task-graph/__tests__/transforms/scalar-conversions.test.d.ts.map +1 -0
  70. package/dist/task-graph/__tests__/transforms/string-casts.test.d.ts +6 -0
  71. package/dist/task-graph/__tests__/transforms/string-casts.test.d.ts.map +1 -0
  72. package/dist/task-graph/autoConnect.d.ts +39 -0
  73. package/dist/task-graph/autoConnect.d.ts.map +1 -0
  74. package/dist/task-graph/transforms/coalesce.d.ts +11 -0
  75. package/dist/task-graph/transforms/coalesce.d.ts.map +1 -0
  76. package/dist/task-graph/transforms/date-conversions.d.ts +12 -0
  77. package/dist/task-graph/transforms/date-conversions.d.ts.map +1 -0
  78. package/dist/task-graph/transforms/index-access.d.ts +11 -0
  79. package/dist/task-graph/transforms/index-access.d.ts.map +1 -0
  80. package/dist/task-graph/transforms/index.d.ts +18 -0
  81. package/dist/task-graph/transforms/index.d.ts.map +1 -0
  82. package/dist/task-graph/transforms/pick.d.ts +11 -0
  83. package/dist/task-graph/transforms/pick.d.ts.map +1 -0
  84. package/dist/task-graph/transforms/scalar-conversions.d.ts +10 -0
  85. package/dist/task-graph/transforms/scalar-conversions.d.ts.map +1 -0
  86. package/dist/task-graph/transforms/string-casts.d.ts +18 -0
  87. package/dist/task-graph/transforms/string-casts.d.ts.map +1 -0
  88. package/package.json +7 -7
  89. package/src/EXECUTION_MODEL.md +6 -0
package/dist/bun.js CHANGED
@@ -3,6 +3,98 @@
3
3
  import { areSemanticallyCompatible } from "@workglow/util/schema";
4
4
  import { EventEmitter } from "@workglow/util";
5
5
 
6
+ // src/task/TaskError.ts
7
+ import { BaseError } from "@workglow/util";
8
+
9
+ class TaskError extends BaseError {
10
+ static type = "TaskError";
11
+ taskType;
12
+ taskId;
13
+ constructor(message) {
14
+ super(message);
15
+ }
16
+ }
17
+
18
+ class TaskConfigurationError extends TaskError {
19
+ static type = "TaskConfigurationError";
20
+ constructor(message) {
21
+ super(message);
22
+ }
23
+ }
24
+
25
+ class WorkflowError extends TaskError {
26
+ static type = "WorkflowError";
27
+ constructor(message) {
28
+ super(message);
29
+ }
30
+ }
31
+
32
+ class TaskAbortedError extends TaskError {
33
+ static type = "TaskAbortedError";
34
+ constructor(message = "Task aborted") {
35
+ super(message);
36
+ }
37
+ }
38
+
39
+ class TaskTimeoutError extends TaskAbortedError {
40
+ static type = "TaskTimeoutError";
41
+ constructor(timeoutMs) {
42
+ super(timeoutMs ? `Task timed out after ${timeoutMs}ms` : "Task timed out");
43
+ }
44
+ }
45
+
46
+ class TaskGraphTimeoutError extends TaskTimeoutError {
47
+ static type = "TaskGraphTimeoutError";
48
+ constructor(timeoutMs) {
49
+ super(timeoutMs);
50
+ this.message = timeoutMs ? `Graph execution timed out after ${timeoutMs}ms` : "Graph execution timed out";
51
+ }
52
+ }
53
+
54
+ class TaskFailedError extends TaskError {
55
+ static type = "TaskFailedError";
56
+ constructor(message = "Task failed") {
57
+ super(message);
58
+ }
59
+ }
60
+
61
+ class JobTaskFailedError extends TaskFailedError {
62
+ static type = "JobTaskFailedError";
63
+ jobError;
64
+ constructor(err) {
65
+ super(String(err));
66
+ this.jobError = err;
67
+ }
68
+ }
69
+
70
+ class TaskJSONError extends TaskError {
71
+ static type = "TaskJSONError";
72
+ constructor(message = "Error converting JSON to a Task") {
73
+ super(message);
74
+ }
75
+ }
76
+
77
+ class TaskInvalidInputError extends TaskError {
78
+ static type = "TaskInvalidInputError";
79
+ constructor(message = "Invalid input data") {
80
+ super(message);
81
+ }
82
+ }
83
+
84
+ class TaskEntitlementError extends TaskError {
85
+ static type = "TaskEntitlementError";
86
+ constructor(message = "Required entitlements denied") {
87
+ super(message);
88
+ }
89
+ }
90
+
91
+ class TaskSerializationError extends TaskError {
92
+ static type = "TaskSerializationError";
93
+ constructor(taskType) {
94
+ super(`Task "${taskType}" cannot be serialized: config contains non-serializable values. ` + `Use a declarative config alternative or remove function-valued config properties.`);
95
+ }
96
+ }
97
+
6
98
  // src/task/TaskTypes.ts
7
99
  var TaskStatus = {
8
100
  PENDING: "PENDING",
@@ -49,6 +141,21 @@ var TaskConfigSchema = {
49
141
  additionalProperties: false
50
142
  };
51
143
 
144
+ // src/task-graph/TransformRegistry.ts
145
+ import { createServiceToken, globalServiceRegistry } from "@workglow/util";
146
+ var transformDefs = new Map;
147
+ var TransformRegistry = {
148
+ all: transformDefs,
149
+ registerTransform(def) {
150
+ transformDefs.set(def.id, def);
151
+ },
152
+ unregisterTransform(id) {
153
+ transformDefs.delete(id);
154
+ }
155
+ };
156
+ var TRANSFORM_DEFS = createServiceToken("transform.defs");
157
+ globalServiceRegistry.registerIfAbsent(TRANSFORM_DEFS, () => TransformRegistry.all, true);
158
+
52
159
  // src/task-graph/Dataflow.ts
53
160
  var DATAFLOW_ALL_PORTS = "*";
54
161
  var DATAFLOW_ERROR_PORT = "[error]";
@@ -178,18 +285,62 @@ class Dataflow {
178
285
  return result;
179
286
  }
180
287
  toJSON() {
181
- return {
288
+ const base = {
182
289
  sourceTaskId: this.sourceTaskId,
183
290
  sourceTaskPortId: this.sourceTaskPortId,
184
291
  targetTaskId: this.targetTaskId,
185
292
  targetTaskPortId: this.targetTaskPortId
186
293
  };
294
+ if (this._transforms.length > 0) {
295
+ base.transforms = this._transforms.map((s) => ({ id: s.id, params: s.params }));
296
+ }
297
+ return base;
187
298
  }
188
299
  _compatibilityCache;
300
+ _transforms = [];
301
+ getTransforms() {
302
+ return this._transforms;
303
+ }
304
+ setTransforms(steps) {
305
+ this._transforms = steps.map((s) => ({ id: s.id, params: s.params }));
306
+ this.invalidateCompatibilityCache();
307
+ }
308
+ addTransform(step) {
309
+ this._transforms.push({ id: step.id, params: step.params });
310
+ this.invalidateCompatibilityCache();
311
+ }
312
+ removeTransform(index) {
313
+ this._transforms.splice(index, 1);
314
+ this.invalidateCompatibilityCache();
315
+ }
316
+ async applyTransforms(registry) {
317
+ if (this._transforms.length === 0)
318
+ return;
319
+ const defs = registry.get(TRANSFORM_DEFS);
320
+ let cur = this.value;
321
+ try {
322
+ for (const step of this._transforms) {
323
+ const def = defs.get(step.id);
324
+ if (!def) {
325
+ throw new Error(`Unknown transform: ${step.id}`);
326
+ }
327
+ cur = await def.apply(cur, step.params ?? {});
328
+ }
329
+ this.value = cur;
330
+ } catch (e) {
331
+ const error = e instanceof TaskError ? e : new TaskError(e instanceof Error ? e.message : String(e));
332
+ if (!(e instanceof TaskError) && e instanceof Error && e.stack) {
333
+ error.stack = e.stack;
334
+ }
335
+ this.error = error;
336
+ this.setStatus(TaskStatus.FAILED);
337
+ throw e;
338
+ }
339
+ }
189
340
  invalidateCompatibilityCache() {
190
341
  this._compatibilityCache = undefined;
191
342
  }
192
- semanticallyCompatible(graph, dataflow) {
343
+ semanticallyCompatible(graph, dataflow, registry) {
193
344
  const sourceTask = graph.getTask(dataflow.sourceTaskId);
194
345
  const targetTask = graph.getTask(dataflow.targetTaskId);
195
346
  const shouldCache = !(sourceTask.constructor.hasDynamicSchemas ?? true) && !(targetTask.constructor.hasDynamicSchemas ?? true);
@@ -218,7 +369,26 @@ class Dataflow {
218
369
  if (sourceSchemaProperty === undefined && sourceSchema.additionalProperties === true) {
219
370
  sourceSchemaProperty = true;
220
371
  }
221
- const result = areSemanticallyCompatible(sourceSchemaProperty, targetSchemaProperty);
372
+ let effectiveSourceSchema = sourceSchemaProperty;
373
+ if (this._transforms.length > 0) {
374
+ const defs = registry ? registry.get(TRANSFORM_DEFS) : TransformRegistry.all;
375
+ try {
376
+ let cur = effectiveSourceSchema === true ? {} : effectiveSourceSchema;
377
+ for (const step of this._transforms) {
378
+ const def = defs.get(step.id);
379
+ if (!def) {
380
+ return "incompatible";
381
+ }
382
+ cur = def.inferOutputSchema(cur, step.params ?? {});
383
+ }
384
+ effectiveSourceSchema = cur;
385
+ } catch {
386
+ if (shouldCache)
387
+ this._compatibilityCache = "incompatible";
388
+ return "incompatible";
389
+ }
390
+ }
391
+ const result = areSemanticallyCompatible(effectiveSourceSchema, targetSchemaProperty);
222
392
  if (shouldCache) {
223
393
  this._compatibilityCache = result;
224
394
  }
@@ -637,7 +807,8 @@ function computeGraphInputSchema(graph, options) {
637
807
  for (const [inputName, inputProp] of Object.entries(taskProperties)) {
638
808
  if (!properties[inputName]) {
639
809
  properties[inputName] = inputProp;
640
- if (taskInputSchema.required && taskInputSchema.required.includes(inputName)) {
810
+ const isRequired = taskInputSchema.required?.includes(inputName) === true && !(task.defaults && task.defaults[inputName] !== undefined);
811
+ if (isRequired) {
641
812
  required.push(inputName);
642
813
  }
643
814
  if (trackOrigins) {
@@ -950,6 +1121,7 @@ import { DirectedAcyclicGraph } from "@workglow/util/graph";
950
1121
 
951
1122
  // src/task/GraphAsTask.ts
952
1123
  import { getLogger as getLogger4 } from "@workglow/util";
1124
+ import { CycleError } from "@workglow/util/graph";
953
1125
  import { compileSchema as compileSchema2 } from "@workglow/util/schema";
954
1126
 
955
1127
  // src/task-graph/TaskGraphRunner.ts
@@ -957,15 +1129,15 @@ import {
957
1129
  collectPropertyValues,
958
1130
  getLogger as getLogger3,
959
1131
  getTelemetryProvider as getTelemetryProvider2,
960
- globalServiceRegistry as globalServiceRegistry2,
1132
+ globalServiceRegistry as globalServiceRegistry3,
961
1133
  ServiceRegistry as ServiceRegistry2,
962
1134
  SpanStatusCode as SpanStatusCode2,
963
1135
  uuid4 as uuid43
964
1136
  } from "@workglow/util";
965
1137
 
966
1138
  // src/storage/TaskOutputRepository.ts
967
- import { createServiceToken, EventEmitter as EventEmitter2 } from "@workglow/util";
968
- var TASK_OUTPUT_REPOSITORY = createServiceToken("taskgraph.taskOutputRepository");
1139
+ import { createServiceToken as createServiceToken2, EventEmitter as EventEmitter2 } from "@workglow/util";
1140
+ var TASK_OUTPUT_REPOSITORY = createServiceToken2("taskgraph.taskOutputRepository");
969
1141
 
970
1142
  class TaskOutputRepository {
971
1143
  outputCompression;
@@ -1067,103 +1239,11 @@ function getNestedValue(obj, path) {
1067
1239
  import { deepEqual, EventEmitter as EventEmitter3, uuid4 as uuid42 } from "@workglow/util";
1068
1240
  import { compileSchema } from "@workglow/util/schema";
1069
1241
 
1070
- // src/task/TaskError.ts
1071
- import { BaseError } from "@workglow/util";
1072
-
1073
- class TaskError extends BaseError {
1074
- static type = "TaskError";
1075
- taskType;
1076
- taskId;
1077
- constructor(message) {
1078
- super(message);
1079
- }
1080
- }
1081
-
1082
- class TaskConfigurationError extends TaskError {
1083
- static type = "TaskConfigurationError";
1084
- constructor(message) {
1085
- super(message);
1086
- }
1087
- }
1088
-
1089
- class WorkflowError extends TaskError {
1090
- static type = "WorkflowError";
1091
- constructor(message) {
1092
- super(message);
1093
- }
1094
- }
1095
-
1096
- class TaskAbortedError extends TaskError {
1097
- static type = "TaskAbortedError";
1098
- constructor(message = "Task aborted") {
1099
- super(message);
1100
- }
1101
- }
1102
-
1103
- class TaskTimeoutError extends TaskAbortedError {
1104
- static type = "TaskTimeoutError";
1105
- constructor(timeoutMs) {
1106
- super(timeoutMs ? `Task timed out after ${timeoutMs}ms` : "Task timed out");
1107
- }
1108
- }
1109
-
1110
- class TaskGraphTimeoutError extends TaskTimeoutError {
1111
- static type = "TaskGraphTimeoutError";
1112
- constructor(timeoutMs) {
1113
- super(timeoutMs);
1114
- this.message = timeoutMs ? `Graph execution timed out after ${timeoutMs}ms` : "Graph execution timed out";
1115
- }
1116
- }
1117
-
1118
- class TaskFailedError extends TaskError {
1119
- static type = "TaskFailedError";
1120
- constructor(message = "Task failed") {
1121
- super(message);
1122
- }
1123
- }
1124
-
1125
- class JobTaskFailedError extends TaskFailedError {
1126
- static type = "JobTaskFailedError";
1127
- jobError;
1128
- constructor(err) {
1129
- super(String(err));
1130
- this.jobError = err;
1131
- }
1132
- }
1133
-
1134
- class TaskJSONError extends TaskError {
1135
- static type = "TaskJSONError";
1136
- constructor(message = "Error converting JSON to a Task") {
1137
- super(message);
1138
- }
1139
- }
1140
-
1141
- class TaskInvalidInputError extends TaskError {
1142
- static type = "TaskInvalidInputError";
1143
- constructor(message = "Invalid input data") {
1144
- super(message);
1145
- }
1146
- }
1147
-
1148
- class TaskEntitlementError extends TaskError {
1149
- static type = "TaskEntitlementError";
1150
- constructor(message = "Required entitlements denied") {
1151
- super(message);
1152
- }
1153
- }
1154
-
1155
- class TaskSerializationError extends TaskError {
1156
- static type = "TaskSerializationError";
1157
- constructor(taskType) {
1158
- super(`Task "${taskType}" cannot be serialized: config contains non-serializable values. ` + `Use a declarative config alternative or remove function-valued config properties.`);
1159
- }
1160
- }
1161
-
1162
1242
  // src/task/TaskRunner.ts
1163
1243
  import {
1164
1244
  getLogger,
1165
1245
  getTelemetryProvider,
1166
- globalServiceRegistry,
1246
+ globalServiceRegistry as globalServiceRegistry2,
1167
1247
  SpanStatusCode
1168
1248
  } from "@workglow/util";
1169
1249
 
@@ -1279,7 +1359,7 @@ class TaskRunner {
1279
1359
  task;
1280
1360
  abortController;
1281
1361
  outputCache;
1282
- registry = globalServiceRegistry;
1362
+ registry = globalServiceRegistry2;
1283
1363
  resourceScope;
1284
1364
  inputStreams;
1285
1365
  timeoutTimer;
@@ -1550,7 +1630,7 @@ class TaskRunner {
1550
1630
  });
1551
1631
  const cache = config.outputCache ?? this.task.runConfig?.outputCache;
1552
1632
  if (cache === true) {
1553
- let instance = globalServiceRegistry.get(TASK_OUTPUT_REPOSITORY);
1633
+ let instance = globalServiceRegistry2.get(TASK_OUTPUT_REPOSITORY);
1554
1634
  this.outputCache = instance;
1555
1635
  } else if (cache === false) {
1556
1636
  this.outputCache = undefined;
@@ -2437,7 +2517,7 @@ class ConditionalTask extends Task {
2437
2517
  }
2438
2518
 
2439
2519
  // src/task/EntitlementEnforcer.ts
2440
- import { createServiceToken as createServiceToken3 } from "@workglow/util";
2520
+ import { createServiceToken as createServiceToken4 } from "@workglow/util";
2441
2521
 
2442
2522
  // src/task/EntitlementPolicy.ts
2443
2523
  var EMPTY_POLICY = Object.freeze({
@@ -2474,9 +2554,14 @@ function evaluatePolicy(policy, required) {
2474
2554
  }
2475
2555
  return results;
2476
2556
  }
2557
+ function can(policy, id, resources) {
2558
+ const required = resources !== undefined ? { id, resources } : { id };
2559
+ const [result] = evaluatePolicy(policy, { entitlements: [required] });
2560
+ return result;
2561
+ }
2477
2562
 
2478
2563
  // src/task/EntitlementResolver.ts
2479
- import { createServiceToken as createServiceToken2 } from "@workglow/util";
2564
+ import { createServiceToken as createServiceToken3 } from "@workglow/util";
2480
2565
  var PERMISSIVE_RESOLVER = {
2481
2566
  lookup: () => "grant",
2482
2567
  prompt: async () => "grant",
@@ -2487,9 +2572,19 @@ var DENY_ALL_RESOLVER = {
2487
2572
  prompt: async () => "deny",
2488
2573
  save: () => {}
2489
2574
  };
2490
- var ENTITLEMENT_RESOLVER = createServiceToken2("workglow.entitlementResolver");
2575
+ var ENTITLEMENT_RESOLVER = createServiceToken3("workglow.entitlementResolver");
2491
2576
 
2492
2577
  // src/task/EntitlementEnforcer.ts
2578
+ function formatEntitlementDenial(denial) {
2579
+ switch (denial.reason) {
2580
+ case "policy-deny":
2581
+ return `${denial.entitlement.id} (denied by rule ${denial.matchedRule.id})`;
2582
+ case "user-deny":
2583
+ return `${denial.entitlement.id} (denied by user)`;
2584
+ case "default-deny":
2585
+ return `${denial.entitlement.id} (no matching grant)`;
2586
+ }
2587
+ }
2493
2588
  var PERMISSIVE_ENFORCER = {
2494
2589
  checkAll: async () => [],
2495
2590
  checkTask: async () => []
@@ -2500,24 +2595,35 @@ function createPolicyEnforcer(policy, resolver = PERMISSIVE_RESOLVER) {
2500
2595
  const denied = [];
2501
2596
  for (const result of results) {
2502
2597
  if (result.verdict === "denied") {
2503
- denied.push(result.entitlement);
2598
+ if (result.matchedRule) {
2599
+ denied.push({
2600
+ entitlement: result.entitlement,
2601
+ reason: "policy-deny",
2602
+ matchedRule: result.matchedRule
2603
+ });
2604
+ } else {
2605
+ denied.push({ entitlement: result.entitlement, reason: "default-deny" });
2606
+ }
2504
2607
  } else if (result.verdict === "ask") {
2505
2608
  const request = {
2506
2609
  entitlement: result.entitlement,
2507
2610
  taskType: taskType ?? "unknown",
2508
2611
  taskId: taskId ?? "unknown"
2509
2612
  };
2510
- const saved = resolver.lookup(request);
2511
- if (saved !== undefined) {
2512
- if (saved === "deny") {
2513
- denied.push(result.entitlement);
2514
- }
2515
- continue;
2613
+ let answer = resolver.lookup(request);
2614
+ if (answer === undefined) {
2615
+ answer = await resolver.prompt(request);
2616
+ resolver.save(request, answer);
2516
2617
  }
2517
- const answer = await resolver.prompt(request);
2518
- resolver.save(request, answer);
2519
2618
  if (answer === "deny") {
2520
- denied.push(result.entitlement);
2619
+ if (!result.matchedRule) {
2620
+ throw new Error(`Invariant violation: ask verdict for "${result.entitlement.id}" is missing matchedRule`);
2621
+ }
2622
+ denied.push({
2623
+ entitlement: result.entitlement,
2624
+ reason: "user-deny",
2625
+ matchedRule: result.matchedRule
2626
+ });
2521
2627
  }
2522
2628
  }
2523
2629
  }
@@ -2539,7 +2645,7 @@ function createScopedEnforcer(grants) {
2539
2645
  function createGrantListEnforcer(grants) {
2540
2646
  return createScopedEnforcer(grants.map((id) => ({ id })));
2541
2647
  }
2542
- var ENTITLEMENT_ENFORCER = createServiceToken3("workglow.entitlementEnforcer");
2648
+ var ENTITLEMENT_ENFORCER = createServiceToken4("workglow.entitlementEnforcer");
2543
2649
 
2544
2650
  // src/task-graph/TaskGraphScheduler.ts
2545
2651
  class TopologicalScheduler {
@@ -2703,7 +2809,7 @@ class TaskGraphRunner {
2703
2809
  graph;
2704
2810
  outputCache;
2705
2811
  accumulateLeafOutputs = true;
2706
- registry = globalServiceRegistry2;
2812
+ registry = globalServiceRegistry3;
2707
2813
  resourceScope;
2708
2814
  abortController;
2709
2815
  inProgressTasks = new Map;
@@ -2785,6 +2891,21 @@ class TaskGraphRunner {
2785
2891
  }
2786
2892
  async runGraphReactive(input = {}, config) {
2787
2893
  await this.handleStartReactive(config);
2894
+ const telemetry = getTelemetryProvider2();
2895
+ const telemetryEnabled = telemetry.isEnabled;
2896
+ const reactiveRunId = telemetryEnabled ? uuid43() : "";
2897
+ let reactiveSpan;
2898
+ if (telemetryEnabled) {
2899
+ reactiveSpan = telemetry.startSpan("workglow.graph.runReactive", {
2900
+ attributes: {
2901
+ "workglow.graph.reactive.run_id": reactiveRunId,
2902
+ "workglow.graph.task_count": this.graph.getTasks().length,
2903
+ "workglow.graph.dataflow_count": this.graph.getDataflows().length
2904
+ }
2905
+ });
2906
+ }
2907
+ const t0 = telemetryEnabled ? performance.now() : 0;
2908
+ const taskTimings = [];
2788
2909
  const results = [];
2789
2910
  try {
2790
2911
  for await (const task of this.reactiveScheduler.tasks()) {
@@ -2794,20 +2915,68 @@ class TaskGraphRunner {
2794
2915
  this.copyInputFromEdgesToNode(task);
2795
2916
  }
2796
2917
  const taskInput = isRootTask ? input : {};
2797
- const taskResult = await task.runReactive(taskInput);
2798
- await this.pushOutputFromNodeToEdges(task, taskResult);
2799
- if (this.graph.getTargetDataflows(task.id).length === 0) {
2800
- results.push({
2801
- id: task.id,
2802
- type: task.constructor.runtype || task.constructor.type,
2803
- data: taskResult
2804
- });
2918
+ if (telemetryEnabled) {
2919
+ const taskType = String(task.constructor.runtype || task.constructor.type || "?");
2920
+ const tReactive = performance.now();
2921
+ const taskResult = await task.runReactive(taskInput);
2922
+ const runReactiveMs = performance.now() - tReactive;
2923
+ const tPush = performance.now();
2924
+ await this.pushOutputFromNodeToEdges(task, taskResult);
2925
+ const pushOutputMs = performance.now() - tPush;
2926
+ taskTimings.push({ id: task.id, type: taskType, runReactiveMs, pushOutputMs });
2927
+ if (this.graph.getTargetDataflows(task.id).length === 0) {
2928
+ results.push({
2929
+ id: task.id,
2930
+ type: task.constructor.runtype || task.constructor.type,
2931
+ data: taskResult
2932
+ });
2933
+ }
2934
+ } else {
2935
+ const taskResult = await task.runReactive(taskInput);
2936
+ await this.pushOutputFromNodeToEdges(task, taskResult);
2937
+ if (this.graph.getTargetDataflows(task.id).length === 0) {
2938
+ results.push({
2939
+ id: task.id,
2940
+ type: task.constructor.runtype || task.constructor.type,
2941
+ data: taskResult
2942
+ });
2943
+ }
2805
2944
  }
2806
2945
  }
2807
2946
  await this.handleCompleteReactive();
2947
+ if (reactiveSpan) {
2948
+ const totalMs = performance.now() - t0;
2949
+ reactiveSpan.setAttributes({
2950
+ "workglow.graph.reactive.duration_ms": Math.round(totalMs * 1000) / 1000,
2951
+ "workglow.graph.reactive.tasks_executed": taskTimings.length
2952
+ });
2953
+ reactiveSpan.setStatus(SpanStatusCode2.OK);
2954
+ reactiveSpan.end();
2955
+ getLogger3().debug("task graph runReactive timings", {
2956
+ reactiveRunId,
2957
+ totalMs: Math.round(totalMs * 1000) / 1000,
2958
+ taskTimings
2959
+ });
2960
+ }
2808
2961
  return this.filterLeafResults(results);
2809
2962
  } catch (error) {
2810
2963
  await this.handleErrorReactive();
2964
+ if (reactiveSpan) {
2965
+ const totalMs = performance.now() - t0;
2966
+ const message = error instanceof Error ? error.message : String(error);
2967
+ reactiveSpan.setAttributes({
2968
+ "workglow.graph.reactive.duration_ms": Math.round(totalMs * 1000) / 1000,
2969
+ "workglow.graph.reactive.tasks_executed": taskTimings.length
2970
+ });
2971
+ reactiveSpan.setStatus(SpanStatusCode2.ERROR, message);
2972
+ reactiveSpan.end();
2973
+ getLogger3().debug("task graph runReactive failed", {
2974
+ reactiveRunId,
2975
+ totalMs: Math.round(totalMs * 1000) / 1000,
2976
+ taskTimings,
2977
+ error
2978
+ });
2979
+ }
2811
2980
  throw error;
2812
2981
  }
2813
2982
  }
@@ -2875,13 +3044,17 @@ class TaskGraphRunner {
2875
3044
  async pushOutputFromNodeToEdges(node, results) {
2876
3045
  const dataflows = this.graph.getTargetDataflows(node.id);
2877
3046
  for (const dataflow of dataflows) {
2878
- const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow);
3047
+ if (dataflow.stream !== undefined)
3048
+ continue;
3049
+ const compatibility = dataflow.semanticallyCompatible(this.graph, dataflow, this.registry);
2879
3050
  if (compatibility === "static") {
2880
3051
  dataflow.setPortData(results);
3052
+ await dataflow.applyTransforms(this.registry);
2881
3053
  } else if (compatibility === "runtime") {
2882
3054
  const task = this.graph.getTask(dataflow.targetTaskId);
2883
3055
  const narrowed = await task.narrowInput({ ...results }, this.registry);
2884
3056
  dataflow.setPortData(narrowed);
3057
+ await dataflow.applyTransforms(this.registry);
2885
3058
  } else {
2886
3059
  const resultsKeys = Object.keys(results);
2887
3060
  if (resultsKeys.length > 0) {
@@ -2907,6 +3080,8 @@ class TaskGraphRunner {
2907
3080
  }
2908
3081
  const activeBranches = node.getActiveBranches();
2909
3082
  for (const dataflow of dataflows) {
3083
+ if (dataflow.status === TaskStatus.FAILED)
3084
+ continue;
2910
3085
  const branchId = portToBranch.get(dataflow.sourceTaskPortId);
2911
3086
  if (branchId !== undefined) {
2912
3087
  if (activeBranches.has(branchId)) {
@@ -2922,6 +3097,8 @@ class TaskGraphRunner {
2922
3097
  return;
2923
3098
  }
2924
3099
  dataflows.forEach((dataflow) => {
3100
+ if (dataflow.status === TaskStatus.FAILED)
3101
+ return;
2925
3102
  dataflow.setStatus(effectiveStatus);
2926
3103
  });
2927
3104
  }
@@ -3025,7 +3202,7 @@ class TaskGraphRunner {
3025
3202
  if (this.activeEnforcer && task.constructor.hasDynamicEntitlements) {
3026
3203
  const denied = await this.activeEnforcer.checkTask(task);
3027
3204
  if (denied.length > 0) {
3028
- throw new TaskEntitlementError(`Task ${task.constructor.type} denied entitlements: ${denied.map((e) => e.id).join(", ")}`);
3205
+ throw new TaskEntitlementError(`Task ${task.constructor.type} denied entitlements: ${denied.map(formatEntitlementDenial).join(", ")}`);
3029
3206
  }
3030
3207
  }
3031
3208
  if (isStreamable) {
@@ -3046,10 +3223,13 @@ class TaskGraphRunner {
3046
3223
  }
3047
3224
  async awaitStreamInputs(task) {
3048
3225
  const dataflows = this.graph.getSourceDataflows(task.id);
3049
- const streamPromises = dataflows.filter((df) => df.stream !== undefined).map((df) => df.awaitStreamValue());
3050
- if (streamPromises.length > 0) {
3051
- await Promise.all(streamPromises);
3052
- }
3226
+ const streamingDataflows = dataflows.filter((df) => df.stream !== undefined);
3227
+ if (streamingDataflows.length === 0)
3228
+ return;
3229
+ await Promise.all(streamingDataflows.map(async (df) => {
3230
+ await df.awaitStreamValue();
3231
+ await df.applyTransforms(this.registry);
3232
+ }));
3053
3233
  }
3054
3234
  async runStreamingTask(task, input) {
3055
3235
  const streamMode = getOutputStreamMode(task.outputSchema());
@@ -3184,7 +3364,7 @@ class TaskGraphRunner {
3184
3364
  if (config?.registry !== undefined) {
3185
3365
  this.registry = config.registry;
3186
3366
  } else if (this.registry === undefined) {
3187
- this.registry = new ServiceRegistry2(globalServiceRegistry2.container.createChildContainer());
3367
+ this.registry = new ServiceRegistry2(globalServiceRegistry3.container.createChildContainer());
3188
3368
  }
3189
3369
  if (config?.resourceScope !== undefined) {
3190
3370
  this.resourceScope = config.resourceScope;
@@ -3248,7 +3428,7 @@ class TaskGraphRunner {
3248
3428
  const enforcer = this.registry.get(ENTITLEMENT_ENFORCER);
3249
3429
  const denied = await enforcer.checkAll(computeGraphEntitlements(this.graph));
3250
3430
  if (denied.length > 0) {
3251
- throw new TaskEntitlementError(`Denied entitlements: ${denied.map((e) => e.id).join(", ")}`);
3431
+ throw new TaskEntitlementError(`Denied entitlements: ${denied.map(formatEntitlementDenial).join(", ")}`);
3252
3432
  }
3253
3433
  this.activeEnforcer = enforcer;
3254
3434
  } else {
@@ -3592,6 +3772,18 @@ class GraphAsTask extends Task {
3592
3772
  yield { type: "finish", data: input };
3593
3773
  }
3594
3774
  }
3775
+ validateAcyclic() {
3776
+ if (!this.hasChildren())
3777
+ return;
3778
+ if (!this.subGraph.isAcyclic()) {
3779
+ throw new CycleError(`${this.type} (${this.id}): subgraph contains a cycle \u2014 loop tasks must wrap an acyclic subgraph.`);
3780
+ }
3781
+ for (const child of this.subGraph.getTasks()) {
3782
+ if (child instanceof GraphAsTask) {
3783
+ child.validateAcyclic();
3784
+ }
3785
+ }
3786
+ }
3595
3787
  regenerateGraph() {
3596
3788
  this._inputSchemaNode = undefined;
3597
3789
  this.events.emit("regenerate");
@@ -3742,7 +3934,7 @@ function convertPipeFunctionToTask(fn, config) {
3742
3934
  properties: {
3743
3935
  [DATAFLOW_ALL_PORTS]: {}
3744
3936
  },
3745
- additionalProperties: false
3937
+ additionalProperties: true
3746
3938
  };
3747
3939
  };
3748
3940
  static outputSchema = () => {
@@ -3751,7 +3943,7 @@ function convertPipeFunctionToTask(fn, config) {
3751
3943
  properties: {
3752
3944
  [DATAFLOW_ALL_PORTS]: {}
3753
3945
  },
3754
- additionalProperties: false
3946
+ additionalProperties: true
3755
3947
  };
3756
3948
  };
3757
3949
  static cacheable = false;
@@ -3860,6 +4052,9 @@ class TaskGraph {
3860
4052
  topologicallySortedNodes() {
3861
4053
  return this._dag.topologicallySortedNodes();
3862
4054
  }
4055
+ isAcyclic() {
4056
+ return this._dag.isAcyclic();
4057
+ }
3863
4058
  addTask(task, config) {
3864
4059
  return this._dag.addNode(ensureTask(task, config));
3865
4060
  }
@@ -4133,7 +4328,315 @@ function serialGraph(tasks, inputHandle, outputHandle) {
4133
4328
  return graph;
4134
4329
  }
4135
4330
  // src/task-graph/Workflow.ts
4136
- import { EventEmitter as EventEmitter5, getLogger as getLogger5, uuid4 as uuid45 } from "@workglow/util";
4331
+ import { EventEmitter as EventEmitter5, getLogger as getLogger5, uuid4 as uuid46 } from "@workglow/util";
4332
+
4333
+ // src/task-graph/autoConnect.ts
4334
+ function autoConnect(graph, sourceTask, targetTask, options) {
4335
+ const matches = new Map;
4336
+ const sourceSchema = sourceTask.outputSchema();
4337
+ const targetSchema = targetTask.inputSchema();
4338
+ const providedInputKeys = options?.providedInputKeys ?? new Set;
4339
+ const connectedInputKeys = options?.connectedInputKeys ?? new Set;
4340
+ const earlierTasks = options?.earlierTasks ?? [];
4341
+ const dryRun = options?.dryRun ?? false;
4342
+ const addDataflow = (df) => {
4343
+ if (!dryRun)
4344
+ graph.addDataflow(df);
4345
+ };
4346
+ const getSpecificTypeIdentifiers = (schema) => {
4347
+ const formats = new Set;
4348
+ const ids = new Set;
4349
+ if (typeof schema === "boolean") {
4350
+ return { formats, ids };
4351
+ }
4352
+ const extractFromSchema = (s) => {
4353
+ if (!s || typeof s !== "object" || Array.isArray(s))
4354
+ return;
4355
+ if (s.format)
4356
+ formats.add(s.format);
4357
+ if (s.$id)
4358
+ ids.add(s.$id);
4359
+ };
4360
+ extractFromSchema(schema);
4361
+ const checkUnion = (schemas) => {
4362
+ if (!schemas)
4363
+ return;
4364
+ for (const s of schemas) {
4365
+ if (typeof s === "boolean")
4366
+ continue;
4367
+ extractFromSchema(s);
4368
+ if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
4369
+ extractFromSchema(s.items);
4370
+ }
4371
+ }
4372
+ };
4373
+ checkUnion(schema.oneOf);
4374
+ checkUnion(schema.anyOf);
4375
+ if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
4376
+ extractFromSchema(schema.items);
4377
+ }
4378
+ return { formats, ids };
4379
+ };
4380
+ const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
4381
+ if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
4382
+ return fromPortOutputSchema === true && toPortInputSchema === true;
4383
+ }
4384
+ const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
4385
+ const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
4386
+ for (const format of outputIds.formats) {
4387
+ if (inputIds.formats.has(format)) {
4388
+ return true;
4389
+ }
4390
+ }
4391
+ for (const id of outputIds.ids) {
4392
+ if (inputIds.ids.has(id)) {
4393
+ return true;
4394
+ }
4395
+ }
4396
+ if (requireSpecificType) {
4397
+ return false;
4398
+ }
4399
+ const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
4400
+ if (!idTypeBlank)
4401
+ return false;
4402
+ if (fromPortOutputSchema.type === toPortInputSchema.type)
4403
+ return true;
4404
+ const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
4405
+ if (typeof schema === "boolean")
4406
+ return schema;
4407
+ return schema.type === fromPortOutputSchema.type;
4408
+ }) ?? false;
4409
+ const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
4410
+ if (typeof schema === "boolean")
4411
+ return schema;
4412
+ return schema.type === fromPortOutputSchema.type;
4413
+ }) ?? false;
4414
+ return matchesOneOf || matchesAnyOf;
4415
+ };
4416
+ const makeMatch = (fromSchema, toSchema, fromTaskId, toTaskId, comparator) => {
4417
+ if (typeof fromSchema === "object") {
4418
+ if (toSchema === true || typeof toSchema === "object" && toSchema.additionalProperties === true) {
4419
+ const outputKeys = Object.keys(fromSchema.properties || {});
4420
+ if (outputKeys.length > 0) {
4421
+ for (const fromOutputPortId of outputKeys) {
4422
+ if (matches.has(fromOutputPortId))
4423
+ continue;
4424
+ matches.set(fromOutputPortId, fromOutputPortId);
4425
+ addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, fromOutputPortId));
4426
+ }
4427
+ } else if (fromSchema.additionalProperties === true) {
4428
+ const sourceGraphTask = graph.getTask(fromTaskId);
4429
+ if (sourceGraphTask && sourceGraphTask.constructor.passthroughInputsToOutputs === true) {
4430
+ const incomingDfs = graph.getSourceDataflows(fromTaskId);
4431
+ for (const df of incomingDfs) {
4432
+ const portId = df.targetTaskPortId;
4433
+ if (portId === DATAFLOW_ALL_PORTS)
4434
+ continue;
4435
+ if (matches.has(portId))
4436
+ continue;
4437
+ if (connectedInputKeys.has(portId))
4438
+ continue;
4439
+ matches.set(portId, portId);
4440
+ addDataflow(new Dataflow(fromTaskId, portId, toTaskId, portId));
4441
+ }
4442
+ }
4443
+ }
4444
+ return;
4445
+ }
4446
+ }
4447
+ if (typeof fromSchema === "object" && fromSchema.additionalProperties === true && typeof toSchema === "object" && (sourceTask.type === "InputTask" || sourceTask.type === "OutputTask")) {
4448
+ for (const toInputPortId of Object.keys(toSchema.properties || {})) {
4449
+ if (matches.has(toInputPortId))
4450
+ continue;
4451
+ if (connectedInputKeys.has(toInputPortId))
4452
+ continue;
4453
+ matches.set(toInputPortId, toInputPortId);
4454
+ addDataflow(new Dataflow(fromTaskId, toInputPortId, toTaskId, toInputPortId));
4455
+ }
4456
+ return;
4457
+ }
4458
+ if (typeof fromSchema === "boolean" || typeof toSchema === "boolean") {
4459
+ return;
4460
+ }
4461
+ for (const [toInputPortId, toPortInputSchema] of Object.entries(toSchema.properties || {})) {
4462
+ if (matches.has(toInputPortId))
4463
+ continue;
4464
+ if (connectedInputKeys.has(toInputPortId))
4465
+ continue;
4466
+ const candidates = [];
4467
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(fromSchema.properties || {})) {
4468
+ if (comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
4469
+ candidates.push(fromOutputPortId);
4470
+ }
4471
+ }
4472
+ if (candidates.length === 0)
4473
+ continue;
4474
+ let winner = candidates[0];
4475
+ if (candidates.length > 1) {
4476
+ const targetStreamMode = getPortStreamMode(toSchema, toInputPortId);
4477
+ const streamMatch = candidates.find((portId) => getPortStreamMode(fromSchema, portId) === targetStreamMode);
4478
+ if (streamMatch)
4479
+ winner = streamMatch;
4480
+ }
4481
+ matches.set(toInputPortId, winner);
4482
+ addDataflow(new Dataflow(fromTaskId, winner, toTaskId, toInputPortId));
4483
+ }
4484
+ };
4485
+ makeMatch(sourceSchema, targetSchema, sourceTask.id, targetTask.id, ([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
4486
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
4487
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
4488
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
4489
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
4490
+ });
4491
+ makeMatch(sourceSchema, targetSchema, sourceTask.id, targetTask.id, ([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
4492
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
4493
+ });
4494
+ const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
4495
+ const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r) && !connectedInputKeys.has(r));
4496
+ let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
4497
+ if (unmatchedRequired.length > 0 && earlierTasks.length > 0) {
4498
+ for (let i = 0;i < earlierTasks.length && unmatchedRequired.length > 0; i++) {
4499
+ const earlierTask = earlierTasks[i];
4500
+ const earlierOutputSchema = earlierTask.outputSchema();
4501
+ if (earlierTask.type === "InputTask") {
4502
+ const inputConfig = earlierTask.config;
4503
+ const inputSchema = inputConfig?.inputSchema ?? inputConfig?.outputSchema;
4504
+ const isManualSchema = inputSchema && typeof inputSchema === "object" && inputSchema["x-ui-manual"] === true;
4505
+ const inputProperties = isManualSchema && inputSchema && typeof inputSchema === "object" && "properties" in inputSchema && inputSchema.properties && typeof inputSchema.properties === "object" ? new Set(Object.keys(inputSchema.properties)) : undefined;
4506
+ for (const requiredInputId of [...unmatchedRequired]) {
4507
+ if (matches.has(requiredInputId))
4508
+ continue;
4509
+ if (inputProperties && !inputProperties.has(requiredInputId))
4510
+ continue;
4511
+ matches.set(requiredInputId, requiredInputId);
4512
+ addDataflow(new Dataflow(earlierTask.id, requiredInputId, targetTask.id, requiredInputId));
4513
+ }
4514
+ unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
4515
+ continue;
4516
+ }
4517
+ const makeMatchFromEarlier = (comparator) => {
4518
+ if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
4519
+ return;
4520
+ }
4521
+ for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
4522
+ for (const requiredInputId of unmatchedRequired) {
4523
+ const toPortInputSchema = targetSchema.properties?.[requiredInputId];
4524
+ if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
4525
+ matches.set(requiredInputId, fromOutputPortId);
4526
+ addDataflow(new Dataflow(earlierTask.id, fromOutputPortId, targetTask.id, requiredInputId));
4527
+ }
4528
+ }
4529
+ }
4530
+ };
4531
+ makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
4532
+ const outputPortIdMatch = fromOutputPortId === toInputPortId;
4533
+ const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
4534
+ const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
4535
+ return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
4536
+ });
4537
+ makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
4538
+ return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
4539
+ });
4540
+ unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
4541
+ }
4542
+ }
4543
+ const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
4544
+ if (stillUnmatchedRequired.length > 0) {
4545
+ return {
4546
+ matches,
4547
+ error: `Could not find matches for required inputs [${stillUnmatchedRequired.join(", ")}] of ${targetTask.type}. ` + `Attempted to match from ${sourceTask.type} and earlier tasks.`,
4548
+ unmatchedRequired: stillUnmatchedRequired
4549
+ };
4550
+ }
4551
+ if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
4552
+ const existingTargetConnections = graph.getSourceDataflows(targetTask.id);
4553
+ if (existingTargetConnections.length > 0) {
4554
+ return { matches, unmatchedRequired: [] };
4555
+ }
4556
+ const hasRequiredInputs = requiredInputs.size > 0;
4557
+ const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
4558
+ const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
4559
+ if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
4560
+ return {
4561
+ matches,
4562
+ 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.`,
4563
+ unmatchedRequired: []
4564
+ };
4565
+ }
4566
+ }
4567
+ return {
4568
+ matches,
4569
+ unmatchedRequired: []
4570
+ };
4571
+ }
4572
+
4573
+ // src/task-graph/ConditionalBuilder.ts
4574
+ import { uuid4 as uuid45 } from "@workglow/util";
4575
+ class ConditionalBuilder {
4576
+ workflow;
4577
+ condition;
4578
+ thenSpec;
4579
+ elseSpec;
4580
+ constructor(workflow, condition) {
4581
+ this.workflow = workflow;
4582
+ this.condition = condition;
4583
+ }
4584
+ then(taskClass, input, config) {
4585
+ this.thenSpec = { taskClass, input, config };
4586
+ return this;
4587
+ }
4588
+ else(taskClass, input, config) {
4589
+ this.elseSpec = { taskClass, input, config };
4590
+ return this;
4591
+ }
4592
+ endIf() {
4593
+ if (!this.thenSpec) {
4594
+ throw new WorkflowError(".endIf() called without a prior .then(...) call");
4595
+ }
4596
+ const thenPort = "then";
4597
+ const elsePort = "else";
4598
+ const branches = [
4599
+ {
4600
+ id: thenPort,
4601
+ condition: this.condition,
4602
+ outputPort: thenPort
4603
+ }
4604
+ ];
4605
+ if (this.elseSpec) {
4606
+ branches.push({
4607
+ id: elsePort,
4608
+ condition: (input) => !this.condition(input),
4609
+ outputPort: elsePort
4610
+ });
4611
+ }
4612
+ const conditionalTask = new ConditionalTask({
4613
+ id: uuid45(),
4614
+ branches,
4615
+ exclusive: true,
4616
+ defaultBranch: this.elseSpec ? elsePort : undefined
4617
+ });
4618
+ this.workflow.graph.addTask(conditionalTask);
4619
+ const thenTask = instantiate(this.thenSpec);
4620
+ this.workflow.graph.addTask(thenTask);
4621
+ this.workflow.graph.addDataflow(new Dataflow(conditionalTask.id, thenPort, thenTask.id, "*"));
4622
+ if (this.elseSpec) {
4623
+ const elseTask = instantiate(this.elseSpec);
4624
+ this.workflow.graph.addTask(elseTask);
4625
+ this.workflow.graph.addDataflow(new Dataflow(conditionalTask.id, elsePort, elseTask.id, "*"));
4626
+ }
4627
+ return this.workflow;
4628
+ }
4629
+ }
4630
+ function instantiate(spec) {
4631
+ const config = {
4632
+ id: uuid45(),
4633
+ ...spec.config,
4634
+ defaults: spec.input
4635
+ };
4636
+ return new spec.taskClass(config);
4637
+ }
4638
+
4639
+ // src/task-graph/Workflow.ts
4137
4640
  function getLastTask(workflow) {
4138
4641
  const tasks = workflow.graph.getTasks();
4139
4642
  return tasks.length > 0 ? tasks[tasks.length - 1] : undefined;
@@ -4276,7 +4779,7 @@ class Workflow {
4276
4779
  this._error = "";
4277
4780
  const parent = getLastTask(this);
4278
4781
  const task = this.addTaskToGraph(taskClass, {
4279
- id: uuid45(),
4782
+ id: uuid46(),
4280
4783
  ...config,
4281
4784
  defaults: input
4282
4785
  });
@@ -4430,8 +4933,10 @@ class Workflow {
4430
4933
  static parallel(args, mergeFn) {
4431
4934
  return parallel(args, mergeFn ?? PROPERTY_ARRAY, new Workflow);
4432
4935
  }
4433
- rename(source, target, index = -1) {
4936
+ rename(source, target, indexOrOptions = -1) {
4434
4937
  this._error = "";
4938
+ const index = typeof indexOrOptions === "number" ? indexOrOptions : indexOrOptions.index ?? -1;
4939
+ const transforms = typeof indexOrOptions === "number" ? undefined : indexOrOptions.transforms;
4435
4940
  const nodes = this._graph.getTasks();
4436
4941
  if (-index > nodes.length) {
4437
4942
  const errorMsg = `Back index greater than number of tasks`;
@@ -4454,7 +4959,10 @@ class Workflow {
4454
4959
  getLogger5().error(this._error);
4455
4960
  throw new WorkflowError(errorMsg);
4456
4961
  }
4457
- this._dataFlows.push(new Dataflow(lastNode.id, source, undefined, target));
4962
+ const df = new Dataflow(lastNode.id, source, undefined, target);
4963
+ if (transforms && transforms.length > 0)
4964
+ df.setTransforms(transforms);
4965
+ this._dataFlows.push(df);
4458
4966
  return this;
4459
4967
  }
4460
4968
  onError(handler) {
@@ -4557,7 +5065,11 @@ class Workflow {
4557
5065
  addLoopTask(taskClass, config = {}) {
4558
5066
  this._error = "";
4559
5067
  const parent = getLastTask(this);
4560
- const task = this.addTaskToGraph(taskClass, { id: uuid45(), ...config });
5068
+ const schema = taskClass.configSchema?.();
5069
+ const required = typeof schema === "object" && schema !== null ? schema.required : undefined;
5070
+ const needsMaxIterations = Array.isArray(required) && required.includes("maxIterations");
5071
+ const resolvedConfig = needsMaxIterations && config.maxIterations === undefined ? { ...config, maxIterations: "unbounded" } : config;
5072
+ const task = this.addTaskToGraph(taskClass, { id: uuid46(), ...resolvedConfig });
4561
5073
  if (this._dataFlows.length > 0) {
4562
5074
  this._dataFlows.forEach((dataflow) => {
4563
5075
  const taskSchema = task.inputSchema();
@@ -4577,6 +5089,9 @@ class Workflow {
4577
5089
  }
4578
5090
  return loopBuilder;
4579
5091
  }
5092
+ if(condition) {
5093
+ return new ConditionalBuilder(this, condition);
5094
+ }
4580
5095
  autoConnectLoopTask(pending) {
4581
5096
  if (!pending)
4582
5097
  return;
@@ -4697,243 +5212,14 @@ class Workflow {
4697
5212
  }
4698
5213
  static AutoConnectOptions = Symbol("AutoConnectOptions");
4699
5214
  static autoConnect(graph, sourceTask, targetTask, options) {
4700
- const matches = new Map;
4701
- const sourceSchema = sourceTask.outputSchema();
4702
- const targetSchema = targetTask.inputSchema();
4703
- const providedInputKeys = options?.providedInputKeys ?? new Set;
4704
- const connectedInputKeys = options?.connectedInputKeys ?? new Set;
4705
- const earlierTasks = options?.earlierTasks ?? [];
4706
- const getSpecificTypeIdentifiers = (schema) => {
4707
- const formats = new Set;
4708
- const ids = new Set;
4709
- if (typeof schema === "boolean") {
4710
- return { formats, ids };
4711
- }
4712
- const extractFromSchema = (s) => {
4713
- if (!s || typeof s !== "object" || Array.isArray(s))
4714
- return;
4715
- if (s.format)
4716
- formats.add(s.format);
4717
- if (s.$id)
4718
- ids.add(s.$id);
4719
- };
4720
- extractFromSchema(schema);
4721
- const checkUnion = (schemas) => {
4722
- if (!schemas)
4723
- return;
4724
- for (const s of schemas) {
4725
- if (typeof s === "boolean")
4726
- continue;
4727
- extractFromSchema(s);
4728
- if (s.items && typeof s.items === "object" && !Array.isArray(s.items)) {
4729
- extractFromSchema(s.items);
4730
- }
4731
- }
4732
- };
4733
- checkUnion(schema.oneOf);
4734
- checkUnion(schema.anyOf);
4735
- if (schema.items && typeof schema.items === "object" && !Array.isArray(schema.items)) {
4736
- extractFromSchema(schema.items);
4737
- }
4738
- return { formats, ids };
4739
- };
4740
- const isTypeCompatible = (fromPortOutputSchema, toPortInputSchema, requireSpecificType = false) => {
4741
- if (typeof fromPortOutputSchema === "boolean" || typeof toPortInputSchema === "boolean") {
4742
- return fromPortOutputSchema === true && toPortInputSchema === true;
4743
- }
4744
- const outputIds = getSpecificTypeIdentifiers(fromPortOutputSchema);
4745
- const inputIds = getSpecificTypeIdentifiers(toPortInputSchema);
4746
- for (const format of outputIds.formats) {
4747
- if (inputIds.formats.has(format)) {
4748
- return true;
4749
- }
4750
- }
4751
- for (const id of outputIds.ids) {
4752
- if (inputIds.ids.has(id)) {
4753
- return true;
4754
- }
4755
- }
4756
- if (requireSpecificType) {
4757
- return false;
4758
- }
4759
- const idTypeBlank = fromPortOutputSchema.$id === undefined && toPortInputSchema.$id === undefined;
4760
- if (!idTypeBlank)
4761
- return false;
4762
- if (fromPortOutputSchema.type === toPortInputSchema.type)
4763
- return true;
4764
- const matchesOneOf = toPortInputSchema.oneOf?.some((schema) => {
4765
- if (typeof schema === "boolean")
4766
- return schema;
4767
- return schema.type === fromPortOutputSchema.type;
4768
- }) ?? false;
4769
- const matchesAnyOf = toPortInputSchema.anyOf?.some((schema) => {
4770
- if (typeof schema === "boolean")
4771
- return schema;
4772
- return schema.type === fromPortOutputSchema.type;
4773
- }) ?? false;
4774
- return matchesOneOf || matchesAnyOf;
4775
- };
4776
- const makeMatch = (fromSchema, toSchema, fromTaskId, toTaskId, comparator) => {
4777
- if (typeof fromSchema === "object") {
4778
- if (toSchema === true || typeof toSchema === "object" && toSchema.additionalProperties === true) {
4779
- const outputKeys = Object.keys(fromSchema.properties || {});
4780
- if (outputKeys.length > 0) {
4781
- for (const fromOutputPortId of outputKeys) {
4782
- if (matches.has(fromOutputPortId))
4783
- continue;
4784
- matches.set(fromOutputPortId, fromOutputPortId);
4785
- graph.addDataflow(new Dataflow(fromTaskId, fromOutputPortId, toTaskId, fromOutputPortId));
4786
- }
4787
- } else if (fromSchema.additionalProperties === true) {
4788
- const sourceGraphTask = graph.getTask(fromTaskId);
4789
- if (sourceGraphTask && sourceGraphTask.constructor.passthroughInputsToOutputs === true) {
4790
- const incomingDfs = graph.getSourceDataflows(fromTaskId);
4791
- for (const df of incomingDfs) {
4792
- const portId = df.targetTaskPortId;
4793
- if (portId === DATAFLOW_ALL_PORTS)
4794
- continue;
4795
- if (matches.has(portId))
4796
- continue;
4797
- if (connectedInputKeys.has(portId))
4798
- continue;
4799
- matches.set(portId, portId);
4800
- graph.addDataflow(new Dataflow(fromTaskId, portId, toTaskId, portId));
4801
- }
4802
- }
4803
- }
4804
- return;
4805
- }
4806
- }
4807
- if (typeof fromSchema === "object" && fromSchema.additionalProperties === true && typeof toSchema === "object" && (sourceTask.type === "InputTask" || sourceTask.type === "OutputTask")) {
4808
- for (const toInputPortId of Object.keys(toSchema.properties || {})) {
4809
- if (matches.has(toInputPortId))
4810
- continue;
4811
- if (connectedInputKeys.has(toInputPortId))
4812
- continue;
4813
- matches.set(toInputPortId, toInputPortId);
4814
- graph.addDataflow(new Dataflow(fromTaskId, toInputPortId, toTaskId, toInputPortId));
4815
- }
4816
- return;
4817
- }
4818
- if (typeof fromSchema === "boolean" || typeof toSchema === "boolean") {
4819
- return;
4820
- }
4821
- for (const [toInputPortId, toPortInputSchema] of Object.entries(toSchema.properties || {})) {
4822
- if (matches.has(toInputPortId))
4823
- continue;
4824
- if (connectedInputKeys.has(toInputPortId))
4825
- continue;
4826
- const candidates = [];
4827
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(fromSchema.properties || {})) {
4828
- if (comparator([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema])) {
4829
- candidates.push(fromOutputPortId);
4830
- }
4831
- }
4832
- if (candidates.length === 0)
4833
- continue;
4834
- let winner = candidates[0];
4835
- if (candidates.length > 1) {
4836
- const targetStreamMode = getPortStreamMode(toSchema, toInputPortId);
4837
- const streamMatch = candidates.find((portId) => getPortStreamMode(fromSchema, portId) === targetStreamMode);
4838
- if (streamMatch)
4839
- winner = streamMatch;
4840
- }
4841
- matches.set(toInputPortId, winner);
4842
- graph.addDataflow(new Dataflow(fromTaskId, winner, toTaskId, toInputPortId));
4843
- }
4844
- };
4845
- makeMatch(sourceSchema, targetSchema, sourceTask.id, targetTask.id, ([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
4846
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
4847
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
4848
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
4849
- return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
4850
- });
4851
- makeMatch(sourceSchema, targetSchema, sourceTask.id, targetTask.id, ([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
4852
- return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
4853
- });
4854
- const requiredInputs = new Set(typeof targetSchema === "object" ? targetSchema.required || [] : []);
4855
- const requiredInputsNeedingConnection = [...requiredInputs].filter((r) => !providedInputKeys.has(r) && !connectedInputKeys.has(r));
4856
- let unmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
4857
- if (unmatchedRequired.length > 0 && earlierTasks.length > 0) {
4858
- for (let i = 0;i < earlierTasks.length && unmatchedRequired.length > 0; i++) {
4859
- const earlierTask = earlierTasks[i];
4860
- const earlierOutputSchema = earlierTask.outputSchema();
4861
- if (earlierTask.type === "InputTask") {
4862
- const inputConfig = earlierTask.config;
4863
- const inputSchema = inputConfig?.inputSchema ?? inputConfig?.outputSchema;
4864
- const isManualSchema = inputSchema && typeof inputSchema === "object" && inputSchema["x-ui-manual"] === true;
4865
- const inputProperties = isManualSchema && inputSchema && typeof inputSchema === "object" && "properties" in inputSchema && inputSchema.properties && typeof inputSchema.properties === "object" ? new Set(Object.keys(inputSchema.properties)) : undefined;
4866
- for (const requiredInputId of [...unmatchedRequired]) {
4867
- if (matches.has(requiredInputId))
4868
- continue;
4869
- if (inputProperties && !inputProperties.has(requiredInputId))
4870
- continue;
4871
- matches.set(requiredInputId, requiredInputId);
4872
- graph.addDataflow(new Dataflow(earlierTask.id, requiredInputId, targetTask.id, requiredInputId));
4873
- }
4874
- unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
4875
- continue;
4876
- }
4877
- const makeMatchFromEarlier = (comparator) => {
4878
- if (typeof earlierOutputSchema === "boolean" || typeof targetSchema === "boolean") {
4879
- return;
4880
- }
4881
- for (const [fromOutputPortId, fromPortOutputSchema] of Object.entries(earlierOutputSchema.properties || {})) {
4882
- for (const requiredInputId of unmatchedRequired) {
4883
- const toPortInputSchema = targetSchema.properties?.[requiredInputId];
4884
- if (!matches.has(requiredInputId) && toPortInputSchema && comparator([fromOutputPortId, fromPortOutputSchema], [requiredInputId, toPortInputSchema])) {
4885
- matches.set(requiredInputId, fromOutputPortId);
4886
- graph.addDataflow(new Dataflow(earlierTask.id, fromOutputPortId, targetTask.id, requiredInputId));
4887
- }
4888
- }
4889
- }
4890
- };
4891
- makeMatchFromEarlier(([fromOutputPortId, fromPortOutputSchema], [toInputPortId, toPortInputSchema]) => {
4892
- const outputPortIdMatch = fromOutputPortId === toInputPortId;
4893
- const outputPortIdOutputInput = fromOutputPortId === "output" && toInputPortId === "input";
4894
- const portIdsCompatible = outputPortIdMatch || outputPortIdOutputInput;
4895
- return portIdsCompatible && isTypeCompatible(fromPortOutputSchema, toPortInputSchema, false);
4896
- });
4897
- makeMatchFromEarlier(([_fromOutputPortId, fromPortOutputSchema], [_toInputPortId, toPortInputSchema]) => {
4898
- return isTypeCompatible(fromPortOutputSchema, toPortInputSchema, true);
4899
- });
4900
- unmatchedRequired = unmatchedRequired.filter((r) => !matches.has(r));
4901
- }
4902
- }
4903
- const stillUnmatchedRequired = requiredInputsNeedingConnection.filter((r) => !matches.has(r));
4904
- if (stillUnmatchedRequired.length > 0) {
4905
- return {
4906
- matches,
4907
- error: `Could not find matches for required inputs [${stillUnmatchedRequired.join(", ")}] of ${targetTask.type}. ` + `Attempted to match from ${sourceTask.type} and earlier tasks.`,
4908
- unmatchedRequired: stillUnmatchedRequired
4909
- };
4910
- }
4911
- if (matches.size === 0 && requiredInputsNeedingConnection.length === 0) {
4912
- const existingTargetConnections = graph.getSourceDataflows(targetTask.id);
4913
- if (existingTargetConnections.length > 0) {
4914
- return { matches, unmatchedRequired: [] };
4915
- }
4916
- const hasRequiredInputs = requiredInputs.size > 0;
4917
- const allRequiredInputsProvided = hasRequiredInputs && [...requiredInputs].every((r) => providedInputKeys.has(r));
4918
- const hasInputsWithDefaults = typeof targetSchema === "object" && targetSchema.properties && Object.values(targetSchema.properties).some((prop) => prop && typeof prop === "object" && ("default" in prop));
4919
- if (!allRequiredInputsProvided && !hasInputsWithDefaults) {
4920
- return {
4921
- matches,
4922
- 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.`,
4923
- unmatchedRequired: []
4924
- };
4925
- }
4926
- }
4927
- return {
4928
- matches,
4929
- unmatchedRequired: []
4930
- };
5215
+ return autoConnect(graph, sourceTask, targetTask, options);
4931
5216
  }
4932
5217
  finalizeTemplate() {
4933
5218
  if (!this._iteratorTask || this.graph.getTasks().length === 0) {
4934
5219
  return;
4935
5220
  }
4936
5221
  this._iteratorTask.subGraph = this.graph;
5222
+ this._iteratorTask.validateAcyclic();
4937
5223
  }
4938
5224
  finalizeAndReturn() {
4939
5225
  if (!this._parentWorkflow) {
@@ -4968,6 +5254,7 @@ function getMethodNameMap() {
4968
5254
  }
4969
5255
  var LOOP_TASK_TYPES = {
4970
5256
  MapTask: { method: "map", endMethod: "endMap" },
5257
+ ForEachTask: { method: "forEach", endMethod: "endForEach" },
4971
5258
  ReduceTask: { method: "reduce", endMethod: "endReduce" },
4972
5259
  WhileTask: { method: "while", endMethod: "endWhile" },
4973
5260
  GraphAsTask: { method: "group", endMethod: "endGroup" }
@@ -5150,7 +5437,8 @@ function extractLoopConfig(task) {
5150
5437
  }
5151
5438
  break;
5152
5439
  }
5153
- case "MapTask": {
5440
+ case "MapTask":
5441
+ case "ForEachTask": {
5154
5442
  if (rawConfig.preserveOrder !== undefined && rawConfig.preserveOrder !== true) {
5155
5443
  config.preserveOrder = rawConfig.preserveOrder;
5156
5444
  }
@@ -5163,16 +5451,22 @@ function extractLoopConfig(task) {
5163
5451
  if (rawConfig.batchSize !== undefined) {
5164
5452
  config.batchSize = rawConfig.batchSize;
5165
5453
  }
5454
+ if (rawConfig.maxIterations !== undefined) {
5455
+ config.maxIterations = rawConfig.maxIterations;
5456
+ }
5166
5457
  break;
5167
5458
  }
5168
5459
  case "ReduceTask": {
5169
5460
  if (rawConfig.initialValue !== undefined) {
5170
5461
  config.initialValue = rawConfig.initialValue;
5171
5462
  }
5463
+ if (rawConfig.maxIterations !== undefined) {
5464
+ config.maxIterations = rawConfig.maxIterations;
5465
+ }
5172
5466
  break;
5173
5467
  }
5174
5468
  case "WhileTask": {
5175
- if (rawConfig.maxIterations !== undefined && rawConfig.maxIterations !== 100) {
5469
+ if (rawConfig.maxIterations !== undefined) {
5176
5470
  config.maxIterations = rawConfig.maxIterations;
5177
5471
  }
5178
5472
  if (rawConfig.chainIterations !== undefined && rawConfig.chainIterations !== true) {
@@ -5282,6 +5576,304 @@ ${baseIndent}}`;
5282
5576
  function resetMethodNameCache() {
5283
5577
  methodNameCache = undefined;
5284
5578
  }
5579
+ // src/task-graph/transforms/pick.ts
5580
+ import { areSemanticallyCompatible as areSemanticallyCompatible2 } from "@workglow/util/schema";
5581
+ function walk(value, path) {
5582
+ if (value == null)
5583
+ return;
5584
+ const parts = path.split(".");
5585
+ let cur = value;
5586
+ for (const p of parts) {
5587
+ if (cur == null)
5588
+ return;
5589
+ cur = cur[p];
5590
+ }
5591
+ return cur;
5592
+ }
5593
+ function walkSchema(schema, path) {
5594
+ const parts = path.split(".");
5595
+ let cur = schema;
5596
+ for (const p of parts) {
5597
+ if (!cur || typeof cur !== "object")
5598
+ return {};
5599
+ if (cur.type !== "object" || !cur.properties || !cur.properties[p]) {
5600
+ return {};
5601
+ }
5602
+ cur = cur.properties[p];
5603
+ }
5604
+ return cur;
5605
+ }
5606
+ var pickTransform = {
5607
+ id: "pick",
5608
+ title: "Pick field",
5609
+ category: "Structural",
5610
+ paramsSchema: {
5611
+ type: "object",
5612
+ properties: {
5613
+ path: { type: "string", description: "Dotted property path" }
5614
+ },
5615
+ required: ["path"]
5616
+ },
5617
+ inferOutputSchema(inputSchema, params) {
5618
+ return walkSchema(inputSchema, params.path);
5619
+ },
5620
+ apply(value, params) {
5621
+ return walk(value, params.path);
5622
+ },
5623
+ suggestFromSchemas(source, target) {
5624
+ if (source.type !== "object" || !source.properties) {
5625
+ return;
5626
+ }
5627
+ const props = source.properties;
5628
+ for (const [name, propSchema] of Object.entries(props)) {
5629
+ const compat = areSemanticallyCompatible2(propSchema, target);
5630
+ if (compat === "static")
5631
+ return { score: 1, params: { path: name } };
5632
+ if (compat === "runtime")
5633
+ return { score: 0.6, params: { path: name } };
5634
+ }
5635
+ return;
5636
+ }
5637
+ };
5638
+
5639
+ // src/task-graph/transforms/index-access.ts
5640
+ import { areSemanticallyCompatible as areSemanticallyCompatible3 } from "@workglow/util/schema";
5641
+ function doIndex(value, idx) {
5642
+ if (!Array.isArray(value))
5643
+ return;
5644
+ const i = idx < 0 ? value.length + idx : idx;
5645
+ return value[i];
5646
+ }
5647
+ var indexTransform = {
5648
+ id: "index",
5649
+ title: "Array index",
5650
+ category: "Structural",
5651
+ paramsSchema: {
5652
+ type: "object",
5653
+ properties: {
5654
+ index: { type: "integer", description: "Array index (negative counts from end)" }
5655
+ },
5656
+ required: ["index"]
5657
+ },
5658
+ inferOutputSchema(inputSchema) {
5659
+ const s = inputSchema;
5660
+ if (s?.type === "array" && s.items)
5661
+ return s.items;
5662
+ return {};
5663
+ },
5664
+ apply(value, params) {
5665
+ return doIndex(value, params.index);
5666
+ },
5667
+ suggestFromSchemas(source, target) {
5668
+ const s = source;
5669
+ if (s?.type !== "array" || !s.items)
5670
+ return;
5671
+ const compat = areSemanticallyCompatible3(s.items, target);
5672
+ if (compat === "static")
5673
+ return { score: 0.9, params: { index: 0 } };
5674
+ if (compat === "runtime")
5675
+ return { score: 0.5, params: { index: 0 } };
5676
+ return;
5677
+ }
5678
+ };
5679
+
5680
+ // src/task-graph/transforms/coalesce.ts
5681
+ function stripNullable(schema) {
5682
+ const s = schema;
5683
+ if (!s || typeof s !== "object")
5684
+ return schema;
5685
+ if (Array.isArray(s.type)) {
5686
+ const nonNull = s.type.filter((t) => t !== "null");
5687
+ if (nonNull.length === 1)
5688
+ return { ...s, type: nonNull[0] };
5689
+ return { ...s, type: nonNull };
5690
+ }
5691
+ return schema;
5692
+ }
5693
+ var coalesceTransform = {
5694
+ id: "coalesce",
5695
+ title: "Coalesce null",
5696
+ category: "Conversion",
5697
+ paramsSchema: {
5698
+ type: "object",
5699
+ properties: { defaultValue: {} },
5700
+ required: ["defaultValue"]
5701
+ },
5702
+ inferOutputSchema: (input) => stripNullable(input),
5703
+ apply: (v, { defaultValue }) => v == null ? defaultValue : v
5704
+ };
5705
+
5706
+ // src/task-graph/transforms/string-casts.ts
5707
+ var stringSchema = { type: "string" };
5708
+ var uppercaseTransform = {
5709
+ id: "uppercase",
5710
+ title: "Uppercase",
5711
+ category: "String",
5712
+ paramsSchema: undefined,
5713
+ inferOutputSchema: () => stringSchema,
5714
+ apply: (v) => String(v ?? "").toUpperCase()
5715
+ };
5716
+ var lowercaseTransform = {
5717
+ id: "lowercase",
5718
+ title: "Lowercase",
5719
+ category: "String",
5720
+ paramsSchema: undefined,
5721
+ inferOutputSchema: () => stringSchema,
5722
+ apply: (v) => String(v ?? "").toLowerCase()
5723
+ };
5724
+ var truncateTransform = {
5725
+ id: "truncate",
5726
+ title: "Truncate",
5727
+ category: "String",
5728
+ paramsSchema: {
5729
+ type: "object",
5730
+ properties: { max: { type: "integer", minimum: 0 } },
5731
+ required: ["max"]
5732
+ },
5733
+ inferOutputSchema: () => stringSchema,
5734
+ apply: (v, { max }) => String(v ?? "").slice(0, max)
5735
+ };
5736
+ var substringTransform = {
5737
+ id: "substring",
5738
+ title: "Substring",
5739
+ category: "String",
5740
+ paramsSchema: {
5741
+ type: "object",
5742
+ properties: {
5743
+ start: { type: "integer" },
5744
+ end: { type: "integer" }
5745
+ },
5746
+ required: ["start"]
5747
+ },
5748
+ inferOutputSchema: () => stringSchema,
5749
+ apply: (v, { start, end }) => String(v ?? "").slice(start, end)
5750
+ };
5751
+
5752
+ // src/task-graph/transforms/date-conversions.ts
5753
+ var isoSchema = { type: "string", format: "date-time" };
5754
+ var numberSchema = { type: "number" };
5755
+ function hasDateTimeFormat(schema) {
5756
+ const s = schema;
5757
+ return s?.type === "string" && s.format === "date-time";
5758
+ }
5759
+ var unixToIsoDateTransform = {
5760
+ id: "unixToIsoDate",
5761
+ title: "Unix timestamp \u2192 ISO date",
5762
+ category: "Date",
5763
+ paramsSchema: {
5764
+ type: "object",
5765
+ properties: {
5766
+ unit: { type: "string", enum: ["s", "ms"] }
5767
+ },
5768
+ required: ["unit"]
5769
+ },
5770
+ inferOutputSchema: () => isoSchema,
5771
+ apply: (v, { unit }) => {
5772
+ const n = Number(v);
5773
+ return new Date(unit === "s" ? n * 1000 : n).toISOString();
5774
+ },
5775
+ suggestFromSchemas(source, target) {
5776
+ const s = source;
5777
+ if (s?.type !== "number" && s?.type !== "integer")
5778
+ return;
5779
+ if (!hasDateTimeFormat(target))
5780
+ return;
5781
+ return { score: 0.85, params: { unit: "s" } };
5782
+ }
5783
+ };
5784
+ var isoDateToUnixTransform = {
5785
+ id: "isoDateToUnix",
5786
+ title: "ISO date \u2192 Unix ms",
5787
+ category: "Date",
5788
+ paramsSchema: undefined,
5789
+ inferOutputSchema: () => numberSchema,
5790
+ apply: (v) => new Date(String(v)).getTime(),
5791
+ suggestFromSchemas(source, target) {
5792
+ if (!hasDateTimeFormat(source))
5793
+ return;
5794
+ const t = target;
5795
+ if (t?.type !== "number" && t?.type !== "integer")
5796
+ return;
5797
+ return { score: 0.9, params: {} };
5798
+ }
5799
+ };
5800
+
5801
+ // src/task-graph/transforms/scalar-conversions.ts
5802
+ var stringSchema2 = { type: "string" };
5803
+ var booleanSchema = { type: "boolean" };
5804
+ var numberToStringTransform = {
5805
+ id: "numberToString",
5806
+ title: "Number \u2192 String",
5807
+ category: "Conversion",
5808
+ paramsSchema: undefined,
5809
+ inferOutputSchema: () => stringSchema2,
5810
+ apply: (v) => String(v),
5811
+ suggestFromSchemas(source, target) {
5812
+ const s = source;
5813
+ const t = target;
5814
+ if ((s?.type === "number" || s?.type === "integer") && t?.type === "string") {
5815
+ return { score: 0.8, params: {} };
5816
+ }
5817
+ return;
5818
+ }
5819
+ };
5820
+ var toBooleanTransform = {
5821
+ id: "toBoolean",
5822
+ title: "To Boolean",
5823
+ category: "Conversion",
5824
+ paramsSchema: undefined,
5825
+ inferOutputSchema: () => booleanSchema,
5826
+ apply: (v) => {
5827
+ if (typeof v === "boolean")
5828
+ return v;
5829
+ if (typeof v === "number")
5830
+ return v !== 0;
5831
+ if (typeof v === "string")
5832
+ return v.toLowerCase() === "true" || v === "1";
5833
+ return Boolean(v);
5834
+ }
5835
+ };
5836
+ var stringifyTransform = {
5837
+ id: "stringify",
5838
+ title: "JSON.stringify",
5839
+ category: "Conversion",
5840
+ paramsSchema: undefined,
5841
+ inferOutputSchema: () => stringSchema2,
5842
+ apply: (v) => JSON.stringify(v),
5843
+ suggestFromSchemas(source, target) {
5844
+ const t = target;
5845
+ return t?.type === "string" ? { score: 0.4, params: {} } : undefined;
5846
+ }
5847
+ };
5848
+ var parseJsonTransform = {
5849
+ id: "parseJson",
5850
+ title: "Parse JSON",
5851
+ category: "Conversion",
5852
+ paramsSchema: undefined,
5853
+ inferOutputSchema: () => ({}),
5854
+ apply: (v) => JSON.parse(String(v))
5855
+ };
5856
+
5857
+ // src/task-graph/transforms/index.ts
5858
+ function registerBuiltInTransforms() {
5859
+ const all = [
5860
+ pickTransform,
5861
+ indexTransform,
5862
+ coalesceTransform,
5863
+ uppercaseTransform,
5864
+ lowercaseTransform,
5865
+ truncateTransform,
5866
+ substringTransform,
5867
+ unixToIsoDateTransform,
5868
+ isoDateToUnixTransform,
5869
+ numberToStringTransform,
5870
+ toBooleanTransform,
5871
+ stringifyTransform,
5872
+ parseJsonTransform
5873
+ ];
5874
+ for (const t of all)
5875
+ TransformRegistry.registerTransform(t);
5876
+ }
5285
5877
  // src/task/EntitlementProfiles.ts
5286
5878
  var BROWSER_GRANTS = [
5287
5879
  { id: Entitlements.NETWORK_HTTP },
@@ -5610,15 +6202,15 @@ async function compactSchemaInputs(input, schema, config, visited = new Set) {
5610
6202
  return compacted;
5611
6203
  }
5612
6204
  // src/task/IteratorTaskRunner.ts
5613
- import { uuid4 as uuid46 } from "@workglow/util";
6205
+ import { uuid4 as uuid47 } from "@workglow/util";
5614
6206
  class IteratorTaskRunner extends GraphAsTaskRunner {
5615
6207
  aggregatingParentMapProgress = false;
5616
6208
  mapPartialProgress = [];
5617
6209
  mapPartialIterationCount = 0;
5618
6210
  async executeTask(input) {
5619
6211
  let analysis = this.task.analyzeIterationInput(input);
5620
- const maxIterations = this.task.config.maxIterations;
5621
- if (maxIterations !== undefined && maxIterations > 0 && analysis.iterationCount > maxIterations) {
6212
+ const maxIterations = resolveIterationBound(this.task.config.maxIterations);
6213
+ if (analysis.iterationCount > maxIterations) {
5622
6214
  analysis = { ...analysis, iterationCount: maxIterations };
5623
6215
  }
5624
6216
  if (analysis.iterationCount === 0) {
@@ -5724,7 +6316,7 @@ class IteratorTaskRunner extends GraphAsTaskRunner {
5724
6316
  const idMap = new Map;
5725
6317
  for (const task of graph.getTasks()) {
5726
6318
  const ctor = task.constructor;
5727
- const newId = uuid46();
6319
+ const newId = uuid47();
5728
6320
  idMap.set(task.config.id, newId);
5729
6321
  const clonedConfig = { ...task.config, id: newId };
5730
6322
  const newTask = new ctor({ ...clonedConfig, defaults: task.defaults }, task.runConfig);
@@ -5794,15 +6386,24 @@ var ITERATOR_CONTEXT_SCHEMA = {
5794
6386
  }
5795
6387
  }
5796
6388
  };
6389
+ function resolveIterationBound(bound) {
6390
+ return bound === "unbounded" ? Number.POSITIVE_INFINITY : bound;
6391
+ }
5797
6392
  var iteratorTaskConfigSchema = {
5798
6393
  type: "object",
5799
6394
  properties: {
5800
6395
  ...graphAsTaskConfigSchema["properties"],
5801
6396
  concurrencyLimit: { type: "integer", minimum: 1 },
5802
6397
  batchSize: { type: "integer", minimum: 1 },
5803
- maxIterations: { type: "integer", minimum: 1 },
6398
+ maxIterations: {
6399
+ oneOf: [
6400
+ { type: "integer", minimum: 1 },
6401
+ { type: "string", const: "unbounded" }
6402
+ ]
6403
+ },
5804
6404
  iterationInputConfig: { type: "object", additionalProperties: true }
5805
6405
  },
6406
+ required: ["maxIterations"],
5806
6407
  additionalProperties: false
5807
6408
  };
5808
6409
  function isArrayVariant(schema) {
@@ -5917,6 +6518,12 @@ class IteratorTask extends GraphAsTask {
5917
6518
  static configSchema() {
5918
6519
  return iteratorTaskConfigSchema;
5919
6520
  }
6521
+ constructor(config = {}, runConfig = {}) {
6522
+ if (config.maxIterations === undefined) {
6523
+ throw new TaskConfigurationError(`${new.target.type ?? "IteratorTask"}: maxIterations is required. ` + `Pass a positive integer to cap iteration, or "unbounded" to opt out explicitly.`);
6524
+ }
6525
+ super(config, runConfig);
6526
+ }
5920
6527
  static getIterationContextSchema() {
5921
6528
  return ITERATOR_CONTEXT_SCHEMA;
5922
6529
  }
@@ -6306,13 +6913,19 @@ var whileTaskConfigSchema = {
6306
6913
  properties: {
6307
6914
  ...graphAsTaskConfigSchema["properties"],
6308
6915
  condition: {},
6309
- maxIterations: { type: "integer", minimum: 1 },
6916
+ maxIterations: {
6917
+ oneOf: [
6918
+ { type: "integer", minimum: 1 },
6919
+ { type: "string", const: "unbounded" }
6920
+ ]
6921
+ },
6310
6922
  chainIterations: { type: "boolean" },
6311
6923
  conditionField: { type: "string" },
6312
6924
  conditionOperator: { type: "string" },
6313
6925
  conditionValue: { type: "string" },
6314
6926
  iterationInputConfig: { type: "object", additionalProperties: true }
6315
6927
  },
6928
+ required: ["maxIterations"],
6316
6929
  additionalProperties: false
6317
6930
  };
6318
6931
 
@@ -6325,6 +6938,12 @@ class WhileTask extends GraphAsTask {
6325
6938
  static configSchema() {
6326
6939
  return whileTaskConfigSchema;
6327
6940
  }
6941
+ constructor(config = {}, runConfig = {}) {
6942
+ if (config.maxIterations === undefined) {
6943
+ throw new TaskConfigurationError(`${new.target.type ?? "WhileTask"}: maxIterations is required. ` + `Pass a positive integer to cap iteration, or "unbounded" to opt out explicitly.`);
6944
+ }
6945
+ super(config, runConfig);
6946
+ }
6328
6947
  static getIterationContextSchema() {
6329
6948
  return WHILE_CONTEXT_SCHEMA;
6330
6949
  }
@@ -6342,7 +6961,7 @@ class WhileTask extends GraphAsTask {
6342
6961
  return this.config.condition;
6343
6962
  }
6344
6963
  get maxIterations() {
6345
- return this.config.maxIterations ?? 100;
6964
+ return resolveIterationBound(this.config.maxIterations);
6346
6965
  }
6347
6966
  get chainIterations() {
6348
6967
  return this.config.chainIterations ?? true;
@@ -6873,8 +7492,8 @@ import {
6873
7492
  JobQueueServer
6874
7493
  } from "@workglow/job-queue";
6875
7494
  import { InMemoryQueueStorage } from "@workglow/storage";
6876
- import { createServiceToken as createServiceToken4, globalServiceRegistry as globalServiceRegistry3 } from "@workglow/util";
6877
- var JOB_QUEUE_FACTORY = createServiceToken4("taskgraph.jobQueueFactory");
7495
+ import { createServiceToken as createServiceToken5, globalServiceRegistry as globalServiceRegistry4 } from "@workglow/util";
7496
+ var JOB_QUEUE_FACTORY = createServiceToken5("taskgraph.jobQueueFactory");
6878
7497
  var defaultJobQueueFactory = async ({
6879
7498
  queueName,
6880
7499
  jobClass,
@@ -6901,7 +7520,7 @@ var defaultJobQueueFactory = async ({
6901
7520
  return { server, client, storage };
6902
7521
  };
6903
7522
  function registerJobQueueFactory(factory) {
6904
- globalServiceRegistry3.registerInstance(JOB_QUEUE_FACTORY, factory);
7523
+ globalServiceRegistry4.registerInstance(JOB_QUEUE_FACTORY, factory);
6905
7524
  }
6906
7525
  function createJobQueueFactoryWithOptions(defaultOptions = {}) {
6907
7526
  return async ({
@@ -6935,12 +7554,12 @@ function createJobQueueFactoryWithOptions(defaultOptions = {}) {
6935
7554
  };
6936
7555
  }
6937
7556
  function getJobQueueFactory() {
6938
- if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
7557
+ if (!globalServiceRegistry4.has(JOB_QUEUE_FACTORY)) {
6939
7558
  registerJobQueueFactory(defaultJobQueueFactory);
6940
7559
  }
6941
- return globalServiceRegistry3.get(JOB_QUEUE_FACTORY);
7560
+ return globalServiceRegistry4.get(JOB_QUEUE_FACTORY);
6942
7561
  }
6943
- if (!globalServiceRegistry3.has(JOB_QUEUE_FACTORY)) {
7562
+ if (!globalServiceRegistry4.has(JOB_QUEUE_FACTORY)) {
6944
7563
  registerJobQueueFactory(defaultJobQueueFactory);
6945
7564
  }
6946
7565
  // src/task/MapTask.ts
@@ -6949,8 +7568,10 @@ var mapTaskConfigSchema = {
6949
7568
  properties: {
6950
7569
  ...iteratorTaskConfigSchema["properties"],
6951
7570
  preserveOrder: { type: "boolean" },
6952
- flatten: { type: "boolean" }
7571
+ flatten: { type: "boolean" },
7572
+ discardResults: { type: "boolean" }
6953
7573
  },
7574
+ required: iteratorTaskConfigSchema.required,
6954
7575
  additionalProperties: false
6955
7576
  };
6956
7577
 
@@ -6983,6 +7604,9 @@ class MapTask extends IteratorTask {
6983
7604
  get flatten() {
6984
7605
  return this.config.flatten ?? false;
6985
7606
  }
7607
+ get discardResults() {
7608
+ return this.config.discardResults ?? false;
7609
+ }
6986
7610
  preserveIterationOrder() {
6987
7611
  return this.preserveOrder;
6988
7612
  }
@@ -7004,6 +7628,9 @@ class MapTask extends IteratorTask {
7004
7628
  return this.getWrappedOutputSchema();
7005
7629
  }
7006
7630
  collectResults(results) {
7631
+ if (this.discardResults) {
7632
+ return this.getEmptyResult();
7633
+ }
7007
7634
  const collected = super.collectResults(results);
7008
7635
  if (!this.flatten || typeof collected !== "object" || collected === null) {
7009
7636
  return collected;
@@ -7019,8 +7646,19 @@ class MapTask extends IteratorTask {
7019
7646
  return flattened;
7020
7647
  }
7021
7648
  }
7649
+
7650
+ class ForEachTask extends MapTask {
7651
+ static type = "ForEachTask";
7652
+ static title = "For Each";
7653
+ static description = "Runs a workflow per array item for side effects; discards collected results";
7654
+ constructor(config = {}, runConfig = {}) {
7655
+ super({ discardResults: true, ...config }, runConfig);
7656
+ }
7657
+ }
7022
7658
  Workflow.prototype.map = CreateLoopWorkflow(MapTask);
7023
7659
  Workflow.prototype.endMap = CreateEndLoopWorkflow("endMap");
7660
+ Workflow.prototype.forEach = CreateLoopWorkflow(ForEachTask);
7661
+ Workflow.prototype.endForEach = CreateEndLoopWorkflow("endForEach");
7024
7662
  // src/task/ReduceTask.ts
7025
7663
  var reduceTaskConfigSchema = {
7026
7664
  type: "object",
@@ -7028,6 +7666,7 @@ var reduceTaskConfigSchema = {
7028
7666
  ...iteratorTaskConfigSchema["properties"],
7029
7667
  initialValue: {}
7030
7668
  },
7669
+ required: iteratorTaskConfigSchema.required,
7031
7670
  additionalProperties: false
7032
7671
  };
7033
7672
 
@@ -7039,13 +7678,13 @@ class ReduceTask extends IteratorTask {
7039
7678
  static configSchema() {
7040
7679
  return reduceTaskConfigSchema;
7041
7680
  }
7042
- constructor(config = {}) {
7681
+ constructor(config = {}, runConfig = {}) {
7043
7682
  const reduceConfig = {
7044
7683
  ...config,
7045
7684
  concurrencyLimit: 1,
7046
7685
  batchSize: 1
7047
7686
  };
7048
- super(reduceConfig);
7687
+ super(reduceConfig, runConfig);
7049
7688
  }
7050
7689
  get initialValue() {
7051
7690
  return this.config.initialValue ?? {};
@@ -7117,9 +7756,9 @@ queueMicrotask(() => {
7117
7756
  });
7118
7757
  // src/task/TaskRegistry.ts
7119
7758
  import {
7120
- createServiceToken as createServiceToken5,
7759
+ createServiceToken as createServiceToken6,
7121
7760
  getLogger as getLogger6,
7122
- globalServiceRegistry as globalServiceRegistry4,
7761
+ globalServiceRegistry as globalServiceRegistry5,
7123
7762
  registerInputCompactor,
7124
7763
  registerInputResolver
7125
7764
  } from "@workglow/util";
@@ -7157,13 +7796,13 @@ var TaskRegistry = {
7157
7796
  registerTask,
7158
7797
  unregisterTask
7159
7798
  };
7160
- var TASK_CONSTRUCTORS = createServiceToken5("task.constructors");
7161
- globalServiceRegistry4.registerIfAbsent(TASK_CONSTRUCTORS, () => TaskRegistry.all, true);
7799
+ var TASK_CONSTRUCTORS = createServiceToken6("task.constructors");
7800
+ globalServiceRegistry5.registerIfAbsent(TASK_CONSTRUCTORS, () => TaskRegistry.all, true);
7162
7801
  function getGlobalTaskConstructors() {
7163
- return globalServiceRegistry4.get(TASK_CONSTRUCTORS);
7802
+ return globalServiceRegistry5.get(TASK_CONSTRUCTORS);
7164
7803
  }
7165
7804
  function setGlobalTaskConstructors(map) {
7166
- globalServiceRegistry4.registerInstance(TASK_CONSTRUCTORS, map);
7805
+ globalServiceRegistry5.registerInstance(TASK_CONSTRUCTORS, map);
7167
7806
  }
7168
7807
  function getTaskConstructors(registry) {
7169
7808
  if (!registry)
@@ -7260,7 +7899,11 @@ var createGraphFromGraphJSON = (graphJsonObj, registry, options) => {
7260
7899
  subGraph.addTask(createTaskFromGraphJSON(subitem, registry, options));
7261
7900
  }
7262
7901
  for (const subitem of graphJsonObj.dataflows) {
7263
- subGraph.addDataflow(new Dataflow(subitem.sourceTaskId, subitem.sourceTaskPortId, subitem.targetTaskId, subitem.targetTaskPortId));
7902
+ const dataflow = new Dataflow(subitem.sourceTaskId, subitem.sourceTaskPortId, subitem.targetTaskId, subitem.targetTaskPortId);
7903
+ if (subitem.transforms && subitem.transforms.length > 0) {
7904
+ dataflow.setTransforms(subitem.transforms);
7905
+ }
7906
+ subGraph.addDataflow(dataflow);
7264
7907
  }
7265
7908
  return subGraph;
7266
7909
  };
@@ -7318,8 +7961,8 @@ var registerBaseTasks = () => {
7318
7961
  return tasks;
7319
7962
  };
7320
7963
  // src/storage/TaskGraphRepository.ts
7321
- import { createServiceToken as createServiceToken6, EventEmitter as EventEmitter7 } from "@workglow/util";
7322
- var TASK_GRAPH_REPOSITORY = createServiceToken6("taskgraph.taskGraphRepository");
7964
+ import { createServiceToken as createServiceToken7, EventEmitter as EventEmitter7 } from "@workglow/util";
7965
+ var TASK_GRAPH_REPOSITORY = createServiceToken7("taskgraph.taskGraphRepository");
7323
7966
 
7324
7967
  class TaskGraphRepository {
7325
7968
  type = "TaskGraphRepository";
@@ -7479,7 +8122,13 @@ class TaskOutputTabularRepository extends TaskOutputRepository {
7479
8122
  export {
7480
8123
  wrapSchemaInArray,
7481
8124
  whileTaskConfigSchema,
8125
+ uppercaseTransform,
8126
+ unixToIsoDateTransform,
8127
+ truncateTransform,
8128
+ toBooleanTransform,
7482
8129
  taskPrototypeHasOwnExecute,
8130
+ substringTransform,
8131
+ stringifyTransform,
7483
8132
  setTaskQueueRegistry,
7484
8133
  setGlobalTaskConstructors,
7485
8134
  serialGraph,
@@ -7489,23 +8138,31 @@ export {
7489
8138
  scanGraphForCredentials,
7490
8139
  resourcePatternMatches,
7491
8140
  resolveSchemaInputs,
8141
+ resolveIterationBound,
7492
8142
  resetMethodNameCache,
7493
8143
  removeIterationProperties,
7494
8144
  registerJobQueueFactory,
8145
+ registerBuiltInTransforms,
7495
8146
  registerBaseTasks,
7496
8147
  reduceTaskConfigSchema,
7497
8148
  pipe,
8149
+ pickTransform,
8150
+ parseJsonTransform,
7498
8151
  parallel,
8152
+ numberToStringTransform,
7499
8153
  mergeResources,
7500
8154
  mergeEntitlements,
7501
8155
  mergeEntitlementPair,
7502
8156
  mergeChainedOutputToInput,
7503
8157
  mapTaskConfigSchema,
8158
+ lowercaseTransform,
7504
8159
  iteratorTaskConfigSchema,
8160
+ isoDateToUnixTransform,
7505
8161
  isTaskStreamable,
7506
8162
  isStrictArraySchema,
7507
8163
  isIterationProperty,
7508
8164
  isFlexibleSchema,
8165
+ indexTransform,
7509
8166
  hasVectorOutput,
7510
8167
  hasVectorLikeInput,
7511
8168
  hasStructuredOutput,
@@ -7531,6 +8188,7 @@ export {
7531
8188
  getFormatPrefix,
7532
8189
  getAppendPortId,
7533
8190
  formatValue,
8191
+ formatEntitlementDenial,
7534
8192
  findArrayPorts,
7535
8193
  filterIterationProperties,
7536
8194
  fallbackTaskConfigSchema,
@@ -7559,8 +8217,11 @@ export {
7559
8217
  computeGraphInputSchema,
7560
8218
  computeGraphEntitlements,
7561
8219
  compactSchemaInputs,
8220
+ coalesceTransform,
8221
+ can,
7562
8222
  calculateNodeDepths,
7563
8223
  buildIterationInputSchema,
8224
+ autoConnect,
7564
8225
  addIterationContextToSchema,
7565
8226
  addBoundaryNodesToGraphJson,
7566
8227
  addBoundaryNodesToDependencyJson,
@@ -7569,6 +8230,7 @@ export {
7569
8230
  WhileTaskRunner,
7570
8231
  WhileTask,
7571
8232
  WHILE_CONTEXT_SCHEMA,
8233
+ TransformRegistry,
7572
8234
  TaskTimeoutError,
7573
8235
  TaskStatus,
7574
8236
  TaskSerializationError,
@@ -7594,6 +8256,7 @@ export {
7594
8256
  TaskConfigSchema,
7595
8257
  TaskAbortedError,
7596
8258
  Task,
8259
+ TRANSFORM_DEFS,
7597
8260
  TASK_OUTPUT_REPOSITORY,
7598
8261
  TASK_GRAPH_REPOSITORY,
7599
8262
  TASK_CONSTRUCTORS,
@@ -7611,6 +8274,7 @@ export {
7611
8274
  GraphAsTaskRunner,
7612
8275
  GraphAsTask,
7613
8276
  GRAPH_RESULT_ARRAY,
8277
+ ForEachTask,
7614
8278
  FallbackTaskRunner,
7615
8279
  FallbackTask,
7616
8280
  EventTaskGraphToDagMapping,
@@ -7634,4 +8298,4 @@ export {
7634
8298
  BROWSER_GRANTS
7635
8299
  };
7636
8300
 
7637
- //# debugId=D70838C34E16DC2B64756E2164756E21
8301
+ //# debugId=2328438A94292C4964756E2164756E21