@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DSL (Domain Specific Language) workflow extraction exports
|
|
3
|
+
*/
|
|
4
|
+
export { extractDSLWorkflow } from './extract-dsl-workflow.js'
|
|
5
|
+
export {
|
|
6
|
+
deserializeDslWorkflow,
|
|
7
|
+
deserializeGraphWorkflow,
|
|
8
|
+
deserializeAllDslWorkflows,
|
|
9
|
+
} from './deserialize-dsl-workflow.js'
|
|
10
|
+
export * from './patterns.js'
|
|
11
|
+
export * from './validation.js'
|
|
@@ -42,6 +42,94 @@ export function isWorkflowSleepCall(
|
|
|
42
42
|
)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Check if a throw statement throws WorkflowCancelledException
|
|
47
|
+
* Matches: throw new WorkflowCancelledException(...) or throw WorkflowCancelledException(...)
|
|
48
|
+
*/
|
|
49
|
+
export function isThrowCancelException(node: ts.ThrowStatement): boolean {
|
|
50
|
+
const expr = node.expression
|
|
51
|
+
if (!expr) return false
|
|
52
|
+
|
|
53
|
+
// Check for: throw new WorkflowCancelledException(...)
|
|
54
|
+
if (ts.isNewExpression(expr)) {
|
|
55
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
56
|
+
return expr.expression.text === 'WorkflowCancelledException'
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check for: throw WorkflowCancelledException(...) - function call style
|
|
61
|
+
if (ts.isCallExpression(expr)) {
|
|
62
|
+
if (ts.isIdentifier(expr.expression)) {
|
|
63
|
+
return expr.expression.text === 'WorkflowCancelledException'
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Extract the reason string from a throw WorkflowCancelledException statement
|
|
72
|
+
*/
|
|
73
|
+
export function extractCancelReason(
|
|
74
|
+
node: ts.ThrowStatement,
|
|
75
|
+
checker: ts.TypeChecker
|
|
76
|
+
): string | undefined {
|
|
77
|
+
const expr = node.expression
|
|
78
|
+
if (!expr) return undefined
|
|
79
|
+
|
|
80
|
+
let args: ts.NodeArray<ts.Expression> | undefined
|
|
81
|
+
|
|
82
|
+
if (ts.isNewExpression(expr) && expr.arguments) {
|
|
83
|
+
args = expr.arguments
|
|
84
|
+
} else if (ts.isCallExpression(expr)) {
|
|
85
|
+
args = expr.arguments
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (args && args.length > 0) {
|
|
89
|
+
const firstArg = args[0]
|
|
90
|
+
if (ts.isStringLiteral(firstArg)) {
|
|
91
|
+
return firstArg.text
|
|
92
|
+
}
|
|
93
|
+
// For template literals or other expressions, return the source text
|
|
94
|
+
return firstArg.getText()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Check if a call expression is array.filter()
|
|
102
|
+
*/
|
|
103
|
+
export function isArrayFilter(node: ts.CallExpression): boolean {
|
|
104
|
+
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
105
|
+
return false
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return node.expression.name.text === 'filter'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Check if a call expression is array.some()
|
|
113
|
+
*/
|
|
114
|
+
export function isArraySome(node: ts.CallExpression): boolean {
|
|
115
|
+
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
116
|
+
return false
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return node.expression.name.text === 'some'
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if a call expression is array.every()
|
|
124
|
+
*/
|
|
125
|
+
export function isArrayEvery(node: ts.CallExpression): boolean {
|
|
126
|
+
if (!ts.isPropertyAccessExpression(node.expression)) {
|
|
127
|
+
return false
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return node.expression.name.text === 'every'
|
|
131
|
+
}
|
|
132
|
+
|
|
45
133
|
/**
|
|
46
134
|
* Check if an expression is Promise.all(array.map(...))
|
|
47
135
|
*/
|
|
@@ -118,6 +206,24 @@ export function isSequentialFanout(node: ts.ForOfStatement): boolean {
|
|
|
118
206
|
return true
|
|
119
207
|
}
|
|
120
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Extract full source path from an expression (e.g., data.memberEmails)
|
|
211
|
+
*/
|
|
212
|
+
function extractSourcePath(expr: ts.Expression): string | null {
|
|
213
|
+
if (ts.isIdentifier(expr)) {
|
|
214
|
+
return expr.text
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (ts.isPropertyAccessExpression(expr)) {
|
|
218
|
+
const base = extractSourcePath(expr.expression)
|
|
219
|
+
if (base) {
|
|
220
|
+
return `${base}.${expr.name.text}`
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
121
227
|
/**
|
|
122
228
|
* Extract the variable name from a for..of statement
|
|
123
229
|
*/
|
|
@@ -135,17 +241,8 @@ export function extractForOfVariable(
|
|
|
135
241
|
|
|
136
242
|
const itemVar = decl.name.text
|
|
137
243
|
|
|
138
|
-
// Extract source variable
|
|
139
|
-
|
|
140
|
-
if (ts.isIdentifier(node.expression)) {
|
|
141
|
-
sourceVar = node.expression.text
|
|
142
|
-
} else if (
|
|
143
|
-
ts.isPropertyAccessExpression(node.expression) &&
|
|
144
|
-
ts.isIdentifier(node.expression.expression)
|
|
145
|
-
) {
|
|
146
|
-
// Handle data.memberEmails
|
|
147
|
-
sourceVar = node.expression.expression.text
|
|
148
|
-
}
|
|
244
|
+
// Extract source variable with full path (e.g., data.memberEmails)
|
|
245
|
+
const sourceVar = extractSourcePath(node.expression)
|
|
149
246
|
|
|
150
247
|
if (!sourceVar) {
|
|
151
248
|
return null
|
|
@@ -16,8 +16,10 @@ export interface ValidationError {
|
|
|
16
16
|
* - VariableStatement (const/let declarations)
|
|
17
17
|
* - ExpressionStatement (await workflow.do, await workflow.sleep, await Promise.all)
|
|
18
18
|
* - IfStatement (branches)
|
|
19
|
+
* - SwitchStatement (switch/case)
|
|
19
20
|
* - ForOfStatement (sequential fanout)
|
|
20
21
|
* - ReturnStatement
|
|
22
|
+
* - ThrowStatement (for WorkflowCancelledException)
|
|
21
23
|
* - Block (containers)
|
|
22
24
|
*/
|
|
23
25
|
export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
|
|
@@ -29,8 +31,10 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
|
|
|
29
31
|
ts.isVariableStatement(statement) ||
|
|
30
32
|
ts.isExpressionStatement(statement) ||
|
|
31
33
|
ts.isIfStatement(statement) ||
|
|
34
|
+
ts.isSwitchStatement(statement) ||
|
|
32
35
|
ts.isForOfStatement(statement) ||
|
|
33
|
-
ts.isReturnStatement(statement)
|
|
36
|
+
ts.isReturnStatement(statement) ||
|
|
37
|
+
ts.isThrowStatement(statement)
|
|
34
38
|
) {
|
|
35
39
|
// Allowed statement type - recurse into it
|
|
36
40
|
visitNode(statement)
|
|
@@ -38,7 +42,7 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
|
|
|
38
42
|
// Unknown/disallowed statement type
|
|
39
43
|
const nodeType = ts.SyntaxKind[statement.kind]
|
|
40
44
|
errors.push({
|
|
41
|
-
message: `Statement type '${nodeType}' is not allowed in simple workflows. Allowed: const/let, if/else, for..of, return, and workflow calls. If this should be supported, please report the node type: ${nodeType}`,
|
|
45
|
+
message: `Statement type '${nodeType}' is not allowed in simple workflows. Allowed: const/let, if/else, switch/case, for..of, return, throw, and workflow calls. If this should be supported, please report the node type: ${nodeType}`,
|
|
42
46
|
node: statement,
|
|
43
47
|
})
|
|
44
48
|
}
|
|
@@ -109,7 +113,30 @@ export function validateNoDisallowedPatterns(node: ts.Node): ValidationError[] {
|
|
|
109
113
|
export function validateAwaitedCalls(node: ts.Node): ValidationError[] {
|
|
110
114
|
const errors: ValidationError[] = []
|
|
111
115
|
|
|
112
|
-
function visit(
|
|
116
|
+
function visit(
|
|
117
|
+
node: ts.Node,
|
|
118
|
+
parentIsAwait: boolean = false,
|
|
119
|
+
insidePromiseAll: boolean = false
|
|
120
|
+
) {
|
|
121
|
+
// Check if this is Promise.all(...) first, before checking for workflow calls
|
|
122
|
+
if (
|
|
123
|
+
ts.isCallExpression(node) &&
|
|
124
|
+
ts.isPropertyAccessExpression(node.expression)
|
|
125
|
+
) {
|
|
126
|
+
const propAccess = node.expression
|
|
127
|
+
if (
|
|
128
|
+
propAccess.name.text === 'all' &&
|
|
129
|
+
ts.isIdentifier(propAccess.expression) &&
|
|
130
|
+
propAccess.expression.text === 'Promise'
|
|
131
|
+
) {
|
|
132
|
+
// console.log('[DEBUG] Found Promise.all, setting insidePromiseAll=true')
|
|
133
|
+
// Visit children with insidePromiseAll = true
|
|
134
|
+
ts.forEachChild(node, (child) => visit(child, parentIsAwait, true))
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Now check for workflow calls
|
|
113
140
|
if (ts.isCallExpression(node)) {
|
|
114
141
|
if (ts.isPropertyAccessExpression(node.expression)) {
|
|
115
142
|
const propAccess = node.expression
|
|
@@ -118,7 +145,7 @@ export function validateAwaitedCalls(node: ts.Node): ValidationError[] {
|
|
|
118
145
|
ts.isIdentifier(propAccess.expression) &&
|
|
119
146
|
propAccess.expression.text === 'workflow'
|
|
120
147
|
) {
|
|
121
|
-
if (!parentIsAwait) {
|
|
148
|
+
if (!parentIsAwait && !insidePromiseAll) {
|
|
122
149
|
errors.push({
|
|
123
150
|
message: `workflow.${propAccess.name.text}() must be awaited`,
|
|
124
151
|
node,
|
|
@@ -130,10 +157,10 @@ export function validateAwaitedCalls(node: ts.Node): ValidationError[] {
|
|
|
130
157
|
}
|
|
131
158
|
|
|
132
159
|
if (ts.isAwaitExpression(node)) {
|
|
133
|
-
//
|
|
134
|
-
|
|
160
|
+
// Visit the expression itself with parentIsAwait=true
|
|
161
|
+
visit(node.expression, true, insidePromiseAll)
|
|
135
162
|
} else {
|
|
136
|
-
ts.forEachChild(node, (child) => visit(child, false))
|
|
163
|
+
ts.forEachChild(node, (child) => visit(child, false, insidePromiseAll))
|
|
137
164
|
}
|
|
138
165
|
}
|
|
139
166
|
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts DSL (Domain Specific Language) step-based format to graph node format
|
|
3
|
+
*/
|
|
4
|
+
import type { WorkflowStepMeta, WorkflowsMeta } from '@pikku/core/workflow'
|
|
5
|
+
import type {
|
|
6
|
+
SerializedGraphNode,
|
|
7
|
+
SerializedWorkflowGraph,
|
|
8
|
+
FunctionNode,
|
|
9
|
+
FlowNode,
|
|
10
|
+
DataRef,
|
|
11
|
+
} from './workflow-graph.types.js'
|
|
12
|
+
|
|
13
|
+
function makeNodeId(
|
|
14
|
+
step: WorkflowStepMeta,
|
|
15
|
+
index: number,
|
|
16
|
+
prefix: string
|
|
17
|
+
): string {
|
|
18
|
+
if ('stepName' in step && step.stepName) {
|
|
19
|
+
return step.stepName
|
|
20
|
+
}
|
|
21
|
+
return `${prefix}_${index}`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a node is a terminal flow (no next step should follow)
|
|
26
|
+
*/
|
|
27
|
+
function isTerminalFlow(node: SerializedGraphNode): boolean {
|
|
28
|
+
if ('flow' in node) {
|
|
29
|
+
// Cancel and return are terminal flows - they end execution
|
|
30
|
+
return node.flow === 'cancel' || node.flow === 'return'
|
|
31
|
+
}
|
|
32
|
+
return false
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert InputSource to DataRef
|
|
37
|
+
*/
|
|
38
|
+
function convertInputSource(source: {
|
|
39
|
+
from: string
|
|
40
|
+
path?: string
|
|
41
|
+
name?: string
|
|
42
|
+
value?: unknown
|
|
43
|
+
parts?: string[]
|
|
44
|
+
expressions?: unknown[]
|
|
45
|
+
}): unknown | DataRef {
|
|
46
|
+
if (source.from === 'literal') {
|
|
47
|
+
return source.value
|
|
48
|
+
}
|
|
49
|
+
if (source.from === 'input') {
|
|
50
|
+
return { $ref: 'trigger', path: source.path }
|
|
51
|
+
}
|
|
52
|
+
if (source.from === 'outputVar') {
|
|
53
|
+
return { $ref: source.name!, path: source.path }
|
|
54
|
+
}
|
|
55
|
+
if (source.from === 'item') {
|
|
56
|
+
return { $ref: '$item', path: source.path }
|
|
57
|
+
}
|
|
58
|
+
if (source.from === 'stateVar') {
|
|
59
|
+
return { $state: source.name!, path: source.path }
|
|
60
|
+
}
|
|
61
|
+
if (source.from === 'template') {
|
|
62
|
+
return {
|
|
63
|
+
$template: {
|
|
64
|
+
parts: source.parts,
|
|
65
|
+
expressions: source.expressions?.map((expr) =>
|
|
66
|
+
convertInputSource(expr as any)
|
|
67
|
+
),
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return source.value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Convert a single DSL step to graph node(s)
|
|
76
|
+
*/
|
|
77
|
+
function convertStepToNode(
|
|
78
|
+
step: WorkflowStepMeta,
|
|
79
|
+
index: number,
|
|
80
|
+
steps: WorkflowStepMeta[],
|
|
81
|
+
nodeIdPrefix: string = 'step'
|
|
82
|
+
): SerializedGraphNode[] {
|
|
83
|
+
const nodeId = makeNodeId(step, index, nodeIdPrefix)
|
|
84
|
+
const nextNodeId =
|
|
85
|
+
index < steps.length - 1
|
|
86
|
+
? makeNodeId(steps[index + 1], index + 1, nodeIdPrefix)
|
|
87
|
+
: undefined
|
|
88
|
+
|
|
89
|
+
switch (step.type) {
|
|
90
|
+
case 'rpc': {
|
|
91
|
+
const node: FunctionNode = {
|
|
92
|
+
nodeId,
|
|
93
|
+
rpcName: step.rpcName,
|
|
94
|
+
next: nextNodeId,
|
|
95
|
+
}
|
|
96
|
+
if (step.inputs) {
|
|
97
|
+
if (step.inputs === 'passthrough') {
|
|
98
|
+
// Entire data is passed through - store as reference to trigger
|
|
99
|
+
node.input = { $passthrough: { $ref: 'trigger' } }
|
|
100
|
+
} else {
|
|
101
|
+
node.input = {}
|
|
102
|
+
for (const [key, source] of Object.entries(step.inputs)) {
|
|
103
|
+
node.input[key] = convertInputSource(source as any)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (step.outputVar) {
|
|
108
|
+
node.outputVar = step.outputVar
|
|
109
|
+
}
|
|
110
|
+
if (step.options) {
|
|
111
|
+
node.options = {
|
|
112
|
+
retries: step.options.retries,
|
|
113
|
+
retryDelay: step.options.retryDelay?.toString(),
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return [node]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
case 'sleep': {
|
|
120
|
+
const node: FlowNode = {
|
|
121
|
+
nodeId,
|
|
122
|
+
flow: 'sleep',
|
|
123
|
+
duration: step.duration,
|
|
124
|
+
next: nextNodeId,
|
|
125
|
+
}
|
|
126
|
+
return [node]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
case 'inline': {
|
|
130
|
+
const node: FlowNode = {
|
|
131
|
+
nodeId,
|
|
132
|
+
flow: 'inline',
|
|
133
|
+
description: step.description,
|
|
134
|
+
next: nextNodeId,
|
|
135
|
+
}
|
|
136
|
+
return [node]
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case 'branch': {
|
|
140
|
+
// Convert all branch conditions (if/else-if chain)
|
|
141
|
+
const branchNodes: SerializedGraphNode[] = []
|
|
142
|
+
const branches: Array<{
|
|
143
|
+
condition: unknown
|
|
144
|
+
entry: string
|
|
145
|
+
}> = []
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < step.branches.length; i++) {
|
|
148
|
+
const branchSteps = convertStepsToNodes(
|
|
149
|
+
step.branches[i].steps,
|
|
150
|
+
`${nodeId}_branch${i}`
|
|
151
|
+
)
|
|
152
|
+
if (branchSteps.length > 0) {
|
|
153
|
+
branches.push({
|
|
154
|
+
condition: step.branches[i].condition,
|
|
155
|
+
entry: branchSteps[0].nodeId,
|
|
156
|
+
})
|
|
157
|
+
// Link last branch node back to next (unless terminal flow)
|
|
158
|
+
if (nextNodeId) {
|
|
159
|
+
const lastBranch = branchSteps[branchSteps.length - 1]
|
|
160
|
+
if (!lastBranch.next && !isTerminalFlow(lastBranch))
|
|
161
|
+
lastBranch.next = nextNodeId
|
|
162
|
+
}
|
|
163
|
+
branchNodes.push(...branchSteps)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Convert else branch
|
|
168
|
+
const elseNodes = step.elseSteps
|
|
169
|
+
? convertStepsToNodes(step.elseSteps, `${nodeId}_else`)
|
|
170
|
+
: []
|
|
171
|
+
if (elseNodes.length > 0 && nextNodeId) {
|
|
172
|
+
const lastElse = elseNodes[elseNodes.length - 1]
|
|
173
|
+
if (!lastElse.next && !isTerminalFlow(lastElse))
|
|
174
|
+
lastElse.next = nextNodeId
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const node: FlowNode = {
|
|
178
|
+
nodeId,
|
|
179
|
+
flow: 'branch',
|
|
180
|
+
branches,
|
|
181
|
+
elseEntry: elseNodes.length > 0 ? elseNodes[0].nodeId : undefined,
|
|
182
|
+
next: nextNodeId,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return [node, ...branchNodes, ...elseNodes]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
case 'switch': {
|
|
189
|
+
const caseNodes: SerializedGraphNode[] = []
|
|
190
|
+
const cases: Array<{
|
|
191
|
+
value?: unknown
|
|
192
|
+
expression?: string
|
|
193
|
+
entry: string
|
|
194
|
+
}> = []
|
|
195
|
+
|
|
196
|
+
for (let i = 0; i < step.cases.length; i++) {
|
|
197
|
+
const caseSteps = convertStepsToNodes(
|
|
198
|
+
step.cases[i].steps,
|
|
199
|
+
`${nodeId}_case${i}`
|
|
200
|
+
)
|
|
201
|
+
if (caseSteps.length > 0) {
|
|
202
|
+
cases.push({
|
|
203
|
+
value: step.cases[i].value,
|
|
204
|
+
expression: step.cases[i].expression,
|
|
205
|
+
entry: caseSteps[0].nodeId,
|
|
206
|
+
})
|
|
207
|
+
// Link last case node to next (unless terminal flow)
|
|
208
|
+
if (nextNodeId) {
|
|
209
|
+
const lastCase = caseSteps[caseSteps.length - 1]
|
|
210
|
+
if (!lastCase.next && !isTerminalFlow(lastCase))
|
|
211
|
+
lastCase.next = nextNodeId
|
|
212
|
+
}
|
|
213
|
+
caseNodes.push(...caseSteps)
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
let defaultEntry: string | undefined
|
|
218
|
+
if (step.defaultSteps) {
|
|
219
|
+
const defaultNodes = convertStepsToNodes(
|
|
220
|
+
step.defaultSteps,
|
|
221
|
+
`${nodeId}_default`
|
|
222
|
+
)
|
|
223
|
+
if (defaultNodes.length > 0) {
|
|
224
|
+
defaultEntry = defaultNodes[0].nodeId
|
|
225
|
+
// Link last default node to next (unless terminal flow)
|
|
226
|
+
if (nextNodeId) {
|
|
227
|
+
const lastDefault = defaultNodes[defaultNodes.length - 1]
|
|
228
|
+
if (!lastDefault.next && !isTerminalFlow(lastDefault))
|
|
229
|
+
lastDefault.next = nextNodeId
|
|
230
|
+
}
|
|
231
|
+
caseNodes.push(...defaultNodes)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const node: FlowNode = {
|
|
236
|
+
nodeId,
|
|
237
|
+
flow: 'switch',
|
|
238
|
+
expression: step.expression,
|
|
239
|
+
cases,
|
|
240
|
+
defaultEntry,
|
|
241
|
+
next: nextNodeId,
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return [node, ...caseNodes]
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
case 'parallel': {
|
|
248
|
+
// Convert children to nodes
|
|
249
|
+
const childNodes: SerializedGraphNode[] = []
|
|
250
|
+
const childEntries: string[] = []
|
|
251
|
+
|
|
252
|
+
for (let i = 0; i < step.children.length; i++) {
|
|
253
|
+
const childSteps = convertStepToNode(
|
|
254
|
+
step.children[i],
|
|
255
|
+
i,
|
|
256
|
+
step.children,
|
|
257
|
+
`${nodeId}_child`
|
|
258
|
+
)
|
|
259
|
+
if (childSteps.length > 0) {
|
|
260
|
+
childEntries.push(childSteps[0].nodeId)
|
|
261
|
+
childNodes.push(...childSteps)
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const node: FlowNode = {
|
|
266
|
+
nodeId,
|
|
267
|
+
flow: 'parallel',
|
|
268
|
+
children: childEntries,
|
|
269
|
+
next: nextNodeId,
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return [node, ...childNodes]
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
case 'fanout': {
|
|
276
|
+
// Convert child step
|
|
277
|
+
const childNodes = convertStepToNode(
|
|
278
|
+
step.child,
|
|
279
|
+
0,
|
|
280
|
+
[step.child],
|
|
281
|
+
`${nodeId}_item`
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
const node: FlowNode = {
|
|
285
|
+
nodeId,
|
|
286
|
+
flow: 'fanout',
|
|
287
|
+
sourceVar: step.sourceVar,
|
|
288
|
+
itemVar: step.itemVar,
|
|
289
|
+
mode: step.mode,
|
|
290
|
+
childEntry: childNodes.length > 0 ? childNodes[0].nodeId : undefined,
|
|
291
|
+
timeBetween: step.timeBetween,
|
|
292
|
+
next: nextNodeId,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return [node, ...childNodes]
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
case 'filter': {
|
|
299
|
+
const node: FlowNode = {
|
|
300
|
+
nodeId,
|
|
301
|
+
flow: 'filter',
|
|
302
|
+
sourceVar: step.sourceVar,
|
|
303
|
+
itemVar: step.itemVar,
|
|
304
|
+
condition: step.condition,
|
|
305
|
+
outputVar: step.outputVar,
|
|
306
|
+
next: nextNodeId,
|
|
307
|
+
}
|
|
308
|
+
return [node]
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
case 'arrayPredicate': {
|
|
312
|
+
const node: FlowNode = {
|
|
313
|
+
nodeId,
|
|
314
|
+
flow: 'arrayPredicate',
|
|
315
|
+
mode: step.mode,
|
|
316
|
+
sourceVar: step.sourceVar,
|
|
317
|
+
itemVar: step.itemVar,
|
|
318
|
+
condition: step.condition,
|
|
319
|
+
outputVar: step.outputVar,
|
|
320
|
+
next: nextNodeId,
|
|
321
|
+
}
|
|
322
|
+
return [node]
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
case 'return': {
|
|
326
|
+
const node: FlowNode = {
|
|
327
|
+
nodeId,
|
|
328
|
+
flow: 'return',
|
|
329
|
+
outputs: step.outputs,
|
|
330
|
+
}
|
|
331
|
+
return [node]
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
case 'cancel': {
|
|
335
|
+
const node: FlowNode = {
|
|
336
|
+
nodeId,
|
|
337
|
+
flow: 'cancel',
|
|
338
|
+
reason: step.reason,
|
|
339
|
+
}
|
|
340
|
+
return [node]
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
case 'set': {
|
|
344
|
+
const node: FlowNode = {
|
|
345
|
+
nodeId,
|
|
346
|
+
flow: 'set',
|
|
347
|
+
variable: step.variable,
|
|
348
|
+
value: step.value,
|
|
349
|
+
next: nextNodeId,
|
|
350
|
+
}
|
|
351
|
+
return [node]
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
default:
|
|
355
|
+
return []
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Convert array of steps to graph nodes
|
|
361
|
+
*/
|
|
362
|
+
function convertStepsToNodes(
|
|
363
|
+
steps: WorkflowStepMeta[],
|
|
364
|
+
nodeIdPrefix: string = 'step'
|
|
365
|
+
): SerializedGraphNode[] {
|
|
366
|
+
const allNodes: SerializedGraphNode[] = []
|
|
367
|
+
|
|
368
|
+
for (let i = 0; i < steps.length; i++) {
|
|
369
|
+
const nodes = convertStepToNode(steps[i], i, steps, nodeIdPrefix)
|
|
370
|
+
allNodes.push(...nodes)
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return allNodes
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Convert a DSL workflow to graph format
|
|
378
|
+
*/
|
|
379
|
+
export function convertDslToGraph(
|
|
380
|
+
workflowName: string,
|
|
381
|
+
meta: WorkflowsMeta[string]
|
|
382
|
+
): SerializedWorkflowGraph {
|
|
383
|
+
const nodes = convertStepsToNodes(meta.steps)
|
|
384
|
+
const nodesRecord: Record<string, SerializedGraphNode> = {}
|
|
385
|
+
|
|
386
|
+
for (const node of nodes) {
|
|
387
|
+
nodesRecord[node.nodeId] = node
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const entryNodeIds = nodes.length > 0 ? [nodes[0].nodeId] : []
|
|
391
|
+
|
|
392
|
+
// Determine source type based on dsl flag:
|
|
393
|
+
// - dsl === true: pure DSL workflow, can be serialized
|
|
394
|
+
// - dsl === false: complex workflow with inline steps, not serializable
|
|
395
|
+
const source = meta.dsl === false ? 'complex' : 'dsl'
|
|
396
|
+
|
|
397
|
+
return {
|
|
398
|
+
name: workflowName,
|
|
399
|
+
pikkuFuncId: meta.pikkuFuncId,
|
|
400
|
+
source,
|
|
401
|
+
description: meta.description,
|
|
402
|
+
tags: meta.tags,
|
|
403
|
+
context: meta.context,
|
|
404
|
+
nodes: nodesRecord,
|
|
405
|
+
entryNodeIds,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Convert all DSL workflows to graph format
|
|
411
|
+
*/
|
|
412
|
+
export function convertAllDslToGraphs(
|
|
413
|
+
workflowsMeta: WorkflowsMeta
|
|
414
|
+
): Record<string, SerializedWorkflowGraph> {
|
|
415
|
+
const result: Record<string, SerializedWorkflowGraph> = {}
|
|
416
|
+
|
|
417
|
+
for (const [name, meta] of Object.entries(workflowsMeta)) {
|
|
418
|
+
result[name] = convertDslToGraph(name, meta)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return result
|
|
422
|
+
}
|