@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/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js}
RENAMED
|
@@ -1,11 +1,26 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
|
-
import {
|
|
3
|
-
import { isWorkflowDoCall, isWorkflowSleepCall, isParallelFanout, isParallelGroup, isSequentialFanout, extractForOfVariable, isArrayType, getSourceText, } from './patterns.js';
|
|
2
|
+
import { isWorkflowDoCall, isWorkflowSleepCall, isThrowCancelException, extractCancelReason, isParallelFanout, isParallelGroup, isSequentialFanout, isArrayFilter, isArraySome, isArrayEvery, extractForOfVariable, isArrayType, getSourceText, } from './patterns.js';
|
|
4
3
|
import { validateNoDisallowedPatterns, validateAwaitedCalls, formatValidationErrors, } from './validation.js';
|
|
4
|
+
import { extractStringLiteral, extractNumberLiteral, } from '../../extract-node-value.js';
|
|
5
|
+
/**
|
|
6
|
+
* Extract full source path from an expression (e.g., data.memberEmails)
|
|
7
|
+
*/
|
|
8
|
+
function extractSourcePath(expr) {
|
|
9
|
+
if (ts.isIdentifier(expr)) {
|
|
10
|
+
return expr.text;
|
|
11
|
+
}
|
|
12
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
13
|
+
const base = extractSourcePath(expr.expression);
|
|
14
|
+
if (base) {
|
|
15
|
+
return `${base}.${expr.name.text}`;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
5
20
|
/**
|
|
6
21
|
* Extract simple workflow metadata from a function declaration
|
|
7
22
|
*/
|
|
8
|
-
export function
|
|
23
|
+
export function extractDSLWorkflow(funcNode, checker) {
|
|
9
24
|
try {
|
|
10
25
|
// Find the async arrow function
|
|
11
26
|
const arrowFunc = findWorkflowFunction(funcNode);
|
|
@@ -33,6 +48,9 @@ export function extractSimpleWorkflow(funcNode, checker) {
|
|
|
33
48
|
conditionalVars: new Set(),
|
|
34
49
|
inputParamName,
|
|
35
50
|
errors: [],
|
|
51
|
+
loopVars: new Set(),
|
|
52
|
+
contextVars: new Map(),
|
|
53
|
+
depth: 0,
|
|
36
54
|
};
|
|
37
55
|
// Validate no disallowed patterns
|
|
38
56
|
const patternErrors = validateNoDisallowedPatterns(arrowFunc.body);
|
|
@@ -62,9 +80,18 @@ export function extractSimpleWorkflow(funcNode, checker) {
|
|
|
62
80
|
simple: false,
|
|
63
81
|
};
|
|
64
82
|
}
|
|
83
|
+
// Build workflow context from extracted context variables
|
|
84
|
+
const workflowContext = {};
|
|
85
|
+
for (const [name, info] of context.contextVars) {
|
|
86
|
+
workflowContext[name] = {
|
|
87
|
+
type: info.type,
|
|
88
|
+
default: info.default,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
65
91
|
return {
|
|
66
92
|
status: 'ok',
|
|
67
93
|
steps,
|
|
94
|
+
context: Object.keys(workflowContext).length > 0 ? workflowContext : undefined,
|
|
68
95
|
simple: true,
|
|
69
96
|
};
|
|
70
97
|
}
|
|
@@ -80,7 +107,7 @@ export function extractSimpleWorkflow(funcNode, checker) {
|
|
|
80
107
|
* Find the workflow function (async arrow function)
|
|
81
108
|
*/
|
|
82
109
|
function findWorkflowFunction(node) {
|
|
83
|
-
// Handle
|
|
110
|
+
// Handle pikkuWorkflowFunc(async () => {}) or pikkuWorkflowComplexFunc(async () => {})
|
|
84
111
|
if (ts.isCallExpression(node)) {
|
|
85
112
|
const arg = node.arguments[0];
|
|
86
113
|
if (arg && ts.isArrowFunction(arg)) {
|
|
@@ -99,7 +126,7 @@ function findWorkflowFunction(node) {
|
|
|
99
126
|
}
|
|
100
127
|
}
|
|
101
128
|
}
|
|
102
|
-
// Handle
|
|
129
|
+
// Handle pikkuWorkflowFunc({ func: async () => {} })
|
|
103
130
|
if (ts.isObjectLiteralExpression(node)) {
|
|
104
131
|
for (const prop of node.properties) {
|
|
105
132
|
if (ts.isPropertyAssignment(prop) &&
|
|
@@ -129,17 +156,25 @@ function extractInputParamName(arrowFunc) {
|
|
|
129
156
|
/**
|
|
130
157
|
* Extract steps from the function body
|
|
131
158
|
*/
|
|
132
|
-
function extractSteps(body, context) {
|
|
159
|
+
function extractSteps(body, context, incrementDepth = false) {
|
|
133
160
|
const steps = [];
|
|
134
161
|
if (!ts.isBlock(body)) {
|
|
135
162
|
return steps;
|
|
136
163
|
}
|
|
164
|
+
// Increment depth when entering a nested block
|
|
165
|
+
if (incrementDepth) {
|
|
166
|
+
context.depth++;
|
|
167
|
+
}
|
|
137
168
|
for (const statement of body.statements) {
|
|
138
169
|
const extracted = extractStep(statement, context);
|
|
139
170
|
if (extracted) {
|
|
140
171
|
steps.push(extracted);
|
|
141
172
|
}
|
|
142
173
|
}
|
|
174
|
+
// Restore depth
|
|
175
|
+
if (incrementDepth) {
|
|
176
|
+
context.depth--;
|
|
177
|
+
}
|
|
143
178
|
return steps;
|
|
144
179
|
}
|
|
145
180
|
/**
|
|
@@ -158,6 +193,10 @@ function extractStep(statement, context) {
|
|
|
158
193
|
if (ts.isIfStatement(statement)) {
|
|
159
194
|
return extractBranch(statement, context);
|
|
160
195
|
}
|
|
196
|
+
// Switch statement
|
|
197
|
+
if (ts.isSwitchStatement(statement)) {
|
|
198
|
+
return extractSwitch(statement, context);
|
|
199
|
+
}
|
|
161
200
|
// For-of statement (sequential fanout)
|
|
162
201
|
if (ts.isForOfStatement(statement)) {
|
|
163
202
|
return extractSequentialFanout(statement, context);
|
|
@@ -166,6 +205,10 @@ function extractStep(statement, context) {
|
|
|
166
205
|
if (ts.isReturnStatement(statement)) {
|
|
167
206
|
return extractReturn(statement, context);
|
|
168
207
|
}
|
|
208
|
+
// Throw statement (for WorkflowCancelledException)
|
|
209
|
+
if (ts.isThrowStatement(statement)) {
|
|
210
|
+
return extractThrowCancel(statement, context);
|
|
211
|
+
}
|
|
169
212
|
return null;
|
|
170
213
|
}
|
|
171
214
|
/**
|
|
@@ -182,9 +225,25 @@ function extractVariableDeclaration(statement, context) {
|
|
|
182
225
|
}
|
|
183
226
|
const varName = decl.name.text;
|
|
184
227
|
const init = decl.initializer;
|
|
228
|
+
// Check for block-scoped variable declarations (not allowed)
|
|
229
|
+
if (context.depth > 0) {
|
|
230
|
+
context.errors.push({
|
|
231
|
+
message: `Variable declaration '${varName}' inside block is not supported in DSL workflows. Move all let/const declarations to the top level.`,
|
|
232
|
+
node: statement,
|
|
233
|
+
});
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
185
236
|
if (!init) {
|
|
186
237
|
return null;
|
|
187
238
|
}
|
|
239
|
+
// Check for simple literal/expression context variable (let x = 'value')
|
|
240
|
+
const literalValue = extractLiteralValue(init);
|
|
241
|
+
if (literalValue !== undefined) {
|
|
242
|
+
const tsType = context.checker.getTypeAtLocation(decl);
|
|
243
|
+
const typeStr = inferSimpleType(tsType, context.checker);
|
|
244
|
+
context.contextVars.set(varName, { type: typeStr, default: literalValue });
|
|
245
|
+
return null; // No step emitted, just register the context var
|
|
246
|
+
}
|
|
188
247
|
// Check for await workflow.do(...)
|
|
189
248
|
if (ts.isAwaitExpression(init) && ts.isCallExpression(init.expression)) {
|
|
190
249
|
const call = init.expression;
|
|
@@ -215,6 +274,28 @@ function extractVariableDeclaration(statement, context) {
|
|
|
215
274
|
}
|
|
216
275
|
}
|
|
217
276
|
}
|
|
277
|
+
// Check for array.filter(...)
|
|
278
|
+
if (ts.isCallExpression(init)) {
|
|
279
|
+
if (isArrayFilter(init)) {
|
|
280
|
+
const filterStep = extractArrayFilter(init, context, varName);
|
|
281
|
+
if (filterStep) {
|
|
282
|
+
const type = context.checker.getTypeAtLocation(decl);
|
|
283
|
+
context.outputVars.set(varName, { type, node: decl });
|
|
284
|
+
if (isArrayType(type, context.checker)) {
|
|
285
|
+
context.arrayVars.add(varName);
|
|
286
|
+
}
|
|
287
|
+
return filterStep;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (isArraySome(init) || isArrayEvery(init)) {
|
|
291
|
+
const predicateStep = extractArrayPredicate(init, context, varName);
|
|
292
|
+
if (predicateStep) {
|
|
293
|
+
const type = context.checker.getTypeAtLocation(decl);
|
|
294
|
+
context.outputVars.set(varName, { type, node: decl });
|
|
295
|
+
return predicateStep;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
218
299
|
return null;
|
|
219
300
|
}
|
|
220
301
|
/**
|
|
@@ -222,13 +303,30 @@ function extractVariableDeclaration(statement, context) {
|
|
|
222
303
|
*/
|
|
223
304
|
function extractExpressionStatement(statement, context) {
|
|
224
305
|
let expr = statement.expression;
|
|
225
|
-
// Handle assignment:
|
|
306
|
+
// Handle assignment: x = value or x = await workflow.do(...)
|
|
226
307
|
let outputVar;
|
|
227
308
|
if (ts.isBinaryExpression(expr) &&
|
|
228
309
|
expr.operatorToken.kind === ts.SyntaxKind.EqualsToken) {
|
|
229
310
|
// Extract variable name from left side
|
|
230
311
|
if (ts.isIdentifier(expr.left)) {
|
|
231
312
|
outputVar = expr.left.text;
|
|
313
|
+
// Check if this is an assignment to a context variable (set step)
|
|
314
|
+
if (context.contextVars.has(outputVar)) {
|
|
315
|
+
const literalValue = extractLiteralValue(expr.right);
|
|
316
|
+
if (literalValue !== undefined) {
|
|
317
|
+
return {
|
|
318
|
+
type: 'set',
|
|
319
|
+
variable: outputVar,
|
|
320
|
+
value: literalValue,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
// Non-literal assignment to context var - use expression as string
|
|
324
|
+
return {
|
|
325
|
+
type: 'set',
|
|
326
|
+
variable: outputVar,
|
|
327
|
+
value: getSourceText(expr.right),
|
|
328
|
+
};
|
|
329
|
+
}
|
|
232
330
|
}
|
|
233
331
|
// Use right side as the expression to extract from
|
|
234
332
|
expr = expr.right;
|
|
@@ -371,35 +469,242 @@ function extractSleepStep(call, context) {
|
|
|
371
469
|
}
|
|
372
470
|
}
|
|
373
471
|
/**
|
|
374
|
-
* Extract
|
|
472
|
+
* Extract cancel step from throw WorkflowCancelledException statement
|
|
473
|
+
*/
|
|
474
|
+
function extractThrowCancel(statement, context) {
|
|
475
|
+
if (!isThrowCancelException(statement)) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
const reason = extractCancelReason(statement, context.checker);
|
|
479
|
+
return {
|
|
480
|
+
type: 'cancel',
|
|
481
|
+
reason,
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Parse a condition expression into a Condition structure
|
|
486
|
+
*/
|
|
487
|
+
function parseCondition(expr) {
|
|
488
|
+
// Handle binary expressions (&&, ||)
|
|
489
|
+
if (ts.isBinaryExpression(expr)) {
|
|
490
|
+
const operator = expr.operatorToken.kind;
|
|
491
|
+
// AND operator (&&)
|
|
492
|
+
if (operator === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
493
|
+
return {
|
|
494
|
+
type: 'and',
|
|
495
|
+
conditions: [parseCondition(expr.left), parseCondition(expr.right)],
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
// OR operator (||)
|
|
499
|
+
if (operator === ts.SyntaxKind.BarBarToken) {
|
|
500
|
+
return {
|
|
501
|
+
type: 'or',
|
|
502
|
+
conditions: [parseCondition(expr.left), parseCondition(expr.right)],
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Handle parenthesized expressions - unwrap and parse inner
|
|
507
|
+
if (ts.isParenthesizedExpression(expr)) {
|
|
508
|
+
return parseCondition(expr.expression);
|
|
509
|
+
}
|
|
510
|
+
// Simple condition (comparison, function call, variable, etc.)
|
|
511
|
+
return {
|
|
512
|
+
type: 'simple',
|
|
513
|
+
expression: getSourceText(expr),
|
|
514
|
+
};
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Extract branch step from if statement (supports if/else-if/else chains)
|
|
375
518
|
*/
|
|
376
519
|
function extractBranch(statement, context) {
|
|
377
|
-
const
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
? extractSteps(
|
|
385
|
-
: extractStepsFromStatement(
|
|
386
|
-
|
|
520
|
+
const branches = [];
|
|
521
|
+
let elseSteps;
|
|
522
|
+
// Walk the if/else-if chain
|
|
523
|
+
let current = statement;
|
|
524
|
+
while (current) {
|
|
525
|
+
const condition = parseCondition(current.expression);
|
|
526
|
+
const steps = ts.isBlock(current.thenStatement)
|
|
527
|
+
? extractSteps(current.thenStatement, context, true)
|
|
528
|
+
: extractStepsFromStatement(current.thenStatement, context);
|
|
529
|
+
branches.push({ condition, steps });
|
|
530
|
+
// Check for else-if or else
|
|
531
|
+
if (current.elseStatement) {
|
|
532
|
+
if (ts.isIfStatement(current.elseStatement)) {
|
|
533
|
+
// else-if: continue the chain
|
|
534
|
+
current = current.elseStatement;
|
|
535
|
+
}
|
|
536
|
+
else {
|
|
537
|
+
// else: extract the final else block and stop
|
|
538
|
+
elseSteps = ts.isBlock(current.elseStatement)
|
|
539
|
+
? extractSteps(current.elseStatement, context, true)
|
|
540
|
+
: extractStepsFromStatement(current.elseStatement, context);
|
|
541
|
+
current = undefined;
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
else {
|
|
545
|
+
// No else clause
|
|
546
|
+
current = undefined;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
387
549
|
return {
|
|
388
550
|
type: 'branch',
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
then: thenSteps,
|
|
392
|
-
else: elseSteps,
|
|
393
|
-
},
|
|
551
|
+
branches,
|
|
552
|
+
elseSteps,
|
|
394
553
|
};
|
|
395
554
|
}
|
|
396
555
|
/**
|
|
397
556
|
* Extract steps from a single statement (non-block)
|
|
398
557
|
*/
|
|
399
558
|
function extractStepsFromStatement(statement, context) {
|
|
559
|
+
// Increment depth for single-statement blocks (if without braces)
|
|
560
|
+
context.depth++;
|
|
400
561
|
const step = extractStep(statement, context);
|
|
562
|
+
context.depth--;
|
|
401
563
|
return step ? [step] : [];
|
|
402
564
|
}
|
|
565
|
+
/**
|
|
566
|
+
* Extract switch statement
|
|
567
|
+
*/
|
|
568
|
+
function extractSwitch(statement, context) {
|
|
569
|
+
const expression = getSourceText(statement.expression);
|
|
570
|
+
const cases = [];
|
|
571
|
+
let defaultSteps;
|
|
572
|
+
for (const clause of statement.caseBlock.clauses) {
|
|
573
|
+
if (ts.isCaseClause(clause)) {
|
|
574
|
+
const caseValue = extractCaseValue(clause.expression);
|
|
575
|
+
const steps = extractCaseSteps(clause.statements, context);
|
|
576
|
+
cases.push({
|
|
577
|
+
value: caseValue.value,
|
|
578
|
+
expression: caseValue.expression,
|
|
579
|
+
steps,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
else if (ts.isDefaultClause(clause)) {
|
|
583
|
+
defaultSteps = extractCaseSteps(clause.statements, context);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return {
|
|
587
|
+
type: 'switch',
|
|
588
|
+
expression,
|
|
589
|
+
cases,
|
|
590
|
+
defaultSteps,
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* Extract case value from expression
|
|
595
|
+
*/
|
|
596
|
+
function extractCaseValue(expr) {
|
|
597
|
+
if (ts.isStringLiteral(expr)) {
|
|
598
|
+
return { value: expr.text };
|
|
599
|
+
}
|
|
600
|
+
if (ts.isNumericLiteral(expr)) {
|
|
601
|
+
return { value: Number(expr.text) };
|
|
602
|
+
}
|
|
603
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
604
|
+
return { value: true };
|
|
605
|
+
}
|
|
606
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
607
|
+
return { value: false };
|
|
608
|
+
}
|
|
609
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
610
|
+
return { value: null };
|
|
611
|
+
}
|
|
612
|
+
return { expression: getSourceText(expr) };
|
|
613
|
+
}
|
|
614
|
+
/**
|
|
615
|
+
* Extract steps from case statements, stopping at break
|
|
616
|
+
*/
|
|
617
|
+
function extractCaseSteps(statements, context) {
|
|
618
|
+
const steps = [];
|
|
619
|
+
// Increment depth for case blocks
|
|
620
|
+
context.depth++;
|
|
621
|
+
for (const statement of statements) {
|
|
622
|
+
if (ts.isBreakStatement(statement)) {
|
|
623
|
+
break;
|
|
624
|
+
}
|
|
625
|
+
const step = extractStep(statement, context);
|
|
626
|
+
if (step) {
|
|
627
|
+
steps.push(step);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// Restore depth
|
|
631
|
+
context.depth--;
|
|
632
|
+
return steps;
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Extract array filter operation
|
|
636
|
+
*/
|
|
637
|
+
function extractArrayFilter(call, context, outputVar) {
|
|
638
|
+
if (!ts.isPropertyAccessExpression(call.expression)) {
|
|
639
|
+
return null;
|
|
640
|
+
}
|
|
641
|
+
const sourceExpr = call.expression.expression;
|
|
642
|
+
const sourceVar = extractSourcePath(sourceExpr);
|
|
643
|
+
if (!sourceVar) {
|
|
644
|
+
return null;
|
|
645
|
+
}
|
|
646
|
+
const filterFn = call.arguments[0];
|
|
647
|
+
if (!filterFn || !ts.isArrowFunction(filterFn)) {
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
const itemParam = filterFn.parameters[0];
|
|
651
|
+
if (!itemParam || !ts.isIdentifier(itemParam.name)) {
|
|
652
|
+
return null;
|
|
653
|
+
}
|
|
654
|
+
const itemVar = itemParam.name.text;
|
|
655
|
+
let condition;
|
|
656
|
+
if (ts.isBlock(filterFn.body)) {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
else {
|
|
660
|
+
condition = parseCondition(filterFn.body);
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
type: 'filter',
|
|
664
|
+
sourceVar,
|
|
665
|
+
itemVar,
|
|
666
|
+
condition,
|
|
667
|
+
outputVar,
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Extract array predicate operation (some/every)
|
|
672
|
+
*/
|
|
673
|
+
function extractArrayPredicate(call, context, outputVar) {
|
|
674
|
+
if (!ts.isPropertyAccessExpression(call.expression)) {
|
|
675
|
+
return null;
|
|
676
|
+
}
|
|
677
|
+
const mode = call.expression.name.text;
|
|
678
|
+
const sourceExpr = call.expression.expression;
|
|
679
|
+
const sourceVar = extractSourcePath(sourceExpr);
|
|
680
|
+
if (!sourceVar) {
|
|
681
|
+
return null;
|
|
682
|
+
}
|
|
683
|
+
const predicateFn = call.arguments[0];
|
|
684
|
+
if (!predicateFn || !ts.isArrowFunction(predicateFn)) {
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const itemParam = predicateFn.parameters[0];
|
|
688
|
+
if (!itemParam || !ts.isIdentifier(itemParam.name)) {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
const itemVar = itemParam.name.text;
|
|
692
|
+
let condition;
|
|
693
|
+
if (ts.isBlock(predicateFn.body)) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
condition = parseCondition(predicateFn.body);
|
|
698
|
+
}
|
|
699
|
+
return {
|
|
700
|
+
type: 'arrayPredicate',
|
|
701
|
+
mode,
|
|
702
|
+
sourceVar,
|
|
703
|
+
itemVar,
|
|
704
|
+
condition,
|
|
705
|
+
outputVar,
|
|
706
|
+
};
|
|
707
|
+
}
|
|
403
708
|
/**
|
|
404
709
|
* Extract parallel fanout from Promise.all(array.map(...))
|
|
405
710
|
*/
|
|
@@ -413,14 +718,7 @@ function extractParallelFanout(call, context) {
|
|
|
413
718
|
}
|
|
414
719
|
// Extract source array
|
|
415
720
|
const sourceExpr = mapCall.expression.expression;
|
|
416
|
-
|
|
417
|
-
if (ts.isIdentifier(sourceExpr)) {
|
|
418
|
-
sourceVar = sourceExpr.text;
|
|
419
|
-
}
|
|
420
|
-
else if (ts.isPropertyAccessExpression(sourceExpr) &&
|
|
421
|
-
ts.isIdentifier(sourceExpr.expression)) {
|
|
422
|
-
sourceVar = sourceExpr.expression.text;
|
|
423
|
-
}
|
|
721
|
+
const sourceVar = extractSourcePath(sourceExpr);
|
|
424
722
|
if (!sourceVar) {
|
|
425
723
|
return null;
|
|
426
724
|
}
|
|
@@ -449,7 +747,15 @@ function extractParallelFanout(call, context) {
|
|
|
449
747
|
else if (ts.isBlock(mapFn.body)) {
|
|
450
748
|
// Look for workflow.do in block
|
|
451
749
|
for (const stmt of mapFn.body.statements) {
|
|
452
|
-
if (ts.
|
|
750
|
+
if (ts.isExpressionStatement(stmt)) {
|
|
751
|
+
// Handle: await workflow.do(...)
|
|
752
|
+
if (ts.isAwaitExpression(stmt.expression) &&
|
|
753
|
+
ts.isCallExpression(stmt.expression.expression)) {
|
|
754
|
+
doCall = stmt.expression.expression;
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
else if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
453
759
|
if (ts.isCallExpression(stmt.expression)) {
|
|
454
760
|
doCall = stmt.expression;
|
|
455
761
|
break;
|
|
@@ -467,10 +773,11 @@ function extractParallelFanout(call, context) {
|
|
|
467
773
|
if (!doCall || !isWorkflowDoCall(doCall, context.checker)) {
|
|
468
774
|
return null;
|
|
469
775
|
}
|
|
470
|
-
// Create a temporary context for the child step
|
|
776
|
+
// Create a temporary context for the child step with the loop variable
|
|
471
777
|
const childContext = {
|
|
472
778
|
...context,
|
|
473
779
|
outputVars: new Map(context.outputVars),
|
|
780
|
+
loopVars: new Set([...context.loopVars, itemVar]),
|
|
474
781
|
};
|
|
475
782
|
const childStep = extractRpcStep(doCall, childContext);
|
|
476
783
|
if (!childStep) {
|
|
@@ -528,14 +835,45 @@ function extractSequentialFanout(statement, context) {
|
|
|
528
835
|
}
|
|
529
836
|
let childStep = null;
|
|
530
837
|
let timeBetween = undefined;
|
|
838
|
+
// Create a child context with the loop variable added
|
|
839
|
+
const childContext = {
|
|
840
|
+
...context,
|
|
841
|
+
outputVars: new Map(context.outputVars),
|
|
842
|
+
loopVars: new Set([...context.loopVars, itemVar]),
|
|
843
|
+
};
|
|
844
|
+
let workflowDoCount = 0;
|
|
531
845
|
for (const stmt of statement.statement.statements) {
|
|
532
|
-
// Look for workflow.do
|
|
846
|
+
// Look for workflow.do in VariableStatement (const x = await workflow.do(...))
|
|
847
|
+
if (ts.isVariableStatement(stmt)) {
|
|
848
|
+
const declList = stmt.declarationList;
|
|
849
|
+
if (declList.declarations.length === 1) {
|
|
850
|
+
const decl = declList.declarations[0];
|
|
851
|
+
const init = decl.initializer;
|
|
852
|
+
if (init &&
|
|
853
|
+
ts.isAwaitExpression(init) &&
|
|
854
|
+
ts.isCallExpression(init.expression)) {
|
|
855
|
+
const call = init.expression;
|
|
856
|
+
if (isWorkflowDoCall(call, context.checker)) {
|
|
857
|
+
workflowDoCount++;
|
|
858
|
+
const varName = ts.isIdentifier(decl.name)
|
|
859
|
+
? decl.name.text
|
|
860
|
+
: undefined;
|
|
861
|
+
const step = extractRpcStep(call, childContext, varName);
|
|
862
|
+
if (step) {
|
|
863
|
+
childStep = step;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
// Look for workflow.do in ExpressionStatement (await workflow.do(...))
|
|
533
870
|
if (ts.isExpressionStatement(stmt)) {
|
|
534
871
|
const expr = stmt.expression;
|
|
535
872
|
if (ts.isAwaitExpression(expr) && ts.isCallExpression(expr.expression)) {
|
|
536
873
|
const call = expr.expression;
|
|
537
874
|
if (isWorkflowDoCall(call, context.checker)) {
|
|
538
|
-
|
|
875
|
+
workflowDoCount++;
|
|
876
|
+
const step = extractRpcStep(call, childContext);
|
|
539
877
|
if (step) {
|
|
540
878
|
childStep = step;
|
|
541
879
|
}
|
|
@@ -595,6 +933,14 @@ function extractSequentialFanout(statement, context) {
|
|
|
595
933
|
if (!childStep) {
|
|
596
934
|
return null;
|
|
597
935
|
}
|
|
936
|
+
// If there are multiple workflow.do calls, the loop is too complex for DSL
|
|
937
|
+
if (workflowDoCount > 1) {
|
|
938
|
+
context.errors.push({
|
|
939
|
+
message: `For-of loop has ${workflowDoCount} workflow.do calls but DSL only supports 1. Use pikkuWorkflowComplexFunc for complex loops.`,
|
|
940
|
+
node: statement,
|
|
941
|
+
});
|
|
942
|
+
return null;
|
|
943
|
+
}
|
|
598
944
|
return {
|
|
599
945
|
type: 'fanout',
|
|
600
946
|
stepName: childStep.stepName,
|
|
@@ -605,6 +951,58 @@ function extractSequentialFanout(statement, context) {
|
|
|
605
951
|
timeBetween,
|
|
606
952
|
};
|
|
607
953
|
}
|
|
954
|
+
/**
|
|
955
|
+
* Extract a single output binding from an expression
|
|
956
|
+
*/
|
|
957
|
+
function extractOutputBinding(expr, context) {
|
|
958
|
+
// Check for property access (e.g., org.id, payment.status)
|
|
959
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
960
|
+
const objName = ts.isIdentifier(expr.expression)
|
|
961
|
+
? expr.expression.text
|
|
962
|
+
: null;
|
|
963
|
+
const propPath = expr.name.text;
|
|
964
|
+
if (objName && context.outputVars.has(objName)) {
|
|
965
|
+
return { from: 'outputVar', name: objName, path: propPath };
|
|
966
|
+
}
|
|
967
|
+
if (objName && context.contextVars.has(objName)) {
|
|
968
|
+
return { from: 'stateVar', name: objName, path: propPath };
|
|
969
|
+
}
|
|
970
|
+
if (objName === context.inputParamName) {
|
|
971
|
+
return { from: 'input', path: propPath };
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
// Check for identifier (simple variable reference)
|
|
975
|
+
if (ts.isIdentifier(expr)) {
|
|
976
|
+
const varName = expr.text;
|
|
977
|
+
if (context.outputVars.has(varName)) {
|
|
978
|
+
return { from: 'outputVar', name: varName };
|
|
979
|
+
}
|
|
980
|
+
if (context.contextVars.has(varName)) {
|
|
981
|
+
return { from: 'stateVar', name: varName };
|
|
982
|
+
}
|
|
983
|
+
if (varName === context.inputParamName) {
|
|
984
|
+
return { from: 'input', path: varName };
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
// Check for literals
|
|
988
|
+
if (ts.isStringLiteral(expr)) {
|
|
989
|
+
return { from: 'literal', value: expr.text };
|
|
990
|
+
}
|
|
991
|
+
if (ts.isNumericLiteral(expr)) {
|
|
992
|
+
return { from: 'literal', value: Number(expr.text) };
|
|
993
|
+
}
|
|
994
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
995
|
+
return { from: 'literal', value: true };
|
|
996
|
+
}
|
|
997
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
998
|
+
return { from: 'literal', value: false };
|
|
999
|
+
}
|
|
1000
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
1001
|
+
return { from: 'literal', value: null };
|
|
1002
|
+
}
|
|
1003
|
+
// For any other expression (comparisons, method calls, etc.), capture as expression
|
|
1004
|
+
return { from: 'expression', expression: getSourceText(expr) };
|
|
1005
|
+
}
|
|
608
1006
|
/**
|
|
609
1007
|
* Extract return step
|
|
610
1008
|
*/
|
|
@@ -625,44 +1023,21 @@ function extractReturn(statement, context) {
|
|
|
625
1023
|
}
|
|
626
1024
|
let binding = null;
|
|
627
1025
|
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
628
|
-
// { orgId } - must be an output variable
|
|
1026
|
+
// { orgId } - must be an output variable, context variable, or input
|
|
629
1027
|
const varName = prop.name.text;
|
|
630
1028
|
if (context.outputVars.has(varName)) {
|
|
631
1029
|
binding = { from: 'outputVar', name: varName };
|
|
632
1030
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const init = prop.initializer;
|
|
636
|
-
// Check for property access (e.g., org.id, owner?.id)
|
|
637
|
-
if (ts.isPropertyAccessExpression(init)) {
|
|
638
|
-
const objName = ts.isIdentifier(init.expression)
|
|
639
|
-
? init.expression.text
|
|
640
|
-
: null;
|
|
641
|
-
const propPath = init.name.text;
|
|
642
|
-
if (objName && context.outputVars.has(objName)) {
|
|
643
|
-
binding = { from: 'outputVar', name: objName, path: propPath };
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
// Check for optional chaining (e.g., owner?.id)
|
|
647
|
-
if (init.kind === ts.SyntaxKind.PropertyAccessExpression ||
|
|
648
|
-
init.kind === ts.SyntaxKind.NonNullExpression) {
|
|
649
|
-
const text = init.getText();
|
|
650
|
-
const match = text.match(/^(\w+)\??\.(\w+)$/);
|
|
651
|
-
if (match) {
|
|
652
|
-
const [, objName, propPath] = match;
|
|
653
|
-
if (context.outputVars.has(objName)) {
|
|
654
|
-
binding = { from: 'outputVar', name: objName, path: propPath };
|
|
655
|
-
}
|
|
656
|
-
}
|
|
1031
|
+
else if (context.contextVars.has(varName)) {
|
|
1032
|
+
binding = { from: 'stateVar', name: varName };
|
|
657
1033
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
const varName = init.text;
|
|
661
|
-
if (context.outputVars.has(varName)) {
|
|
662
|
-
binding = { from: 'outputVar', name: varName };
|
|
663
|
-
}
|
|
1034
|
+
else {
|
|
1035
|
+
binding = { from: 'input', path: varName };
|
|
664
1036
|
}
|
|
665
1037
|
}
|
|
1038
|
+
else if (ts.isPropertyAssignment(prop)) {
|
|
1039
|
+
binding = extractOutputBinding(prop.initializer, context);
|
|
1040
|
+
}
|
|
666
1041
|
if (binding) {
|
|
667
1042
|
outputs[propName] = binding;
|
|
668
1043
|
}
|
|
@@ -680,6 +1055,17 @@ function extractReturn(statement, context) {
|
|
|
680
1055
|
* Extract input sources from an argument node
|
|
681
1056
|
*/
|
|
682
1057
|
function extractInputSources(node, context) {
|
|
1058
|
+
// Handle when data is passed directly (e.g., workflow.do('step', 'rpc', data))
|
|
1059
|
+
if (ts.isIdentifier(node)) {
|
|
1060
|
+
if (node.text === context.inputParamName) {
|
|
1061
|
+
// The entire input data is being passed through
|
|
1062
|
+
return 'passthrough';
|
|
1063
|
+
}
|
|
1064
|
+
// Check if it's an output variable being passed directly
|
|
1065
|
+
if (context.outputVars.has(node.text)) {
|
|
1066
|
+
return 'passthrough';
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
683
1069
|
if (!ts.isObjectLiteralExpression(node)) {
|
|
684
1070
|
return undefined;
|
|
685
1071
|
}
|
|
@@ -693,9 +1079,12 @@ function extractInputSources(node, context) {
|
|
|
693
1079
|
}
|
|
694
1080
|
let source = null;
|
|
695
1081
|
if (ts.isShorthandPropertyAssignment(prop)) {
|
|
696
|
-
// { email } - could be from
|
|
1082
|
+
// { email } - could be from loop var, output var, or input
|
|
697
1083
|
const varName = prop.name.text;
|
|
698
|
-
if (context.
|
|
1084
|
+
if (context.loopVars.has(varName)) {
|
|
1085
|
+
source = { from: 'item', path: varName };
|
|
1086
|
+
}
|
|
1087
|
+
else if (context.outputVars.has(varName)) {
|
|
699
1088
|
source = { from: 'outputVar', name: varName };
|
|
700
1089
|
}
|
|
701
1090
|
else {
|
|
@@ -723,6 +1112,25 @@ function extractInputSources(node, context) {
|
|
|
723
1112
|
}
|
|
724
1113
|
return Object.keys(inputs).length > 0 ? inputs : undefined;
|
|
725
1114
|
}
|
|
1115
|
+
function inputSourceToInlineValue(source) {
|
|
1116
|
+
switch (source.from) {
|
|
1117
|
+
case 'literal':
|
|
1118
|
+
return source.value;
|
|
1119
|
+
case 'input':
|
|
1120
|
+
return { $ref: 'trigger', path: source.path };
|
|
1121
|
+
case 'outputVar':
|
|
1122
|
+
return { $ref: source.name, path: source.path };
|
|
1123
|
+
case 'item':
|
|
1124
|
+
return { $ref: '$item', path: source.path };
|
|
1125
|
+
case 'template':
|
|
1126
|
+
return {
|
|
1127
|
+
$template: {
|
|
1128
|
+
parts: source.parts,
|
|
1129
|
+
expressions: source.expressions.map(inputSourceToInlineValue),
|
|
1130
|
+
},
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
726
1134
|
/**
|
|
727
1135
|
* Extract a single input source
|
|
728
1136
|
*/
|
|
@@ -744,6 +1152,10 @@ function extractInputSource(node, context) {
|
|
|
744
1152
|
// Identifier: email, orgId
|
|
745
1153
|
if (ts.isIdentifier(node)) {
|
|
746
1154
|
const varName = node.text;
|
|
1155
|
+
// Check if it's a loop variable (from fanout)
|
|
1156
|
+
if (context.loopVars.has(varName)) {
|
|
1157
|
+
return { from: 'item', path: varName };
|
|
1158
|
+
}
|
|
747
1159
|
if (context.outputVars.has(varName)) {
|
|
748
1160
|
return { from: 'outputVar', name: varName };
|
|
749
1161
|
}
|
|
@@ -781,8 +1193,8 @@ function extractInputSource(node, context) {
|
|
|
781
1193
|
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
782
1194
|
const propName = prop.name.text;
|
|
783
1195
|
const propSource = extractInputSource(prop.initializer, context);
|
|
784
|
-
if (propSource
|
|
785
|
-
obj[propName] = propSource
|
|
1196
|
+
if (propSource) {
|
|
1197
|
+
obj[propName] = inputSourceToInlineValue(propSource);
|
|
786
1198
|
}
|
|
787
1199
|
}
|
|
788
1200
|
}
|
|
@@ -793,11 +1205,99 @@ function extractInputSource(node, context) {
|
|
|
793
1205
|
const arr = [];
|
|
794
1206
|
for (const elem of node.elements) {
|
|
795
1207
|
const elemSource = extractInputSource(elem, context);
|
|
796
|
-
if (elemSource
|
|
797
|
-
arr.push(elemSource
|
|
1208
|
+
if (elemSource) {
|
|
1209
|
+
arr.push(inputSourceToInlineValue(elemSource));
|
|
798
1210
|
}
|
|
799
1211
|
}
|
|
800
1212
|
return { from: 'literal', value: arr };
|
|
801
1213
|
}
|
|
1214
|
+
// No substitution template literal: `hello`
|
|
1215
|
+
if (ts.isNoSubstitutionTemplateLiteral(node)) {
|
|
1216
|
+
return { from: 'literal', value: node.text };
|
|
1217
|
+
}
|
|
1218
|
+
// Template expression with substitutions: `hello ${name}`
|
|
1219
|
+
if (ts.isTemplateExpression(node)) {
|
|
1220
|
+
const parts = [node.head.text];
|
|
1221
|
+
const expressions = [];
|
|
1222
|
+
for (const span of node.templateSpans) {
|
|
1223
|
+
// Extract each expression
|
|
1224
|
+
const exprSource = extractInputSource(span.expression, context);
|
|
1225
|
+
if (exprSource) {
|
|
1226
|
+
expressions.push(exprSource);
|
|
1227
|
+
}
|
|
1228
|
+
else {
|
|
1229
|
+
// Fallback: use source text as literal
|
|
1230
|
+
expressions.push({
|
|
1231
|
+
from: 'literal',
|
|
1232
|
+
value: getSourceText(span.expression),
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
parts.push(span.literal.text);
|
|
1236
|
+
}
|
|
1237
|
+
return { from: 'template', parts, expressions };
|
|
1238
|
+
}
|
|
802
1239
|
return null;
|
|
803
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Extract a literal value from an expression
|
|
1243
|
+
*/
|
|
1244
|
+
function extractLiteralValue(expr) {
|
|
1245
|
+
if (ts.isStringLiteral(expr)) {
|
|
1246
|
+
return expr.text;
|
|
1247
|
+
}
|
|
1248
|
+
if (ts.isNumericLiteral(expr)) {
|
|
1249
|
+
return Number(expr.text);
|
|
1250
|
+
}
|
|
1251
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
1252
|
+
return true;
|
|
1253
|
+
}
|
|
1254
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
1255
|
+
return false;
|
|
1256
|
+
}
|
|
1257
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
1258
|
+
return null;
|
|
1259
|
+
}
|
|
1260
|
+
// Array literal
|
|
1261
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
1262
|
+
const values = [];
|
|
1263
|
+
for (const el of expr.elements) {
|
|
1264
|
+
const v = extractLiteralValue(el);
|
|
1265
|
+
if (v === undefined)
|
|
1266
|
+
return undefined;
|
|
1267
|
+
values.push(v);
|
|
1268
|
+
}
|
|
1269
|
+
return values;
|
|
1270
|
+
}
|
|
1271
|
+
// Object literal (simple keys with literal values)
|
|
1272
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
1273
|
+
const obj = {};
|
|
1274
|
+
for (const prop of expr.properties) {
|
|
1275
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
1276
|
+
const v = extractLiteralValue(prop.initializer);
|
|
1277
|
+
if (v === undefined)
|
|
1278
|
+
return undefined;
|
|
1279
|
+
obj[prop.name.text] = v;
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
return undefined;
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return obj;
|
|
1286
|
+
}
|
|
1287
|
+
return undefined;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Infer a simple type string from a TypeScript type
|
|
1291
|
+
*/
|
|
1292
|
+
function inferSimpleType(type, checker) {
|
|
1293
|
+
const typeStr = checker.typeToString(type);
|
|
1294
|
+
if (typeStr === 'string')
|
|
1295
|
+
return 'string';
|
|
1296
|
+
if (typeStr === 'number')
|
|
1297
|
+
return 'number';
|
|
1298
|
+
if (typeStr === 'boolean')
|
|
1299
|
+
return 'boolean';
|
|
1300
|
+
if (typeStr.endsWith('[]') || typeStr.startsWith('Array<'))
|
|
1301
|
+
return 'array';
|
|
1302
|
+
return 'object';
|
|
1303
|
+
}
|