@pikku/inspector 0.12.9 → 0.12.11

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.
@@ -2,6 +2,7 @@ import * as ts from 'typescript'
2
2
  import type {
3
3
  WorkflowStepMeta,
4
4
  RpcStepMeta,
5
+ InlineStepMeta,
5
6
  BranchStepMeta,
6
7
  ParallelGroupStepMeta,
7
8
  FanoutStepMeta,
@@ -32,6 +33,7 @@ import {
32
33
  extractForOfVariable,
33
34
  isArrayType,
34
35
  getSourceText,
36
+ extractSourcePath,
35
37
  } from './patterns.js'
36
38
  import type { ValidationError } from './validation.js'
37
39
  import {
@@ -63,25 +65,7 @@ interface ExtractionContext {
63
65
  }
64
66
 
65
67
  /**
66
- * Extract full source path from an expression (e.g., data.memberEmails)
67
- */
68
- function extractSourcePath(expr: ts.Expression): string | null {
69
- if (ts.isIdentifier(expr)) {
70
- return expr.text
71
- }
72
-
73
- if (ts.isPropertyAccessExpression(expr)) {
74
- const base = extractSourcePath(expr.expression)
75
- if (base) {
76
- return `${base}.${expr.name.text}`
77
- }
78
- }
79
-
80
- return null
81
- }
82
-
83
- /**
84
- * Result of simple workflow extraction
68
+ * Result of DSL workflow extraction
85
69
  */
86
70
  export interface ExtractionResult {
87
71
  status: 'ok' | 'error'
@@ -89,15 +73,15 @@ export interface ExtractionResult {
89
73
  /** Workflow context (top-level variables) */
90
74
  context?: WorkflowContext
91
75
  reason?: string
92
- simple?: boolean
93
76
  }
94
77
 
95
78
  /**
96
- * Extract simple workflow metadata from a function declaration
79
+ * Extract DSL workflow metadata from a function declaration
97
80
  */
98
81
  export function extractDSLWorkflow(
99
82
  funcNode: ts.Node,
100
- checker: ts.TypeChecker
83
+ checker: ts.TypeChecker,
84
+ options?: { allowInline?: boolean }
101
85
  ): ExtractionResult {
102
86
  try {
103
87
  // Find the async arrow function
@@ -106,7 +90,6 @@ export function extractDSLWorkflow(
106
90
  return {
107
91
  status: 'error',
108
92
  reason: 'Could not find async arrow function in workflow definition',
109
- simple: false,
110
93
  }
111
94
  }
112
95
 
@@ -116,7 +99,6 @@ export function extractDSLWorkflow(
116
99
  return {
117
100
  status: 'error',
118
101
  reason: 'Could not determine input parameter name',
119
- simple: false,
120
102
  }
121
103
  }
122
104
 
@@ -134,12 +116,13 @@ export function extractDSLWorkflow(
134
116
  }
135
117
 
136
118
  // Validate no disallowed patterns
137
- const patternErrors = validateNoDisallowedPatterns(arrowFunc.body)
119
+ const patternErrors = validateNoDisallowedPatterns(arrowFunc.body, {
120
+ allowInline: options?.allowInline,
121
+ })
138
122
  if (patternErrors.length > 0) {
139
123
  return {
140
124
  status: 'error',
141
125
  reason: formatValidationErrors(patternErrors),
142
- simple: false,
143
126
  }
144
127
  }
145
128
 
@@ -149,7 +132,6 @@ export function extractDSLWorkflow(
149
132
  return {
150
133
  status: 'error',
151
134
  reason: formatValidationErrors(awaitErrors),
152
- simple: false,
153
135
  }
154
136
  }
155
137
 
@@ -161,7 +143,6 @@ export function extractDSLWorkflow(
161
143
  return {
162
144
  status: 'error',
163
145
  reason: formatValidationErrors(context.errors),
164
- simple: false,
165
146
  }
166
147
  }
167
148
 
@@ -179,13 +160,11 @@ export function extractDSLWorkflow(
179
160
  steps,
180
161
  context:
181
162
  Object.keys(workflowContext).length > 0 ? workflowContext : undefined,
182
- simple: true,
183
163
  }
184
164
  } catch (error) {
185
165
  return {
186
166
  status: 'error',
187
167
  reason: error instanceof Error ? error.message : String(error),
188
- simple: false,
189
168
  }
190
169
  }
191
170
  }
@@ -375,7 +354,9 @@ function extractVariableDeclaration(
375
354
  if (ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)) {
376
355
  const call = init.expression
377
356
  if (isWorkflowDoCall(call, context.checker)) {
378
- const step = extractRpcStep(call, context, varName)
357
+ const step = isInlineDoCall(call)
358
+ ? extractInlineStep(call, context)
359
+ : extractRpcStep(call, context, varName)
379
360
  if (step) {
380
361
  // Track output variable
381
362
  const type = context.checker.getTypeAtLocation(decl)
@@ -481,7 +462,9 @@ function extractExpressionStatement(
481
462
  const call = expr.expression
482
463
 
483
464
  if (isWorkflowDoCall(call, context.checker)) {
484
- const step = extractRpcStep(call, context, outputVar)
465
+ const step = isInlineDoCall(call)
466
+ ? extractInlineStep(call, context)
467
+ : extractRpcStep(call, context, outputVar)
485
468
 
486
469
  // Track output variable if this is an assignment
487
470
  if (outputVar && step) {
@@ -559,6 +542,49 @@ function extractRpcStep(
559
542
  }
560
543
  }
561
544
 
545
+ /**
546
+ * Extract inline step from workflow.do() call with a function argument
547
+ */
548
+ function extractInlineStep(
549
+ call: ts.CallExpression,
550
+ context: ExtractionContext
551
+ ): InlineStepMeta | null {
552
+ const args = call.arguments
553
+ if (args.length < 2) return null
554
+
555
+ try {
556
+ const stepName = extractStringLiteral(args[0], context.checker)
557
+ const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined
558
+ const options =
559
+ optionsArg && ts.isObjectLiteralExpression(optionsArg)
560
+ ? extractStepOptions(optionsArg, context)
561
+ : undefined
562
+
563
+ return {
564
+ type: 'inline',
565
+ stepName,
566
+ options,
567
+ }
568
+ } catch (error) {
569
+ context.errors.push({
570
+ message: `Failed to extract inline step: ${error instanceof Error ? error.message : String(error)}`,
571
+ node: call,
572
+ })
573
+ return null
574
+ }
575
+ }
576
+
577
+ /**
578
+ * Check if the second argument of a workflow.do() call is a function
579
+ */
580
+ function isInlineDoCall(call: ts.CallExpression): boolean {
581
+ const secondArg = call.arguments[1]
582
+ return (
583
+ !!secondArg &&
584
+ (ts.isArrowFunction(secondArg) || ts.isFunctionExpression(secondArg))
585
+ )
586
+ }
587
+
562
588
  /**
563
589
  * Extract step options from options object
564
590
  */
@@ -1,7 +1,7 @@
1
1
  import * as ts from 'typescript'
2
2
 
3
3
  /**
4
- * Pattern detection helpers for simple workflow extraction
4
+ * Pattern detection helpers for DSL workflow extraction
5
5
  */
6
6
 
7
7
  /**
@@ -209,7 +209,7 @@ export function isSequentialFanout(node: ts.ForOfStatement): boolean {
209
209
  /**
210
210
  * Extract full source path from an expression (e.g., data.memberEmails)
211
211
  */
212
- function extractSourcePath(expr: ts.Expression): string | null {
212
+ export function extractSourcePath(expr: ts.Expression): string | null {
213
213
  if (ts.isIdentifier(expr)) {
214
214
  return expr.text
215
215
  }
@@ -1,7 +1,7 @@
1
1
  import * as ts from 'typescript'
2
2
 
3
3
  /**
4
- * Validation rules for simple workflows
4
+ * Validation rules for DSL workflows
5
5
  */
6
6
 
7
7
  export interface ValidationError {
@@ -22,7 +22,10 @@ export interface ValidationError {
22
22
  * - ThrowStatement (for WorkflowCancelledException)
23
23
  * - Block (containers)
24
24
  */
25
- export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
25
+ export function validateNoDisallowedPatterns(
26
+ node: ts.Node,
27
+ options?: { allowInline?: boolean }
28
+ ): ValidationError[] {
26
29
  const errors: ValidationError[] = []
27
30
 
28
31
  function visitBlock(block: ts.Block) {
@@ -42,7 +45,7 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
42
45
  // Unknown/disallowed statement type
43
46
  const nodeType = ts.SyntaxKind[statement.kind]
44
47
  errors.push({
45
- message: `Statement type '${nodeType}' is not allowed in simple workflows. Allowed: const/let, if/else, switch/case, for..of, return, throw, and workflow calls. If this should be supported, please report the node type: ${nodeType}`,
48
+ message: `Statement type '${nodeType}' is not allowed in DSL workflows. Allowed: const/let, if/else, switch/case, for..of, return, throw, and workflow calls. If this should be supported, please report the node type: ${nodeType}`,
46
49
  node: statement,
47
50
  })
48
51
  }
@@ -53,7 +56,7 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
53
56
  // Disallow while and do-while
54
57
  if (ts.isWhileStatement(node) || ts.isDoStatement(node)) {
55
58
  errors.push({
56
- message: 'while and do-while loops are not allowed in simple workflows',
59
+ message: 'while and do-while loops are not allowed in DSL workflows',
57
60
  node,
58
61
  })
59
62
  return
@@ -63,14 +66,14 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
63
66
  if (ts.isForInStatement(node) || ts.isForStatement(node)) {
64
67
  errors.push({
65
68
  message:
66
- 'for and for-in loops are not allowed in simple workflows. Use for-of instead.',
69
+ 'for and for-in loops are not allowed in DSL workflows. Use for-of instead.',
67
70
  node,
68
71
  })
69
72
  return
70
73
  }
71
74
 
72
75
  // Check for inline workflow.do
73
- if (ts.isCallExpression(node)) {
76
+ if (!options?.allowInline && ts.isCallExpression(node)) {
74
77
  if (ts.isPropertyAccessExpression(node.expression)) {
75
78
  const propAccess = node.expression
76
79
  if (
@@ -86,7 +89,7 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
86
89
  ) {
87
90
  errors.push({
88
91
  message:
89
- 'Inline workflow.do with function argument is not allowed in simple workflows. Use RPC form instead.',
92
+ 'Inline workflow.do with function argument is not allowed in DSL workflows. Use pikkuWorkflowComplexFunc instead.',
90
93
  node,
91
94
  })
92
95
  return
@@ -129,11 +132,20 @@ export function validateAwaitedCalls(node: ts.Node): ValidationError[] {
129
132
  ts.isIdentifier(propAccess.expression) &&
130
133
  propAccess.expression.text === 'Promise'
131
134
  ) {
132
- // console.log('[DEBUG] Found Promise.all, setting insidePromiseAll=true')
133
- // Visit children with insidePromiseAll = true
134
135
  ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
135
136
  return
136
137
  }
138
+ // .push() on an array — workflow.do() inside is collecting promises
139
+ if (propAccess.name.text === 'push') {
140
+ ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
141
+ return
142
+ }
143
+ }
144
+
145
+ // Array literal — workflow.do() inside is collecting promises
146
+ if (ts.isArrayLiteralExpression(node)) {
147
+ ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
148
+ return
137
149
  }
138
150
 
139
151
  // Now check for workflow calls