@pikku/inspector 0.9.5 → 0.10.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 (133) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/add/add-channel.d.ts +17 -0
  3. package/dist/{add-channel.js → add/add-channel.js} +60 -34
  4. package/dist/add/add-cli.d.ts +9 -0
  5. package/dist/add/add-cli.js +566 -0
  6. package/dist/{add-file-extends-core-type.d.ts → add/add-file-extends-core-type.d.ts} +2 -2
  7. package/dist/{add-file-extends-core-type.js → add/add-file-extends-core-type.js} +17 -4
  8. package/dist/{add-file-with-config.d.ts → add/add-file-with-config.d.ts} +1 -1
  9. package/dist/{add-file-with-config.js → add/add-file-with-config.js} +1 -1
  10. package/dist/{add-file-with-factory.d.ts → add/add-file-with-factory.d.ts} +2 -2
  11. package/dist/{add-file-with-factory.js → add/add-file-with-factory.js} +38 -5
  12. package/dist/add/add-functions.d.ts +6 -0
  13. package/dist/{add-functions.js → add/add-functions.js} +77 -10
  14. package/dist/{add-http-route.d.ts → add/add-http-route.d.ts} +2 -3
  15. package/dist/{add-http-route.js → add/add-http-route.js} +26 -13
  16. package/dist/add/add-mcp-prompt.d.ts +2 -0
  17. package/dist/add/add-mcp-prompt.js +74 -0
  18. package/dist/add/add-mcp-resource.d.ts +2 -0
  19. package/dist/add/add-mcp-resource.js +84 -0
  20. package/dist/add/add-mcp-tool.d.ts +2 -0
  21. package/dist/add/add-mcp-tool.js +80 -0
  22. package/dist/add/add-middleware.d.ts +5 -0
  23. package/dist/add/add-middleware.js +290 -0
  24. package/dist/add/add-permission.d.ts +5 -0
  25. package/dist/add/add-permission.js +292 -0
  26. package/dist/add/add-queue-worker.d.ts +2 -0
  27. package/dist/add/add-queue-worker.js +52 -0
  28. package/dist/{add-rpc-invocations.d.ts → add/add-rpc-invocations.d.ts} +1 -1
  29. package/dist/add/add-schedule.d.ts +2 -0
  30. package/dist/{add-schedule.js → add/add-schedule.js} +16 -11
  31. package/dist/error-codes.d.ts +35 -0
  32. package/dist/error-codes.js +40 -0
  33. package/dist/index.d.ts +6 -0
  34. package/dist/index.js +4 -0
  35. package/dist/inspector.d.ts +2 -3
  36. package/dist/inspector.js +38 -8
  37. package/dist/types.d.ts +108 -1
  38. package/dist/utils/ensure-function-metadata.d.ts +6 -0
  39. package/dist/utils/ensure-function-metadata.js +18 -0
  40. package/dist/utils/extract-function-name.d.ts +31 -0
  41. package/dist/{utils.js → utils/extract-function-name.js} +35 -149
  42. package/dist/utils/extract-services.d.ts +6 -0
  43. package/dist/utils/extract-services.js +29 -0
  44. package/dist/utils/filter-inspector-state.d.ts +6 -0
  45. package/dist/utils/filter-inspector-state.js +382 -0
  46. package/dist/utils/filter-utils.d.ts +19 -0
  47. package/dist/utils/filter-utils.js +109 -0
  48. package/dist/utils/find-root-dir.d.ts +23 -0
  49. package/dist/utils/find-root-dir.js +55 -0
  50. package/dist/utils/get-files-and-methods.d.ts +22 -0
  51. package/dist/utils/get-files-and-methods.js +61 -0
  52. package/dist/utils/get-property-value.d.ts +12 -0
  53. package/dist/{get-property-value.js → utils/get-property-value.js} +20 -0
  54. package/dist/utils/middleware.d.ts +39 -0
  55. package/dist/utils/middleware.js +157 -0
  56. package/dist/utils/permissions.d.ts +43 -0
  57. package/dist/utils/permissions.js +178 -0
  58. package/dist/utils/post-process.d.ts +16 -0
  59. package/dist/utils/post-process.js +132 -0
  60. package/dist/utils/serialize-inspector-state.d.ts +179 -0
  61. package/dist/utils/serialize-inspector-state.js +170 -0
  62. package/dist/utils/type-utils.d.ts +3 -0
  63. package/dist/utils/type-utils.js +50 -0
  64. package/dist/visit.d.ts +3 -3
  65. package/dist/visit.js +35 -31
  66. package/package.json +5 -6
  67. package/src/{add-channel.ts → add/add-channel.ts} +108 -56
  68. package/src/add/add-cli.ts +822 -0
  69. package/src/{add-file-extends-core-type.ts → add/add-file-extends-core-type.ts} +23 -5
  70. package/src/{add-file-with-config.ts → add/add-file-with-config.ts} +2 -2
  71. package/src/{add-file-with-factory.ts → add/add-file-with-factory.ts} +49 -6
  72. package/src/{add-functions.ts → add/add-functions.ts} +89 -19
  73. package/src/{add-http-route.ts → add/add-http-route.ts} +66 -32
  74. package/src/add/add-mcp-prompt.ts +128 -0
  75. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  76. package/src/add/add-mcp-resource.ts +145 -0
  77. package/src/add/add-mcp-resource.ts.tmp +0 -0
  78. package/src/add/add-mcp-tool.ts +137 -0
  79. package/src/add/add-middleware.ts +385 -0
  80. package/src/add/add-permission.ts +391 -0
  81. package/src/add/add-queue-worker.ts +92 -0
  82. package/src/{add-rpc-invocations.ts → add/add-rpc-invocations.ts} +1 -1
  83. package/src/{add-schedule.ts → add/add-schedule.ts} +30 -28
  84. package/src/error-codes.ts +43 -0
  85. package/src/index.ts +12 -0
  86. package/src/inspector.ts +41 -17
  87. package/src/types.ts +128 -1
  88. package/src/utils/ensure-function-metadata.ts +24 -0
  89. package/src/{utils.ts → utils/extract-function-name.ts} +44 -206
  90. package/src/utils/extract-services.ts +35 -0
  91. package/src/utils/filter-inspector-state.test.ts +1433 -0
  92. package/src/utils/filter-inspector-state.ts +526 -0
  93. package/src/{utils.test.ts → utils/filter-utils.test.ts} +351 -2
  94. package/src/utils/filter-utils.ts +152 -0
  95. package/src/utils/find-root-dir.ts +68 -0
  96. package/src/utils/get-files-and-methods.ts +151 -0
  97. package/src/{get-property-value.ts → utils/get-property-value.ts} +27 -0
  98. package/src/utils/middleware.ts +241 -0
  99. package/src/utils/permissions.test.ts +327 -0
  100. package/src/utils/permissions.ts +262 -0
  101. package/src/utils/post-process.ts +178 -0
  102. package/src/utils/serialize-inspector-state.ts +375 -0
  103. package/src/utils/test-data/inspector-state.json +1680 -0
  104. package/src/utils/type-utils.ts +74 -0
  105. package/src/visit.ts +50 -34
  106. package/tsconfig.tsbuildinfo +1 -1
  107. package/dist/add-channel.d.ts +0 -13
  108. package/dist/add-functions.d.ts +0 -7
  109. package/dist/add-mcp-prompt.d.ts +0 -3
  110. package/dist/add-mcp-prompt.js +0 -61
  111. package/dist/add-mcp-resource.d.ts +0 -3
  112. package/dist/add-mcp-resource.js +0 -68
  113. package/dist/add-mcp-tool.d.ts +0 -3
  114. package/dist/add-mcp-tool.js +0 -64
  115. package/dist/add-middleware.d.ts +0 -7
  116. package/dist/add-middleware.js +0 -35
  117. package/dist/add-permission.d.ts +0 -7
  118. package/dist/add-permission.js +0 -35
  119. package/dist/add-queue-worker.d.ts +0 -3
  120. package/dist/add-queue-worker.js +0 -48
  121. package/dist/add-schedule.d.ts +0 -3
  122. package/dist/get-property-value.d.ts +0 -3
  123. package/dist/utils.d.ts +0 -39
  124. package/src/add-mcp-prompt.ts +0 -104
  125. package/src/add-mcp-resource.ts +0 -116
  126. package/src/add-mcp-tool.ts +0 -107
  127. package/src/add-middleware.ts +0 -51
  128. package/src/add-permission.ts +0 -53
  129. package/src/add-queue-worker.ts +0 -92
  130. /package/dist/{add-rpc-invocations.js → add/add-rpc-invocations.js} +0 -0
  131. /package/dist/{does-type-extend-core-type.d.ts → utils/does-type-extend-core-type.d.ts} +0 -0
  132. /package/dist/{does-type-extend-core-type.js → utils/does-type-extend-core-type.js} +0 -0
  133. /package/src/{does-type-extend-core-type.ts → utils/does-type-extend-core-type.ts} +0 -0
@@ -1,11 +1,12 @@
1
1
  import * as ts from 'typescript'
2
- import { PathToNameAndType } from './types.js'
2
+ import { PathToNameAndType, InspectorState } from '../types.js'
3
3
 
4
4
  export const addFileExtendsCoreType = (
5
5
  node: ts.Node,
6
6
  checker: ts.TypeChecker,
7
7
  methods: PathToNameAndType,
8
- expectedTypeName: string
8
+ expectedTypeName: string,
9
+ state?: InspectorState
9
10
  ) => {
10
11
  if (ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node)) {
11
12
  const fileName = node.getSourceFile().fileName
@@ -32,13 +33,30 @@ export const addFileExtendsCoreType = (
32
33
  extendedTypeDeclarationPath = sourceFile.fileName // Get the path of the file where the extended type was declared
33
34
  }
34
35
 
35
- const variables = methods[fileName] || []
36
+ const variables = methods.get(fileName) || []
37
+
38
+ if (!typeName) {
39
+ throw new Error(
40
+ `Found anonymous ${ts.isClassDeclaration(node) ? 'class' : 'interface'} extending ${expectedTypeName} in ${fileName}. ` +
41
+ `Classes and interfaces that extend core types must have a name.`
42
+ )
43
+ }
36
44
  variables.push({
37
- variable: undefined,
45
+ variable: typeName,
38
46
  type: typeName,
39
47
  typePath: extendedTypeDeclarationPath,
40
48
  })
41
- methods[fileName] = variables
49
+ methods.set(fileName, variables)
50
+
51
+ // Store the type in typesLookup if state is provided
52
+ if (state && node.name) {
53
+ const symbol = checker.getSymbolAtLocation(node.name)
54
+ if (symbol) {
55
+ const declaredType = checker.getDeclaredTypeOfSymbol(symbol)
56
+ // Use the type name as the key in typesLookup
57
+ state.typesLookup.set(typeName, [declaredType])
58
+ }
59
+ }
42
60
  }
43
61
  }
44
62
  }
@@ -1,6 +1,6 @@
1
1
  import * as ts from 'typescript'
2
- import { doesTypeExtendsCore } from './does-type-extend-core-type.js'
3
- import { PathToNameAndType } from './types.js'
2
+ import { doesTypeExtendsCore } from '../utils/does-type-extend-core-type.js'
3
+ import { PathToNameAndType } from '../types.js'
4
4
 
5
5
  export const addFileWithConfig = (
6
6
  node: ts.Node,
@@ -1,11 +1,13 @@
1
1
  import * as ts from 'typescript'
2
- import { PathToNameAndType } from './types.js'
2
+ import { PathToNameAndType, InspectorState } from '../types.js'
3
+ import { extractServicesFromFunction } from '../utils/extract-services.js'
3
4
 
4
5
  export const addFileWithFactory = (
5
6
  node: ts.Node,
6
7
  checker: ts.TypeChecker,
7
8
  methods: PathToNameAndType = new Map(),
8
- expectedTypeName: string
9
+ expectedTypeName: string,
10
+ state?: InspectorState
9
11
  ) => {
10
12
  if (ts.isVariableDeclaration(node)) {
11
13
  const fileName = node.getSourceFile().fileName
@@ -30,13 +32,32 @@ export const addFileWithFactory = (
30
32
  typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
31
33
  }
32
34
 
33
- const variables = methods[fileName] || []
35
+ const variables = methods.get(fileName) || []
34
36
  variables.push({
35
37
  variable: variableName,
36
38
  type: typeNameNode.getText(),
37
39
  typePath: typeDeclarationPath,
38
40
  })
39
- methods[fileName] = variables
41
+ methods.set(fileName, variables)
42
+
43
+ // Extract singleton services for CreateSessionServices factories
44
+ if (
45
+ expectedTypeName === 'CreateSessionServices' &&
46
+ state &&
47
+ node.initializer
48
+ ) {
49
+ let functionNode: ts.ArrowFunction | ts.FunctionExpression | undefined
50
+ if (ts.isArrowFunction(node.initializer)) {
51
+ functionNode = node.initializer
52
+ } else if (ts.isFunctionExpression(node.initializer)) {
53
+ functionNode = node.initializer
54
+ }
55
+
56
+ if (functionNode) {
57
+ const servicesMeta = extractServicesFromFunction(functionNode)
58
+ state.sessionServicesMeta.set(variableName, servicesMeta.services)
59
+ }
60
+ }
40
61
  }
41
62
 
42
63
  // Handle qualified type names if necessary
@@ -51,13 +72,35 @@ export const addFileWithFactory = (
51
72
  typeDeclarationPath = sourceFile.fileName // Get the path of the file where the type was declared
52
73
  }
53
74
 
54
- const variables = methods[fileName] || []
75
+ const variables = methods.get(fileName) || []
55
76
  variables.push({
56
77
  variable: variableName,
57
78
  type: typeNameNode.getText(),
58
79
  typePath: typeDeclarationPath,
59
80
  })
60
- methods[fileName] = variables
81
+ methods.set(fileName, variables)
82
+
83
+ // Extract singleton services for CreateSessionServices factories
84
+ if (
85
+ expectedTypeName === 'CreateSessionServices' &&
86
+ state &&
87
+ node.initializer
88
+ ) {
89
+ let functionNode:
90
+ | ts.ArrowFunction
91
+ | ts.FunctionExpression
92
+ | undefined
93
+ if (ts.isArrowFunction(node.initializer)) {
94
+ functionNode = node.initializer
95
+ } else if (ts.isFunctionExpression(node.initializer)) {
96
+ functionNode = node.initializer
97
+ }
98
+
99
+ if (functionNode) {
100
+ const servicesMeta = extractServicesFromFunction(functionNode)
101
+ state.sessionServicesMeta.set(variableName, servicesMeta.services)
102
+ }
103
+ }
61
104
  }
62
105
  }
63
106
  }
@@ -1,12 +1,11 @@
1
1
  import * as ts from 'typescript'
2
- import { InspectorLogger, InspectorState } from './types.js'
3
- import { TypesMap } from './types-map.js'
4
- import {
5
- extractFunctionName,
6
- getPropertyAssignmentInitializer,
7
- } from './utils.js'
2
+ import { AddWiring } from '../types.js'
3
+ import { TypesMap } from '../types-map.js'
4
+ import { extractFunctionName } from '../utils/extract-function-name.js'
5
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
8
6
  import { FunctionServicesMeta, PikkuDocs } from '@pikku/core'
9
- import { getPropertyValue } from './get-property-value.js'
7
+ import { getPropertyValue } from '../utils/get-property-value.js'
8
+ import { resolveMiddleware } from '../utils/middleware.js'
10
9
 
11
10
  const isValidVariableName = (name: string) => {
12
11
  const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
@@ -28,7 +27,8 @@ const nullifyTypes = (type: string | null) => {
28
27
  const resolveTypeImports = (
29
28
  type: ts.Type,
30
29
  resolvedTypes: TypesMap,
31
- isCustom: boolean
30
+ isCustom: boolean,
31
+ checker: ts.TypeChecker
32
32
  ): string[] => {
33
33
  const types: string[] = []
34
34
 
@@ -43,10 +43,14 @@ const resolveTypeImports = (
43
43
  const path = sourceFile.fileName
44
44
 
45
45
  // Skip built-in utility types or TypeScript lib types
46
+ // Skip enum members (but not the enum type itself)
47
+ const isEnumMember = declaration && ts.isEnumMember(declaration)
48
+
46
49
  if (
47
50
  !path.includes('node_modules/typescript') &&
48
51
  symbol.getName() !== '__type' &&
49
- !isPrimitiveType(currentType)
52
+ !isPrimitiveType(currentType) &&
53
+ !isEnumMember
50
54
  ) {
51
55
  const originalName = symbol.getName()
52
56
  // Check if the type is already in the map
@@ -103,6 +107,32 @@ const resolveTypeImports = (
103
107
  const typeRef = currentType as ts.TypeReference
104
108
  typeRef.typeArguments?.forEach(visitType)
105
109
  }
110
+
111
+ // Handle anonymous object types with enum properties (e.g., { userType: UserType })
112
+ // Only traverse into enum property types to avoid over-importing other named types
113
+ if (currentType.flags & ts.TypeFlags.Object) {
114
+ const objectType = currentType as ts.ObjectType
115
+ const typeSymbol = objectType.getSymbol()
116
+
117
+ // Only traverse properties for anonymous object types (no symbol or __type symbol)
118
+ // Skip named types, interfaces, and enums to avoid over-importing
119
+ const isAnonymousObject = !typeSymbol || typeSymbol.getName() === '__type'
120
+
121
+ if (isAnonymousObject) {
122
+ const properties = objectType.getProperties()
123
+ for (const prop of properties) {
124
+ if (prop.valueDeclaration) {
125
+ const propType = checker.getTypeOfSymbolAtLocation(
126
+ prop,
127
+ prop.valueDeclaration
128
+ )
129
+ if (propType) {
130
+ visitType(propType)
131
+ }
132
+ }
133
+ }
134
+ }
135
+ }
106
136
  }
107
137
 
108
138
  visitType(type)
@@ -171,7 +201,7 @@ const getNamesAndTypes = (
171
201
 
172
202
  // record the alias in your TypesMap
173
203
  const references = rawTypes
174
- .map((t) => resolveTypeImports(t, typesMap, true))
204
+ .map((t) => resolveTypeImports(t, typesMap, true, checker))
175
205
  .flat()
176
206
 
177
207
  typesMap.addCustomType(aliasName, aliasType, references)
@@ -193,7 +223,7 @@ const getNamesAndTypes = (
193
223
  return name
194
224
  }
195
225
  // non-primitive: import/alias it inline
196
- return resolveTypeImports(t, typesMap, false)
226
+ return resolveTypeImports(t, typesMap, false, checker)
197
227
  })
198
228
  .flat()
199
229
 
@@ -247,12 +277,7 @@ function unwrapPromise(checker: ts.TypeChecker, type: ts.Type): ts.Type {
247
277
  * Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
248
278
  * then push into state.functions.meta.
249
279
  */
250
- export function addFunctions(
251
- node: ts.Node,
252
- checker: ts.TypeChecker,
253
- state: InspectorState,
254
- logger: InspectorLogger
255
- ) {
280
+ export const addFunctions: AddWiring = (logger, node, checker, state) => {
256
281
  if (!ts.isCallExpression(node)) return
257
282
 
258
283
  const { expression, arguments: args, typeArguments } = node
@@ -276,11 +301,12 @@ export function addFunctions(
276
301
  if (args.length === 0) return
277
302
 
278
303
  const { pikkuFuncName, name, explicitName, exportedName } =
279
- extractFunctionName(node, checker)
304
+ extractFunctionName(node, checker, state.rootDir)
280
305
 
281
306
  let tags: string[] | undefined
282
307
  let expose: boolean | undefined
283
308
  let docs: PikkuDocs | undefined
309
+ let objectNode: ts.ObjectLiteralExpression | undefined
284
310
 
285
311
  // determine the actual handler expression:
286
312
  // either the `func` prop or the first argument directly
@@ -289,6 +315,7 @@ export function addFunctions(
289
315
 
290
316
  if (ts.isObjectLiteralExpression(handlerNode)) {
291
317
  isDirectFunction = false // This is object format with func property
318
+ objectNode = handlerNode
292
319
  tags = (getPropertyValue(handlerNode, 'tags') as string[]) || undefined
293
320
  expose = getPropertyValue(handlerNode, 'expose') as boolean | undefined
294
321
  docs = getPropertyValue(handlerNode, 'docs') as PikkuDocs | undefined
@@ -304,6 +331,17 @@ export function addFunctions(
304
331
  (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
305
332
  ) {
306
333
  logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`)
334
+ // Create stub metadata to prevent "function not found" errors in wirings
335
+ state.functions.meta[pikkuFuncName] = {
336
+ pikkuFuncName,
337
+ name,
338
+ services: { optimized: false, services: [] },
339
+ inputSchemaName: null,
340
+ outputSchemaName: null,
341
+ inputs: [],
342
+ outputs: [],
343
+ middleware: undefined,
344
+ }
307
345
  return
308
346
  }
309
347
  handlerNode = fnProp
@@ -314,6 +352,17 @@ export function addFunctions(
314
352
  !ts.isFunctionExpression(handlerNode)
315
353
  ) {
316
354
  logger.error(`• Handler for ${name} is not a function.`)
355
+ // Create stub metadata to prevent "function not found" errors in wirings
356
+ state.functions.meta[pikkuFuncName] = {
357
+ pikkuFuncName,
358
+ name,
359
+ services: { optimized: false, services: [] },
360
+ inputSchemaName: null,
361
+ outputSchemaName: null,
362
+ inputs: [],
363
+ outputs: [],
364
+ middleware: undefined,
365
+ }
317
366
  return
318
367
  }
319
368
 
@@ -347,7 +396,7 @@ export function addFunctions(
347
396
  .map((t) => unwrapPromise(checker, t))
348
397
 
349
398
  // --- Input Extraction ---
350
- let { names: inputNames } = getNamesAndTypes(
399
+ let { names: inputNames, types: inputTypes } = getNamesAndTypes(
351
400
  checker,
352
401
  state.functions.typesMap,
353
402
  'Input',
@@ -391,6 +440,11 @@ export function addFunctions(
391
440
  )
392
441
  }
393
442
 
443
+ // --- resolve middleware ---
444
+ const middleware = objectNode
445
+ ? resolveMiddleware(state, objectNode, tags, checker)
446
+ : undefined
447
+
394
448
  state.functions.meta[pikkuFuncName] = {
395
449
  pikkuFuncName,
396
450
  name,
@@ -403,6 +457,20 @@ export function addFunctions(
403
457
  tags: tags || undefined,
404
458
  docs: docs || undefined,
405
459
  isDirectFunction,
460
+ middleware,
461
+ }
462
+
463
+ // Store the input type for later use
464
+ if (inputTypes.length > 0) {
465
+ state.typesLookup.set(pikkuFuncName, inputTypes)
466
+ }
467
+
468
+ // Store function file location for wiring generation
469
+ if (exportedName) {
470
+ state.functions.files.set(pikkuFuncName, {
471
+ path: node.getSourceFile().fileName,
472
+ exportedName,
473
+ })
406
474
  }
407
475
 
408
476
  if (exportedName || explicitName) {
@@ -419,6 +487,8 @@ export function addFunctions(
419
487
  path: node.getSourceFile().fileName,
420
488
  exportedName,
421
489
  })
490
+ // Track exposed RPC function for service aggregation
491
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName)
422
492
  }
423
493
 
424
494
  // We add it to internal meta to allow autocomplete for everything
@@ -1,14 +1,19 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from './get-property-value.js'
2
+ import {
3
+ getPropertyValue,
4
+ getPropertyTags,
5
+ } from '../utils/get-property-value.js'
3
6
  import { pathToRegexp } from 'path-to-regexp'
4
7
  import { HTTPMethod } from '@pikku/core/http'
5
- import { PikkuDocs, PikkuWiringTypes } from '@pikku/core'
6
- import {
7
- extractFunctionName,
8
- getPropertyAssignmentInitializer,
9
- matchesFilters,
10
- } from './utils.js'
11
- import { InspectorState, InspectorFilters, InspectorLogger } from './types.js'
8
+ import { PikkuDocs } from '@pikku/core'
9
+ import { extractFunctionName } from '../utils/extract-function-name.js'
10
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
11
+ import { AddWiring } from '../types.js'
12
+ import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js'
13
+ import { resolveHTTPPermissionsFromObject } from '../utils/permissions.js'
14
+ import { extractWireNames } from '../utils/post-process.js'
15
+ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
16
+ import { ErrorCode } from '../error-codes.js'
12
17
 
13
18
  /**
14
19
  * Populate metaInputTypes for a given route based on method, input type,
@@ -39,12 +44,12 @@ export const getInputTypes = (
39
44
  * Simplified wireHTTP: re-uses function metadata from state.functions.meta
40
45
  * instead of re-inferring types here.
41
46
  */
42
- export const addHTTPRoute = (
43
- node: ts.Node,
44
- checker: ts.TypeChecker,
45
- state: InspectorState,
46
- filters: InspectorFilters,
47
- logger: InspectorLogger
47
+ export const addHTTPRoute: AddWiring = (
48
+ logger,
49
+ node,
50
+ checker,
51
+ state,
52
+ options
48
53
  ) => {
49
54
  // only look at calls
50
55
  if (!ts.isCallExpression(node)) return
@@ -67,23 +72,10 @@ export const addHTTPRoute = (
67
72
  const method =
68
73
  (getPropertyValue(obj, 'method') as string)?.toLowerCase() || 'get'
69
74
  const docs = (getPropertyValue(obj, 'docs') as PikkuDocs) || undefined
70
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
75
+ const tags = getPropertyTags(obj, 'HTTP route', route, logger)
71
76
  const query = (getPropertyValue(obj, 'query') as string[]) || []
72
77
 
73
- const filePath = node.getSourceFile().fileName
74
-
75
- if (
76
- !matchesFilters(
77
- filters,
78
- { tags },
79
- { type: PikkuWiringTypes.http, name: route, filePath },
80
- logger
81
- )
82
- ) {
83
- return
84
- }
85
-
86
- // --- find the referenced function ---
78
+ // --- find the referenced function name first for filtering ---
87
79
  const funcInitializer = getPropertyAssignmentInitializer(
88
80
  obj,
89
81
  'func',
@@ -91,16 +83,29 @@ export const addHTTPRoute = (
91
83
  checker
92
84
  )
93
85
  if (!funcInitializer) {
94
- console.error(`• No valid 'func' property for route '${route}'.`)
86
+ logger.critical(
87
+ ErrorCode.MISSING_FUNC,
88
+ `No valid 'func' property for route '${route}'.`
89
+ )
95
90
  return
96
91
  }
97
92
 
98
- const funcName = extractFunctionName(funcInitializer, checker).pikkuFuncName
93
+ const funcName = extractFunctionName(
94
+ funcInitializer,
95
+ checker,
96
+ state.rootDir
97
+ ).pikkuFuncName
98
+
99
+ // Ensure function metadata exists (creates stub for inline functions)
100
+ ensureFunctionMetadata(state, funcName, route)
99
101
 
100
102
  // lookup existing function metadata
101
103
  const fnMeta = state.functions.meta[funcName]
102
104
  if (!fnMeta) {
103
- console.error(`• No function metadata found for '${funcName}'.`)
105
+ logger.critical(
106
+ ErrorCode.FUNCTION_METADATA_NOT_FOUND,
107
+ `No function metadata found for '${funcName}'.`
108
+ )
104
109
  return
105
110
  }
106
111
  const input = fnMeta.inputs?.[0] || null
@@ -114,6 +119,33 @@ export const addHTTPRoute = (
114
119
  params
115
120
  )
116
121
 
122
+ // --- resolve middleware ---
123
+ const middleware = resolveHTTPMiddlewareFromObject(
124
+ state,
125
+ route,
126
+ obj,
127
+ tags,
128
+ checker
129
+ )
130
+
131
+ // --- resolve permissions ---
132
+ const permissions = resolveHTTPPermissionsFromObject(
133
+ state,
134
+ route,
135
+ obj,
136
+ tags,
137
+ checker
138
+ )
139
+
140
+ // --- track used functions/middleware/permissions for service aggregation ---
141
+ state.serviceAggregation.usedFunctions.add(funcName)
142
+ extractWireNames(middleware).forEach((name) =>
143
+ state.serviceAggregation.usedMiddleware.add(name)
144
+ )
145
+ extractWireNames(permissions).forEach((name) =>
146
+ state.serviceAggregation.usedPermissions.add(name)
147
+ )
148
+
117
149
  // --- record route ---
118
150
  state.http.files.add(node.getSourceFile().fileName)
119
151
  state.http.meta[method][route] = {
@@ -125,5 +157,7 @@ export const addHTTPRoute = (
125
157
  inputTypes,
126
158
  docs,
127
159
  tags,
160
+ middleware,
161
+ permissions,
128
162
  }
129
163
  }
@@ -0,0 +1,128 @@
1
+ import * as ts from 'typescript'
2
+ import {
3
+ getPropertyValue,
4
+ getPropertyTags,
5
+ } from '../utils/get-property-value.js'
6
+ import { extractWireNames } from '../utils/post-process.js'
7
+ import { ensureFunctionMetadata } from '../utils/ensure-function-metadata.js'
8
+ import { AddWiring } from '../types.js'
9
+ import { extractFunctionName } from '../utils/extract-function-name.js'
10
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
11
+ import { resolveMiddleware } from '../utils/middleware.js'
12
+ import { resolvePermissions } from '../utils/permissions.js'
13
+ import { ErrorCode } from '../error-codes.js'
14
+
15
+ export const addMCPPrompt: AddWiring = (
16
+ logger,
17
+ node,
18
+ checker,
19
+ state,
20
+ options
21
+ ) => {
22
+ if (!ts.isCallExpression(node)) {
23
+ return
24
+ }
25
+
26
+ const args = node.arguments
27
+ const firstArg = args[0]
28
+ const expression = node.expression
29
+
30
+ // Check if the call is to wireMCPPrompt
31
+ if (!ts.isIdentifier(expression) || expression.text !== 'wireMCPPrompt') {
32
+ return
33
+ }
34
+
35
+ if (!firstArg) {
36
+ return
37
+ }
38
+
39
+ if (ts.isObjectLiteralExpression(firstArg)) {
40
+ const obj = firstArg
41
+
42
+ const nameValue = getPropertyValue(obj, 'name') as string | null
43
+ const descriptionValue = getPropertyValue(obj, 'description') as
44
+ | string
45
+ | null
46
+ const tags = getPropertyTags(obj, 'MCP prompt', nameValue, logger)
47
+
48
+ const funcInitializer = getPropertyAssignmentInitializer(
49
+ obj,
50
+ 'func',
51
+ true,
52
+ checker
53
+ )
54
+ if (!funcInitializer) {
55
+ logger.critical(
56
+ ErrorCode.MISSING_FUNC,
57
+ `No valid 'func' property for MCP prompt '${nameValue}'.`
58
+ )
59
+ return
60
+ }
61
+
62
+ const pikkuFuncName = extractFunctionName(
63
+ funcInitializer,
64
+ checker,
65
+ state.rootDir
66
+ ).pikkuFuncName
67
+
68
+ // Ensure function metadata exists (creates stub for inline functions)
69
+ ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined)
70
+
71
+ if (!nameValue) {
72
+ logger.critical(
73
+ ErrorCode.MISSING_NAME,
74
+ "MCP prompt is missing the required 'name' property."
75
+ )
76
+ return
77
+ }
78
+
79
+ if (!descriptionValue) {
80
+ logger.critical(
81
+ ErrorCode.MISSING_DESCRIPTION,
82
+ `MCP prompt '${nameValue}' is missing a description.`
83
+ )
84
+ return
85
+ }
86
+
87
+ // lookup existing function metadata
88
+ const fnMeta = state.functions.meta[pikkuFuncName]
89
+ if (!fnMeta) {
90
+ logger.critical(
91
+ ErrorCode.FUNCTION_METADATA_NOT_FOUND,
92
+ `No function metadata found for '${pikkuFuncName}'.`
93
+ )
94
+ return
95
+ }
96
+ const inputSchema = fnMeta.inputs?.[0] || null
97
+ const outputSchema = fnMeta.outputs?.[0] || null
98
+
99
+ // --- resolve middleware ---
100
+ const middleware = resolveMiddleware(state, obj, tags, checker)
101
+
102
+ // --- resolve permissions ---
103
+ const permissions = resolvePermissions(state, obj, tags, checker)
104
+
105
+ // --- track used functions/middleware/permissions for service aggregation ---
106
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName)
107
+ extractWireNames(middleware).forEach((name) =>
108
+ state.serviceAggregation.usedMiddleware.add(name)
109
+ )
110
+ extractWireNames(permissions).forEach((name) =>
111
+ state.serviceAggregation.usedPermissions.add(name)
112
+ )
113
+
114
+ state.mcpEndpoints.files.add(node.getSourceFile().fileName)
115
+
116
+ state.mcpEndpoints.promptsMeta[nameValue] = {
117
+ pikkuFuncName,
118
+ name: nameValue,
119
+ description: descriptionValue,
120
+ tags,
121
+ inputSchema,
122
+ outputSchema,
123
+ arguments: [], // Will be populated by CLI during serialization
124
+ middleware,
125
+ permissions,
126
+ }
127
+ }
128
+ }
File without changes