@pikku/inspector 0.11.1 → 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 (68) hide show
  1. package/CHANGELOG.md +16 -1
  2. package/dist/add/add-forge-credential.d.ts +8 -0
  3. package/dist/add/add-forge-credential.js +77 -0
  4. package/dist/add/add-forge-node.d.ts +7 -0
  5. package/dist/add/add-forge-node.js +77 -0
  6. package/dist/add/add-functions.js +102 -9
  7. package/dist/add/add-http-route.js +24 -1
  8. package/dist/add/add-rpc-invocations.d.ts +3 -0
  9. package/dist/add/add-rpc-invocations.js +51 -25
  10. package/dist/add/add-workflow-graph.d.ts +6 -0
  11. package/dist/add/add-workflow-graph.js +659 -0
  12. package/dist/add/add-workflow.js +118 -22
  13. package/dist/error-codes.d.ts +3 -1
  14. package/dist/error-codes.js +3 -1
  15. package/dist/index.d.ts +3 -0
  16. package/dist/index.js +2 -0
  17. package/dist/inspector.js +19 -3
  18. package/dist/types.d.ts +26 -0
  19. package/dist/utils/extract-function-name.js +7 -7
  20. package/dist/utils/get-property-value.d.ts +2 -1
  21. package/dist/utils/get-property-value.js +6 -2
  22. package/dist/utils/serialize-inspector-state.d.ts +24 -1
  23. package/dist/utils/serialize-inspector-state.js +24 -0
  24. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.d.ts +24 -0
  25. package/dist/utils/workflow/dsl/deserialize-dsl-workflow.js +898 -0
  26. package/dist/{workflow/extract-simple-workflow.d.ts → utils/workflow/dsl/extract-dsl-workflow.d.ts} +4 -2
  27. package/dist/{workflow/extract-simple-workflow.js → utils/workflow/dsl/extract-dsl-workflow.js} +549 -68
  28. package/dist/utils/workflow/dsl/index.d.ts +7 -0
  29. package/dist/utils/workflow/dsl/index.js +7 -0
  30. package/dist/{workflow → utils/workflow/dsl}/patterns.d.ts +21 -0
  31. package/dist/{workflow → utils/workflow/dsl}/patterns.js +90 -10
  32. package/dist/{workflow → utils/workflow/dsl}/validation.d.ts +2 -0
  33. package/dist/{workflow → utils/workflow/dsl}/validation.js +25 -7
  34. package/dist/utils/workflow/graph/convert-dsl-to-graph.d.ts +13 -0
  35. package/dist/utils/workflow/graph/convert-dsl-to-graph.js +316 -0
  36. package/dist/utils/workflow/graph/index.d.ts +6 -0
  37. package/dist/utils/workflow/graph/index.js +6 -0
  38. package/dist/utils/workflow/graph/serialize-workflow-graph.d.ts +43 -0
  39. package/dist/utils/workflow/graph/serialize-workflow-graph.js +152 -0
  40. package/dist/utils/workflow/graph/workflow-graph.types.d.ts +229 -0
  41. package/dist/utils/workflow/graph/workflow-graph.types.js +38 -0
  42. package/dist/visit.js +6 -0
  43. package/package.json +14 -2
  44. package/src/add/add-forge-credential.ts +119 -0
  45. package/src/add/add-forge-node.ts +132 -0
  46. package/src/add/add-functions.ts +129 -15
  47. package/src/add/add-http-route.ts +25 -1
  48. package/src/add/add-rpc-invocations.ts +61 -31
  49. package/src/add/add-workflow-graph.ts +864 -0
  50. package/src/add/add-workflow.ts +112 -26
  51. package/src/error-codes.ts +3 -1
  52. package/src/index.ts +10 -0
  53. package/src/inspector.ts +20 -4
  54. package/src/types.ts +25 -1
  55. package/src/utils/extract-function-name.ts +7 -7
  56. package/src/utils/get-property-value.ts +9 -2
  57. package/src/utils/serialize-inspector-state.ts +39 -1
  58. package/src/utils/workflow/dsl/deserialize-dsl-workflow.ts +1180 -0
  59. package/src/{workflow/extract-simple-workflow.ts → utils/workflow/dsl/extract-dsl-workflow.ts} +654 -81
  60. package/src/utils/workflow/dsl/index.ts +11 -0
  61. package/src/{workflow → utils/workflow/dsl}/patterns.ts +108 -11
  62. package/src/{workflow → utils/workflow/dsl}/validation.ts +34 -7
  63. package/src/utils/workflow/graph/convert-dsl-to-graph.ts +415 -0
  64. package/src/utils/workflow/graph/index.ts +6 -0
  65. package/src/utils/workflow/graph/serialize-workflow-graph.ts +223 -0
  66. package/src/utils/workflow/graph/workflow-graph.types.ts +280 -0
  67. package/src/visit.ts +6 -0
  68. package/tsconfig.tsbuildinfo +1 -1
@@ -0,0 +1,223 @@
1
+ import type {
2
+ SerializedWorkflowGraph,
3
+ SerializedGraphNode,
4
+ FunctionNode,
5
+ DataRef,
6
+ SerializedNext,
7
+ } from './workflow-graph.types.js'
8
+
9
+ /**
10
+ * Convert a RefValue (from runtime) to DataRef (serialized)
11
+ */
12
+ function convertRef(ref: { nodeId: string; path?: string }): DataRef {
13
+ return {
14
+ $ref: ref.nodeId,
15
+ path: ref.path,
16
+ }
17
+ }
18
+
19
+ /**
20
+ * Check if a value is a runtime RefValue
21
+ */
22
+ function isRefValue(
23
+ value: unknown
24
+ ): value is { __isRef: true; nodeId: string; path?: string } {
25
+ return (
26
+ typeof value === 'object' &&
27
+ value !== null &&
28
+ '__isRef' in value &&
29
+ (value as any).__isRef === true
30
+ )
31
+ }
32
+
33
+ /**
34
+ * Convert input mapping from runtime format to serialized format
35
+ */
36
+ function serializeInputMapping(
37
+ input: Record<string, unknown>
38
+ ): Record<string, unknown | DataRef> {
39
+ const result: Record<string, unknown | DataRef> = {}
40
+
41
+ for (const [key, value] of Object.entries(input)) {
42
+ if (isRefValue(value)) {
43
+ result[key] = convertRef(value)
44
+ } else {
45
+ result[key] = value
46
+ }
47
+ }
48
+
49
+ return result
50
+ }
51
+
52
+ /**
53
+ * Convert next config from runtime format to serialized format
54
+ * Runtime uses Record<string, string | string[]> for branching with graph.branch()
55
+ * Serialized uses { conditions: [...], default: ... } for UI-friendly branching
56
+ */
57
+ function serializeNext(
58
+ next: string | string[] | Record<string, string | string[]> | undefined
59
+ ): SerializedNext | undefined {
60
+ if (!next) return undefined
61
+
62
+ if (typeof next === 'string') return next
63
+ if (Array.isArray(next)) return next
64
+
65
+ // Record format - convert to conditions format
66
+ // For now, treat keys as branch identifiers (from graph.branch())
67
+ // UI can display these as condition labels
68
+ const conditions = Object.entries(next).map(([key, target]) => ({
69
+ expression: key, // The branch key becomes the expression
70
+ target,
71
+ }))
72
+
73
+ return { conditions }
74
+ }
75
+
76
+ /**
77
+ * Serialize a workflow graph definition (from runtime) to JSON format
78
+ *
79
+ * @param definition - The runtime definition (with callbacks evaluated)
80
+ * @param rpcNameLookup - Function to get RPC name from a node's func
81
+ */
82
+ export function serializeWorkflowGraph(
83
+ definition: {
84
+ name: string
85
+ wires: {
86
+ http?: { route: string; method: string }
87
+ queue?: string
88
+ }
89
+ graph: Record<
90
+ string,
91
+ {
92
+ func: { name?: string }
93
+ input?: (ref: any) => Record<string, unknown>
94
+ next?: string | string[] | Record<string, string | string[]>
95
+ onError?: string | string[]
96
+ }
97
+ >
98
+ },
99
+ options?: {
100
+ description?: string
101
+ tags?: string[]
102
+ }
103
+ ): SerializedWorkflowGraph {
104
+ const nodes: Record<string, SerializedGraphNode> = {}
105
+ const entryNodeIds: string[] = []
106
+
107
+ // Create a ref function that captures refs
108
+ const createRef = (nodeId: string, path?: string) => ({
109
+ __isRef: true as const,
110
+ nodeId,
111
+ path,
112
+ })
113
+
114
+ // Track which nodes have incoming edges
115
+ const hasIncomingEdge = new Set<string>()
116
+
117
+ // First pass: identify nodes with incoming edges
118
+ for (const [_nodeId, node] of Object.entries(definition.graph)) {
119
+ const next = node.next
120
+ if (!next) continue
121
+
122
+ if (typeof next === 'string') {
123
+ hasIncomingEdge.add(next)
124
+ } else if (Array.isArray(next)) {
125
+ next.forEach((n) => hasIncomingEdge.add(n))
126
+ } else {
127
+ for (const targets of Object.values(next)) {
128
+ if (typeof targets === 'string') {
129
+ hasIncomingEdge.add(targets)
130
+ } else {
131
+ targets.forEach((n) => hasIncomingEdge.add(n))
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ // Second pass: serialize nodes
138
+ for (const [nodeId, node] of Object.entries(definition.graph)) {
139
+ // Evaluate input callback to get the mapping
140
+ let input: Record<string, unknown | DataRef> = {}
141
+ if (node.input) {
142
+ const rawInput = node.input(createRef)
143
+ input = serializeInputMapping(rawInput)
144
+ }
145
+
146
+ // Get RPC name from func
147
+ const rpcName = node.func?.name || 'unknown'
148
+
149
+ const funcNode: FunctionNode = {
150
+ nodeId,
151
+ rpcName,
152
+ input,
153
+ next: serializeNext(node.next),
154
+ onError: node.onError,
155
+ }
156
+ nodes[nodeId] = funcNode
157
+
158
+ // Entry nodes have no incoming edges
159
+ if (!hasIncomingEdge.has(nodeId)) {
160
+ entryNodeIds.push(nodeId)
161
+ }
162
+ }
163
+
164
+ return {
165
+ name: definition.name,
166
+ pikkuFuncName: definition.name, // For graph workflows, pikkuFuncName is the workflow name
167
+ source: 'graph' as const,
168
+ description: options?.description,
169
+ tags: options?.tags,
170
+ wires: definition.wires as SerializedWorkflowGraph['wires'],
171
+ nodes,
172
+ entryNodeIds,
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Deserialize a workflow graph from JSON to runtime format
178
+ * This re-hydrates the JSON so it can be executed
179
+ */
180
+ export function deserializeWorkflowGraph(serialized: SerializedWorkflowGraph): {
181
+ name: string
182
+ wires: SerializedWorkflowGraph['wires']
183
+ graph: Record<
184
+ string,
185
+ {
186
+ rpcName: string
187
+ input: Record<string, unknown | DataRef>
188
+ next?: SerializedNext
189
+ onError?: string | string[]
190
+ }
191
+ >
192
+ entryNodeIds: string[]
193
+ } {
194
+ const graph: Record<
195
+ string,
196
+ {
197
+ rpcName: string
198
+ input: Record<string, unknown | DataRef>
199
+ next?: SerializedNext
200
+ onError?: string | string[]
201
+ }
202
+ > = {}
203
+
204
+ for (const [nodeId, node] of Object.entries(serialized.nodes)) {
205
+ // Only include FunctionNode properties (nodes with rpcName)
206
+ if ('rpcName' in node) {
207
+ const funcNode = node as FunctionNode
208
+ graph[nodeId] = {
209
+ rpcName: funcNode.rpcName,
210
+ input: funcNode.input ?? {},
211
+ next: funcNode.next,
212
+ onError: funcNode.onError,
213
+ }
214
+ }
215
+ }
216
+
217
+ return {
218
+ name: serialized.name,
219
+ wires: serialized.wires,
220
+ graph,
221
+ entryNodeIds: serialized.entryNodeIds,
222
+ }
223
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Serialized types for workflow graphs
3
+ * These are extracted by the inspector and stored as JSON
4
+ * Can be created from code (wireWorkflow) or UI
5
+ */
6
+
7
+ /**
8
+ * Reference to data from another node or trigger
9
+ */
10
+ export interface DataRef {
11
+ /** Source: 'trigger' for trigger input, or node ID for node output */
12
+ $ref: string
13
+ /** Optional path into the data (dot notation: 'body.orderId') */
14
+ path?: string
15
+ }
16
+
17
+ /**
18
+ * Check if value is a DataRef
19
+ */
20
+ export const isDataRef = (value: unknown): value is DataRef =>
21
+ typeof value === 'object' &&
22
+ value !== null &&
23
+ '$ref' in value &&
24
+ typeof (value as DataRef).$ref === 'string'
25
+
26
+ /**
27
+ * Reference to a context/state variable
28
+ */
29
+ export interface StateRef {
30
+ /** Context variable name */
31
+ $state: string
32
+ /** Optional path into the value (dot notation for nested objects) */
33
+ path?: string
34
+ }
35
+
36
+ /**
37
+ * Check if value is a StateRef
38
+ */
39
+ export const isStateRef = (value: unknown): value is StateRef =>
40
+ typeof value === 'object' &&
41
+ value !== null &&
42
+ '$state' in value &&
43
+ typeof (value as StateRef).$state === 'string'
44
+
45
+ /**
46
+ * Helper functions for building input mappings
47
+ */
48
+ export const ref = (nodeId: string, path?: string): DataRef => ({
49
+ $ref: nodeId,
50
+ path,
51
+ })
52
+ export const state = (name: string, path?: string): StateRef => ({
53
+ $state: name,
54
+ path,
55
+ })
56
+
57
+ /**
58
+ * Condition for branching
59
+ */
60
+ export interface BranchCondition {
61
+ /** Expression to evaluate (uses node output references) */
62
+ expression: string
63
+ /** Target node(s) if condition is true */
64
+ target: string | string[]
65
+ }
66
+
67
+ /**
68
+ * Next node configuration
69
+ */
70
+ export type SerializedNext =
71
+ | string // Single next node
72
+ | string[] // Parallel execution
73
+ | {
74
+ /** Conditions evaluated in order, first match wins */
75
+ conditions: BranchCondition[]
76
+ /** Default target if no conditions match */
77
+ default?: string | string[]
78
+ }
79
+
80
+ /**
81
+ * Node execution options
82
+ */
83
+ export interface NodeOptions {
84
+ /** Number of retry attempts on failure */
85
+ retries?: number
86
+ /** Delay between retries (e.g., '1s', '5s') */
87
+ retryDelay?: string
88
+ /** Timeout for node execution (e.g., '30s', '5m') */
89
+ timeout?: string
90
+ /** If true, execute via queue (async). Default: false (inline) */
91
+ async?: boolean
92
+ }
93
+
94
+ /**
95
+ * Flow node types for control flow (no RPC call)
96
+ */
97
+ export type FlowType =
98
+ | 'sleep'
99
+ | 'branch'
100
+ | 'parallel'
101
+ | 'fanout'
102
+ | 'inline'
103
+ | 'switch'
104
+ | 'filter'
105
+ | 'arrayPredicate'
106
+ | 'return'
107
+ | 'cancel'
108
+ | 'set'
109
+
110
+ // Import and re-export context types from core
111
+ import type { ContextVariable, WorkflowContext } from '@pikku/core/workflow'
112
+
113
+ export type { ContextVariable, WorkflowContext }
114
+
115
+ /**
116
+ * Base node properties shared by all node types
117
+ */
118
+ interface BaseNode {
119
+ /** Node ID */
120
+ nodeId: string
121
+ /** Step name/description */
122
+ stepName?: string
123
+ /** Next node(s) - simple, parallel, or conditional */
124
+ next?: SerializedNext
125
+ /** Error routing - node(s) to execute on error */
126
+ onError?: string | string[]
127
+ /** Execution options */
128
+ options?: NodeOptions
129
+ }
130
+
131
+ /**
132
+ * Function node - calls an RPC
133
+ */
134
+ export interface FunctionNode extends BaseNode {
135
+ /** RPC function name */
136
+ rpcName: string
137
+ /** Input mapping - values can be literals or DataRefs */
138
+ input?: Record<string, unknown | DataRef>
139
+ /** Output variable name for storing result */
140
+ outputVar?: string
141
+ }
142
+
143
+ /**
144
+ * Flow node - control flow only, no RPC call
145
+ */
146
+ export interface FlowNode extends BaseNode {
147
+ /** Flow type */
148
+ flow: FlowType
149
+ /** Flow-specific properties */
150
+ [key: string]: unknown
151
+ }
152
+
153
+ /**
154
+ * Serialized graph node - either a function node or flow node
155
+ */
156
+ export type SerializedGraphNode = FunctionNode | FlowNode
157
+
158
+ /**
159
+ * Type guard for function nodes
160
+ */
161
+ export const isFunctionNode = (
162
+ node: SerializedGraphNode
163
+ ): node is FunctionNode => 'rpcName' in node
164
+
165
+ /**
166
+ * Type guard for flow nodes
167
+ */
168
+ export const isFlowNode = (node: SerializedGraphNode): node is FlowNode =>
169
+ 'flow' in node
170
+
171
+ /**
172
+ * HTTP wire configuration with startNode
173
+ */
174
+ export interface HttpWire {
175
+ route: string
176
+ method: 'get' | 'post' | 'put' | 'patch' | 'delete'
177
+ startNode: string
178
+ }
179
+
180
+ /**
181
+ * Channel wire configuration
182
+ */
183
+ export interface ChannelWire {
184
+ name: string
185
+ onConnect?: string
186
+ onDisconnect?: string
187
+ onMessage?: string
188
+ }
189
+
190
+ /**
191
+ * Queue wire configuration
192
+ */
193
+ export interface QueueWire {
194
+ name: string
195
+ startNode: string
196
+ }
197
+
198
+ /**
199
+ * CLI wire configuration
200
+ */
201
+ export interface CliWire {
202
+ command: string
203
+ startNode: string
204
+ }
205
+
206
+ /**
207
+ * MCP wire configurations
208
+ */
209
+ export interface McpWires {
210
+ tool?: Array<{ name: string; startNode: string }>
211
+ prompt?: Array<{ name: string; startNode: string }>
212
+ resource?: Array<{ uri: string; startNode: string }>
213
+ }
214
+
215
+ /**
216
+ * Schedule wire configuration
217
+ */
218
+ export interface ScheduleWire {
219
+ cron?: string
220
+ interval?: string
221
+ startNode: string
222
+ }
223
+
224
+ /**
225
+ * Trigger wire configuration
226
+ */
227
+ export interface TriggerWire {
228
+ name: string
229
+ startNode: string
230
+ }
231
+
232
+ /**
233
+ * All wire configurations for workflows
234
+ */
235
+ export interface WorkflowWiresConfig {
236
+ http?: HttpWire[]
237
+ channel?: ChannelWire[]
238
+ queue?: QueueWire[]
239
+ cli?: CliWire[]
240
+ mcp?: McpWires
241
+ schedule?: ScheduleWire[]
242
+ trigger?: TriggerWire[]
243
+ }
244
+
245
+ /**
246
+ * Workflow source type
247
+ * - 'dsl': Pure DSL workflow (pikkuWorkflowFunc) - can be round-tripped to code
248
+ * - 'complex': Complex workflow (pikkuWorkflowComplexFunc) - contains inline steps, not serializable
249
+ * - 'graph': Graph-based workflow (pikkuWorkflowGraph)
250
+ */
251
+ export type WorkflowSourceType = 'dsl' | 'complex' | 'graph'
252
+
253
+ /**
254
+ * Serialized workflow graph - the canonical JSON format
255
+ */
256
+ export interface SerializedWorkflowGraph {
257
+ /** Workflow name */
258
+ name: string
259
+ /** Pikku function name (for runtime registration) */
260
+ pikkuFuncName: string
261
+ /** Source type: 'dsl' for pikkuWorkflowFunc, 'graph' for pikkuWorkflowGraph */
262
+ source: WorkflowSourceType
263
+ /** Optional description */
264
+ description?: string
265
+ /** Tags for organization */
266
+ tags?: string[]
267
+ /** Workflow context/state variables (from Zod schema) */
268
+ context?: WorkflowContext
269
+ /** Wires - how the workflow is triggered */
270
+ wires: WorkflowWiresConfig
271
+ /** Serialized nodes */
272
+ nodes: Record<string, SerializedGraphNode>
273
+ /** Entry node(s) - first nodes to execute */
274
+ entryNodeIds: string[]
275
+ }
276
+
277
+ /**
278
+ * All workflow graphs (serialized)
279
+ */
280
+ export type SerializedWorkflowGraphs = Record<string, SerializedWorkflowGraph>
package/src/visit.ts CHANGED
@@ -15,6 +15,9 @@ import { addRPCInvocations } from './add/add-rpc-invocations.js'
15
15
  import { addMiddleware } from './add/add-middleware.js'
16
16
  import { addPermission } from './add/add-permission.js'
17
17
  import { addCLI, addCLIRenderers } from './add/add-cli.js'
18
+ import { addForgeNode } from './add/add-forge-node.js'
19
+ import { addForgeCredential } from './add/add-forge-credential.js'
20
+ import { addWorkflowGraph } from './add/add-workflow-graph.js'
18
21
 
19
22
  export const visitSetup = (
20
23
  logger: InspectorLogger,
@@ -99,6 +102,9 @@ export const visitRoutes = (
99
102
  addMCPResource(logger, node, checker, state, options)
100
103
  addMCPTool(logger, node, checker, state, options)
101
104
  addMCPPrompt(logger, node, checker, state, options)
105
+ addForgeNode(logger, node, checker, state, options)
106
+ addForgeCredential(logger, node, checker, state, options)
107
+ addWorkflowGraph(logger, node, checker, state, options)
102
108
 
103
109
  ts.forEachChild(node, (child) =>
104
110
  visitRoutes(logger, checker, child, state, options)