@pikku/inspector 0.9.5 → 0.9.6-next.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 (94) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{add-channel.d.ts → add/add-channel.d.ts} +2 -2
  3. package/dist/{add-channel.js → add/add-channel.js} +12 -5
  4. package/dist/add/add-cli.d.ts +5 -0
  5. package/dist/add/add-cli.js +461 -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 -5
  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} +1 -1
  11. package/dist/{add-file-with-factory.js → add/add-file-with-factory.js} +4 -4
  12. package/dist/add/add-functions.d.ts +6 -0
  13. package/dist/{add-functions.js → add/add-functions.js} +25 -5
  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} +10 -4
  16. package/dist/add/add-mcp-prompt.d.ts +2 -0
  17. package/dist/{add-mcp-prompt.js → add/add-mcp-prompt.js} +10 -4
  18. package/dist/add/add-mcp-resource.d.ts +2 -0
  19. package/dist/{add-mcp-resource.js → add/add-mcp-resource.js} +10 -4
  20. package/dist/add/add-mcp-tool.d.ts +2 -0
  21. package/dist/{add-mcp-tool.js → add/add-mcp-tool.js} +10 -4
  22. package/dist/add/add-middleware.d.ts +5 -0
  23. package/dist/add/add-middleware.js +251 -0
  24. package/dist/add/add-permission.d.ts +6 -0
  25. package/dist/{add-permission.js → add/add-permission.js} +4 -3
  26. package/dist/add/add-queue-worker.d.ts +2 -0
  27. package/dist/{add-queue-worker.js → add/add-queue-worker.js} +10 -4
  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} +10 -4
  31. package/dist/index.d.ts +2 -0
  32. package/dist/index.js +1 -0
  33. package/dist/inspector.d.ts +2 -3
  34. package/dist/inspector.js +19 -8
  35. package/dist/types.d.ts +79 -0
  36. package/dist/{utils.d.ts → utils/extract-function-name.d.ts} +7 -15
  37. package/dist/{utils.js → utils/extract-function-name.js} +23 -142
  38. package/dist/utils/extract-services.d.ts +6 -0
  39. package/dist/utils/extract-services.js +29 -0
  40. package/dist/utils/filter-utils.d.ts +9 -0
  41. package/dist/utils/filter-utils.js +45 -0
  42. package/dist/utils/get-files-and-methods.d.ts +21 -0
  43. package/dist/utils/get-files-and-methods.js +60 -0
  44. package/dist/utils/middleware.d.ts +39 -0
  45. package/dist/utils/middleware.js +157 -0
  46. package/dist/utils/type-utils.d.ts +3 -0
  47. package/dist/utils/type-utils.js +50 -0
  48. package/dist/visit.d.ts +3 -3
  49. package/dist/visit.js +33 -30
  50. package/package.json +3 -4
  51. package/src/{add-channel.ts → add/add-channel.ts} +19 -19
  52. package/src/add/add-cli.ts +663 -0
  53. package/src/{add-file-extends-core-type.ts → add/add-file-extends-core-type.ts} +21 -6
  54. package/src/{add-file-with-config.ts → add/add-file-with-config.ts} +2 -2
  55. package/src/{add-file-with-factory.ts → add/add-file-with-factory.ts} +5 -5
  56. package/src/{add-functions.ts → add/add-functions.ts} +29 -14
  57. package/src/{add-http-route.ts → add/add-http-route.ts} +23 -14
  58. package/src/{add-mcp-prompt.ts → add/add-mcp-prompt.ts} +18 -15
  59. package/src/{add-mcp-resource.ts → add/add-mcp-resource.ts} +18 -15
  60. package/src/{add-mcp-tool.ts → add/add-mcp-tool.ts} +18 -15
  61. package/src/add/add-middleware.ts +326 -0
  62. package/src/{add-permission.ts → add/add-permission.ts} +4 -8
  63. package/src/{add-queue-worker.ts → add/add-queue-worker.ts} +17 -14
  64. package/src/{add-rpc-invocations.ts → add/add-rpc-invocations.ts} +1 -1
  65. package/src/{add-schedule.ts → add/add-schedule.ts} +17 -14
  66. package/src/index.ts +5 -0
  67. package/src/inspector.ts +20 -17
  68. package/src/types.ts +92 -0
  69. package/src/{utils.ts → utils/extract-function-name.ts} +25 -199
  70. package/src/utils/extract-services.ts +35 -0
  71. package/src/{utils.test.ts → utils/filter-utils.test.ts} +2 -2
  72. package/src/utils/filter-utils.ts +72 -0
  73. package/src/utils/get-files-and-methods.ts +143 -0
  74. package/src/utils/middleware.ts +234 -0
  75. package/src/utils/type-utils.ts +74 -0
  76. package/src/visit.ts +47 -33
  77. package/tsconfig.tsbuildinfo +1 -1
  78. package/dist/add-functions.d.ts +0 -7
  79. package/dist/add-mcp-prompt.d.ts +0 -3
  80. package/dist/add-mcp-resource.d.ts +0 -3
  81. package/dist/add-mcp-tool.d.ts +0 -3
  82. package/dist/add-middleware.d.ts +0 -7
  83. package/dist/add-middleware.js +0 -35
  84. package/dist/add-permission.d.ts +0 -7
  85. package/dist/add-queue-worker.d.ts +0 -3
  86. package/dist/add-schedule.d.ts +0 -3
  87. package/src/add-middleware.ts +0 -51
  88. /package/dist/{add-rpc-invocations.js → add/add-rpc-invocations.js} +0 -0
  89. /package/dist/{does-type-extend-core-type.d.ts → utils/does-type-extend-core-type.d.ts} +0 -0
  90. /package/dist/{does-type-extend-core-type.js → utils/does-type-extend-core-type.js} +0 -0
  91. /package/dist/{get-property-value.d.ts → utils/get-property-value.d.ts} +0 -0
  92. /package/dist/{get-property-value.js → utils/get-property-value.js} +0 -0
  93. /package/src/{does-type-extend-core-type.ts → utils/does-type-extend-core-type.ts} +0 -0
  94. /package/src/{get-property-value.ts → utils/get-property-value.ts} +0 -0
@@ -1,6 +1,8 @@
1
1
  import * as ts from 'typescript';
2
- import { extractFunctionName, getPropertyAssignmentInitializer, } from './utils.js';
3
- import { getPropertyValue } from './get-property-value.js';
2
+ import { extractFunctionName } from '../utils/extract-function-name.js';
3
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
4
+ import { getPropertyValue } from '../utils/get-property-value.js';
5
+ import { resolveMiddleware } from '../utils/middleware.js';
4
6
  const isValidVariableName = (name) => {
5
7
  const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
6
8
  return regex.test(name);
@@ -186,7 +188,7 @@ function unwrapPromise(checker, type) {
186
188
  * Inspect pikkuFunc calls, extract input/output and first-arg destructuring,
187
189
  * then push into state.functions.meta.
188
190
  */
189
- export function addFunctions(node, checker, state, logger) {
191
+ export const addFunctions = (logger, node, checker, state) => {
190
192
  if (!ts.isCallExpression(node))
191
193
  return;
192
194
  const { expression, arguments: args, typeArguments } = node;
@@ -209,12 +211,14 @@ export function addFunctions(node, checker, state, logger) {
209
211
  let tags;
210
212
  let expose;
211
213
  let docs;
214
+ let objectNode;
212
215
  // determine the actual handler expression:
213
216
  // either the `func` prop or the first argument directly
214
217
  let handlerNode = args[0];
215
218
  let isDirectFunction = true; // Default to direct function format
216
219
  if (ts.isObjectLiteralExpression(handlerNode)) {
217
220
  isDirectFunction = false; // This is object format with func property
221
+ objectNode = handlerNode;
218
222
  tags = getPropertyValue(handlerNode, 'tags') || undefined;
219
223
  expose = getPropertyValue(handlerNode, 'expose');
220
224
  docs = getPropertyValue(handlerNode, 'docs');
@@ -258,7 +262,7 @@ export function addFunctions(node, checker, state, logger) {
258
262
  .map((tn) => checker.getTypeFromTypeNode(tn))
259
263
  .map((t) => unwrapPromise(checker, t));
260
264
  // --- Input Extraction ---
261
- let { names: inputNames } = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
265
+ let { names: inputNames, types: inputTypes } = getNamesAndTypes(checker, state.functions.typesMap, 'Input', name, genericTypes[0]);
262
266
  // if (inputTypes.length === 0) {
263
267
  // logger.debug(
264
268
  // `\x1b[31m• Unknown input type for '${name}', assuming void.\x1b[0m`
@@ -280,6 +284,10 @@ export function addFunctions(node, checker, state, logger) {
280
284
  if (inputNames.length > 1) {
281
285
  logger.warn('More than one input type detected, only the first one will be used as a schema.');
282
286
  }
287
+ // --- resolve middleware ---
288
+ const middleware = objectNode
289
+ ? resolveMiddleware(state, objectNode, tags, checker)
290
+ : undefined;
283
291
  state.functions.meta[pikkuFuncName] = {
284
292
  pikkuFuncName,
285
293
  name,
@@ -292,7 +300,19 @@ export function addFunctions(node, checker, state, logger) {
292
300
  tags: tags || undefined,
293
301
  docs: docs || undefined,
294
302
  isDirectFunction,
303
+ middleware,
295
304
  };
305
+ // Store the input type for later use
306
+ if (inputTypes.length > 0) {
307
+ state.typesLookup.set(pikkuFuncName, inputTypes);
308
+ }
309
+ // Store function file location for wiring generation
310
+ if (exportedName) {
311
+ state.functions.files.set(pikkuFuncName, {
312
+ path: node.getSourceFile().fileName,
313
+ exportedName,
314
+ });
315
+ }
296
316
  if (exportedName || explicitName) {
297
317
  if (!exportedName) {
298
318
  logger.error(`• Function with explicit name '${name}' is not exported, this is not allowed.`);
@@ -316,4 +336,4 @@ export function addFunctions(node, checker, state, logger) {
316
336
  });
317
337
  }
318
338
  }
319
- }
339
+ };
@@ -1,5 +1,4 @@
1
- import * as ts from 'typescript';
2
- import { InspectorState, InspectorFilters, InspectorLogger } from './types.js';
1
+ import { AddWiring } from '../types.js';
3
2
  /**
4
3
  * Populate metaInputTypes for a given route based on method, input type,
5
4
  * query and params. Returns undefined (we only mutate metaTypes).
@@ -13,4 +12,4 @@ export declare const getInputTypes: (metaTypes: Map<string, {
13
12
  * Simplified wireHTTP: re-uses function metadata from state.functions.meta
14
13
  * instead of re-inferring types here.
15
14
  */
16
- export declare const addHTTPRoute: (node: ts.Node, checker: ts.TypeChecker, state: InspectorState, filters: InspectorFilters, logger: InspectorLogger) => void;
15
+ export declare const addHTTPRoute: AddWiring;
@@ -1,8 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { pathToRegexp } from 'path-to-regexp';
4
4
  import { PikkuWiringTypes } from '@pikku/core';
5
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
+ import { extractFunctionName } from '../utils/extract-function-name.js';
6
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
+ import { matchesFilters } from '../utils/filter-utils.js';
8
+ import { resolveHTTPMiddlewareFromObject } from '../utils/middleware.js';
6
9
  /**
7
10
  * Populate metaInputTypes for a given route based on method, input type,
8
11
  * query and params. Returns undefined (we only mutate metaTypes).
@@ -23,7 +26,7 @@ export const getInputTypes = (metaTypes, methodType, inputType, queryValues, par
23
26
  * Simplified wireHTTP: re-uses function metadata from state.functions.meta
24
27
  * instead of re-inferring types here.
25
28
  */
26
- export const addHTTPRoute = (node, checker, state, filters, logger) => {
29
+ export const addHTTPRoute = (logger, node, checker, state, options) => {
27
30
  // only look at calls
28
31
  if (!ts.isCallExpression(node))
29
32
  return;
@@ -46,7 +49,7 @@ export const addHTTPRoute = (node, checker, state, filters, logger) => {
46
49
  const tags = getPropertyValue(obj, 'tags') || undefined;
47
50
  const query = getPropertyValue(obj, 'query') || [];
48
51
  const filePath = node.getSourceFile().fileName;
49
- if (!matchesFilters(filters, { tags }, { type: PikkuWiringTypes.http, name: route, filePath }, logger)) {
52
+ if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.http, name: route, filePath }, logger)) {
50
53
  return;
51
54
  }
52
55
  // --- find the referenced function ---
@@ -65,6 +68,8 @@ export const addHTTPRoute = (node, checker, state, filters, logger) => {
65
68
  const input = fnMeta.inputs?.[0] || null;
66
69
  // --- compute inputTypes (body/query/params) ---
67
70
  const inputTypes = getInputTypes(state.http.metaInputTypes, method, input, query, params);
71
+ // --- resolve middleware ---
72
+ const middleware = resolveHTTPMiddlewareFromObject(state, route, obj, tags, checker);
68
73
  // --- record route ---
69
74
  state.http.files.add(node.getSourceFile().fileName);
70
75
  state.http.meta[method][route] = {
@@ -76,5 +81,6 @@ export const addHTTPRoute = (node, checker, state, filters, logger) => {
76
81
  inputTypes,
77
82
  docs,
78
83
  tags,
84
+ middleware,
79
85
  };
80
86
  };
@@ -0,0 +1,2 @@
1
+ import { AddWiring } from '../types.js';
2
+ export declare const addMCPPrompt: AddWiring;
@@ -1,8 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { PikkuWiringTypes } from '@pikku/core';
4
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
- export const addMCPPrompt = (node, checker, state, filters, logger) => {
4
+ import { extractFunctionName } from '../utils/extract-function-name.js';
5
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
+ import { matchesFilters } from '../utils/filter-utils.js';
7
+ import { resolveMiddleware } from '../utils/middleware.js';
8
+ export const addMCPPrompt = (logger, node, checker, state, options) => {
6
9
  if (!ts.isCallExpression(node)) {
7
10
  return;
8
11
  }
@@ -36,7 +39,7 @@ export const addMCPPrompt = (node, checker, state, filters, logger) => {
36
39
  return;
37
40
  }
38
41
  const filePath = node.getSourceFile().fileName;
39
- if (!matchesFilters(filters, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
42
+ if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
40
43
  return;
41
44
  }
42
45
  // lookup existing function metadata
@@ -47,6 +50,8 @@ export const addMCPPrompt = (node, checker, state, filters, logger) => {
47
50
  }
48
51
  const inputSchema = fnMeta.inputs?.[0] || null;
49
52
  const outputSchema = fnMeta.outputs?.[0] || null;
53
+ // --- resolve middleware ---
54
+ const middleware = resolveMiddleware(state, obj, tags, checker);
50
55
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
51
56
  state.mcpEndpoints.promptsMeta[nameValue] = {
52
57
  pikkuFuncName,
@@ -56,6 +61,7 @@ export const addMCPPrompt = (node, checker, state, filters, logger) => {
56
61
  inputSchema,
57
62
  outputSchema,
58
63
  arguments: [], // Will be populated by CLI during serialization
64
+ middleware,
59
65
  };
60
66
  }
61
67
  };
@@ -0,0 +1,2 @@
1
+ import { AddWiring } from '../types.js';
2
+ export declare const addMCPResource: AddWiring;
@@ -1,8 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { PikkuWiringTypes } from '@pikku/core';
4
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
- export const addMCPResource = (node, checker, state, filters, logger) => {
4
+ import { extractFunctionName } from '../utils/extract-function-name.js';
5
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
+ import { matchesFilters } from '../utils/filter-utils.js';
7
+ import { resolveMiddleware } from '../utils/middleware.js';
8
+ export const addMCPResource = (logger, node, checker, state, options) => {
6
9
  if (!ts.isCallExpression(node)) {
7
10
  return;
8
11
  }
@@ -42,7 +45,7 @@ export const addMCPResource = (node, checker, state, filters, logger) => {
42
45
  return;
43
46
  }
44
47
  const filePath = node.getSourceFile().fileName;
45
- if (!matchesFilters(filters, { tags }, { type: PikkuWiringTypes.mcp, name: uriValue, filePath }, logger)) {
48
+ if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: uriValue, filePath }, logger)) {
46
49
  return;
47
50
  }
48
51
  // lookup existing function metadata
@@ -53,6 +56,8 @@ export const addMCPResource = (node, checker, state, filters, logger) => {
53
56
  }
54
57
  const inputSchema = fnMeta.inputs?.[0] || null;
55
58
  const outputSchema = fnMeta.outputs?.[0] || null;
59
+ // --- resolve middleware ---
60
+ const middleware = resolveMiddleware(state, obj, tags, checker);
56
61
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
57
62
  state.mcpEndpoints.resourcesMeta[uriValue] = {
58
63
  pikkuFuncName,
@@ -63,6 +68,7 @@ export const addMCPResource = (node, checker, state, filters, logger) => {
63
68
  tags,
64
69
  inputSchema,
65
70
  outputSchema,
71
+ middleware,
66
72
  };
67
73
  }
68
74
  };
@@ -0,0 +1,2 @@
1
+ import { AddWiring } from '../types.js';
2
+ export declare const addMCPTool: AddWiring;
@@ -1,8 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { PikkuWiringTypes } from '@pikku/core';
4
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
- export const addMCPTool = (node, checker, state, filters, logger) => {
4
+ import { extractFunctionName } from '../utils/extract-function-name.js';
5
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
+ import { matchesFilters } from '../utils/filter-utils.js';
7
+ import { resolveMiddleware } from '../utils/middleware.js';
8
+ export const addMCPTool = (logger, node, checker, state, options) => {
6
9
  if (!ts.isCallExpression(node)) {
7
10
  return;
8
11
  }
@@ -38,7 +41,7 @@ export const addMCPTool = (node, checker, state, filters, logger) => {
38
41
  return;
39
42
  }
40
43
  const filePath = node.getSourceFile().fileName;
41
- if (!matchesFilters(filters, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
44
+ if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.mcp, name: nameValue, filePath }, logger)) {
42
45
  return;
43
46
  }
44
47
  // lookup existing function metadata
@@ -49,6 +52,8 @@ export const addMCPTool = (node, checker, state, filters, logger) => {
49
52
  }
50
53
  const inputSchema = fnMeta.inputs?.[0] || null;
51
54
  const outputSchema = fnMeta.outputs?.[0] || null;
55
+ // --- resolve middleware ---
56
+ const middleware = resolveMiddleware(state, obj, tags, checker);
52
57
  state.mcpEndpoints.files.add(node.getSourceFile().fileName);
53
58
  state.mcpEndpoints.toolsMeta[nameValue] = {
54
59
  pikkuFuncName,
@@ -59,6 +64,7 @@ export const addMCPTool = (node, checker, state, filters, logger) => {
59
64
  tags,
60
65
  inputSchema,
61
66
  outputSchema,
67
+ middleware,
62
68
  };
63
69
  }
64
70
  };
@@ -0,0 +1,5 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
4
+ */
5
+ export declare const addMiddleware: AddWiring;
@@ -0,0 +1,251 @@
1
+ import * as ts from 'typescript';
2
+ import { extractFunctionName, isNamedExport, } from '../utils/extract-function-name.js';
3
+ import { extractServicesFromFunction } from '../utils/extract-services.js';
4
+ import { extractMiddlewarePikkuNames } from '../utils/middleware.js';
5
+ /**
6
+ * Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
7
+ */
8
+ export const addMiddleware = (logger, node, checker, state) => {
9
+ if (!ts.isCallExpression(node))
10
+ return;
11
+ const { expression, arguments: args } = node;
12
+ // only handle specific function calls
13
+ if (!ts.isIdentifier(expression)) {
14
+ return;
15
+ }
16
+ // Handle pikkuMiddleware(...) - individual middleware function definition
17
+ if (expression.text === 'pikkuMiddleware') {
18
+ const handlerNode = args[0];
19
+ if (!handlerNode)
20
+ return;
21
+ if (!ts.isArrowFunction(handlerNode) &&
22
+ !ts.isFunctionExpression(handlerNode)) {
23
+ logger.error(`• Handler for pikkuMiddleware is not a function.`);
24
+ return;
25
+ }
26
+ const services = extractServicesFromFunction(handlerNode);
27
+ const { pikkuFuncName, exportedName } = extractFunctionName(node, checker);
28
+ state.middleware.meta[pikkuFuncName] = {
29
+ services,
30
+ sourceFile: node.getSourceFile().fileName,
31
+ position: node.getStart(),
32
+ exportedName,
33
+ };
34
+ logger.debug(`• Found middleware with services: ${services.services.join(', ')}`);
35
+ return;
36
+ }
37
+ // Handle pikkuMiddlewareFactory(...) - middleware factory function
38
+ if (expression.text === 'pikkuMiddlewareFactory') {
39
+ const factoryNode = args[0];
40
+ if (!factoryNode)
41
+ return;
42
+ if (!ts.isArrowFunction(factoryNode) &&
43
+ !ts.isFunctionExpression(factoryNode)) {
44
+ logger.error(`• Handler for pikkuMiddlewareFactory is not a function.`);
45
+ return;
46
+ }
47
+ // Extract services by looking inside the factory function body
48
+ // The factory should return pikkuMiddleware(...), so we need to find that call
49
+ let services = { optimized: false, services: [] };
50
+ const findPikkuMiddlewareCall = (node) => {
51
+ if (ts.isCallExpression(node)) {
52
+ const expr = node.expression;
53
+ if (ts.isIdentifier(expr) && expr.text === 'pikkuMiddleware') {
54
+ return node;
55
+ }
56
+ }
57
+ return ts.forEachChild(node, findPikkuMiddlewareCall);
58
+ };
59
+ const pikkuMiddlewareCall = findPikkuMiddlewareCall(factoryNode);
60
+ if (pikkuMiddlewareCall && pikkuMiddlewareCall.arguments[0]) {
61
+ const middlewareHandler = pikkuMiddlewareCall.arguments[0];
62
+ if (ts.isArrowFunction(middlewareHandler) ||
63
+ ts.isFunctionExpression(middlewareHandler)) {
64
+ services = extractServicesFromFunction(middlewareHandler);
65
+ }
66
+ }
67
+ const { pikkuFuncName, exportedName } = extractFunctionName(node, checker);
68
+ state.middleware.meta[pikkuFuncName] = {
69
+ services,
70
+ sourceFile: node.getSourceFile().fileName,
71
+ position: node.getStart(),
72
+ exportedName,
73
+ factory: true,
74
+ };
75
+ logger.debug(`• Found middleware factory with services: ${services.services.join(', ')}`);
76
+ return;
77
+ }
78
+ // Handle addMiddleware('tag', [middleware1, middleware2])
79
+ // Supports two patterns:
80
+ // 1. export const x = () => addMiddleware('tag', [...]) (factory - tree-shakeable)
81
+ // 2. export const x = addMiddleware('tag', [...]) (direct - no tree-shaking)
82
+ if (expression.text === 'addMiddleware') {
83
+ const tagArg = args[0];
84
+ const middlewareArrayArg = args[1];
85
+ if (!tagArg || !middlewareArrayArg)
86
+ return;
87
+ // Extract tag name
88
+ let tag;
89
+ if (ts.isStringLiteral(tagArg)) {
90
+ tag = tagArg.text;
91
+ }
92
+ if (!tag) {
93
+ logger.warn(`• addMiddleware call without valid tag string`);
94
+ return;
95
+ }
96
+ // Check if middleware array is a literal array
97
+ if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
98
+ logger.error(`• addMiddleware('${tag}', ...) must have a literal array as second argument`);
99
+ return;
100
+ }
101
+ // Extract middleware pikkuFuncNames from array
102
+ const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker);
103
+ if (middlewareNames.length === 0) {
104
+ logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`);
105
+ return;
106
+ }
107
+ // Collect services from all middleware in the group
108
+ const allServices = new Set();
109
+ for (const middlewareName of middlewareNames) {
110
+ const middlewareMeta = state.middleware.meta[middlewareName];
111
+ if (middlewareMeta && middlewareMeta.services) {
112
+ for (const service of middlewareMeta.services.services) {
113
+ allServices.add(service);
114
+ }
115
+ }
116
+ }
117
+ // Check if this call is wrapped in a factory function
118
+ // We need to walk up the tree to see if the parent is: const x = () => addMiddleware(...)
119
+ let isFactory = false;
120
+ let exportedName = null;
121
+ let parent = node.parent;
122
+ // Check if parent is arrow function: () => addMiddleware(...)
123
+ if (parent && ts.isArrowFunction(parent)) {
124
+ // Check if arrow function has no parameters
125
+ if (parent.parameters.length === 0) {
126
+ isFactory = true;
127
+ // For factories, we need to check the arrow function's parent for the export name
128
+ // const apiTagMiddleware = () => addMiddleware(...)
129
+ const arrowParent = parent.parent;
130
+ if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
131
+ if (ts.isIdentifier(arrowParent.name)) {
132
+ // Check if it's exported
133
+ if (isNamedExport(arrowParent)) {
134
+ exportedName = arrowParent.name.text;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+ // If not a factory, get export name from the call expression itself
141
+ if (!isFactory) {
142
+ const extracted = extractFunctionName(node, checker);
143
+ exportedName = extracted.exportedName;
144
+ }
145
+ // Log warning if not using factory pattern
146
+ if (!isFactory && exportedName) {
147
+ logger.warn(`• Middleware group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
148
+ `For tree-shaking, use: export const ${exportedName} = () => addMiddleware('${tag}', [...])`);
149
+ }
150
+ // Store group metadata
151
+ state.middleware.tagMiddleware.set(tag, {
152
+ exportName: exportedName,
153
+ sourceFile: node.getSourceFile().fileName,
154
+ position: node.getStart(),
155
+ services: {
156
+ optimized: false,
157
+ services: Array.from(allServices),
158
+ },
159
+ middlewareCount: middlewareNames.length,
160
+ isFactory,
161
+ });
162
+ logger.debug(`• Found tag middleware group: ${tag} -> [${middlewareNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
163
+ return;
164
+ }
165
+ // Handle addHTTPMiddleware(pattern, [middleware1, middleware2])
166
+ // Supports two patterns:
167
+ // 1. export const x = () => addHTTPMiddleware('*', [...]) (factory - tree-shakeable)
168
+ // 2. export const x = addHTTPMiddleware('*', [...]) (direct - no tree-shaking)
169
+ if (expression.text === 'addHTTPMiddleware') {
170
+ const patternArg = args[0];
171
+ const middlewareArrayArg = args[1];
172
+ if (!patternArg || !middlewareArrayArg)
173
+ return;
174
+ // Extract route pattern
175
+ let pattern;
176
+ if (ts.isStringLiteral(patternArg)) {
177
+ pattern = patternArg.text;
178
+ }
179
+ if (!pattern) {
180
+ logger.warn(`• addHTTPMiddleware call without valid pattern string`);
181
+ return;
182
+ }
183
+ // Check if middleware array is a literal array
184
+ if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
185
+ logger.error(`• addHTTPMiddleware('${pattern}', ...) must have a literal array as second argument`);
186
+ return;
187
+ }
188
+ // Extract middleware pikkuFuncNames from array
189
+ const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker);
190
+ if (middlewareNames.length === 0) {
191
+ logger.warn(`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`);
192
+ return;
193
+ }
194
+ // Collect services from all middleware in the group
195
+ const allServices = new Set();
196
+ for (const middlewareName of middlewareNames) {
197
+ const middlewareMeta = state.middleware.meta[middlewareName];
198
+ if (middlewareMeta && middlewareMeta.services) {
199
+ for (const service of middlewareMeta.services.services) {
200
+ allServices.add(service);
201
+ }
202
+ }
203
+ }
204
+ // Check if this call is wrapped in a factory function
205
+ let isFactory = false;
206
+ let exportedName = null;
207
+ let parent = node.parent;
208
+ // Check if parent is arrow function: () => addHTTPMiddleware(...)
209
+ if (parent && ts.isArrowFunction(parent)) {
210
+ // Check if arrow function has no parameters
211
+ if (parent.parameters.length === 0) {
212
+ isFactory = true;
213
+ // For factories, we need to check the arrow function's parent for the export name
214
+ // const apiRouteMiddleware = () => addHTTPMiddleware(...)
215
+ const arrowParent = parent.parent;
216
+ if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
217
+ if (ts.isIdentifier(arrowParent.name)) {
218
+ // Check if it's exported
219
+ if (isNamedExport(arrowParent)) {
220
+ exportedName = arrowParent.name.text;
221
+ }
222
+ }
223
+ }
224
+ }
225
+ }
226
+ // If not a factory, get export name from the call expression itself
227
+ if (!isFactory) {
228
+ const extracted = extractFunctionName(node, checker);
229
+ exportedName = extracted.exportedName;
230
+ }
231
+ // Log warning if not using factory pattern
232
+ if (!isFactory && exportedName) {
233
+ logger.warn(`• HTTP middleware group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
234
+ `For tree-shaking, use: export const ${exportedName} = () => addHTTPMiddleware('${pattern}', [...])`);
235
+ }
236
+ // Store group metadata
237
+ state.http.routeMiddleware.set(pattern, {
238
+ exportName: exportedName,
239
+ sourceFile: node.getSourceFile().fileName,
240
+ position: node.getStart(),
241
+ services: {
242
+ optimized: false,
243
+ services: Array.from(allServices),
244
+ },
245
+ middlewareCount: middlewareNames.length,
246
+ isFactory,
247
+ });
248
+ logger.debug(`• Found HTTP route middleware group: ${pattern} -> [${middlewareNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
249
+ return;
250
+ }
251
+ };
@@ -0,0 +1,6 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspect pikkuPermission calls and extract first-arg destructuring
4
+ * for tree shaking optimization.
5
+ */
6
+ export declare const addPermission: AddWiring;
@@ -1,10 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { extractFunctionName, extractServicesFromFunction } from './utils.js';
2
+ import { extractFunctionName } from '../utils/extract-function-name.js';
3
+ import { extractServicesFromFunction } from '../utils/extract-services.js';
3
4
  /**
4
5
  * Inspect pikkuPermission calls and extract first-arg destructuring
5
6
  * for tree shaking optimization.
6
7
  */
7
- export function addPermission(node, checker, state, logger) {
8
+ export const addPermission = (logger, node, checker, state) => {
8
9
  if (!ts.isCallExpression(node))
9
10
  return;
10
11
  const { expression, arguments: args } = node;
@@ -32,4 +33,4 @@ export function addPermission(node, checker, state, logger) {
32
33
  exportedName,
33
34
  };
34
35
  logger.debug(`• Found permission with services: ${services.services.join(', ')}`);
35
- }
36
+ };
@@ -0,0 +1,2 @@
1
+ import { AddWiring } from '../types.js';
2
+ export declare const addQueueWorker: AddWiring;
@@ -1,8 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { PikkuWiringTypes } from '@pikku/core';
4
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
- export const addQueueWorker = (node, checker, state, filters, logger) => {
4
+ import { extractFunctionName } from '../utils/extract-function-name.js';
5
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
+ import { matchesFilters } from '../utils/filter-utils.js';
7
+ import { resolveMiddleware } from '../utils/middleware.js';
8
+ export const addQueueWorker = (logger, node, checker, state, options) => {
6
9
  if (!ts.isCallExpression(node)) {
7
10
  return;
8
11
  }
@@ -33,16 +36,19 @@ export const addQueueWorker = (node, checker, state, filters, logger) => {
33
36
  return;
34
37
  }
35
38
  const filePath = node.getSourceFile().fileName;
36
- if (!matchesFilters(filters, { tags }, { type: PikkuWiringTypes.queue, name: queueName, filePath }, logger)) {
39
+ if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.queue, name: queueName, filePath }, logger)) {
37
40
  console.info(`• Skipping queue processor '${pikkuFuncName}' for queue '${queueName}' due to filter mismatch.`);
38
41
  return;
39
42
  }
43
+ // --- resolve middleware ---
44
+ const middleware = resolveMiddleware(state, obj, tags, checker);
40
45
  state.queueWorkers.files.add(node.getSourceFile().fileName);
41
46
  state.queueWorkers.meta[queueName] = {
42
47
  pikkuFuncName,
43
48
  queueName,
44
49
  docs,
45
50
  tags,
51
+ middleware,
46
52
  };
47
53
  }
48
54
  };
@@ -1,5 +1,5 @@
1
1
  import * as ts from 'typescript';
2
- import { InspectorState, InspectorLogger } from './types.js';
2
+ import { InspectorState, InspectorLogger } from '../types.js';
3
3
  /**
4
4
  * Scan for rpc.invoke() calls to track which functions are actually being invoked
5
5
  */
@@ -0,0 +1,2 @@
1
+ import { AddWiring } from '../types.js';
2
+ export declare const addSchedule: AddWiring;
@@ -1,8 +1,11 @@
1
1
  import * as ts from 'typescript';
2
- import { getPropertyValue } from './get-property-value.js';
2
+ import { getPropertyValue } from '../utils/get-property-value.js';
3
3
  import { PikkuWiringTypes } from '@pikku/core';
4
- import { extractFunctionName, getPropertyAssignmentInitializer, matchesFilters, } from './utils.js';
5
- export const addSchedule = (node, checker, state, filters, logger) => {
4
+ import { extractFunctionName } from '../utils/extract-function-name.js';
5
+ import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
6
+ import { matchesFilters } from '../utils/filter-utils.js';
7
+ import { resolveMiddleware } from '../utils/middleware.js';
8
+ export const addSchedule = (logger, node, checker, state, options) => {
6
9
  if (!ts.isCallExpression(node)) {
7
10
  return;
8
11
  }
@@ -32,9 +35,11 @@ export const addSchedule = (node, checker, state, filters, logger) => {
32
35
  return;
33
36
  }
34
37
  const filePath = node.getSourceFile().fileName;
35
- if (!matchesFilters(filters, { tags }, { type: PikkuWiringTypes.scheduler, name: nameValue, filePath }, logger)) {
38
+ if (!matchesFilters(options.filters || {}, { tags }, { type: PikkuWiringTypes.scheduler, name: nameValue, filePath }, logger)) {
36
39
  return;
37
40
  }
41
+ // --- resolve middleware ---
42
+ const middleware = resolveMiddleware(state, obj, tags, checker);
38
43
  state.scheduledTasks.files.add(node.getSourceFile().fileName);
39
44
  state.scheduledTasks.meta[nameValue] = {
40
45
  pikkuFuncName,
@@ -42,6 +47,7 @@ export const addSchedule = (node, checker, state, filters, logger) => {
42
47
  schedule: scheduleValue,
43
48
  docs,
44
49
  tags,
50
+ middleware,
45
51
  };
46
52
  }
47
53
  };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export { inspect } from './inspector.js';
2
+ export { getFilesAndMethods } from './utils/get-files-and-methods.js';
2
3
  export type { TypesMap } from './types-map.js';
3
4
  export type * from './types.js';
4
5
  export type { InspectorState } from './types.js';
6
+ export type { FilesAndMethods, FilesAndMethodsErrors, } from './utils/get-files-and-methods.js';