@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,21 +1,33 @@
1
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';
2
+ import { extractFunctionName, isNamedExport, makeContextBasedId, } from '../utils/extract-function-name.js';
3
+ import { extractServicesFromFunction, extractUsedWires, } from '../utils/extract-services.js';
4
+ import { extractMiddlewareRefs } from '../utils/middleware.js';
5
5
  import { getPropertyValue } from '../utils/get-property-value.js';
6
6
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js';
7
- /**
8
- * Inspect pikkuMiddleware calls, addMiddleware calls, and addHTTPMiddleware calls
9
- */
7
+ function renameTempDefinitions(state, definitionIds, groupType, groupKey, storeKey = 'middleware') {
8
+ const tempIndices = definitionIds
9
+ .map((name, i) => (name.startsWith('__temp_') ? i : -1))
10
+ .filter((i) => i >= 0);
11
+ for (const idx of tempIndices) {
12
+ const oldId = definitionIds[idx];
13
+ const newId = tempIndices.length === 1
14
+ ? makeContextBasedId(groupType, groupKey)
15
+ : makeContextBasedId(groupType, groupKey, String(idx));
16
+ const existing = state[storeKey].definitions[oldId];
17
+ if (existing) {
18
+ delete state[storeKey].definitions[oldId];
19
+ state[storeKey].definitions[newId] = existing;
20
+ }
21
+ definitionIds[idx] = newId;
22
+ }
23
+ }
10
24
  export const addMiddleware = (logger, node, checker, state) => {
11
25
  if (!ts.isCallExpression(node))
12
26
  return;
13
27
  const { expression, arguments: args } = node;
14
- // only handle specific function calls
15
28
  if (!ts.isIdentifier(expression)) {
16
29
  return;
17
30
  }
18
- // Handle pikkuMiddleware(...) - individual middleware function definition
19
31
  if (expression.text === 'pikkuMiddleware') {
20
32
  const arg = args[0];
21
33
  if (!arg)
@@ -23,14 +35,11 @@ export const addMiddleware = (logger, node, checker, state) => {
23
35
  let actualHandler;
24
36
  let name;
25
37
  let description;
26
- // Check if using object syntax: pikkuMiddleware({ func: ..., name: '...', description: '...' })
27
38
  if (ts.isObjectLiteralExpression(arg)) {
28
- // Extract name and description metadata
29
39
  const nameValue = getPropertyValue(arg, 'name');
30
40
  const descValue = getPropertyValue(arg, 'description');
31
41
  name = typeof nameValue === 'string' ? nameValue : undefined;
32
42
  description = typeof descValue === 'string' ? descValue : undefined;
33
- // Extract the func property
34
43
  const fnProp = getPropertyAssignmentInitializer(arg, 'func', true, checker);
35
44
  if (!fnProp ||
36
45
  (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))) {
@@ -47,9 +56,26 @@ export const addMiddleware = (logger, node, checker, state) => {
47
56
  return;
48
57
  }
49
58
  const services = extractServicesFromFunction(actualHandler);
50
- const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
51
- state.middleware.meta[pikkuFuncName] = {
59
+ const wires = extractUsedWires(actualHandler, 1);
60
+ let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
61
+ if (pikkuFuncId.startsWith('__temp_')) {
62
+ if (ts.isVariableDeclaration(node.parent) &&
63
+ ts.isIdentifier(node.parent.name)) {
64
+ pikkuFuncId = node.parent.name.text;
65
+ }
66
+ else if (ts.isPropertyAssignment(node.parent) &&
67
+ ts.isIdentifier(node.parent.name)) {
68
+ pikkuFuncId = node.parent.name.text;
69
+ }
70
+ else {
71
+ logger.error(`• pikkuMiddleware() must be assigned to a variable or object property. ` +
72
+ `Extract it to a const: const myMiddleware = pikkuMiddleware(...)`);
73
+ return;
74
+ }
75
+ }
76
+ state.middleware.definitions[pikkuFuncId] = {
52
77
  services,
78
+ wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
53
79
  sourceFile: node.getSourceFile().fileName,
54
80
  position: node.getStart(),
55
81
  exportedName,
@@ -59,7 +85,6 @@ export const addMiddleware = (logger, node, checker, state) => {
59
85
  logger.debug(`• Found middleware with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
60
86
  return;
61
87
  }
62
- // Handle pikkuMiddlewareFactory(...) - middleware factory function
63
88
  if (expression.text === 'pikkuMiddlewareFactory') {
64
89
  const factoryNode = args[0];
65
90
  if (!factoryNode)
@@ -69,10 +94,13 @@ export const addMiddleware = (logger, node, checker, state) => {
69
94
  logger.error(`• Handler for pikkuMiddlewareFactory is not a function.`);
70
95
  return;
71
96
  }
72
- // Extract services by looking inside the factory function body
73
- // The factory should return pikkuMiddleware(...), so we need to find that call
74
- // If no wrapper is found, extract from the factory's returned function directly
75
97
  let services = { optimized: false, services: [] };
98
+ let wires = {
99
+ optimized: true,
100
+ wires: [],
101
+ };
102
+ let name;
103
+ let description;
76
104
  const findPikkuMiddlewareCall = (node) => {
77
105
  if (ts.isCallExpression(node)) {
78
106
  const expr = node.expression;
@@ -84,46 +112,70 @@ export const addMiddleware = (logger, node, checker, state) => {
84
112
  };
85
113
  const pikkuMiddlewareCall = findPikkuMiddlewareCall(factoryNode);
86
114
  if (pikkuMiddlewareCall && pikkuMiddlewareCall.arguments[0]) {
87
- const middlewareHandler = pikkuMiddlewareCall.arguments[0];
88
- if (ts.isArrowFunction(middlewareHandler) ||
89
- ts.isFunctionExpression(middlewareHandler)) {
90
- services = extractServicesFromFunction(middlewareHandler);
115
+ const middlewareArg = pikkuMiddlewareCall.arguments[0];
116
+ if (ts.isObjectLiteralExpression(middlewareArg)) {
117
+ const nameValue = getPropertyValue(middlewareArg, 'name');
118
+ const descValue = getPropertyValue(middlewareArg, 'description');
119
+ name = typeof nameValue === 'string' ? nameValue : undefined;
120
+ description = typeof descValue === 'string' ? descValue : undefined;
121
+ const fnProp = getPropertyAssignmentInitializer(middlewareArg, 'func', true, checker);
122
+ if (fnProp &&
123
+ (ts.isArrowFunction(fnProp) || ts.isFunctionExpression(fnProp))) {
124
+ services = extractServicesFromFunction(fnProp);
125
+ wires = extractUsedWires(fnProp, 1);
126
+ }
127
+ }
128
+ else if (ts.isArrowFunction(middlewareArg) ||
129
+ ts.isFunctionExpression(middlewareArg)) {
130
+ services = extractServicesFromFunction(middlewareArg);
131
+ wires = extractUsedWires(middlewareArg, 1);
91
132
  }
92
133
  }
93
134
  else {
94
- // No pikkuMiddleware wrapper found - extract from factory's return value directly
95
- // Factory pattern: (config) => (services, wire, next) => { ... }
96
135
  if (ts.isArrowFunction(factoryNode) ||
97
136
  ts.isFunctionExpression(factoryNode)) {
98
137
  const factoryBody = factoryNode.body;
99
- // Check if the body is an arrow function (direct return)
100
138
  if (ts.isArrowFunction(factoryBody) ||
101
139
  ts.isFunctionExpression(factoryBody)) {
102
140
  services = extractServicesFromFunction(factoryBody);
141
+ wires = extractUsedWires(factoryBody, 1);
103
142
  }
104
143
  }
105
144
  }
106
- const { pikkuFuncName, exportedName } = extractFunctionName(node, checker, state.rootDir);
107
- state.middleware.meta[pikkuFuncName] = {
145
+ let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
146
+ if (pikkuFuncId.startsWith('__temp_')) {
147
+ if (ts.isVariableDeclaration(node.parent) &&
148
+ ts.isIdentifier(node.parent.name)) {
149
+ pikkuFuncId = node.parent.name.text;
150
+ }
151
+ else if (ts.isPropertyAssignment(node.parent) &&
152
+ ts.isIdentifier(node.parent.name)) {
153
+ pikkuFuncId = node.parent.name.text;
154
+ }
155
+ else {
156
+ logger.error(`• pikkuMiddlewareFactory() must be assigned to a variable or object property. ` +
157
+ `Extract it to a const: const myMiddleware = pikkuMiddlewareFactory(...)`);
158
+ return;
159
+ }
160
+ }
161
+ state.middleware.definitions[pikkuFuncId] = {
108
162
  services,
163
+ wires: wires.wires.length > 0 || !wires.optimized ? wires : undefined,
109
164
  sourceFile: node.getSourceFile().fileName,
110
165
  position: node.getStart(),
111
166
  exportedName,
112
167
  factory: true,
168
+ name,
169
+ description,
113
170
  };
114
- logger.debug(`• Found middleware factory with services: ${services.services.join(', ')}`);
171
+ logger.debug(`• Found middleware factory with services: ${services.services.join(', ')}${name ? ` (name: ${name})` : ''}${description ? ` (description: ${description})` : ''}`);
115
172
  return;
116
173
  }
117
- // Handle addMiddleware('tag', [middleware1, middleware2])
118
- // Supports two patterns:
119
- // 1. export const x = () => addMiddleware('tag', [...]) (factory - tree-shakeable)
120
- // 2. export const x = addMiddleware('tag', [...]) (direct - no tree-shaking)
121
174
  if (expression.text === 'addMiddleware') {
122
175
  const tagArg = args[0];
123
176
  const middlewareArrayArg = args[1];
124
177
  if (!tagArg || !middlewareArrayArg)
125
178
  return;
126
- // Extract tag name
127
179
  let tag;
128
180
  if (ts.isStringLiteral(tagArg)) {
129
181
  tag = tagArg.text;
@@ -132,43 +184,47 @@ export const addMiddleware = (logger, node, checker, state) => {
132
184
  logger.warn(`• addMiddleware call without valid tag string`);
133
185
  return;
134
186
  }
135
- // Check if middleware array is a literal array
136
187
  if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
137
188
  logger.error(`• addMiddleware('${tag}', ...) must have a literal array as second argument`);
138
189
  return;
139
190
  }
140
- // Extract middleware pikkuFuncNames from array
141
- const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker, state.rootDir);
142
- if (middlewareNames.length === 0) {
191
+ const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
192
+ if (refs.length === 0) {
143
193
  logger.warn(`• addMiddleware('${tag}', ...) has empty middleware array`);
144
194
  return;
145
195
  }
146
- // Collect services from all middleware in the group
196
+ const definitionIds = refs.map((r) => r.definitionId);
197
+ renameTempDefinitions(state, definitionIds, 'tag', tag);
198
+ const sourceFile = node.getSourceFile().fileName;
199
+ const instanceIds = [];
200
+ for (let i = 0; i < refs.length; i++) {
201
+ const instanceId = makeContextBasedId('tag', tag, String(i));
202
+ state.middleware.instances[instanceId] = {
203
+ definitionId: definitionIds[i],
204
+ sourceFile,
205
+ position: node.getStart(),
206
+ isFactoryCall: refs[i].isFactoryCall,
207
+ };
208
+ instanceIds.push(instanceId);
209
+ }
147
210
  const allServices = new Set();
148
- for (const middlewareName of middlewareNames) {
149
- const middlewareMeta = state.middleware.meta[middlewareName];
150
- if (middlewareMeta && middlewareMeta.services) {
151
- for (const service of middlewareMeta.services.services) {
211
+ for (const defId of definitionIds) {
212
+ const def = state.middleware.definitions[defId];
213
+ if (def?.services) {
214
+ for (const service of def.services.services) {
152
215
  allServices.add(service);
153
216
  }
154
217
  }
155
218
  }
156
- // Check if this call is wrapped in a factory function
157
- // We need to walk up the tree to see if the parent is: const x = () => addMiddleware(...)
158
219
  let isFactory = false;
159
220
  let exportedName = null;
160
221
  let parent = node.parent;
161
- // Check if parent is arrow function: () => addMiddleware(...)
162
222
  if (parent && ts.isArrowFunction(parent)) {
163
- // Check if arrow function has no parameters
164
223
  if (parent.parameters.length === 0) {
165
224
  isFactory = true;
166
- // For factories, we need to check the arrow function's parent for the export name
167
- // const apiTagMiddleware = () => addMiddleware(...)
168
225
  const arrowParent = parent.parent;
169
226
  if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
170
227
  if (ts.isIdentifier(arrowParent.name)) {
171
- // Check if it's exported
172
228
  if (isNamedExport(arrowParent)) {
173
229
  exportedName = arrowParent.name.text;
174
230
  }
@@ -176,41 +232,34 @@ export const addMiddleware = (logger, node, checker, state) => {
176
232
  }
177
233
  }
178
234
  }
179
- // If not a factory, get export name from the call expression itself
180
235
  if (!isFactory) {
181
236
  const extracted = extractFunctionName(node, checker, state.rootDir);
182
237
  exportedName = extracted.exportedName;
183
238
  }
184
- // Log warning if not using factory pattern
185
239
  if (!isFactory && exportedName) {
186
240
  logger.warn(`• Middleware group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
187
241
  `For tree-shaking, use: export const ${exportedName} = () => addMiddleware('${tag}', [...])`);
188
242
  }
189
- // Store group metadata
190
243
  state.middleware.tagMiddleware.set(tag, {
191
244
  exportName: exportedName,
192
- sourceFile: node.getSourceFile().fileName,
245
+ sourceFile,
193
246
  position: node.getStart(),
194
247
  services: {
195
248
  optimized: false,
196
249
  services: Array.from(allServices),
197
250
  },
198
- middlewareCount: middlewareNames.length,
251
+ count: refs.length,
252
+ instanceIds,
199
253
  isFactory,
200
254
  });
201
- logger.debug(`• Found tag middleware group: ${tag} -> [${middlewareNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
255
+ logger.debug(`• Found tag middleware group: ${tag} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
202
256
  return;
203
257
  }
204
- // Handle addHTTPMiddleware(pattern, [middleware1, middleware2])
205
- // Supports two patterns:
206
- // 1. export const x = () => addHTTPMiddleware('*', [...]) (factory - tree-shakeable)
207
- // 2. export const x = addHTTPMiddleware('*', [...]) (direct - no tree-shaking)
208
258
  if (expression.text === 'addHTTPMiddleware') {
209
259
  const patternArg = args[0];
210
260
  const middlewareArrayArg = args[1];
211
261
  if (!patternArg || !middlewareArrayArg)
212
262
  return;
213
- // Extract route pattern
214
263
  let pattern;
215
264
  if (ts.isStringLiteral(patternArg)) {
216
265
  pattern = patternArg.text;
@@ -219,42 +268,47 @@ export const addMiddleware = (logger, node, checker, state) => {
219
268
  logger.warn(`• addHTTPMiddleware call without valid pattern string`);
220
269
  return;
221
270
  }
222
- // Check if middleware array is a literal array
223
271
  if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
224
272
  logger.error(`• addHTTPMiddleware('${pattern}', ...) must have a literal array as second argument`);
225
273
  return;
226
274
  }
227
- // Extract middleware pikkuFuncNames from array
228
- const middlewareNames = extractMiddlewarePikkuNames(middlewareArrayArg, checker, state.rootDir);
229
- if (middlewareNames.length === 0) {
275
+ const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
276
+ if (refs.length === 0) {
230
277
  logger.warn(`• addHTTPMiddleware('${pattern}', ...) has empty middleware array`);
231
278
  return;
232
279
  }
233
- // Collect services from all middleware in the group
280
+ const definitionIds = refs.map((r) => r.definitionId);
281
+ renameTempDefinitions(state, definitionIds, 'http', pattern);
282
+ const sourceFile = node.getSourceFile().fileName;
283
+ const instanceIds = [];
284
+ for (let i = 0; i < refs.length; i++) {
285
+ const instanceId = makeContextBasedId('http', pattern, String(i));
286
+ state.middleware.instances[instanceId] = {
287
+ definitionId: definitionIds[i],
288
+ sourceFile,
289
+ position: node.getStart(),
290
+ isFactoryCall: refs[i].isFactoryCall,
291
+ };
292
+ instanceIds.push(instanceId);
293
+ }
234
294
  const allServices = new Set();
235
- for (const middlewareName of middlewareNames) {
236
- const middlewareMeta = state.middleware.meta[middlewareName];
237
- if (middlewareMeta && middlewareMeta.services) {
238
- for (const service of middlewareMeta.services.services) {
295
+ for (const defId of definitionIds) {
296
+ const def = state.middleware.definitions[defId];
297
+ if (def?.services) {
298
+ for (const service of def.services.services) {
239
299
  allServices.add(service);
240
300
  }
241
301
  }
242
302
  }
243
- // Check if this call is wrapped in a factory function
244
303
  let isFactory = false;
245
304
  let exportedName = null;
246
305
  let parent = node.parent;
247
- // Check if parent is arrow function: () => addHTTPMiddleware(...)
248
306
  if (parent && ts.isArrowFunction(parent)) {
249
- // Check if arrow function has no parameters
250
307
  if (parent.parameters.length === 0) {
251
308
  isFactory = true;
252
- // For factories, we need to check the arrow function's parent for the export name
253
- // const apiRouteMiddleware = () => addHTTPMiddleware(...)
254
309
  const arrowParent = parent.parent;
255
310
  if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
256
311
  if (ts.isIdentifier(arrowParent.name)) {
257
- // Check if it's exported
258
312
  if (isNamedExport(arrowParent)) {
259
313
  exportedName = arrowParent.name.text;
260
314
  }
@@ -262,29 +316,260 @@ export const addMiddleware = (logger, node, checker, state) => {
262
316
  }
263
317
  }
264
318
  }
265
- // If not a factory, get export name from the call expression itself
266
319
  if (!isFactory) {
267
320
  const extracted = extractFunctionName(node, checker, state.rootDir);
268
321
  exportedName = extracted.exportedName;
269
322
  }
270
- // Log warning if not using factory pattern
271
323
  if (!isFactory && exportedName) {
272
324
  logger.warn(`• HTTP middleware group '${exportedName}' for pattern '${pattern}' is not wrapped in a factory function. ` +
273
325
  `For tree-shaking, use: export const ${exportedName} = () => addHTTPMiddleware('${pattern}', [...])`);
274
326
  }
275
- // Store group metadata
276
327
  state.http.routeMiddleware.set(pattern, {
277
328
  exportName: exportedName,
329
+ sourceFile,
330
+ position: node.getStart(),
331
+ services: {
332
+ optimized: false,
333
+ services: Array.from(allServices),
334
+ },
335
+ count: refs.length,
336
+ instanceIds,
337
+ isFactory,
338
+ });
339
+ logger.debug(`• Found HTTP route middleware group: ${pattern} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
340
+ return;
341
+ }
342
+ if (expression.text === 'pikkuChannelMiddleware') {
343
+ const arg = args[0];
344
+ if (!arg)
345
+ return;
346
+ let actualHandler;
347
+ if (ts.isArrowFunction(arg) || ts.isFunctionExpression(arg)) {
348
+ actualHandler = arg;
349
+ }
350
+ else {
351
+ logger.error(`• Handler for pikkuChannelMiddleware is not a function.`);
352
+ return;
353
+ }
354
+ const services = extractServicesFromFunction(actualHandler);
355
+ let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
356
+ if (pikkuFuncId.startsWith('__temp_')) {
357
+ if (ts.isVariableDeclaration(node.parent) &&
358
+ ts.isIdentifier(node.parent.name)) {
359
+ pikkuFuncId = node.parent.name.text;
360
+ }
361
+ else if (ts.isPropertyAssignment(node.parent) &&
362
+ ts.isIdentifier(node.parent.name)) {
363
+ pikkuFuncId = node.parent.name.text;
364
+ }
365
+ else {
366
+ logger.error(`• pikkuChannelMiddleware() must be assigned to a variable or object property. ` +
367
+ `Extract it to a const: const myMiddleware = pikkuChannelMiddleware(...)`);
368
+ return;
369
+ }
370
+ }
371
+ state.channelMiddleware.definitions[pikkuFuncId] = {
372
+ services,
278
373
  sourceFile: node.getSourceFile().fileName,
279
374
  position: node.getStart(),
375
+ exportedName,
376
+ };
377
+ logger.debug(`• Found channel middleware with services: ${services.services.join(', ')}`);
378
+ return;
379
+ }
380
+ if (expression.text === 'pikkuChannelMiddlewareFactory') {
381
+ const factoryNode = args[0];
382
+ if (!factoryNode)
383
+ return;
384
+ if (!ts.isArrowFunction(factoryNode) &&
385
+ !ts.isFunctionExpression(factoryNode)) {
386
+ logger.error(`• Handler for pikkuChannelMiddlewareFactory is not a function.`);
387
+ return;
388
+ }
389
+ let services = { optimized: false, services: [] };
390
+ const findPikkuChannelMiddlewareCall = (n) => {
391
+ if (ts.isCallExpression(n)) {
392
+ const expr = n.expression;
393
+ if (ts.isIdentifier(expr) && expr.text === 'pikkuChannelMiddleware') {
394
+ return n;
395
+ }
396
+ }
397
+ return ts.forEachChild(n, findPikkuChannelMiddlewareCall);
398
+ };
399
+ const channelMiddlewareCall = findPikkuChannelMiddlewareCall(factoryNode);
400
+ if (channelMiddlewareCall && channelMiddlewareCall.arguments[0]) {
401
+ const middlewareHandler = channelMiddlewareCall.arguments[0];
402
+ if (ts.isArrowFunction(middlewareHandler) ||
403
+ ts.isFunctionExpression(middlewareHandler)) {
404
+ services = extractServicesFromFunction(middlewareHandler);
405
+ }
406
+ }
407
+ else {
408
+ if (ts.isArrowFunction(factoryNode) ||
409
+ ts.isFunctionExpression(factoryNode)) {
410
+ const factoryBody = factoryNode.body;
411
+ if (ts.isArrowFunction(factoryBody) ||
412
+ ts.isFunctionExpression(factoryBody)) {
413
+ services = extractServicesFromFunction(factoryBody);
414
+ }
415
+ }
416
+ }
417
+ let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
418
+ if (pikkuFuncId.startsWith('__temp_')) {
419
+ if (ts.isVariableDeclaration(node.parent) &&
420
+ ts.isIdentifier(node.parent.name)) {
421
+ pikkuFuncId = node.parent.name.text;
422
+ }
423
+ else if (ts.isPropertyAssignment(node.parent) &&
424
+ ts.isIdentifier(node.parent.name)) {
425
+ pikkuFuncId = node.parent.name.text;
426
+ }
427
+ else {
428
+ logger.error(`• pikkuChannelMiddlewareFactory() must be assigned to a variable or object property. ` +
429
+ `Extract it to a const: const myMiddleware = pikkuChannelMiddlewareFactory(...)`);
430
+ return;
431
+ }
432
+ }
433
+ state.channelMiddleware.definitions[pikkuFuncId] = {
434
+ services,
435
+ sourceFile: node.getSourceFile().fileName,
436
+ position: node.getStart(),
437
+ exportedName,
438
+ factory: true,
439
+ };
440
+ logger.debug(`• Found channel middleware factory with services: ${services.services.join(', ')}`);
441
+ return;
442
+ }
443
+ if (expression.text === 'pikkuAIMiddleware') {
444
+ const arg = args[0];
445
+ if (!arg)
446
+ return;
447
+ if (!ts.isObjectLiteralExpression(arg)) {
448
+ logger.error(`• pikkuAIMiddleware() requires an object literal argument.`);
449
+ return;
450
+ }
451
+ const allServices = new Set();
452
+ for (const prop of arg.properties) {
453
+ if (ts.isPropertyAssignment(prop) &&
454
+ (ts.isArrowFunction(prop.initializer) ||
455
+ ts.isFunctionExpression(prop.initializer))) {
456
+ const hookServices = extractServicesFromFunction(prop.initializer);
457
+ for (const s of hookServices.services) {
458
+ allServices.add(s);
459
+ }
460
+ }
461
+ }
462
+ const services = {
463
+ optimized: allServices.size > 0,
464
+ services: Array.from(allServices),
465
+ };
466
+ let { pikkuFuncId, exportedName } = extractFunctionName(node, checker, state.rootDir);
467
+ if (pikkuFuncId.startsWith('__temp_')) {
468
+ if (ts.isVariableDeclaration(node.parent) &&
469
+ ts.isIdentifier(node.parent.name)) {
470
+ pikkuFuncId = node.parent.name.text;
471
+ }
472
+ else if (ts.isPropertyAssignment(node.parent) &&
473
+ ts.isIdentifier(node.parent.name)) {
474
+ pikkuFuncId = node.parent.name.text;
475
+ }
476
+ else {
477
+ logger.error(`• pikkuAIMiddleware() must be assigned to a variable or object property. ` +
478
+ `Extract it to a const: const myMiddleware = pikkuAIMiddleware(...)`);
479
+ return;
480
+ }
481
+ }
482
+ state.aiMiddleware.definitions[pikkuFuncId] = {
483
+ services,
484
+ sourceFile: node.getSourceFile().fileName,
485
+ position: node.getStart(),
486
+ exportedName,
487
+ };
488
+ logger.debug(`• Found AI middleware with services: ${services.services.join(', ')}`);
489
+ return;
490
+ }
491
+ if (expression.text === 'addChannelMiddleware') {
492
+ const tagArg = args[0];
493
+ const middlewareArrayArg = args[1];
494
+ if (!tagArg || !middlewareArrayArg)
495
+ return;
496
+ let tag;
497
+ if (ts.isStringLiteral(tagArg)) {
498
+ tag = tagArg.text;
499
+ }
500
+ if (!tag) {
501
+ logger.warn(`• addChannelMiddleware call without valid tag string`);
502
+ return;
503
+ }
504
+ if (!ts.isArrayLiteralExpression(middlewareArrayArg)) {
505
+ logger.error(`• addChannelMiddleware('${tag}', ...) must have a literal array as second argument`);
506
+ return;
507
+ }
508
+ const refs = extractMiddlewareRefs(middlewareArrayArg, checker, state.rootDir);
509
+ if (refs.length === 0) {
510
+ logger.warn(`• addChannelMiddleware('${tag}', ...) has empty middleware array`);
511
+ return;
512
+ }
513
+ const definitionIds = refs.map((r) => r.definitionId);
514
+ renameTempDefinitions(state, definitionIds, 'tag', tag, 'channelMiddleware');
515
+ const sourceFile = node.getSourceFile().fileName;
516
+ const instanceIds = [];
517
+ for (let i = 0; i < refs.length; i++) {
518
+ const instanceId = makeContextBasedId('tag', tag, String(i));
519
+ state.channelMiddleware.instances[instanceId] = {
520
+ definitionId: definitionIds[i],
521
+ sourceFile,
522
+ position: node.getStart(),
523
+ isFactoryCall: refs[i].isFactoryCall,
524
+ };
525
+ instanceIds.push(instanceId);
526
+ }
527
+ const allServices = new Set();
528
+ for (const defId of definitionIds) {
529
+ const def = state.channelMiddleware.definitions[defId];
530
+ if (def?.services) {
531
+ for (const service of def.services.services) {
532
+ allServices.add(service);
533
+ }
534
+ }
535
+ }
536
+ let isFactory = false;
537
+ let exportedName = null;
538
+ let parent = node.parent;
539
+ if (parent && ts.isArrowFunction(parent)) {
540
+ if (parent.parameters.length === 0) {
541
+ isFactory = true;
542
+ const arrowParent = parent.parent;
543
+ if (arrowParent && ts.isVariableDeclaration(arrowParent)) {
544
+ if (ts.isIdentifier(arrowParent.name)) {
545
+ if (isNamedExport(arrowParent)) {
546
+ exportedName = arrowParent.name.text;
547
+ }
548
+ }
549
+ }
550
+ }
551
+ }
552
+ if (!isFactory) {
553
+ const extracted = extractFunctionName(node, checker, state.rootDir);
554
+ exportedName = extracted.exportedName;
555
+ }
556
+ if (!isFactory && exportedName) {
557
+ logger.warn(`• Channel middleware group '${exportedName}' for tag '${tag}' is not wrapped in a factory function. ` +
558
+ `For tree-shaking, use: export const ${exportedName} = () => addChannelMiddleware('${tag}', [...])`);
559
+ }
560
+ state.channelMiddleware.tagMiddleware.set(tag, {
561
+ exportName: exportedName,
562
+ sourceFile,
563
+ position: node.getStart(),
280
564
  services: {
281
565
  optimized: false,
282
566
  services: Array.from(allServices),
283
567
  },
284
- middlewareCount: middlewareNames.length,
568
+ count: refs.length,
569
+ instanceIds,
285
570
  isFactory,
286
571
  });
287
- logger.debug(`• Found HTTP route middleware group: ${pattern} -> [${middlewareNames.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
572
+ logger.debug(`• Found tag channel middleware group: ${tag} -> [${instanceIds.join(', ')}] (${isFactory ? 'factory' : 'direct'})`);
288
573
  return;
289
574
  }
290
575
  };
@@ -1,4 +1,4 @@
1
- import { AddWiring } from '../types.js';
1
+ import type { AddWiring } from '../types.js';
2
2
  /**
3
3
  * Inspect pikkuPermission calls, addPermission calls, and addHTTPPermission calls
4
4
  */