@lotiai/composer 0.2.0 → 0.2.1

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 (189) hide show
  1. package/dist/build-config/index.d.ts +52 -0
  2. package/dist/build-config/index.d.ts.map +1 -0
  3. package/dist/build-config/index.js +66 -0
  4. package/dist/build-config/index.js.map +1 -0
  5. package/dist/build-config/loader.d.ts +80 -0
  6. package/dist/build-config/loader.d.ts.map +1 -0
  7. package/dist/build-config/loader.js +238 -0
  8. package/dist/build-config/loader.js.map +1 -0
  9. package/dist/build-config/schema.d.ts +101 -0
  10. package/dist/build-config/schema.d.ts.map +1 -0
  11. package/dist/build-config/schema.js +94 -0
  12. package/dist/build-config/schema.js.map +1 -0
  13. package/dist/cli/commands/dev-temporal.d.ts +15 -0
  14. package/dist/cli/commands/dev-temporal.d.ts.map +1 -0
  15. package/dist/cli/commands/dev-temporal.js +68 -0
  16. package/dist/cli/commands/dev-temporal.js.map +1 -0
  17. package/dist/cli/commands/dev.d.ts +21 -0
  18. package/dist/cli/commands/dev.d.ts.map +1 -0
  19. package/dist/cli/commands/dev.js +421 -0
  20. package/dist/cli/commands/dev.js.map +1 -0
  21. package/dist/cli/commands/profiles.d.ts +15 -0
  22. package/dist/cli/commands/profiles.d.ts.map +1 -0
  23. package/dist/cli/commands/profiles.js +69 -0
  24. package/dist/cli/commands/profiles.js.map +1 -0
  25. package/dist/cli/index.d.ts +21 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +44 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/index.d.ts +15 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +57 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/internal/async/build/workflow-factory.d.ts +212 -0
  34. package/dist/internal/async/build/workflow-factory.d.ts.map +1 -0
  35. package/dist/internal/async/build/workflow-factory.js +533 -0
  36. package/dist/internal/async/build/workflow-factory.js.map +1 -0
  37. package/dist/internal/async/build-scripts/utils/common.d.ts +28 -0
  38. package/dist/internal/async/build-scripts/utils/common.d.ts.map +1 -0
  39. package/dist/internal/async/build-scripts/utils/common.js +50 -0
  40. package/dist/internal/async/build-scripts/utils/common.js.map +1 -0
  41. package/dist/internal/async/build-scripts/utils/import-module.d.ts +19 -0
  42. package/dist/internal/async/build-scripts/utils/import-module.d.ts.map +1 -0
  43. package/dist/internal/async/build-scripts/utils/import-module.js +80 -0
  44. package/dist/internal/async/build-scripts/utils/import-module.js.map +1 -0
  45. package/dist/internal/async/build-scripts/utils/type-guards.d.ts +123 -0
  46. package/dist/internal/async/build-scripts/utils/type-guards.d.ts.map +1 -0
  47. package/dist/internal/async/build-scripts/utils/type-guards.js +145 -0
  48. package/dist/internal/async/build-scripts/utils/type-guards.js.map +1 -0
  49. package/dist/internal/async/config/cleanup-policy.d.ts +50 -0
  50. package/dist/internal/async/config/cleanup-policy.d.ts.map +1 -0
  51. package/dist/internal/async/config/cleanup-policy.js +20 -0
  52. package/dist/internal/async/config/cleanup-policy.js.map +1 -0
  53. package/dist/internal/async/config/worker-profiles.d.ts +172 -0
  54. package/dist/internal/async/config/worker-profiles.d.ts.map +1 -0
  55. package/dist/internal/async/config/worker-profiles.js +275 -0
  56. package/dist/internal/async/config/worker-profiles.js.map +1 -0
  57. package/dist/internal/async/execute/temporal-client.d.ts +182 -0
  58. package/dist/internal/async/execute/temporal-client.d.ts.map +1 -0
  59. package/dist/internal/async/execute/temporal-client.js +190 -0
  60. package/dist/internal/async/execute/temporal-client.js.map +1 -0
  61. package/dist/internal/async/execute/workflow-execution.d.ts +98 -0
  62. package/dist/internal/async/execute/workflow-execution.d.ts.map +1 -0
  63. package/dist/internal/async/execute/workflow-execution.js +191 -0
  64. package/dist/internal/async/execute/workflow-execution.js.map +1 -0
  65. package/dist/internal/async/metrics/task-queue-metrics.d.ts +74 -0
  66. package/dist/internal/async/metrics/task-queue-metrics.d.ts.map +1 -0
  67. package/dist/internal/async/metrics/task-queue-metrics.js +155 -0
  68. package/dist/internal/async/metrics/task-queue-metrics.js.map +1 -0
  69. package/dist/internal/async/register/activity-worker.d.ts +123 -0
  70. package/dist/internal/async/register/activity-worker.d.ts.map +1 -0
  71. package/dist/internal/async/register/activity-worker.js +355 -0
  72. package/dist/internal/async/register/activity-worker.js.map +1 -0
  73. package/dist/internal/async/register/generate-workflow-source.d.ts +43 -0
  74. package/dist/internal/async/register/generate-workflow-source.d.ts.map +1 -0
  75. package/dist/internal/async/register/generate-workflow-source.js +193 -0
  76. package/dist/internal/async/register/generate-workflow-source.js.map +1 -0
  77. package/dist/internal/async/register/workflow-worker.d.ts +125 -0
  78. package/dist/internal/async/register/workflow-worker.d.ts.map +1 -0
  79. package/dist/internal/async/register/workflow-worker.js +215 -0
  80. package/dist/internal/async/register/workflow-worker.js.map +1 -0
  81. package/dist/internal/async/register-scripts/start-activity-worker.d.ts +70 -0
  82. package/dist/internal/async/register-scripts/start-activity-worker.d.ts.map +1 -0
  83. package/dist/internal/async/register-scripts/start-activity-worker.js +75 -0
  84. package/dist/internal/async/register-scripts/start-activity-worker.js.map +1 -0
  85. package/dist/internal/async/register-scripts/start-all-workers.d.ts +81 -0
  86. package/dist/internal/async/register-scripts/start-all-workers.d.ts.map +1 -0
  87. package/dist/internal/async/register-scripts/start-all-workers.js +84 -0
  88. package/dist/internal/async/register-scripts/start-all-workers.js.map +1 -0
  89. package/dist/internal/async/register-scripts/start-workflow-worker.d.ts +69 -0
  90. package/dist/internal/async/register-scripts/start-workflow-worker.d.ts.map +1 -0
  91. package/dist/internal/async/register-scripts/start-workflow-worker.js +74 -0
  92. package/dist/internal/async/register-scripts/start-workflow-worker.js.map +1 -0
  93. package/dist/internal/async/register-scripts/sync-schedules-cli.d.ts +28 -0
  94. package/dist/internal/async/register-scripts/sync-schedules-cli.d.ts.map +1 -0
  95. package/dist/internal/async/register-scripts/sync-schedules-cli.js +124 -0
  96. package/dist/internal/async/register-scripts/sync-schedules-cli.js.map +1 -0
  97. package/dist/internal/async/register-scripts/sync-schedules-via-lambda.d.ts +55 -0
  98. package/dist/internal/async/register-scripts/sync-schedules-via-lambda.d.ts.map +1 -0
  99. package/dist/internal/async/register-scripts/sync-schedules-via-lambda.js +146 -0
  100. package/dist/internal/async/register-scripts/sync-schedules-via-lambda.js.map +1 -0
  101. package/dist/internal/async/register-scripts/sync-schedules.d.ts +68 -0
  102. package/dist/internal/async/register-scripts/sync-schedules.d.ts.map +1 -0
  103. package/dist/internal/async/register-scripts/sync-schedules.js +83 -0
  104. package/dist/internal/async/register-scripts/sync-schedules.js.map +1 -0
  105. package/dist/internal/async/schedule/constants.d.ts +10 -0
  106. package/dist/internal/async/schedule/constants.d.ts.map +1 -0
  107. package/dist/internal/async/schedule/constants.js +13 -0
  108. package/dist/internal/async/schedule/constants.js.map +1 -0
  109. package/dist/internal/async/schedule/define-schedule.d.ts +192 -0
  110. package/dist/internal/async/schedule/define-schedule.d.ts.map +1 -0
  111. package/dist/internal/async/schedule/define-schedule.js +79 -0
  112. package/dist/internal/async/schedule/define-schedule.js.map +1 -0
  113. package/dist/internal/async/schedule/index.d.ts +3 -0
  114. package/dist/internal/async/schedule/index.d.ts.map +1 -0
  115. package/dist/internal/async/schedule/index.js +9 -0
  116. package/dist/internal/async/schedule/index.js.map +1 -0
  117. package/dist/internal/async/schedule/schedule-definition-schema.d.ts +64 -0
  118. package/dist/internal/async/schedule/schedule-definition-schema.d.ts.map +1 -0
  119. package/dist/internal/async/schedule/schedule-definition-schema.js +77 -0
  120. package/dist/internal/async/schedule/schedule-definition-schema.js.map +1 -0
  121. package/dist/internal/async/schedule/sync-schedules.d.ts +71 -0
  122. package/dist/internal/async/schedule/sync-schedules.d.ts.map +1 -0
  123. package/dist/internal/async/schedule/sync-schedules.js +231 -0
  124. package/dist/internal/async/schedule/sync-schedules.js.map +1 -0
  125. package/dist/internal/async/utils/ensure-namespace.d.ts +16 -0
  126. package/dist/internal/async/utils/ensure-namespace.d.ts.map +1 -0
  127. package/dist/internal/async/utils/ensure-namespace.js +64 -0
  128. package/dist/internal/async/utils/ensure-namespace.js.map +1 -0
  129. package/dist/internal/async/utils/find-package-root.d.ts +9 -0
  130. package/dist/internal/async/utils/find-package-root.d.ts.map +1 -0
  131. package/dist/internal/async/utils/find-package-root.js +28 -0
  132. package/dist/internal/async/utils/find-package-root.js.map +1 -0
  133. package/dist/internal/async/utils/is-composer-error.d.ts +14 -0
  134. package/dist/internal/async/utils/is-composer-error.d.ts.map +1 -0
  135. package/dist/internal/async/utils/is-composer-error.js +17 -0
  136. package/dist/internal/async/utils/is-composer-error.js.map +1 -0
  137. package/dist/internal/context-provider.d.ts +316 -0
  138. package/dist/internal/context-provider.d.ts.map +1 -0
  139. package/dist/internal/context-provider.js +12 -0
  140. package/dist/internal/context-provider.js.map +1 -0
  141. package/dist/internal/create-composer.d.ts +57 -0
  142. package/dist/internal/create-composer.d.ts.map +1 -0
  143. package/dist/internal/create-composer.js +125 -0
  144. package/dist/internal/create-composer.js.map +1 -0
  145. package/dist/internal/dag-sync-fanout.d.ts +79 -0
  146. package/dist/internal/dag-sync-fanout.d.ts.map +1 -0
  147. package/dist/internal/dag-sync-fanout.js +59 -0
  148. package/dist/internal/dag-sync-fanout.js.map +1 -0
  149. package/dist/internal/dag-sync-step.d.ts +230 -0
  150. package/dist/internal/dag-sync-step.d.ts.map +1 -0
  151. package/dist/internal/dag-sync-step.js +159 -0
  152. package/dist/internal/dag-sync-step.js.map +1 -0
  153. package/dist/internal/dag-sync-workflow.d.ts +535 -0
  154. package/dist/internal/dag-sync-workflow.d.ts.map +1 -0
  155. package/dist/internal/dag-sync-workflow.js +843 -0
  156. package/dist/internal/dag-sync-workflow.js.map +1 -0
  157. package/dist/internal/defaults.d.ts +24 -0
  158. package/dist/internal/defaults.d.ts.map +1 -0
  159. package/dist/internal/defaults.js +102 -0
  160. package/dist/internal/defaults.js.map +1 -0
  161. package/dist/internal/errors.d.ts +109 -0
  162. package/dist/internal/errors.d.ts.map +1 -0
  163. package/dist/internal/errors.js +212 -0
  164. package/dist/internal/errors.js.map +1 -0
  165. package/dist/internal/index.d.ts +9 -0
  166. package/dist/internal/index.d.ts.map +1 -0
  167. package/dist/internal/index.js +22 -0
  168. package/dist/internal/index.js.map +1 -0
  169. package/dist/internal/observability.d.ts +63 -0
  170. package/dist/internal/observability.d.ts.map +1 -0
  171. package/dist/internal/observability.js +323 -0
  172. package/dist/internal/observability.js.map +1 -0
  173. package/dist/internal/types.d.ts +80 -0
  174. package/dist/internal/types.d.ts.map +1 -0
  175. package/dist/internal/types.js +9 -0
  176. package/dist/internal/types.js.map +1 -0
  177. package/dist/internal/workflow-planning.d.ts +107 -0
  178. package/dist/internal/workflow-planning.d.ts.map +1 -0
  179. package/dist/internal/workflow-planning.js +216 -0
  180. package/dist/internal/workflow-planning.js.map +1 -0
  181. package/dist/schedule-sync.d.ts +17 -0
  182. package/dist/schedule-sync.d.ts.map +1 -0
  183. package/dist/schedule-sync.js +22 -0
  184. package/dist/schedule-sync.js.map +1 -0
  185. package/dist/temporal-naming.d.ts +27 -0
  186. package/dist/temporal-naming.d.ts.map +1 -0
  187. package/dist/temporal-naming.js +33 -0
  188. package/dist/temporal-naming.js.map +1 -0
  189. package/package.json +16 -17
@@ -0,0 +1,843 @@
1
+ "use strict";
2
+ /**
3
+ * DAG-based Workflow Engine with Compile-time Validation
4
+ *
5
+ * This module implements a directed acyclic graph (DAG) workflow system that:
6
+ * - Validates step dependencies at compile-time using TypeScript
7
+ * - Executes steps in parallel when dependencies allow
8
+ * - Uses topological sorting with batching for optimal performance
9
+ * - Supports workflow composition for modularity and reusability
10
+ *
11
+ * ## Core Concepts
12
+ *
13
+ * **Step**: A unit of work with declared inputs (needs) and outputs (provides)
14
+ * **Workflow**: A collection of steps with automatic dependency resolution
15
+ * **Bag**: The data container that flows through the workflow
16
+ *
17
+ * ## Execution Model
18
+ *
19
+ * The engine uses **batch-based parallel execution**:
20
+ * 1. **Dependency Analysis**: Builds a dependency graph using topological sorting
21
+ * 2. **Batch Formation**: Groups steps that can run simultaneously
22
+ * 3. **Parallel Execution**: Runs each batch with Promise.all()
23
+ * 4. **Dependency Tracking**: Uses indegree counters to trigger next batch
24
+ *
25
+ * ## Example Execution Flow
26
+ *
27
+ * Given workflow: [stepA, stepB, stepC, stepD]
28
+ * - stepA: needs [] → provides ["x"]
29
+ * - stepB: needs ["x"] → provides ["y"]
30
+ * - stepC: needs ["x"] → provides ["z"]
31
+ * - stepD: needs ["y", "z"] → provides ["result"]
32
+ *
33
+ * Execution batches:
34
+ * - Batch 1: [stepA] (sequential)
35
+ * - Batch 2: [stepB, stepC] (parallel - both need "x")
36
+ * - Batch 3: [stepD] (sequential - needs both "y" and "z")
37
+ *
38
+ * ## Workflow Composition
39
+ *
40
+ * Workflows can be composed using the `use()` helper to create modular, reusable components:
41
+ *
42
+ * ```typescript
43
+ * // Define a reusable child workflow
44
+ * const authWorkflow = createWorkflow<Bag>("authentication")
45
+ * .requires("userId")
46
+ * .build([validateUser, checkPermissions]);
47
+ *
48
+ * // Compose into parent workflow
49
+ * const mainWorkflow = createWorkflow<Bag>("main")
50
+ * .requires("userId")
51
+ * .build([
52
+ * fetchUserData, // Provides userId
53
+ * use(authWorkflow), // Reuses authentication logic
54
+ * processRequest, // Uses auth results
55
+ * ]);
56
+ * ```
57
+ *
58
+ * **Key Composition Features:**
59
+ * - **Step Flattening**: Child workflow steps are merged into parent's dependency graph
60
+ * - **Maximum Parallelization**: Steps from different workflows can execute in the same batch
61
+ * - **Automatic Namespacing**: Child steps are prefixed as "childName.stepName" to prevent conflicts
62
+ * - **Dependency Propagation**: Child initial requirements become regular dependencies in parent
63
+ * - **Nested Composition**: Workflows can compose other composed workflows to any depth
64
+ * - **Observability Preservation**: SubWorkflow spans maintain logical boundaries in traces
65
+ *
66
+ * **Composition Benefits:**
67
+ * - Code reusability across different workflows
68
+ * - Easier testing of workflow components in isolation
69
+ * - Better organization of complex business logic
70
+ * - Maintains compile-time type safety across composition boundaries
71
+ *
72
+ * ## Compile-time Safety
73
+ *
74
+ * The type system validates:
75
+ * - All step dependencies are satisfied by previous steps or initial data
76
+ * - Required initial fields are provided at runtime
77
+ * - Clear error messages for missing dependencies
78
+ *
79
+ * ## Data Protection & Overwrite Prevention
80
+ *
81
+ * The engine enforces strict data integrity rules:
82
+ * - **Step-to-Step Protection**: Each field can have only one producer step (duplicate producers are rejected)
83
+ * - **Initial Data Protection**: Steps cannot overwrite fields provided in initial data
84
+ * - **Immutable Outputs**: Once a step produces a field, it cannot be modified by other steps
85
+ * - **Early Detection**: All conflicts are caught at workflow execution time, not during step execution
86
+ *
87
+ * ## Performance Characteristics
88
+ *
89
+ * - **Automatic Parallelization**: Steps run concurrently when safe
90
+ * - **Batch Synchronization**: All steps in a batch complete before next batch starts
91
+ * - **Zero Polling**: Event-driven execution using dependency counters
92
+ * - **Optimal Scheduling**: Steps execute as soon as dependencies are satisfied
93
+ */
94
+ Object.defineProperty(exports, "__esModule", { value: true });
95
+ exports.DEFAULT_CHECKPOINT_TIMEOUT_MS = void 0;
96
+ exports.use = use;
97
+ exports.createWorkflow = createWorkflow;
98
+ exports.runSyncWorkflow = runSyncWorkflow;
99
+ const api_1 = require("@opentelemetry/api");
100
+ const uuid_1 = require("uuid");
101
+ const dag_sync_fanout_1 = require("./dag-sync-fanout");
102
+ const defaults_1 = require("./defaults");
103
+ const errors_1 = require("./errors");
104
+ const observability_1 = require("./observability");
105
+ const workflow_planning_1 = require("./workflow-planning");
106
+ // ============================================================================
107
+ // Checkpoint Types
108
+ // ============================================================================
109
+ /**
110
+ * Default timeout for checkpoints in milliseconds.
111
+ *
112
+ * Checkpoints are designed for fast early returns within HTTP timeout windows.
113
+ * The default of 30 seconds provides a reasonable window for initial step(s)
114
+ * to complete while leaving headroom before typical HTTP timeouts (60s).
115
+ */
116
+ exports.DEFAULT_CHECKPOINT_TIMEOUT_MS = 30_000; // 30 seconds
117
+ /**
118
+ * IMMUTABILITY PROTECTION FEATURE FLAG
119
+ *
120
+ * When enabled, all step outputs are deeply frozen before being merged into the shared bag.
121
+ * This prevents mutation bugs that can cause non-deterministic workflow behavior.
122
+ *
123
+ * ## What This Protects Against:
124
+ *
125
+ * 1. **Parallel batch mutations**: Two steps in the same batch mutating shared references
126
+ * Example: Both steps call bag.identities.push(...) → race condition
127
+ *
128
+ * 2. **Downstream mutations**: A step mutates data it received from a previous step
129
+ * Example: step.run((ctx, bag) => { bag.rules.sort(); ... }) → changes data for peers
130
+ *
131
+ * 3. **Post-return mutations**: A step keeps a reference and mutates it later
132
+ * Example: setTimeout(() => myData.length = 0, 100) → corrupts bag after batch completes
133
+ *
134
+ * 4. **Array/nested mutations**: TypeScript's Readonly<> is shallow and can't prevent these
135
+ * Example: bag.identities[0].name = "x" → compiles but violates immutability
136
+ *
137
+ * 5. **Third-party library side effects**: SDKs that mutate arguments in place
138
+ * Example: someLibrary.normalize(bag.config) → unexpected mutations
139
+ *
140
+ * ## Why It's Feature-Flagged:
141
+ *
142
+ * Deep freeze has a performance cost (~0.5-10ms per output depending on object size).
143
+ * For workflows where steps do I/O (database, API calls), this overhead is negligible.
144
+ * For CPU-intensive workflows with large objects, it may be noticeable.
145
+ *
146
+ * Recommendation:
147
+ * - Enable in development/test to catch mutation bugs early
148
+ * - Enable in production unless profiling shows it's a bottleneck
149
+ * - If disabled in prod, ensure code reviews catch mutation patterns
150
+ *
151
+ * Deep freeze is now controlled by the `deepFreeze` option in ComposerConfig.
152
+ *
153
+ * TODO(nate): Add observability for cost of deep freeze + any runtime validation.
154
+ */
155
+ /**
156
+ * Recursively freezes an object and all nested objects/arrays.
157
+ *
158
+ * This provides runtime immutability guarantees that TypeScript's type system cannot enforce.
159
+ * After deep freezing, any attempt to mutate the object or its nested properties will throw
160
+ * an error in strict mode or silently fail in non-strict mode.
161
+ *
162
+ * @param obj - The object to freeze
163
+ * @returns The frozen object (same reference, now immutable)
164
+ *
165
+ * Performance characteristics:
166
+ * - O(n) where n is the total number of nodes in the object graph
167
+ * - Typical cost: 0.5-10ms for objects with 100-1000 nodes
168
+ * - Skips already-frozen objects for efficiency
169
+ * - Handles null/undefined/primitives safely
170
+ */
171
+ function deepFreeze(obj) {
172
+ // Skip primitives, null, undefined, and already-frozen objects
173
+ if (!obj || typeof obj !== "object" || Object.isFrozen(obj)) {
174
+ return obj;
175
+ }
176
+ // Freeze this level
177
+ Object.freeze(obj);
178
+ // Recursively freeze all nested values
179
+ for (const value of Object.values(obj)) {
180
+ deepFreeze(value);
181
+ }
182
+ return obj;
183
+ }
184
+ /**
185
+ * Runtime validation that ensures all step names are unique.
186
+ *
187
+ * Step names are used as primary keys in dependency maps (stepsByName, children, indegree).
188
+ * Duplicate names would cause silent overwrites in these maps, corrupting the DAG structure
189
+ * and leading to wrong execution order or missing dependencies.
190
+ *
191
+ * This function provides clear error messages that help developers identify which steps
192
+ * have conflicting names and where they appear in the workflow definition.
193
+ */
194
+ function assertUniqueStepNames(steps) {
195
+ const seen = new Map(); // Track first occurrence index for better errors
196
+ steps.forEach((step, index) => {
197
+ const stepName = step.name;
198
+ if (seen.has(stepName)) {
199
+ const firstIndex = seen.get(stepName);
200
+ throw new Error(`Duplicate step name "${stepName}" found at positions ${firstIndex} and ${index}. ` +
201
+ `Step names must be unique within a workflow.\n\n` +
202
+ `💡 Fix: Rename one of the steps to have a unique name.`);
203
+ }
204
+ seen.set(stepName, index);
205
+ });
206
+ }
207
+ /**
208
+ * Helper function to find the first duplicate key in an array.
209
+ *
210
+ * Returns undefined if no duplicates exist, or the duplicated key if found.
211
+ */
212
+ function findDuplicateKey(keys) {
213
+ const seen = new Set();
214
+ for (const key of keys) {
215
+ const keyStr = String(key);
216
+ if (seen.has(keyStr)) {
217
+ return keyStr;
218
+ }
219
+ seen.add(keyStr);
220
+ }
221
+ return undefined;
222
+ }
223
+ /**
224
+ * Runtime validation that ensures no duplicates exist in step needs/provides arrays.
225
+ *
226
+ * Duplicate needs cause deadlocks in the topological sort (indegree is incremented twice
227
+ * but only decremented once, so the step never reaches zero and never executes).
228
+ *
229
+ * Duplicate provides violate the semantic contract and make observability confusing,
230
+ * even though they don't break execution (the field only exists once in the return object).
231
+ */
232
+ function assertNoDuplicatesInStepArrays(steps) {
233
+ for (const step of steps) {
234
+ const duplicateNeed = findDuplicateKey(step.needs);
235
+ if (duplicateNeed) {
236
+ throw new Error(`Step "${step.name}" lists "${duplicateNeed}" twice in "needs". ` +
237
+ `Each dependency should only be declared once.\n\n` +
238
+ `💡 Fix: Remove the duplicate "${duplicateNeed}" from the needs array.`);
239
+ }
240
+ const duplicateProvide = findDuplicateKey(step.provides);
241
+ if (duplicateProvide) {
242
+ throw new Error(`Step "${step.name}" lists "${duplicateProvide}" twice in "provides". ` +
243
+ `Each output field should only be declared once.\n\n` +
244
+ `💡 Fix: Remove the duplicate "${duplicateProvide}" from the provides array.`);
245
+ }
246
+ }
247
+ }
248
+ /**
249
+ * Composes a child workflow into a parent workflow.
250
+ *
251
+ * When building a workflow, use `use(childWorkflow)` to include all steps from the child.
252
+ * The steps will be:
253
+ * - Flattened into the parent's dependency graph for maximum parallelization
254
+ * - Automatically namespaced as "childWorkflowName.stepName" to prevent conflicts
255
+ * - Tracked with workflow provenance metadata for proper observability
256
+ *
257
+ * Any initial requirements from the child workflow become regular dependencies that
258
+ * must be satisfied by the parent's steps or initial data.
259
+ *
260
+ * Example:
261
+ * ```typescript
262
+ * const childWf = createWorkflow<Bag>("child").build([stepA, stepB]);
263
+ * const parentWf = createWorkflow<Bag>("parent").build([
264
+ * stepX,
265
+ * use(childWf), // Includes stepA and stepB with proper namespacing
266
+ * stepY,
267
+ * ]);
268
+ *
269
+ * ## Observability
270
+ *
271
+ * Composed workflows create SubWorkflow spans in traces, making it easy to see logical
272
+ * boundaries even though execution is flattened:
273
+ * ```
274
+ * Workflow: main
275
+ * └─ Batch 1
276
+ * └─ SubWorkflow: authentication
277
+ * └─ Step: authentication.validateUser
278
+ * ```
279
+ */
280
+ function use(workflow) {
281
+ return {
282
+ __composedWorkflow: true,
283
+ workflow,
284
+ };
285
+ }
286
+ function workflow(name, steps, requiredInitial, configuredValues, checkpoints) {
287
+ // Explicitly specify all 5 type parameters to ensure CheckpointNames is `never`
288
+ const wf = {
289
+ name,
290
+ steps,
291
+ ...(requiredInitial && requiredInitial.length > 0 ? { requiredInitial } : {}),
292
+ ...(configuredValues && Object.keys(configuredValues).length > 0 ? { configuredValues } : {}),
293
+ ...(checkpoints && checkpoints.length > 0 ? { checkpoints } : {}),
294
+ };
295
+ // Create a builder function that returns a new WorkflowBuilder with updated checkpoints
296
+ // The CheckpointNames type parameter is tracked at compile-time only; runtime uses currentCheckpoints
297
+ function createBuilder(currentWf, currentCheckpoints) {
298
+ // Implementation uses 'any' for internal assignment since TypeScript can't track
299
+ // the conditional types through Object.assign. The public interface types are correct.
300
+ const builder = {
301
+ ...currentWf,
302
+ checkpoint(checkpointName, options) {
303
+ // Validate checkpoint name is unique (runtime validation as safety net)
304
+ if (currentCheckpoints.some((cp) => cp.name === checkpointName)) {
305
+ throw new Error(`Duplicate checkpoint name "${checkpointName}". Checkpoint names must be unique within a workflow.`);
306
+ }
307
+ // Extract step name from step reference
308
+ const stepName = options.afterStep.name;
309
+ // Validate step exists (supports both namespaced and unnamespaced)
310
+ // The TypeScript constraint S extends Steps[number] provides compile-time validation,
311
+ // but we still do runtime validation for composed workflows where step names get namespaced
312
+ const resolvedStepName = resolveStepName(steps, stepName);
313
+ if (!resolvedStepName) {
314
+ const availableSteps = steps.map((s) => s.name).join(", ");
315
+ throw new Error(`Checkpoint "${checkpointName}" references unknown step "${stepName}". ` +
316
+ `Available steps: [${availableSteps}]`);
317
+ }
318
+ const newCheckpoint = {
319
+ name: checkpointName,
320
+ afterStep: resolvedStepName, // Use resolved (namespaced) name
321
+ timeout: options.timeout,
322
+ };
323
+ const newCheckpoints = [...currentCheckpoints, newCheckpoint];
324
+ const newWf = {
325
+ ...currentWf,
326
+ checkpoints: newCheckpoints,
327
+ };
328
+ return createBuilder(newWf, newCheckpoints);
329
+ },
330
+ onError(handler) {
331
+ return {
332
+ ...currentWf,
333
+ checkpoints: currentCheckpoints.length > 0 ? currentCheckpoints : undefined,
334
+ errorHandler: handler,
335
+ };
336
+ },
337
+ };
338
+ return builder;
339
+ }
340
+ // Explicitly pass 'never' as the type parameter since no checkpoints are defined yet
341
+ return createBuilder(wf, checkpoints ?? []);
342
+ }
343
+ /**
344
+ * Resolves a step name, handling both namespaced and unnamespaced names.
345
+ *
346
+ * - If `stepName` exactly matches a step, returns it as-is.
347
+ * - If `stepName` doesn't match but matches the suffix of a namespaced step
348
+ * (e.g., "validateUser" matches "auth.validateUser"), returns the full namespaced name.
349
+ * - If multiple steps match the unnamespaced suffix, throws an error (ambiguous).
350
+ * - Returns null if no match is found.
351
+ */
352
+ function resolveStepName(steps, stepName) {
353
+ // Direct match - return as-is
354
+ const directMatch = steps.find((s) => s.name === stepName);
355
+ if (directMatch) {
356
+ return directMatch.name;
357
+ }
358
+ // Try to find by suffix (unnamespaced name matching namespaced step)
359
+ const suffixMatches = steps.filter((s) => {
360
+ const parts = s.name.split(".");
361
+ const unnamespaced = parts[parts.length - 1];
362
+ return unnamespaced === stepName;
363
+ });
364
+ if (suffixMatches.length === 1) {
365
+ return suffixMatches[0].name;
366
+ }
367
+ if (suffixMatches.length > 1) {
368
+ const matchingNames = suffixMatches.map((s) => s.name).join(", ");
369
+ throw new Error(`Ambiguous step name "${stepName}" matches multiple steps: [${matchingNames}]. ` +
370
+ `Use the full namespaced name to specify which step.`);
371
+ }
372
+ return null;
373
+ }
374
+ /**
375
+ * Type guard to check if an item is a ComposedWorkflow marker.
376
+ */
377
+ function isComposedWorkflow(item) {
378
+ return item && typeof item === "object" && item.__composedWorkflow === true;
379
+ }
380
+ /**
381
+ * Flattens a step array that may contain ComposedWorkflow markers.
382
+ *
383
+ * This function:
384
+ * - Recursively processes the array to handle nested composition
385
+ * - Namespaces steps as "workflowName.stepName" to prevent conflicts
386
+ * - Tracks workflow provenance in the workflowPath property
387
+ * - Returns a flat array of steps ready for execution
388
+ *
389
+ * @param items - Array of steps and/or ComposedWorkflow markers
390
+ * @param parentPath - The workflow path of the parent (for nested composition)
391
+ * @returns Flattened array of steps with namespacing and provenance
392
+ */
393
+ function flattenSteps(items, parentPath = []) {
394
+ const result = [];
395
+ for (const item of items) {
396
+ if (isComposedWorkflow(item)) {
397
+ const childWorkflow = item.workflow;
398
+ const childPath = [...parentPath, childWorkflow.name];
399
+ // Recursively flatten child workflow's steps
400
+ const childSteps = flattenSteps(childWorkflow.steps, childPath);
401
+ // Namespace each step and set workflow path
402
+ for (const step of childSteps) {
403
+ // Build the namespaced name: if step already has path, preserve it
404
+ const namespacedName = step.workflowPath
405
+ ? step.name // Already namespaced from nested composition
406
+ : `${childWorkflow.name}.${step.name}`;
407
+ // For workflow path: if step already has one from nested composition,
408
+ // it starts with the child workflow's name, so we need to prepend the parent path
409
+ let workflowPath;
410
+ if (step.workflowPath && step.workflowPath.length > 0) {
411
+ // Step already has a path from being in a composed child workflow
412
+ // Prepend parent path (excluding the child workflow name since it's already in step.workflowPath)
413
+ workflowPath = [...parentPath, ...step.workflowPath];
414
+ }
415
+ else {
416
+ // Step is directly in this child workflow
417
+ workflowPath = childPath;
418
+ }
419
+ result.push({
420
+ ...step,
421
+ name: namespacedName,
422
+ workflowPath,
423
+ });
424
+ }
425
+ }
426
+ else {
427
+ // Regular step - add as-is
428
+ result.push(item);
429
+ }
430
+ }
431
+ return result;
432
+ }
433
+ /**
434
+ * Creates a workflow builder with compile-time dependency validation.
435
+ *
436
+ * This builder provides three modes:
437
+ * 1. build() - for workflows that don't require initial data
438
+ * 2. requires(fields).build() - for workflows that need the caller to provide specific fields
439
+ * 3. configure(configuration).requires(fields).build() - for workflows that need the bag
440
+ * configured with specific values, as well as initial values provided at runtime
441
+ *
442
+ * The compile-time validation works by:
443
+ * - Analyzing the step dependencies using recursive conditional types
444
+ * - If validation fails, injecting an error property that causes a compile error
445
+ * - If validation passes, allowing the workflow to be created normally
446
+ *
447
+ * Usage:
448
+ * // No initial data required
449
+ * const wf1 = createWorkflow<Bag>("my-workflow").build([stepA, stepB]);
450
+ *
451
+ * // Requires 'promptText' to be provided at runtime
452
+ * const wf2 = createWorkflow<Bag>("prompt-workflow").requires("promptText").build([stepA, stepB]);
453
+ *
454
+ * // Sets lotiContentType to "prompt_media" and requires 'promptText' to be
455
+ * provided at runtime.
456
+ * const wf3 = createWorkflow<Bag>("configured-prompt-workflow")
457
+ * .configure({ lotiContentType: "prompt_media" })
458
+ * .requires("promptText")
459
+ * .build([stepA, stepB]);
460
+ */
461
+ function createWorkflow(name) {
462
+ return {
463
+ // Build workflow without required initial fields
464
+ build(steps) {
465
+ const flattenedSteps = flattenSteps(steps, [name]);
466
+ assertUniqueStepNames(flattenedSteps);
467
+ assertNoDuplicatesInStepArrays(flattenedSteps);
468
+ return workflow(name, flattenedSteps, undefined);
469
+ },
470
+ // Allow specifying initial fields that will be provided at runtime (rest parameters)
471
+ requires(...fields) {
472
+ return {
473
+ // Build workflow with required initial fields
474
+ build(steps) {
475
+ const flattenedSteps = flattenSteps(steps, [name]);
476
+ assertUniqueStepNames(flattenedSteps);
477
+ assertNoDuplicatesInStepArrays(flattenedSteps);
478
+ return workflow(name, flattenedSteps, fields);
479
+ },
480
+ };
481
+ },
482
+ configure(configuration) {
483
+ return {
484
+ build(steps) {
485
+ const flattenedSteps = flattenSteps(steps, [name]);
486
+ assertUniqueStepNames(flattenedSteps);
487
+ assertNoDuplicatesInStepArrays(flattenedSteps);
488
+ return workflow(name, flattenedSteps, undefined, configuration);
489
+ },
490
+ // Exclude ConfigKeys to prevent requiring fields that are already configured
491
+ requires(...fields) {
492
+ return {
493
+ build(steps) {
494
+ const flattenedSteps = flattenSteps(steps, [name]);
495
+ assertUniqueStepNames(flattenedSteps);
496
+ assertNoDuplicatesInStepArrays(flattenedSteps);
497
+ return workflow(name, flattenedSteps, fields, configuration);
498
+ },
499
+ };
500
+ },
501
+ };
502
+ },
503
+ };
504
+ }
505
+ // Implementation
506
+ async function runSyncWorkflow(wf, initialData, contextProvider, logger, enableDeepFreeze) {
507
+ // Resolve defaults for optional parameters
508
+ const log = logger ?? defaults_1.defaultLogger;
509
+ const deepFreeze_ = enableDeepFreeze ?? false;
510
+ // Generate workflowId for observability tracking
511
+ const workflowId = (0, uuid_1.v7)();
512
+ // Initialize bag outside try block so it's accessible in catch block
513
+ const configuredValues = (wf.configuredValues ?? {});
514
+ const bag = {
515
+ ...initialData,
516
+ ...configuredValues,
517
+ };
518
+ // Start workflow observability, catching any initialization errors
519
+ let workflowHandle;
520
+ try {
521
+ workflowHandle = (0, observability_1.startWorkflowObservability)(workflowId, wf, initialData, log);
522
+ }
523
+ catch (observabilityError) {
524
+ // Observability initialization failed - return error without throwing
525
+ const error = observabilityError instanceof Error
526
+ ? observabilityError
527
+ : new Error(String(observabilityError));
528
+ return {
529
+ bag: bag,
530
+ error: new errors_1.WorkflowStepError({
531
+ workflowId,
532
+ stepName: "__observability_init__",
533
+ batchNumber: 0,
534
+ originalError: error,
535
+ bagState: bag,
536
+ workflowPath: [],
537
+ }),
538
+ };
539
+ }
540
+ // Track execution context for enhanced error logging
541
+ const executionContext = {
542
+ stepName: undefined,
543
+ stepNumber: undefined,
544
+ totalSteps: wf.steps.length,
545
+ batchNumber: 0,
546
+ stepStartTime: undefined,
547
+ };
548
+ try {
549
+ // === PHASE 1: VALIDATION & PLANNING ===
550
+ // Validate that all step names are unique (defense-in-depth for dynamically constructed workflows)
551
+ assertUniqueStepNames(wf.steps);
552
+ // Validate that no step has duplicate entries in needs/provides arrays
553
+ assertNoDuplicatesInStepArrays(wf.steps);
554
+ // Plan workflow execution using topological sort
555
+ // This validates dependencies, detects cycles, and organizes steps into parallel execution batches
556
+ const initialFields = new Set([
557
+ ...Object.keys(configuredValues),
558
+ ...Object.keys(initialData ?? {}),
559
+ ]);
560
+ const plan = (0, workflow_planning_1.planWorkflowBatches)([...wf.steps], initialFields);
561
+ // === PHASE 2: EXECUTE BATCHES ===
562
+ // Each batch contains steps that can run in parallel
563
+ for (let batchIndex = 0; batchIndex < plan.batches.length; batchIndex++) {
564
+ const batch = plan.batches[batchIndex]; // Safe: within bounds check
565
+ const batchNumber = batchIndex + 1;
566
+ // Update execution context for current batch
567
+ executionContext.batchNumber = batchNumber;
568
+ // Start batch observability
569
+ const batchHandle = (0, observability_1.startBatchObservability)(workflowHandle, batchNumber, batch.map((step) => step.name));
570
+ // Execute all steps in this batch in parallel using allSettled
571
+ // This ensures all steps complete (even if some fail) so we can:
572
+ // 1. Collect outputs from successful steps
573
+ // 2. Aggregate all failures into a single WorkflowBatchError
574
+ const settledResults = await Promise.allSettled(batch.map(async (step) => {
575
+ // Update execution context for current step
576
+ executionContext.stepName = step.name;
577
+ executionContext.stepNumber =
578
+ wf.steps.findIndex((workflowStep) => workflowStep.name === step.name) + 1; // 1-based indexing
579
+ executionContext.stepStartTime = Date.now();
580
+ // Start step observability
581
+ const stepHandle = (0, observability_1.startStepObservability)(workflowHandle, batchHandle, step);
582
+ // Create context via beforeStep hook if provider exists
583
+ let ctx;
584
+ if (contextProvider) {
585
+ ctx = await contextProvider.beforeStep(step.name);
586
+ }
587
+ let stepError;
588
+ try {
589
+ let stepOutput;
590
+ if ((0, dag_sync_fanout_1.isFanOutStep)(step)) {
591
+ stepOutput = await executeFanOut(step.__fanOut, step.name, bag, contextProvider, log, deepFreeze_);
592
+ }
593
+ else {
594
+ // Build input object with only the fields this step needs
595
+ const stepInput = step.needs.reduce((inputAcc, neededField) => {
596
+ inputAcc[neededField] = bag[neededField];
597
+ return inputAcc;
598
+ }, {});
599
+ // Freeze the step input if immutability protection is enabled
600
+ // This prevents steps from modifying their input bag
601
+ if (deepFreeze_) {
602
+ Object.freeze(stepInput);
603
+ }
604
+ const syncRuntime = {
605
+ heartbeat: () => { },
606
+ getHeartbeatDetails: () => undefined,
607
+ };
608
+ // Object.assign preserves context identity and prototype chain when ctx is
609
+ // a class instance, so afterStep receives the same object the step mutated.
610
+ // Falls back to spread when ctx is undefined (no context provider).
611
+ const stepCtx = ctx != null && typeof ctx === "object"
612
+ ? Object.assign(ctx, syncRuntime)
613
+ : syncRuntime;
614
+ // Execute the step within the OTel step context for proper span hierarchy
615
+ stepOutput = (await api_1.context.with(stepHandle.stepContext, () => step.run(stepCtx, stepInput)));
616
+ }
617
+ // End step observability - success
618
+ (0, observability_1.endStepObservability)(stepHandle, {
619
+ success: true,
620
+ outputFields: Object.keys(stepOutput),
621
+ });
622
+ return { stepName: step.name, output: stepOutput };
623
+ }
624
+ catch (error) {
625
+ // Wrap error with workflow context
626
+ stepError = error instanceof Error ? error : new Error(String(error));
627
+ const workflowError = new errors_1.WorkflowStepError({
628
+ workflowId: workflowId,
629
+ stepName: step.name,
630
+ batchNumber,
631
+ originalError: stepError,
632
+ bagState: bag,
633
+ workflowPath: step.workflowPath,
634
+ });
635
+ // End step observability - error
636
+ (0, observability_1.endStepObservability)(stepHandle, {
637
+ success: false,
638
+ error: workflowError,
639
+ });
640
+ throw workflowError;
641
+ }
642
+ finally {
643
+ // Call afterStep hook for cleanup if provider exists.
644
+ // Wrapped in try/catch to prevent cleanup errors (e.g., EM flush failures)
645
+ // from swallowing the original step error. The step's business logic error
646
+ // is more important to surface than infrastructure cleanup errors.
647
+ if (contextProvider && ctx !== undefined) {
648
+ try {
649
+ await contextProvider.afterStep(ctx, stepError);
650
+ }
651
+ catch (cleanupError) {
652
+ log.error("afterStep cleanup failed", {
653
+ stepName: step.name,
654
+ workflowId,
655
+ cleanupError: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
656
+ originalStepError: stepError?.message,
657
+ });
658
+ }
659
+ }
660
+ }
661
+ }));
662
+ // Separate fulfilled and rejected results
663
+ const fulfilled = settledResults.filter((r) => r.status === "fulfilled");
664
+ const rejected = settledResults.filter((r) => r.status === "rejected");
665
+ // First, merge successful step outputs into the bag
666
+ // This ensures the bag has all available data even if some steps failed
667
+ for (const result of fulfilled) {
668
+ const { output } = result.value;
669
+ if (deepFreeze_) {
670
+ for (const [key, value] of Object.entries(output)) {
671
+ bag[key] = deepFreeze(value);
672
+ }
673
+ }
674
+ else {
675
+ Object.assign(bag, output);
676
+ }
677
+ }
678
+ // If any steps failed, aggregate into WorkflowBatchError
679
+ if (rejected.length > 0) {
680
+ const stepErrors = rejected.map((r) => r.reason);
681
+ // End batch observability - error
682
+ (0, observability_1.endBatchObservability)(batchHandle, {
683
+ success: false,
684
+ error: stepErrors[0], // Primary error for observability (safe: length > 0)
685
+ });
686
+ throw new errors_1.WorkflowBatchError({
687
+ errors: stepErrors,
688
+ bagState: bag,
689
+ batchNumber,
690
+ workflowId,
691
+ });
692
+ }
693
+ // End batch observability - success
694
+ (0, observability_1.endBatchObservability)(batchHandle, {
695
+ success: true,
696
+ outputFields: fulfilled.flatMap((r) => Object.keys(r.value.output)),
697
+ });
698
+ }
699
+ // End workflow observability - success
700
+ (0, observability_1.endWorkflowObservability)(workflowHandle, {
701
+ success: true,
702
+ batchCount: plan.batches.length,
703
+ outputFields: Object.keys(bag),
704
+ }, executionContext, bag);
705
+ return {
706
+ bag: bag,
707
+ error: undefined,
708
+ };
709
+ }
710
+ catch (error) {
711
+ // End workflow observability - error
712
+ (0, observability_1.endWorkflowObservability)(workflowHandle, {
713
+ success: false,
714
+ error: error instanceof Error ? error : new Error(String(error)),
715
+ }, executionContext, bag);
716
+ // Cast bag for return type
717
+ const resultBag = bag;
718
+ // Invoke error handler if present
719
+ if (wf.errorHandler &&
720
+ (error instanceof errors_1.WorkflowStepError || error instanceof errors_1.WorkflowBatchError)) {
721
+ // Get context for error handler
722
+ let ctx;
723
+ if (contextProvider) {
724
+ try {
725
+ ctx = await contextProvider.beforeStep("__errorHandler__");
726
+ }
727
+ catch (contextError) {
728
+ // If we can't create context, proceed without it
729
+ log.warn("Failed to create context for error handler", {
730
+ workflowId,
731
+ contextError: contextError instanceof Error ? contextError.message : String(contextError),
732
+ });
733
+ }
734
+ }
735
+ try {
736
+ // Handler returns Error | undefined
737
+ // - undefined = handled, no error to propagate
738
+ // - Error = propagate that error (possibly transformed)
739
+ const handlerResult = await wf.errorHandler(ctx, bag, error);
740
+ // Clean up context if we created one
741
+ if (contextProvider && ctx !== undefined) {
742
+ try {
743
+ await contextProvider.afterStep(ctx, undefined);
744
+ }
745
+ catch (cleanupError) {
746
+ log.error("afterStep cleanup failed for error handler", {
747
+ workflowId,
748
+ cleanupError: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
749
+ });
750
+ }
751
+ }
752
+ // Return based on handler result
753
+ return {
754
+ bag: resultBag,
755
+ error: handlerResult, // undefined = handled, Error = propagate
756
+ };
757
+ }
758
+ catch (handlerError) {
759
+ // Handler threw unexpectedly - wrap both errors
760
+ // Clean up context if we created one
761
+ if (contextProvider && ctx !== undefined) {
762
+ try {
763
+ await contextProvider.afterStep(ctx, handlerError instanceof Error ? handlerError : undefined);
764
+ }
765
+ catch (cleanupError) {
766
+ log.error("afterStep cleanup failed for error handler", {
767
+ workflowId,
768
+ cleanupError: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
769
+ });
770
+ }
771
+ }
772
+ // Handler failed due to its own error (e.g., DB failure during cleanup)
773
+ return {
774
+ bag: resultBag,
775
+ error: new errors_1.WorkflowErrorHandlerFailure({
776
+ originalError: error,
777
+ handlerError: handlerError instanceof Error ? handlerError : new Error(String(handlerError)),
778
+ workflowId,
779
+ }),
780
+ };
781
+ }
782
+ }
783
+ // No handler - return original error
784
+ return {
785
+ bag: resultBag,
786
+ error: error instanceof Error ? error : new Error(String(error)),
787
+ };
788
+ }
789
+ }
790
+ /**
791
+ * Executes a FanOut step using the lane-based concurrency pattern.
792
+ *
793
+ * 1. Calls mapInput to produce child workflow inputs from the current bag
794
+ * 2. Spawns min(concurrency, N) async lanes that pull work from a shared index
795
+ * 3. Each lane runs child workflows via runSyncWorkflow, collecting results
796
+ * 4. After all lanes complete, separates successes from failures
797
+ * 5. If all children succeed, calls aggregateResults to produce the step output
798
+ * 6. If any children fail, throws AggregateError containing child errors
799
+ * (compatible with extractErrorCandidates via .errors[])
800
+ */
801
+ async function executeFanOut(metadata, fanOutName, currentBag, contextProvider, logger, enableDeepFreeze) {
802
+ const { childWorkflow, mapInput, aggregateResults, concurrency } = metadata;
803
+ const inputs = mapInput(currentBag);
804
+ if (inputs.length === 0) {
805
+ return aggregateResults([]);
806
+ }
807
+ const laneCount = Math.min(concurrency, inputs.length);
808
+ const results = new Array(inputs.length);
809
+ let nextIndex = 0;
810
+ const lane = async () => {
811
+ while (true) {
812
+ const index = nextIndex++;
813
+ if (index >= inputs.length)
814
+ break;
815
+ // Cast: type safety is enforced at the fanOut() factory level, not here
816
+ const childResult = await runSyncWorkflow(childWorkflow, inputs[index], contextProvider, logger, enableDeepFreeze);
817
+ if (childResult.error) {
818
+ results[index] = { error: childResult.error };
819
+ }
820
+ else {
821
+ results[index] = { bag: childResult.bag };
822
+ }
823
+ }
824
+ };
825
+ await Promise.all(Array.from({ length: laneCount }, () => lane()));
826
+ const errors = [];
827
+ const successfulBags = [];
828
+ for (const result of results) {
829
+ if (!result)
830
+ continue;
831
+ if ("error" in result) {
832
+ errors.push(result.error);
833
+ }
834
+ else {
835
+ successfulBags.push(result.bag);
836
+ }
837
+ }
838
+ if (errors.length > 0) {
839
+ throw new AggregateError(errors, `FanOut "${fanOutName}": ${errors.length} of ${inputs.length} child workflow(s) failed`);
840
+ }
841
+ return aggregateResults(successfulBags);
842
+ }
843
+ //# sourceMappingURL=dag-sync-workflow.js.map