@workflow/builders 4.0.1-beta.5 → 4.0.1-beta.50

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 (88) hide show
  1. package/dist/apply-swc-transform.d.ts +14 -4
  2. package/dist/apply-swc-transform.d.ts.map +1 -1
  3. package/dist/apply-swc-transform.js +59 -9
  4. package/dist/apply-swc-transform.js.map +1 -1
  5. package/dist/base-builder.d.ts +65 -22
  6. package/dist/base-builder.d.ts.map +1 -1
  7. package/dist/base-builder.js +438 -115
  8. package/dist/base-builder.js.map +1 -1
  9. package/dist/build-queue.d.ts +18 -0
  10. package/dist/build-queue.d.ts.map +1 -0
  11. package/dist/build-queue.js +26 -0
  12. package/dist/build-queue.js.map +1 -0
  13. package/dist/config-helpers.d.ts +31 -0
  14. package/dist/config-helpers.d.ts.map +1 -1
  15. package/dist/config-helpers.js +60 -0
  16. package/dist/config-helpers.js.map +1 -1
  17. package/dist/constants.d.ts +2 -2
  18. package/dist/constants.js +2 -2
  19. package/dist/discover-entries-esbuild-plugin.d.ts +2 -3
  20. package/dist/discover-entries-esbuild-plugin.d.ts.map +1 -1
  21. package/dist/discover-entries-esbuild-plugin.js +57 -26
  22. package/dist/discover-entries-esbuild-plugin.js.map +1 -1
  23. package/dist/index.d.ts +9 -3
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +8 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/module-specifier.d.ts +85 -0
  28. package/dist/module-specifier.d.ts.map +1 -0
  29. package/dist/module-specifier.js +437 -0
  30. package/dist/module-specifier.js.map +1 -0
  31. package/dist/module-specifier.test.d.ts +2 -0
  32. package/dist/module-specifier.test.d.ts.map +1 -0
  33. package/dist/module-specifier.test.js +169 -0
  34. package/dist/module-specifier.test.js.map +1 -0
  35. package/dist/node-module-esbuild-plugin.d.ts +37 -0
  36. package/dist/node-module-esbuild-plugin.d.ts.map +1 -1
  37. package/dist/node-module-esbuild-plugin.js +289 -9
  38. package/dist/node-module-esbuild-plugin.js.map +1 -1
  39. package/dist/node-module-esbuild-plugin.test.js +359 -88
  40. package/dist/node-module-esbuild-plugin.test.js.map +1 -1
  41. package/dist/pseudo-package-esbuild-plugin.d.ts +20 -0
  42. package/dist/pseudo-package-esbuild-plugin.d.ts.map +1 -0
  43. package/dist/pseudo-package-esbuild-plugin.js +47 -0
  44. package/dist/pseudo-package-esbuild-plugin.js.map +1 -0
  45. package/dist/pseudo-package-esbuild-plugin.test.d.ts +2 -0
  46. package/dist/pseudo-package-esbuild-plugin.test.d.ts.map +1 -0
  47. package/dist/pseudo-package-esbuild-plugin.test.js +315 -0
  48. package/dist/pseudo-package-esbuild-plugin.test.js.map +1 -0
  49. package/dist/request-converter.d.ts +3 -0
  50. package/dist/request-converter.d.ts.map +1 -0
  51. package/dist/request-converter.js +14 -0
  52. package/dist/request-converter.js.map +1 -0
  53. package/dist/standalone.d.ts +3 -0
  54. package/dist/standalone.d.ts.map +1 -1
  55. package/dist/standalone.js +37 -13
  56. package/dist/standalone.js.map +1 -1
  57. package/dist/swc-esbuild-plugin.d.ts +0 -2
  58. package/dist/swc-esbuild-plugin.d.ts.map +1 -1
  59. package/dist/swc-esbuild-plugin.js +33 -15
  60. package/dist/swc-esbuild-plugin.js.map +1 -1
  61. package/dist/transform-utils.d.ts +68 -0
  62. package/dist/transform-utils.d.ts.map +1 -0
  63. package/dist/transform-utils.js +97 -0
  64. package/dist/transform-utils.js.map +1 -0
  65. package/dist/transform-utils.test.d.ts +2 -0
  66. package/dist/transform-utils.test.d.ts.map +1 -0
  67. package/dist/transform-utils.test.js +266 -0
  68. package/dist/transform-utils.test.js.map +1 -0
  69. package/dist/types.d.ts +26 -2
  70. package/dist/types.d.ts.map +1 -1
  71. package/dist/types.js +2 -0
  72. package/dist/types.js.map +1 -1
  73. package/dist/vercel-build-output-api.d.ts.map +1 -1
  74. package/dist/vercel-build-output-api.js +36 -14
  75. package/dist/vercel-build-output-api.js.map +1 -1
  76. package/dist/workflow-alias.d.ts +3 -0
  77. package/dist/workflow-alias.d.ts.map +1 -0
  78. package/dist/workflow-alias.js +46 -0
  79. package/dist/workflow-alias.js.map +1 -0
  80. package/dist/workflow-alias.test.d.ts +2 -0
  81. package/dist/workflow-alias.test.d.ts.map +1 -0
  82. package/dist/workflow-alias.test.js +46 -0
  83. package/dist/workflow-alias.test.js.map +1 -0
  84. package/dist/workflows-extractor.d.ts +92 -0
  85. package/dist/workflows-extractor.d.ts.map +1 -0
  86. package/dist/workflows-extractor.js +1476 -0
  87. package/dist/workflows-extractor.js.map +1 -0
  88. package/package.json +10 -9
@@ -0,0 +1,1476 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parseSync } from '@swc/core';
3
+ // ============================================================================
4
+ // Constants
5
+ // ============================================================================
6
+ /**
7
+ * Workflow primitives that should be shown as nodes in the graph.
8
+ * These are built-in workflow functions that represent meaningful
9
+ * pauses or wait points in the workflow execution.
10
+ */
11
+ const WORKFLOW_PRIMITIVES = new Set(['sleep', 'createHook', 'createWebhook']);
12
+ /**
13
+ * Extract the original function name from a stepId.
14
+ * stepId format: "step//path/to/file.ts//functionName"
15
+ * The bundler may rename functions to avoid collisions (e.g. add -> add2),
16
+ * but the stepId contains the original TypeScript function name.
17
+ */
18
+ function getOriginalStepName(stepId, fallbackName) {
19
+ const parts = stepId.split('//');
20
+ return parts.length > 2 ? parts[2] : fallbackName;
21
+ }
22
+ /**
23
+ * Extract a readable condition text from an Expression AST node.
24
+ * Recursively builds a string representation of the condition.
25
+ */
26
+ function getConditionText(expr) {
27
+ switch (expr.type) {
28
+ case 'Identifier':
29
+ return expr.value;
30
+ case 'BooleanLiteral':
31
+ return String(expr.value);
32
+ case 'NumericLiteral':
33
+ return String(expr.value);
34
+ case 'StringLiteral':
35
+ return `"${expr.value}"`;
36
+ case 'BinaryExpression': {
37
+ const bin = expr;
38
+ const left = getConditionText(bin.left);
39
+ const right = getConditionText(bin.right);
40
+ return `${left} ${bin.operator} ${right}`;
41
+ }
42
+ case 'UnaryExpression': {
43
+ const unary = expr;
44
+ const arg = getConditionText(unary.argument);
45
+ return `${unary.operator}${arg}`;
46
+ }
47
+ case 'MemberExpression': {
48
+ const member = expr;
49
+ const obj = getConditionText(member.object);
50
+ if (member.property.type === 'Identifier') {
51
+ return `${obj}.${member.property.value}`;
52
+ }
53
+ if (member.property.type === 'Computed') {
54
+ const computed = member.property.expression;
55
+ return `${obj}[${getConditionText(computed)}]`;
56
+ }
57
+ return obj;
58
+ }
59
+ case 'CallExpression': {
60
+ const call = expr;
61
+ const callee = call.callee;
62
+ // Handle callee which could be Expression, Super, or Import
63
+ if (callee.type === 'Super' || callee.type === 'Import') {
64
+ return `${callee.type.toLowerCase()}()`;
65
+ }
66
+ return `${getConditionText(callee)}()`;
67
+ }
68
+ case 'ParenthesisExpression': {
69
+ const paren = expr;
70
+ return `(${getConditionText(paren.expression)})`;
71
+ }
72
+ default:
73
+ return 'condition';
74
+ }
75
+ }
76
+ // =============================================================================
77
+ // Extraction Functions
78
+ // =============================================================================
79
+ /**
80
+ * Extracts workflow graphs from a bundled workflow file.
81
+ * Returns workflow entries organized by file path, ready for merging into Manifest.
82
+ */
83
+ export async function extractWorkflowGraphs(bundlePath) {
84
+ const bundleCode = await readFile(bundlePath, 'utf-8');
85
+ try {
86
+ let actualWorkflowCode = bundleCode;
87
+ const bundleAst = parseSync(bundleCode, {
88
+ syntax: 'ecmascript',
89
+ target: 'es2022',
90
+ });
91
+ const workflowCodeValue = extractWorkflowCodeFromBundle(bundleAst);
92
+ if (workflowCodeValue) {
93
+ actualWorkflowCode = workflowCodeValue;
94
+ }
95
+ const ast = parseSync(actualWorkflowCode, {
96
+ syntax: 'ecmascript',
97
+ target: 'es2022',
98
+ });
99
+ const stepDeclarations = extractStepDeclarations(actualWorkflowCode);
100
+ const functionMap = buildFunctionMap(ast, stepDeclarations);
101
+ const variableMap = buildVariableMap(ast);
102
+ return extractWorkflows(ast, stepDeclarations, functionMap, variableMap);
103
+ }
104
+ catch (error) {
105
+ console.error('Failed to extract workflow graphs from bundle:', error);
106
+ return {};
107
+ }
108
+ }
109
+ /**
110
+ * Extract the workflowCode string value from a parsed bundle AST
111
+ */
112
+ function extractWorkflowCodeFromBundle(ast) {
113
+ for (const item of ast.body) {
114
+ if (item.type === 'VariableDeclaration') {
115
+ for (const decl of item.declarations) {
116
+ if (decl.id.type === 'Identifier' &&
117
+ decl.id.value === 'workflowCode' &&
118
+ decl.init) {
119
+ if (decl.init.type === 'TemplateLiteral') {
120
+ return decl.init.quasis.map((q) => q.cooked || q.raw).join('');
121
+ }
122
+ if (decl.init.type === 'StringLiteral') {
123
+ return decl.init.value;
124
+ }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ return null;
130
+ }
131
+ /**
132
+ * Extract step declarations using regex for speed
133
+ */
134
+ function extractStepDeclarations(bundleCode) {
135
+ const stepDeclarations = new Map();
136
+ const stepPattern = /var (\w+) = globalThis\[Symbol\.for\("WORKFLOW_USE_STEP"\)\]\("([^"]+)"\)/g;
137
+ const lines = bundleCode.split('\n');
138
+ for (const line of lines) {
139
+ stepPattern.lastIndex = 0;
140
+ const match = stepPattern.exec(line);
141
+ if (match) {
142
+ const [, varName, stepId] = match;
143
+ stepDeclarations.set(varName, { stepId });
144
+ }
145
+ }
146
+ return stepDeclarations;
147
+ }
148
+ /**
149
+ * Extract inline step declarations from within a function body.
150
+ * These are steps defined as variable declarations inside a workflow function.
151
+ * Pattern: var/const varName = globalThis[Symbol.for("WORKFLOW_USE_STEP")]("stepId")
152
+ */
153
+ function extractInlineStepDeclarations(stmts) {
154
+ const inlineSteps = new Map();
155
+ for (const stmt of stmts) {
156
+ if (stmt.type === 'VariableDeclaration') {
157
+ const varDecl = stmt;
158
+ for (const decl of varDecl.declarations) {
159
+ if (decl.id.type === 'Identifier' &&
160
+ decl.init?.type === 'CallExpression') {
161
+ const callExpr = decl.init;
162
+ // Check for globalThis[Symbol.for("WORKFLOW_USE_STEP")]("stepId") pattern
163
+ if (callExpr.callee.type === 'MemberExpression') {
164
+ const member = callExpr.callee;
165
+ // Check if object is globalThis
166
+ if (member.object.type === 'Identifier' &&
167
+ member.object.value === 'globalThis' &&
168
+ member.property.type === 'Computed') {
169
+ // For computed member access globalThis[Symbol.for(...)],
170
+ // the property is a Computed type containing the expression
171
+ const computedExpr = member.property.expression;
172
+ if (computedExpr?.type === 'CallExpression') {
173
+ const symbolCall = computedExpr;
174
+ // Check if it's Symbol.for("WORKFLOW_USE_STEP")
175
+ if (symbolCall.callee.type === 'MemberExpression') {
176
+ const symbolMember = symbolCall.callee;
177
+ if (symbolMember.object.type === 'Identifier' &&
178
+ symbolMember.object.value === 'Symbol' &&
179
+ symbolMember.property.type === 'Identifier' &&
180
+ symbolMember.property.value === 'for' &&
181
+ symbolCall.arguments.length > 0 &&
182
+ symbolCall.arguments[0].expression.type ===
183
+ 'StringLiteral' &&
184
+ symbolCall.arguments[0].expression.value ===
185
+ 'WORKFLOW_USE_STEP') {
186
+ // Extract the stepId from the outer call arguments
187
+ if (callExpr.arguments.length > 0 &&
188
+ callExpr.arguments[0].expression.type === 'StringLiteral') {
189
+ const stepId = callExpr.arguments[0].expression
190
+ .value;
191
+ const varName = decl.id.value;
192
+ inlineSteps.set(varName, { stepId });
193
+ }
194
+ }
195
+ }
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ }
202
+ }
203
+ return inlineSteps;
204
+ }
205
+ /**
206
+ * Build a map of all functions in the bundle for transitive step resolution
207
+ */
208
+ function buildFunctionMap(ast, stepDeclarations) {
209
+ const functionMap = new Map();
210
+ for (const item of ast.body) {
211
+ if (item.type === 'FunctionDeclaration') {
212
+ const func = item;
213
+ if (func.identifier) {
214
+ const name = func.identifier.value;
215
+ const isStep = stepDeclarations.has(name);
216
+ functionMap.set(name, {
217
+ name,
218
+ body: func.body,
219
+ isStep,
220
+ stepId: isStep ? stepDeclarations.get(name)?.stepId : undefined,
221
+ });
222
+ }
223
+ }
224
+ if (item.type === 'VariableDeclaration') {
225
+ const varDecl = item;
226
+ for (const decl of varDecl.declarations) {
227
+ if (decl.id.type === 'Identifier' && decl.init) {
228
+ const name = decl.id.value;
229
+ const isStep = stepDeclarations.has(name);
230
+ if (decl.init.type === 'FunctionExpression') {
231
+ const funcExpr = decl.init;
232
+ functionMap.set(name, {
233
+ name,
234
+ body: funcExpr.body,
235
+ isStep,
236
+ stepId: isStep ? stepDeclarations.get(name)?.stepId : undefined,
237
+ });
238
+ }
239
+ else if (decl.init.type === 'ArrowFunctionExpression') {
240
+ const arrowFunc = decl.init;
241
+ functionMap.set(name, {
242
+ name,
243
+ body: arrowFunc.body,
244
+ isStep,
245
+ stepId: isStep ? stepDeclarations.get(name)?.stepId : undefined,
246
+ });
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ return functionMap;
253
+ }
254
+ /**
255
+ * Build a map of variable definitions (objects) for tool resolution
256
+ * This allows us to resolve tools objects to the actual tools object
257
+ */
258
+ function buildVariableMap(ast) {
259
+ const variableMap = new Map();
260
+ for (const item of ast.body) {
261
+ if (item.type === 'VariableDeclaration') {
262
+ const varDecl = item;
263
+ for (const decl of varDecl.declarations) {
264
+ if (decl.type === 'VariableDeclarator' &&
265
+ decl.id.type === 'Identifier' &&
266
+ decl.init?.type === 'ObjectExpression') {
267
+ variableMap.set(decl.id.value, decl.init);
268
+ }
269
+ }
270
+ }
271
+ }
272
+ return variableMap;
273
+ }
274
+ /**
275
+ * Extract workflows from AST
276
+ */
277
+ function extractWorkflows(ast, stepDeclarations, functionMap, variableMap) {
278
+ const result = {};
279
+ for (const item of ast.body) {
280
+ if (item.type === 'FunctionDeclaration') {
281
+ const func = item;
282
+ if (!func.identifier)
283
+ continue;
284
+ const workflowName = func.identifier.value;
285
+ const workflowId = findWorkflowId(ast, workflowName);
286
+ if (!workflowId)
287
+ continue;
288
+ // Extract file path and actual workflow name from workflowId: "workflow//path/to/file.ts//functionName"
289
+ // The bundler may rename functions to avoid collisions (e.g. addTenWorkflow -> addTenWorkflow2),
290
+ // but the workflowId contains the original TypeScript function name.
291
+ const parts = workflowId.split('//');
292
+ const filePath = parts.length > 1 ? parts[1] : 'unknown';
293
+ const actualWorkflowName = parts.length > 2 ? parts[2] : workflowName;
294
+ const graph = analyzeWorkflowFunction(func, workflowName, stepDeclarations, functionMap, variableMap);
295
+ if (!result[filePath]) {
296
+ result[filePath] = {};
297
+ }
298
+ result[filePath][actualWorkflowName] = {
299
+ workflowId,
300
+ graph,
301
+ };
302
+ }
303
+ }
304
+ return result;
305
+ }
306
+ /**
307
+ * Find workflowId assignment for a function
308
+ */
309
+ function findWorkflowId(ast, functionName) {
310
+ for (const item of ast.body) {
311
+ if (item.type === 'ExpressionStatement') {
312
+ const expr = item.expression;
313
+ if (expr.type === 'AssignmentExpression') {
314
+ const left = expr.left;
315
+ if (left.type === 'MemberExpression') {
316
+ const obj = left.object;
317
+ const prop = left.property;
318
+ if (obj.type === 'Identifier' &&
319
+ obj.value === functionName &&
320
+ prop.type === 'Identifier' &&
321
+ prop.value === 'workflowId') {
322
+ const right = expr.right;
323
+ if (right.type === 'StringLiteral') {
324
+ return right.value;
325
+ }
326
+ }
327
+ }
328
+ }
329
+ }
330
+ }
331
+ return null;
332
+ }
333
+ /**
334
+ * Analyze a workflow function and build its graph
335
+ */
336
+ function analyzeWorkflowFunction(func, workflowName, stepDeclarations, functionMap, variableMap) {
337
+ const nodes = [];
338
+ const edges = [];
339
+ // Add start node
340
+ nodes.push({
341
+ id: 'start',
342
+ type: 'workflowStart',
343
+ data: {
344
+ label: `Start: ${workflowName}`,
345
+ nodeKind: 'workflow_start',
346
+ },
347
+ });
348
+ const context = {
349
+ parallelCounter: 0,
350
+ loopCounter: 0,
351
+ conditionalCounter: 0,
352
+ nodeCounter: 0,
353
+ inLoop: null,
354
+ inConditional: null,
355
+ webhookVariables: new Set(),
356
+ promiseArrays: new Map(),
357
+ };
358
+ let prevExitIds = ['start'];
359
+ if (func.body?.stmts) {
360
+ // Extract inline step declarations from the workflow body
361
+ // These are steps defined as variables inside the workflow function
362
+ const inlineSteps = extractInlineStepDeclarations(func.body.stmts);
363
+ // Merge inline steps with global step declarations
364
+ const mergedStepDeclarations = new Map(stepDeclarations);
365
+ for (const [name, info] of inlineSteps) {
366
+ mergedStepDeclarations.set(name, info);
367
+ }
368
+ for (const stmt of func.body.stmts) {
369
+ const result = analyzeStatement(stmt, mergedStepDeclarations, context, functionMap, variableMap);
370
+ nodes.push(...result.nodes);
371
+ edges.push(...result.edges);
372
+ for (const prevId of prevExitIds) {
373
+ for (const entryId of result.entryNodeIds) {
374
+ const edgeId = `e_${prevId}_${entryId}`;
375
+ if (!edges.find((e) => e.id === edgeId)) {
376
+ const targetNode = result.nodes.find((n) => n.id === entryId);
377
+ // Only use 'parallel' type for parallel group connections
378
+ // Sequential connections (including to/from loops) should be 'default'
379
+ const edgeType = targetNode?.metadata?.parallelGroupId
380
+ ? 'parallel'
381
+ : 'default';
382
+ edges.push({
383
+ id: edgeId,
384
+ source: prevId,
385
+ target: entryId,
386
+ type: edgeType,
387
+ });
388
+ }
389
+ }
390
+ }
391
+ if (result.exitNodeIds.length > 0) {
392
+ prevExitIds = result.exitNodeIds;
393
+ }
394
+ }
395
+ }
396
+ // Add end node
397
+ nodes.push({
398
+ id: 'end',
399
+ type: 'workflowEnd',
400
+ data: {
401
+ label: 'Return',
402
+ nodeKind: 'workflow_end',
403
+ },
404
+ });
405
+ for (const prevId of prevExitIds) {
406
+ edges.push({
407
+ id: `e_${prevId}_end`,
408
+ source: prevId,
409
+ target: 'end',
410
+ type: 'default',
411
+ });
412
+ }
413
+ return { nodes, edges };
414
+ }
415
+ /**
416
+ * Check if a statement or block contains await expressions (recursively)
417
+ * Used to determine if a for/while loop is truly a looping execution pattern
418
+ * vs just collecting promises for parallel execution
419
+ */
420
+ function containsAwaitExpression(node) {
421
+ if (!node)
422
+ return false;
423
+ // Direct await expression
424
+ if (node.type === 'AwaitExpression')
425
+ return true;
426
+ // Check block statements
427
+ if (node.type === 'BlockStatement' && node.stmts) {
428
+ return node.stmts.some((stmt) => containsAwaitExpression(stmt));
429
+ }
430
+ // Check expression statements
431
+ if (node.type === 'ExpressionStatement' && node.expression) {
432
+ return containsAwaitExpression(node.expression);
433
+ }
434
+ // Check variable declarations
435
+ if (node.type === 'VariableDeclaration' && node.declarations) {
436
+ return node.declarations.some((decl) => decl.init && containsAwaitExpression(decl.init));
437
+ }
438
+ // Check if statements
439
+ if (node.type === 'IfStatement') {
440
+ return (containsAwaitExpression(node.consequent) ||
441
+ containsAwaitExpression(node.alternate));
442
+ }
443
+ // Check for statements
444
+ if (node.type === 'ForStatement' ||
445
+ node.type === 'WhileStatement' ||
446
+ node.type === 'ForOfStatement' ||
447
+ node.type === 'ForInStatement') {
448
+ return containsAwaitExpression(node.body);
449
+ }
450
+ // Check assignment expressions (e.g., result = await doWork())
451
+ if (node.type === 'AssignmentExpression') {
452
+ return containsAwaitExpression(node.right);
453
+ }
454
+ // Check call expressions (for await in arguments)
455
+ if (node.type === 'CallExpression') {
456
+ if (node.arguments) {
457
+ return node.arguments.some((arg) => containsAwaitExpression(arg.expression || arg));
458
+ }
459
+ }
460
+ return false;
461
+ }
462
+ /**
463
+ * Analyze a statement and extract step calls with proper CFG structure
464
+ */
465
+ function analyzeStatement(stmt, stepDeclarations, context, functionMap, variableMap) {
466
+ const nodes = [];
467
+ const edges = [];
468
+ let entryNodeIds = [];
469
+ let exitNodeIds = [];
470
+ if (stmt.type === 'VariableDeclaration') {
471
+ const varDecl = stmt;
472
+ for (const decl of varDecl.declarations) {
473
+ if (decl.init) {
474
+ // Track webhook/hook variable assignments: const webhook = createWebhook()
475
+ if (decl.id.type === 'Identifier' &&
476
+ decl.init.type === 'CallExpression' &&
477
+ decl.init.callee.type === 'Identifier') {
478
+ const funcName = decl.init.callee
479
+ .value;
480
+ if (funcName === 'createWebhook' || funcName === 'createHook') {
481
+ context.webhookVariables.add(decl.id.value);
482
+ }
483
+ }
484
+ // Track empty array assignments for Promise.all pattern: const promises = []
485
+ if (decl.id.type === 'Identifier' &&
486
+ decl.init.type === 'ArrayExpression') {
487
+ const elements = decl.init.elements;
488
+ // Empty array: elements is undefined, null, or empty array
489
+ if (!elements || elements.length === 0) {
490
+ const varName = decl.id.value;
491
+ context.promiseArrays.set(varName, []);
492
+ }
493
+ }
494
+ const result = analyzeExpression(decl.init, stepDeclarations, context, functionMap, variableMap);
495
+ nodes.push(...result.nodes);
496
+ edges.push(...result.edges);
497
+ if (entryNodeIds.length === 0) {
498
+ entryNodeIds = result.entryNodeIds;
499
+ }
500
+ else {
501
+ for (const prevId of exitNodeIds) {
502
+ for (const entryId of result.entryNodeIds) {
503
+ edges.push({
504
+ id: `e_${prevId}_${entryId}`,
505
+ source: prevId,
506
+ target: entryId,
507
+ type: 'default',
508
+ });
509
+ }
510
+ }
511
+ }
512
+ exitNodeIds = result.exitNodeIds;
513
+ }
514
+ }
515
+ }
516
+ if (stmt.type === 'ExpressionStatement') {
517
+ const result = analyzeExpression(stmt.expression, stepDeclarations, context, functionMap, variableMap);
518
+ nodes.push(...result.nodes);
519
+ edges.push(...result.edges);
520
+ entryNodeIds = result.entryNodeIds;
521
+ exitNodeIds = result.exitNodeIds;
522
+ }
523
+ if (stmt.type === 'IfStatement') {
524
+ const savedConditional = context.inConditional;
525
+ const conditionalId = `cond_${context.conditionalCounter++}`;
526
+ context.inConditional = conditionalId;
527
+ // Analyze the "then" branch first to check if it has any workflow-relevant nodes
528
+ let thenResult;
529
+ if (stmt.consequent.type === 'BlockStatement') {
530
+ thenResult = analyzeBlock(stmt.consequent.stmts, stepDeclarations, context, functionMap, variableMap);
531
+ }
532
+ else {
533
+ // Handle single-statement consequent (no braces)
534
+ thenResult = analyzeStatement(stmt.consequent, stepDeclarations, context, functionMap, variableMap);
535
+ }
536
+ // Analyze the "else" branch if it exists
537
+ let elseResult = null;
538
+ if (stmt.alternate) {
539
+ if (stmt.alternate.type === 'BlockStatement') {
540
+ elseResult = analyzeBlock(stmt.alternate.stmts, stepDeclarations, context, functionMap, variableMap);
541
+ }
542
+ else {
543
+ // Handle single-statement alternate (no braces) or else-if
544
+ elseResult = analyzeStatement(stmt.alternate, stepDeclarations, context, functionMap, variableMap);
545
+ }
546
+ }
547
+ // Only create conditional node if at least one branch has workflow-relevant nodes.
548
+ // This avoids creating nodes for runtime assertions like `if (!ctx) { throw ... }`
549
+ const thenHasNodes = thenResult.nodes.length > 0;
550
+ const elseHasNodes = elseResult ? elseResult.nodes.length > 0 : false;
551
+ if (thenHasNodes || elseHasNodes) {
552
+ // Create the conditional decision node
553
+ const conditionText = getConditionText(stmt.test);
554
+ const condNodeId = `${conditionalId}_node`;
555
+ const condMetadata = {};
556
+ if (context.inLoop) {
557
+ condMetadata.loopId = context.inLoop;
558
+ }
559
+ const condNode = {
560
+ id: condNodeId,
561
+ type: 'conditional',
562
+ data: {
563
+ label: conditionText,
564
+ nodeKind: 'conditional',
565
+ },
566
+ metadata: Object.keys(condMetadata).length > 0 ? condMetadata : undefined,
567
+ };
568
+ nodes.push(condNode);
569
+ // The conditional node is the entry point
570
+ entryNodeIds.push(condNodeId);
571
+ for (const node of thenResult.nodes) {
572
+ if (!node.metadata)
573
+ node.metadata = {};
574
+ node.metadata.conditionalId = conditionalId;
575
+ node.metadata.conditionalBranch = 'Then';
576
+ }
577
+ nodes.push(...thenResult.nodes);
578
+ edges.push(...thenResult.edges);
579
+ // Create edge from conditional node to "then" branch with "true" label
580
+ for (const thenEntryId of thenResult.entryNodeIds) {
581
+ edges.push({
582
+ id: `e_${condNodeId}_${thenEntryId}_true`,
583
+ source: condNodeId,
584
+ target: thenEntryId,
585
+ type: 'conditional',
586
+ label: 'true',
587
+ });
588
+ }
589
+ exitNodeIds.push(...thenResult.exitNodeIds);
590
+ if (elseResult) {
591
+ for (const node of elseResult.nodes) {
592
+ if (!node.metadata)
593
+ node.metadata = {};
594
+ node.metadata.conditionalId = conditionalId;
595
+ node.metadata.conditionalBranch = 'Else';
596
+ }
597
+ nodes.push(...elseResult.nodes);
598
+ edges.push(...elseResult.edges);
599
+ // Create edge from conditional node to "else" branch with "false" label
600
+ for (const elseEntryId of elseResult.entryNodeIds) {
601
+ edges.push({
602
+ id: `e_${condNodeId}_${elseEntryId}_false`,
603
+ source: condNodeId,
604
+ target: elseEntryId,
605
+ type: 'conditional',
606
+ label: 'false',
607
+ });
608
+ }
609
+ exitNodeIds.push(...elseResult.exitNodeIds);
610
+ }
611
+ }
612
+ // Note: When there's no else branch, we don't add the conditional node as an exit.
613
+ // The then-branch exits are the only exits. This means the graph shows the "true" path;
614
+ // the "false" case (when condition is false and there's no else) implicitly means
615
+ // execution continues with no steps from this if statement.
616
+ //
617
+ // When both branches have no workflow-relevant nodes (e.g., runtime assertions like
618
+ // `if (!ctx) { throw ... }`), we skip creating the conditional node entirely.
619
+ context.inConditional = savedConditional;
620
+ }
621
+ if (stmt.type === 'WhileStatement' || stmt.type === 'ForStatement') {
622
+ const body = stmt.type === 'WhileStatement' ? stmt.body : stmt.body;
623
+ // Only treat as a loop if the body contains await expressions
624
+ // Otherwise it's likely a "collect promises" pattern (for parallel execution)
625
+ const hasAwait = containsAwaitExpression(body);
626
+ const loopId = hasAwait ? `loop_${context.loopCounter++}` : undefined;
627
+ const savedLoop = context.inLoop;
628
+ if (loopId) {
629
+ context.inLoop = loopId;
630
+ }
631
+ if (body.type === 'BlockStatement') {
632
+ const loopResult = analyzeBlock(body.stmts, stepDeclarations, context, functionMap, variableMap);
633
+ // Only add loop metadata if this is truly a looping pattern
634
+ if (loopId) {
635
+ for (const node of loopResult.nodes) {
636
+ if (!node.metadata)
637
+ node.metadata = {};
638
+ node.metadata.loopId = loopId;
639
+ }
640
+ }
641
+ nodes.push(...loopResult.nodes);
642
+ edges.push(...loopResult.edges);
643
+ entryNodeIds = loopResult.entryNodeIds;
644
+ exitNodeIds = loopResult.exitNodeIds;
645
+ // Only create loop-back edges if this is truly a looping pattern
646
+ if (loopId) {
647
+ for (const exitId of loopResult.exitNodeIds) {
648
+ for (const entryId of loopResult.entryNodeIds) {
649
+ edges.push({
650
+ id: `e_${exitId}_back_${entryId}`,
651
+ source: exitId,
652
+ target: entryId,
653
+ type: 'loop',
654
+ });
655
+ }
656
+ }
657
+ }
658
+ }
659
+ else {
660
+ // Handle single-statement body (no braces)
661
+ const loopResult = analyzeStatement(body, stepDeclarations, context, functionMap, variableMap);
662
+ // Only add loop metadata if this is truly a looping pattern
663
+ if (loopId) {
664
+ for (const node of loopResult.nodes) {
665
+ if (!node.metadata)
666
+ node.metadata = {};
667
+ node.metadata.loopId = loopId;
668
+ }
669
+ }
670
+ nodes.push(...loopResult.nodes);
671
+ edges.push(...loopResult.edges);
672
+ entryNodeIds = loopResult.entryNodeIds;
673
+ exitNodeIds = loopResult.exitNodeIds;
674
+ // Only create loop-back edges if this is truly a looping pattern
675
+ if (loopId) {
676
+ for (const exitId of loopResult.exitNodeIds) {
677
+ for (const entryId of loopResult.entryNodeIds) {
678
+ edges.push({
679
+ id: `e_${exitId}_back_${entryId}`,
680
+ source: exitId,
681
+ target: entryId,
682
+ type: 'loop',
683
+ });
684
+ }
685
+ }
686
+ }
687
+ }
688
+ context.inLoop = savedLoop;
689
+ }
690
+ if (stmt.type === 'ForOfStatement') {
691
+ const loopId = `loop_${context.loopCounter++}`;
692
+ const savedLoop = context.inLoop;
693
+ context.inLoop = loopId;
694
+ const isAwait = stmt.isAwait || stmt.await;
695
+ const body = stmt.body;
696
+ if (body.type === 'BlockStatement') {
697
+ const loopResult = analyzeBlock(body.stmts, stepDeclarations, context, functionMap, variableMap);
698
+ for (const node of loopResult.nodes) {
699
+ if (!node.metadata)
700
+ node.metadata = {};
701
+ node.metadata.loopId = loopId;
702
+ node.metadata.loopIsAwait = isAwait;
703
+ }
704
+ nodes.push(...loopResult.nodes);
705
+ edges.push(...loopResult.edges);
706
+ entryNodeIds = loopResult.entryNodeIds;
707
+ exitNodeIds = loopResult.exitNodeIds;
708
+ for (const exitId of loopResult.exitNodeIds) {
709
+ for (const entryId of loopResult.entryNodeIds) {
710
+ edges.push({
711
+ id: `e_${exitId}_back_${entryId}`,
712
+ source: exitId,
713
+ target: entryId,
714
+ type: 'loop',
715
+ });
716
+ }
717
+ }
718
+ }
719
+ else {
720
+ // Handle single-statement body (no braces)
721
+ const loopResult = analyzeStatement(body, stepDeclarations, context, functionMap, variableMap);
722
+ for (const node of loopResult.nodes) {
723
+ if (!node.metadata)
724
+ node.metadata = {};
725
+ node.metadata.loopId = loopId;
726
+ node.metadata.loopIsAwait = isAwait;
727
+ }
728
+ nodes.push(...loopResult.nodes);
729
+ edges.push(...loopResult.edges);
730
+ entryNodeIds = loopResult.entryNodeIds;
731
+ exitNodeIds = loopResult.exitNodeIds;
732
+ for (const exitId of loopResult.exitNodeIds) {
733
+ for (const entryId of loopResult.entryNodeIds) {
734
+ edges.push({
735
+ id: `e_${exitId}_back_${entryId}`,
736
+ source: exitId,
737
+ target: entryId,
738
+ type: 'loop',
739
+ });
740
+ }
741
+ }
742
+ }
743
+ context.inLoop = savedLoop;
744
+ }
745
+ // Handle plain BlockStatement (bare blocks like { ... })
746
+ if (stmt.type === 'BlockStatement') {
747
+ const blockResult = analyzeBlock(stmt.stmts, stepDeclarations, context, functionMap, variableMap);
748
+ nodes.push(...blockResult.nodes);
749
+ edges.push(...blockResult.edges);
750
+ entryNodeIds = blockResult.entryNodeIds;
751
+ exitNodeIds = blockResult.exitNodeIds;
752
+ }
753
+ if (stmt.type === 'ReturnStatement' && stmt.argument) {
754
+ const result = analyzeExpression(stmt.argument, stepDeclarations, context, functionMap, variableMap);
755
+ nodes.push(...result.nodes);
756
+ edges.push(...result.edges);
757
+ entryNodeIds = result.entryNodeIds;
758
+ exitNodeIds = result.exitNodeIds;
759
+ }
760
+ return { nodes, edges, entryNodeIds, exitNodeIds };
761
+ }
762
+ /**
763
+ * Analyze a block of statements with proper sequential chaining
764
+ */
765
+ function analyzeBlock(stmts, stepDeclarations, context, functionMap, variableMap) {
766
+ const nodes = [];
767
+ const edges = [];
768
+ let entryNodeIds = [];
769
+ let currentExitIds = [];
770
+ for (const stmt of stmts) {
771
+ const result = analyzeStatement(stmt, stepDeclarations, context, functionMap, variableMap);
772
+ if (result.nodes.length === 0)
773
+ continue;
774
+ nodes.push(...result.nodes);
775
+ edges.push(...result.edges);
776
+ if (entryNodeIds.length === 0 && result.entryNodeIds.length > 0) {
777
+ entryNodeIds = result.entryNodeIds;
778
+ }
779
+ if (currentExitIds.length > 0 && result.entryNodeIds.length > 0) {
780
+ for (const prevId of currentExitIds) {
781
+ for (const entryId of result.entryNodeIds) {
782
+ const targetNode = result.nodes.find((n) => n.id === entryId);
783
+ const edgeType = targetNode?.metadata?.parallelGroupId
784
+ ? 'parallel'
785
+ : 'default';
786
+ edges.push({
787
+ id: `e_${prevId}_${entryId}`,
788
+ source: prevId,
789
+ target: entryId,
790
+ type: edgeType,
791
+ });
792
+ }
793
+ }
794
+ }
795
+ if (result.exitNodeIds.length > 0) {
796
+ currentExitIds = result.exitNodeIds;
797
+ }
798
+ }
799
+ return { nodes, edges, entryNodeIds, exitNodeIds: currentExitIds };
800
+ }
801
+ /**
802
+ * Analyze an expression and extract step calls
803
+ */
804
+ function analyzeExpression(expr, stepDeclarations, context, functionMap, variableMap, visitedFunctions = new Set()) {
805
+ const nodes = [];
806
+ const edges = [];
807
+ const entryNodeIds = [];
808
+ const exitNodeIds = [];
809
+ if (expr.type === 'AwaitExpression') {
810
+ const awaitedExpr = expr.argument;
811
+ if (awaitedExpr.type === 'CallExpression') {
812
+ const callExpr = awaitedExpr;
813
+ // Check for Promise.all/race/allSettled/any
814
+ if (callExpr.callee.type === 'MemberExpression') {
815
+ const member = callExpr.callee;
816
+ if (member.object.type === 'Identifier' &&
817
+ member.object.value === 'Promise' &&
818
+ member.property.type === 'Identifier') {
819
+ const method = member.property.value;
820
+ if (['all', 'race', 'allSettled', 'any'].includes(method)) {
821
+ const parallelId = `parallel_${context.parallelCounter++}`;
822
+ if (callExpr.arguments.length > 0) {
823
+ const arg = callExpr.arguments[0].expression;
824
+ if (arg.type === 'ArrayExpression') {
825
+ for (const element of arg.elements) {
826
+ if (element?.expression) {
827
+ const elemResult = analyzeExpression(element.expression, stepDeclarations, context, functionMap, variableMap, visitedFunctions);
828
+ for (const node of elemResult.nodes) {
829
+ if (!node.metadata)
830
+ node.metadata = {};
831
+ node.metadata.parallelGroupId = parallelId;
832
+ node.metadata.parallelMethod = method;
833
+ if (context.inLoop) {
834
+ node.metadata.loopId = context.inLoop;
835
+ }
836
+ }
837
+ nodes.push(...elemResult.nodes);
838
+ edges.push(...elemResult.edges);
839
+ entryNodeIds.push(...elemResult.entryNodeIds);
840
+ exitNodeIds.push(...elemResult.exitNodeIds);
841
+ }
842
+ }
843
+ }
844
+ else if (arg.type === 'Identifier' &&
845
+ context.promiseArrays.has(arg.value)) {
846
+ // Handle Promise.all(variableName) where variable was built via push()
847
+ const arrayName = arg.value;
848
+ const trackedNodes = context.promiseArrays.get(arrayName);
849
+ // Apply parallelGroupId to all nodes that were pushed to this array
850
+ if (trackedNodes && trackedNodes.length > 0) {
851
+ for (const trackedNode of trackedNodes) {
852
+ if (!trackedNode.metadata)
853
+ trackedNode.metadata = {};
854
+ trackedNode.metadata.parallelGroupId = parallelId;
855
+ trackedNode.metadata.parallelMethod = method;
856
+ if (context.inLoop) {
857
+ trackedNode.metadata.loopId = context.inLoop;
858
+ }
859
+ // Return tracked node IDs for proper edge connections
860
+ entryNodeIds.push(trackedNode.id);
861
+ exitNodeIds.push(trackedNode.id);
862
+ }
863
+ }
864
+ }
865
+ else {
866
+ // Handle non-array arguments like array.map(stepFn)
867
+ const argResult = analyzeExpression(arg, stepDeclarations, context, functionMap, variableMap, visitedFunctions);
868
+ for (const node of argResult.nodes) {
869
+ if (!node.metadata)
870
+ node.metadata = {};
871
+ node.metadata.parallelGroupId = parallelId;
872
+ node.metadata.parallelMethod = method;
873
+ if (context.inLoop) {
874
+ node.metadata.loopId = context.inLoop;
875
+ }
876
+ }
877
+ nodes.push(...argResult.nodes);
878
+ edges.push(...argResult.edges);
879
+ entryNodeIds.push(...argResult.entryNodeIds);
880
+ exitNodeIds.push(...argResult.exitNodeIds);
881
+ }
882
+ }
883
+ return { nodes, edges, entryNodeIds, exitNodeIds };
884
+ }
885
+ }
886
+ }
887
+ // Regular call - check if it's a step, workflow primitive, or helper function
888
+ if (callExpr.callee.type === 'Identifier') {
889
+ const funcName = callExpr.callee.value;
890
+ const stepInfo = stepDeclarations.get(funcName);
891
+ if (stepInfo) {
892
+ const nodeId = `node_${context.nodeCounter++}`;
893
+ const metadata = {};
894
+ if (context.inLoop) {
895
+ metadata.loopId = context.inLoop;
896
+ }
897
+ if (context.inConditional) {
898
+ metadata.conditionalId = context.inConditional;
899
+ }
900
+ const node = {
901
+ id: nodeId,
902
+ type: 'step',
903
+ data: {
904
+ label: getOriginalStepName(stepInfo.stepId, funcName),
905
+ nodeKind: 'step',
906
+ stepId: stepInfo.stepId,
907
+ },
908
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
909
+ };
910
+ nodes.push(node);
911
+ entryNodeIds.push(nodeId);
912
+ exitNodeIds.push(nodeId);
913
+ }
914
+ else if (WORKFLOW_PRIMITIVES.has(funcName)) {
915
+ // Handle workflow primitives like sleep
916
+ const nodeId = `node_${context.nodeCounter++}`;
917
+ const metadata = {};
918
+ if (context.inLoop) {
919
+ metadata.loopId = context.inLoop;
920
+ }
921
+ if (context.inConditional) {
922
+ metadata.conditionalId = context.inConditional;
923
+ }
924
+ const node = {
925
+ id: nodeId,
926
+ type: 'primitive',
927
+ data: {
928
+ label: funcName,
929
+ nodeKind: 'primitive',
930
+ },
931
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
932
+ };
933
+ nodes.push(node);
934
+ entryNodeIds.push(nodeId);
935
+ exitNodeIds.push(nodeId);
936
+ }
937
+ else {
938
+ const transitiveResult = analyzeTransitiveCall(funcName, stepDeclarations, context, functionMap, variableMap, visitedFunctions);
939
+ nodes.push(...transitiveResult.nodes);
940
+ edges.push(...transitiveResult.edges);
941
+ entryNodeIds.push(...transitiveResult.entryNodeIds);
942
+ exitNodeIds.push(...transitiveResult.exitNodeIds);
943
+ }
944
+ }
945
+ // Also analyze the arguments of awaited calls for step references in objects
946
+ for (const arg of callExpr.arguments) {
947
+ if (arg.expression?.type === 'ObjectExpression') {
948
+ const refResult = analyzeObjectForStepReferences(arg.expression, stepDeclarations, context, '');
949
+ nodes.push(...refResult.nodes);
950
+ edges.push(...refResult.edges);
951
+ entryNodeIds.push(...refResult.entryNodeIds);
952
+ exitNodeIds.push(...refResult.exitNodeIds);
953
+ }
954
+ }
955
+ }
956
+ // Handle await on a webhook/hook variable: await webhook
957
+ if (awaitedExpr.type === 'Identifier') {
958
+ const varName = awaitedExpr.value;
959
+ if (context.webhookVariables.has(varName)) {
960
+ const nodeId = `node_${context.nodeCounter++}`;
961
+ const metadata = {};
962
+ if (context.inLoop) {
963
+ metadata.loopId = context.inLoop;
964
+ }
965
+ if (context.inConditional) {
966
+ metadata.conditionalId = context.inConditional;
967
+ }
968
+ const node = {
969
+ id: nodeId,
970
+ type: 'primitive',
971
+ data: {
972
+ label: 'awaitWebhook',
973
+ nodeKind: 'primitive',
974
+ },
975
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
976
+ };
977
+ nodes.push(node);
978
+ entryNodeIds.push(nodeId);
979
+ exitNodeIds.push(nodeId);
980
+ }
981
+ }
982
+ }
983
+ // Non-awaited call expression
984
+ if (expr.type === 'CallExpression') {
985
+ const callExpr = expr;
986
+ if (callExpr.callee.type === 'Identifier') {
987
+ const funcName = callExpr.callee.value;
988
+ const stepInfo = stepDeclarations.get(funcName);
989
+ if (stepInfo) {
990
+ const nodeId = `node_${context.nodeCounter++}`;
991
+ const metadata = {};
992
+ if (context.inLoop) {
993
+ metadata.loopId = context.inLoop;
994
+ }
995
+ if (context.inConditional) {
996
+ metadata.conditionalId = context.inConditional;
997
+ }
998
+ const node = {
999
+ id: nodeId,
1000
+ type: 'step',
1001
+ data: {
1002
+ label: getOriginalStepName(stepInfo.stepId, funcName),
1003
+ nodeKind: 'step',
1004
+ stepId: stepInfo.stepId,
1005
+ },
1006
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
1007
+ };
1008
+ nodes.push(node);
1009
+ entryNodeIds.push(nodeId);
1010
+ exitNodeIds.push(nodeId);
1011
+ }
1012
+ else if (WORKFLOW_PRIMITIVES.has(funcName)) {
1013
+ // Handle non-awaited workflow primitives like createHook, createWebhook
1014
+ const nodeId = `node_${context.nodeCounter++}`;
1015
+ const metadata = {};
1016
+ if (context.inLoop) {
1017
+ metadata.loopId = context.inLoop;
1018
+ }
1019
+ if (context.inConditional) {
1020
+ metadata.conditionalId = context.inConditional;
1021
+ }
1022
+ const node = {
1023
+ id: nodeId,
1024
+ type: 'primitive',
1025
+ data: {
1026
+ label: funcName,
1027
+ nodeKind: 'primitive',
1028
+ },
1029
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
1030
+ };
1031
+ nodes.push(node);
1032
+ entryNodeIds.push(nodeId);
1033
+ exitNodeIds.push(nodeId);
1034
+ }
1035
+ else {
1036
+ const transitiveResult = analyzeTransitiveCall(funcName, stepDeclarations, context, functionMap, variableMap, visitedFunctions);
1037
+ nodes.push(...transitiveResult.nodes);
1038
+ edges.push(...transitiveResult.edges);
1039
+ entryNodeIds.push(...transitiveResult.entryNodeIds);
1040
+ exitNodeIds.push(...transitiveResult.exitNodeIds);
1041
+ }
1042
+ }
1043
+ }
1044
+ // Check for step references in object literals
1045
+ if (expr.type === 'ObjectExpression') {
1046
+ const refResult = analyzeObjectForStepReferences(expr, stepDeclarations, context, '');
1047
+ nodes.push(...refResult.nodes);
1048
+ edges.push(...refResult.edges);
1049
+ entryNodeIds.push(...refResult.entryNodeIds);
1050
+ exitNodeIds.push(...refResult.exitNodeIds);
1051
+ }
1052
+ // Check for step references and step calls in function call arguments
1053
+ // Skip for array methods (map, forEach, etc.) which have a specialized handler below
1054
+ if (expr.type === 'CallExpression') {
1055
+ const callExpr = expr;
1056
+ // Check if this is an array method call - if so, skip the generic handler
1057
+ // and let the specialized handler at the end of this function handle it
1058
+ const isArrayMethodCall = callExpr.callee.type === 'MemberExpression' &&
1059
+ callExpr.callee.property.type === 'Identifier' &&
1060
+ ['map', 'forEach', 'filter', 'find', 'some', 'every', 'flatMap'].includes(callExpr.callee.property.value);
1061
+ // Check if this is a .push() call on a tracked promise array
1062
+ // Pattern: promises.push(stepCall())
1063
+ let pushArrayName = null;
1064
+ if (callExpr.callee.type === 'MemberExpression' &&
1065
+ callExpr.callee.object.type === 'Identifier' &&
1066
+ callExpr.callee.property.type === 'Identifier' &&
1067
+ callExpr.callee.property.value ===
1068
+ 'push') {
1069
+ const objName = callExpr.callee.object.value;
1070
+ if (context.promiseArrays.has(objName)) {
1071
+ pushArrayName = objName;
1072
+ }
1073
+ }
1074
+ for (const arg of callExpr.arguments) {
1075
+ if (arg.expression) {
1076
+ // For array method calls, skip step identifier detection here
1077
+ // since we have a specialized handler for those
1078
+ if (arg.expression.type === 'Identifier' && !isArrayMethodCall) {
1079
+ const argName = arg.expression.value;
1080
+ const stepInfo = stepDeclarations.get(argName);
1081
+ if (stepInfo) {
1082
+ const nodeId = `node_${context.nodeCounter++}`;
1083
+ const node = {
1084
+ id: nodeId,
1085
+ type: 'step',
1086
+ data: {
1087
+ label: `${getOriginalStepName(stepInfo.stepId, argName)} (ref)`,
1088
+ nodeKind: 'step',
1089
+ stepId: stepInfo.stepId,
1090
+ },
1091
+ metadata: {
1092
+ isStepReference: true,
1093
+ referenceContext: 'function argument',
1094
+ },
1095
+ };
1096
+ nodes.push(node);
1097
+ entryNodeIds.push(nodeId);
1098
+ exitNodeIds.push(nodeId);
1099
+ }
1100
+ }
1101
+ // Handle step calls passed as arguments (e.g., promises.push(stepCall()))
1102
+ // Note: Don't add loopId here - these are non-awaited calls being collected
1103
+ // for parallel execution (like Promise.all), not truly looping calls
1104
+ if (arg.expression.type === 'CallExpression') {
1105
+ const argCallExpr = arg.expression;
1106
+ if (argCallExpr.callee.type === 'Identifier') {
1107
+ const funcName = argCallExpr.callee.value;
1108
+ const stepInfo = stepDeclarations.get(funcName);
1109
+ if (stepInfo) {
1110
+ const nodeId = `node_${context.nodeCounter++}`;
1111
+ const metadata = {};
1112
+ // Don't add loopId - this is a non-awaited call, likely being
1113
+ // collected for parallel execution (Promise.all pattern)
1114
+ if (context.inConditional) {
1115
+ metadata.conditionalId = context.inConditional;
1116
+ }
1117
+ const node = {
1118
+ id: nodeId,
1119
+ type: 'step',
1120
+ data: {
1121
+ label: getOriginalStepName(stepInfo.stepId, funcName),
1122
+ nodeKind: 'step',
1123
+ stepId: stepInfo.stepId,
1124
+ },
1125
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
1126
+ };
1127
+ // If this is being pushed to a tracked promise array, store the node
1128
+ // so we can apply parallelGroupId when Promise.all is reached
1129
+ if (pushArrayName) {
1130
+ const trackedNodes = context.promiseArrays.get(pushArrayName);
1131
+ if (trackedNodes) {
1132
+ trackedNodes.push(node);
1133
+ }
1134
+ }
1135
+ nodes.push(node);
1136
+ entryNodeIds.push(nodeId);
1137
+ exitNodeIds.push(nodeId);
1138
+ }
1139
+ }
1140
+ }
1141
+ if (arg.expression.type === 'ObjectExpression') {
1142
+ const refResult = analyzeObjectForStepReferences(arg.expression, stepDeclarations, context, '');
1143
+ nodes.push(...refResult.nodes);
1144
+ edges.push(...refResult.edges);
1145
+ entryNodeIds.push(...refResult.entryNodeIds);
1146
+ exitNodeIds.push(...refResult.exitNodeIds);
1147
+ }
1148
+ }
1149
+ }
1150
+ }
1151
+ // Check for step references in 'new' expressions
1152
+ if (expr.type === 'NewExpression') {
1153
+ const newExpr = expr;
1154
+ // Check if this is a DurableAgent instantiation
1155
+ const isDurableAgent = newExpr.callee?.type === 'Identifier' &&
1156
+ newExpr.callee?.value === 'DurableAgent';
1157
+ if (isDurableAgent && newExpr.arguments?.length > 0) {
1158
+ // Create a node for the DurableAgent itself
1159
+ const agentNodeId = `node_${context.nodeCounter++}`;
1160
+ const agentNode = {
1161
+ id: agentNodeId,
1162
+ type: 'agent',
1163
+ data: {
1164
+ label: 'DurableAgent',
1165
+ nodeKind: 'agent',
1166
+ },
1167
+ metadata: {
1168
+ isStepReference: true,
1169
+ referenceContext: 'DurableAgent',
1170
+ },
1171
+ };
1172
+ nodes.push(agentNode);
1173
+ entryNodeIds.push(agentNodeId);
1174
+ // Look for tools in the constructor options
1175
+ const optionsArg = newExpr.arguments[0]?.expression;
1176
+ if (optionsArg?.type === 'ObjectExpression') {
1177
+ const toolsResult = analyzeDurableAgentTools(optionsArg, stepDeclarations, context, agentNodeId, variableMap);
1178
+ nodes.push(...toolsResult.nodes);
1179
+ edges.push(...toolsResult.edges);
1180
+ // If we found tools, they are the exit nodes
1181
+ if (toolsResult.exitNodeIds.length > 0) {
1182
+ exitNodeIds.push(...toolsResult.exitNodeIds);
1183
+ }
1184
+ else {
1185
+ exitNodeIds.push(agentNodeId);
1186
+ }
1187
+ }
1188
+ else {
1189
+ exitNodeIds.push(agentNodeId);
1190
+ }
1191
+ }
1192
+ else if (newExpr.arguments) {
1193
+ for (const arg of newExpr.arguments) {
1194
+ if (arg.expression?.type === 'ObjectExpression') {
1195
+ const refResult = analyzeObjectForStepReferences(arg.expression, stepDeclarations, context, '');
1196
+ nodes.push(...refResult.nodes);
1197
+ edges.push(...refResult.edges);
1198
+ entryNodeIds.push(...refResult.entryNodeIds);
1199
+ exitNodeIds.push(...refResult.exitNodeIds);
1200
+ }
1201
+ }
1202
+ }
1203
+ }
1204
+ // Handle AssignmentExpression - analyze the right-hand side
1205
+ if (expr.type === 'AssignmentExpression') {
1206
+ const assignExpr = expr;
1207
+ if (assignExpr.right) {
1208
+ const rightResult = analyzeExpression(assignExpr.right, stepDeclarations, context, functionMap, variableMap, visitedFunctions);
1209
+ nodes.push(...rightResult.nodes);
1210
+ edges.push(...rightResult.edges);
1211
+ entryNodeIds.push(...rightResult.entryNodeIds);
1212
+ exitNodeIds.push(...rightResult.exitNodeIds);
1213
+ }
1214
+ }
1215
+ // Handle MemberExpression calls like array.map(stepFn) where step is passed as callback
1216
+ if (expr.type === 'CallExpression') {
1217
+ const callExpr = expr;
1218
+ if (callExpr.callee.type === 'MemberExpression') {
1219
+ const member = callExpr.callee;
1220
+ // Check if this is a method call like .map(), .forEach(), .filter() etc.
1221
+ if (member.property.type === 'Identifier') {
1222
+ const methodName = member.property.value;
1223
+ if ([
1224
+ 'map',
1225
+ 'forEach',
1226
+ 'filter',
1227
+ 'find',
1228
+ 'some',
1229
+ 'every',
1230
+ 'flatMap',
1231
+ ].includes(methodName)) {
1232
+ // Check if any argument is a step function reference
1233
+ for (const arg of callExpr.arguments) {
1234
+ if (arg.expression?.type === 'Identifier') {
1235
+ const argName = arg.expression.value;
1236
+ const stepInfo = stepDeclarations.get(argName);
1237
+ if (stepInfo) {
1238
+ const nodeId = `node_${context.nodeCounter++}`;
1239
+ const metadata = {};
1240
+ if (context.inLoop) {
1241
+ metadata.loopId = context.inLoop;
1242
+ }
1243
+ if (context.inConditional) {
1244
+ metadata.conditionalId = context.inConditional;
1245
+ }
1246
+ const node = {
1247
+ id: nodeId,
1248
+ type: 'step',
1249
+ data: {
1250
+ label: getOriginalStepName(stepInfo.stepId, argName),
1251
+ nodeKind: 'step',
1252
+ stepId: stepInfo.stepId,
1253
+ },
1254
+ metadata: Object.keys(metadata).length > 0 ? metadata : undefined,
1255
+ };
1256
+ nodes.push(node);
1257
+ entryNodeIds.push(nodeId);
1258
+ exitNodeIds.push(nodeId);
1259
+ }
1260
+ }
1261
+ }
1262
+ }
1263
+ }
1264
+ }
1265
+ }
1266
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1267
+ }
1268
+ /**
1269
+ * Analyze DurableAgent tools property to extract tool nodes
1270
+ */
1271
+ function analyzeDurableAgentTools(optionsObj, stepDeclarations, context, agentNodeId, variableMap) {
1272
+ const nodes = [];
1273
+ const edges = [];
1274
+ const entryNodeIds = [];
1275
+ const exitNodeIds = [];
1276
+ if (!optionsObj.properties)
1277
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1278
+ // Helper function to extract tools from an ObjectExpression
1279
+ function extractToolsFromObject(toolsObj) {
1280
+ for (const toolProp of toolsObj.properties || []) {
1281
+ if (toolProp.type !== 'KeyValueProperty')
1282
+ continue;
1283
+ let toolName = '';
1284
+ if (toolProp.key.type === 'Identifier') {
1285
+ toolName = toolProp.key.value;
1286
+ }
1287
+ if (!toolName)
1288
+ continue;
1289
+ // Look for execute property in the tool definition
1290
+ if (toolProp.value.type === 'ObjectExpression') {
1291
+ for (const innerProp of toolProp.value.properties || []) {
1292
+ if (innerProp.type !== 'KeyValueProperty')
1293
+ continue;
1294
+ let innerKey = '';
1295
+ if (innerProp.key.type === 'Identifier') {
1296
+ innerKey = innerProp.key.value;
1297
+ }
1298
+ if (innerKey === 'execute' && innerProp.value.type === 'Identifier') {
1299
+ const stepName = innerProp.value.value;
1300
+ const stepInfo = stepDeclarations.get(stepName);
1301
+ const nodeId = `node_${context.nodeCounter++}`;
1302
+ const node = {
1303
+ id: nodeId,
1304
+ type: 'tool',
1305
+ data: {
1306
+ label: stepName,
1307
+ nodeKind: 'tool',
1308
+ stepId: stepInfo?.stepId,
1309
+ },
1310
+ metadata: {
1311
+ isTool: true,
1312
+ toolName: toolName,
1313
+ referenceContext: `tools.${toolName}.execute`,
1314
+ },
1315
+ };
1316
+ nodes.push(node);
1317
+ exitNodeIds.push(nodeId);
1318
+ // Connect agent to this tool with tool edge type
1319
+ edges.push({
1320
+ id: `e_${agentNodeId}_${nodeId}`,
1321
+ source: agentNodeId,
1322
+ target: nodeId,
1323
+ type: 'tool',
1324
+ });
1325
+ }
1326
+ }
1327
+ }
1328
+ }
1329
+ }
1330
+ // Find the 'tools' property
1331
+ for (const prop of optionsObj.properties) {
1332
+ if (prop.type !== 'KeyValueProperty')
1333
+ continue;
1334
+ let keyName = '';
1335
+ if (prop.key.type === 'Identifier') {
1336
+ keyName = prop.key.value;
1337
+ }
1338
+ if (keyName !== 'tools')
1339
+ continue;
1340
+ // Handle inline tools object
1341
+ if (prop.value.type === 'ObjectExpression') {
1342
+ extractToolsFromObject(prop.value);
1343
+ }
1344
+ // Handle tools as a variable reference - resolve it from variableMap
1345
+ if (prop.value.type === 'Identifier') {
1346
+ const toolsVarName = prop.value.value;
1347
+ // Try to resolve the variable from the variableMap (bundled code)
1348
+ const resolvedToolsObj = variableMap.get(toolsVarName);
1349
+ if (resolvedToolsObj && resolvedToolsObj.type === 'ObjectExpression') {
1350
+ // Successfully resolved - extract individual tools
1351
+ extractToolsFromObject(resolvedToolsObj);
1352
+ }
1353
+ else {
1354
+ // Fallback: create a placeholder node if we can't resolve
1355
+ const nodeId = `node_${context.nodeCounter++}`;
1356
+ const node = {
1357
+ id: nodeId,
1358
+ type: 'tool',
1359
+ data: {
1360
+ label: `${toolsVarName}`,
1361
+ nodeKind: 'tool',
1362
+ },
1363
+ metadata: {
1364
+ isToolsCollection: true,
1365
+ toolsVariable: toolsVarName,
1366
+ referenceContext: `tools:${toolsVarName}`,
1367
+ },
1368
+ };
1369
+ nodes.push(node);
1370
+ exitNodeIds.push(nodeId);
1371
+ // Connect agent to tools with tool edge type
1372
+ edges.push({
1373
+ id: `e_${agentNodeId}_${nodeId}`,
1374
+ source: agentNodeId,
1375
+ target: nodeId,
1376
+ type: 'tool',
1377
+ });
1378
+ }
1379
+ }
1380
+ }
1381
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1382
+ }
1383
+ /**
1384
+ * Analyze an object expression for step references
1385
+ */
1386
+ function analyzeObjectForStepReferences(obj, stepDeclarations, context, path) {
1387
+ const nodes = [];
1388
+ const edges = [];
1389
+ const entryNodeIds = [];
1390
+ const exitNodeIds = [];
1391
+ if (!obj.properties)
1392
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1393
+ for (const prop of obj.properties) {
1394
+ if (prop.type !== 'KeyValueProperty')
1395
+ continue;
1396
+ let keyName = '';
1397
+ if (prop.key.type === 'Identifier') {
1398
+ keyName = prop.key.value;
1399
+ }
1400
+ else if (prop.key.type === 'StringLiteral') {
1401
+ keyName = prop.key.value;
1402
+ }
1403
+ const currentPath = path ? `${path}.${keyName}` : keyName;
1404
+ if (prop.value.type === 'Identifier') {
1405
+ const valueName = prop.value.value;
1406
+ const stepInfo = stepDeclarations.get(valueName);
1407
+ if (stepInfo) {
1408
+ const nodeId = `node_${context.nodeCounter++}`;
1409
+ const node = {
1410
+ id: nodeId,
1411
+ type: 'step',
1412
+ data: {
1413
+ label: `${getOriginalStepName(stepInfo.stepId, valueName)} (tool)`,
1414
+ nodeKind: 'step',
1415
+ stepId: stepInfo.stepId,
1416
+ },
1417
+ metadata: {
1418
+ isStepReference: true,
1419
+ referenceContext: currentPath,
1420
+ },
1421
+ };
1422
+ nodes.push(node);
1423
+ entryNodeIds.push(nodeId);
1424
+ exitNodeIds.push(nodeId);
1425
+ }
1426
+ }
1427
+ if (prop.value.type === 'ObjectExpression') {
1428
+ const nestedResult = analyzeObjectForStepReferences(prop.value, stepDeclarations, context, currentPath);
1429
+ nodes.push(...nestedResult.nodes);
1430
+ edges.push(...nestedResult.edges);
1431
+ entryNodeIds.push(...nestedResult.entryNodeIds);
1432
+ exitNodeIds.push(...nestedResult.exitNodeIds);
1433
+ }
1434
+ }
1435
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1436
+ }
1437
+ /**
1438
+ * Analyze a transitive function call to find step calls within helper functions
1439
+ */
1440
+ function analyzeTransitiveCall(funcName, stepDeclarations, context, functionMap, variableMap, visitedFunctions) {
1441
+ const nodes = [];
1442
+ const edges = [];
1443
+ const entryNodeIds = [];
1444
+ const exitNodeIds = [];
1445
+ if (visitedFunctions.has(funcName)) {
1446
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1447
+ }
1448
+ const funcInfo = functionMap.get(funcName);
1449
+ if (!funcInfo || funcInfo.isStep) {
1450
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1451
+ }
1452
+ visitedFunctions.add(funcName);
1453
+ try {
1454
+ if (funcInfo.body) {
1455
+ if (funcInfo.body.type === 'BlockStatement') {
1456
+ const bodyResult = analyzeBlock(funcInfo.body.stmts, stepDeclarations, context, functionMap, variableMap);
1457
+ nodes.push(...bodyResult.nodes);
1458
+ edges.push(...bodyResult.edges);
1459
+ entryNodeIds.push(...bodyResult.entryNodeIds);
1460
+ exitNodeIds.push(...bodyResult.exitNodeIds);
1461
+ }
1462
+ else {
1463
+ const exprResult = analyzeExpression(funcInfo.body, stepDeclarations, context, functionMap, variableMap, visitedFunctions);
1464
+ nodes.push(...exprResult.nodes);
1465
+ edges.push(...exprResult.edges);
1466
+ entryNodeIds.push(...exprResult.entryNodeIds);
1467
+ exitNodeIds.push(...exprResult.exitNodeIds);
1468
+ }
1469
+ }
1470
+ }
1471
+ finally {
1472
+ visitedFunctions.delete(funcName);
1473
+ }
1474
+ return { nodes, edges, entryNodeIds, exitNodeIds };
1475
+ }
1476
+ //# sourceMappingURL=workflows-extractor.js.map