@pikku/inspector 0.10.2 → 0.11.1

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 (82) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/add/add-channel.js +11 -10
  3. package/dist/add/add-file-with-factory.js +10 -10
  4. package/dist/add/add-functions.js +65 -44
  5. package/dist/add/add-http-route.js +5 -4
  6. package/dist/add/add-mcp-prompt.js +6 -5
  7. package/dist/add/add-mcp-resource.js +6 -5
  8. package/dist/add/add-mcp-tool.js +6 -5
  9. package/dist/add/add-middleware.js +1 -1
  10. package/dist/add/add-permission.js +1 -1
  11. package/dist/add/add-queue-worker.js +6 -5
  12. package/dist/add/add-schedule.js +5 -4
  13. package/dist/add/add-workflow.d.ts +6 -0
  14. package/dist/add/add-workflow.js +178 -0
  15. package/dist/error-codes.d.ts +5 -1
  16. package/dist/error-codes.js +5 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +1 -0
  19. package/dist/inspector.js +13 -5
  20. package/dist/types.d.ts +27 -9
  21. package/dist/utils/extract-function-node.d.ts +10 -0
  22. package/dist/utils/extract-function-node.js +38 -0
  23. package/dist/utils/extract-node-value.d.ts +32 -0
  24. package/dist/utils/extract-node-value.js +103 -0
  25. package/dist/utils/extract-service-metadata.d.ts +19 -0
  26. package/dist/utils/extract-service-metadata.js +244 -0
  27. package/dist/utils/get-files-and-methods.d.ts +3 -3
  28. package/dist/utils/get-files-and-methods.js +3 -3
  29. package/dist/utils/get-property-value.d.ts +13 -6
  30. package/dist/utils/get-property-value.js +51 -43
  31. package/dist/utils/post-process.d.ts +9 -0
  32. package/dist/utils/post-process.js +30 -3
  33. package/dist/utils/serialize-inspector-state.d.ts +21 -4
  34. package/dist/utils/serialize-inspector-state.js +18 -8
  35. package/dist/utils/type-utils.d.ts +4 -0
  36. package/dist/utils/type-utils.js +55 -0
  37. package/dist/utils/write-service-metadata.d.ts +13 -0
  38. package/dist/utils/write-service-metadata.js +37 -0
  39. package/dist/visit.js +4 -2
  40. package/dist/workflow/extract-simple-workflow.d.ts +15 -0
  41. package/dist/workflow/extract-simple-workflow.js +803 -0
  42. package/dist/workflow/patterns.d.ts +39 -0
  43. package/dist/workflow/patterns.js +138 -0
  44. package/dist/workflow/validation.d.ts +28 -0
  45. package/dist/workflow/validation.js +124 -0
  46. package/package.json +4 -4
  47. package/src/add/add-channel.ts +37 -17
  48. package/src/add/add-file-with-factory.ts +10 -10
  49. package/src/add/add-functions.ts +81 -57
  50. package/src/add/add-http-route.ts +10 -5
  51. package/src/add/add-mcp-prompt.ts +11 -7
  52. package/src/add/add-mcp-resource.ts +11 -7
  53. package/src/add/add-mcp-tool.ts +11 -7
  54. package/src/add/add-middleware.ts +1 -1
  55. package/src/add/add-permission.ts +1 -1
  56. package/src/add/add-queue-worker.ts +11 -12
  57. package/src/add/add-schedule.ts +10 -5
  58. package/src/add/add-workflow.ts +241 -0
  59. package/src/error-codes.ts +6 -0
  60. package/src/index.ts +2 -0
  61. package/src/inspector.ts +19 -5
  62. package/src/types.ts +24 -9
  63. package/src/utils/extract-function-node.ts +58 -0
  64. package/src/utils/extract-node-value.ts +132 -0
  65. package/src/utils/extract-service-metadata.ts +353 -0
  66. package/src/utils/filter-inspector-state.test.ts +3 -3
  67. package/src/utils/filter-utils.test.ts +45 -51
  68. package/src/utils/get-files-and-methods.ts +11 -11
  69. package/src/utils/get-property-value.ts +60 -53
  70. package/src/utils/permissions.test.ts +3 -3
  71. package/src/utils/post-process.ts +56 -3
  72. package/src/utils/serialize-inspector-state.ts +37 -15
  73. package/src/utils/test-data/inspector-state.json +13 -9
  74. package/src/utils/type-utils.ts +69 -0
  75. package/src/utils/write-service-metadata.ts +51 -0
  76. package/src/visit.ts +5 -3
  77. package/src/workflow/extract-simple-workflow.ts +1035 -0
  78. package/src/workflow/patterns.ts +182 -0
  79. package/src/workflow/validation.ts +153 -0
  80. package/tsconfig.tsbuildinfo +1 -1
  81. package/src/add/add-mcp-prompt.ts.tmp +0 -0
  82. package/src/add/add-mcp-resource.ts.tmp +0 -0
@@ -1,3 +1,4 @@
1
+ import * as ts from 'typescript'
1
2
  import { InspectorState } from '../types.js'
2
3
  import {
3
4
  FunctionServicesMeta,
@@ -5,6 +6,10 @@ import {
5
6
  PermissionMetadata,
6
7
  } from '@pikku/core'
7
8
  import { extractTypeKeys } from './type-utils.js'
9
+ import {
10
+ extractAllServiceMetadata,
11
+ ServiceMetadata,
12
+ } from './extract-service-metadata.js'
8
13
 
9
14
  /**
10
15
  * Helper to extract wire-level middleware/permission names from metadata.
@@ -82,9 +87,9 @@ function extractAllServices(
82
87
  const servicesTypes = state.typesLookup.get('Services')
83
88
  if (servicesTypes && servicesTypes.length > 0) {
84
89
  const allServiceNames = extractTypeKeys(servicesTypes[0])
85
- // Session services are those in Services but not in SingletonServices
90
+ // Wire services are those in Services but not in SingletonServices
86
91
  const singletonSet = new Set(state.serviceAggregation.allSingletonServices)
87
- state.serviceAggregation.allSessionServices = allServiceNames
92
+ state.serviceAggregation.allWireServices = allServiceNames
88
93
  .filter((name) => !singletonSet.has(name))
89
94
  .sort()
90
95
  }
@@ -206,7 +211,7 @@ export function aggregateRequiredServices(
206
211
  }
207
212
 
208
213
  // 5. Services from session service factories
209
- for (const singletonServices of state.sessionServicesMeta.values()) {
214
+ for (const singletonServices of state.wireServicesMeta.values()) {
210
215
  singletonServices.forEach((service) => {
211
216
  if (!internalServices.has(service)) {
212
217
  requiredServices.add(service)
@@ -214,3 +219,51 @@ export function aggregateRequiredServices(
214
219
  })
215
220
  }
216
221
  }
222
+
223
+ /**
224
+ * Extract service interface metadata for all user-defined services.
225
+ * This extracts metadata for services in SingletonServices and Services types
226
+ * to generate documentation for AI consumption.
227
+ *
228
+ * Must be called after aggregateRequiredServices() to ensure types are loaded.
229
+ */
230
+ export function extractServiceInterfaceMetadata(
231
+ state: InspectorState | Omit<InspectorState, 'typesLookup'>,
232
+ checker: ts.TypeChecker
233
+ ): void {
234
+ if (!('typesLookup' in state)) {
235
+ return
236
+ }
237
+
238
+ const allMetadata: ServiceMetadata[] = []
239
+
240
+ const singletonServicesTypes = state.typesLookup.get('SingletonServices')
241
+ if (singletonServicesTypes && singletonServicesTypes.length > 0) {
242
+ const singletonMeta = extractAllServiceMetadata(
243
+ singletonServicesTypes[0],
244
+ checker,
245
+ state.rootDir
246
+ )
247
+ allMetadata.push(...singletonMeta)
248
+ }
249
+
250
+ const servicesTypes = state.typesLookup.get('Services')
251
+ if (servicesTypes && servicesTypes.length > 0) {
252
+ const wireServicesMeta = extractAllServiceMetadata(
253
+ servicesTypes[0],
254
+ checker,
255
+ state.rootDir
256
+ )
257
+
258
+ const singletonNames = new Set(
259
+ state.serviceAggregation.allSingletonServices
260
+ )
261
+ const uniqueWireServices = wireServicesMeta.filter(
262
+ (meta) => !singletonNames.has(meta.name)
263
+ )
264
+
265
+ allMetadata.push(...uniqueWireServices)
266
+ }
267
+
268
+ state.serviceMetadata = allMetadata
269
+ }
@@ -13,7 +13,7 @@ export interface SerializableInspectorState {
13
13
  { variable: string; type: string | null; typePath: string | null }[],
14
14
  ]
15
15
  >
16
- sessionServicesTypeImportMap: Array<
16
+ wireServicesTypeImportMap: Array<
17
17
  [
18
18
  string,
19
19
  { variable: string; type: string | null; typePath: string | null }[],
@@ -37,13 +37,13 @@ export interface SerializableInspectorState {
37
37
  { variable: string; type: string | null; typePath: string | null }[],
38
38
  ]
39
39
  >
40
- sessionServicesFactories: Array<
40
+ wireServicesFactories: Array<
41
41
  [
42
42
  string,
43
43
  { variable: string; type: string | null; typePath: string | null }[],
44
44
  ]
45
45
  >
46
- sessionServicesMeta: Array<[string, string[]]>
46
+ wireServicesMeta: Array<[string, string[]]>
47
47
  configFactories: Array<
48
48
  [
49
49
  string,
@@ -112,6 +112,10 @@ export interface SerializableInspectorState {
112
112
  meta: InspectorState['queueWorkers']['meta']
113
113
  files: string[]
114
114
  }
115
+ workflows: {
116
+ meta: InspectorState['workflows']['meta']
117
+ files: Array<[string, { path: string; exportedName: string }]>
118
+ }
115
119
  rpc: {
116
120
  internalMeta: InspectorState['rpc']['internalMeta']
117
121
  internalFiles: Array<[string, { path: string; exportedName: string }]>
@@ -163,8 +167,18 @@ export interface SerializableInspectorState {
163
167
  usedMiddleware: string[]
164
168
  usedPermissions: string[]
165
169
  allSingletonServices: string[]
166
- allSessionServices: string[]
170
+ allWireServices: string[]
167
171
  }
172
+ serviceMetadata: Array<{
173
+ name: string
174
+ summary: string
175
+ description: string
176
+ package: string
177
+ path: string
178
+ version: string
179
+ interface: string
180
+ expandedProperties: Record<string, string>
181
+ }>
168
182
  }
169
183
 
170
184
  /**
@@ -200,8 +214,8 @@ export function serializeInspectorState(
200
214
  singletonServicesTypeImportMap: Array.from(
201
215
  state.singletonServicesTypeImportMap.entries()
202
216
  ),
203
- sessionServicesTypeImportMap: Array.from(
204
- state.sessionServicesTypeImportMap.entries()
217
+ wireServicesTypeImportMap: Array.from(
218
+ state.wireServicesTypeImportMap.entries()
205
219
  ),
206
220
  userSessionTypeImportMap: Array.from(
207
221
  state.userSessionTypeImportMap.entries()
@@ -210,10 +224,8 @@ export function serializeInspectorState(
210
224
  singletonServicesFactories: Array.from(
211
225
  state.singletonServicesFactories.entries()
212
226
  ),
213
- sessionServicesFactories: Array.from(
214
- state.sessionServicesFactories.entries()
215
- ),
216
- sessionServicesMeta: Array.from(state.sessionServicesMeta.entries()),
227
+ wireServicesFactories: Array.from(state.wireServicesFactories.entries()),
228
+ wireServicesMeta: Array.from(state.wireServicesMeta.entries()),
217
229
  configFactories: Array.from(state.configFactories.entries()),
218
230
  filesAndMethods: state.filesAndMethods,
219
231
  filesAndMethodsErrors: Array.from(
@@ -243,6 +255,10 @@ export function serializeInspectorState(
243
255
  meta: state.queueWorkers.meta,
244
256
  files: Array.from(state.queueWorkers.files),
245
257
  },
258
+ workflows: {
259
+ meta: state.workflows.meta,
260
+ files: Array.from(state.workflows.files.entries()),
261
+ },
246
262
  rpc: {
247
263
  internalMeta: state.rpc.internalMeta,
248
264
  internalFiles: Array.from(state.rpc.internalFiles.entries()),
@@ -274,8 +290,9 @@ export function serializeInspectorState(
274
290
  usedMiddleware: Array.from(state.serviceAggregation.usedMiddleware),
275
291
  usedPermissions: Array.from(state.serviceAggregation.usedPermissions),
276
292
  allSingletonServices: state.serviceAggregation.allSingletonServices,
277
- allSessionServices: state.serviceAggregation.allSessionServices,
293
+ allWireServices: state.serviceAggregation.allWireServices,
278
294
  },
295
+ serviceMetadata: state.serviceMetadata,
279
296
  }
280
297
  }
281
298
 
@@ -306,12 +323,12 @@ export function deserializeInspectorState(
306
323
  singletonServicesTypeImportMap: new Map(
307
324
  data.singletonServicesTypeImportMap
308
325
  ),
309
- sessionServicesTypeImportMap: new Map(data.sessionServicesTypeImportMap),
326
+ wireServicesTypeImportMap: new Map(data.wireServicesTypeImportMap),
310
327
  userSessionTypeImportMap: new Map(data.userSessionTypeImportMap),
311
328
  configTypeImportMap: new Map(data.configTypeImportMap),
312
329
  singletonServicesFactories: new Map(data.singletonServicesFactories),
313
- sessionServicesFactories: new Map(data.sessionServicesFactories),
314
- sessionServicesMeta: new Map(data.sessionServicesMeta),
330
+ wireServicesFactories: new Map(data.wireServicesFactories),
331
+ wireServicesMeta: new Map(data.wireServicesMeta),
315
332
  configFactories: new Map(data.configFactories),
316
333
  filesAndMethods: data.filesAndMethods,
317
334
  filesAndMethodsErrors: new Map(
@@ -344,6 +361,10 @@ export function deserializeInspectorState(
344
361
  meta: data.queueWorkers.meta,
345
362
  files: new Set(data.queueWorkers.files),
346
363
  },
364
+ workflows: {
365
+ meta: data.workflows.meta,
366
+ files: new Map(data.workflows.files),
367
+ },
347
368
  rpc: {
348
369
  internalMeta: data.rpc.internalMeta,
349
370
  internalFiles: new Map(data.rpc.internalFiles),
@@ -375,7 +396,8 @@ export function deserializeInspectorState(
375
396
  usedMiddleware: new Set(data.serviceAggregation.usedMiddleware),
376
397
  usedPermissions: new Set(data.serviceAggregation.usedPermissions),
377
398
  allSingletonServices: data.serviceAggregation.allSingletonServices,
378
- allSessionServices: data.serviceAggregation.allSessionServices,
399
+ allWireServices: data.serviceAggregation.allWireServices,
379
400
  },
401
+ serviceMetadata: data.serviceMetadata || [],
380
402
  }
381
403
  }
@@ -12,7 +12,7 @@
12
12
  ]
13
13
  ]
14
14
  ],
15
- "sessionServicesTypeImportMap": [
15
+ "wireServicesTypeImportMap": [
16
16
  [
17
17
  "/Users/yasser/git/pikku/pikku/templates/functions/types/application-types.d.ts",
18
18
  [
@@ -60,19 +60,19 @@
60
60
  ]
61
61
  ]
62
62
  ],
63
- "sessionServicesFactories": [
63
+ "wireServicesFactories": [
64
64
  [
65
65
  "src/services.ts",
66
66
  [
67
67
  {
68
- "variable": "createSessionServices",
69
- "type": "CreateSessionServices",
68
+ "variable": "createWireServices",
69
+ "type": "CreateWireServices",
70
70
  "typePath": "src/services.ts"
71
71
  }
72
72
  ]
73
73
  ]
74
74
  ],
75
- "sessionServicesMeta": [["createSessionServices", []]],
75
+ "wireServicesMeta": [["createWireServices", []]],
76
76
  "configFactories": [
77
77
  [
78
78
  "src/services.ts",
@@ -98,7 +98,7 @@
98
98
  "variable": "SingletonServices",
99
99
  "typePath": "/Users/yasser/git/pikku/pikku/templates/functions/types/application-types.d.ts"
100
100
  },
101
- "sessionServicesType": {
101
+ "wireServicesType": {
102
102
  "file": "/Users/yasser/git/pikku/pikku/templates/functions/types/application-types.d.ts",
103
103
  "type": "Services",
104
104
  "variable": "Services",
@@ -122,10 +122,10 @@
122
122
  "variable": "createSingletonServices",
123
123
  "typePath": "src/services.ts"
124
124
  },
125
- "sessionServicesFactory": {
125
+ "wireServicesFactory": {
126
126
  "file": "src/services.ts",
127
- "type": "CreateSessionServices",
128
- "variable": "createSessionServices",
127
+ "type": "CreateWireServices",
128
+ "variable": "createWireServices",
129
129
  "typePath": "src/services.ts"
130
130
  }
131
131
  },
@@ -1260,6 +1260,10 @@
1260
1260
  },
1261
1261
  "files": ["src/queue-worker.wiring.ts"]
1262
1262
  },
1263
+ "workflows": {
1264
+ "meta": {},
1265
+ "files": []
1266
+ },
1263
1267
  "rpc": {
1264
1268
  "internalMeta": {
1265
1269
  "onConnect": "onConnect",
@@ -4,6 +4,75 @@ export const extractTypeKeys = (type: ts.Type): string[] => {
4
4
  return type.getProperties().map((symbol) => symbol.getName())
5
5
  }
6
6
 
7
+ /**
8
+ * Resolve an identifier or call expression to the actual function declaration
9
+ */
10
+ export function resolveFunctionDeclaration(
11
+ node: ts.Node,
12
+ checker: ts.TypeChecker
13
+ ): ts.Node | null {
14
+ // If it's already a function-like node, return it
15
+ if (
16
+ ts.isFunctionDeclaration(node) ||
17
+ ts.isFunctionExpression(node) ||
18
+ ts.isArrowFunction(node)
19
+ ) {
20
+ return node
21
+ }
22
+
23
+ // If it's a call expression (e.g., pikkuWorkflowFunc(...)), get its first argument
24
+ if (ts.isCallExpression(node) && node.arguments.length > 0) {
25
+ const firstArg = node.arguments[0]
26
+ if (ts.isFunctionExpression(firstArg) || ts.isArrowFunction(firstArg)) {
27
+ return firstArg
28
+ }
29
+ }
30
+
31
+ // If it's an identifier, resolve to declaration
32
+ if (ts.isIdentifier(node)) {
33
+ const symbol = checker.getSymbolAtLocation(node)
34
+ if (!symbol) return null
35
+
36
+ // Try valueDeclaration first, then fallback to declarations[0]
37
+ const decl = symbol.valueDeclaration || symbol.declarations?.[0]
38
+ if (!decl) return null
39
+
40
+ // If it's an import specifier, resolve the aliased symbol
41
+ if (ts.isImportSpecifier(decl)) {
42
+ const aliasedSymbol = checker.getAliasedSymbol(symbol)
43
+ if (aliasedSymbol) {
44
+ const aliasedDecl =
45
+ aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
46
+ if (aliasedDecl) {
47
+ // For variable declarations, get the initializer
48
+ if (
49
+ ts.isVariableDeclaration(aliasedDecl) &&
50
+ aliasedDecl.initializer
51
+ ) {
52
+ return resolveFunctionDeclaration(aliasedDecl.initializer, checker)
53
+ }
54
+ // For function declarations, return directly
55
+ if (ts.isFunctionDeclaration(aliasedDecl)) {
56
+ return aliasedDecl
57
+ }
58
+ }
59
+ }
60
+ }
61
+
62
+ // If it's a variable declaration, get the initializer
63
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
64
+ return resolveFunctionDeclaration(decl.initializer, checker)
65
+ }
66
+
67
+ // If it's a function declaration
68
+ if (ts.isFunctionDeclaration(decl)) {
69
+ return decl
70
+ }
71
+ }
72
+
73
+ return null
74
+ }
75
+
7
76
  export function getPropertyAssignmentInitializer(
8
77
  obj: ts.ObjectLiteralExpression,
9
78
  propName: string,
@@ -0,0 +1,51 @@
1
+ import * as fs from 'fs'
2
+ import * as path from 'path'
3
+ import { ServiceMetadata } from './extract-service-metadata.js'
4
+
5
+ /**
6
+ * Write service metadata to a JSON file in .pikku/services directory
7
+ */
8
+ export function writeServiceMetadata(
9
+ serviceMeta: ServiceMetadata,
10
+ outDir: string
11
+ ): void {
12
+ const servicesDir = path.join(outDir, 'services')
13
+
14
+ if (!fs.existsSync(servicesDir)) {
15
+ fs.mkdirSync(servicesDir, { recursive: true })
16
+ }
17
+
18
+ const fileName = `${serviceMeta.name}.gen.json`
19
+ const filePath = path.join(servicesDir, fileName)
20
+
21
+ const jsonContent = JSON.stringify(serviceMeta, null, 2)
22
+ fs.writeFileSync(filePath, jsonContent, 'utf-8')
23
+ }
24
+
25
+ /**
26
+ * Write all service metadata files
27
+ */
28
+ export function writeAllServiceMetadata(
29
+ servicesMetadata: ServiceMetadata[],
30
+ outDir: string
31
+ ): void {
32
+ for (const serviceMeta of servicesMetadata) {
33
+ writeServiceMetadata(serviceMeta, outDir)
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Clean up services directory (remove old service JSON files)
39
+ */
40
+ export function cleanServicesDirectory(outDir: string): void {
41
+ const servicesDir = path.join(outDir, 'services')
42
+
43
+ if (fs.existsSync(servicesDir)) {
44
+ const files = fs.readdirSync(servicesDir)
45
+ for (const file of files) {
46
+ if (file.endsWith('.gen.json')) {
47
+ fs.unlinkSync(path.join(servicesDir, file))
48
+ }
49
+ }
50
+ }
51
+ }
package/src/visit.ts CHANGED
@@ -4,6 +4,7 @@ import { addFileExtendsCoreType } from './add/add-file-extends-core-type.js'
4
4
  import { addHTTPRoute } from './add/add-http-route.js'
5
5
  import { addSchedule } from './add/add-schedule.js'
6
6
  import { addQueueWorker } from './add/add-queue-worker.js'
7
+ import { addWorkflow } from './add/add-workflow.js'
7
8
  import { addMCPResource } from './add/add-mcp-resource.js'
8
9
  import { addMCPTool } from './add/add-mcp-tool.js'
9
10
  import { addMCPPrompt } from './add/add-mcp-prompt.js'
@@ -33,7 +34,7 @@ export const visitSetup = (
33
34
  addFileExtendsCoreType(
34
35
  node,
35
36
  checker,
36
- state.sessionServicesTypeImportMap,
37
+ state.wireServicesTypeImportMap,
37
38
  'CoreServices',
38
39
  state
39
40
  )
@@ -64,8 +65,8 @@ export const visitSetup = (
64
65
  addFileWithFactory(
65
66
  node,
66
67
  checker,
67
- state.sessionServicesFactories,
68
- 'CreateSessionServices',
68
+ state.wireServicesFactories,
69
+ 'CreateWireServices',
69
70
  state
70
71
  )
71
72
 
@@ -74,6 +75,7 @@ export const visitSetup = (
74
75
  addRPCInvocations(node, state, logger)
75
76
  addMiddleware(logger, node, checker, state, options)
76
77
  addPermission(logger, node, checker, state, options)
78
+ addWorkflow(logger, node, checker, state, options)
77
79
 
78
80
  ts.forEachChild(node, (child) =>
79
81
  visitSetup(logger, checker, child, state, options)