@pikku/inspector 0.11.0 → 0.11.2

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 (109) hide show
  1. package/CHANGELOG.md +32 -2
  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-forge-credential.d.ts +8 -0
  5. package/dist/add/add-forge-credential.js +77 -0
  6. package/dist/add/add-forge-node.d.ts +7 -0
  7. package/dist/add/add-forge-node.js +77 -0
  8. package/dist/add/add-functions.js +158 -51
  9. package/dist/add/add-http-route.js +28 -4
  10. package/dist/add/add-mcp-prompt.js +6 -5
  11. package/dist/add/add-mcp-resource.js +6 -5
  12. package/dist/add/add-mcp-tool.js +6 -5
  13. package/dist/add/add-middleware.js +1 -1
  14. package/dist/add/add-permission.js +1 -1
  15. package/dist/add/add-queue-worker.js +6 -5
  16. package/dist/add/add-rpc-invocations.d.ts +3 -0
  17. package/dist/add/add-rpc-invocations.js +51 -25
  18. package/dist/add/add-schedule.js +5 -4
  19. package/dist/add/add-workflow-graph.d.ts +6 -0
  20. package/dist/add/add-workflow-graph.js +659 -0
  21. package/dist/add/add-workflow.d.ts +1 -1
  22. package/dist/add/add-workflow.js +191 -69
  23. package/dist/error-codes.d.ts +3 -0
  24. package/dist/error-codes.js +3 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +3 -0
  27. package/dist/inspector.js +29 -9
  28. package/dist/types.d.ts +47 -8
  29. package/dist/utils/extract-function-name.js +7 -7
  30. package/dist/utils/extract-function-node.d.ts +10 -0
  31. package/dist/utils/extract-function-node.js +38 -0
  32. package/dist/utils/extract-node-value.d.ts +8 -0
  33. package/dist/utils/extract-node-value.js +24 -0
  34. package/dist/utils/extract-service-metadata.d.ts +19 -0
  35. package/dist/utils/extract-service-metadata.js +244 -0
  36. package/dist/utils/get-files-and-methods.d.ts +3 -3
  37. package/dist/utils/get-files-and-methods.js +3 -3
  38. package/dist/utils/get-property-value.d.ts +14 -6
  39. package/dist/utils/get-property-value.js +55 -43
  40. package/dist/utils/post-process.d.ts +9 -0
  41. package/dist/utils/post-process.js +30 -3
  42. package/dist/utils/serialize-inspector-state.d.ts +42 -6
  43. package/dist/utils/serialize-inspector-state.js +36 -10
  44. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  45. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
  46. package/dist/utils/workflow/dsl/extract-dsl-workflow.d.ts +17 -0
  47. package/dist/utils/workflow/dsl/extract-dsl-workflow.js +1284 -0
  48. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  49. package/dist/utils/workflow/dsl/index.js +7 -0
  50. package/dist/utils/workflow/dsl/patterns.d.ts +60 -0
  51. package/dist/utils/workflow/dsl/patterns.js +218 -0
  52. package/dist/utils/workflow/dsl/validation.d.ts +30 -0
  53. package/dist/utils/workflow/dsl/validation.js +142 -0
  54. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  55. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
  56. package/dist/utils/workflow/graph/index.d.ts +6 -0
  57. package/dist/utils/workflow/graph/index.js +6 -0
  58. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
  59. package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
  60. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
  61. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  62. package/dist/utils/write-service-metadata.d.ts +13 -0
  63. package/dist/utils/write-service-metadata.js +37 -0
  64. package/dist/visit.js +8 -2
  65. package/package.json +16 -4
  66. package/src/add/add-channel.ts +37 -17
  67. package/src/add/add-file-with-factory.ts +10 -10
  68. package/src/add/add-forge-credential.ts +119 -0
  69. package/src/add/add-forge-node.ts +132 -0
  70. package/src/add/add-functions.ts +199 -69
  71. package/src/add/add-http-route.ts +34 -5
  72. package/src/add/add-mcp-prompt.ts +11 -7
  73. package/src/add/add-mcp-resource.ts +11 -7
  74. package/src/add/add-mcp-tool.ts +11 -7
  75. package/src/add/add-middleware.ts +1 -1
  76. package/src/add/add-permission.ts +1 -1
  77. package/src/add/add-queue-worker.ts +11 -12
  78. package/src/add/add-rpc-invocations.ts +61 -31
  79. package/src/add/add-schedule.ts +10 -5
  80. package/src/add/add-workflow-graph.ts +864 -0
  81. package/src/add/add-workflow.ts +212 -116
  82. package/src/error-codes.ts +3 -0
  83. package/src/index.ts +12 -0
  84. package/src/inspector.ts +36 -10
  85. package/src/types.ts +43 -9
  86. package/src/utils/extract-function-name.ts +7 -7
  87. package/src/utils/extract-function-node.ts +58 -0
  88. package/src/utils/extract-node-value.ts +31 -0
  89. package/src/utils/extract-service-metadata.ts +353 -0
  90. package/src/utils/filter-inspector-state.test.ts +3 -3
  91. package/src/utils/filter-utils.test.ts +45 -51
  92. package/src/utils/get-files-and-methods.ts +11 -11
  93. package/src/utils/get-property-value.ts +67 -53
  94. package/src/utils/permissions.test.ts +3 -3
  95. package/src/utils/post-process.ts +56 -3
  96. package/src/utils/serialize-inspector-state.ts +67 -19
  97. package/src/utils/test-data/inspector-state.json +9 -9
  98. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
  99. package/src/utils/workflow/dsl/extract-dsl-workflow.ts +1608 -0
  100. package/src/utils/workflow/dsl/index.ts +11 -0
  101. package/src/utils/workflow/dsl/patterns.ts +279 -0
  102. package/src/utils/workflow/dsl/validation.ts +180 -0
  103. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
  104. package/src/utils/workflow/graph/index.ts +6 -0
  105. package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
  106. package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
  107. package/src/utils/write-service-metadata.ts +51 -0
  108. package/src/visit.ts +9 -3
  109. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,119 @@
1
+ import * as ts from 'typescript'
2
+ import { getPropertyValue } from '../utils/get-property-value.js'
3
+ import { AddWiring } from '../types.js'
4
+ import { ErrorCode } from '../error-codes.js'
5
+
6
+ /**
7
+ * Inspector for wireForgeCredential calls.
8
+ * Extracts metadata for Forge package credential declarations.
9
+ * Note: wireForgeCredential is metadata-only - no runtime behavior.
10
+ * Schema is stored as the variable name reference; actual Zod→JSON Schema conversion happens at CLI build time.
11
+ */
12
+ export const addForgeCredential: AddWiring = (
13
+ logger,
14
+ node,
15
+ _checker,
16
+ state,
17
+ _options
18
+ ) => {
19
+ if (!ts.isCallExpression(node)) {
20
+ return
21
+ }
22
+
23
+ const args = node.arguments
24
+ const firstArg = args[0]
25
+ const expression = node.expression
26
+
27
+ // Check if the call is to wireForgeCredential
28
+ if (
29
+ !ts.isIdentifier(expression) ||
30
+ expression.text !== 'wireForgeCredential'
31
+ ) {
32
+ return
33
+ }
34
+
35
+ if (!firstArg) {
36
+ return
37
+ }
38
+
39
+ if (ts.isObjectLiteralExpression(firstArg)) {
40
+ const obj = firstArg
41
+
42
+ const nameValue = getPropertyValue(obj, 'name') as string | null
43
+ const displayNameValue = getPropertyValue(obj, 'displayName') as
44
+ | string
45
+ | null
46
+ const descriptionValue = getPropertyValue(obj, 'description') as
47
+ | string
48
+ | null
49
+ const secretIdValue = getPropertyValue(obj, 'secretId') as string | null
50
+
51
+ // Get schema variable name for later runtime import
52
+ let schemaVariableName: string | null = null
53
+ for (const prop of obj.properties) {
54
+ if (
55
+ ts.isPropertyAssignment(prop) &&
56
+ ts.isIdentifier(prop.name) &&
57
+ prop.name.text === 'schema'
58
+ ) {
59
+ if (ts.isIdentifier(prop.initializer)) {
60
+ schemaVariableName = prop.initializer.text
61
+ }
62
+ break
63
+ }
64
+ }
65
+
66
+ // Validate required fields
67
+ if (!nameValue) {
68
+ logger.critical(
69
+ ErrorCode.MISSING_NAME,
70
+ "Forge credential is missing the required 'name' property."
71
+ )
72
+ return
73
+ }
74
+
75
+ if (!displayNameValue) {
76
+ logger.critical(
77
+ ErrorCode.MISSING_NAME,
78
+ `Forge credential '${nameValue}' is missing the required 'displayName' property.`
79
+ )
80
+ return
81
+ }
82
+
83
+ if (!secretIdValue) {
84
+ logger.critical(
85
+ ErrorCode.MISSING_NAME,
86
+ `Forge credential '${nameValue}' is missing the required 'secretId' property.`
87
+ )
88
+ return
89
+ }
90
+
91
+ if (!schemaVariableName) {
92
+ logger.critical(
93
+ ErrorCode.MISSING_NAME,
94
+ `Forge credential '${nameValue}' is missing the required 'schema' property or schema is not a variable reference.`
95
+ )
96
+ return
97
+ }
98
+
99
+ const sourceFile = node.getSourceFile().fileName
100
+
101
+ state.forgeCredentials.files.add(sourceFile)
102
+
103
+ // Register the zod schema in the central zodLookup for deferred conversion
104
+ const schemaLookupName = `ForgeCredential_${nameValue}`
105
+ state.zodLookup.set(schemaLookupName, {
106
+ variableName: schemaVariableName,
107
+ sourceFile,
108
+ })
109
+
110
+ // Store metadata - schema conversion happens later in schema-generator
111
+ state.forgeCredentials.meta[nameValue] = {
112
+ name: nameValue,
113
+ displayName: displayNameValue,
114
+ description: descriptionValue || undefined,
115
+ secretId: secretIdValue,
116
+ schema: schemaLookupName,
117
+ }
118
+ }
119
+ }
@@ -0,0 +1,132 @@
1
+ import * as ts from 'typescript'
2
+ import {
3
+ getPropertyValue,
4
+ getCommonWireMetaData,
5
+ } from '../utils/get-property-value.js'
6
+ import { AddWiring } from '../types.js'
7
+ import { ErrorCode } from '../error-codes.js'
8
+ import type { ForgeNodeType } from '@pikku/core/forge-node'
9
+
10
+ /**
11
+ * Inspector for wireForgeNode calls.
12
+ * Extracts metadata for Forge workflow builder nodes.
13
+ * Note: wireForgeNode is metadata-only - no runtime behavior.
14
+ */
15
+ export const addForgeNode: AddWiring = (
16
+ logger,
17
+ node,
18
+ checker,
19
+ state,
20
+ _options
21
+ ) => {
22
+ if (!ts.isCallExpression(node)) {
23
+ return
24
+ }
25
+
26
+ const args = node.arguments
27
+ const firstArg = args[0]
28
+ const expression = node.expression
29
+
30
+ // Check if the call is to wireForgeNode
31
+ if (!ts.isIdentifier(expression) || expression.text !== 'wireForgeNode') {
32
+ return
33
+ }
34
+
35
+ if (!firstArg) {
36
+ return
37
+ }
38
+
39
+ if (ts.isObjectLiteralExpression(firstArg)) {
40
+ const obj = firstArg
41
+
42
+ const nameValue = getPropertyValue(obj, 'name') as string | null
43
+ const displayNameValue = getPropertyValue(obj, 'displayName') as
44
+ | string
45
+ | null
46
+ const categoryValue = getPropertyValue(obj, 'category') as string | null
47
+ const typeValue = getPropertyValue(obj, 'type') as ForgeNodeType | null
48
+ const rpcValue = getPropertyValue(obj, 'rpc') as string | null
49
+ const errorOutputValue = getPropertyValue(obj, 'errorOutput') as
50
+ | boolean
51
+ | null
52
+
53
+ const { tags, description } = getCommonWireMetaData(
54
+ obj,
55
+ 'Forge node',
56
+ nameValue,
57
+ logger
58
+ )
59
+
60
+ // Validate required fields
61
+ if (!nameValue) {
62
+ logger.critical(
63
+ ErrorCode.MISSING_NAME,
64
+ "Forge node is missing the required 'name' property."
65
+ )
66
+ return
67
+ }
68
+
69
+ if (!displayNameValue) {
70
+ logger.critical(
71
+ ErrorCode.MISSING_NAME,
72
+ `Forge node '${nameValue}' is missing the required 'displayName' property.`
73
+ )
74
+ return
75
+ }
76
+
77
+ if (!categoryValue) {
78
+ logger.critical(
79
+ ErrorCode.MISSING_NAME,
80
+ `Forge node '${nameValue}' is missing the required 'category' property.`
81
+ )
82
+ return
83
+ }
84
+
85
+ if (!typeValue) {
86
+ logger.critical(
87
+ ErrorCode.MISSING_NAME,
88
+ `Forge node '${nameValue}' is missing the required 'type' property.`
89
+ )
90
+ return
91
+ }
92
+
93
+ if (!['trigger', 'action', 'end'].includes(typeValue)) {
94
+ logger.critical(
95
+ ErrorCode.INVALID_VALUE,
96
+ `Forge node '${nameValue}' has invalid type '${typeValue}'. Must be 'trigger', 'action', or 'end'.`
97
+ )
98
+ return
99
+ }
100
+
101
+ if (!rpcValue) {
102
+ logger.critical(
103
+ ErrorCode.MISSING_NAME,
104
+ `Forge node '${nameValue}' is missing the required 'rpc' property.`
105
+ )
106
+ return
107
+ }
108
+
109
+ // Get function metadata for input/output schemas
110
+ const fnMeta = state.functions.meta[rpcValue]
111
+ const inputSchemaName = fnMeta?.inputs?.[0] || null
112
+ const outputSchemaName = fnMeta?.outputs?.[0] || null
113
+
114
+ // Note: Category validation against forge.node.categories config
115
+ // is done at CLI build time, not during inspection
116
+
117
+ state.forgeNodes.files.add(node.getSourceFile().fileName)
118
+
119
+ state.forgeNodes.meta[nameValue] = {
120
+ name: nameValue,
121
+ displayName: displayNameValue,
122
+ category: categoryValue,
123
+ type: typeValue,
124
+ rpc: rpcValue,
125
+ description,
126
+ errorOutput: errorOutputValue ?? false,
127
+ inputSchemaName,
128
+ outputSchemaName,
129
+ tags,
130
+ }
131
+ }
132
+ }
@@ -2,10 +2,15 @@ import * as ts from 'typescript'
2
2
  import { AddWiring } from '../types.js'
3
3
  import { TypesMap } from '../types-map.js'
4
4
  import { extractFunctionName } from '../utils/extract-function-name.js'
5
- import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
6
- import { FunctionServicesMeta, PikkuDocs } from '@pikku/core'
7
- import { getPropertyValue } from '../utils/get-property-value.js'
5
+ import { extractFunctionNode } from '../utils/extract-function-node.js'
6
+ import { FunctionServicesMeta } from '@pikku/core'
7
+ import {
8
+ getPropertyValue,
9
+ getCommonWireMetaData,
10
+ } from '../utils/get-property-value.js'
8
11
  import { resolveMiddleware } from '../utils/middleware.js'
12
+ import { resolvePermissions } from '../utils/permissions.js'
13
+ import { ErrorCode } from '../error-codes.js'
9
14
 
10
15
  const isValidVariableName = (name: string) => {
11
16
  const regex = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/
@@ -149,14 +154,18 @@ const resolveUnionTypes = (
149
154
  // Check if it's a union type AND not part of an intersection
150
155
  if (type.isUnion() && !(type.flags & ts.TypeFlags.Intersection)) {
151
156
  for (const t of type.types) {
152
- const name = nullifyTypes(checker.typeToString(t))
157
+ const name = nullifyTypes(
158
+ checker.typeToString(t, undefined, ts.TypeFormatFlags.NoTruncation)
159
+ )
153
160
  if (name) {
154
161
  types.push(t)
155
162
  names.push(name)
156
163
  }
157
164
  }
158
165
  } else {
159
- const name = nullifyTypes(checker.typeToString(type))
166
+ const name = nullifyTypes(
167
+ checker.typeToString(type, undefined, ts.TypeFormatFlags.NoTruncation)
168
+ )
160
169
  if (name) {
161
170
  types.push(type)
162
171
  names.push(name)
@@ -303,57 +312,114 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
303
312
  const { pikkuFuncName, name, explicitName, exportedName } =
304
313
  extractFunctionName(node, checker, state.rootDir)
305
314
 
315
+ let title: string | undefined
306
316
  let tags: string[] | undefined
317
+ let summary: string | undefined
318
+ let description: string | undefined
319
+ let errors: string[] | undefined
307
320
  let expose: boolean | undefined
308
321
  let internal: boolean | undefined
309
- let docs: PikkuDocs | undefined
310
322
  let objectNode: ts.ObjectLiteralExpression | undefined
311
323
 
312
- // determine the actual handler expression:
313
- // either the `func` prop or the first argument directly
314
- let handlerNode: ts.Expression = args[0]!
315
- let isDirectFunction = true // Default to direct function format
316
-
317
- if (ts.isObjectLiteralExpression(handlerNode)) {
318
- isDirectFunction = false // This is object format with func property
319
- objectNode = handlerNode
320
- tags = (getPropertyValue(handlerNode, 'tags') as string[]) || undefined
321
- expose = getPropertyValue(handlerNode, 'expose') as boolean | undefined
322
- internal = getPropertyValue(handlerNode, 'internal') as boolean | undefined
323
- docs = getPropertyValue(handlerNode, 'docs') as PikkuDocs | undefined
324
-
325
- const fnProp = getPropertyAssignmentInitializer(
326
- handlerNode,
327
- 'func',
328
- true,
329
- checker
330
- )
331
- if (
332
- !fnProp ||
333
- (!ts.isArrowFunction(fnProp) && !ts.isFunctionExpression(fnProp))
334
- ) {
335
- logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`)
336
- // Create stub metadata to prevent "function not found" errors in wirings
337
- state.functions.meta[pikkuFuncName] = {
338
- pikkuFuncName,
339
- name,
340
- services: { optimized: false, services: [] },
341
- inputSchemaName: null,
342
- outputSchemaName: null,
343
- inputs: [],
344
- outputs: [],
345
- middleware: undefined,
324
+ // Extract the function node using shared utility
325
+ const firstArg = args[0]!
326
+ const {
327
+ funcNode: handlerNode,
328
+ resolvedFunc,
329
+ isDirectFunction,
330
+ } = extractFunctionNode(firstArg, checker)
331
+
332
+ // Variables to hold zod schema references if provided
333
+ let inputZodSchemaRef: { variableName: string; sourceFile: string } | null =
334
+ null
335
+ let outputZodSchemaRef: { variableName: string; sourceFile: string } | null =
336
+ null
337
+
338
+ // Helper to resolve schema identifier to its actual source file
339
+ const resolveSchemaSourceFile = (
340
+ identifier: ts.Identifier
341
+ ): { variableName: string; sourceFile: string } | null => {
342
+ const symbol = checker.getSymbolAtLocation(identifier)
343
+ if (!symbol) return null
344
+
345
+ const decl = symbol.valueDeclaration || symbol.declarations?.[0]
346
+ if (!decl) return null
347
+
348
+ // If it's an import specifier, resolve the aliased symbol to get the actual source
349
+ if (ts.isImportSpecifier(decl)) {
350
+ const aliasedSymbol = checker.getAliasedSymbol(symbol)
351
+ if (aliasedSymbol) {
352
+ const aliasedDecl =
353
+ aliasedSymbol.valueDeclaration || aliasedSymbol.declarations?.[0]
354
+ if (aliasedDecl) {
355
+ return {
356
+ variableName: identifier.text,
357
+ sourceFile: aliasedDecl.getSourceFile().fileName,
358
+ }
359
+ }
346
360
  }
347
- return
348
361
  }
349
- handlerNode = fnProp
362
+
363
+ // Not an import - use the current source file
364
+ return {
365
+ variableName: identifier.text,
366
+ sourceFile: decl.getSourceFile().fileName,
367
+ }
350
368
  }
351
369
 
352
- if (
353
- !ts.isArrowFunction(handlerNode) &&
354
- !ts.isFunctionExpression(handlerNode)
355
- ) {
356
- logger.error(`• Handler for ${name} is not a function.`)
370
+ // Extract config properties if using object form
371
+ if (ts.isObjectLiteralExpression(firstArg)) {
372
+ objectNode = firstArg
373
+ const metadata = getCommonWireMetaData(firstArg, 'Function', name, logger)
374
+ title = metadata.title
375
+ tags = metadata.tags
376
+ summary = metadata.summary
377
+ description = metadata.description
378
+ errors = metadata.errors
379
+ expose = getPropertyValue(firstArg, 'expose') as boolean | undefined
380
+ internal = getPropertyValue(firstArg, 'internal') as boolean | undefined
381
+
382
+ // Extract zod schema variable names from input/output properties
383
+ for (const prop of firstArg.properties) {
384
+ if (ts.isPropertyAssignment(prop) && ts.isIdentifier(prop.name)) {
385
+ const propName = prop.name.text
386
+ if (propName === 'input' || propName === 'output') {
387
+ if (ts.isIdentifier(prop.initializer)) {
388
+ // Good - it's a variable reference, resolve its actual source file
389
+ const ref = resolveSchemaSourceFile(prop.initializer)
390
+ if (ref) {
391
+ if (propName === 'input') {
392
+ inputZodSchemaRef = ref
393
+ } else {
394
+ outputZodSchemaRef = ref
395
+ }
396
+ }
397
+ } else if (ts.isCallExpression(prop.initializer)) {
398
+ // Bad - it's an inline expression
399
+ const schemaName = `${name.charAt(0).toUpperCase() + name.slice(1)}${propName.charAt(0).toUpperCase() + propName.slice(1)}`
400
+ logger.critical(
401
+ ErrorCode.INLINE_ZOD_SCHEMA,
402
+ `Inline Zod schemas are not supported for '${propName}' in '${name}'.\n` +
403
+ ` Extract to an exported variable:\n` +
404
+ ` export const ${schemaName} = ${prop.initializer.getText()}\n` +
405
+ ` Then use: ${propName}: ${schemaName}`
406
+ )
407
+ }
408
+ }
409
+ }
410
+ }
411
+ }
412
+
413
+ // Pick the handler: use resolvedFunc when it exists and is a function, otherwise fall back to handlerNode
414
+ const handler =
415
+ resolvedFunc &&
416
+ (ts.isArrowFunction(resolvedFunc) || ts.isFunctionExpression(resolvedFunc))
417
+ ? resolvedFunc
418
+ : handlerNode
419
+
420
+ // Validate that we got a valid function
421
+ if (!ts.isArrowFunction(handler) && !ts.isFunctionExpression(handler)) {
422
+ logger.error(`• No valid 'func' property found for ${pikkuFuncName}.`)
357
423
  // Create stub metadata to prevent "function not found" errors in wirings
358
424
  state.functions.meta[pikkuFuncName] = {
359
425
  pikkuFuncName,
@@ -373,7 +439,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
373
439
  services: [],
374
440
  }
375
441
 
376
- const firstParam = handlerNode.parameters[0]
442
+ const firstParam = handler.parameters[0]
377
443
  if (firstParam) {
378
444
  if (ts.isObjectBindingPattern(firstParam.name)) {
379
445
  for (const elem of firstParam.name.elements) {
@@ -392,28 +458,76 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
392
458
  }
393
459
  }
394
460
 
461
+ // --- Extract used wires from third parameter ---
462
+ const usedWires: string[] = []
463
+ const thirdParam = handler.parameters[2]
464
+ if (thirdParam && ts.isObjectBindingPattern(thirdParam.name)) {
465
+ for (const elem of thirdParam.name.elements) {
466
+ const propertyName =
467
+ elem.propertyName && ts.isIdentifier(elem.propertyName)
468
+ ? elem.propertyName.text
469
+ : ts.isIdentifier(elem.name)
470
+ ? elem.name.text
471
+ : undefined
472
+ if (propertyName) {
473
+ usedWires.push(propertyName)
474
+ }
475
+ }
476
+ }
477
+
395
478
  // --- Generics → ts.Type[], unwrapped from Promise ---
396
479
  const genericTypes: ts.Type[] = (typeArguments ?? [])
397
480
  .map((tn) => checker.getTypeFromTypeNode(tn))
398
481
  .map((t) => unwrapPromise(checker, t))
399
482
 
483
+ const capitalizedName = name.charAt(0).toUpperCase() + name.slice(1)
484
+
400
485
  // --- Input Extraction ---
401
- let { names: inputNames, types: inputTypes } = getNamesAndTypes(
402
- checker,
403
- state.functions.typesMap,
404
- 'Input',
405
- name,
406
- genericTypes[0]
407
- )
408
- // if (inputTypes.length === 0) {
409
- // logger.debug(
410
- // `\x1b[31m• Unknown input type for '${name}', assuming void.\x1b[0m`
411
- // )
412
- // }
486
+ let inputNames: string[] = []
487
+ let inputTypes: ts.Type[] = []
488
+
489
+ if (inputZodSchemaRef) {
490
+ const schemaName = `${capitalizedName}Input`
491
+ inputNames = [schemaName]
492
+ state.zodLookup.set(schemaName, inputZodSchemaRef)
493
+ state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
494
+ } else if (genericTypes.length >= 1 && genericTypes[0]) {
495
+ // Fall back to extracting from generic type arguments
496
+ const result = getNamesAndTypes(
497
+ checker,
498
+ state.functions.typesMap,
499
+ 'Input',
500
+ name,
501
+ genericTypes[0]
502
+ )
503
+ inputNames = result.names
504
+ inputTypes = result.types
505
+ } else {
506
+ // Fall back to extracting from the function's second parameter type
507
+ const secondParam = handler.parameters[1]
508
+ if (secondParam) {
509
+ const paramType = checker.getTypeAtLocation(secondParam)
510
+ const result = getNamesAndTypes(
511
+ checker,
512
+ state.functions.typesMap,
513
+ 'Input',
514
+ pikkuFuncName,
515
+ paramType
516
+ )
517
+ inputNames = result.names
518
+ inputTypes = result.types
519
+ }
520
+ }
413
521
 
414
522
  // --- Output Extraction ---
415
523
  let outputNames: string[] = []
416
- if (genericTypes.length >= 2) {
524
+
525
+ if (outputZodSchemaRef) {
526
+ const schemaName = `${capitalizedName}Output`
527
+ outputNames = [schemaName]
528
+ state.zodLookup.set(schemaName, outputZodSchemaRef)
529
+ state.functions.typesMap.addCustomType(schemaName, 'unknown', [])
530
+ } else if (genericTypes.length >= 2) {
417
531
  outputNames = getNamesAndTypes(
418
532
  checker,
419
533
  state.functions.typesMap,
@@ -422,7 +536,7 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
422
536
  genericTypes[1]
423
537
  ).names
424
538
  } else {
425
- const sig = checker.getSignatureFromDeclaration(handlerNode)
539
+ const sig = checker.getSignatureFromDeclaration(handler)
426
540
  if (sig) {
427
541
  const rawRet = checker.getReturnTypeOfSignature(sig)
428
542
  const unwrapped = unwrapPromise(checker, rawRet)
@@ -442,30 +556,40 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
442
556
  )
443
557
  }
444
558
 
559
+ // Store the input type for later use
560
+ if (inputTypes.length > 0) {
561
+ state.typesLookup.set(pikkuFuncName, inputTypes)
562
+ }
563
+
445
564
  // --- resolve middleware ---
446
565
  const middleware = objectNode
447
566
  ? resolveMiddleware(state, objectNode, tags, checker)
448
567
  : undefined
449
568
 
569
+ // --- resolve permissions ---
570
+ const permissions = objectNode
571
+ ? resolvePermissions(state, objectNode, tags, checker)
572
+ : undefined
573
+
450
574
  state.functions.meta[pikkuFuncName] = {
451
575
  pikkuFuncName,
452
576
  name,
453
577
  services,
578
+ usedWires: usedWires.length > 0 ? usedWires : undefined,
454
579
  inputSchemaName: inputNames[0] ?? null,
455
580
  outputSchemaName: outputNames[0] ?? null,
456
581
  inputs: inputNames.filter((n) => n !== 'void') ?? null,
457
582
  outputs: outputNames.filter((n) => n !== 'void') ?? null,
458
583
  expose: expose || undefined,
459
584
  internal: internal || undefined,
585
+ title,
460
586
  tags: tags || undefined,
461
- docs: docs || undefined,
462
- isDirectFunction,
587
+ summary,
588
+ description,
589
+ errors,
463
590
  middleware,
464
- }
465
-
466
- // Store the input type for later use
467
- if (inputTypes.length > 0) {
468
- state.typesLookup.set(pikkuFuncName, inputTypes)
591
+ permissions,
592
+ isDirectFunction,
469
593
  }
470
594
 
471
595
  // Store function file location for wiring generation
@@ -476,6 +600,12 @@ export const addFunctions: AddWiring = (logger, node, checker, state) => {
476
600
  })
477
601
  }
478
602
 
603
+ // Workflow functions don't get registered as RPC functions,
604
+ // they are their own type handled by add-workdflow
605
+ if (expression.text.includes('Workflow')) {
606
+ return
607
+ }
608
+
479
609
  if (exportedName || explicitName) {
480
610
  if (!exportedName) {
481
611
  logger.error(
@@ -1,11 +1,10 @@
1
1
  import * as ts from 'typescript'
2
2
  import {
3
3
  getPropertyValue,
4
- getPropertyTags,
4
+ getCommonWireMetaData,
5
5
  } from '../utils/get-property-value.js'
6
6
  import { pathToRegexp } from 'path-to-regexp'
7
7
  import { HTTPMethod } from '@pikku/core/http'
8
- import { PikkuDocs } from '@pikku/core'
9
8
  import { extractFunctionName } from '../utils/extract-function-name.js'
10
9
  import { getPropertyAssignmentInitializer } from '../utils/type-utils.js'
11
10
  import { AddWiring } from '../types.js'
@@ -71,10 +70,37 @@ export const addHTTPRoute: AddWiring = (
71
70
 
72
71
  const method =
73
72
  (getPropertyValue(obj, 'method') as string)?.toLowerCase() || 'get'
74
- const docs = (getPropertyValue(obj, 'docs') as PikkuDocs) || undefined
75
- const tags = getPropertyTags(obj, 'HTTP route', route, logger)
73
+ const { title, tags, summary, description, errors } = getCommonWireMetaData(
74
+ obj,
75
+ 'HTTP route',
76
+ route,
77
+ logger
78
+ )
76
79
  const query = (getPropertyValue(obj, 'query') as string[]) || []
77
80
 
81
+ // Check if this is a workflow trigger (workflow: true)
82
+ const isWorkflowTrigger = getPropertyValue(obj, 'workflow') === true
83
+ if (isWorkflowTrigger) {
84
+ // Workflow triggers don't need func - they're handled by workflow-utils
85
+ // Just record the route for HTTP meta but skip function processing
86
+ state.http.files.add(node.getSourceFile().fileName)
87
+ state.http.meta[method][route] = {
88
+ pikkuFuncName: '', // No function - workflow handles it
89
+ route,
90
+ method: method as HTTPMethod,
91
+ params: params.length > 0 ? params : undefined,
92
+ query: query.length > 0 ? query : undefined,
93
+ inputTypes: undefined,
94
+ title,
95
+ summary,
96
+ description,
97
+ errors,
98
+ tags,
99
+ workflow: true,
100
+ }
101
+ return
102
+ }
103
+
78
104
  // --- find the referenced function name first for filtering ---
79
105
  const funcInitializer = getPropertyAssignmentInitializer(
80
106
  obj,
@@ -155,7 +181,10 @@ export const addHTTPRoute: AddWiring = (
155
181
  params: params.length > 0 ? params : undefined,
156
182
  query: query.length > 0 ? query : undefined,
157
183
  inputTypes,
158
- docs,
184
+ title,
185
+ summary,
186
+ description,
187
+ errors,
159
188
  tags,
160
189
  middleware,
161
190
  permissions,