@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,87 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
|
+
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
4
|
+
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
5
|
+
import { resolveMiddleware } from '../utils/middleware.js';
|
|
6
|
+
import { extractWireNames } from '../utils/post-process.js';
|
|
7
|
+
import { resolveExternalPackageName } from '../utils/resolve-external-package.js';
|
|
8
|
+
import { ErrorCode } from '../error-codes.js';
|
|
9
|
+
export const addTrigger = (logger, node, checker, state, options) => {
|
|
10
|
+
if (!ts.isCallExpression(node)) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const args = node.arguments;
|
|
14
|
+
const firstArg = args[0];
|
|
15
|
+
const expression = node.expression;
|
|
16
|
+
if (!ts.isIdentifier(expression)) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (expression.text === 'wireTrigger') {
|
|
20
|
+
addWireTrigger(logger, node, checker, state, firstArg);
|
|
21
|
+
}
|
|
22
|
+
else if (expression.text === 'wireTriggerSource') {
|
|
23
|
+
addWireTriggerSource(logger, node, checker, state, options, firstArg);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const addWireTrigger = (logger, node, checker, state, firstArg) => {
|
|
27
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const obj = firstArg;
|
|
31
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
32
|
+
const { disabled, tags, summary, description, errors } = getCommonWireMetaData(obj, 'Trigger', nameValue, logger);
|
|
33
|
+
if (disabled)
|
|
34
|
+
return;
|
|
35
|
+
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
36
|
+
if (!funcInitializer) {
|
|
37
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for trigger '${nameValue}'.`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
|
|
41
|
+
let pikkuFuncId = extracted.pikkuFuncId;
|
|
42
|
+
if (pikkuFuncId.startsWith('__temp_') && nameValue) {
|
|
43
|
+
pikkuFuncId = makeContextBasedId('trigger', nameValue);
|
|
44
|
+
}
|
|
45
|
+
if (!nameValue) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// --- resolve middleware ---
|
|
49
|
+
const middleware = resolveMiddleware(state, obj, tags, checker);
|
|
50
|
+
// --- track used functions/middleware for service aggregation ---
|
|
51
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
52
|
+
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
53
|
+
state.triggers.files.add(node.getSourceFile().fileName);
|
|
54
|
+
state.triggers.meta[nameValue] = {
|
|
55
|
+
pikkuFuncId,
|
|
56
|
+
name: nameValue,
|
|
57
|
+
summary,
|
|
58
|
+
description,
|
|
59
|
+
errors,
|
|
60
|
+
tags,
|
|
61
|
+
middleware,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const addWireTriggerSource = (logger, node, checker, state, options, firstArg) => {
|
|
65
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg)) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const obj = firstArg;
|
|
69
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
70
|
+
if (!nameValue) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
74
|
+
if (!funcInitializer) {
|
|
75
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for trigger source '${nameValue}'.`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
if (ts.isIdentifier(funcInitializer)) {
|
|
79
|
+
const packageName = resolveExternalPackageName(funcInitializer, checker, options.externalPackages);
|
|
80
|
+
state.triggers.sourceMeta[nameValue] = {
|
|
81
|
+
name: nameValue,
|
|
82
|
+
pikkuFuncId: funcInitializer.text,
|
|
83
|
+
packageName: packageName || undefined,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
state.triggers.files.add(node.getSourceFile().fileName);
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const addVariable: import("../types.js").AddWiring;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createAddKeyedWiring } from './add-keyed-wiring.js';
|
|
2
|
+
export const addVariable = createAddKeyedWiring({
|
|
3
|
+
functionName: 'wireVariable',
|
|
4
|
+
idField: 'variableId',
|
|
5
|
+
label: 'Variable',
|
|
6
|
+
schemaPrefix: 'VariableSchema',
|
|
7
|
+
getState: (state) => state.variables,
|
|
8
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { AddWiring } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Inspector for pikkuWorkflowGraph() calls
|
|
4
|
+
* Detects: pikkuWorkflowGraph({ nodes: {...}, config: {...} })
|
|
5
|
+
* or: export const x = pikkuWorkflowGraph({...}) where the call is found via variable declaration
|
|
6
|
+
*/
|
|
7
|
+
export declare const addWorkflowGraph: AddWiring;
|
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { ErrorCode } from '../error-codes.js';
|
|
3
|
+
import { extractStringLiteral } from '../utils/extract-node-value.js';
|
|
4
|
+
function extractAstValue(expr, refParamName, templateParamName) {
|
|
5
|
+
if (ts.isStringLiteral(expr)) {
|
|
6
|
+
return expr.text;
|
|
7
|
+
}
|
|
8
|
+
if (ts.isNumericLiteral(expr)) {
|
|
9
|
+
return Number(expr.text);
|
|
10
|
+
}
|
|
11
|
+
if (expr.kind === ts.SyntaxKind.TrueKeyword) {
|
|
12
|
+
return true;
|
|
13
|
+
}
|
|
14
|
+
if (expr.kind === ts.SyntaxKind.FalseKeyword) {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
if (expr.kind === ts.SyntaxKind.NullKeyword) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (ts.isCallExpression(expr)) {
|
|
21
|
+
const callee = expr.expression;
|
|
22
|
+
if (ts.isIdentifier(callee)) {
|
|
23
|
+
if (callee.text === refParamName) {
|
|
24
|
+
const args = expr.arguments;
|
|
25
|
+
const nodeId = args[0] && ts.isStringLiteral(args[0]) ? args[0].text : 'unknown';
|
|
26
|
+
const path = args[1] && ts.isStringLiteral(args[1]) ? args[1].text : undefined;
|
|
27
|
+
return { $ref: nodeId, path };
|
|
28
|
+
}
|
|
29
|
+
if (templateParamName && callee.text === templateParamName) {
|
|
30
|
+
const templateStr = expr.arguments[0] && ts.isStringLiteral(expr.arguments[0])
|
|
31
|
+
? expr.arguments[0].text
|
|
32
|
+
: '';
|
|
33
|
+
const refsArg = expr.arguments[1];
|
|
34
|
+
const refs = [];
|
|
35
|
+
if (refsArg && ts.isArrayLiteralExpression(refsArg)) {
|
|
36
|
+
for (const el of refsArg.elements) {
|
|
37
|
+
const resolved = extractAstValue(el, refParamName, templateParamName);
|
|
38
|
+
if (typeof resolved === 'object' &&
|
|
39
|
+
resolved !== null &&
|
|
40
|
+
'$ref' in resolved) {
|
|
41
|
+
refs.push(resolved);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const parts = [];
|
|
46
|
+
const expressions = [];
|
|
47
|
+
const regex = /\$(\d+)/g;
|
|
48
|
+
let lastIndex = 0;
|
|
49
|
+
let match;
|
|
50
|
+
while ((match = regex.exec(templateStr)) !== null) {
|
|
51
|
+
parts.push(templateStr.slice(lastIndex, match.index));
|
|
52
|
+
const refIndex = parseInt(match[1], 10);
|
|
53
|
+
expressions.push(refs[refIndex] ?? { $ref: 'unknown' });
|
|
54
|
+
lastIndex = regex.lastIndex;
|
|
55
|
+
}
|
|
56
|
+
parts.push(templateStr.slice(lastIndex));
|
|
57
|
+
return { $template: { parts, expressions } };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (ts.isArrayLiteralExpression(expr)) {
|
|
62
|
+
return expr.elements.map((el) => extractAstValue(el, refParamName, templateParamName));
|
|
63
|
+
}
|
|
64
|
+
if (ts.isObjectLiteralExpression(expr)) {
|
|
65
|
+
const obj = {};
|
|
66
|
+
for (const prop of expr.properties) {
|
|
67
|
+
if (!ts.isPropertyAssignment(prop))
|
|
68
|
+
continue;
|
|
69
|
+
const key = ts.isIdentifier(prop.name)
|
|
70
|
+
? prop.name.text
|
|
71
|
+
: ts.isStringLiteral(prop.name)
|
|
72
|
+
? prop.name.text
|
|
73
|
+
: null;
|
|
74
|
+
if (!key)
|
|
75
|
+
continue;
|
|
76
|
+
obj[key] = extractAstValue(prop.initializer, refParamName, templateParamName);
|
|
77
|
+
}
|
|
78
|
+
return obj;
|
|
79
|
+
}
|
|
80
|
+
if (ts.isPrefixUnaryExpression(expr)) {
|
|
81
|
+
if (expr.operator === ts.SyntaxKind.MinusToken &&
|
|
82
|
+
ts.isNumericLiteral(expr.operand)) {
|
|
83
|
+
return -Number(expr.operand.text);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return undefined;
|
|
87
|
+
}
|
|
88
|
+
function extractInputMapping(node, _checker) {
|
|
89
|
+
if (!ts.isArrowFunction(node)) {
|
|
90
|
+
return {};
|
|
91
|
+
}
|
|
92
|
+
let bodyObj;
|
|
93
|
+
if (ts.isObjectLiteralExpression(node.body)) {
|
|
94
|
+
bodyObj = node.body;
|
|
95
|
+
}
|
|
96
|
+
else if (ts.isParenthesizedExpression(node.body)) {
|
|
97
|
+
if (ts.isObjectLiteralExpression(node.body.expression)) {
|
|
98
|
+
bodyObj = node.body.expression;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (ts.isBlock(node.body)) {
|
|
102
|
+
for (const stmt of node.body.statements) {
|
|
103
|
+
if (ts.isReturnStatement(stmt) && stmt.expression) {
|
|
104
|
+
if (ts.isObjectLiteralExpression(stmt.expression)) {
|
|
105
|
+
bodyObj = stmt.expression;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!bodyObj) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
const refParamName = node.parameters.length > 0 && ts.isIdentifier(node.parameters[0].name)
|
|
114
|
+
? node.parameters[0].name.text
|
|
115
|
+
: 'ref';
|
|
116
|
+
const templateParamName = node.parameters.length > 1 && ts.isIdentifier(node.parameters[1].name)
|
|
117
|
+
? node.parameters[1].name.text
|
|
118
|
+
: undefined;
|
|
119
|
+
const input = {};
|
|
120
|
+
for (const prop of bodyObj.properties) {
|
|
121
|
+
if (!ts.isPropertyAssignment(prop))
|
|
122
|
+
continue;
|
|
123
|
+
const key = ts.isIdentifier(prop.name)
|
|
124
|
+
? prop.name.text
|
|
125
|
+
: ts.isStringLiteral(prop.name)
|
|
126
|
+
? prop.name.text
|
|
127
|
+
: null;
|
|
128
|
+
if (!key)
|
|
129
|
+
continue;
|
|
130
|
+
const value = extractAstValue(prop.initializer, refParamName, templateParamName);
|
|
131
|
+
if (value !== undefined) {
|
|
132
|
+
input[key] = value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return input;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Extract next config (string, array, or record)
|
|
139
|
+
*/
|
|
140
|
+
function extractNextConfig(node, _checker) {
|
|
141
|
+
if (ts.isStringLiteral(node)) {
|
|
142
|
+
return node.text;
|
|
143
|
+
}
|
|
144
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
145
|
+
return node.elements
|
|
146
|
+
.filter(ts.isStringLiteral)
|
|
147
|
+
.map((el) => el.text);
|
|
148
|
+
}
|
|
149
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
150
|
+
const result = {};
|
|
151
|
+
for (const prop of node.properties) {
|
|
152
|
+
if (!ts.isPropertyAssignment(prop))
|
|
153
|
+
continue;
|
|
154
|
+
const key = ts.isIdentifier(prop.name)
|
|
155
|
+
? prop.name.text
|
|
156
|
+
: ts.isStringLiteral(prop.name)
|
|
157
|
+
? prop.name.text
|
|
158
|
+
: null;
|
|
159
|
+
if (!key)
|
|
160
|
+
continue;
|
|
161
|
+
if (ts.isStringLiteral(prop.initializer)) {
|
|
162
|
+
result[key] = prop.initializer.text;
|
|
163
|
+
}
|
|
164
|
+
else if (ts.isArrayLiteralExpression(prop.initializer)) {
|
|
165
|
+
result[key] = prop.initializer.elements
|
|
166
|
+
.filter(ts.isStringLiteral)
|
|
167
|
+
.map((el) => el.text);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Compute entry node IDs from graph nodes
|
|
176
|
+
*/
|
|
177
|
+
function computeEntryNodeIds(graphNodes) {
|
|
178
|
+
const hasIncomingEdge = new Set();
|
|
179
|
+
for (const node of Object.values(graphNodes)) {
|
|
180
|
+
const next = node.next;
|
|
181
|
+
if (!next)
|
|
182
|
+
continue;
|
|
183
|
+
if (typeof next === 'string') {
|
|
184
|
+
hasIncomingEdge.add(next);
|
|
185
|
+
}
|
|
186
|
+
else if (Array.isArray(next)) {
|
|
187
|
+
next.forEach((n) => hasIncomingEdge.add(n));
|
|
188
|
+
}
|
|
189
|
+
else if (typeof next === 'object') {
|
|
190
|
+
for (const targets of Object.values(next)) {
|
|
191
|
+
if (typeof targets === 'string') {
|
|
192
|
+
hasIncomingEdge.add(targets);
|
|
193
|
+
}
|
|
194
|
+
else if (Array.isArray(targets)) {
|
|
195
|
+
;
|
|
196
|
+
targets.forEach((n) => hasIncomingEdge.add(n));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return Object.keys(graphNodes).filter((nodeId) => !hasIncomingEdge.has(nodeId));
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Extract pikkuWorkflowGraph config from an object literal argument
|
|
205
|
+
*/
|
|
206
|
+
function extractWorkflowGraphConfig(configArg, checker) {
|
|
207
|
+
let name;
|
|
208
|
+
let description;
|
|
209
|
+
let tags;
|
|
210
|
+
let disabled;
|
|
211
|
+
let nodesNode;
|
|
212
|
+
let configNode;
|
|
213
|
+
for (const prop of configArg.properties) {
|
|
214
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
215
|
+
continue;
|
|
216
|
+
const propName = prop.name.text;
|
|
217
|
+
if (propName === 'name') {
|
|
218
|
+
name = extractStringLiteral(prop.initializer, checker);
|
|
219
|
+
}
|
|
220
|
+
else if (propName === 'description') {
|
|
221
|
+
description = extractStringLiteral(prop.initializer, checker);
|
|
222
|
+
}
|
|
223
|
+
else if (propName === 'disabled') {
|
|
224
|
+
if (prop.initializer.kind === ts.SyntaxKind.TrueKeyword) {
|
|
225
|
+
disabled = true;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
else if (propName === 'tags' &&
|
|
229
|
+
ts.isArrayLiteralExpression(prop.initializer)) {
|
|
230
|
+
tags = prop.initializer.elements
|
|
231
|
+
.filter(ts.isStringLiteral)
|
|
232
|
+
.map((el) => el.text);
|
|
233
|
+
}
|
|
234
|
+
else if (propName === 'nodes' &&
|
|
235
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
236
|
+
nodesNode = prop.initializer;
|
|
237
|
+
}
|
|
238
|
+
else if (propName === 'config' &&
|
|
239
|
+
ts.isObjectLiteralExpression(prop.initializer)) {
|
|
240
|
+
configNode = prop.initializer;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { name, description, tags, disabled, nodesNode, configNode };
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Extract graph nodes from the new pikkuWorkflowGraph format
|
|
247
|
+
* New format: { nodes: { entry: 'rpcName', ... }, config: { entry: { next: 'sendWelcome', ... }, ... } }
|
|
248
|
+
*/
|
|
249
|
+
function extractGraphFromNewFormat(nodesNode, configNode, checker, state) {
|
|
250
|
+
const nodes = {};
|
|
251
|
+
if (!nodesNode) {
|
|
252
|
+
return nodes;
|
|
253
|
+
}
|
|
254
|
+
// Extract node ID to RPC name mapping from 'nodes' property
|
|
255
|
+
const nodeRpcMap = {};
|
|
256
|
+
for (const prop of nodesNode.properties) {
|
|
257
|
+
if (!ts.isPropertyAssignment(prop))
|
|
258
|
+
continue;
|
|
259
|
+
const nodeId = ts.isIdentifier(prop.name)
|
|
260
|
+
? prop.name.text
|
|
261
|
+
: ts.isStringLiteral(prop.name)
|
|
262
|
+
? prop.name.text
|
|
263
|
+
: null;
|
|
264
|
+
if (!nodeId)
|
|
265
|
+
continue;
|
|
266
|
+
const rpcName = extractStringLiteral(prop.initializer, checker);
|
|
267
|
+
if (rpcName) {
|
|
268
|
+
nodeRpcMap[nodeId] = rpcName;
|
|
269
|
+
state.rpc.invokedFunctions.add(rpcName);
|
|
270
|
+
const funcFile = state.functions.files.get(rpcName);
|
|
271
|
+
if (funcFile && !state.rpc.internalFiles.has(rpcName)) {
|
|
272
|
+
state.rpc.internalFiles.set(rpcName, funcFile);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Initialize nodes with their RPC names
|
|
277
|
+
for (const [nodeId, rpcName] of Object.entries(nodeRpcMap)) {
|
|
278
|
+
nodes[nodeId] = {
|
|
279
|
+
nodeId,
|
|
280
|
+
rpcName,
|
|
281
|
+
input: {},
|
|
282
|
+
next: undefined,
|
|
283
|
+
onError: undefined,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
// Extract config for each node from 'config' property
|
|
287
|
+
if (configNode) {
|
|
288
|
+
for (const prop of configNode.properties) {
|
|
289
|
+
if (!ts.isPropertyAssignment(prop))
|
|
290
|
+
continue;
|
|
291
|
+
const nodeId = ts.isIdentifier(prop.name)
|
|
292
|
+
? prop.name.text
|
|
293
|
+
: ts.isStringLiteral(prop.name)
|
|
294
|
+
? prop.name.text
|
|
295
|
+
: null;
|
|
296
|
+
if (!nodeId || !nodes[nodeId])
|
|
297
|
+
continue;
|
|
298
|
+
if (ts.isObjectLiteralExpression(prop.initializer)) {
|
|
299
|
+
const nodeConfig = extractNodeConfigFromObject(prop.initializer, checker);
|
|
300
|
+
if (nodeConfig) {
|
|
301
|
+
nodes[nodeId].next = nodeConfig.next;
|
|
302
|
+
nodes[nodeId].onError = nodeConfig.onError;
|
|
303
|
+
nodes[nodeId].input = nodeConfig.input;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return nodes;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Extract node config (next, onError, input) from object literal
|
|
312
|
+
*/
|
|
313
|
+
function extractNodeConfigFromObject(obj, checker) {
|
|
314
|
+
let next = undefined;
|
|
315
|
+
let onError = undefined;
|
|
316
|
+
let input = {};
|
|
317
|
+
for (const prop of obj.properties) {
|
|
318
|
+
if (!ts.isPropertyAssignment(prop) || !ts.isIdentifier(prop.name))
|
|
319
|
+
continue;
|
|
320
|
+
const propName = prop.name.text;
|
|
321
|
+
if (propName === 'next') {
|
|
322
|
+
next = extractNextConfig(prop.initializer, checker);
|
|
323
|
+
}
|
|
324
|
+
else if (propName === 'onError') {
|
|
325
|
+
onError = extractNextConfig(prop.initializer, checker);
|
|
326
|
+
}
|
|
327
|
+
else if (propName === 'input') {
|
|
328
|
+
input = extractInputMapping(prop.initializer, checker);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { next, onError, input };
|
|
332
|
+
}
|
|
333
|
+
/**
|
|
334
|
+
* Inspector for pikkuWorkflowGraph() calls
|
|
335
|
+
* Detects: pikkuWorkflowGraph({ nodes: {...}, config: {...} })
|
|
336
|
+
* or: export const x = pikkuWorkflowGraph({...}) where the call is found via variable declaration
|
|
337
|
+
*/
|
|
338
|
+
export const addWorkflowGraph = (logger, node, checker, state) => {
|
|
339
|
+
if (!ts.isCallExpression(node)) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const expression = node.expression;
|
|
343
|
+
if (!ts.isIdentifier(expression) ||
|
|
344
|
+
expression.text !== 'pikkuWorkflowGraph') {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
const args = node.arguments;
|
|
348
|
+
const firstArg = args[0];
|
|
349
|
+
if (!firstArg) {
|
|
350
|
+
logger.critical(ErrorCode.MISSING_FUNC, 'pikkuWorkflowGraph requires an argument');
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
353
|
+
if (!ts.isObjectLiteralExpression(firstArg)) {
|
|
354
|
+
logger.critical(ErrorCode.MISSING_FUNC, 'pikkuWorkflowGraph requires an object argument');
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
const graphConfig = extractWorkflowGraphConfig(firstArg, checker);
|
|
358
|
+
if (!graphConfig) {
|
|
359
|
+
logger.critical(ErrorCode.MISSING_NAME, 'pikkuWorkflowGraph: failed to extract config');
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// Resolve exportedName from variable declaration if this is `export const x = pikkuWorkflowGraph({...})`
|
|
363
|
+
const parent = node.parent;
|
|
364
|
+
if (!graphConfig.exportedName &&
|
|
365
|
+
parent &&
|
|
366
|
+
ts.isVariableDeclaration(parent) &&
|
|
367
|
+
ts.isIdentifier(parent.name)) {
|
|
368
|
+
graphConfig.exportedName = parent.name.text;
|
|
369
|
+
}
|
|
370
|
+
const workflowName = graphConfig.name || graphConfig.exportedName;
|
|
371
|
+
if (!workflowName) {
|
|
372
|
+
logger.critical(ErrorCode.MISSING_NAME, 'pikkuWorkflowGraph requires a name property or exported variable name');
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
let graphNodes = {};
|
|
376
|
+
if (graphConfig.nodesNode) {
|
|
377
|
+
graphNodes = extractGraphFromNewFormat(graphConfig.nodesNode, graphConfig.configNode, checker, state);
|
|
378
|
+
}
|
|
379
|
+
const entryNodeIds = computeEntryNodeIds(graphNodes);
|
|
380
|
+
const serialized = {
|
|
381
|
+
name: workflowName,
|
|
382
|
+
pikkuFuncId: workflowName,
|
|
383
|
+
source: 'graph',
|
|
384
|
+
description: graphConfig.description,
|
|
385
|
+
tags: graphConfig.tags,
|
|
386
|
+
nodes: graphNodes,
|
|
387
|
+
entryNodeIds,
|
|
388
|
+
};
|
|
389
|
+
if (graphConfig.disabled)
|
|
390
|
+
return;
|
|
391
|
+
state.workflows.graphMeta[workflowName] = serialized;
|
|
392
|
+
state.workflows.graphFiles.set(workflowName, {
|
|
393
|
+
path: node.getSourceFile().fileName,
|
|
394
|
+
exportedName: graphConfig.exportedName || workflowName,
|
|
395
|
+
});
|
|
396
|
+
};
|