@pikku/core 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 (143) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/errors/error-handler.js +3 -3
  3. package/dist/function/function-runner.d.ts +4 -3
  4. package/dist/function/function-runner.js +60 -14
  5. package/dist/function/functions.types.d.ts +84 -5
  6. package/dist/function/functions.types.js +41 -1
  7. package/dist/index.d.ts +6 -2
  8. package/dist/index.js +5 -2
  9. package/dist/middleware/auth-apikey.d.ts +1 -1
  10. package/dist/middleware/auth-bearer.d.ts +1 -1
  11. package/dist/middleware/auth-cookie.d.ts +1 -1
  12. package/dist/middleware/timeout.d.ts +1 -1
  13. package/dist/middleware-runner.d.ts +3 -14
  14. package/dist/middleware-runner.js +8 -8
  15. package/dist/permissions.d.ts +3 -86
  16. package/dist/permissions.js +11 -67
  17. package/dist/pikku-state.d.ts +32 -104
  18. package/dist/pikku-state.js +140 -66
  19. package/dist/schema.d.ts +8 -6
  20. package/dist/schema.js +25 -13
  21. package/dist/services/workflow-service.d.ts +38 -0
  22. package/dist/services/workflow-service.js +1 -0
  23. package/dist/types/core.types.d.ts +12 -4
  24. package/dist/types/core.types.js +1 -1
  25. package/dist/types/state.types.d.ts +135 -0
  26. package/dist/types/state.types.js +1 -0
  27. package/dist/utils.d.ts +8 -0
  28. package/dist/utils.js +45 -0
  29. package/dist/wirings/channel/channel-handler.js +1 -1
  30. package/dist/wirings/channel/channel-runner.js +5 -5
  31. package/dist/wirings/channel/log-channels.js +1 -1
  32. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
  33. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  34. package/dist/wirings/cli/cli-runner.js +10 -10
  35. package/dist/wirings/cli/cli.types.d.ts +1 -0
  36. package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
  37. package/dist/wirings/forge-node/forge-node.types.js +38 -0
  38. package/dist/wirings/forge-node/index.d.ts +1 -0
  39. package/dist/wirings/forge-node/index.js +1 -0
  40. package/dist/wirings/http/http-runner.d.ts +2 -2
  41. package/dist/wirings/http/http-runner.js +21 -12
  42. package/dist/wirings/http/http.types.d.ts +14 -1
  43. package/dist/wirings/http/log-http-routes.js +1 -1
  44. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  45. package/dist/wirings/mcp/mcp-runner.js +21 -21
  46. package/dist/wirings/queue/queue-runner.js +6 -6
  47. package/dist/wirings/rpc/index.d.ts +1 -1
  48. package/dist/wirings/rpc/index.js +1 -1
  49. package/dist/wirings/rpc/rpc-runner.d.ts +7 -8
  50. package/dist/wirings/rpc/rpc-runner.js +62 -14
  51. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  52. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  53. package/dist/wirings/scheduler/scheduler-runner.js +5 -5
  54. package/dist/wirings/trigger/index.d.ts +2 -0
  55. package/dist/wirings/trigger/index.js +2 -0
  56. package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
  57. package/dist/wirings/trigger/trigger-runner.js +57 -0
  58. package/dist/wirings/trigger/trigger.types.d.ts +42 -0
  59. package/dist/wirings/trigger/trigger.types.js +1 -0
  60. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  61. package/dist/wirings/workflow/dsl/index.js +4 -0
  62. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
  63. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  64. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  65. package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
  66. package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
  67. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  68. package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
  69. package/dist/wirings/workflow/graph/graph-runner.js +452 -0
  70. package/dist/wirings/workflow/graph/index.d.ts +3 -0
  71. package/dist/wirings/workflow/graph/index.js +3 -0
  72. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
  73. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  74. package/dist/wirings/workflow/index.d.ts +7 -12
  75. package/dist/wirings/workflow/index.js +6 -11
  76. package/dist/wirings/workflow/pikku-workflow-service.d.ts +96 -6
  77. package/dist/wirings/workflow/pikku-workflow-service.js +230 -103
  78. package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
  79. package/dist/wirings/workflow/wire-workflow.js +53 -0
  80. package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
  81. package/dist/wirings/workflow/workflow-utils.js +66 -0
  82. package/dist/wirings/workflow/workflow.types.d.ts +133 -211
  83. package/package.json +10 -1
  84. package/src/errors/error-handler.ts +3 -3
  85. package/src/function/function-runner.test.ts +1 -1
  86. package/src/function/function-runner.ts +86 -16
  87. package/src/function/functions.types.ts +126 -4
  88. package/src/index.ts +10 -5
  89. package/src/middleware-runner.ts +16 -9
  90. package/src/permissions.test.ts +28 -86
  91. package/src/permissions.ts +21 -80
  92. package/src/pikku-state.ts +156 -201
  93. package/src/schema.ts +42 -13
  94. package/src/services/workflow-service.ts +74 -0
  95. package/src/types/core.types.ts +12 -3
  96. package/src/types/state.types.ts +195 -0
  97. package/src/utils.ts +55 -0
  98. package/src/wirings/channel/channel-handler.ts +1 -1
  99. package/src/wirings/channel/channel-runner.ts +5 -5
  100. package/src/wirings/channel/local/local-channel-runner.test.ts +3 -1
  101. package/src/wirings/channel/log-channels.ts +1 -1
  102. package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -2
  103. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  104. package/src/wirings/cli/cli-runner.test.ts +19 -19
  105. package/src/wirings/cli/cli-runner.ts +10 -9
  106. package/src/wirings/cli/cli.types.ts +1 -0
  107. package/src/wirings/forge-node/forge-node.types.ts +135 -0
  108. package/src/wirings/forge-node/index.ts +1 -0
  109. package/src/wirings/http/http-runner.test.ts +2 -2
  110. package/src/wirings/http/http-runner.ts +30 -13
  111. package/src/wirings/http/http.types.ts +14 -0
  112. package/src/wirings/http/log-http-routes.ts +1 -1
  113. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  114. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  115. package/src/wirings/mcp/mcp-runner.ts +21 -21
  116. package/src/wirings/queue/queue-runner.test.ts +19 -19
  117. package/src/wirings/queue/queue-runner.ts +6 -6
  118. package/src/wirings/rpc/index.ts +1 -1
  119. package/src/wirings/rpc/rpc-runner.ts +88 -22
  120. package/src/wirings/rpc/rpc-types.ts +8 -0
  121. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  122. package/src/wirings/scheduler/scheduler-runner.test.ts +27 -27
  123. package/src/wirings/scheduler/scheduler-runner.ts +5 -5
  124. package/src/wirings/trigger/index.ts +2 -0
  125. package/src/wirings/trigger/trigger-runner.ts +96 -0
  126. package/src/wirings/trigger/trigger.types.ts +56 -0
  127. package/src/wirings/workflow/dsl/index.ts +30 -0
  128. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
  129. package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
  130. package/src/wirings/workflow/graph/graph-node.ts +227 -0
  131. package/src/wirings/workflow/graph/graph-runner.ts +694 -0
  132. package/src/wirings/workflow/graph/index.ts +3 -0
  133. package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
  134. package/src/wirings/workflow/index.ts +53 -26
  135. package/src/wirings/workflow/pikku-workflow-service.ts +333 -117
  136. package/src/wirings/workflow/wire-workflow.ts +94 -0
  137. package/src/wirings/workflow/workflow-utils.ts +120 -0
  138. package/src/wirings/workflow/workflow.types.ts +166 -277
  139. package/tsconfig.tsbuildinfo +1 -1
  140. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  141. package/dist/wirings/workflow/workflow-runner.js +0 -65
  142. package/src/function/function-runner.ts.bak +0 -188
  143. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -0,0 +1,452 @@
1
+ import { createRef, isRef } from './workflow-graph.types.js';
2
+ import { pikkuState } from '../../../pikku-state.js';
3
+ /**
4
+ * Add a workflow graph to the system
5
+ * This is called by the generated workflow wirings
6
+ */
7
+ export const addWorkflowGraph = (workflowName, graphResult) => {
8
+ // Get workflow metadata from inspector
9
+ const meta = pikkuState(null, 'workflows', 'meta');
10
+ const workflowMeta = meta[workflowName];
11
+ if (!workflowMeta) {
12
+ throw new Error(`Workflow metadata not found for '${workflowName}'. Make sure to run the CLI to generate metadata.`);
13
+ }
14
+ // Store workflow graph definition in state
15
+ const registrations = pikkuState(null, 'workflows', 'graphRegistrations');
16
+ registrations.set(workflowName, {
17
+ name: workflowName,
18
+ wires: graphResult.wires || {},
19
+ graph: graphResult.graph,
20
+ });
21
+ };
22
+ /**
23
+ * Get a registered workflow graph by name
24
+ */
25
+ export function getWorkflowGraph(name) {
26
+ const registrations = pikkuState(null, 'workflows', 'graphRegistrations');
27
+ return registrations.get(name);
28
+ }
29
+ /**
30
+ * Resolve next config to array of node IDs
31
+ * For branching (Record), uses the branch key from step
32
+ */
33
+ function resolveNextFromConfig(next, branchKey) {
34
+ if (!next)
35
+ return [];
36
+ if (typeof next === 'string')
37
+ return [next];
38
+ if (Array.isArray(next))
39
+ return next;
40
+ // Record (branching) - use branch key set by graph.branch()
41
+ if (!branchKey || !(branchKey in next))
42
+ return [];
43
+ const branchNext = next[branchKey];
44
+ return Array.isArray(branchNext) ? branchNext : [branchNext];
45
+ }
46
+ /**
47
+ * Check if a value is a template
48
+ */
49
+ function isTemplate(value) {
50
+ return (typeof value === 'object' &&
51
+ value !== null &&
52
+ '$template' in value &&
53
+ typeof value.$template === 'object');
54
+ }
55
+ /**
56
+ * Create a template function for use in input callbacks
57
+ */
58
+ function createTemplate(templateStr, refs) {
59
+ const parts = [];
60
+ const expressions = [];
61
+ const regex = /\$(\d+)/g;
62
+ let lastIndex = 0;
63
+ let match;
64
+ while ((match = regex.exec(templateStr)) !== null) {
65
+ parts.push(templateStr.slice(lastIndex, match.index));
66
+ const refIndex = parseInt(match[1], 10);
67
+ const refValue = refs[refIndex];
68
+ if (refValue) {
69
+ expressions.push({ $ref: refValue.nodeId, path: refValue.path });
70
+ }
71
+ else {
72
+ expressions.push({ $ref: 'unknown' });
73
+ }
74
+ lastIndex = regex.lastIndex;
75
+ }
76
+ parts.push(templateStr.slice(lastIndex));
77
+ return { $template: { parts, expressions } };
78
+ }
79
+ /**
80
+ * Evaluate a node's input callback to get the input mapping
81
+ */
82
+ function evaluateInputCallback(node) {
83
+ if (!node.input)
84
+ return {};
85
+ const ref = (targetNodeId, path) => createRef(targetNodeId, path);
86
+ const template = (templateStr, refs) => createTemplate(templateStr, refs);
87
+ // Call with both ref and template - input callback may accept 1 or 2 params
88
+ return node.input(ref, template);
89
+ }
90
+ /**
91
+ * Extract node IDs referenced in an input mapping (including from templates)
92
+ */
93
+ function extractReferencedNodeIds(inputMapping) {
94
+ const nodeIds = [];
95
+ for (const value of Object.values(inputMapping)) {
96
+ if (isRef(value)) {
97
+ nodeIds.push(value.nodeId);
98
+ }
99
+ else if (isTemplate(value)) {
100
+ for (const expr of value.$template.expressions) {
101
+ nodeIds.push(expr.$ref);
102
+ }
103
+ }
104
+ }
105
+ return [...new Set(nodeIds)];
106
+ }
107
+ /**
108
+ * Resolve a template value using node results
109
+ */
110
+ function resolveTemplate(template, nodeResults) {
111
+ const { parts, expressions } = template.$template;
112
+ let result = '';
113
+ for (let i = 0; i < parts.length; i++) {
114
+ result += parts[i];
115
+ if (i < expressions.length) {
116
+ const expr = expressions[i];
117
+ const nodeResult = nodeResults[expr.$ref];
118
+ const value = expr.path
119
+ ? getValueAtPath(nodeResult, expr.path)
120
+ : nodeResult;
121
+ result += String(value ?? '');
122
+ }
123
+ }
124
+ return result;
125
+ }
126
+ /**
127
+ * Resolve input mapping using node results
128
+ */
129
+ function resolveInputMapping(inputMapping, nodeResults) {
130
+ const resolved = {};
131
+ for (const [key, value] of Object.entries(inputMapping)) {
132
+ if (isRef(value)) {
133
+ const nodeResult = nodeResults[value.nodeId];
134
+ resolved[key] = value.path
135
+ ? getValueAtPath(nodeResult, value.path)
136
+ : nodeResult;
137
+ }
138
+ else if (isTemplate(value)) {
139
+ resolved[key] = resolveTemplate(value, nodeResults);
140
+ }
141
+ else {
142
+ resolved[key] = value;
143
+ }
144
+ }
145
+ return resolved;
146
+ }
147
+ /**
148
+ * Get value at a dot-notation path from an object
149
+ */
150
+ function getValueAtPath(obj, path) {
151
+ if (!path)
152
+ return obj;
153
+ const parts = path.split('.');
154
+ let current = obj;
155
+ for (const part of parts) {
156
+ if (current == null)
157
+ return undefined;
158
+ current = current[part];
159
+ }
160
+ return current;
161
+ }
162
+ /**
163
+ * Get the RPC name from a graph node's func
164
+ * Supports both function references (func.name) and string RPC names
165
+ */
166
+ function getRpcName(node) {
167
+ const func = node.func;
168
+ // If func is a string, use it directly as the RPC name
169
+ if (typeof func === 'string') {
170
+ return func;
171
+ }
172
+ // Otherwise, it's a function reference - use its name
173
+ return func?.name || 'unknown';
174
+ }
175
+ /**
176
+ * Queue a graph node for execution
177
+ */
178
+ async function queueGraphNode(workflowService, runId, _graphName, nodeId, rpcName, input) {
179
+ // Step name convention: node:<nodeId>
180
+ // Graph name is stored as the workflow name on the run
181
+ await workflowService.insertStepState(runId, `node:${nodeId}`, rpcName, input, { retries: 3 });
182
+ await workflowService.resumeWorkflow(runId);
183
+ }
184
+ /**
185
+ * Continue graph execution
186
+ * Non-blocking - finds pending nodes, resolves inputs, queues them for execution
187
+ */
188
+ export async function continueGraph(workflowService, runId, graphName) {
189
+ const definition = getWorkflowGraph(graphName);
190
+ if (!definition) {
191
+ throw new Error(`Workflow graph '${graphName}' not found`);
192
+ }
193
+ const graph = definition.graph;
194
+ // Get completed node IDs + branch keys (lightweight, no results)
195
+ const { completedNodeIds, branchKeys } = await workflowService.getCompletedGraphState(runId);
196
+ // Find candidate next nodes from completed nodes
197
+ const candidateNodes = [];
198
+ for (const nodeId of completedNodeIds) {
199
+ const node = graph[nodeId];
200
+ if (!node?.next)
201
+ continue;
202
+ const nextNodes = resolveNextFromConfig(node.next, branchKeys[nodeId]);
203
+ candidateNodes.push(...nextNodes);
204
+ }
205
+ if (candidateNodes.length === 0 && completedNodeIds.length > 0) {
206
+ // No more nodes to run - graph complete
207
+ await workflowService.updateRunStatus(runId, 'completed');
208
+ return;
209
+ }
210
+ // Filter to only nodes that don't have a step yet
211
+ const nodesToQueue = await workflowService.getNodesWithoutSteps(runId, candidateNodes);
212
+ // Queue each node for execution
213
+ for (const nodeId of nodesToQueue) {
214
+ const node = graph[nodeId];
215
+ if (!node)
216
+ continue;
217
+ // Evaluate input callback to get the mapping
218
+ const inputMapping = evaluateInputCallback(node);
219
+ // Only fetch results for nodes referenced in this node's input
220
+ const referencedNodeIds = extractReferencedNodeIds(inputMapping);
221
+ const nodeResults = await workflowService.getNodeResults(runId, referencedNodeIds);
222
+ const resolvedInput = resolveInputMapping(inputMapping, nodeResults);
223
+ const rpcName = getRpcName(node);
224
+ await queueGraphNode(workflowService, runId, graphName, nodeId, rpcName, resolvedInput);
225
+ }
226
+ }
227
+ /**
228
+ * Execute a graph step with wire context.
229
+ * Called by the step worker when executing graph nodes.
230
+ */
231
+ export async function executeGraphStep(workflowService, rpcService, runId, stepId, stepName, rpcName, data, graphName) {
232
+ const nodeId = stepName.replace(/^node:/, '');
233
+ const wireState = {};
234
+ const graphWire = {
235
+ runId,
236
+ graphName,
237
+ nodeId,
238
+ branch: (key) => {
239
+ wireState.branchKey = key;
240
+ },
241
+ setState: (name, value) => workflowService.updateRunState(runId, name, value),
242
+ getState: () => workflowService.getRunState(runId),
243
+ };
244
+ try {
245
+ // Execute the RPC with graph wire context
246
+ const result = await rpcService.rpcWithWire(rpcName, data, {
247
+ graph: graphWire,
248
+ });
249
+ // If branch was called, store the branch key
250
+ if (wireState.branchKey) {
251
+ await workflowService.setBranchTaken(stepId, wireState.branchKey);
252
+ }
253
+ return result;
254
+ }
255
+ catch (error) {
256
+ // Check if this node has an onError handler
257
+ const definition = getWorkflowGraph(graphName);
258
+ if (definition) {
259
+ const node = definition.graph[nodeId];
260
+ if (node?.onError) {
261
+ // Route to error handler nodes
262
+ const errorNodes = Array.isArray(node.onError)
263
+ ? node.onError
264
+ : [node.onError];
265
+ for (const errorNodeId of errorNodes) {
266
+ const errorNode = definition.graph[errorNodeId];
267
+ if (errorNode) {
268
+ const errorRpcName = getRpcName(errorNode);
269
+ // Queue error node with the error as input
270
+ await queueGraphNode(workflowService, runId, graphName, errorNodeId, errorRpcName, { error: { message: error.message } });
271
+ }
272
+ }
273
+ // Don't rethrow - error was handled by routing to error nodes
274
+ return;
275
+ }
276
+ }
277
+ // No error handler - rethrow
278
+ throw error;
279
+ }
280
+ }
281
+ /**
282
+ * Handle node completion - re-triggers graph continuation
283
+ */
284
+ export async function onGraphNodeComplete(workflowService, runId, graphName) {
285
+ await continueGraph(workflowService, runId, graphName);
286
+ }
287
+ /**
288
+ * Execute a graph node inline (without queue)
289
+ */
290
+ async function executeGraphNodeInline(workflowService, rpcService, runId, graphName, nodeId, input, graph) {
291
+ const node = graph[nodeId];
292
+ if (!node)
293
+ return;
294
+ const rpcName = getRpcName(node);
295
+ const stepName = `node:${nodeId}`;
296
+ // Insert step state
297
+ const stepState = await workflowService.insertStepState(runId, stepName, rpcName, input, { retries: 3 });
298
+ await workflowService.setStepRunning(stepState.stepId);
299
+ // Execute with graph wire context
300
+ const wireState = {};
301
+ const graphWire = {
302
+ runId,
303
+ graphName,
304
+ nodeId,
305
+ branch: (key) => {
306
+ wireState.branchKey = key;
307
+ },
308
+ setState: (name, value) => workflowService.updateRunState(runId, name, value),
309
+ getState: () => workflowService.getRunState(runId),
310
+ };
311
+ try {
312
+ const result = await rpcService.rpcWithWire(rpcName, input, {
313
+ graph: graphWire,
314
+ });
315
+ // If branch was called, store the branch key
316
+ if (wireState.branchKey) {
317
+ await workflowService.setBranchTaken(stepState.stepId, wireState.branchKey);
318
+ }
319
+ await workflowService.setStepResult(stepState.stepId, result);
320
+ }
321
+ catch (error) {
322
+ await workflowService.setStepError(stepState.stepId, error);
323
+ // Check if this node has an onError handler
324
+ const definition = getWorkflowGraph(graphName);
325
+ if (definition) {
326
+ const node = definition.graph[nodeId];
327
+ if (node?.onError) {
328
+ // Route to error handler nodes (inline)
329
+ const errorNodes = Array.isArray(node.onError)
330
+ ? node.onError
331
+ : [node.onError];
332
+ await Promise.all(errorNodes.map((errorNodeId) => executeGraphNodeInline(workflowService, rpcService, runId, graphName, errorNodeId, { error: { message: error.message } }, graph)));
333
+ return;
334
+ }
335
+ }
336
+ // No error handler - rethrow
337
+ throw error;
338
+ }
339
+ }
340
+ /**
341
+ * Continue graph execution inline (without queue)
342
+ * Executes nodes in parallel where possible using Promise.all
343
+ */
344
+ async function continueGraphInline(workflowService, rpcService, runId, graphName, graph, triggerInput) {
345
+ while (true) {
346
+ // Get completed node IDs + branch keys (lightweight, no results)
347
+ const { completedNodeIds, branchKeys } = await workflowService.getCompletedGraphState(runId);
348
+ // Find candidate next nodes from completed nodes
349
+ const candidateNodes = [];
350
+ for (const nodeId of completedNodeIds) {
351
+ const node = graph[nodeId];
352
+ if (!node?.next)
353
+ continue;
354
+ const nextNodes = resolveNextFromConfig(node.next, branchKeys[nodeId]);
355
+ candidateNodes.push(...nextNodes);
356
+ }
357
+ if (candidateNodes.length === 0 && completedNodeIds.length > 0) {
358
+ // No more nodes to run - graph complete
359
+ await workflowService.updateRunStatus(runId, 'completed');
360
+ return;
361
+ }
362
+ // Filter to only nodes that don't have a step yet
363
+ const nodesToExecute = await workflowService.getNodesWithoutSteps(runId, candidateNodes);
364
+ if (nodesToExecute.length === 0) {
365
+ // No more nodes to execute - if we've completed at least one node, we're done
366
+ // This handles branching where only some paths are taken
367
+ if (completedNodeIds.length > 0) {
368
+ await workflowService.updateRunStatus(runId, 'completed');
369
+ }
370
+ return;
371
+ }
372
+ // Execute all nodes in parallel
373
+ await Promise.all(nodesToExecute.map(async (nodeId) => {
374
+ const node = graph[nodeId];
375
+ if (!node)
376
+ return;
377
+ // Evaluate input callback to get the mapping
378
+ const inputMapping = evaluateInputCallback(node);
379
+ // Only fetch results for nodes referenced in this node's input (excluding trigger)
380
+ const referencedNodeIds = extractReferencedNodeIds(inputMapping).filter((id) => id !== 'trigger');
381
+ const fetchedResults = await workflowService.getNodeResults(runId, referencedNodeIds);
382
+ // Merge fetched results with trigger input
383
+ const nodeResults = { trigger: triggerInput, ...fetchedResults };
384
+ const resolvedInput = resolveInputMapping(inputMapping, nodeResults);
385
+ await executeGraphNodeInline(workflowService, rpcService, runId, graphName, nodeId, resolvedInput, graph);
386
+ }));
387
+ }
388
+ }
389
+ /**
390
+ * Start a workflow graph execution
391
+ * @param startNode - Optional starting node ID (from wire config). If not provided, uses wires.api from graph definition.
392
+ */
393
+ export async function runWorkflowGraph(workflowService, graphName, triggerInput, rpcService, inline, startNode) {
394
+ const definition = getWorkflowGraph(graphName);
395
+ if (!definition) {
396
+ throw new Error(`Workflow graph '${graphName}' not found`);
397
+ }
398
+ // Use startNode from caller if provided, otherwise use wires.api from graph definition
399
+ const apiStartNode = startNode ?? definition.wires?.api;
400
+ const entryNodes = apiStartNode ? [apiStartNode] : [];
401
+ if (entryNodes.length === 0) {
402
+ throw new Error(`Workflow graph '${graphName}' has no wires.api defined and no startNode was provided`);
403
+ }
404
+ const graph = definition.graph;
405
+ const runId = await workflowService.createRun(graphName, triggerInput, inline);
406
+ // Register as inline for fast lookup
407
+ if (inline) {
408
+ workflowService.registerInlineRun(runId);
409
+ }
410
+ // Create nodeResults with trigger for resolving entry node inputs
411
+ const triggerNodeResults = { trigger: triggerInput };
412
+ try {
413
+ if (inline && rpcService) {
414
+ // Inline mode - execute entry nodes in parallel
415
+ await Promise.all(entryNodes.map(async (nodeId) => {
416
+ const node = graph[nodeId];
417
+ if (!node)
418
+ return;
419
+ // Evaluate and resolve entry node input
420
+ const inputMapping = evaluateInputCallback(node);
421
+ const resolvedInput = Object.keys(inputMapping).length > 0
422
+ ? resolveInputMapping(inputMapping, triggerNodeResults)
423
+ : triggerInput;
424
+ await executeGraphNodeInline(workflowService, rpcService, runId, graphName, nodeId, resolvedInput, graph);
425
+ }));
426
+ // Continue executing remaining nodes inline
427
+ await continueGraphInline(workflowService, rpcService, runId, graphName, graph, triggerInput);
428
+ }
429
+ else {
430
+ // Queue-based mode
431
+ for (const nodeId of entryNodes) {
432
+ const node = graph[nodeId];
433
+ if (!node)
434
+ continue;
435
+ // Evaluate and resolve entry node input
436
+ const inputMapping = evaluateInputCallback(node);
437
+ const resolvedInput = Object.keys(inputMapping).length > 0
438
+ ? resolveInputMapping(inputMapping, triggerNodeResults)
439
+ : triggerInput;
440
+ const rpcName = getRpcName(node);
441
+ await queueGraphNode(workflowService, runId, graphName, nodeId, rpcName, resolvedInput);
442
+ }
443
+ }
444
+ return { runId };
445
+ }
446
+ finally {
447
+ // Clean up inline tracking
448
+ if (inline) {
449
+ workflowService.unregisterInlineRun(runId);
450
+ }
451
+ }
452
+ }
@@ -0,0 +1,3 @@
1
+ export * from './workflow-graph.types.js';
2
+ export * from './graph-node.js';
3
+ export * from './graph-runner.js';
@@ -0,0 +1,3 @@
1
+ export * from './workflow-graph.types.js';
2
+ export * from './graph-node.js';
3
+ export * from './graph-runner.js';
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Ref value - internal representation after input callback is evaluated
3
+ */
4
+ export interface RefValue {
5
+ __isRef: true;
6
+ nodeId: string;
7
+ path?: string;
8
+ }
9
+ /**
10
+ * Helper to create a ref value
11
+ */
12
+ export declare const createRef: (nodeId: string, path?: string) => RefValue;
13
+ /**
14
+ * Check if a value is a ref
15
+ */
16
+ export declare const isRef: (value: unknown) => value is RefValue;
17
+ /**
18
+ * Input reference for fetching step results
19
+ */
20
+ export interface InputRef {
21
+ nodeId: string;
22
+ path?: string;
23
+ }
24
+ /**
25
+ * Ref function type - provides type-safe references to other nodes' outputs
26
+ * The actual typing is done at the pikkuWorkflowGraph level
27
+ */
28
+ export type RefFn<NodeIds extends string = string> = (nodeId: NodeIds, path?: string) => RefValue;
29
+ /**
30
+ * Next node configuration - fully serializable
31
+ * - string: single next node
32
+ * - string[]: parallel execution (all run concurrently)
33
+ * - Record<string, string | string[]>: branching (function calls graph.branch('key'))
34
+ */
35
+ export type NextConfig<NodeIds extends string = string> = NodeIds | NodeIds[] | Record<string, NodeIds | NodeIds[]>;
36
+ /**
37
+ * Graph node configuration - references functions by RPC name
38
+ */
39
+ export interface GraphNodeConfig<NodeIds extends string = string> {
40
+ /** RPC function name */
41
+ func: string;
42
+ /** Input mapping callback - receives ref function, returns input object */
43
+ input?: (ref: RefFn<NodeIds>) => Record<string, unknown>;
44
+ /** Next nodes - string, array, or record for branching */
45
+ next?: NextConfig<NodeIds>;
46
+ /** Error routing - node(s) to execute on error */
47
+ onError?: NodeIds | NodeIds[];
48
+ }
49
+ import type { WorkflowWires } from '../workflow.types.js';
50
+ export type { WorkflowWires };
51
+ /**
52
+ * Workflow graph definition (internal use)
53
+ */
54
+ export interface WorkflowGraphDefinition<Nodes extends Record<string, GraphNodeConfig<string>> = Record<string, GraphNodeConfig>> {
55
+ /** Unique workflow name */
56
+ name: string;
57
+ /** Wire configuration */
58
+ wires: WorkflowWires;
59
+ /** Graph nodes */
60
+ graph: Nodes;
61
+ }
62
+ /**
63
+ * Graph wire context - available to functions running in a workflow graph
64
+ */
65
+ export interface PikkuGraphWire {
66
+ /** Workflow run ID */
67
+ runId: string;
68
+ /** Graph name */
69
+ graphName: string;
70
+ /** Current node ID */
71
+ nodeId: string;
72
+ /**
73
+ * Select which branch to take for Record-based next config.
74
+ * Must be called if the node has a Record `next` configuration.
75
+ * @param key - The branch key to take (must match a key in the next Record)
76
+ */
77
+ branch: (key: string) => void;
78
+ /**
79
+ * Set a state variable value.
80
+ * @param name - Variable name
81
+ * @param value - Value to store
82
+ */
83
+ setState: (name: string, value: unknown) => Promise<void>;
84
+ /**
85
+ * Get the entire state object.
86
+ * @returns The state object with all variables
87
+ */
88
+ getState: () => Promise<Record<string, unknown>>;
89
+ }
90
+ /**
91
+ * Internal mutable state for graph wire (used by runner to capture branch)
92
+ */
93
+ export interface GraphWireState {
94
+ branchKey?: string;
95
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Helper to create a ref value
3
+ */
4
+ export const createRef = (nodeId, path) => ({
5
+ __isRef: true,
6
+ nodeId,
7
+ path,
8
+ });
9
+ /**
10
+ * Check if a value is a ref
11
+ */
12
+ export const isRef = (value) => typeof value === 'object' &&
13
+ value !== null &&
14
+ '__isRef' in value &&
15
+ value.__isRef === true;
@@ -1,15 +1,10 @@
1
1
  /**
2
2
  * Workflow module exports
3
3
  */
4
- export type { CoreWorkflow, WorkflowStepOptions, WorkflowStepMeta, RpcStepMeta, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, InputSource, OutputBinding, PikkuWorkflowWire, PikkuWorkflow, WorkflowsMeta, WorkflowRun, StepState, WorkflowStatus, StepStatus, } from './workflow.types.js';
5
- export { PikkuWorkflowService } from './pikku-workflow-service.js';
6
- export { addWorkflow } from './workflow-runner.js';
7
- /**
8
- * @deprecated This function is no longer used and will be removed in a future release.
9
- * It exists only for backwards compatibility with generated code.
10
- * TODO: Remove this export in a future release after updating code generation
11
- */
12
- export declare const wireWorkflow: <T extends {
13
- name: string;
14
- func: any;
15
- }>(workflow: T) => void;
4
+ export { PikkuWorkflowService, WorkflowCancelledException, } from './pikku-workflow-service.js';
5
+ export { addWorkflow } from './dsl/workflow-runner.js';
6
+ export { addWorkflowGraph } from './graph/graph-runner.js';
7
+ export { wireWorkflow } from './wire-workflow.js';
8
+ export type { WorkflowDefinition, WorkflowDefinitionFunc, WorkflowDefinitionGraph, } from './wire-workflow.js';
9
+ export type { WorkflowService, WorkflowServiceConfig, WorkflowHTTPWire, WorkflowWires, WorkflowStatus, StepStatus, WorkflowRun, StepState, CoreWorkflow, PikkuWorkflow, ContextVariable, WorkflowContext, WorkflowsMeta, WorkflowRuntimeMeta, WorkflowsRuntimeMeta, WorkflowStepInput, WorkflowOrchestratorInput, WorkflowSleeperInput, } from './workflow.types.js';
10
+ export type { WorkflowStepOptions, WorkflowWireDoRPC, WorkflowWireDoInline, WorkflowWireSleep, InputSource, OutputBinding, RpcStepMeta, SimpleCondition, Condition, BranchCase, BranchStepMeta, ParallelGroupStepMeta, FanoutStepMeta, ReturnStepMeta, InlineStepMeta, SleepStepMeta, CancelStepMeta, SetStepMeta, SwitchCaseMeta, SwitchStepMeta, FilterStepMeta, ArrayPredicateStepMeta, WorkflowStepMeta, WorkflowStepWire, PikkuWorkflowWire, } from './workflow.types.js';
@@ -1,14 +1,9 @@
1
1
  /**
2
2
  * Workflow module exports
3
3
  */
4
- export { PikkuWorkflowService } from './pikku-workflow-service.js';
5
- // Internal registration function (used by generated code)
6
- export { addWorkflow } from './workflow-runner.js';
7
- /**
8
- * @deprecated This function is no longer used and will be removed in a future release.
9
- * It exists only for backwards compatibility with generated code.
10
- * TODO: Remove this export in a future release after updating code generation
11
- */
12
- export const wireWorkflow = (workflow) => {
13
- // Empty function - no longer used, kept for backwards compatibility
14
- };
4
+ export { PikkuWorkflowService, WorkflowCancelledException, } from './pikku-workflow-service.js';
5
+ // Internal registration functions (used by generated code)
6
+ export { addWorkflow } from './dsl/workflow-runner.js';
7
+ export { addWorkflowGraph } from './graph/graph-runner.js';
8
+ // Unified wireWorkflow function
9
+ export { wireWorkflow } from './wire-workflow.js';