@pikku/core 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.
- package/CHANGELOG.md +35 -1
- package/dist/errors/error-handler.js +3 -3
- package/dist/errors/errors.d.ts +1 -1
- package/dist/errors/errors.js +3 -3
- package/dist/function/function-runner.d.ts +8 -8
- package/dist/function/function-runner.js +88 -23
- package/dist/function/functions.types.d.ts +104 -33
- package/dist/function/functions.types.js +50 -7
- package/dist/handle-error.d.ts +1 -1
- package/dist/handle-error.js +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +5 -2
- package/dist/middleware/auth-apikey.d.ts +1 -1
- package/dist/middleware/auth-apikey.js +3 -3
- package/dist/middleware/auth-bearer.d.ts +1 -1
- package/dist/middleware/auth-bearer.js +3 -3
- package/dist/middleware/auth-cookie.d.ts +1 -1
- package/dist/middleware/auth-cookie.js +5 -5
- package/dist/middleware/timeout.d.ts +1 -1
- package/dist/middleware/timeout.js +3 -2
- package/dist/middleware-runner.d.ts +6 -20
- package/dist/middleware-runner.js +19 -20
- package/dist/permissions.d.ts +6 -89
- package/dist/permissions.js +24 -81
- package/dist/pikku-state.d.ts +32 -104
- package/dist/pikku-state.js +140 -66
- package/dist/schema.d.ts +8 -6
- package/dist/schema.js +25 -13
- package/dist/services/user-session-service.d.ts +7 -3
- package/dist/services/user-session-service.js +8 -1
- package/dist/services/workflow-service.d.ts +38 -0
- package/dist/services/workflow-service.js +1 -0
- package/dist/types/core.types.d.ts +40 -41
- package/dist/types/core.types.js +4 -15
- package/dist/types/state.types.d.ts +135 -0
- package/dist/types/state.types.js +1 -0
- package/dist/utils.d.ts +9 -1
- package/dist/utils.js +48 -3
- package/dist/wirings/channel/channel-common.js +2 -3
- package/dist/wirings/channel/channel-handler.d.ts +3 -2
- package/dist/wirings/channel/channel-handler.js +7 -10
- package/dist/wirings/channel/channel-runner.d.ts +2 -2
- package/dist/wirings/channel/channel-runner.js +5 -5
- package/dist/wirings/channel/channel.types.d.ts +10 -5
- package/dist/wirings/channel/local/local-channel-runner.d.ts +1 -1
- package/dist/wirings/channel/local/local-channel-runner.js +19 -26
- package/dist/wirings/channel/log-channels.js +1 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.d.ts +1 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +43 -48
- package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
- package/dist/wirings/cli/cli-runner.d.ts +5 -5
- package/dist/wirings/cli/cli-runner.js +20 -38
- package/dist/wirings/cli/cli.types.d.ts +16 -11
- package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
- package/dist/wirings/forge-node/forge-node.types.js +38 -0
- package/dist/wirings/forge-node/index.d.ts +1 -0
- package/dist/wirings/forge-node/index.js +1 -0
- package/dist/wirings/http/http-runner.d.ts +8 -8
- package/dist/wirings/http/http-runner.js +46 -51
- package/dist/wirings/http/http.types.d.ts +19 -11
- package/dist/wirings/http/log-http-routes.js +1 -1
- package/dist/wirings/http/routers/path-to-regex.js +2 -2
- package/dist/wirings/mcp/mcp-runner.d.ts +2 -2
- package/dist/wirings/mcp/mcp-runner.js +33 -40
- package/dist/wirings/mcp/mcp.types.d.ts +6 -0
- package/dist/wirings/queue/queue-runner.d.ts +3 -3
- package/dist/wirings/queue/queue-runner.js +13 -23
- package/dist/wirings/queue/queue.types.d.ts +4 -11
- package/dist/wirings/rpc/index.d.ts +1 -1
- package/dist/wirings/rpc/index.js +1 -1
- package/dist/wirings/rpc/rpc-runner.d.ts +17 -16
- package/dist/wirings/rpc/rpc-runner.js +98 -61
- package/dist/wirings/rpc/rpc-types.d.ts +7 -0
- package/dist/wirings/scheduler/log-schedulers.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.d.ts +3 -3
- package/dist/wirings/scheduler/scheduler-runner.js +18 -29
- package/dist/wirings/scheduler/scheduler.types.d.ts +3 -8
- package/dist/wirings/trigger/index.d.ts +2 -0
- package/dist/wirings/trigger/index.js +2 -0
- package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
- package/dist/wirings/trigger/trigger-runner.js +57 -0
- package/dist/wirings/trigger/trigger.types.d.ts +42 -0
- package/dist/wirings/trigger/trigger.types.js +1 -0
- package/dist/wirings/workflow/dsl/index.d.ts +5 -0
- package/dist/wirings/workflow/dsl/index.js +4 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
- package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
- package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
- package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
- package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
- package/dist/wirings/workflow/graph/graph-node.js +58 -0
- package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
- package/dist/wirings/workflow/graph/graph-runner.js +452 -0
- package/dist/wirings/workflow/graph/index.d.ts +3 -0
- package/dist/wirings/workflow/graph/index.js +3 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
- package/dist/wirings/workflow/index.d.ts +7 -3
- package/dist/wirings/workflow/index.js +6 -3
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +108 -10
- package/dist/wirings/workflow/pikku-workflow-service.js +274 -165
- package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
- package/dist/wirings/workflow/wire-workflow.js +53 -0
- package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
- package/dist/wirings/workflow/workflow-utils.js +66 -0
- package/dist/wirings/workflow/workflow.types.d.ts +135 -128
- package/package.json +11 -2
- package/src/errors/error-handler.ts +3 -3
- package/src/errors/errors.ts +3 -3
- package/src/factory-functions.test.ts +9 -36
- package/src/function/function-runner.test.ts +58 -69
- package/src/function/function-runner.ts +130 -37
- package/src/function/functions.types.ts +193 -54
- package/src/handle-error.ts +1 -1
- package/src/index.ts +10 -5
- package/src/middleware/auth-apikey.test.ts +360 -0
- package/src/middleware/auth-apikey.ts +3 -7
- package/src/middleware/auth-bearer.test.ts +447 -0
- package/src/middleware/auth-bearer.ts +27 -33
- package/src/middleware/auth-cookie.test.ts +525 -0
- package/src/middleware/auth-cookie.ts +6 -6
- package/src/middleware/timeout.ts +4 -2
- package/src/middleware-runner.test.ts +58 -127
- package/src/middleware-runner.ts +29 -25
- package/src/permissions.test.ts +68 -130
- package/src/permissions.ts +41 -100
- package/src/pikku-state.ts +156 -201
- package/src/schema.ts +42 -13
- package/src/services/user-session-service.ts +14 -4
- package/src/services/workflow-service.ts +74 -0
- package/src/types/core.types.ts +79 -58
- package/src/types/state.types.ts +195 -0
- package/src/utils.ts +59 -4
- package/src/wirings/channel/channel-common.ts +4 -7
- package/src/wirings/channel/channel-handler.ts +17 -23
- package/src/wirings/channel/channel-runner.ts +7 -7
- package/src/wirings/channel/channel.types.ts +14 -11
- package/src/wirings/channel/local/local-channel-runner.test.ts +8 -4
- package/src/wirings/channel/local/local-channel-runner.ts +23 -38
- package/src/wirings/channel/log-channels.ts +1 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +53 -79
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
- package/src/wirings/cli/cli-runner.test.ts +26 -26
- package/src/wirings/cli/cli-runner.ts +34 -71
- package/src/wirings/cli/cli.types.ts +23 -16
- package/src/wirings/forge-node/forge-node.types.ts +135 -0
- package/src/wirings/forge-node/index.ts +1 -0
- package/src/wirings/http/http-runner.test.ts +10 -10
- package/src/wirings/http/http-runner.ts +56 -66
- package/src/wirings/http/http.types.ts +19 -14
- package/src/wirings/http/log-http-routes.ts +1 -1
- package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
- package/src/wirings/http/routers/path-to-regex.ts +2 -2
- package/src/wirings/mcp/mcp-runner.ts +44 -69
- package/src/wirings/mcp/mcp.types.ts +6 -0
- package/src/wirings/queue/queue-runner.test.ts +641 -0
- package/src/wirings/queue/queue-runner.ts +16 -38
- package/src/wirings/queue/queue.types.ts +4 -11
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +129 -77
- package/src/wirings/rpc/rpc-types.ts +8 -0
- package/src/wirings/scheduler/log-schedulers.ts +1 -1
- package/src/wirings/scheduler/scheduler-runner.test.ts +627 -0
- package/src/wirings/scheduler/scheduler-runner.ts +29 -55
- package/src/wirings/scheduler/scheduler.types.ts +3 -9
- package/src/wirings/trigger/index.ts +2 -0
- package/src/wirings/trigger/trigger-runner.ts +96 -0
- package/src/wirings/trigger/trigger.types.ts +56 -0
- package/src/wirings/workflow/dsl/index.ts +30 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
- package/src/wirings/workflow/graph/graph-node.ts +227 -0
- package/src/wirings/workflow/graph/graph-runner.ts +694 -0
- package/src/wirings/workflow/graph/index.ts +3 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
- package/src/wirings/workflow/index.ts +60 -12
- package/src/wirings/workflow/pikku-workflow-service.ts +401 -195
- package/src/wirings/workflow/wire-workflow.ts +94 -0
- package/src/wirings/workflow/workflow-utils.ts +120 -0
- package/src/wirings/workflow/workflow.types.ts +167 -188
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/workflow/workflow-runner.d.ts +0 -35
- package/dist/wirings/workflow/workflow-runner.js +0 -68
- package/src/wirings/workflow/workflow-runner.ts +0 -85
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { GraphNodeConfig, NextConfig, RefValue } from './workflow-graph.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Standard RPC handler interface (matches generated FlattenedRPCMap entries)
|
|
4
|
+
*/
|
|
5
|
+
export interface RPCHandler<I = any, O = any> {
|
|
6
|
+
input: I;
|
|
7
|
+
output: O;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Compute output types for all nodes based on their func (RPC name)
|
|
11
|
+
*/
|
|
12
|
+
export type NodeOutputMap<Nodes extends Record<string, {
|
|
13
|
+
func: string;
|
|
14
|
+
}>, RPCMap extends Record<string, RPCHandler>> = {
|
|
15
|
+
[K in keyof Nodes]: Nodes[K]['func'] extends keyof RPCMap ? RPCMap[Nodes[K]['func']]['output'] : unknown;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Node definition with RPC name reference
|
|
19
|
+
*/
|
|
20
|
+
export interface GraphNodeDef<RPCMap extends Record<string, RPCHandler>, NodeIds extends string = string, NodeOutputs extends Record<string, any> = Record<string, any>> {
|
|
21
|
+
/** RPC function name (must be a key in RPCMap) */
|
|
22
|
+
func: keyof RPCMap & string;
|
|
23
|
+
/** Input mapping - reference outputs from other nodes with autocomplete */
|
|
24
|
+
input?: (ref: <N extends keyof NodeOutputs & string>(nodeId: N, path: keyof NodeOutputs[N] & string) => RefValue) => Record<string, unknown>;
|
|
25
|
+
/** Next node(s) to execute */
|
|
26
|
+
next?: NextConfig<NodeIds>;
|
|
27
|
+
/** Error handling - node(s) to execute on error */
|
|
28
|
+
onError?: NodeIds | NodeIds[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Helper to create a type-safe graph definition with RPC autocomplete.
|
|
32
|
+
* Pass node IDs as first type parameter for type-safe next/ref/output key completion.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* // Import the typed graph from generated types
|
|
37
|
+
* import { pikkuWorkflowGraph, wireWorkflow } from './.pikku/workflow/pikku-workflow-types.gen.js'
|
|
38
|
+
*
|
|
39
|
+
* export const myGraph = pikkuWorkflowGraph({
|
|
40
|
+
* name: 'myWorkflow',
|
|
41
|
+
* nodes: {
|
|
42
|
+
* entry: 'createUserProfile', // autocompletes RPC names
|
|
43
|
+
* sendWelcome: 'sendEmail',
|
|
44
|
+
* },
|
|
45
|
+
* config: {
|
|
46
|
+
* entry: { next: 'sendWelcome' },
|
|
47
|
+
* sendWelcome: {
|
|
48
|
+
* input: (ref) => ({
|
|
49
|
+
* to: ref('entry', 'email'), // 'entry' and 'email' both autocomplete
|
|
50
|
+
* }),
|
|
51
|
+
* },
|
|
52
|
+
* },
|
|
53
|
+
* })
|
|
54
|
+
*
|
|
55
|
+
* wireWorkflow({
|
|
56
|
+
* wires: { http: { route: '/start', method: 'post' } },
|
|
57
|
+
* graph: myGraph,
|
|
58
|
+
* })
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
/**
|
|
62
|
+
* Type to compute output types for all nodes based on their func mapping
|
|
63
|
+
*/
|
|
64
|
+
type ComputeNodeOutputs<FuncMap extends Record<string, string>, RPCMap extends Record<string, RPCHandler>> = {
|
|
65
|
+
[K in keyof FuncMap]: FuncMap[K] extends keyof RPCMap ? RPCMap[FuncMap[K]]['output'] : unknown;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Type to compute input types for all nodes based on their func mapping
|
|
69
|
+
*/
|
|
70
|
+
type ComputeNodeInputs<FuncMap extends Record<string, string>, RPCMap extends Record<string, RPCHandler>> = {
|
|
71
|
+
[K in keyof FuncMap]: FuncMap[K] extends keyof RPCMap ? RPCMap[FuncMap[K]]['input'] : unknown;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Typed ref value - carries the type of the referenced field as a phantom type.
|
|
75
|
+
* At runtime this is just RefValue, but TypeScript tracks the type T.
|
|
76
|
+
*/
|
|
77
|
+
export type TypedRef<T> = RefValue & {
|
|
78
|
+
__phantomType?: T;
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Map input type fields to allow TypedRef of matching type as an alternative
|
|
82
|
+
*/
|
|
83
|
+
type InputWithRefs<T> = {
|
|
84
|
+
[K in keyof T]: T[K] | TypedRef<T[K]>;
|
|
85
|
+
};
|
|
86
|
+
export declare function createGraph<RPCMap extends Record<string, RPCHandler>>(): <const FuncMap extends Record<string, keyof RPCMap & string>>(funcMap: FuncMap, nodesOrBuilder?: GraphNodeConfigMap<FuncMap, RPCMap> | ((nodes: FuncMap) => GraphNodeConfigMap<FuncMap, RPCMap>)) => Record<Extract<keyof FuncMap, string>, GraphNodeConfig<Extract<keyof FuncMap, string>>>;
|
|
87
|
+
/**
|
|
88
|
+
* Type helper for node configuration map
|
|
89
|
+
*/
|
|
90
|
+
type GraphNodeConfigMap<FuncMap extends Record<string, string>, RPCMap extends Record<string, RPCHandler>> = {
|
|
91
|
+
[K in Extract<keyof FuncMap, string>]?: {
|
|
92
|
+
next?: NextConfig<Extract<keyof FuncMap, string>>;
|
|
93
|
+
input?: (ref: <N extends Extract<keyof FuncMap, string>, P extends keyof ComputeNodeOutputs<FuncMap, RPCMap>[N] & string>(nodeId: N, path: P) => TypedRef<ComputeNodeOutputs<FuncMap, RPCMap>[N][P]>) => InputWithRefs<ComputeNodeInputs<FuncMap, RPCMap>[K]>;
|
|
94
|
+
onError?: Extract<keyof FuncMap, string> | Extract<keyof FuncMap, string>[];
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Untyped graph for use without RPC map.
|
|
99
|
+
* Pass node IDs as type parameter for type-safe next/ref, or omit for untyped usage.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* // Typed usage - next and ref are type-checked
|
|
104
|
+
* graph<'entry' | 'sendWelcome'>({
|
|
105
|
+
* entry: { func: 'createUserProfile', next: 'sendWelcome' },
|
|
106
|
+
* sendWelcome: { func: 'sendEmail', input: (ref) => ({ to: ref('entry', 'email') }) },
|
|
107
|
+
* })
|
|
108
|
+
*
|
|
109
|
+
* // Untyped usage - no type checking on next/ref
|
|
110
|
+
* graph({
|
|
111
|
+
* entry: { func: 'createUserProfile', next: 'sendWelcome' },
|
|
112
|
+
* sendWelcome: { func: 'sendEmail' },
|
|
113
|
+
* })
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
export declare function graph<NodeIds extends string = string>(nodes: Record<NodeIds, {
|
|
117
|
+
func: string;
|
|
118
|
+
next?: NextConfig<NodeIds>;
|
|
119
|
+
input?: (ref: (nodeId: NodeIds, path: string) => RefValue) => Record<string, unknown>;
|
|
120
|
+
onError?: NodeIds | NodeIds[];
|
|
121
|
+
}>): Record<NodeIds, GraphNodeConfig<NodeIds>>;
|
|
122
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export function createGraph() {
|
|
2
|
+
return (funcMap, nodesOrBuilder) => {
|
|
3
|
+
// If no nodes provided, return just the funcMap converted to graph nodes
|
|
4
|
+
if (!nodesOrBuilder) {
|
|
5
|
+
const result = {};
|
|
6
|
+
for (const [nodeId, rpcName] of Object.entries(funcMap)) {
|
|
7
|
+
result[nodeId] = {
|
|
8
|
+
func: rpcName,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return result;
|
|
12
|
+
}
|
|
13
|
+
const nodes = typeof nodesOrBuilder === 'function'
|
|
14
|
+
? nodesOrBuilder(funcMap)
|
|
15
|
+
: nodesOrBuilder;
|
|
16
|
+
const result = {};
|
|
17
|
+
for (const [nodeId, def] of Object.entries(nodes)) {
|
|
18
|
+
result[nodeId] = {
|
|
19
|
+
func: funcMap[nodeId],
|
|
20
|
+
input: def?.input,
|
|
21
|
+
next: def?.next,
|
|
22
|
+
onError: def?.onError,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Untyped graph for use without RPC map.
|
|
30
|
+
* Pass node IDs as type parameter for type-safe next/ref, or omit for untyped usage.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* // Typed usage - next and ref are type-checked
|
|
35
|
+
* graph<'entry' | 'sendWelcome'>({
|
|
36
|
+
* entry: { func: 'createUserProfile', next: 'sendWelcome' },
|
|
37
|
+
* sendWelcome: { func: 'sendEmail', input: (ref) => ({ to: ref('entry', 'email') }) },
|
|
38
|
+
* })
|
|
39
|
+
*
|
|
40
|
+
* // Untyped usage - no type checking on next/ref
|
|
41
|
+
* graph({
|
|
42
|
+
* entry: { func: 'createUserProfile', next: 'sendWelcome' },
|
|
43
|
+
* sendWelcome: { func: 'sendEmail' },
|
|
44
|
+
* })
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function graph(nodes) {
|
|
48
|
+
const result = {};
|
|
49
|
+
for (const [nodeId, def] of Object.entries(nodes)) {
|
|
50
|
+
result[nodeId] = {
|
|
51
|
+
func: def.func,
|
|
52
|
+
input: def.input,
|
|
53
|
+
next: def.next,
|
|
54
|
+
onError: def.onError,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { PikkuWorkflowService } from '../pikku-workflow-service.js';
|
|
2
|
+
import type { GraphNodeConfig, WorkflowGraphDefinition } from './workflow-graph.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Add a workflow graph to the system
|
|
5
|
+
* This is called by the generated workflow wirings
|
|
6
|
+
*/
|
|
7
|
+
export declare const addWorkflowGraph: (workflowName: string, graphResult: {
|
|
8
|
+
graph: Record<string, GraphNodeConfig<string>>;
|
|
9
|
+
wires?: any;
|
|
10
|
+
}) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Get a registered workflow graph by name
|
|
13
|
+
*/
|
|
14
|
+
export declare function getWorkflowGraph(name: string): WorkflowGraphDefinition<any> | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Continue graph execution
|
|
17
|
+
* Non-blocking - finds pending nodes, resolves inputs, queues them for execution
|
|
18
|
+
*/
|
|
19
|
+
export declare function continueGraph(workflowService: PikkuWorkflowService, runId: string, graphName: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Execute a graph step with wire context.
|
|
22
|
+
* Called by the step worker when executing graph nodes.
|
|
23
|
+
*/
|
|
24
|
+
export declare function executeGraphStep(workflowService: PikkuWorkflowService, rpcService: any, runId: string, stepId: string, stepName: string, rpcName: string, data: any, graphName: string): Promise<any>;
|
|
25
|
+
/**
|
|
26
|
+
* Handle node completion - re-triggers graph continuation
|
|
27
|
+
*/
|
|
28
|
+
export declare function onGraphNodeComplete(workflowService: PikkuWorkflowService, runId: string, graphName: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Start a workflow graph execution
|
|
31
|
+
* @param startNode - Optional starting node ID (from wire config). If not provided, uses wires.api from graph definition.
|
|
32
|
+
*/
|
|
33
|
+
export declare function runWorkflowGraph(workflowService: PikkuWorkflowService, graphName: string, triggerInput: any, rpcService?: any, inline?: boolean, startNode?: string): Promise<{
|
|
34
|
+
runId: string;
|
|
35
|
+
}>;
|
|
@@ -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
|
+
}
|