@pikku/inspector 0.11.2 → 0.12.1
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 +36 -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 +81 -61
- package/dist/add/add-cli.d.ts +1 -1
- package/dist/add/add-cli.js +42 -19
- package/dist/add/add-file-extends-core-type.d.ts +1 -1
- package/dist/add/add-file-with-config.d.ts +1 -1
- package/dist/add/add-file-with-factory.d.ts +1 -1
- package/dist/add/add-file-with-factory.js +2 -0
- package/dist/add/add-functions.d.ts +1 -1
- package/dist/add/add-functions.js +256 -82
- package/dist/add/add-http-route.d.ts +20 -10
- package/dist/add/add-http-route.js +156 -66
- package/dist/add/add-http-routes.d.ts +5 -0
- package/dist/add/add-http-routes.js +160 -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.d.ts +1 -1
- package/dist/add/add-mcp-prompt.js +14 -9
- package/dist/add/add-mcp-resource.d.ts +1 -1
- 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.d.ts +1 -1
- package/dist/add/add-queue-worker.js +18 -12
- package/dist/add/add-rpc-invocations.d.ts +3 -3
- package/dist/add/add-rpc-invocations.js +24 -10
- package/dist/add/add-schedule.d.ts +1 -1
- 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-wire-addon.d.ts +7 -0
- package/dist/add/add-wire-addon.js +70 -0
- package/dist/add/add-workflow-graph.d.ts +3 -2
- package/dist/add/add-workflow-graph.js +143 -406
- package/dist/add/add-workflow.d.ts +1 -1
- package/dist/add/add-workflow.js +6 -4
- package/dist/error-codes.d.ts +15 -1
- package/dist/error-codes.js +20 -1
- package/dist/index.d.ts +9 -8
- package/dist/index.js +5 -4
- package/dist/inspector.d.ts +2 -2
- package/dist/inspector.js +95 -15
- 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 -50
- package/dist/utils/compute-required-schemas.d.ts +4 -0
- package/dist/utils/compute-required-schemas.js +40 -0
- package/dist/utils/contract-hashes.d.ts +52 -0
- package/dist/utils/contract-hashes.js +269 -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/does-type-extend-core-type.d.ts +1 -1
- package/dist/utils/ensure-function-metadata.d.ts +6 -3
- 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 +86 -291
- package/dist/utils/extract-services.d.ts +2 -1
- package/dist/utils/extract-services.js +25 -1
- package/dist/utils/filter-inspector-state.d.ts +1 -1
- package/dist/utils/filter-inspector-state.js +107 -23
- package/dist/utils/filter-utils.d.ts +2 -2
- package/dist/utils/get-files-and-methods.d.ts +1 -1
- package/dist/utils/get-property-value.d.ts +6 -1
- package/dist/utils/get-property-value.js +28 -3
- package/dist/utils/hash.d.ts +2 -0
- package/dist/utils/hash.js +23 -0
- package/dist/utils/middleware.d.ts +9 -32
- package/dist/utils/middleware.js +80 -66
- package/dist/utils/permissions.d.ts +4 -4
- package/dist/utils/permissions.js +10 -10
- package/dist/utils/post-process.d.ts +11 -11
- package/dist/utils/post-process.js +247 -24
- package/dist/utils/resolve-addon-package.d.ts +16 -0
- package/dist/utils/resolve-addon-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 +70 -23
- package/dist/utils/serialize-inspector-state.js +98 -22
- 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/validate-auth-sessionless.d.ts +3 -0
- package/dist/utils/validate-auth-sessionless.js +14 -0
- package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +34 -102
- package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +1 -1
- package/dist/utils/workflow/dsl/extract-dsl-workflow.js +23 -4
- package/dist/utils/workflow/graph/convert-dsl-to-graph.js +12 -10
- 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 +2 -0
- package/dist/utils/workflow/graph/index.js +2 -0
- package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +0 -8
- package/dist/utils/workflow/graph/serialize-workflow-graph.js +1 -3
- package/dist/utils/workflow/graph/workflow-graph.types.d.ts +53 -79
- package/dist/utils/workflow/graph/workflow-graph.types.js +1 -1
- package/dist/visit.d.ts +1 -1
- package/dist/visit.js +13 -6
- package/package.json +14 -4
- package/src/add/add-ai-agent.ts +468 -0
- package/src/add/add-channel.ts +103 -79
- package/src/add/add-cli.ts +68 -24
- package/src/add/add-file-extends-core-type.ts +1 -1
- package/src/add/add-file-with-config.ts +1 -1
- package/src/add/add-file-with-factory.ts +3 -1
- package/src/add/add-functions.ts +349 -103
- package/src/add/add-http-route.ts +263 -89
- package/src/add/add-http-routes.ts +229 -0
- package/src/add/add-keyed-wiring.ts +151 -0
- package/src/add/add-mcp-prompt.ts +27 -16
- package/src/add/add-mcp-resource.ts +28 -16
- package/src/add/add-middleware.ts +482 -80
- package/src/add/add-permission.ts +199 -40
- package/src/add/add-queue-worker.ts +25 -20
- package/src/add/add-rpc-invocations.ts +28 -11
- package/src/add/add-schedule.ts +17 -12
- 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-wire-addon.ts +80 -0
- package/src/add/add-workflow-graph.ts +180 -522
- package/src/add/add-workflow.ts +7 -6
- package/src/error-codes.ts +25 -1
- package/src/index.ts +23 -13
- package/src/inspector.ts +139 -19
- package/src/schema-generator.ts +1 -0
- package/src/types-map.ts +12 -1
- package/src/types.ts +199 -69
- package/src/utils/compute-required-schemas.ts +48 -0
- package/src/utils/contract-hashes.test.ts +553 -0
- package/src/utils/contract-hashes.ts +386 -0
- package/src/utils/custom-types-generator.ts +88 -0
- package/src/utils/detect-schema-vendor.ts +90 -0
- package/src/utils/does-type-extend-core-type.ts +1 -1
- package/src/utils/ensure-function-metadata.ts +325 -8
- package/src/utils/extract-function-name.ts +101 -351
- package/src/utils/extract-services.ts +35 -2
- package/src/utils/filter-inspector-state.test.ts +37 -25
- package/src/utils/filter-inspector-state.ts +146 -32
- package/src/utils/filter-utils.test.ts +1 -1
- package/src/utils/filter-utils.ts +2 -2
- package/src/utils/get-files-and-methods.ts +1 -1
- package/src/utils/get-property-value.ts +42 -4
- package/src/utils/hash.ts +26 -0
- package/src/utils/middleware.test.ts +204 -0
- package/src/utils/middleware.ts +131 -69
- package/src/utils/permissions.test.ts +35 -12
- package/src/utils/permissions.ts +12 -12
- package/src/utils/post-process.ts +306 -44
- package/src/utils/resolve-addon-package.ts +49 -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 +184 -43
- 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/validate-auth-sessionless.ts +29 -0
- package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +43 -119
- package/src/utils/workflow/dsl/extract-dsl-workflow.ts +26 -6
- package/src/utils/workflow/graph/convert-dsl-to-graph.ts +17 -10
- 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 +5 -0
- package/src/utils/workflow/graph/serialize-workflow-graph.ts +1 -8
- package/src/utils/workflow/graph/workflow-graph.types.ts +29 -78
- package/src/visit.ts +19 -7
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/add/add-forge-credential.d.ts +0 -8
- package/dist/add/add-forge-credential.js +0 -77
- package/dist/add/add-forge-node.d.ts +0 -7
- package/dist/add/add-forge-node.js +0 -77
- 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-forge-credential.ts +0 -119
- package/src/add/add-forge-node.ts +0 -132
- 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
|
@@ -1,18 +1,68 @@
|
|
|
1
1
|
import * as ts from 'typescript';
|
|
2
2
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
3
|
import { pathToRegexp } from 'path-to-regexp';
|
|
4
|
-
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
|
-
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
4
|
+
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
5
|
+
import { getPropertyAssignmentInitializer, extractTypeKeys, } from '../utils/type-utils.js';
|
|
6
6
|
import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
|
|
7
7
|
import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js';
|
|
8
8
|
import { extractWireNames } from '../utils/post-process.js';
|
|
9
9
|
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
10
10
|
import { ErrorCode } from '../error-codes.js';
|
|
11
|
+
import { validateAuthSessionless } from '../utils/validate-auth-sessionless.js';
|
|
12
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
13
|
+
/**
|
|
14
|
+
* Extract header schema reference from headers property
|
|
15
|
+
*/
|
|
16
|
+
const extractHeadersSchema = (obj, routeName, method, state, checker, logger) => {
|
|
17
|
+
const headersNode = getPropertyAssignmentInitializer(obj, 'headers', true, checker);
|
|
18
|
+
if (!headersNode || !ts.isIdentifier(headersNode))
|
|
19
|
+
return undefined;
|
|
20
|
+
// Resolve the schema reference
|
|
21
|
+
const symbol = checker.getSymbolAtLocation(headersNode);
|
|
22
|
+
if (!symbol)
|
|
23
|
+
return undefined;
|
|
24
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
25
|
+
if (!decl)
|
|
26
|
+
return undefined;
|
|
27
|
+
let sourceFile;
|
|
28
|
+
if (ts.isImportSpecifier(decl)) {
|
|
29
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
30
|
+
if (aliasedSymbol) {
|
|
31
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
|
|
32
|
+
if (aliasedDecl) {
|
|
33
|
+
sourceFile = aliasedDecl.getSourceFile().fileName;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
sourceFile = decl.getSourceFile().fileName;
|
|
45
|
+
}
|
|
46
|
+
const vendor = detectSchemaVendorOrError(headersNode, checker, logger, `Route '${routeName}' header`, sourceFile);
|
|
47
|
+
if (!vendor)
|
|
48
|
+
return undefined;
|
|
49
|
+
// Create a sanitized schema name from route and method to avoid collisions
|
|
50
|
+
const sanitizedRoute = routeName
|
|
51
|
+
.replace(/[^a-zA-Z0-9]/g, '_')
|
|
52
|
+
.replace(/^_+|_+$/g, '');
|
|
53
|
+
const schemaName = `${method.toUpperCase()}_${sanitizedRoute}_Headers`;
|
|
54
|
+
state.schemaLookup.set(schemaName, {
|
|
55
|
+
variableName: headersNode.text,
|
|
56
|
+
sourceFile,
|
|
57
|
+
vendor,
|
|
58
|
+
});
|
|
59
|
+
return schemaName;
|
|
60
|
+
};
|
|
11
61
|
/**
|
|
12
62
|
* Populate metaInputTypes for a given route based on method, input type,
|
|
13
|
-
* query and params.
|
|
63
|
+
* query and params.
|
|
14
64
|
*/
|
|
15
|
-
|
|
65
|
+
const computeInputTypes = (metaTypes, methodType, inputType, queryValues, paramsValues) => {
|
|
16
66
|
if (!inputType)
|
|
17
67
|
return;
|
|
18
68
|
metaTypes.set(inputType, {
|
|
@@ -22,96 +72,136 @@ export const getInputTypes = (metaTypes, methodType, inputType, queryValues, par
|
|
|
22
72
|
? [...new Set([...queryValues, ...paramsValues])]
|
|
23
73
|
: [],
|
|
24
74
|
});
|
|
25
|
-
return;
|
|
26
75
|
};
|
|
27
76
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
77
|
+
* Shared function to register an HTTP route in the inspector state.
|
|
78
|
+
* Used by both wireHTTP and wireHTTPRoutes.
|
|
30
79
|
*/
|
|
31
|
-
export
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const { expression, arguments: args } = node;
|
|
36
|
-
if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTP')
|
|
80
|
+
export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, basePath = '', inheritedTags = [], inheritedAuth, }) {
|
|
81
|
+
// Extract route path
|
|
82
|
+
const routePath = getPropertyValue(obj, 'route');
|
|
83
|
+
if (!routePath)
|
|
37
84
|
return;
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
85
|
+
const method = (getPropertyValue(obj, 'method') || 'get').toLowerCase();
|
|
86
|
+
const fullRoute = basePath + routePath;
|
|
87
|
+
// Extract params from route path
|
|
88
|
+
let params = [];
|
|
89
|
+
try {
|
|
90
|
+
const keys = pathToRegexp(fullRoute).keys;
|
|
91
|
+
params = keys.filter((k) => k.type === 'param').map((k) => k.name);
|
|
92
|
+
}
|
|
93
|
+
catch (e) {
|
|
94
|
+
logger.error(`Failed to parse route '${fullRoute}': ${e instanceof Error ? e.message : e}`);
|
|
41
95
|
return;
|
|
42
|
-
|
|
43
|
-
//
|
|
44
|
-
const
|
|
45
|
-
if (
|
|
96
|
+
}
|
|
97
|
+
// Get common metadata
|
|
98
|
+
const { disabled, title, tags: routeTags, summary, description, errors, } = getCommonWireMetaData(obj, 'HTTP route', fullRoute, logger);
|
|
99
|
+
if (disabled)
|
|
46
100
|
return;
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
|
|
50
|
-
const { title, tags, summary, description, errors } = getCommonWireMetaData(obj, 'HTTP route', route, logger);
|
|
101
|
+
// Merge inherited tags with route tags
|
|
102
|
+
const tags = [...inheritedTags, ...(routeTags || [])];
|
|
51
103
|
const query = getPropertyValue(obj, 'query') || [];
|
|
52
|
-
//
|
|
53
|
-
const isWorkflowTrigger = getPropertyValue(obj, 'workflow') === true;
|
|
54
|
-
if (isWorkflowTrigger) {
|
|
55
|
-
// Workflow triggers don't need func - they're handled by workflow-utils
|
|
56
|
-
// Just record the route for HTTP meta but skip function processing
|
|
57
|
-
state.http.files.add(node.getSourceFile().fileName);
|
|
58
|
-
state.http.meta[method][route] = {
|
|
59
|
-
pikkuFuncName: '', // No function - workflow handles it
|
|
60
|
-
route,
|
|
61
|
-
method: method,
|
|
62
|
-
params: params.length > 0 ? params : undefined,
|
|
63
|
-
query: query.length > 0 ? query : undefined,
|
|
64
|
-
inputTypes: undefined,
|
|
65
|
-
title,
|
|
66
|
-
summary,
|
|
67
|
-
description,
|
|
68
|
-
errors,
|
|
69
|
-
tags,
|
|
70
|
-
workflow: true,
|
|
71
|
-
};
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
// --- find the referenced function name first for filtering ---
|
|
104
|
+
// Get function reference
|
|
75
105
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
76
106
|
if (!funcInitializer) {
|
|
77
|
-
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for route '${
|
|
107
|
+
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for route '${fullRoute}'.`);
|
|
78
108
|
return;
|
|
79
109
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
110
|
+
const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
|
|
111
|
+
let funcName = extracted.pikkuFuncId;
|
|
112
|
+
if (funcName.startsWith('__temp_')) {
|
|
113
|
+
funcName = makeContextBasedId('http', method, fullRoute);
|
|
114
|
+
}
|
|
115
|
+
ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
|
|
116
|
+
// Lookup existing function metadata
|
|
84
117
|
const fnMeta = state.functions.meta[funcName];
|
|
85
118
|
if (!fnMeta) {
|
|
86
119
|
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${funcName}'.`);
|
|
87
120
|
return;
|
|
88
121
|
}
|
|
122
|
+
if (!validateAuthSessionless(logger, obj, state, funcName, `Route '${fullRoute}'`, inheritedAuth)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
89
125
|
const input = fnMeta.inputs?.[0] || null;
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
126
|
+
// Validate that route params and query params exist in function input type
|
|
127
|
+
if (params.length > 0 || query.length > 0) {
|
|
128
|
+
const inputTypes = state.typesLookup.get(funcName);
|
|
129
|
+
if (inputTypes && inputTypes.length > 0) {
|
|
130
|
+
const inputKeys = extractTypeKeys(inputTypes[0]);
|
|
131
|
+
// Check path params
|
|
132
|
+
if (params.length > 0) {
|
|
133
|
+
const missingParams = params.filter((p) => !inputKeys.includes(p));
|
|
134
|
+
if (missingParams.length > 0) {
|
|
135
|
+
logger.critical(ErrorCode.ROUTE_PARAM_MISMATCH, `Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
|
|
136
|
+
`not found in function '${funcName}' input type. ` +
|
|
137
|
+
`Input type has: [${inputKeys.join(', ')}]`);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Check query params
|
|
142
|
+
if (query.length > 0) {
|
|
143
|
+
const missingQuery = query.filter((q) => !inputKeys.includes(q));
|
|
144
|
+
if (missingQuery.length > 0) {
|
|
145
|
+
logger.critical(ErrorCode.ROUTE_QUERY_MISMATCH, `Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
|
|
146
|
+
`not found in function '${funcName}' input type. ` +
|
|
147
|
+
`Input type has: [${inputKeys.join(', ')}]`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Compute inputTypes (body/query/params)
|
|
154
|
+
computeInputTypes(state.http.metaInputTypes, method, input, query, params);
|
|
155
|
+
// Resolve middleware
|
|
156
|
+
const middleware = resolveHTTPMiddlewareFromObject(state, fullRoute, obj, tags, checker);
|
|
157
|
+
// Resolve permissions
|
|
158
|
+
const permissions = resolveHTTPPermissionsFromObject(state, fullRoute, obj, tags, checker);
|
|
159
|
+
// Track used functions/middleware/permissions for service aggregation
|
|
97
160
|
state.serviceAggregation.usedFunctions.add(funcName);
|
|
98
161
|
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
99
162
|
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
163
|
+
// Check for SSE
|
|
164
|
+
const sse = getPropertyValue(obj, 'sse') === true;
|
|
165
|
+
// Extract header schema
|
|
166
|
+
const headersSchemaName = extractHeadersSchema(obj, fullRoute, method, state, checker, logger);
|
|
167
|
+
// Record route
|
|
168
|
+
state.http.files.add(sourceFile.fileName);
|
|
169
|
+
state.http.meta[method][fullRoute] = {
|
|
170
|
+
pikkuFuncId: funcName,
|
|
171
|
+
route: fullRoute,
|
|
105
172
|
method: method,
|
|
106
173
|
params: params.length > 0 ? params : undefined,
|
|
107
174
|
query: query.length > 0 ? query : undefined,
|
|
108
|
-
inputTypes,
|
|
175
|
+
inputTypes: undefined,
|
|
109
176
|
title,
|
|
110
177
|
summary,
|
|
111
178
|
description,
|
|
112
179
|
errors,
|
|
113
|
-
tags,
|
|
180
|
+
tags: tags.length > 0 ? tags : undefined,
|
|
114
181
|
middleware,
|
|
115
182
|
permissions,
|
|
183
|
+
sse: sse ? true : undefined,
|
|
184
|
+
headersSchemaName,
|
|
185
|
+
groupBasePath: basePath || undefined,
|
|
116
186
|
};
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Process wireHTTP calls
|
|
190
|
+
*/
|
|
191
|
+
export const addHTTPRoute = (logger, node, checker, state, _options) => {
|
|
192
|
+
if (!ts.isCallExpression(node))
|
|
193
|
+
return;
|
|
194
|
+
const { expression, arguments: args } = node;
|
|
195
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTP')
|
|
196
|
+
return;
|
|
197
|
+
const firstArg = args[0];
|
|
198
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
199
|
+
return;
|
|
200
|
+
registerHTTPRoute({
|
|
201
|
+
obj: firstArg,
|
|
202
|
+
state,
|
|
203
|
+
checker,
|
|
204
|
+
logger,
|
|
205
|
+
sourceFile: node.getSourceFile(),
|
|
206
|
+
});
|
|
117
207
|
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
+
import { registerHTTPRoute } from './add-http-route.js';
|
|
4
|
+
import { resolveIdentifier } from '../utils/resolve-identifier.js';
|
|
5
|
+
/**
|
|
6
|
+
* Process wireHTTPRoutes calls
|
|
7
|
+
*/
|
|
8
|
+
export const addHTTPRoutes = (logger, node, checker, state, _options) => {
|
|
9
|
+
if (!ts.isCallExpression(node))
|
|
10
|
+
return;
|
|
11
|
+
const { expression, arguments: args } = node;
|
|
12
|
+
if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTPRoutes')
|
|
13
|
+
return;
|
|
14
|
+
const firstArg = args[0];
|
|
15
|
+
if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
|
|
16
|
+
return;
|
|
17
|
+
// Extract group config
|
|
18
|
+
const groupConfig = extractGroupConfig(firstArg);
|
|
19
|
+
// Get routes property
|
|
20
|
+
const routesProp = getPropertyAssignment(firstArg, 'routes');
|
|
21
|
+
if (!routesProp)
|
|
22
|
+
return;
|
|
23
|
+
// Process routes recursively
|
|
24
|
+
processRoutes(routesProp.initializer, groupConfig, state, checker, logger, node.getSourceFile());
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Get a property assignment from an object literal
|
|
28
|
+
*/
|
|
29
|
+
function getPropertyAssignment(obj, propName) {
|
|
30
|
+
for (const prop of obj.properties) {
|
|
31
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
32
|
+
ts.isIdentifier(prop.name) &&
|
|
33
|
+
prop.name.text === propName) {
|
|
34
|
+
return prop;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract group configuration from an object literal
|
|
41
|
+
*/
|
|
42
|
+
function extractGroupConfig(obj) {
|
|
43
|
+
const basePath = getPropertyValue(obj, 'basePath') || '';
|
|
44
|
+
const tags = getPropertyValue(obj, 'tags') || [];
|
|
45
|
+
const auth = getPropertyValue(obj, 'auth');
|
|
46
|
+
return {
|
|
47
|
+
basePath,
|
|
48
|
+
tags,
|
|
49
|
+
auth: auth === true ? true : auth === false ? false : undefined,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Merge two group configs following cascading rules
|
|
54
|
+
*/
|
|
55
|
+
function mergeConfigs(parent, child) {
|
|
56
|
+
return {
|
|
57
|
+
basePath: parent.basePath + child.basePath,
|
|
58
|
+
tags: [...parent.tags, ...child.tags],
|
|
59
|
+
auth: child.auth ?? parent.auth,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Check if a value is a route config (has method, func, and route)
|
|
64
|
+
*/
|
|
65
|
+
function isRouteConfig(obj) {
|
|
66
|
+
let hasMethod = false;
|
|
67
|
+
let hasFunc = false;
|
|
68
|
+
let hasRoute = false;
|
|
69
|
+
for (const prop of obj.properties) {
|
|
70
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
71
|
+
if (prop.name.text === 'method')
|
|
72
|
+
hasMethod = true;
|
|
73
|
+
if (prop.name.text === 'func')
|
|
74
|
+
hasFunc = true;
|
|
75
|
+
if (prop.name.text === 'route')
|
|
76
|
+
hasRoute = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return hasMethod && hasFunc && hasRoute;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if a value is a route contract (has routes property but no method/func)
|
|
83
|
+
*/
|
|
84
|
+
function isRouteContract(obj) {
|
|
85
|
+
let hasRoutes = false;
|
|
86
|
+
let hasMethod = false;
|
|
87
|
+
let hasFunc = false;
|
|
88
|
+
for (const prop of obj.properties) {
|
|
89
|
+
if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
|
|
90
|
+
if (prop.name.text === 'routes')
|
|
91
|
+
hasRoutes = true;
|
|
92
|
+
if (prop.name.text === 'method')
|
|
93
|
+
hasMethod = true;
|
|
94
|
+
if (prop.name.text === 'func')
|
|
95
|
+
hasFunc = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return hasRoutes && !hasMethod && !hasFunc;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Recursively process routes - handles nested maps, contracts, and identifiers
|
|
102
|
+
*/
|
|
103
|
+
function processRoutes(node, parentConfig, state, checker, logger, sourceFile) {
|
|
104
|
+
// Handle array of routes
|
|
105
|
+
if (ts.isArrayLiteralExpression(node)) {
|
|
106
|
+
for (const element of node.elements) {
|
|
107
|
+
if (ts.isObjectLiteralExpression(element) && isRouteConfig(element)) {
|
|
108
|
+
processRoute(element, parentConfig, state, checker, logger, sourceFile);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Handle object literal
|
|
114
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
115
|
+
// Check if this is a route config
|
|
116
|
+
if (isRouteConfig(node)) {
|
|
117
|
+
processRoute(node, parentConfig, state, checker, logger, sourceFile);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// Check if this is a route contract
|
|
121
|
+
if (isRouteContract(node)) {
|
|
122
|
+
const contractConfig = extractGroupConfig(node);
|
|
123
|
+
const mergedConfig = mergeConfigs(parentConfig, contractConfig);
|
|
124
|
+
const routesProp = getPropertyAssignment(node, 'routes');
|
|
125
|
+
if (routesProp) {
|
|
126
|
+
processRoutes(routesProp.initializer, mergedConfig, state, checker, logger, sourceFile);
|
|
127
|
+
}
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// Otherwise it's a nested map - process each property
|
|
131
|
+
for (const prop of node.properties) {
|
|
132
|
+
if (ts.isPropertyAssignment(prop)) {
|
|
133
|
+
processRoutes(prop.initializer, parentConfig, state, checker, logger, sourceFile);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Handle identifier - resolve to its definition
|
|
139
|
+
if (ts.isIdentifier(node)) {
|
|
140
|
+
const resolved = resolveIdentifier(node, checker, ['defineHTTPRoutes']);
|
|
141
|
+
if (resolved) {
|
|
142
|
+
processRoutes(resolved, parentConfig, state, checker, logger, sourceFile);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Register a single route using the shared registerHTTPRoute function
|
|
148
|
+
*/
|
|
149
|
+
function processRoute(obj, groupConfig, state, checker, logger, sourceFile) {
|
|
150
|
+
registerHTTPRoute({
|
|
151
|
+
obj,
|
|
152
|
+
state,
|
|
153
|
+
checker,
|
|
154
|
+
logger,
|
|
155
|
+
sourceFile,
|
|
156
|
+
basePath: groupConfig.basePath,
|
|
157
|
+
inheritedTags: groupConfig.tags,
|
|
158
|
+
inheritedAuth: groupConfig.auth,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AddWiring, InspectorState } from '../types.js';
|
|
2
|
+
export interface KeyedWiringConfig {
|
|
3
|
+
functionName: string;
|
|
4
|
+
idField: string;
|
|
5
|
+
label: string;
|
|
6
|
+
schemaPrefix: string;
|
|
7
|
+
getState: (state: InspectorState) => {
|
|
8
|
+
definitions: any[];
|
|
9
|
+
files: Set<string>;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare const createAddKeyedWiring: (config: KeyedWiringConfig) => AddWiring;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import * as ts from 'typescript';
|
|
2
|
+
import { getPropertyValue } from '../utils/get-property-value.js';
|
|
3
|
+
import { ErrorCode } from '../error-codes.js';
|
|
4
|
+
import { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
|
|
5
|
+
export const createAddKeyedWiring = (config) => {
|
|
6
|
+
return (logger, node, checker, state, _options) => {
|
|
7
|
+
if (!ts.isCallExpression(node)) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const args = node.arguments;
|
|
11
|
+
const firstArg = args[0];
|
|
12
|
+
const expression = node.expression;
|
|
13
|
+
if (!ts.isIdentifier(expression) ||
|
|
14
|
+
expression.text !== config.functionName) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!firstArg) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
21
|
+
const obj = firstArg;
|
|
22
|
+
const nameValue = getPropertyValue(obj, 'name');
|
|
23
|
+
const displayNameValue = getPropertyValue(obj, 'displayName');
|
|
24
|
+
const descriptionValue = getPropertyValue(obj, 'description');
|
|
25
|
+
const idValue = getPropertyValue(obj, config.idField);
|
|
26
|
+
let schemaVariableName = null;
|
|
27
|
+
let schemaSourceFile = null;
|
|
28
|
+
let schemaIdentifier = null;
|
|
29
|
+
for (const prop of obj.properties) {
|
|
30
|
+
if (ts.isPropertyAssignment(prop) &&
|
|
31
|
+
ts.isIdentifier(prop.name) &&
|
|
32
|
+
prop.name.text === 'schema') {
|
|
33
|
+
if (ts.isIdentifier(prop.initializer)) {
|
|
34
|
+
schemaVariableName = prop.initializer.text;
|
|
35
|
+
schemaIdentifier = prop.initializer;
|
|
36
|
+
const symbol = checker.getSymbolAtLocation(prop.initializer);
|
|
37
|
+
if (symbol) {
|
|
38
|
+
const decl = symbol.valueDeclaration || symbol.declarations?.[0];
|
|
39
|
+
if (decl) {
|
|
40
|
+
if (ts.isImportSpecifier(decl)) {
|
|
41
|
+
const aliasedSymbol = checker.getAliasedSymbol(symbol);
|
|
42
|
+
if (aliasedSymbol) {
|
|
43
|
+
const aliasedDecl = aliasedSymbol.valueDeclaration ||
|
|
44
|
+
aliasedSymbol.declarations?.[0];
|
|
45
|
+
if (aliasedDecl) {
|
|
46
|
+
schemaSourceFile = aliasedDecl.getSourceFile().fileName;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
schemaSourceFile = decl.getSourceFile().fileName;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!nameValue) {
|
|
60
|
+
logger.critical(ErrorCode.MISSING_NAME, `${config.label} is missing the required 'name' property.`);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!displayNameValue) {
|
|
64
|
+
logger.critical(ErrorCode.MISSING_NAME, `${config.label} '${nameValue}' is missing the required 'displayName' property.`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!idValue) {
|
|
68
|
+
logger.critical(ErrorCode.MISSING_NAME, `${config.label} '${nameValue}' is missing the required '${config.idField}' property.`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (!schemaVariableName || !schemaSourceFile || !schemaIdentifier) {
|
|
72
|
+
logger.critical(ErrorCode.MISSING_NAME, `${config.label} '${nameValue}' is missing the required 'schema' property or schema is not a variable reference.`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const sourceFile = node.getSourceFile().fileName;
|
|
76
|
+
const wiringState = config.getState(state);
|
|
77
|
+
wiringState.files.add(sourceFile);
|
|
78
|
+
const vendor = detectSchemaVendorOrError(schemaIdentifier, checker, logger, `${config.label} '${nameValue}'`, schemaSourceFile);
|
|
79
|
+
if (!vendor)
|
|
80
|
+
return;
|
|
81
|
+
const schemaLookupName = `${config.schemaPrefix}_${nameValue}`;
|
|
82
|
+
state.schemaLookup.set(schemaLookupName, {
|
|
83
|
+
variableName: schemaVariableName,
|
|
84
|
+
sourceFile: schemaSourceFile,
|
|
85
|
+
vendor,
|
|
86
|
+
});
|
|
87
|
+
wiringState.definitions.push({
|
|
88
|
+
name: nameValue,
|
|
89
|
+
displayName: displayNameValue,
|
|
90
|
+
description: descriptionValue || undefined,
|
|
91
|
+
[config.idField]: idValue,
|
|
92
|
+
schema: schemaLookupName,
|
|
93
|
+
sourceFile,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AddWiring } from '../types.js';
|
|
1
|
+
import type { AddWiring } from '../types.js';
|
|
2
2
|
export declare const addMCPPrompt: AddWiring;
|
|
@@ -2,7 +2,7 @@ import * as ts from 'typescript';
|
|
|
2
2
|
import { getPropertyValue, getCommonWireMetaData, } from '../utils/get-property-value.js';
|
|
3
3
|
import { extractWireNames } from '../utils/post-process.js';
|
|
4
4
|
import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js';
|
|
5
|
-
import { extractFunctionName } from '../utils/extract-function-name.js';
|
|
5
|
+
import { extractFunctionName, makeContextBasedId, } from '../utils/extract-function-name.js';
|
|
6
6
|
import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
|
|
7
7
|
import { resolveMiddleware } from '../utils/middleware.js';
|
|
8
8
|
import { resolvePermissions } from '../utils/permissions.js';
|
|
@@ -24,15 +24,20 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
24
24
|
if (ts.isObjectLiteralExpression(firstArg)) {
|
|
25
25
|
const obj = firstArg;
|
|
26
26
|
const nameValue = getPropertyValue(obj, 'name');
|
|
27
|
-
const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP prompt', nameValue, logger);
|
|
27
|
+
const { disabled, tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP prompt', nameValue, logger);
|
|
28
|
+
if (disabled)
|
|
29
|
+
return;
|
|
28
30
|
const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
|
|
29
31
|
if (!funcInitializer) {
|
|
30
32
|
logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP prompt '${nameValue}'.`);
|
|
31
33
|
return;
|
|
32
34
|
}
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
|
|
36
|
+
let pikkuFuncId = extracted.pikkuFuncId;
|
|
37
|
+
if (pikkuFuncId.startsWith('__temp_') && nameValue) {
|
|
38
|
+
pikkuFuncId = makeContextBasedId('mcp', 'prompt', nameValue);
|
|
39
|
+
}
|
|
40
|
+
ensureFunctionMetadata(state, pikkuFuncId, nameValue || undefined, funcInitializer, checker, extracted.isHelper);
|
|
36
41
|
if (!nameValue) {
|
|
37
42
|
logger.critical(ErrorCode.MISSING_NAME, "MCP prompt is missing the required 'name' property.");
|
|
38
43
|
return;
|
|
@@ -42,9 +47,9 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
42
47
|
return;
|
|
43
48
|
}
|
|
44
49
|
// lookup existing function metadata
|
|
45
|
-
const fnMeta = state.functions.meta[
|
|
50
|
+
const fnMeta = state.functions.meta[pikkuFuncId];
|
|
46
51
|
if (!fnMeta) {
|
|
47
|
-
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${
|
|
52
|
+
logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncId}'.`);
|
|
48
53
|
return;
|
|
49
54
|
}
|
|
50
55
|
const inputSchema = fnMeta.inputs?.[0] || null;
|
|
@@ -54,12 +59,12 @@ export const addMCPPrompt = (logger, node, checker, state, options) => {
|
|
|
54
59
|
// --- resolve permissions ---
|
|
55
60
|
const permissions = resolvePermissions(state, obj, tags, checker);
|
|
56
61
|
// --- track used functions/middleware/permissions for service aggregation ---
|
|
57
|
-
state.serviceAggregation.usedFunctions.add(
|
|
62
|
+
state.serviceAggregation.usedFunctions.add(pikkuFuncId);
|
|
58
63
|
extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
|
|
59
64
|
extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
|
|
60
65
|
state.mcpEndpoints.files.add(node.getSourceFile().fileName);
|
|
61
66
|
state.mcpEndpoints.promptsMeta[nameValue] = {
|
|
62
|
-
|
|
67
|
+
pikkuFuncId,
|
|
63
68
|
name: nameValue,
|
|
64
69
|
description,
|
|
65
70
|
summary,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { AddWiring } from '../types.js';
|
|
1
|
+
import type { AddWiring } from '../types.js';
|
|
2
2
|
export declare const addMCPResource: AddWiring;
|