@pikku/inspector 0.11.1 → 0.12.0
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.
- package/CHANGELOG.md +26 -1
- package/OPTIMIZATION-PLAN.md +195 -0
- package/dist/add/add-ai-agent.d.ts +2 -0
- package/dist/add/add-ai-agent.js +314 -0
- package/dist/add/add-channel.js +69 -61
- package/dist/add/add-cli.js +36 -18
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.js +327 -59
- package/dist/add/add-http-route.d.ts +19 -10
- package/dist/add/add-http-route.js +153 -44
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +159 -0
- package/dist/add/add-keyed-wiring.d.ts +12 -0
- package/dist/add/add-keyed-wiring.js +97 -0
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.js +14 -9
- package/dist/add/add-middleware.d.ts +1 -4
- package/dist/add/add-middleware.js +364 -79
- package/dist/add/add-permission.d.ts +1 -1
- package/dist/add/add-permission.js +152 -40
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.d.ts +3 -0
- package/dist/add/add-rpc-invocations.js +65 -25
- package/dist/add/add-schedule.js +11 -5
- package/dist/add/add-secret.d.ts +3 -0
- package/dist/add/add-secret.js +82 -0
- package/dist/add/add-trigger.d.ts +2 -0
- package/dist/add/add-trigger.js +87 -0
- package/dist/add/add-variable.d.ts +1 -0
- package/dist/add/add-variable.js +8 -0
- package/dist/add/add-workflow-graph.d.ts +7 -0
- package/dist/add/add-workflow-graph.js +396 -0
- package/dist/add/add-workflow.js +124 -26
- package/dist/error-codes.d.ts +16 -1
- package/dist/error-codes.js +21 -1
- package/dist/index.d.ts +9 -5
- package/dist/index.js +5 -2
- package/dist/inspector.d.ts +1 -1
- package/dist/inspector.js +106 -13
- package/dist/schema-generator.d.ts +1 -0
- package/dist/schema-generator.js +1 -0
- package/dist/types-map.js +10 -1
- package/dist/types.d.ts +180 -30
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +41 -0
- package/dist/utils/contract-hashes.d.ts +35 -0
- package/dist/utils/contract-hashes.js +202 -0
- package/dist/utils/custom-types-generator.d.ts +9 -0
- package/dist/utils/custom-types-generator.js +71 -0
- package/dist/utils/detect-schema-vendor.d.ts +22 -0
- package/dist/utils/detect-schema-vendor.js +76 -0
- package/dist/utils/ensure-function-metadata.d.ts +5 -2
- package/dist/utils/ensure-function-metadata.js +220 -6
- package/dist/utils/extract-function-name.d.ts +5 -16
- package/dist/utils/extract-function-name.js +93 -298
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/get-property-value.d.ts +8 -2
- package/dist/utils/get-property-value.js +33 -4
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +7 -30
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +2 -2
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +9 -10
- package/dist/utils/post-process.js +231 -24
- package/dist/utils/resolve-external-package.d.ts +12 -0
- package/dist/utils/resolve-external-package.js +34 -0
- package/dist/utils/resolve-function-types.d.ts +6 -0
- package/dist/utils/resolve-function-types.js +29 -0
- package/dist/utils/resolve-identifier.d.ts +10 -0
- package/dist/utils/resolve-identifier.js +36 -0
- package/dist/utils/resolve-versions.d.ts +2 -0
- package/dist/utils/resolve-versions.js +78 -0
- package/dist/utils/schema-generator.d.ts +9 -0
- package/dist/utils/schema-generator.js +209 -0
- package/dist/utils/serialize-inspector-state.d.ts +73 -13
- package/dist/utils/serialize-inspector-state.js +102 -6
- package/dist/utils/serialize-mcp-json.d.ts +2 -0
- package/dist/utils/serialize-mcp-json.js +99 -0
- package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
- package/dist/utils/serialize-middleware-groups-meta.js +28 -0
- package/dist/utils/serialize-openapi-json.d.ts +85 -0
- package/dist/utils/serialize-openapi-json.js +151 -0
- package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
- package/dist/utils/serialize-permissions-groups-meta.js +31 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
- package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
- package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
- package/dist/utils/workflow/dsl/index.d.ts +7 -0
- package/dist/utils/workflow/dsl/index.js +7 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
- package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
- package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
- package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
- package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
- package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
- package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
- package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
- package/dist/utils/workflow/graph/index.d.ts +8 -0
- package/dist/utils/workflow/graph/index.js +8 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
- package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
- package/dist/visit.js +13 -2
- package/package.json +26 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +82 -79
- package/src/add/add-cli.ts +49 -20
- package/src/add/add-file-with-factory.ts +2 -0
- package/src/add/add-functions.ts +429 -71
- package/src/add/add-http-route.ts +246 -65
- package/src/add/add-http-routes.ts +228 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +26 -15
- package/src/add/add-mcp-resource.ts +27 -15
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +24 -19
- package/src/add/add-rpc-invocations.ts +78 -31
- package/src/add/add-schedule.ts +16 -11
- package/src/add/add-secret.ts +140 -0
- package/src/add/add-trigger.ts +154 -0
- package/src/add/add-variable.ts +9 -0
- package/src/add/add-workflow-graph.ts +522 -0
- package/src/add/add-workflow.ts +117 -30
- package/src/error-codes.ts +26 -1
- package/src/index.ts +27 -8
- package/src/inspector.ts +145 -17
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +192 -51
- package/src/utils/compute-required-schemas.ts +49 -0
- package/src/utils/contract-hashes.test.ts +528 -0
- package/src/utils/contract-hashes.ts +290 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/ensure-function-metadata.ts +324 -7
- package/src/utils/extract-function-name.ts +108 -358
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +34 -20
- package/src/utils/filter-inspector-state.ts +140 -31
- package/src/utils/get-property-value.ts +50 -5
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +129 -67
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +10 -10
- package/src/utils/post-process.ts +283 -43
- package/src/utils/resolve-external-package.ts +42 -0
- package/src/utils/resolve-function-types.ts +42 -0
- package/src/utils/resolve-identifier.ts +46 -0
- package/src/utils/resolve-versions.test.ts +249 -0
- package/src/utils/resolve-versions.ts +105 -0
- package/src/utils/schema-generator.ts +329 -0
- package/src/utils/serialize-inspector-state.ts +181 -20
- package/src/utils/serialize-mcp-json.ts +145 -0
- package/src/utils/serialize-middleware-groups-meta.ts +33 -0
- package/src/utils/serialize-openapi-json.ts +277 -0
- package/src/utils/serialize-permissions-groups-meta.ts +35 -0
- package/src/utils/test-data/inspector-state.json +69 -66
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
- package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
- package/src/utils/workflow/dsl/index.ts +11 -0
- package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
- package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
- package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
- package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
- package/src/utils/workflow/graph/index.ts +11 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
- package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
- package/src/visit.ts +14 -2
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-mcp-tool.d.ts +0 -2
- package/dist/add/add-mcp-tool.js +0 -81
- package/dist/utils/extract-service-metadata.d.ts +0 -19
- package/dist/utils/extract-service-metadata.js +0 -244
- package/dist/utils/write-service-metadata.d.ts +0 -13
- package/dist/utils/write-service-metadata.js +0 -37
- package/src/add/add-mcp-tool.ts +0 -141
- package/src/utils/extract-service-metadata.ts +0 -353
- package/src/utils/write-service-metadata.ts +0 -51
package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts}
RENAMED
|
@@ -6,19 +6,29 @@ import {
|
|
|
6
6
|
ParallelGroupStepMeta,
|
|
7
7
|
FanoutStepMeta,
|
|
8
8
|
ReturnStepMeta,
|
|
9
|
+
CancelStepMeta,
|
|
10
|
+
SetStepMeta,
|
|
11
|
+
SwitchStepMeta,
|
|
12
|
+
SwitchCaseMeta,
|
|
13
|
+
FilterStepMeta,
|
|
14
|
+
ArrayPredicateStepMeta,
|
|
9
15
|
InputSource,
|
|
10
16
|
OutputBinding,
|
|
17
|
+
Condition,
|
|
18
|
+
WorkflowContext,
|
|
19
|
+
ContextVariable,
|
|
11
20
|
} from '@pikku/core/workflow'
|
|
12
|
-
import {
|
|
13
|
-
extractStringLiteral,
|
|
14
|
-
extractNumberLiteral,
|
|
15
|
-
} from '../utils/extract-node-value.js'
|
|
16
21
|
import {
|
|
17
22
|
isWorkflowDoCall,
|
|
18
23
|
isWorkflowSleepCall,
|
|
24
|
+
isThrowCancelException,
|
|
25
|
+
extractCancelReason,
|
|
19
26
|
isParallelFanout,
|
|
20
27
|
isParallelGroup,
|
|
21
28
|
isSequentialFanout,
|
|
29
|
+
isArrayFilter,
|
|
30
|
+
isArraySome,
|
|
31
|
+
isArrayEvery,
|
|
22
32
|
extractForOfVariable,
|
|
23
33
|
isArrayType,
|
|
24
34
|
getSourceText,
|
|
@@ -29,6 +39,10 @@ import {
|
|
|
29
39
|
formatValidationErrors,
|
|
30
40
|
ValidationError,
|
|
31
41
|
} from './validation.js'
|
|
42
|
+
import {
|
|
43
|
+
extractStringLiteral,
|
|
44
|
+
extractNumberLiteral,
|
|
45
|
+
} from '../../extract-node-value.js'
|
|
32
46
|
|
|
33
47
|
/**
|
|
34
48
|
* Extraction context to track state during AST traversal
|
|
@@ -40,6 +54,30 @@ interface ExtractionContext {
|
|
|
40
54
|
conditionalVars: Set<string>
|
|
41
55
|
inputParamName: string
|
|
42
56
|
errors: ValidationError[]
|
|
57
|
+
/** Loop variables in scope (for fanout item variables) */
|
|
58
|
+
loopVars: Set<string>
|
|
59
|
+
/** Context variables (top-level let/const with simple values) */
|
|
60
|
+
contextVars: Map<string, { type: string; default: unknown }>
|
|
61
|
+
/** Track nesting depth to detect block-scoped vars */
|
|
62
|
+
depth: number
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
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
|
|
43
81
|
}
|
|
44
82
|
|
|
45
83
|
/**
|
|
@@ -48,6 +86,8 @@ interface ExtractionContext {
|
|
|
48
86
|
export interface ExtractionResult {
|
|
49
87
|
status: 'ok' | 'error'
|
|
50
88
|
steps?: WorkflowStepMeta[]
|
|
89
|
+
/** Workflow context (top-level variables) */
|
|
90
|
+
context?: WorkflowContext
|
|
51
91
|
reason?: string
|
|
52
92
|
simple?: boolean
|
|
53
93
|
}
|
|
@@ -55,7 +95,7 @@ export interface ExtractionResult {
|
|
|
55
95
|
/**
|
|
56
96
|
* Extract simple workflow metadata from a function declaration
|
|
57
97
|
*/
|
|
58
|
-
export function
|
|
98
|
+
export function extractDSLWorkflow(
|
|
59
99
|
funcNode: ts.Node,
|
|
60
100
|
checker: ts.TypeChecker
|
|
61
101
|
): ExtractionResult {
|
|
@@ -88,6 +128,9 @@ export function extractSimpleWorkflow(
|
|
|
88
128
|
conditionalVars: new Set(),
|
|
89
129
|
inputParamName,
|
|
90
130
|
errors: [],
|
|
131
|
+
loopVars: new Set(),
|
|
132
|
+
contextVars: new Map(),
|
|
133
|
+
depth: 0,
|
|
91
134
|
}
|
|
92
135
|
|
|
93
136
|
// Validate no disallowed patterns
|
|
@@ -122,9 +165,20 @@ export function extractSimpleWorkflow(
|
|
|
122
165
|
}
|
|
123
166
|
}
|
|
124
167
|
|
|
168
|
+
// Build workflow context from extracted context variables
|
|
169
|
+
const workflowContext: WorkflowContext = {}
|
|
170
|
+
for (const [name, info] of context.contextVars) {
|
|
171
|
+
workflowContext[name] = {
|
|
172
|
+
type: info.type as ContextVariable['type'],
|
|
173
|
+
default: info.default,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
125
177
|
return {
|
|
126
178
|
status: 'ok',
|
|
127
179
|
steps,
|
|
180
|
+
context:
|
|
181
|
+
Object.keys(workflowContext).length > 0 ? workflowContext : undefined,
|
|
128
182
|
simple: true,
|
|
129
183
|
}
|
|
130
184
|
} catch (error) {
|
|
@@ -140,7 +194,7 @@ export function extractSimpleWorkflow(
|
|
|
140
194
|
* Find the workflow function (async arrow function)
|
|
141
195
|
*/
|
|
142
196
|
function findWorkflowFunction(node: ts.Node): ts.ArrowFunction | null {
|
|
143
|
-
// Handle
|
|
197
|
+
// Handle pikkuWorkflowFunc(async () => {}) or pikkuWorkflowComplexFunc(async () => {})
|
|
144
198
|
if (ts.isCallExpression(node)) {
|
|
145
199
|
const arg = node.arguments[0]
|
|
146
200
|
if (arg && ts.isArrowFunction(arg)) {
|
|
@@ -162,7 +216,7 @@ function findWorkflowFunction(node: ts.Node): ts.ArrowFunction | null {
|
|
|
162
216
|
}
|
|
163
217
|
}
|
|
164
218
|
|
|
165
|
-
// Handle
|
|
219
|
+
// Handle pikkuWorkflowFunc({ func: async () => {} })
|
|
166
220
|
if (ts.isObjectLiteralExpression(node)) {
|
|
167
221
|
for (const prop of node.properties) {
|
|
168
222
|
if (
|
|
@@ -201,7 +255,8 @@ function extractInputParamName(arrowFunc: ts.ArrowFunction): string | null {
|
|
|
201
255
|
*/
|
|
202
256
|
function extractSteps(
|
|
203
257
|
body: ts.Node,
|
|
204
|
-
context: ExtractionContext
|
|
258
|
+
context: ExtractionContext,
|
|
259
|
+
incrementDepth = false
|
|
205
260
|
): WorkflowStepMeta[] {
|
|
206
261
|
const steps: WorkflowStepMeta[] = []
|
|
207
262
|
|
|
@@ -209,6 +264,11 @@ function extractSteps(
|
|
|
209
264
|
return steps
|
|
210
265
|
}
|
|
211
266
|
|
|
267
|
+
// Increment depth when entering a nested block
|
|
268
|
+
if (incrementDepth) {
|
|
269
|
+
context.depth++
|
|
270
|
+
}
|
|
271
|
+
|
|
212
272
|
for (const statement of body.statements) {
|
|
213
273
|
const extracted = extractStep(statement, context)
|
|
214
274
|
if (extracted) {
|
|
@@ -216,6 +276,11 @@ function extractSteps(
|
|
|
216
276
|
}
|
|
217
277
|
}
|
|
218
278
|
|
|
279
|
+
// Restore depth
|
|
280
|
+
if (incrementDepth) {
|
|
281
|
+
context.depth--
|
|
282
|
+
}
|
|
283
|
+
|
|
219
284
|
return steps
|
|
220
285
|
}
|
|
221
286
|
|
|
@@ -241,6 +306,11 @@ function extractStep(
|
|
|
241
306
|
return extractBranch(statement, context)
|
|
242
307
|
}
|
|
243
308
|
|
|
309
|
+
// Switch statement
|
|
310
|
+
if (ts.isSwitchStatement(statement)) {
|
|
311
|
+
return extractSwitch(statement, context)
|
|
312
|
+
}
|
|
313
|
+
|
|
244
314
|
// For-of statement (sequential fanout)
|
|
245
315
|
if (ts.isForOfStatement(statement)) {
|
|
246
316
|
return extractSequentialFanout(statement, context)
|
|
@@ -251,6 +321,11 @@ function extractStep(
|
|
|
251
321
|
return extractReturn(statement, context)
|
|
252
322
|
}
|
|
253
323
|
|
|
324
|
+
// Throw statement (for WorkflowCancelledException)
|
|
325
|
+
if (ts.isThrowStatement(statement)) {
|
|
326
|
+
return extractThrowCancel(statement, context)
|
|
327
|
+
}
|
|
328
|
+
|
|
254
329
|
return null
|
|
255
330
|
}
|
|
256
331
|
|
|
@@ -274,10 +349,28 @@ function extractVariableDeclaration(
|
|
|
274
349
|
const varName = decl.name.text
|
|
275
350
|
const init = decl.initializer
|
|
276
351
|
|
|
352
|
+
// Check for block-scoped variable declarations (not allowed)
|
|
353
|
+
if (context.depth > 0) {
|
|
354
|
+
context.errors.push({
|
|
355
|
+
message: `Variable declaration '${varName}' inside block is not supported in DSL workflows. Move all let/const declarations to the top level.`,
|
|
356
|
+
node: statement,
|
|
357
|
+
})
|
|
358
|
+
return null
|
|
359
|
+
}
|
|
360
|
+
|
|
277
361
|
if (!init) {
|
|
278
362
|
return null
|
|
279
363
|
}
|
|
280
364
|
|
|
365
|
+
// Check for simple literal/expression context variable (let x = 'value')
|
|
366
|
+
const literalValue = extractLiteralValue(init)
|
|
367
|
+
if (literalValue !== undefined) {
|
|
368
|
+
const tsType = context.checker.getTypeAtLocation(decl)
|
|
369
|
+
const typeStr = inferSimpleType(tsType, context.checker)
|
|
370
|
+
context.contextVars.set(varName, { type: typeStr, default: literalValue })
|
|
371
|
+
return null // No step emitted, just register the context var
|
|
372
|
+
}
|
|
373
|
+
|
|
281
374
|
// Check for await workflow.do(...)
|
|
282
375
|
if (ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)) {
|
|
283
376
|
const call = init.expression
|
|
@@ -315,6 +408,30 @@ function extractVariableDeclaration(
|
|
|
315
408
|
}
|
|
316
409
|
}
|
|
317
410
|
|
|
411
|
+
// Check for array.filter(...)
|
|
412
|
+
if (ts.isCallExpression(init)) {
|
|
413
|
+
if (isArrayFilter(init)) {
|
|
414
|
+
const filterStep = extractArrayFilter(init, context, varName)
|
|
415
|
+
if (filterStep) {
|
|
416
|
+
const type = context.checker.getTypeAtLocation(decl)
|
|
417
|
+
context.outputVars.set(varName, { type, node: decl })
|
|
418
|
+
if (isArrayType(type, context.checker)) {
|
|
419
|
+
context.arrayVars.add(varName)
|
|
420
|
+
}
|
|
421
|
+
return filterStep
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (isArraySome(init) || isArrayEvery(init)) {
|
|
426
|
+
const predicateStep = extractArrayPredicate(init, context, varName)
|
|
427
|
+
if (predicateStep) {
|
|
428
|
+
const type = context.checker.getTypeAtLocation(decl)
|
|
429
|
+
context.outputVars.set(varName, { type, node: decl })
|
|
430
|
+
return predicateStep
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
318
435
|
return null
|
|
319
436
|
}
|
|
320
437
|
|
|
@@ -327,7 +444,7 @@ function extractExpressionStatement(
|
|
|
327
444
|
): WorkflowStepMeta | null {
|
|
328
445
|
let expr = statement.expression
|
|
329
446
|
|
|
330
|
-
// Handle assignment:
|
|
447
|
+
// Handle assignment: x = value or x = await workflow.do(...)
|
|
331
448
|
let outputVar: string | undefined
|
|
332
449
|
if (
|
|
333
450
|
ts.isBinaryExpression(expr) &&
|
|
@@ -336,6 +453,24 @@ function extractExpressionStatement(
|
|
|
336
453
|
// Extract variable name from left side
|
|
337
454
|
if (ts.isIdentifier(expr.left)) {
|
|
338
455
|
outputVar = expr.left.text
|
|
456
|
+
|
|
457
|
+
// Check if this is an assignment to a context variable (set step)
|
|
458
|
+
if (context.contextVars.has(outputVar)) {
|
|
459
|
+
const literalValue = extractLiteralValue(expr.right)
|
|
460
|
+
if (literalValue !== undefined) {
|
|
461
|
+
return {
|
|
462
|
+
type: 'set',
|
|
463
|
+
variable: outputVar,
|
|
464
|
+
value: literalValue,
|
|
465
|
+
} as SetStepMeta
|
|
466
|
+
}
|
|
467
|
+
// Non-literal assignment to context var - use expression as string
|
|
468
|
+
return {
|
|
469
|
+
type: 'set',
|
|
470
|
+
variable: outputVar,
|
|
471
|
+
value: getSourceText(expr.right),
|
|
472
|
+
} as SetStepMeta
|
|
473
|
+
}
|
|
339
474
|
}
|
|
340
475
|
// Use right side as the expression to extract from
|
|
341
476
|
expr = expr.right
|
|
@@ -510,32 +645,102 @@ function extractSleepStep(
|
|
|
510
645
|
}
|
|
511
646
|
|
|
512
647
|
/**
|
|
513
|
-
* Extract
|
|
648
|
+
* Extract cancel step from throw WorkflowCancelledException statement
|
|
649
|
+
*/
|
|
650
|
+
function extractThrowCancel(
|
|
651
|
+
statement: ts.ThrowStatement,
|
|
652
|
+
context: ExtractionContext
|
|
653
|
+
): CancelStepMeta | null {
|
|
654
|
+
if (!isThrowCancelException(statement)) {
|
|
655
|
+
return null
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const reason = extractCancelReason(statement, context.checker)
|
|
659
|
+
return {
|
|
660
|
+
type: 'cancel',
|
|
661
|
+
reason,
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Parse a condition expression into a Condition structure
|
|
667
|
+
*/
|
|
668
|
+
function parseCondition(expr: ts.Expression): Condition {
|
|
669
|
+
// Handle binary expressions (&&, ||)
|
|
670
|
+
if (ts.isBinaryExpression(expr)) {
|
|
671
|
+
const operator = expr.operatorToken.kind
|
|
672
|
+
|
|
673
|
+
// AND operator (&&)
|
|
674
|
+
if (operator === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
675
|
+
return {
|
|
676
|
+
type: 'and',
|
|
677
|
+
conditions: [parseCondition(expr.left), parseCondition(expr.right)],
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// OR operator (||)
|
|
682
|
+
if (operator === ts.SyntaxKind.BarBarToken) {
|
|
683
|
+
return {
|
|
684
|
+
type: 'or',
|
|
685
|
+
conditions: [parseCondition(expr.left), parseCondition(expr.right)],
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// Handle parenthesized expressions - unwrap and parse inner
|
|
691
|
+
if (ts.isParenthesizedExpression(expr)) {
|
|
692
|
+
return parseCondition(expr.expression)
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
// Simple condition (comparison, function call, variable, etc.)
|
|
696
|
+
return {
|
|
697
|
+
type: 'simple',
|
|
698
|
+
expression: getSourceText(expr),
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
/**
|
|
703
|
+
* Extract branch step from if statement (supports if/else-if/else chains)
|
|
514
704
|
*/
|
|
515
705
|
function extractBranch(
|
|
516
706
|
statement: ts.IfStatement,
|
|
517
707
|
context: ExtractionContext
|
|
518
708
|
): BranchStepMeta | null {
|
|
519
|
-
const
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
709
|
+
const branches: BranchStepMeta['branches'] = []
|
|
710
|
+
let elseSteps: BranchStepMeta['elseSteps']
|
|
711
|
+
|
|
712
|
+
// Walk the if/else-if chain
|
|
713
|
+
let current: ts.IfStatement | undefined = statement
|
|
714
|
+
while (current) {
|
|
715
|
+
const condition = parseCondition(current.expression)
|
|
716
|
+
const steps = ts.isBlock(current.thenStatement)
|
|
717
|
+
? extractSteps(current.thenStatement, context, true)
|
|
718
|
+
: extractStepsFromStatement(current.thenStatement, context)
|
|
719
|
+
|
|
720
|
+
branches.push({ condition, steps })
|
|
721
|
+
|
|
722
|
+
// Check for else-if or else
|
|
723
|
+
if (current.elseStatement) {
|
|
724
|
+
if (ts.isIfStatement(current.elseStatement)) {
|
|
725
|
+
// else-if: continue the chain
|
|
726
|
+
current = current.elseStatement
|
|
727
|
+
} else {
|
|
728
|
+
// else: extract the final else block and stop
|
|
729
|
+
elseSteps = ts.isBlock(current.elseStatement)
|
|
730
|
+
? extractSteps(current.elseStatement, context, true)
|
|
731
|
+
: extractStepsFromStatement(current.elseStatement, context)
|
|
732
|
+
current = undefined
|
|
733
|
+
}
|
|
734
|
+
} else {
|
|
735
|
+
// No else clause
|
|
736
|
+
current = undefined
|
|
737
|
+
}
|
|
738
|
+
}
|
|
531
739
|
|
|
532
740
|
return {
|
|
533
741
|
type: 'branch',
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
then: thenSteps,
|
|
537
|
-
else: elseSteps,
|
|
538
|
-
},
|
|
742
|
+
branches,
|
|
743
|
+
elseSteps,
|
|
539
744
|
}
|
|
540
745
|
}
|
|
541
746
|
|
|
@@ -546,10 +751,198 @@ function extractStepsFromStatement(
|
|
|
546
751
|
statement: ts.Statement,
|
|
547
752
|
context: ExtractionContext
|
|
548
753
|
): WorkflowStepMeta[] {
|
|
754
|
+
// Increment depth for single-statement blocks (if without braces)
|
|
755
|
+
context.depth++
|
|
549
756
|
const step = extractStep(statement, context)
|
|
757
|
+
context.depth--
|
|
550
758
|
return step ? [step] : []
|
|
551
759
|
}
|
|
552
760
|
|
|
761
|
+
/**
|
|
762
|
+
* Extract switch statement
|
|
763
|
+
*/
|
|
764
|
+
function extractSwitch(
|
|
765
|
+
statement: ts.SwitchStatement,
|
|
766
|
+
context: ExtractionContext
|
|
767
|
+
): SwitchStepMeta | null {
|
|
768
|
+
const expression = getSourceText(statement.expression)
|
|
769
|
+
const cases: SwitchCaseMeta[] = []
|
|
770
|
+
let defaultSteps: WorkflowStepMeta[] | undefined
|
|
771
|
+
|
|
772
|
+
for (const clause of statement.caseBlock.clauses) {
|
|
773
|
+
if (ts.isCaseClause(clause)) {
|
|
774
|
+
const caseValue = extractCaseValue(clause.expression)
|
|
775
|
+
const steps = extractCaseSteps(clause.statements, context)
|
|
776
|
+
|
|
777
|
+
cases.push({
|
|
778
|
+
value: caseValue.value,
|
|
779
|
+
expression: caseValue.expression,
|
|
780
|
+
steps,
|
|
781
|
+
})
|
|
782
|
+
} else if (ts.isDefaultClause(clause)) {
|
|
783
|
+
defaultSteps = extractCaseSteps(clause.statements, context)
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
return {
|
|
788
|
+
type: 'switch',
|
|
789
|
+
expression,
|
|
790
|
+
cases,
|
|
791
|
+
defaultSteps,
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
/**
|
|
796
|
+
* Extract case value from expression
|
|
797
|
+
*/
|
|
798
|
+
function extractCaseValue(expr: ts.Expression): {
|
|
799
|
+
value?: string | number | boolean | null
|
|
800
|
+
expression?: string
|
|
801
|
+
} {
|
|
802
|
+
if (ts.isStringLiteral(expr)) {
|
|
803
|
+
return { value: expr.text }
|
|
804
|
+
}
|
|
805
|
+
if (ts.isNumericLiteral(expr)) {
|
|
806
|
+
return { value: Number(expr.text) }
|
|
807
|
+
}
|
|
808
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
809
|
+
return { value: true }
|
|
810
|
+
}
|
|
811
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
812
|
+
return { value: false }
|
|
813
|
+
}
|
|
814
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
815
|
+
return { value: null }
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
return { expression: getSourceText(expr) }
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/**
|
|
822
|
+
* Extract steps from case statements, stopping at break
|
|
823
|
+
*/
|
|
824
|
+
function extractCaseSteps(
|
|
825
|
+
statements: ts.NodeArray<ts.Statement>,
|
|
826
|
+
context: ExtractionContext
|
|
827
|
+
): WorkflowStepMeta[] {
|
|
828
|
+
const steps: WorkflowStepMeta[] = []
|
|
829
|
+
|
|
830
|
+
// Increment depth for case blocks
|
|
831
|
+
context.depth++
|
|
832
|
+
|
|
833
|
+
for (const statement of statements) {
|
|
834
|
+
if (ts.isBreakStatement(statement)) {
|
|
835
|
+
break
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
const step = extractStep(statement, context)
|
|
839
|
+
if (step) {
|
|
840
|
+
steps.push(step)
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Restore depth
|
|
845
|
+
context.depth--
|
|
846
|
+
|
|
847
|
+
return steps
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
/**
|
|
851
|
+
* Extract array filter operation
|
|
852
|
+
*/
|
|
853
|
+
function extractArrayFilter(
|
|
854
|
+
call: ts.CallExpression,
|
|
855
|
+
context: ExtractionContext,
|
|
856
|
+
outputVar?: string
|
|
857
|
+
): FilterStepMeta | null {
|
|
858
|
+
if (!ts.isPropertyAccessExpression(call.expression)) {
|
|
859
|
+
return null
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
const sourceExpr = call.expression.expression
|
|
863
|
+
const sourceVar = extractSourcePath(sourceExpr)
|
|
864
|
+
|
|
865
|
+
if (!sourceVar) {
|
|
866
|
+
return null
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
const filterFn = call.arguments[0]
|
|
870
|
+
if (!filterFn || !ts.isArrowFunction(filterFn)) {
|
|
871
|
+
return null
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
const itemParam = filterFn.parameters[0]
|
|
875
|
+
if (!itemParam || !ts.isIdentifier(itemParam.name)) {
|
|
876
|
+
return null
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const itemVar = itemParam.name.text
|
|
880
|
+
|
|
881
|
+
let condition: Condition
|
|
882
|
+
if (ts.isBlock(filterFn.body)) {
|
|
883
|
+
return null
|
|
884
|
+
} else {
|
|
885
|
+
condition = parseCondition(filterFn.body)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return {
|
|
889
|
+
type: 'filter',
|
|
890
|
+
sourceVar,
|
|
891
|
+
itemVar,
|
|
892
|
+
condition,
|
|
893
|
+
outputVar,
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Extract array predicate operation (some/every)
|
|
899
|
+
*/
|
|
900
|
+
function extractArrayPredicate(
|
|
901
|
+
call: ts.CallExpression,
|
|
902
|
+
context: ExtractionContext,
|
|
903
|
+
outputVar?: string
|
|
904
|
+
): ArrayPredicateStepMeta | null {
|
|
905
|
+
if (!ts.isPropertyAccessExpression(call.expression)) {
|
|
906
|
+
return null
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const mode = call.expression.name.text as 'some' | 'every'
|
|
910
|
+
const sourceExpr = call.expression.expression
|
|
911
|
+
const sourceVar = extractSourcePath(sourceExpr)
|
|
912
|
+
|
|
913
|
+
if (!sourceVar) {
|
|
914
|
+
return null
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const predicateFn = call.arguments[0]
|
|
918
|
+
if (!predicateFn || !ts.isArrowFunction(predicateFn)) {
|
|
919
|
+
return null
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
const itemParam = predicateFn.parameters[0]
|
|
923
|
+
if (!itemParam || !ts.isIdentifier(itemParam.name)) {
|
|
924
|
+
return null
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
const itemVar = itemParam.name.text
|
|
928
|
+
|
|
929
|
+
let condition: Condition
|
|
930
|
+
if (ts.isBlock(predicateFn.body)) {
|
|
931
|
+
return null
|
|
932
|
+
} else {
|
|
933
|
+
condition = parseCondition(predicateFn.body)
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
return {
|
|
937
|
+
type: 'arrayPredicate',
|
|
938
|
+
mode,
|
|
939
|
+
sourceVar,
|
|
940
|
+
itemVar,
|
|
941
|
+
condition,
|
|
942
|
+
outputVar,
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
553
946
|
/**
|
|
554
947
|
* Extract parallel fanout from Promise.all(array.map(...))
|
|
555
948
|
*/
|
|
@@ -568,16 +961,7 @@ function extractParallelFanout(
|
|
|
568
961
|
|
|
569
962
|
// Extract source array
|
|
570
963
|
const sourceExpr = mapCall.expression.expression
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
if (ts.isIdentifier(sourceExpr)) {
|
|
574
|
-
sourceVar = sourceExpr.text
|
|
575
|
-
} else if (
|
|
576
|
-
ts.isPropertyAccessExpression(sourceExpr) &&
|
|
577
|
-
ts.isIdentifier(sourceExpr.expression)
|
|
578
|
-
) {
|
|
579
|
-
sourceVar = sourceExpr.expression.text
|
|
580
|
-
}
|
|
964
|
+
const sourceVar = extractSourcePath(sourceExpr)
|
|
581
965
|
|
|
582
966
|
if (!sourceVar) {
|
|
583
967
|
return null
|
|
@@ -610,7 +994,16 @@ function extractParallelFanout(
|
|
|
610
994
|
} else if (ts.isBlock(mapFn.body)) {
|
|
611
995
|
// Look for workflow.do in block
|
|
612
996
|
for (const stmt of mapFn.body.statements) {
|
|
613
|
-
if (ts.
|
|
997
|
+
if (ts.isExpressionStatement(stmt)) {
|
|
998
|
+
// Handle: await workflow.do(...)
|
|
999
|
+
if (
|
|
1000
|
+
ts.isAwaitExpression(stmt.expression) &&
|
|
1001
|
+
ts.isCallExpression(stmt.expression.expression)
|
|
1002
|
+
) {
|
|
1003
|
+
doCall = stmt.expression.expression
|
|
1004
|
+
break
|
|
1005
|
+
}
|
|
1006
|
+
} else if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
614
1007
|
if (ts.isCallExpression(stmt.expression)) {
|
|
615
1008
|
doCall = stmt.expression
|
|
616
1009
|
break
|
|
@@ -629,10 +1022,11 @@ function extractParallelFanout(
|
|
|
629
1022
|
return null
|
|
630
1023
|
}
|
|
631
1024
|
|
|
632
|
-
// Create a temporary context for the child step
|
|
1025
|
+
// Create a temporary context for the child step with the loop variable
|
|
633
1026
|
const childContext: ExtractionContext = {
|
|
634
1027
|
...context,
|
|
635
1028
|
outputVars: new Map(context.outputVars),
|
|
1029
|
+
loopVars: new Set([...context.loopVars, itemVar]),
|
|
636
1030
|
}
|
|
637
1031
|
|
|
638
1032
|
const childStep = extractRpcStep(doCall, childContext)
|
|
@@ -709,8 +1103,43 @@ function extractSequentialFanout(
|
|
|
709
1103
|
let childStep: RpcStepMeta | null = null
|
|
710
1104
|
let timeBetween: string | undefined = undefined
|
|
711
1105
|
|
|
1106
|
+
// Create a child context with the loop variable added
|
|
1107
|
+
const childContext: ExtractionContext = {
|
|
1108
|
+
...context,
|
|
1109
|
+
outputVars: new Map(context.outputVars),
|
|
1110
|
+
loopVars: new Set([...context.loopVars, itemVar]),
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
let workflowDoCount = 0
|
|
1114
|
+
|
|
712
1115
|
for (const stmt of statement.statement.statements) {
|
|
713
|
-
// Look for workflow.do
|
|
1116
|
+
// Look for workflow.do in VariableStatement (const x = await workflow.do(...))
|
|
1117
|
+
if (ts.isVariableStatement(stmt)) {
|
|
1118
|
+
const declList = stmt.declarationList
|
|
1119
|
+
if (declList.declarations.length === 1) {
|
|
1120
|
+
const decl = declList.declarations[0]
|
|
1121
|
+
const init = decl.initializer
|
|
1122
|
+
if (
|
|
1123
|
+
init &&
|
|
1124
|
+
ts.isAwaitExpression(init) &&
|
|
1125
|
+
ts.isCallExpression(init.expression)
|
|
1126
|
+
) {
|
|
1127
|
+
const call = init.expression
|
|
1128
|
+
if (isWorkflowDoCall(call, context.checker)) {
|
|
1129
|
+
workflowDoCount++
|
|
1130
|
+
const varName = ts.isIdentifier(decl.name)
|
|
1131
|
+
? decl.name.text
|
|
1132
|
+
: undefined
|
|
1133
|
+
const step = extractRpcStep(call, childContext, varName)
|
|
1134
|
+
if (step) {
|
|
1135
|
+
childStep = step
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
// Look for workflow.do in ExpressionStatement (await workflow.do(...))
|
|
714
1143
|
if (ts.isExpressionStatement(stmt)) {
|
|
715
1144
|
const expr = stmt.expression
|
|
716
1145
|
|
|
@@ -718,7 +1147,8 @@ function extractSequentialFanout(
|
|
|
718
1147
|
const call = expr.expression
|
|
719
1148
|
|
|
720
1149
|
if (isWorkflowDoCall(call, context.checker)) {
|
|
721
|
-
|
|
1150
|
+
workflowDoCount++
|
|
1151
|
+
const step = extractRpcStep(call, childContext)
|
|
722
1152
|
if (step) {
|
|
723
1153
|
childStep = step
|
|
724
1154
|
}
|
|
@@ -785,6 +1215,15 @@ function extractSequentialFanout(
|
|
|
785
1215
|
return null
|
|
786
1216
|
}
|
|
787
1217
|
|
|
1218
|
+
// If there are multiple workflow.do calls, the loop is too complex for DSL
|
|
1219
|
+
if (workflowDoCount > 1) {
|
|
1220
|
+
context.errors.push({
|
|
1221
|
+
message: `For-of loop has ${workflowDoCount} workflow.do calls but DSL only supports 1. Use pikkuWorkflowComplexFunc for complex loops.`,
|
|
1222
|
+
node: statement,
|
|
1223
|
+
})
|
|
1224
|
+
return null
|
|
1225
|
+
}
|
|
1226
|
+
|
|
788
1227
|
return {
|
|
789
1228
|
type: 'fanout',
|
|
790
1229
|
stepName: childStep.stepName,
|
|
@@ -796,6 +1235,66 @@ function extractSequentialFanout(
|
|
|
796
1235
|
}
|
|
797
1236
|
}
|
|
798
1237
|
|
|
1238
|
+
/**
|
|
1239
|
+
* Extract a single output binding from an expression
|
|
1240
|
+
*/
|
|
1241
|
+
function extractOutputBinding(
|
|
1242
|
+
expr: ts.Expression,
|
|
1243
|
+
context: ExtractionContext
|
|
1244
|
+
): OutputBinding | null {
|
|
1245
|
+
// Check for property access (e.g., org.id, payment.status)
|
|
1246
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
1247
|
+
const objName = ts.isIdentifier(expr.expression)
|
|
1248
|
+
? expr.expression.text
|
|
1249
|
+
: null
|
|
1250
|
+
const propPath = expr.name.text
|
|
1251
|
+
|
|
1252
|
+
if (objName && context.outputVars.has(objName)) {
|
|
1253
|
+
return { from: 'outputVar', name: objName, path: propPath }
|
|
1254
|
+
}
|
|
1255
|
+
if (objName && context.contextVars.has(objName)) {
|
|
1256
|
+
return { from: 'stateVar', name: objName, path: propPath }
|
|
1257
|
+
}
|
|
1258
|
+
if (objName === context.inputParamName) {
|
|
1259
|
+
return { from: 'input', path: propPath }
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
// Check for identifier (simple variable reference)
|
|
1264
|
+
if (ts.isIdentifier(expr)) {
|
|
1265
|
+
const varName = expr.text
|
|
1266
|
+
if (context.outputVars.has(varName)) {
|
|
1267
|
+
return { from: 'outputVar', name: varName }
|
|
1268
|
+
}
|
|
1269
|
+
if (context.contextVars.has(varName)) {
|
|
1270
|
+
return { from: 'stateVar', name: varName }
|
|
1271
|
+
}
|
|
1272
|
+
if (varName === context.inputParamName) {
|
|
1273
|
+
return { from: 'input', path: varName }
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// Check for literals
|
|
1278
|
+
if (ts.isStringLiteral(expr)) {
|
|
1279
|
+
return { from: 'literal', value: expr.text }
|
|
1280
|
+
}
|
|
1281
|
+
if (ts.isNumericLiteral(expr)) {
|
|
1282
|
+
return { from: 'literal', value: Number(expr.text) }
|
|
1283
|
+
}
|
|
1284
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
1285
|
+
return { from: 'literal', value: true }
|
|
1286
|
+
}
|
|
1287
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
1288
|
+
return { from: 'literal', value: false }
|
|
1289
|
+
}
|
|
1290
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
1291
|
+
return { from: 'literal', value: null }
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
// For any other expression (comparisons, method calls, etc.), capture as expression
|
|
1295
|
+
return { from: 'expression', expression: getSourceText(expr) }
|
|
1296
|
+
}
|
|
1297
|
+
|
|
799
1298
|
/**
|
|
800
1299
|
* Extract return step
|
|
801
1300
|
*/
|
|
@@ -826,48 +1325,17 @@ function extractReturn(
|
|
|
826
1325
|
let binding: OutputBinding | null = null
|
|
827
1326
|
|
|
828
1327
|
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
829
|
-
// { orgId } - must be an output variable
|
|
1328
|
+
// { orgId } - must be an output variable, context variable, or input
|
|
830
1329
|
const varName = prop.name.text
|
|
831
1330
|
if (context.outputVars.has(varName)) {
|
|
832
1331
|
binding = { from: 'outputVar', name: varName }
|
|
1332
|
+
} else if (context.contextVars.has(varName)) {
|
|
1333
|
+
binding = { from: 'stateVar', name: varName }
|
|
1334
|
+
} else {
|
|
1335
|
+
binding = { from: 'input', path: varName }
|
|
833
1336
|
}
|
|
834
1337
|
} else if (ts.isPropertyAssignment(prop)) {
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
// Check for property access (e.g., org.id, owner?.id)
|
|
838
|
-
if (ts.isPropertyAccessExpression(init)) {
|
|
839
|
-
const objName = ts.isIdentifier(init.expression)
|
|
840
|
-
? init.expression.text
|
|
841
|
-
: null
|
|
842
|
-
const propPath = init.name.text
|
|
843
|
-
|
|
844
|
-
if (objName && context.outputVars.has(objName)) {
|
|
845
|
-
binding = { from: 'outputVar', name: objName, path: propPath }
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
// Check for optional chaining (e.g., owner?.id)
|
|
850
|
-
if (
|
|
851
|
-
init.kind === ts.SyntaxKind.PropertyAccessExpression ||
|
|
852
|
-
init.kind === ts.SyntaxKind.NonNullExpression
|
|
853
|
-
) {
|
|
854
|
-
const text = init.getText()
|
|
855
|
-
const match = text.match(/^(\w+)\??\.(\w+)$/)
|
|
856
|
-
if (match) {
|
|
857
|
-
const [, objName, propPath] = match
|
|
858
|
-
if (context.outputVars.has(objName)) {
|
|
859
|
-
binding = { from: 'outputVar', name: objName, path: propPath }
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// Check for identifier (simple variable reference)
|
|
865
|
-
if (ts.isIdentifier(init)) {
|
|
866
|
-
const varName = init.text
|
|
867
|
-
if (context.outputVars.has(varName)) {
|
|
868
|
-
binding = { from: 'outputVar', name: varName }
|
|
869
|
-
}
|
|
870
|
-
}
|
|
1338
|
+
binding = extractOutputBinding(prop.initializer, context)
|
|
871
1339
|
}
|
|
872
1340
|
|
|
873
1341
|
if (binding) {
|
|
@@ -892,7 +1360,19 @@ function extractReturn(
|
|
|
892
1360
|
function extractInputSources(
|
|
893
1361
|
node: ts.Node,
|
|
894
1362
|
context: ExtractionContext
|
|
895
|
-
): Record<string, InputSource> | undefined {
|
|
1363
|
+
): Record<string, InputSource> | 'passthrough' | undefined {
|
|
1364
|
+
// Handle when data is passed directly (e.g., workflow.do('step', 'rpc', data))
|
|
1365
|
+
if (ts.isIdentifier(node)) {
|
|
1366
|
+
if (node.text === context.inputParamName) {
|
|
1367
|
+
// The entire input data is being passed through
|
|
1368
|
+
return 'passthrough'
|
|
1369
|
+
}
|
|
1370
|
+
// Check if it's an output variable being passed directly
|
|
1371
|
+
if (context.outputVars.has(node.text)) {
|
|
1372
|
+
return 'passthrough'
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
896
1376
|
if (!ts.isObjectLiteralExpression(node)) {
|
|
897
1377
|
return undefined
|
|
898
1378
|
}
|
|
@@ -912,9 +1392,11 @@ function extractInputSources(
|
|
|
912
1392
|
let source: InputSource | null = null
|
|
913
1393
|
|
|
914
1394
|
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
915
|
-
// { email } - could be from
|
|
1395
|
+
// { email } - could be from loop var, output var, or input
|
|
916
1396
|
const varName = prop.name.text
|
|
917
|
-
if (context.
|
|
1397
|
+
if (context.loopVars.has(varName)) {
|
|
1398
|
+
source = { from: 'item', path: varName }
|
|
1399
|
+
} else if (context.outputVars.has(varName)) {
|
|
918
1400
|
source = { from: 'outputVar', name: varName }
|
|
919
1401
|
} else {
|
|
920
1402
|
source = { from: 'input', path: varName }
|
|
@@ -944,6 +1426,26 @@ function extractInputSources(
|
|
|
944
1426
|
return Object.keys(inputs).length > 0 ? inputs : undefined
|
|
945
1427
|
}
|
|
946
1428
|
|
|
1429
|
+
function inputSourceToInlineValue(source: InputSource): unknown {
|
|
1430
|
+
switch (source.from) {
|
|
1431
|
+
case 'literal':
|
|
1432
|
+
return source.value
|
|
1433
|
+
case 'input':
|
|
1434
|
+
return { $ref: 'trigger', path: source.path }
|
|
1435
|
+
case 'outputVar':
|
|
1436
|
+
return { $ref: source.name, path: source.path }
|
|
1437
|
+
case 'item':
|
|
1438
|
+
return { $ref: '$item', path: source.path }
|
|
1439
|
+
case 'template':
|
|
1440
|
+
return {
|
|
1441
|
+
$template: {
|
|
1442
|
+
parts: source.parts,
|
|
1443
|
+
expressions: source.expressions.map(inputSourceToInlineValue),
|
|
1444
|
+
},
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
|
|
947
1449
|
/**
|
|
948
1450
|
* Extract a single input source
|
|
949
1451
|
*/
|
|
@@ -973,6 +1475,11 @@ function extractInputSource(
|
|
|
973
1475
|
if (ts.isIdentifier(node)) {
|
|
974
1476
|
const varName = node.text
|
|
975
1477
|
|
|
1478
|
+
// Check if it's a loop variable (from fanout)
|
|
1479
|
+
if (context.loopVars.has(varName)) {
|
|
1480
|
+
return { from: 'item', path: varName }
|
|
1481
|
+
}
|
|
1482
|
+
|
|
976
1483
|
if (context.outputVars.has(varName)) {
|
|
977
1484
|
return { from: 'outputVar', name: varName }
|
|
978
1485
|
}
|
|
@@ -1011,8 +1518,8 @@ function extractInputSource(
|
|
|
1011
1518
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
1012
1519
|
const propName = prop.name.text
|
|
1013
1520
|
const propSource = extractInputSource(prop.initializer, context)
|
|
1014
|
-
if (propSource
|
|
1015
|
-
obj[propName] = propSource
|
|
1521
|
+
if (propSource) {
|
|
1522
|
+
obj[propName] = inputSourceToInlineValue(propSource)
|
|
1016
1523
|
}
|
|
1017
1524
|
}
|
|
1018
1525
|
}
|
|
@@ -1024,12 +1531,98 @@ function extractInputSource(
|
|
|
1024
1531
|
const arr: unknown[] = []
|
|
1025
1532
|
for (const elem of node.elements) {
|
|
1026
1533
|
const elemSource = extractInputSource(elem, context)
|
|
1027
|
-
if (elemSource
|
|
1028
|
-
arr.push(elemSource
|
|
1534
|
+
if (elemSource) {
|
|
1535
|
+
arr.push(inputSourceToInlineValue(elemSource))
|
|
1029
1536
|
}
|
|
1030
1537
|
}
|
|
1031
1538
|
return { from: 'literal', value: arr }
|
|
1032
1539
|
}
|
|
1033
1540
|
|
|
1541
|
+
// No substitution template literal: `hello`
|
|
1542
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
1543
|
+
return { from: 'literal', value: node.text }
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
// Template expression with substitutions: `hello ${name}`
|
|
1547
|
+
if (ts.isTemplateExpression(node)) {
|
|
1548
|
+
const parts: string[] = [node.head.text]
|
|
1549
|
+
const expressions: InputSource[] = []
|
|
1550
|
+
|
|
1551
|
+
for (const span of node.templateSpans) {
|
|
1552
|
+
// Extract each expression
|
|
1553
|
+
const exprSource = extractInputSource(span.expression, context)
|
|
1554
|
+
if (exprSource) {
|
|
1555
|
+
expressions.push(exprSource)
|
|
1556
|
+
} else {
|
|
1557
|
+
// Fallback: use source text as literal
|
|
1558
|
+
expressions.push({
|
|
1559
|
+
from: 'literal',
|
|
1560
|
+
value: getSourceText(span.expression),
|
|
1561
|
+
})
|
|
1562
|
+
}
|
|
1563
|
+
parts.push(span.literal.text)
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
return { from: 'template', parts, expressions }
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1034
1569
|
return null
|
|
1035
1570
|
}
|
|
1571
|
+
|
|
1572
|
+
/**
|
|
1573
|
+
* Extract a literal value from an expression
|
|
1574
|
+
*/
|
|
1575
|
+
function extractLiteralValue(expr: ts.Expression): unknown | undefined {
|
|
1576
|
+
if (ts.isStringLiteral(expr)) {
|
|
1577
|
+
return expr.text
|
|
1578
|
+
}
|
|
1579
|
+
if (ts.isNumericLiteral(expr)) {
|
|
1580
|
+
return Number(expr.text)
|
|
1581
|
+
}
|
|
1582
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
1583
|
+
return true
|
|
1584
|
+
}
|
|
1585
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
1586
|
+
return false
|
|
1587
|
+
}
|
|
1588
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
1589
|
+
return null
|
|
1590
|
+
}
|
|
1591
|
+
// Array literal
|
|
1592
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
1593
|
+
const values: unknown[] = []
|
|
1594
|
+
for (const el of expr.elements) {
|
|
1595
|
+
const v = extractLiteralValue(el)
|
|
1596
|
+
if (v === undefined) return undefined
|
|
1597
|
+
values.push(v)
|
|
1598
|
+
}
|
|
1599
|
+
return values
|
|
1600
|
+
}
|
|
1601
|
+
// Object literal (simple keys with literal values)
|
|
1602
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
1603
|
+
const obj: Record<string, unknown> = {}
|
|
1604
|
+
for (const prop of expr.properties) {
|
|
1605
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
1606
|
+
const v = extractLiteralValue(prop.initializer)
|
|
1607
|
+
if (v === undefined) return undefined
|
|
1608
|
+
obj[prop.name.text] = v
|
|
1609
|
+
} else {
|
|
1610
|
+
return undefined
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return obj
|
|
1614
|
+
}
|
|
1615
|
+
return undefined
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Infer a simple type string from a TypeScript type
|
|
1620
|
+
*/
|
|
1621
|
+
function inferSimpleType(type: ts.Type, checker: ts.TypeChecker): string {
|
|
1622
|
+
const typeStr = checker.typeToString(type)
|
|
1623
|
+
if (typeStr === 'string') return 'string'
|
|
1624
|
+
if (typeStr === 'number') return 'number'
|
|
1625
|
+
if (typeStr === 'boolean') return 'boolean'
|
|
1626
|
+
if (typeStr.endsWith('[]') || typeStr.startsWith('Array<')) return 'array'
|
|
1627
|
+
return 'object'
|
|
1628
|
+
}
|