@pikku/inspector 0.9.6-next.0 → 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 (84) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/add/add-channel.d.ts +5 -1
  3. package/dist/add/add-channel.js +51 -32
  4. package/dist/add/add-cli.d.ts +4 -0
  5. package/dist/add/add-cli.js +128 -23
  6. package/dist/add/add-file-extends-core-type.js +3 -2
  7. package/dist/add/add-file-with-factory.d.ts +2 -2
  8. package/dist/add/add-file-with-factory.js +34 -1
  9. package/dist/add/add-functions.js +52 -5
  10. package/dist/add/add-http-route.js +19 -12
  11. package/dist/add/add-mcp-prompt.js +20 -13
  12. package/dist/add/add-mcp-resource.js +24 -14
  13. package/dist/add/add-mcp-tool.js +23 -13
  14. package/dist/add/add-middleware.js +51 -12
  15. package/dist/add/add-permission.d.ts +1 -2
  16. package/dist/add/add-permission.js +275 -19
  17. package/dist/add/add-queue-worker.js +10 -12
  18. package/dist/add/add-schedule.js +9 -10
  19. package/dist/error-codes.d.ts +35 -0
  20. package/dist/error-codes.js +40 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +3 -0
  23. package/dist/inspector.js +20 -1
  24. package/dist/types.d.ts +31 -3
  25. package/dist/utils/ensure-function-metadata.d.ts +6 -0
  26. package/dist/utils/ensure-function-metadata.js +18 -0
  27. package/dist/utils/extract-function-name.d.ts +2 -2
  28. package/dist/utils/extract-function-name.js +13 -8
  29. package/dist/utils/filter-inspector-state.d.ts +6 -0
  30. package/dist/utils/filter-inspector-state.js +382 -0
  31. package/dist/utils/filter-utils.d.ts +10 -0
  32. package/dist/utils/filter-utils.js +66 -2
  33. package/dist/utils/find-root-dir.d.ts +23 -0
  34. package/dist/utils/find-root-dir.js +55 -0
  35. package/dist/utils/get-files-and-methods.d.ts +2 -1
  36. package/dist/utils/get-files-and-methods.js +2 -1
  37. package/dist/utils/get-property-value.d.ts +9 -0
  38. package/dist/utils/get-property-value.js +20 -0
  39. package/dist/utils/middleware.d.ts +1 -1
  40. package/dist/utils/middleware.js +7 -7
  41. package/dist/utils/permissions.d.ts +43 -0
  42. package/dist/utils/permissions.js +178 -0
  43. package/dist/utils/post-process.d.ts +16 -0
  44. package/dist/utils/post-process.js +132 -0
  45. package/dist/utils/serialize-inspector-state.d.ts +179 -0
  46. package/dist/utils/serialize-inspector-state.js +170 -0
  47. package/dist/visit.js +3 -2
  48. package/package.json +4 -4
  49. package/src/add/add-channel.ts +92 -40
  50. package/src/add/add-cli.ts +188 -29
  51. package/src/add/add-file-extends-core-type.ts +5 -2
  52. package/src/add/add-file-with-factory.ts +45 -2
  53. package/src/add/add-functions.ts +60 -5
  54. package/src/add/add-http-route.ts +46 -21
  55. package/src/add/add-mcp-prompt.ts +42 -21
  56. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  57. package/src/add/add-mcp-resource.ts +50 -24
  58. package/src/add/add-mcp-resource.ts.tmp +0 -0
  59. package/src/add/add-mcp-tool.ts +48 -21
  60. package/src/add/add-middleware.ts +74 -15
  61. package/src/add/add-permission.ts +364 -22
  62. package/src/add/add-queue-worker.ts +22 -25
  63. package/src/add/add-schedule.ts +19 -20
  64. package/src/error-codes.ts +43 -0
  65. package/src/index.ts +7 -0
  66. package/src/inspector.ts +22 -1
  67. package/src/types.ts +38 -3
  68. package/src/utils/ensure-function-metadata.ts +24 -0
  69. package/src/utils/extract-function-name.ts +20 -8
  70. package/src/utils/filter-inspector-state.test.ts +1433 -0
  71. package/src/utils/filter-inspector-state.ts +526 -0
  72. package/src/utils/filter-utils.test.ts +350 -1
  73. package/src/utils/filter-utils.ts +82 -2
  74. package/src/utils/find-root-dir.ts +68 -0
  75. package/src/utils/get-files-and-methods.ts +8 -0
  76. package/src/utils/get-property-value.ts +27 -0
  77. package/src/utils/middleware.ts +14 -7
  78. package/src/utils/permissions.test.ts +327 -0
  79. package/src/utils/permissions.ts +262 -0
  80. package/src/utils/post-process.ts +178 -0
  81. package/src/utils/serialize-inspector-state.ts +375 -0
  82. package/src/utils/test-data/inspector-state.json +1680 -0
  83. package/src/visit.ts +4 -2
  84. package/tsconfig.tsbuildinfo +1 -1
@@ -1,11 +1,16 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from '../utils/get-property-value.js'
3
- import { PikkuWiringTypes } from '@pikku/core'
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'
4
8
  import { AddWiring } from '../types.js'
5
9
  import { extractFunctionName } from '../utils/extract-function-name.js'
6
10
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
7
- import { matchesFilters } from '../utils/filter-utils.js'
8
11
  import { resolveMiddleware } from '../utils/middleware.js'
12
+ import { resolvePermissions } from '../utils/permissions.js'
13
+ import { ErrorCode } from '../error-codes.js'
9
14
 
10
15
  export const addMCPPrompt: AddWiring = (
11
16
  logger,
@@ -38,7 +43,7 @@ export const addMCPPrompt: AddWiring = (
38
43
  const descriptionValue = getPropertyValue(obj, 'description') as
39
44
  | string
40
45
  | null
41
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
46
+ const tags = getPropertyTags(obj, 'MCP prompt', nameValue, logger)
42
47
 
43
48
  const funcInitializer = getPropertyAssignmentInitializer(
44
49
  obj,
@@ -47,42 +52,45 @@ export const addMCPPrompt: AddWiring = (
47
52
  checker
48
53
  )
49
54
  if (!funcInitializer) {
50
- console.error(`• No valid 'func' property for MCP prompt '${nameValue}'.`)
55
+ logger.critical(
56
+ ErrorCode.MISSING_FUNC,
57
+ `No valid 'func' property for MCP prompt '${nameValue}'.`
58
+ )
51
59
  return
52
60
  }
53
61
 
54
62
  const pikkuFuncName = extractFunctionName(
55
63
  funcInitializer,
56
- checker
64
+ checker,
65
+ state.rootDir
57
66
  ).pikkuFuncName
58
67
 
68
+ // Ensure function metadata exists (creates stub for inline functions)
69
+ ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined)
70
+
59
71
  if (!nameValue) {
60
- console.error(`• MCP prompt is missing the required 'name' property.`)
72
+ logger.critical(
73
+ ErrorCode.MISSING_NAME,
74
+ "MCP prompt is missing the required 'name' property."
75
+ )
61
76
  return
62
77
  }
63
78
 
64
79
  if (!descriptionValue) {
65
- console.error(`• MCP prompt '${nameValue}' is missing a description.`)
66
- return
67
- }
68
-
69
- const filePath = node.getSourceFile().fileName
70
-
71
- if (
72
- !matchesFilters(
73
- options.filters || {},
74
- { tags },
75
- { type: PikkuWiringTypes.mcp, name: nameValue, filePath },
76
- logger
80
+ logger.critical(
81
+ ErrorCode.MISSING_DESCRIPTION,
82
+ `MCP prompt '${nameValue}' is missing a description.`
77
83
  )
78
- ) {
79
84
  return
80
85
  }
81
86
 
82
87
  // lookup existing function metadata
83
88
  const fnMeta = state.functions.meta[pikkuFuncName]
84
89
  if (!fnMeta) {
85
- console.error(`• No function metadata found for '${pikkuFuncName}'.`)
90
+ logger.critical(
91
+ ErrorCode.FUNCTION_METADATA_NOT_FOUND,
92
+ `No function metadata found for '${pikkuFuncName}'.`
93
+ )
86
94
  return
87
95
  }
88
96
  const inputSchema = fnMeta.inputs?.[0] || null
@@ -91,6 +99,18 @@ export const addMCPPrompt: AddWiring = (
91
99
  // --- resolve middleware ---
92
100
  const middleware = resolveMiddleware(state, obj, tags, checker)
93
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
+
94
114
  state.mcpEndpoints.files.add(node.getSourceFile().fileName)
95
115
 
96
116
  state.mcpEndpoints.promptsMeta[nameValue] = {
@@ -102,6 +122,7 @@ export const addMCPPrompt: AddWiring = (
102
122
  outputSchema,
103
123
  arguments: [], // Will be populated by CLI during serialization
104
124
  middleware,
125
+ permissions,
105
126
  }
106
127
  }
107
128
  }
File without changes
@@ -1,11 +1,16 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from '../utils/get-property-value.js'
3
- import { PikkuWiringTypes } from '@pikku/core'
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'
4
8
  import { AddWiring } from '../types.js'
5
9
  import { extractFunctionName } from '../utils/extract-function-name.js'
6
10
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
7
- import { matchesFilters } from '../utils/filter-utils.js'
8
11
  import { resolveMiddleware } from '../utils/middleware.js'
12
+ import { resolvePermissions } from '../utils/permissions.js'
13
+ import { ErrorCode } from '../error-codes.js'
9
14
 
10
15
  export const addMCPResource: AddWiring = (
11
16
  logger,
@@ -40,7 +45,13 @@ export const addMCPResource: AddWiring = (
40
45
  | string
41
46
  | null
42
47
  const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
43
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
48
+ const tags = getPropertyTags(obj, 'MCP resource', uriValue, logger)
49
+
50
+ if (streamingValue === true) {
51
+ logger.warn(
52
+ `MCP resource '${uriValue}' has streaming enabled, but streaming is not yet supported.`
53
+ )
54
+ }
44
55
 
45
56
  const funcInitializer = getPropertyAssignmentInitializer(
46
57
  obj,
@@ -49,51 +60,53 @@ export const addMCPResource: AddWiring = (
49
60
  checker
50
61
  )
51
62
  if (!funcInitializer) {
52
- console.error(
53
- `• No valid 'func' property for MCP resource '${uriValue}'.`
63
+ logger.critical(
64
+ ErrorCode.MISSING_FUNC,
65
+ `No valid 'func' property for MCP resource '${uriValue}'.`
54
66
  )
55
67
  return
56
68
  }
57
69
 
58
70
  const pikkuFuncName = extractFunctionName(
59
71
  funcInitializer,
60
- checker
72
+ checker,
73
+ state.rootDir
61
74
  ).pikkuFuncName
62
75
 
76
+ // Ensure function metadata exists (creates stub for inline functions)
77
+ ensureFunctionMetadata(state, pikkuFuncName, uriValue || undefined)
78
+
63
79
  if (!uriValue) {
64
- console.error(`• MCP resource is missing the required 'uri' property.`)
80
+ logger.critical(
81
+ ErrorCode.MISSING_URI,
82
+ "MCP resource is missing the required 'uri' property."
83
+ )
65
84
  return
66
85
  }
67
86
 
68
87
  if (!titleValue) {
69
- console.error(
70
- `• MCP resource '${uriValue}' is missing the required 'title' property.`
88
+ logger.critical(
89
+ ErrorCode.MISSING_TITLE,
90
+ `MCP resource '${uriValue}' is missing the required 'title' property.`
71
91
  )
72
92
  return
73
93
  }
74
94
 
75
95
  if (!descriptionValue) {
76
- console.error(`• MCP resource '${uriValue}' is missing a description.`)
77
- return
78
- }
79
-
80
- const filePath = node.getSourceFile().fileName
81
-
82
- if (
83
- !matchesFilters(
84
- options.filters || {},
85
- { tags },
86
- { type: PikkuWiringTypes.mcp, name: uriValue, filePath },
87
- logger
96
+ logger.critical(
97
+ ErrorCode.MISSING_DESCRIPTION,
98
+ `MCP resource '${uriValue}' is missing a description.`
88
99
  )
89
- ) {
90
100
  return
91
101
  }
92
102
 
93
103
  // lookup existing function metadata
94
104
  const fnMeta = state.functions.meta[pikkuFuncName]
95
105
  if (!fnMeta) {
96
- console.error(`• No function metadata found for '${pikkuFuncName}'.`)
106
+ logger.critical(
107
+ ErrorCode.FUNCTION_METADATA_NOT_FOUND,
108
+ `No function metadata found for '${pikkuFuncName}'.`
109
+ )
97
110
  return
98
111
  }
99
112
  const inputSchema = fnMeta.inputs?.[0] || null
@@ -102,6 +115,18 @@ export const addMCPResource: AddWiring = (
102
115
  // --- resolve middleware ---
103
116
  const middleware = resolveMiddleware(state, obj, tags, checker)
104
117
 
118
+ // --- resolve permissions ---
119
+ const permissions = resolvePermissions(state, obj, tags, checker)
120
+
121
+ // --- track used functions/middleware/permissions for service aggregation ---
122
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName)
123
+ extractWireNames(middleware).forEach((name) =>
124
+ state.serviceAggregation.usedMiddleware.add(name)
125
+ )
126
+ extractWireNames(permissions).forEach((name) =>
127
+ state.serviceAggregation.usedPermissions.add(name)
128
+ )
129
+
105
130
  state.mcpEndpoints.files.add(node.getSourceFile().fileName)
106
131
 
107
132
  state.mcpEndpoints.resourcesMeta[uriValue] = {
@@ -114,6 +139,7 @@ export const addMCPResource: AddWiring = (
114
139
  inputSchema,
115
140
  outputSchema,
116
141
  middleware,
142
+ permissions,
117
143
  }
118
144
  }
119
145
  }
File without changes
@@ -1,11 +1,16 @@
1
1
  import * as ts from 'typescript'
2
- import { getPropertyValue } from '../utils/get-property-value.js'
3
- import { PikkuWiringTypes } from '@pikku/core'
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'
4
8
  import { AddWiring } from '../types.js'
5
9
  import { extractFunctionName } from '../utils/extract-function-name.js'
6
10
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
7
- import { matchesFilters } from '../utils/filter-utils.js'
8
11
  import { resolveMiddleware } from '../utils/middleware.js'
12
+ import { resolvePermissions } from '../utils/permissions.js'
13
+ import { ErrorCode } from '../error-codes.js'
9
14
 
10
15
  export const addMCPTool: AddWiring = (
11
16
  logger,
@@ -40,7 +45,13 @@ export const addMCPTool: AddWiring = (
40
45
  | string
41
46
  | null
42
47
  const streamingValue = getPropertyValue(obj, 'streaming') as boolean | null
43
- const tags = (getPropertyValue(obj, 'tags') as string[]) || undefined
48
+ const tags = getPropertyTags(obj, 'MCP tool', nameValue, logger)
49
+
50
+ if (streamingValue === true) {
51
+ logger.warn(
52
+ `MCP tool '${nameValue}' has streaming enabled, but streaming is not yet supported.`
53
+ )
54
+ }
44
55
 
45
56
  const funcInitializer = getPropertyAssignmentInitializer(
46
57
  obj,
@@ -49,42 +60,45 @@ export const addMCPTool: AddWiring = (
49
60
  checker
50
61
  )
51
62
  if (!funcInitializer) {
52
- console.error(`• No valid 'func' property for MCP tool '${nameValue}'.`)
63
+ logger.critical(
64
+ ErrorCode.MISSING_FUNC,
65
+ `No valid 'func' property for MCP tool '${nameValue}'.`
66
+ )
53
67
  return
54
68
  }
55
69
 
56
70
  const pikkuFuncName = extractFunctionName(
57
71
  funcInitializer,
58
- checker
72
+ checker,
73
+ state.rootDir
59
74
  ).pikkuFuncName
60
75
 
76
+ // Ensure function metadata exists (creates stub for inline functions)
77
+ ensureFunctionMetadata(state, pikkuFuncName, nameValue || undefined)
78
+
61
79
  if (!nameValue) {
62
- console.error(`• MCP tool is missing the required 'name' property.`)
80
+ logger.critical(
81
+ ErrorCode.MISSING_NAME,
82
+ "MCP tool is missing the required 'name' property."
83
+ )
63
84
  return
64
85
  }
65
86
 
66
87
  if (!descriptionValue) {
67
- console.error(`• MCP tool '${nameValue}' is missing a description.`)
68
- return
69
- }
70
-
71
- const filePath = node.getSourceFile().fileName
72
-
73
- if (
74
- !matchesFilters(
75
- options.filters || {},
76
- { tags },
77
- { type: PikkuWiringTypes.mcp, name: nameValue, filePath },
78
- logger
88
+ logger.critical(
89
+ ErrorCode.MISSING_DESCRIPTION,
90
+ `MCP tool '${nameValue}' is missing a description.`
79
91
  )
80
- ) {
81
92
  return
82
93
  }
83
94
 
84
95
  // lookup existing function metadata
85
96
  const fnMeta = state.functions.meta[pikkuFuncName]
86
97
  if (!fnMeta) {
87
- console.error(`• No function metadata found for '${pikkuFuncName}'.`)
98
+ logger.critical(
99
+ ErrorCode.FUNCTION_METADATA_NOT_FOUND,
100
+ `No function metadata found for '${pikkuFuncName}'.`
101
+ )
88
102
  return
89
103
  }
90
104
  const inputSchema = fnMeta.inputs?.[0] || null
@@ -93,6 +107,18 @@ export const addMCPTool: AddWiring = (
93
107
  // --- resolve middleware ---
94
108
  const middleware = resolveMiddleware(state, obj, tags, checker)
95
109
 
110
+ // --- resolve permissions ---
111
+ const permissions = resolvePermissions(state, obj, tags, checker)
112
+
113
+ // --- track used functions/middleware/permissions for service aggregation ---
114
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName)
115
+ extractWireNames(middleware).forEach((name) =>
116
+ state.serviceAggregation.usedMiddleware.add(name)
117
+ )
118
+ extractWireNames(permissions).forEach((name) =>
119
+ state.serviceAggregation.usedPermissions.add(name)
120
+ )
121
+
96
122
  state.mcpEndpoints.files.add(node.getSourceFile().fileName)
97
123
 
98
124
  state.mcpEndpoints.toolsMeta[nameValue] = {
@@ -105,6 +131,7 @@ export const addMCPTool: AddWiring = (
105
131
  inputSchema,
106
132
  outputSchema,
107
133
  middleware,
134
+ permissions,
108
135
  }
109
136
  }
110
137
  }
@@ -6,6 +6,8 @@ import {
6
6
  } from '../utils/extract-function-name.js'
7
7
  import { extractServicesFromFunction } from '../utils/extract-services.js'
8
8
  import { extractMiddlewarePikkuNames } from '../utils/middleware.js'
9
+ import { getPropertyValue } from '../utils/get-property-value.js'
10
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
9
11
 
10
12
  /**
11
13
  * Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
@@ -22,28 +24,62 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
22
24
 
23
25
  // Handle pikkuMiddleware(...) - individual middleware function definition
24
26
  if (expression.text === 'pikkuMiddleware') {
25
- const handlerNode = args[0]
26
- if (!handlerNode) return
27
-
28
- if (
29
- !ts.isArrowFunction(handlerNode) &&
30
- !ts.isFunctionExpression(handlerNode)
31
- ) {
27
+ const arg = args[0]
28
+ if (!arg) return
29
+
30
+ let actualHandler: ts.ArrowFunction | ts.FunctionExpression
31
+ let name: string | undefined
32
+ let description: string | undefined
33
+
34
+ // Check if using object syntax: pikkuMiddleware({ func: ..., name: '...', description: '...' })
35
+ if (ts.isObjectLiteralExpression(arg)) {
36
+ // Extract name and description metadata
37
+ const nameValue = getPropertyValue(arg, 'name')
38
+ const descValue = getPropertyValue(arg, 'description')
39
+ name = typeof nameValue === 'string' ? nameValue : undefined
40
+ description = typeof descValue === 'string' ? descValue : undefined
41
+
42
+ // Extract the func property
43
+ const fnProp = getPropertyAssignmentInitializer(
44
+ arg,
45
+ 'func',
46
+ true,
47
+ checker
48
+ )
49
+ if (
50
+ !fnProp ||
51
+ (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
52
+ ) {
53
+ logger.error(
54
+ `• pikkuMiddleware object missing required 'func' property.`
55
+ )
56
+ return
57
+ }
58
+ actualHandler = fnProp
59
+ } else if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
60
+ actualHandler = arg
61
+ } else {
32
62
  logger.error(`• Handler for pikkuMiddleware is not a function.`)
33
63
  return
34
64
  }
35
65
 
36
- const services = extractServicesFromFunction(handlerNode)
37
- const { pikkuFuncName, exportedName } = extractFunctionName(node, checker)
66
+ const services = extractServicesFromFunction(actualHandler)
67
+ const { pikkuFuncName, exportedName } = extractFunctionName(
68
+ node,
69
+ checker,
70
+ state.rootDir
71
+ )
38
72
  state.middleware.meta[pikkuFuncName] = {
39
73
  services,
40
74
  sourceFile: node.getSourceFile().fileName,
41
75
  position: node.getStart(),
42
76
  exportedName,
77
+ name,
78
+ description,
43
79
  }
44
80
 
45
81
  logger.debug(
46
- `• Found middleware with services: ${services.services.join(', ')}`
82
+ `• Found middleware with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`
47
83
  )
48
84
  return
49
85
  }
@@ -63,6 +99,7 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
63
99
 
64
100
  // Extract services by looking inside the factory function body
65
101
  // The factory should return pikkuMiddleware(...), so we need to find that call
102
+ // If no wrapper is found, extract from the factory's returned function directly
66
103
  let services = { optimized: false, services: [] as string[] }
67
104
 
68
105
  const findPikkuMiddlewareCall = (
@@ -86,9 +123,29 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
86
123
  ) {
87
124
  services = extractServicesFromFunction(middlewareHandler)
88
125
  }
126
+ } else {
127
+ // No pikkuMiddleware wrapper found - extract from factory's return value directly
128
+ // Factory pattern: (config) => (services, interaction, next) => { ... }
129
+ if (
130
+ ts.isArrowFunction(factoryNode) ||
131
+ ts.isFunctionExpression(factoryNode)
132
+ ) {
133
+ const factoryBody = factoryNode.body
134
+ // Check if the body is an arrow function (direct return)
135
+ if (
136
+ ts.isArrowFunction(factoryBody) ||
137
+ ts.isFunctionExpression(factoryBody)
138
+ ) {
139
+ services = extractServicesFromFunction(factoryBody)
140
+ }
141
+ }
89
142
  }
90
143
 
91
- const { pikkuFuncName, exportedName } = extractFunctionName(node, checker)
144
+ const { pikkuFuncName, exportedName } = extractFunctionName(
145
+ node,
146
+ checker,
147
+ state.rootDir
148
+ )
92
149
  state.middleware.meta[pikkuFuncName] = {
93
150
  services,
94
151
  sourceFile: node.getSourceFile().fileName,
@@ -135,7 +192,8 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
135
192
  // Extract middleware pikkuFuncNames from array
136
193
  const middlewareNames = extractMiddlewarePikkuNames(
137
194
  middlewareArrayArg,
138
- checker
195
+ checker,
196
+ state.rootDir
139
197
  )
140
198
 
141
199
  if (middlewareNames.length === 0) {
@@ -182,7 +240,7 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
182
240
 
183
241
  // If not a factory, get export name from the call expression itself
184
242
  if (!isFactory) {
185
- const extracted = extractFunctionName(node, checker)
243
+ const extracted = extractFunctionName(node, checker, state.rootDir)
186
244
  exportedName = extracted.exportedName
187
245
  }
188
246
 
@@ -245,7 +303,8 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
245
303
  // Extract middleware pikkuFuncNames from array
246
304
  const middlewareNames = extractMiddlewarePikkuNames(
247
305
  middlewareArrayArg,
248
- checker
306
+ checker,
307
+ state.rootDir
249
308
  )
250
309
 
251
310
  if (middlewareNames.length === 0) {
@@ -293,7 +352,7 @@ export const addMiddleware: AddWiring = (logger, node, checker, state) => {
293
352
 
294
353
  // If not a factory, get export name from the call expression itself
295
354
  if (!isFactory) {
296
- const extracted = extractFunctionName(node, checker)
355
+ const extracted = extractFunctionName(node, checker, state.rootDir)
297
356
  exportedName = extracted.exportedName
298
357
  }
299
358