@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.
Files changed (189) hide show
  1. package/CHANGELOG.md +26 -1
  2. package/OPTIMIZATION-PLAN.md +195 -0
  3. package/dist/add/add-ai-agent.d.ts +2 -0
  4. package/dist/add/add-ai-agent.js +314 -0
  5. package/dist/add/add-channel.js +69 -61
  6. package/dist/add/add-cli.js +36 -18
  7. package/dist/add/add-file-with-factory.js +2 -0
  8. package/dist/add/add-functions.js +327 -59
  9. package/dist/add/add-http-route.d.ts +19 -10
  10. package/dist/add/add-http-route.js +153 -44
  11. package/dist/add/add-http-routes.d.ts +5 -0
  12. package/dist/add/add-http-routes.js +159 -0
  13. package/dist/add/add-keyed-wiring.d.ts +12 -0
  14. package/dist/add/add-keyed-wiring.js +97 -0
  15. package/dist/add/add-mcp-prompt.js +14 -9
  16. package/dist/add/add-mcp-resource.js +14 -9
  17. package/dist/add/add-middleware.d.ts +1 -4
  18. package/dist/add/add-middleware.js +364 -79
  19. package/dist/add/add-permission.d.ts +1 -1
  20. package/dist/add/add-permission.js +152 -40
  21. package/dist/add/add-queue-worker.js +18 -12
  22. package/dist/add/add-rpc-invocations.d.ts +3 -0
  23. package/dist/add/add-rpc-invocations.js +65 -25
  24. package/dist/add/add-schedule.js +11 -5
  25. package/dist/add/add-secret.d.ts +3 -0
  26. package/dist/add/add-secret.js +82 -0
  27. package/dist/add/add-trigger.d.ts +2 -0
  28. package/dist/add/add-trigger.js +87 -0
  29. package/dist/add/add-variable.d.ts +1 -0
  30. package/dist/add/add-variable.js +8 -0
  31. package/dist/add/add-workflow-graph.d.ts +7 -0
  32. package/dist/add/add-workflow-graph.js +396 -0
  33. package/dist/add/add-workflow.js +124 -26
  34. package/dist/error-codes.d.ts +16 -1
  35. package/dist/error-codes.js +21 -1
  36. package/dist/index.d.ts +9 -5
  37. package/dist/index.js +5 -2
  38. package/dist/inspector.d.ts +1 -1
  39. package/dist/inspector.js +106 -13
  40. package/dist/schema-generator.d.ts +1 -0
  41. package/dist/schema-generator.js +1 -0
  42. package/dist/types-map.js +10 -1
  43. package/dist/types.d.ts +180 -30
  44. package/dist/utils/compute-required-schemas.d.ts +4 -0
  45. package/dist/utils/compute-required-schemas.js +41 -0
  46. package/dist/utils/contract-hashes.d.ts +35 -0
  47. package/dist/utils/contract-hashes.js +202 -0
  48. package/dist/utils/custom-types-generator.d.ts +9 -0
  49. package/dist/utils/custom-types-generator.js +71 -0
  50. package/dist/utils/detect-schema-vendor.d.ts +22 -0
  51. package/dist/utils/detect-schema-vendor.js +76 -0
  52. package/dist/utils/ensure-function-metadata.d.ts +5 -2
  53. package/dist/utils/ensure-function-metadata.js +220 -6
  54. package/dist/utils/extract-function-name.d.ts +5 -16
  55. package/dist/utils/extract-function-name.js +93 -298
  56. package/dist/utils/extract-services.d.ts +2 -1
  57. package/dist/utils/extract-services.js +25 -1
  58. package/dist/utils/filter-inspector-state.js +107 -23
  59. package/dist/utils/get-property-value.d.ts +8 -2
  60. package/dist/utils/get-property-value.js +33 -4
  61. package/dist/utils/hash.d.ts +2 -0
  62. package/dist/utils/hash.js +23 -0
  63. package/dist/utils/middleware.d.ts +7 -30
  64. package/dist/utils/middleware.js +80 -66
  65. package/dist/utils/permissions.d.ts +2 -2
  66. package/dist/utils/permissions.js +10 -10
  67. package/dist/utils/post-process.d.ts +9 -10
  68. package/dist/utils/post-process.js +231 -24
  69. package/dist/utils/resolve-external-package.d.ts +12 -0
  70. package/dist/utils/resolve-external-package.js +34 -0
  71. package/dist/utils/resolve-function-types.d.ts +6 -0
  72. package/dist/utils/resolve-function-types.js +29 -0
  73. package/dist/utils/resolve-identifier.d.ts +10 -0
  74. package/dist/utils/resolve-identifier.js +36 -0
  75. package/dist/utils/resolve-versions.d.ts +2 -0
  76. package/dist/utils/resolve-versions.js +78 -0
  77. package/dist/utils/schema-generator.d.ts +9 -0
  78. package/dist/utils/schema-generator.js +209 -0
  79. package/dist/utils/serialize-inspector-state.d.ts +73 -13
  80. package/dist/utils/serialize-inspector-state.js +102 -6
  81. package/dist/utils/serialize-mcp-json.d.ts +2 -0
  82. package/dist/utils/serialize-mcp-json.js +99 -0
  83. package/dist/utils/serialize-middleware-groups-meta.d.ts +12 -0
  84. package/dist/utils/serialize-middleware-groups-meta.js +28 -0
  85. package/dist/utils/serialize-openapi-json.d.ts +85 -0
  86. package/dist/utils/serialize-openapi-json.js +151 -0
  87. package/dist/utils/serialize-permissions-groups-meta.d.ts +6 -0
  88. package/dist/utils/serialize-permissions-groups-meta.js +31 -0
  89. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  90. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +830 -0
  91. package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
  92. package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +572 -72
  93. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  94. package/dist/utils/workflow/dsl/index.js +7 -0
  95. package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
  96. package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
  97. package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
  98. package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
  99. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  100. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +318 -0
  101. package/dist/utils/workflow/graph/finalize-workflow-wires.d.ts +3 -0
  102. package/dist/utils/workflow/graph/finalize-workflow-wires.js +276 -0
  103. package/dist/utils/workflow/graph/finalize-workflows.d.ts +2 -0
  104. package/dist/utils/workflow/graph/finalize-workflows.js +75 -0
  105. package/dist/utils/workflow/graph/index.d.ts +8 -0
  106. package/dist/utils/workflow/graph/index.js +8 -0
  107. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +35 -0
  108. package/dist/utils/workflow/graph/serialize-workflow-graph.js +150 -0
  109. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +203 -0
  110. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  111. package/dist/visit.js +13 -2
  112. package/package.json +26 -4
  113. package/src/add/add-ai-agent.ts +468 -0
  114. package/src/add/add-channel.ts +82 -79
  115. package/src/add/add-cli.ts +49 -20
  116. package/src/add/add-file-with-factory.ts +2 -0
  117. package/src/add/add-functions.ts +429 -71
  118. package/src/add/add-http-route.ts +246 -65
  119. package/src/add/add-http-routes.ts +228 -0
  120. package/src/add/add-keyed-wiring.ts +151 -0
  121. package/src/add/add-mcp-prompt.ts +26 -15
  122. package/src/add/add-mcp-resource.ts +27 -15
  123. package/src/add/add-middleware.ts +482 -80
  124. package/src/add/add-permission.ts +199 -40
  125. package/src/add/add-queue-worker.ts +24 -19
  126. package/src/add/add-rpc-invocations.ts +78 -31
  127. package/src/add/add-schedule.ts +16 -11
  128. package/src/add/add-secret.ts +140 -0
  129. package/src/add/add-trigger.ts +154 -0
  130. package/src/add/add-variable.ts +9 -0
  131. package/src/add/add-workflow-graph.ts +522 -0
  132. package/src/add/add-workflow.ts +117 -30
  133. package/src/error-codes.ts +26 -1
  134. package/src/index.ts +27 -8
  135. package/src/inspector.ts +145 -17
  136. package/src/schema-generator.ts +1 -0
  137. package/src/types-map.ts +12 -1
  138. package/src/types.ts +192 -51
  139. package/src/utils/compute-required-schemas.ts +49 -0
  140. package/src/utils/contract-hashes.test.ts +528 -0
  141. package/src/utils/contract-hashes.ts +290 -0
  142. package/src/utils/custom-types-generator.ts +88 -0
  143. package/src/utils/detect-schema-vendor.ts +90 -0
  144. package/src/utils/ensure-function-metadata.ts +324 -7
  145. package/src/utils/extract-function-name.ts +108 -358
  146. package/src/utils/extract-services.ts +35 -2
  147. package/src/utils/filter-inspector-state.test.ts +34 -20
  148. package/src/utils/filter-inspector-state.ts +140 -31
  149. package/src/utils/get-property-value.ts +50 -5
  150. package/src/utils/hash.ts +26 -0
  151. package/src/utils/middleware.test.ts +204 -0
  152. package/src/utils/middleware.ts +129 -67
  153. package/src/utils/permissions.test.ts +35 -12
  154. package/src/utils/permissions.ts +10 -10
  155. package/src/utils/post-process.ts +283 -43
  156. package/src/utils/resolve-external-package.ts +42 -0
  157. package/src/utils/resolve-function-types.ts +42 -0
  158. package/src/utils/resolve-identifier.ts +46 -0
  159. package/src/utils/resolve-versions.test.ts +249 -0
  160. package/src/utils/resolve-versions.ts +105 -0
  161. package/src/utils/schema-generator.ts +329 -0
  162. package/src/utils/serialize-inspector-state.ts +181 -20
  163. package/src/utils/serialize-mcp-json.ts +145 -0
  164. package/src/utils/serialize-middleware-groups-meta.ts +33 -0
  165. package/src/utils/serialize-openapi-json.ts +277 -0
  166. package/src/utils/serialize-permissions-groups-meta.ts +35 -0
  167. package/src/utils/test-data/inspector-state.json +69 -66
  168. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1104 -0
  169. package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +678 -85
  170. package/src/utils/workflow/dsl/index.ts +11 -0
  171. package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
  172. package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
  173. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +422 -0
  174. package/src/utils/workflow/graph/finalize-workflow-wires.ts +310 -0
  175. package/src/utils/workflow/graph/finalize-workflows.ts +100 -0
  176. package/src/utils/workflow/graph/index.ts +11 -0
  177. package/src/utils/workflow/graph/serialize-workflow-graph.ts +216 -0
  178. package/src/utils/workflow/graph/workflow-graph.types.ts +231 -0
  179. package/src/visit.ts +14 -2
  180. package/tsconfig.tsbuildinfo +1 -1
  181. package/dist/add/add-mcp-tool.d.ts +0 -2
  182. package/dist/add/add-mcp-tool.js +0 -81
  183. package/dist/utils/extract-service-metadata.d.ts +0 -19
  184. package/dist/utils/extract-service-metadata.js +0 -244
  185. package/dist/utils/write-service-metadata.d.ts +0 -13
  186. package/dist/utils/write-service-metadata.js +0 -37
  187. package/src/add/add-mcp-tool.ts +0 -141
  188. package/src/utils/extract-service-metadata.ts +0 -353
  189. package/src/utils/write-service-metadata.ts +0 -51
@@ -1,18 +1,67 @@
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 { detectSchemaVendorOrError } from '../utils/detect-schema-vendor.js';
12
+ /**
13
+ * Extract header schema reference from headers property
14
+ */
15
+ const extractHeadersSchema = (obj, routeName, method, state, checker, logger) => {
16
+ const headersNode = getPropertyAssignmentInitializer(obj, 'headers', true, checker);
17
+ if (!headersNode || !ts.isIdentifier(headersNode))
18
+ return undefined;
19
+ // Resolve the schema reference
20
+ const symbol = checker.getSymbolAtLocation(headersNode);
21
+ if (!symbol)
22
+ return undefined;
23
+ const decl = symbol.valueDeclaration || symbol.declarations?.[0];
24
+ if (!decl)
25
+ return undefined;
26
+ let sourceFile;
27
+ if (ts.isImportSpecifier(decl)) {
28
+ const aliasedSymbol = checker.getAliasedSymbol(symbol);
29
+ if (aliasedSymbol) {
30
+ const aliasedDecl = aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0];
31
+ if (aliasedDecl) {
32
+ sourceFile = aliasedDecl.getSourceFile().fileName;
33
+ }
34
+ else {
35
+ return undefined;
36
+ }
37
+ }
38
+ else {
39
+ return undefined;
40
+ }
41
+ }
42
+ else {
43
+ sourceFile = decl.getSourceFile().fileName;
44
+ }
45
+ const vendor = detectSchemaVendorOrError(headersNode, checker, logger, `Route '${routeName}' header`, sourceFile);
46
+ if (!vendor)
47
+ return undefined;
48
+ // Create a sanitized schema name from route and method to avoid collisions
49
+ const sanitizedRoute = routeName
50
+ .replace(/[^a-zA-Z0-9]/g, '_')
51
+ .replace(/^_+|_+$/g, '');
52
+ const schemaName = `${method.toUpperCase()}_${sanitizedRoute}_Headers`;
53
+ state.schemaLookup.set(schemaName, {
54
+ variableName: headersNode.text,
55
+ sourceFile,
56
+ vendor,
57
+ });
58
+ return schemaName;
59
+ };
11
60
  /**
12
61
  * Populate metaInputTypes for a given route based on method, input type,
13
- * query and params. Returns undefined (we only mutate metaTypes).
62
+ * query and params.
14
63
  */
15
- export const getInputTypes = (metaTypes, methodType, inputType, queryValues, paramsValues) => {
64
+ const computeInputTypes = (metaTypes, methodType, inputType, queryValues, paramsValues) => {
16
65
  if (!inputType)
17
66
  return;
18
67
  metaTypes.set(inputType, {
@@ -22,73 +71,133 @@ export const getInputTypes = (metaTypes, methodType, inputType, queryValues, par
22
71
  ? [...new Set([...queryValues, ...paramsValues])]
23
72
  : [],
24
73
  });
25
- return;
26
74
  };
27
75
  /**
28
- * Simplified wireHTTP: re-uses function metadata from state.functions.meta
29
- * instead of re-inferring types here.
76
+ * Shared function to register an HTTP route in the inspector state.
77
+ * Used by both wireHTTP and wireHTTPRoutes.
30
78
  */
31
- export const addHTTPRoute = (logger, node, checker, state, options) => {
32
- // only look at calls
33
- if (!ts.isCallExpression(node))
79
+ export function registerHTTPRoute({ obj, state, checker, logger, sourceFile, basePath = '', inheritedTags = [], }) {
80
+ // Extract route path
81
+ const routePath = getPropertyValue(obj, 'route');
82
+ if (!routePath)
34
83
  return;
35
- const { expression, arguments: args } = node;
36
- if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTP')
37
- return;
38
- // must pass an object literal
39
- const firstArg = args[0];
40
- if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
84
+ const method = (getPropertyValue(obj, 'method') || 'get').toLowerCase();
85
+ const fullRoute = basePath + routePath;
86
+ // Extract params from route path
87
+ let params = [];
88
+ try {
89
+ const keys = pathToRegexp(fullRoute).keys;
90
+ params = keys.filter((k) => k.type === 'param').map((k) => k.name);
91
+ }
92
+ catch (e) {
93
+ logger.error(`Failed to parse route '${fullRoute}': ${e instanceof Error ? e.message : e}`);
41
94
  return;
42
- const obj = firstArg;
43
- // --- extract HTTP metadata ---
44
- const route = getPropertyValue(obj, 'route');
45
- if (!route)
95
+ }
96
+ // Get common metadata
97
+ const { disabled, title, tags: routeTags, summary, description, errors, } = getCommonWireMetaData(obj, 'HTTP route', fullRoute, logger);
98
+ if (disabled)
46
99
  return;
47
- const keys = pathToRegexp(route).keys;
48
- const params = keys.filter((k) => k.type === 'param').map((k) => k.name);
49
- const method = getPropertyValue(obj, 'method')?.toLowerCase() || 'get';
50
- const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'HTTP route', route, logger);
100
+ // Merge inherited tags with route tags
101
+ const tags = [...inheritedTags, ...(routeTags || [])];
51
102
  const query = getPropertyValue(obj, 'query') || [];
52
- // --- find the referenced function name first for filtering ---
103
+ // Get function reference
53
104
  const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
54
105
  if (!funcInitializer) {
55
- logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for route '${route}'.`);
106
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for route '${fullRoute}'.`);
56
107
  return;
57
108
  }
58
- const funcName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
59
- // Ensure function metadata exists (creates stub for inline functions)
60
- ensureFunctionMetadata(state, funcName, route);
61
- // lookup existing function metadata
109
+ const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
110
+ let funcName = extracted.pikkuFuncId;
111
+ if (funcName.startsWith('__temp_')) {
112
+ funcName = makeContextBasedId('http', method, fullRoute);
113
+ }
114
+ ensureFunctionMetadata(state, funcName, fullRoute, funcInitializer, checker, extracted.isHelper);
115
+ // Lookup existing function metadata
62
116
  const fnMeta = state.functions.meta[funcName];
63
117
  if (!fnMeta) {
64
118
  logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${funcName}'.`);
65
119
  return;
66
120
  }
67
121
  const input = fnMeta.inputs?.[0] || null;
68
- // --- compute inputTypes (body/query/params) ---
69
- const inputTypes = getInputTypes(state.http.metaInputTypes, method, input, query, params);
70
- // --- resolve middleware ---
71
- const middleware = resolveHTTPMiddlewareFromObject(state, route, obj, tags, checker);
72
- // --- resolve permissions ---
73
- const permissions = resolveHTTPPermissionsFromObject(state, route, obj, tags, checker);
74
- // --- track used functions/middleware/permissions for service aggregation ---
122
+ // Validate that route params and query params exist in function input type
123
+ if (params.length > 0 || query.length > 0) {
124
+ const inputTypes = state.typesLookup.get(funcName);
125
+ if (inputTypes && inputTypes.length > 0) {
126
+ const inputKeys = extractTypeKeys(inputTypes[0]);
127
+ // Check path params
128
+ if (params.length > 0) {
129
+ const missingParams = params.filter((p) => !inputKeys.includes(p));
130
+ if (missingParams.length > 0) {
131
+ logger.critical(ErrorCode.ROUTE_PARAM_MISMATCH, `Route '${fullRoute}' has path parameter(s) [${missingParams.join(', ')}] ` +
132
+ `not found in function '${funcName}' input type. ` +
133
+ `Input type has: [${inputKeys.join(', ')}]`);
134
+ return;
135
+ }
136
+ }
137
+ // Check query params
138
+ if (query.length > 0) {
139
+ const missingQuery = query.filter((q) => !inputKeys.includes(q));
140
+ if (missingQuery.length > 0) {
141
+ logger.critical(ErrorCode.ROUTE_QUERY_MISMATCH, `Route '${fullRoute}' has query parameter(s) [${missingQuery.join(', ')}] ` +
142
+ `not found in function '${funcName}' input type. ` +
143
+ `Input type has: [${inputKeys.join(', ')}]`);
144
+ return;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ // Compute inputTypes (body/query/params)
150
+ computeInputTypes(state.http.metaInputTypes, method, input, query, params);
151
+ // Resolve middleware
152
+ const middleware = resolveHTTPMiddlewareFromObject(state, fullRoute, obj, tags, checker);
153
+ // Resolve permissions
154
+ const permissions = resolveHTTPPermissionsFromObject(state, fullRoute, obj, tags, checker);
155
+ // Track used functions/middleware/permissions for service aggregation
75
156
  state.serviceAggregation.usedFunctions.add(funcName);
76
157
  extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
77
158
  extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
78
- // --- record route ---
79
- state.http.files.add(node.getSourceFile().fileName);
80
- state.http.meta[method][route] = {
81
- pikkuFuncName: funcName,
82
- route,
159
+ // Check for SSE
160
+ const sse = getPropertyValue(obj, 'sse') === true;
161
+ // Extract header schema
162
+ const headersSchemaName = extractHeadersSchema(obj, fullRoute, method, state, checker, logger);
163
+ // Record route
164
+ state.http.files.add(sourceFile.fileName);
165
+ state.http.meta[method][fullRoute] = {
166
+ pikkuFuncId: funcName,
167
+ route: fullRoute,
83
168
  method: method,
84
169
  params: params.length > 0 ? params : undefined,
85
170
  query: query.length > 0 ? query : undefined,
86
- inputTypes,
171
+ inputTypes: undefined,
172
+ title,
87
173
  summary,
88
174
  description,
89
175
  errors,
90
- tags,
176
+ tags: tags.length > 0 ? tags : undefined,
91
177
  middleware,
92
178
  permissions,
179
+ sse: sse ? true : undefined,
180
+ headersSchemaName,
181
+ groupBasePath: basePath || undefined,
93
182
  };
183
+ }
184
+ /**
185
+ * Process wireHTTP calls
186
+ */
187
+ export const addHTTPRoute = (logger, node, checker, state, _options) => {
188
+ if (!ts.isCallExpression(node))
189
+ return;
190
+ const { expression, arguments: args } = node;
191
+ if (!ts.isIdentifier(expression) || expression.text !== 'wireHTTP')
192
+ return;
193
+ const firstArg = args[0];
194
+ if (!firstArg || !ts.isObjectLiteralExpression(firstArg))
195
+ return;
196
+ registerHTTPRoute({
197
+ obj: firstArg,
198
+ state,
199
+ checker,
200
+ logger,
201
+ sourceFile: node.getSourceFile(),
202
+ });
94
203
  };
@@ -0,0 +1,5 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Process wireHTTPRoutes calls
4
+ */
5
+ export declare const addHTTPRoutes: AddWiring;
@@ -0,0 +1,159 @@
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
+ });
159
+ }
@@ -0,0 +1,12 @@
1
+ import { 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
+ };
@@ -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 pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
34
- // Ensure function metadata exists (creates stub for inline functions)
35
- ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined);
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[pikkuFuncName];
50
+ const fnMeta = state.functions.meta[pikkuFuncId];
46
51
  if (!fnMeta) {
47
- logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
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(pikkuFuncName);
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
- pikkuFuncName,
67
+ pikkuFuncId,
63
68
  name: nameValue,
64
69
  description,
65
70
  summary,
@@ -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';
@@ -25,7 +25,9 @@ export const addMCPResource = (logger, node, checker, state, options) => {
25
25
  const obj = firstArg;
26
26
  const uriValue = getPropertyValue(obj, 'uri');
27
27
  const titleValue = getPropertyValue(obj, 'title');
28
- const { tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP resource', uriValue, logger);
28
+ const { disabled, tags, summary, description, errors } = getCommonWireMetaData(obj, 'MCP resource', uriValue, logger);
29
+ if (disabled)
30
+ return;
29
31
  const streamingValue = getPropertyValue(obj, 'streaming');
30
32
  if (streamingValue === true) {
31
33
  logger.warn(`MCP resource '${uriValue}' has streaming enabled, but streaming is not yet supported.`);
@@ -35,9 +37,12 @@ export const addMCPResource = (logger, node, checker, state, options) => {
35
37
  logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for MCP resource '${uriValue}'.`);
36
38
  return;
37
39
  }
38
- const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
39
- // Ensure function metadata exists (creates stub for inline functions)
40
- ensureFunctionMetadata(state, pikkuFuncName, uriValue || undefined);
40
+ const extracted = extractFunctionName(funcInitializer, checker, state.rootDir);
41
+ let pikkuFuncId = extracted.pikkuFuncId;
42
+ if (pikkuFuncId.startsWith('__temp_') && uriValue) {
43
+ pikkuFuncId = makeContextBasedId('mcp', 'resource', uriValue);
44
+ }
45
+ ensureFunctionMetadata(state, pikkuFuncId, uriValue || undefined, funcInitializer, checker, extracted.isHelper);
41
46
  if (!uriValue) {
42
47
  logger.critical(ErrorCode.MISSING_URI, "MCP resource is missing the required 'uri' property.");
43
48
  return;
@@ -51,9 +56,9 @@ export const addMCPResource = (logger, node, checker, state, options) => {
51
56
  return;
52
57
  }
53
58
  // lookup existing function metadata
54
- const fnMeta = state.functions.meta[pikkuFuncName];
59
+ const fnMeta = state.functions.meta[pikkuFuncId];
55
60
  if (!fnMeta) {
56
- logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncName}'.`);
61
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for '${pikkuFuncId}'.`);
57
62
  return;
58
63
  }
59
64
  const inputSchema = fnMeta.inputs?.[0] || null;
@@ -63,12 +68,12 @@ export const addMCPResource = (logger, node, checker, state, options) => {
63
68
  // --- resolve permissions ---
64
69
  const permissions = resolvePermissions(state, obj, tags, checker);
65
70
  // --- track used functions/middleware/permissions for service aggregation ---
66
- state.serviceAggregation.usedFunctions.add(pikkuFuncName);
71
+ state.serviceAggregation.usedFunctions.add(pikkuFuncId);
67
72
  extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
68
73
  extractWireNames(permissions).forEach((name) => state.serviceAggregation.usedPermissions.add(name));
69
74
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
70
75
  state.mcpEndpoints.resourcesMeta[uriValue] = {
71
- pikkuFuncName,
76
+ pikkuFuncId,
72
77
  uri: uriValue,
73
78
  title: titleValue,
74
79
  description,
@@ -1,5 +1,2 @@
1
- import { AddWiring } from '../types.js';
2
- /**
3
- * Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
4
- */
1
+ import type { AddWiring } from '../types.js';
5
2
  export declare const addMiddleware: AddWiring;