@pikku/inspector 0.10.1 → 0.11.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 (38) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/dist/add/add-channel.js +68 -14
  3. package/dist/add/add-functions.js +9 -2
  4. package/dist/add/add-workflow.d.ts +6 -0
  5. package/dist/add/add-workflow.js +152 -0
  6. package/dist/error-codes.d.ts +4 -1
  7. package/dist/error-codes.js +4 -0
  8. package/dist/index.d.ts +1 -1
  9. package/dist/index.js +1 -1
  10. package/dist/inspector.d.ts +6 -0
  11. package/dist/inspector.js +53 -15
  12. package/dist/types.d.ts +10 -2
  13. package/dist/utils/extract-node-value.d.ts +24 -0
  14. package/dist/utils/extract-node-value.js +79 -0
  15. package/dist/utils/post-process.d.ts +1 -1
  16. package/dist/utils/post-process.js +30 -0
  17. package/dist/utils/serialize-inspector-state.d.ts +6 -0
  18. package/dist/utils/serialize-inspector-state.js +12 -0
  19. package/dist/utils/type-utils.d.ts +4 -0
  20. package/dist/utils/type-utils.js +60 -3
  21. package/dist/visit.js +2 -0
  22. package/package.json +2 -2
  23. package/src/add/add-channel.ts +94 -19
  24. package/src/add/add-functions.ts +10 -2
  25. package/src/add/add-workflow.ts +231 -0
  26. package/src/error-codes.ts +5 -0
  27. package/src/index.ts +1 -1
  28. package/src/inspector.ts +77 -22
  29. package/src/types.ts +10 -2
  30. package/src/utils/extract-node-value.ts +101 -0
  31. package/src/utils/post-process.ts +40 -2
  32. package/src/utils/serialize-inspector-state.ts +18 -0
  33. package/src/utils/test-data/inspector-state.json +4 -0
  34. package/src/utils/type-utils.ts +74 -3
  35. package/src/visit.ts +3 -1
  36. package/tsconfig.tsbuildinfo +1 -1
  37. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  38. package/src/add/add-mcp-resource.ts.tmp +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,59 @@
1
+ ## 0.11.0
2
+
3
+ ### Minor Changes
4
+
5
+ - Add workflow inspection and analysis
6
+ - Add enhanced type extraction utilities
7
+
8
+
1
9
  # @pikku/inspector
2
10
 
11
+ ## 0.10.2
12
+
13
+ ### Patch Changes
14
+
15
+ - 1967172: Update code generation to support channel middleware enhancements
16
+
17
+ **Code Generation Updates:**
18
+
19
+ - Update channel type serialization to include middleware support
20
+ - Improve WebSocket wrapper generation for middleware handling
21
+ - Update CLI channel client generation with better type support
22
+ - Enhance services and schema generation for channel configurations
23
+
24
+ **Inspector Updates:**
25
+
26
+ - Improve channel metadata extraction for middleware
27
+ - Better type analysis for channel lifecycle functions
28
+ - Enhanced post-processing for channel configurations
29
+
30
+ - 753481a: Add bootstrap command, performance optimizations, and CLI improvements
31
+
32
+ **New Features:**
33
+
34
+ - Add `pikku bootstrap` command for type-only generation (~13.5% faster than `pikku all`)
35
+ - Add configurable `ignoreFiles` option to pikku.config.json with sensible defaults (_.gen.ts, _.test.ts, \*.spec.ts)
36
+ - Export pikkuCLIRender helper from serialize-cli-types.ts with JSDoc documentation
37
+
38
+ **Performance Improvements:**
39
+
40
+ - Add aggressive TypeScript compiler options (skipDefaultLibCheck, types: []) - ~37% faster TypeScript setup
41
+ - Add detailed performance timing to inspector phases (--logLevel=debug)
42
+ - Optimize file inspection with ignore patterns - ~10-20% faster overall
43
+
44
+ **Enhancements:**
45
+
46
+ - Fix --logLevel flag to properly apply log level to logger
47
+ - Update middleware logging to use structured log format
48
+ - Improve CLI renderers to consistently use destructured logger service
49
+ - Fix middleware file generation when middleware groups exist
50
+
51
+ - 44d71a8: fix: fixing inspector ensuring pikkuConfig is set
52
+ - Updated dependencies [ea652dc]
53
+ - Updated dependencies [4349ec5]
54
+ - Updated dependencies [44d71a8]
55
+ - @pikku/core@0.10.2
56
+
3
57
  ## 0.10.1
4
58
 
5
59
  ### Patch Changes
@@ -94,7 +94,15 @@ export function addMessagesRoutes(logger, obj, state, checker) {
94
94
  const init = getInitializerOf(routeElem);
95
95
  if (!init)
96
96
  continue;
97
- const routeKey = routeElem.name.getText();
97
+ // Get the route key, stripping quotes if it's a string literal
98
+ const routeName = routeElem.name;
99
+ if (!routeName)
100
+ continue;
101
+ let routeKey = routeName.getText();
102
+ // For string literals like 'greet' or "greet", strip the quotes
103
+ if (ts.isStringLiteral(routeName)) {
104
+ routeKey = routeName.text;
105
+ }
98
106
  // For shorthand properties, we need to resolve the identifier to its declaration
99
107
  if (ts.isShorthandPropertyAssignment(routeElem)) {
100
108
  // Get the symbol for the shorthand property
@@ -133,8 +141,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
133
141
  // Look up in the registry
134
142
  const fnMeta = state.functions.meta[handlerName];
135
143
  if (fnMeta) {
144
+ // Resolve middleware for this route
145
+ const routeTags = ts.isObjectLiteralExpression(init)
146
+ ? getPropertyTags(init, 'channel', channelKey, logger)
147
+ : undefined;
148
+ const routeMiddleware = ts.isObjectLiteralExpression(init)
149
+ ? resolveMiddleware(state, init, routeTags, checker)
150
+ : undefined;
136
151
  result[channelKey][routeKey] = {
137
152
  pikkuFuncName: handlerName,
153
+ middleware: routeMiddleware,
138
154
  };
139
155
  continue;
140
156
  }
@@ -147,8 +163,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
147
163
  // Look up in the registry
148
164
  const fnMeta = state.functions.meta[handlerName];
149
165
  if (fnMeta) {
166
+ // Resolve middleware for this route
167
+ const routeTags = ts.isObjectLiteralExpression(init)
168
+ ? getPropertyTags(init, 'channel', channelKey, logger)
169
+ : undefined;
170
+ const routeMiddleware = ts.isObjectLiteralExpression(init)
171
+ ? resolveMiddleware(state, init, routeTags, checker)
172
+ : undefined;
150
173
  result[channelKey][routeKey] = {
151
174
  pikkuFuncName: handlerName,
175
+ middleware: routeMiddleware,
152
176
  };
153
177
  continue;
154
178
  }
@@ -172,8 +196,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
172
196
  const handlerName = pikkuFuncName;
173
197
  const fnMeta = state.functions.meta[handlerName];
174
198
  if (fnMeta) {
199
+ // Resolve middleware for this route
200
+ const routeTags = ts.isObjectLiteralExpression(init)
201
+ ? getPropertyTags(init, 'channel', channelKey, logger)
202
+ : undefined;
203
+ const routeMiddleware = ts.isObjectLiteralExpression(init)
204
+ ? resolveMiddleware(state, init, routeTags, checker)
205
+ : undefined;
175
206
  result[channelKey][routeKey] = {
176
207
  pikkuFuncName: handlerName,
208
+ middleware: routeMiddleware,
177
209
  };
178
210
  continue;
179
211
  }
@@ -183,8 +215,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
183
215
  const handlerName = pikkuFuncName;
184
216
  const fnMeta = state.functions.meta[handlerName];
185
217
  if (fnMeta) {
218
+ // Resolve middleware for this route
219
+ const routeTags = ts.isObjectLiteralExpression(init)
220
+ ? getPropertyTags(init, 'channel', channelKey, logger)
221
+ : undefined;
222
+ const routeMiddleware = ts.isObjectLiteralExpression(init)
223
+ ? resolveMiddleware(state, init, routeTags, checker)
224
+ : undefined;
186
225
  result[channelKey][routeKey] = {
187
226
  pikkuFuncName: handlerName,
227
+ middleware: routeMiddleware,
188
228
  };
189
229
  continue;
190
230
  }
@@ -239,8 +279,16 @@ export function addMessagesRoutes(logger, obj, state, checker) {
239
279
  // Now use this handlerName to look up in the registry
240
280
  const fnMeta = state.functions.meta[handlerName];
241
281
  if (fnMeta) {
282
+ // Resolve middleware for this route
283
+ const routeTags = ts.isObjectLiteralExpression(init)
284
+ ? getPropertyTags(init, 'channel', channelKey, logger)
285
+ : undefined;
286
+ const routeMiddleware = ts.isObjectLiteralExpression(init)
287
+ ? resolveMiddleware(state, init, routeTags, checker)
288
+ : undefined;
242
289
  result[channelKey][routeKey] = {
243
290
  pikkuFuncName: handlerName,
291
+ middleware: routeMiddleware,
244
292
  };
245
293
  continue; // Skip the normal processing below
246
294
  }
@@ -259,8 +307,17 @@ export function addMessagesRoutes(logger, obj, state, checker) {
259
307
  logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for handler '${handlerName}'`);
260
308
  continue;
261
309
  }
310
+ // Resolve middleware and permissions for this route
311
+ // Check if the route config is an object literal with middleware/permissions
312
+ const routeTags = ts.isObjectLiteralExpression(init)
313
+ ? getPropertyTags(init, 'channel', channelKey, logger)
314
+ : undefined;
315
+ const routeMiddleware = ts.isObjectLiteralExpression(init)
316
+ ? resolveMiddleware(state, init, routeTags, checker)
317
+ : undefined;
262
318
  result[channelKey][routeKey] = {
263
319
  pikkuFuncName: handlerName,
320
+ middleware: routeMiddleware,
264
321
  };
265
322
  }
266
323
  }
@@ -295,24 +352,21 @@ export const addChannel = (logger, node, checker, state, options) => {
295
352
  const docs = getPropertyValue(obj, 'docs');
296
353
  const tags = getPropertyTags(obj, 'Channel', route, logger);
297
354
  const query = getPropertyValue(obj, 'query');
298
- const connect = getPropertyAssignmentInitializer(obj, 'onConnect', false, checker);
299
- const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect', false, checker);
355
+ const connect = getPropertyAssignmentInitializer(obj, 'onConnect', true, checker);
356
+ const disconnect = getPropertyAssignmentInitializer(obj, 'onDisconnect', true, checker);
300
357
  // default onMessage handler
301
358
  let message = null;
302
- const onMsgProp = getPropertyAssignmentInitializer(obj, 'onMessage', false, checker);
359
+ const onMsgProp = getPropertyAssignmentInitializer(obj, 'onMessage', true, checker);
303
360
  if (onMsgProp) {
304
- const handlerName = onMsgProp &&
305
- getHandlerNameFromExpression(onMsgProp, checker, state.rootDir);
306
- const fnMeta = handlerName && state.functions.meta[handlerName];
361
+ const { pikkuFuncName } = extractFunctionName(onMsgProp, checker, state.rootDir);
362
+ const fnMeta = state.functions.meta[pikkuFuncName];
307
363
  if (!fnMeta) {
308
- console.error(`No function metadata for onMessage handler '${handlerName}'`);
309
- throw new Error();
310
- }
311
- else {
312
- message = {
313
- pikkuFuncName: extractFunctionName(onMsgProp, checker, state.rootDir).pikkuFuncName,
314
- };
364
+ logger.critical(ErrorCode.FUNCTION_METADATA_NOT_FOUND, `No function metadata found for onMessage handler '${pikkuFuncName}'`);
365
+ return;
315
366
  }
367
+ message = {
368
+ pikkuFuncName,
369
+ };
316
370
  }
317
371
  // nested message-routes
318
372
  const messageWirings = addMessagesRoutes(logger, obj, state, checker);
@@ -233,6 +233,7 @@ export const addFunctions = (logger, node, checker, state) => {
233
233
  const { pikkuFuncName, name, explicitName, exportedName } = extractFunctionName(node, checker, state.rootDir);
234
234
  let tags;
235
235
  let expose;
236
+ let internal;
236
237
  let docs;
237
238
  let objectNode;
238
239
  // determine the actual handler expression:
@@ -244,6 +245,7 @@ export const addFunctions = (logger, node, checker, state) => {
244
245
  objectNode = handlerNode;
245
246
  tags = getPropertyValue(handlerNode, 'tags') || undefined;
246
247
  expose = getPropertyValue(handlerNode, 'expose');
248
+ internal = getPropertyValue(handlerNode, 'internal');
247
249
  docs = getPropertyValue(handlerNode, 'docs');
248
250
  const fnProp = getPropertyAssignmentInitializer(handlerNode, 'func', true, checker);
249
251
  if (!fnProp ||
@@ -342,6 +344,7 @@ export const addFunctions = (logger, node, checker, state) => {
342
344
  inputs: inputNames.filter((n) => n !== 'void') ?? null,
343
345
  outputs: outputNames.filter((n) => n !== 'void') ?? null,
344
346
  expose: expose || undefined,
347
+ internal: internal || undefined,
345
348
  tags: tags || undefined,
346
349
  docs: docs || undefined,
347
350
  isDirectFunction,
@@ -363,6 +366,10 @@ export const addFunctions = (logger, node, checker, state) => {
363
366
  logger.error(`• Function with explicit name '${name}' is not exported, this is not allowed.`);
364
367
  return;
365
368
  }
369
+ // Mark internal functions as invoked to force bundling
370
+ if (internal) {
371
+ state.rpc.invokedFunctions.add(pikkuFuncName);
372
+ }
366
373
  if (expose) {
367
374
  state.rpc.exposedMeta[name] = pikkuFuncName;
368
375
  state.rpc.exposedFiles.set(name, {
@@ -376,8 +383,8 @@ export const addFunctions = (logger, node, checker, state) => {
376
383
  state.rpc.internalMeta[name] = pikkuFuncName;
377
384
  // But we only import the actual function if it's actually invoked to keep
378
385
  // bundle size down
379
- if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose) {
380
- state.rpc.internalFiles.set(name, {
386
+ if (state.rpc.invokedFunctions.has(pikkuFuncName) || expose || internal) {
387
+ state.rpc.internalFiles.set(pikkuFuncName, {
381
388
  path: node.getSourceFile().fileName,
382
389
  exportedName,
383
390
  });
@@ -0,0 +1,6 @@
1
+ import { AddWiring } from '../types.js';
2
+ /**
3
+ * Inspector for wireWorkflow() calls
4
+ * Detects workflow registration and extracts metadata
5
+ */
6
+ export declare const addWorkflow: AddWiring;
@@ -0,0 +1,152 @@
1
+ import * as ts from 'typescript';
2
+ import { getPropertyValue, getPropertyTags, } from '../utils/get-property-value.js';
3
+ import { extractFunctionName } from '../utils/extract-function-name.js';
4
+ import { getPropertyAssignmentInitializer, resolveFunctionDeclaration, } from '../utils/type-utils.js';
5
+ import { resolveMiddleware } from '../utils/middleware.js';
6
+ import { extractWireNames } from '../utils/post-process.js';
7
+ import { ErrorCode } from '../error-codes.js';
8
+ import { extractStringLiteral, extractNumberLiteral, extractPropertyString, isStringLike, isFunctionLike, } from '../utils/extract-node-value.js';
9
+ /**
10
+ * Scan for workflow.do() and workflow.sleep() calls to extract workflow steps
11
+ */
12
+ function getWorkflowInvocations(node, checker, state, workflowName, steps) {
13
+ // Look for property access expressions: workflow.do or workflow.sleep
14
+ if (ts.isPropertyAccessExpression(node)) {
15
+ const { name } = node;
16
+ // Check if this is accessing 'do' or 'sleep' property
17
+ if (name.text === 'do' || name.text === 'sleep') {
18
+ // Check if the parent is a call expression
19
+ const parent = node.parent;
20
+ if (ts.isCallExpression(parent) && parent.expression === node) {
21
+ const args = parent.arguments;
22
+ if (name.text === 'do' && args.length >= 2) {
23
+ // workflow.do(stepName, rpcName|fn, data?, options?)
24
+ const stepNameArg = args[0];
25
+ const secondArg = args[1];
26
+ const optionsArg = args.length >= 3 ? args[args.length - 1] : undefined;
27
+ const stepName = extractStringLiteral(stepNameArg, checker);
28
+ const description = extractDescription(optionsArg, checker) ?? undefined;
29
+ // Determine form by checking 2nd argument type
30
+ if (isStringLike(secondArg, checker)) {
31
+ // RPC form: workflow.do(stepName, rpcName, data, options?)
32
+ const rpcName = extractStringLiteral(secondArg, checker);
33
+ steps.push({
34
+ type: 'rpc',
35
+ stepName,
36
+ rpcName,
37
+ description,
38
+ });
39
+ state.rpc.invokedFunctions.add(rpcName);
40
+ }
41
+ else if (isFunctionLike(secondArg)) {
42
+ // Inline form: workflow.do(stepName, fn, options?)
43
+ steps.push({
44
+ type: 'inline',
45
+ stepName: stepName || '<dynamic>',
46
+ description: description || '<dynamic>',
47
+ });
48
+ }
49
+ }
50
+ else if (name.text === 'sleep' && args.length >= 2) {
51
+ // workflow.sleep(stepName, duration)
52
+ const stepNameArg = args[0];
53
+ const durationArg = args[1];
54
+ const stepName = extractStringLiteral(stepNameArg, checker);
55
+ const duration = extractDuration(durationArg, checker);
56
+ steps.push({
57
+ type: 'sleep',
58
+ stepName: stepName || '<dynamic>',
59
+ duration: duration || '<dynamic>',
60
+ });
61
+ }
62
+ }
63
+ }
64
+ }
65
+ // Don't recurse into nested functions - only look at top-level workflow calls
66
+ ts.forEachChild(node, (child) => {
67
+ if (ts.isFunctionDeclaration(child) ||
68
+ ts.isFunctionExpression(child) ||
69
+ ts.isArrowFunction(child)) {
70
+ return;
71
+ }
72
+ getWorkflowInvocations(child, checker, state, workflowName, steps);
73
+ });
74
+ }
75
+ /**
76
+ * Extract description from options object
77
+ */
78
+ function extractDescription(optionsNode, checker) {
79
+ if (!optionsNode || !ts.isObjectLiteralExpression(optionsNode)) {
80
+ return null;
81
+ }
82
+ return extractPropertyString(optionsNode, 'description', checker);
83
+ }
84
+ /**
85
+ * Extract duration value (number or string)
86
+ */
87
+ function extractDuration(node, checker) {
88
+ const numValue = extractNumberLiteral(node);
89
+ if (numValue !== null) {
90
+ return numValue;
91
+ }
92
+ return extractStringLiteral(node, checker);
93
+ }
94
+ /**
95
+ * Inspector for wireWorkflow() calls
96
+ * Detects workflow registration and extracts metadata
97
+ */
98
+ export const addWorkflow = (logger, node, checker, state, options) => {
99
+ if (!ts.isCallExpression(node)) {
100
+ return;
101
+ }
102
+ const args = node.arguments;
103
+ const firstArg = args[0];
104
+ const expression = node.expression;
105
+ // Check if the call is to wireWorkflow
106
+ if (!ts.isIdentifier(expression) || expression.text !== 'wireWorkflow') {
107
+ return;
108
+ }
109
+ if (!firstArg) {
110
+ return;
111
+ }
112
+ if (ts.isObjectLiteralExpression(firstArg)) {
113
+ const obj = firstArg;
114
+ const workflowName = getPropertyValue(obj, 'name');
115
+ const description = getPropertyValue(obj, 'description');
116
+ const docs = getPropertyValue(obj, 'docs') || undefined;
117
+ const tags = getPropertyTags(obj, 'Workflow', workflowName, logger);
118
+ // --- find the referenced function ---
119
+ const funcInitializer = getPropertyAssignmentInitializer(obj, 'func', true, checker);
120
+ if (!workflowName) {
121
+ logger.critical(ErrorCode.MISSING_NAME, `Wasn't able to determine 'name' property for workflow wiring.`);
122
+ return;
123
+ }
124
+ if (!funcInitializer) {
125
+ logger.critical(ErrorCode.MISSING_FUNC, `No valid 'func' property for workflow '${workflowName}'.`);
126
+ return;
127
+ }
128
+ const pikkuFuncName = extractFunctionName(funcInitializer, checker, state.rootDir).pikkuFuncName;
129
+ // --- resolve middleware ---
130
+ const middleware = resolveMiddleware(state, obj, tags, checker);
131
+ // --- track used functions/middleware for service aggregation ---
132
+ state.serviceAggregation.usedFunctions.add(pikkuFuncName);
133
+ extractWireNames(middleware).forEach((name) => state.serviceAggregation.usedMiddleware.add(name));
134
+ state.workflows.files.add(node.getSourceFile().fileName);
135
+ // Extract workflow steps from function body
136
+ // Resolve the identifier to the actual function declaration
137
+ const resolvedFunc = resolveFunctionDeclaration(funcInitializer, checker);
138
+ const steps = [];
139
+ if (resolvedFunc) {
140
+ getWorkflowInvocations(resolvedFunc, checker, state, workflowName, steps);
141
+ }
142
+ state.workflows.meta[workflowName] = {
143
+ pikkuFuncName,
144
+ workflowName,
145
+ description,
146
+ docs,
147
+ tags,
148
+ middleware,
149
+ steps,
150
+ };
151
+ }
152
+ };
@@ -17,6 +17,8 @@ export declare enum ErrorCode {
17
17
  MISSING_QUEUE_NAME = "PKU384",
18
18
  MISSING_CHANNEL_NAME = "PKU400",
19
19
  CLI_CLIENTSIDE_RENDERER_HAS_SERVICES = "PKU672",
20
+ DYNAMIC_STEP_NAME = "PKU529",
21
+ WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED = "PKU600",
20
22
  CONFIG_TYPE_NOT_FOUND = "PKU426",
21
23
  CONFIG_TYPE_UNDEFINED = "PKU427",
22
24
  SCHEMA_NO_ROOT = "PKU431",
@@ -31,5 +33,6 @@ export declare enum ErrorCode {
31
33
  PERMISSION_HANDLER_INVALID = "PKU835",
32
34
  PERMISSION_TAG_INVALID = "PKU836",
33
35
  PERMISSION_EMPTY_ARRAY = "PKU937",
34
- PERMISSION_PATTERN_INVALID = "PKU975"
36
+ PERMISSION_PATTERN_INVALID = "PKU975",
37
+ WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED = "PKU901"
35
38
  }
@@ -19,6 +19,8 @@ export var ErrorCode;
19
19
  ErrorCode["MISSING_QUEUE_NAME"] = "PKU384";
20
20
  ErrorCode["MISSING_CHANNEL_NAME"] = "PKU400";
21
21
  ErrorCode["CLI_CLIENTSIDE_RENDERER_HAS_SERVICES"] = "PKU672";
22
+ ErrorCode["DYNAMIC_STEP_NAME"] = "PKU529";
23
+ ErrorCode["WORKFLOW_ORCHESTRATOR_NOT_CONFIGURED"] = "PKU600";
22
24
  // Configuration errors
23
25
  ErrorCode["CONFIG_TYPE_NOT_FOUND"] = "PKU426";
24
26
  ErrorCode["CONFIG_TYPE_UNDEFINED"] = "PKU427";
@@ -37,4 +39,6 @@ export var ErrorCode;
37
39
  ErrorCode["PERMISSION_TAG_INVALID"] = "PKU836";
38
40
  ErrorCode["PERMISSION_EMPTY_ARRAY"] = "PKU937";
39
41
  ErrorCode["PERMISSION_PATTERN_INVALID"] = "PKU975";
42
+ // Feature Flag
43
+ ErrorCode["WORKFLOW_MULTI_QUEUE_NOT_SUPPORTED"] = "PKU901";
40
44
  })(ErrorCode || (ErrorCode = {}));
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { inspect } from './inspector.js';
1
+ export { inspect, getInitialInspectorState } from './inspector.js';
2
2
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
3
3
  export type { TypesMap } from './types-map.js';
4
4
  export type * from './types.js';
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { inspect } from './inspector.js';
1
+ export { inspect, getInitialInspectorState } from './inspector.js';
2
2
  export { getFilesAndMethods } from './utils/get-files-and-methods.js';
3
3
  export { ErrorCode } from './error-codes.js';
4
4
  export { serializeInspectorState, deserializeInspectorState, } from './utils/serialize-inspector-state.js';
@@ -1,2 +1,8 @@
1
1
  import { InspectorState, InspectorLogger, InspectorOptions } from './types.js';
2
+ /**
3
+ * Creates an initial/empty inspector state with all required properties initialized
4
+ * @param rootDir - The root directory for the project
5
+ * @returns A fresh InspectorState with empty collections
6
+ */
7
+ export declare function getInitialInspectorState(rootDir: string): InspectorState;
2
8
  export declare const inspect: (logger: InspectorLogger, routeFiles: string[], options?: InspectorOptions) => InspectorState;
package/dist/inspector.js CHANGED
@@ -1,19 +1,17 @@
1
1
  import * as ts from 'typescript';
2
+ import { performance } from 'perf_hooks';
2
3
  import { visitSetup, visitRoutes } from './visit.js';
3
4
  import { TypesMap } from './types-map.js';
4
5
  import { getFilesAndMethods } from './utils/get-files-and-methods.js';
5
6
  import { findCommonAncestor } from './utils/find-root-dir.js';
6
7
  import { aggregateRequiredServices } from './utils/post-process.js';
7
- export const inspect = (logger, routeFiles, options = {}) => {
8
- const program = ts.createProgram(routeFiles, {
9
- target: ts.ScriptTarget.ESNext,
10
- module: ts.ModuleKind.CommonJS,
11
- });
12
- const checker = program.getTypeChecker();
13
- const sourceFiles = program.getSourceFiles();
14
- // Infer root directory from source files
15
- const rootDir = findCommonAncestor(routeFiles);
16
- const state = {
8
+ /**
9
+ * Creates an initial/empty inspector state with all required properties initialized
10
+ * @param rootDir - The root directory for the project
11
+ * @returns A fresh InspectorState with empty collections
12
+ */
13
+ export function getInitialInspectorState(rootDir) {
14
+ return {
17
15
  rootDir,
18
16
  singletonServicesTypeImportMap: new Map(),
19
17
  sessionServicesTypeImportMap: new Map(),
@@ -58,6 +56,10 @@ export const inspect = (logger, routeFiles, options = {}) => {
58
56
  meta: {},
59
57
  files: new Set(),
60
58
  },
59
+ workflows: {
60
+ meta: {},
61
+ files: new Set(),
62
+ },
61
63
  rpc: {
62
64
  internalMeta: {},
63
65
  internalFiles: new Map(),
@@ -91,21 +93,57 @@ export const inspect = (logger, routeFiles, options = {}) => {
91
93
  usedFunctions: new Set(),
92
94
  usedMiddleware: new Set(),
93
95
  usedPermissions: new Set(),
96
+ allSingletonServices: [],
97
+ allSessionServices: [],
94
98
  },
95
99
  };
100
+ }
101
+ export const inspect = (logger, routeFiles, options = {}) => {
102
+ const startProgram = performance.now();
103
+ const program = ts.createProgram(routeFiles, {
104
+ target: ts.ScriptTarget.ESNext,
105
+ module: ts.ModuleKind.CommonJS,
106
+ skipLibCheck: true,
107
+ skipDefaultLibCheck: true,
108
+ moduleResolution: ts.ModuleResolutionKind.Node10,
109
+ types: [],
110
+ allowJs: false,
111
+ checkJs: false,
112
+ });
113
+ logger.debug(`Created program in ${(performance.now() - startProgram).toFixed(2)}ms`);
114
+ const startChecker = performance.now();
115
+ const checker = program.getTypeChecker();
116
+ logger.debug(`Got type checker in ${(performance.now() - startChecker).toFixed(2)}ms`);
117
+ const startSourceFiles = performance.now();
118
+ const sourceFiles = program.getSourceFiles();
119
+ logger.debug(`Got source files in ${(performance.now() - startSourceFiles).toFixed(2)}ms`);
120
+ // Infer root directory from source files
121
+ const rootDir = findCommonAncestor(routeFiles);
122
+ const state = getInitialInspectorState(rootDir);
96
123
  // First sweep: add all functions
124
+ const startSetup = performance.now();
97
125
  for (const sourceFile of sourceFiles) {
98
126
  ts.forEachChild(sourceFile, (child) => visitSetup(logger, checker, child, state, options));
99
127
  }
100
- // Second sweep: add all transports
101
- for (const sourceFile of sourceFiles) {
102
- ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, options));
128
+ logger.debug(`Visit setup phase completed in ${(performance.now() - startSetup).toFixed(2)}ms`);
129
+ if (!options.setupOnly) {
130
+ // Second sweep: add all transports
131
+ const startRoutes = performance.now();
132
+ for (const sourceFile of sourceFiles) {
133
+ ts.forEachChild(sourceFile, (child) => visitRoutes(logger, checker, child, state, options));
134
+ }
135
+ logger.debug(`Visit routes phase completed in ${(performance.now() - startRoutes).toFixed(2)}ms`);
103
136
  }
104
137
  // Populate filesAndMethods
138
+ const startFilesAndMethods = performance.now();
105
139
  const { result, errors } = getFilesAndMethods(state, options.types);
106
140
  state.filesAndMethods = result;
107
141
  state.filesAndMethodsErrors = errors;
108
- // Post-processing: Aggregate required services from wired functions/middleware/permissions
109
- aggregateRequiredServices(state);
142
+ logger.debug(`Get files and methods completed in ${(performance.now() - startFilesAndMethods).toFixed(2)}ms`);
143
+ if (!options.setupOnly) {
144
+ const startAggregate = performance.now();
145
+ aggregateRequiredServices(state);
146
+ logger.debug(`Aggregate required services completed in ${(performance.now() - startAggregate).toFixed(2)}ms`);
147
+ }
110
148
  return state;
111
149
  };
package/dist/types.d.ts CHANGED
@@ -2,7 +2,8 @@ import * as ts from 'typescript';
2
2
  import { ChannelsMeta } from '@pikku/core/channel';
3
3
  import { HTTPWiringsMeta } from '@pikku/core/http';
4
4
  import { ScheduledTasksMeta } from '@pikku/core/scheduler';
5
- import { queueWorkersMeta } from '@pikku/core/queue';
5
+ import { QueueWorkersMeta } from '@pikku/core/queue';
6
+ import { WorkflowsMeta } from '@pikku/core/workflow';
6
7
  import { MCPResourceMeta, MCPToolMeta, MCPPromptMeta } from '@pikku/core/mcp';
7
8
  import { CLIMeta } from '@pikku/core/cli';
8
9
  import { TypesMap } from './types-map.js';
@@ -86,6 +87,7 @@ export type InspectorFilters = {
86
87
  httpMethods?: string[];
87
88
  };
88
89
  export type InspectorOptions = Partial<{
90
+ setupOnly: boolean;
89
91
  types: Partial<{
90
92
  configFileType: string;
91
93
  userSessionType: string;
@@ -167,7 +169,11 @@ export interface InspectorState {
167
169
  files: Set<string>;
168
170
  };
169
171
  queueWorkers: {
170
- meta: queueWorkersMeta;
172
+ meta: QueueWorkersMeta;
173
+ files: Set<string>;
174
+ };
175
+ workflows: {
176
+ meta: WorkflowsMeta;
171
177
  files: Set<string>;
172
178
  };
173
179
  rpc: {
@@ -200,5 +206,7 @@ export interface InspectorState {
200
206
  usedFunctions: Set<string>;
201
207
  usedMiddleware: Set<string>;
202
208
  usedPermissions: Set<string>;
209
+ allSingletonServices: string[];
210
+ allSessionServices: string[];
203
211
  };
204
212
  }
@@ -0,0 +1,24 @@
1
+ import * as ts from 'typescript';
2
+ /**
3
+ * Extract string literal value from a TypeScript node.
4
+ * Handles string literals, template literals (including placeholders),
5
+ * and constant variable references.
6
+ */
7
+ export declare function extractStringLiteral(node: ts.Node, checker: ts.TypeChecker): string;
8
+ /**
9
+ * Check if node is string-like (string literal or template expression)
10
+ */
11
+ export declare function isStringLike(node: ts.Node, _checker: ts.TypeChecker): boolean;
12
+ /**
13
+ * Check if node is function-like (arrow, function expression, or function declaration)
14
+ */
15
+ export declare function isFunctionLike(node: ts.Node): boolean;
16
+ /**
17
+ * Extract number literal value from a node
18
+ */
19
+ export declare function extractNumberLiteral(node: ts.Node): number | null;
20
+ /**
21
+ * Extract a property value from an object literal expression
22
+ * Returns the extracted value or null if not found/cannot extract
23
+ */
24
+ export declare function extractPropertyString(objNode: ts.ObjectLiteralExpression, propertyName: string, checker: ts.TypeChecker): string | null;