@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.
- package/CHANGELOG.md +24 -0
- package/dist/errors/error-handler.js +3 -3
- package/dist/function/function-runner.d.ts +4 -3
- package/dist/function/function-runner.js +60 -14
- package/dist/function/functions.types.d.ts +84 -5
- package/dist/function/functions.types.js +41 -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-bearer.d.ts +1 -1
- package/dist/middleware/auth-cookie.d.ts +1 -1
- package/dist/middleware/timeout.d.ts +1 -1
- package/dist/middleware-runner.d.ts +3 -14
- package/dist/middleware-runner.js +8 -8
- package/dist/permissions.d.ts +3 -86
- package/dist/permissions.js +11 -67
- 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/workflow-service.d.ts +38 -0
- package/dist/services/workflow-service.js +1 -0
- package/dist/types/core.types.d.ts +12 -4
- package/dist/types/core.types.js +1 -1
- package/dist/types/state.types.d.ts +135 -0
- package/dist/types/state.types.js +1 -0
- package/dist/utils.d.ts +8 -0
- package/dist/utils.js +45 -0
- package/dist/wirings/channel/channel-handler.js +1 -1
- package/dist/wirings/channel/channel-runner.js +5 -5
- package/dist/wirings/channel/log-channels.js +1 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
- package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
- package/dist/wirings/cli/cli-runner.js +10 -10
- package/dist/wirings/cli/cli.types.d.ts +1 -0
- 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 +2 -2
- package/dist/wirings/http/http-runner.js +21 -12
- package/dist/wirings/http/http.types.d.ts +14 -1
- 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.js +21 -21
- package/dist/wirings/queue/queue-runner.js +6 -6
- 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 +7 -8
- package/dist/wirings/rpc/rpc-runner.js +62 -14
- 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.js +5 -5
- 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 -12
- package/dist/wirings/workflow/index.js +6 -11
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +96 -6
- package/dist/wirings/workflow/pikku-workflow-service.js +230 -103
- 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 +133 -211
- package/package.json +10 -1
- package/src/errors/error-handler.ts +3 -3
- package/src/function/function-runner.test.ts +1 -1
- package/src/function/function-runner.ts +86 -16
- package/src/function/functions.types.ts +126 -4
- package/src/index.ts +10 -5
- package/src/middleware-runner.ts +16 -9
- package/src/permissions.test.ts +28 -86
- package/src/permissions.ts +21 -80
- package/src/pikku-state.ts +156 -201
- package/src/schema.ts +42 -13
- package/src/services/workflow-service.ts +74 -0
- package/src/types/core.types.ts +12 -3
- package/src/types/state.types.ts +195 -0
- package/src/utils.ts +55 -0
- package/src/wirings/channel/channel-handler.ts +1 -1
- package/src/wirings/channel/channel-runner.ts +5 -5
- package/src/wirings/channel/local/local-channel-runner.test.ts +3 -1
- package/src/wirings/channel/log-channels.ts +1 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -2
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
- package/src/wirings/cli/cli-runner.test.ts +19 -19
- package/src/wirings/cli/cli-runner.ts +10 -9
- package/src/wirings/cli/cli.types.ts +1 -0
- 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 +2 -2
- package/src/wirings/http/http-runner.ts +30 -13
- package/src/wirings/http/http.types.ts +14 -0
- 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 +21 -21
- package/src/wirings/queue/queue-runner.test.ts +19 -19
- package/src/wirings/queue/queue-runner.ts +6 -6
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +88 -22
- 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 +27 -27
- package/src/wirings/scheduler/scheduler-runner.ts +5 -5
- 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 +53 -26
- package/src/wirings/workflow/pikku-workflow-service.ts +333 -117
- 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 +166 -277
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
- package/dist/wirings/workflow/workflow-runner.js +0 -65
- package/src/function/function-runner.ts.bak +0 -188
- package/src/wirings/workflow/workflow-runner.ts +0 -72
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
import { CoreConfig, CoreSingletonServices, CreateWireServices, SerializedError } from '../../types/core.types.js';
|
|
2
|
-
import type { StepState, WorkflowRun, WorkflowStatus,
|
|
2
|
+
import type { StepState, WorkflowRun, WorkflowStatus, WorkflowStepOptions } from './workflow.types.js';
|
|
3
|
+
import { WorkflowService } from '../../services/workflow-service.js';
|
|
4
|
+
import { PikkuError } from '../../errors/error-handler.js';
|
|
5
|
+
/**
|
|
6
|
+
* Exception thrown when workflow needs to pause for async step
|
|
7
|
+
*/
|
|
8
|
+
export declare class WorkflowAsyncException extends Error {
|
|
9
|
+
readonly runId: string;
|
|
10
|
+
readonly stepName: string;
|
|
11
|
+
constructor(runId: string, stepName: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Exception thrown when workflow is cancelled
|
|
15
|
+
*/
|
|
16
|
+
export declare class WorkflowCancelledException extends Error {
|
|
17
|
+
readonly runId: string;
|
|
18
|
+
readonly reason?: string | undefined;
|
|
19
|
+
constructor(runId: string, reason?: string | undefined);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Error class for workflow not found
|
|
23
|
+
*/
|
|
24
|
+
export declare class WorkflowNotFoundError extends PikkuError {
|
|
25
|
+
constructor(name: string);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Error class for workflow not found
|
|
29
|
+
*/
|
|
30
|
+
export declare class WorkflowRunNotFound extends PikkuError {
|
|
31
|
+
constructor(runId: string);
|
|
32
|
+
}
|
|
3
33
|
export declare class WorkflowServiceNotInitialized extends Error {
|
|
4
34
|
}
|
|
5
35
|
export declare class WorkflowStepNameNotString extends Error {
|
|
@@ -14,7 +44,20 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
14
44
|
private config;
|
|
15
45
|
private singletonServices;
|
|
16
46
|
private createWireServices;
|
|
47
|
+
private inlineRuns;
|
|
17
48
|
constructor();
|
|
49
|
+
/**
|
|
50
|
+
* Check if a run is executing inline (without queues)
|
|
51
|
+
*/
|
|
52
|
+
protected isInline(runId: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Register a run as inline (for graph-runner to use)
|
|
55
|
+
*/
|
|
56
|
+
registerInlineRun(runId: string): void;
|
|
57
|
+
/**
|
|
58
|
+
* Unregister a run from inline tracking
|
|
59
|
+
*/
|
|
60
|
+
unregisterInlineRun(runId: string): void;
|
|
18
61
|
setServices(singletonServices: CoreSingletonServices, createWireServices: CreateWireServices, { workflow }: CoreConfig): void;
|
|
19
62
|
/**
|
|
20
63
|
* Create a new workflow run
|
|
@@ -22,7 +65,7 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
22
65
|
* @param input - Input data for the workflow
|
|
23
66
|
* @returns Run ID
|
|
24
67
|
*/
|
|
25
|
-
abstract createRun(workflowName: string, input: any): Promise<string>;
|
|
68
|
+
abstract createRun(workflowName: string, input: any, inline?: boolean): Promise<string>;
|
|
26
69
|
/**
|
|
27
70
|
* Get a workflow run by ID
|
|
28
71
|
* @param id - Run ID
|
|
@@ -115,6 +158,48 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
115
158
|
* Close any open connections
|
|
116
159
|
*/
|
|
117
160
|
abstract close(): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Get completed graph state (lightweight - no results)
|
|
163
|
+
* @param runId - Run ID
|
|
164
|
+
* @returns Completed node IDs and their branch keys
|
|
165
|
+
*/
|
|
166
|
+
abstract getCompletedGraphState(runId: string): Promise<{
|
|
167
|
+
completedNodeIds: string[];
|
|
168
|
+
branchKeys: Record<string, string>;
|
|
169
|
+
}>;
|
|
170
|
+
/**
|
|
171
|
+
* Filter candidate nodes to only those without existing steps
|
|
172
|
+
* @param runId - Run ID
|
|
173
|
+
* @param nodeIds - Candidate node IDs to check
|
|
174
|
+
* @returns Node IDs that don't have a step yet
|
|
175
|
+
*/
|
|
176
|
+
abstract getNodesWithoutSteps(runId: string, nodeIds: string[]): Promise<string[]>;
|
|
177
|
+
/**
|
|
178
|
+
* Get results for specific nodes
|
|
179
|
+
* @param runId - Run ID
|
|
180
|
+
* @param nodeIds - Node IDs to fetch results for
|
|
181
|
+
* @returns Map of nodeId to result
|
|
182
|
+
*/
|
|
183
|
+
abstract getNodeResults(runId: string, nodeIds: string[]): Promise<Record<string, any>>;
|
|
184
|
+
/**
|
|
185
|
+
* Set the branch key for a graph node step
|
|
186
|
+
* @param stepId - Step ID
|
|
187
|
+
* @param branchKey - Branch key selected by graph.branch()
|
|
188
|
+
*/
|
|
189
|
+
abstract setBranchTaken(stepId: string, branchKey: string): Promise<void>;
|
|
190
|
+
/**
|
|
191
|
+
* Update a state variable in the workflow run's state
|
|
192
|
+
* @param runId - Run ID
|
|
193
|
+
* @param name - Variable name
|
|
194
|
+
* @param value - Value to store
|
|
195
|
+
*/
|
|
196
|
+
abstract updateRunState(runId: string, name: string, value: unknown): Promise<void>;
|
|
197
|
+
/**
|
|
198
|
+
* Get the entire state object for a workflow run
|
|
199
|
+
* @param runId - Run ID
|
|
200
|
+
* @returns The state object with all variables
|
|
201
|
+
*/
|
|
202
|
+
abstract getRunState(runId: string): Promise<Record<string, unknown>>;
|
|
118
203
|
/**
|
|
119
204
|
* Resume a paused workflow by triggering the orchestrator
|
|
120
205
|
* @param runId - Run ID
|
|
@@ -125,7 +210,7 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
125
210
|
* Sets the step result to null and resumes the workflow
|
|
126
211
|
* @param data - Sleeper input data
|
|
127
212
|
*/
|
|
128
|
-
|
|
213
|
+
executeWorkflowSleepCompleted(runId: string, stepId: string): Promise<void>;
|
|
129
214
|
/**
|
|
130
215
|
* Schedule orchestrator retry with delay
|
|
131
216
|
* @param runId - Run ID
|
|
@@ -134,8 +219,14 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
134
219
|
protected scheduleOrchestratorRetry(runId: string, retryDelay?: number | string): Promise<void>;
|
|
135
220
|
/**
|
|
136
221
|
* Start a new workflow run
|
|
137
|
-
|
|
138
|
-
|
|
222
|
+
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
223
|
+
* @param options.inline - If true, execute workflow directly without queue service
|
|
224
|
+
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
225
|
+
*/
|
|
226
|
+
startWorkflow<I>(name: string, input: I, rpcService: any, options?: {
|
|
227
|
+
inline?: boolean;
|
|
228
|
+
startNode?: string;
|
|
229
|
+
}): Promise<{
|
|
139
230
|
runId: string;
|
|
140
231
|
}>;
|
|
141
232
|
runWorkflowJob(runId: string, rpcService: any): Promise<void>;
|
|
@@ -153,7 +244,6 @@ export declare abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
153
244
|
private rpcStep;
|
|
154
245
|
private inlineStep;
|
|
155
246
|
private sleepStep;
|
|
156
|
-
private cancelWorkflow;
|
|
157
247
|
private createWorkflowWire;
|
|
158
248
|
private verifyStepName;
|
|
159
249
|
private getConfig;
|
|
@@ -1,7 +1,50 @@
|
|
|
1
1
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
3
|
import { getDurationInMilliseconds } from '../../time-utils.js';
|
|
4
|
-
import {
|
|
4
|
+
import { executeGraphStep, runWorkflowGraph } from './graph/graph-runner.js';
|
|
5
|
+
import { PikkuError } from '../../errors/error-handler.js';
|
|
6
|
+
/**
|
|
7
|
+
* Exception thrown when workflow needs to pause for async step
|
|
8
|
+
*/
|
|
9
|
+
export class WorkflowAsyncException extends Error {
|
|
10
|
+
runId;
|
|
11
|
+
stepName;
|
|
12
|
+
constructor(runId, stepName) {
|
|
13
|
+
super(`Workflow paused at step: ${stepName}`);
|
|
14
|
+
this.runId = runId;
|
|
15
|
+
this.stepName = stepName;
|
|
16
|
+
this.name = 'WorkflowAsyncException';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Exception thrown when workflow is cancelled
|
|
21
|
+
*/
|
|
22
|
+
export class WorkflowCancelledException extends Error {
|
|
23
|
+
runId;
|
|
24
|
+
reason;
|
|
25
|
+
constructor(runId, reason) {
|
|
26
|
+
super(reason || 'Workflow cancelled');
|
|
27
|
+
this.runId = runId;
|
|
28
|
+
this.reason = reason;
|
|
29
|
+
this.name = 'WorkflowCancelledException';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Error class for workflow not found
|
|
34
|
+
*/
|
|
35
|
+
export class WorkflowNotFoundError extends PikkuError {
|
|
36
|
+
constructor(name) {
|
|
37
|
+
super(`Workflow not found: ${name}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Error class for workflow not found
|
|
42
|
+
*/
|
|
43
|
+
export class WorkflowRunNotFound extends PikkuError {
|
|
44
|
+
constructor(runId) {
|
|
45
|
+
super(`Workflow run not found: ${runId}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
5
48
|
export class WorkflowServiceNotInitialized extends Error {
|
|
6
49
|
}
|
|
7
50
|
export class WorkflowStepNameNotString extends Error {
|
|
@@ -18,7 +61,26 @@ export class PikkuWorkflowService {
|
|
|
18
61
|
config;
|
|
19
62
|
singletonServices;
|
|
20
63
|
createWireServices;
|
|
64
|
+
inlineRuns = new Set();
|
|
21
65
|
constructor() { }
|
|
66
|
+
/**
|
|
67
|
+
* Check if a run is executing inline (without queues)
|
|
68
|
+
*/
|
|
69
|
+
isInline(runId) {
|
|
70
|
+
return this.inlineRuns.has(runId);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register a run as inline (for graph-runner to use)
|
|
74
|
+
*/
|
|
75
|
+
registerInlineRun(runId) {
|
|
76
|
+
this.inlineRuns.add(runId);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Unregister a run from inline tracking
|
|
80
|
+
*/
|
|
81
|
+
unregisterInlineRun(runId) {
|
|
82
|
+
this.inlineRuns.delete(runId);
|
|
83
|
+
}
|
|
22
84
|
setServices(singletonServices, createWireServices, { workflow }) {
|
|
23
85
|
this.singletonServices = singletonServices;
|
|
24
86
|
this.createWireServices = createWireServices;
|
|
@@ -27,7 +89,7 @@ export class PikkuWorkflowService {
|
|
|
27
89
|
retryDelay: workflow?.retryDelay ?? 0,
|
|
28
90
|
orchestratorQueueName: workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
|
|
29
91
|
stepWorkerQueueName: workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
|
|
30
|
-
sleeperRPCName: workflow?.sleeperRPCName ?? '
|
|
92
|
+
sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
|
|
31
93
|
};
|
|
32
94
|
}
|
|
33
95
|
// ============================================================================
|
|
@@ -46,7 +108,7 @@ export class PikkuWorkflowService {
|
|
|
46
108
|
* Sets the step result to null and resumes the workflow
|
|
47
109
|
* @param data - Sleeper input data
|
|
48
110
|
*/
|
|
49
|
-
async
|
|
111
|
+
async executeWorkflowSleepCompleted(runId, stepId) {
|
|
50
112
|
await this.setStepResult(stepId, null);
|
|
51
113
|
await this.resumeWorkflow(runId);
|
|
52
114
|
}
|
|
@@ -61,44 +123,69 @@ export class PikkuWorkflowService {
|
|
|
61
123
|
}
|
|
62
124
|
/**
|
|
63
125
|
* Start a new workflow run
|
|
126
|
+
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
127
|
+
* @param options.inline - If true, execute workflow directly without queue service
|
|
128
|
+
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
64
129
|
*/
|
|
65
|
-
async startWorkflow(name, input, rpcService) {
|
|
66
|
-
|
|
130
|
+
async startWorkflow(name, input, rpcService, options) {
|
|
131
|
+
// Check meta to determine workflow type
|
|
132
|
+
const meta = pikkuState(null, 'workflows', 'meta');
|
|
133
|
+
const workflowMeta = meta[name];
|
|
134
|
+
if (!workflowMeta) {
|
|
135
|
+
throw new WorkflowNotFoundError(name);
|
|
136
|
+
}
|
|
137
|
+
// Check if this is a graph workflow (source === 'graph')
|
|
138
|
+
if (workflowMeta.source === 'graph') {
|
|
139
|
+
// Graph workflows use the graph scheduler
|
|
140
|
+
return runWorkflowGraph(this, name, input, rpcService, options?.inline, options?.startNode);
|
|
141
|
+
}
|
|
142
|
+
// DSL workflow - check registration exists
|
|
143
|
+
const registrations = pikkuState(null, 'workflows', 'registrations');
|
|
67
144
|
const workflow = registrations.get(name);
|
|
68
145
|
if (!workflow) {
|
|
69
146
|
throw new WorkflowNotFoundError(name);
|
|
70
147
|
}
|
|
71
148
|
// Create workflow run in state
|
|
72
|
-
const runId = await this.createRun(name, input);
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
// Queue orchestrator to start the workflow
|
|
77
|
-
await this.resumeWorkflow(runId);
|
|
149
|
+
const runId = await this.createRun(name, input, options?.inline);
|
|
150
|
+
// Register inline run for fast lookup
|
|
151
|
+
if (options?.inline) {
|
|
152
|
+
this.inlineRuns.add(runId);
|
|
78
153
|
}
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
|
|
154
|
+
try {
|
|
155
|
+
// If inline mode or no queue service, execute directly
|
|
156
|
+
// Otherwise, use remote execution (queue-based)
|
|
157
|
+
if (options?.inline || !this.singletonServices?.queueService) {
|
|
158
|
+
await this.runWorkflowJob(runId, rpcService);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
await this.resumeWorkflow(runId);
|
|
162
|
+
}
|
|
163
|
+
return { runId };
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
// Clean up inline tracking
|
|
167
|
+
if (options?.inline) {
|
|
168
|
+
this.inlineRuns.delete(runId);
|
|
169
|
+
}
|
|
82
170
|
}
|
|
83
|
-
return { runId };
|
|
84
171
|
}
|
|
85
172
|
async runWorkflowJob(runId, rpcService) {
|
|
86
173
|
const run = await this.getRun(runId);
|
|
87
174
|
if (!run) {
|
|
88
175
|
throw new WorkflowRunNotFound(runId);
|
|
89
176
|
}
|
|
90
|
-
const registrations = pikkuState('workflows', 'registrations');
|
|
177
|
+
const registrations = pikkuState(null, 'workflows', 'registrations');
|
|
91
178
|
const workflow = registrations.get(run.workflow);
|
|
92
179
|
if (!workflow) {
|
|
93
180
|
throw new WorkflowNotFoundError(run.workflow);
|
|
94
181
|
}
|
|
95
182
|
await this.withRunLock(runId, async () => {
|
|
96
|
-
const meta = pikkuState('workflows', 'meta');
|
|
183
|
+
const meta = pikkuState(null, 'workflows', 'meta');
|
|
97
184
|
const workflowMeta = meta[run.workflow];
|
|
98
185
|
const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService);
|
|
99
186
|
const wire = { workflow: workflowWire };
|
|
100
187
|
try {
|
|
101
|
-
const result = await runPikkuFunc('workflow', workflowMeta.
|
|
188
|
+
const result = await runPikkuFunc('workflow', workflowMeta.name, workflowMeta.pikkuFuncName, {
|
|
102
189
|
singletonServices: this.singletonServices,
|
|
103
190
|
wire,
|
|
104
191
|
createWireServices: this.createWireServices,
|
|
@@ -113,7 +200,12 @@ export class PikkuWorkflowService {
|
|
|
113
200
|
}
|
|
114
201
|
// Check if it's a WorkflowCancelledException
|
|
115
202
|
if (error instanceof WorkflowCancelledException) {
|
|
116
|
-
// Workflow was cancelled - status
|
|
203
|
+
// Workflow was cancelled - update status and rethrow
|
|
204
|
+
await this.updateRunStatus(runId, 'cancelled', undefined, {
|
|
205
|
+
message: error.message || 'Workflow cancelled',
|
|
206
|
+
stack: '',
|
|
207
|
+
code: 'WORKFLOW_CANCELLED',
|
|
208
|
+
});
|
|
117
209
|
throw error;
|
|
118
210
|
}
|
|
119
211
|
await this.updateRunStatus(runId, 'failed', undefined, {
|
|
@@ -152,23 +244,32 @@ export class PikkuWorkflowService {
|
|
|
152
244
|
await this.setStepRunning(stepState.stepId);
|
|
153
245
|
}
|
|
154
246
|
try {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
247
|
+
let result;
|
|
248
|
+
// Check if this is a graph node step (step name starts with 'node:')
|
|
249
|
+
if (stepName.startsWith('node:')) {
|
|
250
|
+
// Get the graph name from the workflow run
|
|
251
|
+
const run = await this.getRun(runId);
|
|
252
|
+
if (!run) {
|
|
253
|
+
throw new Error(`Workflow run not found: ${runId}`);
|
|
254
|
+
}
|
|
255
|
+
const graphName = run.workflow;
|
|
256
|
+
// Execute as graph step with graph wire context
|
|
257
|
+
result = await executeGraphStep(this, rpcService, runId, stepState.stepId, stepName, rpcName, data, graphName);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// Execute RPC with workflow step context (regular workflow)
|
|
261
|
+
result = await rpcService.rpcWithWire(rpcName, data, {
|
|
262
|
+
workflowStep: {
|
|
263
|
+
runId,
|
|
264
|
+
stepId: stepState.stepId,
|
|
265
|
+
attemptCount: stepState.attemptCount,
|
|
266
|
+
},
|
|
267
|
+
});
|
|
268
|
+
}
|
|
163
269
|
// Store result and mark succeeded
|
|
164
270
|
await this.setStepResult(stepState.stepId, result);
|
|
165
271
|
// Resume orchestrator to continue workflow
|
|
166
|
-
|
|
167
|
-
await this.resumeWorkflow(runId);
|
|
168
|
-
}
|
|
169
|
-
catch (resumeError) {
|
|
170
|
-
throw resumeError;
|
|
171
|
-
}
|
|
272
|
+
await this.resumeWorkflow(runId);
|
|
172
273
|
}
|
|
173
274
|
catch (error) {
|
|
174
275
|
// Store error and mark failed
|
|
@@ -218,7 +319,7 @@ export class PikkuWorkflowService {
|
|
|
218
319
|
try {
|
|
219
320
|
stepState = await this.getStepState(runId, stepName);
|
|
220
321
|
}
|
|
221
|
-
catch
|
|
322
|
+
catch {
|
|
222
323
|
// Step doesn't exist - create it
|
|
223
324
|
stepState = await this.insertStepState(runId, stepName, rpcName, data, stepOptions);
|
|
224
325
|
}
|
|
@@ -242,8 +343,8 @@ export class PikkuWorkflowService {
|
|
|
242
343
|
}
|
|
243
344
|
// Step is pending - schedule it
|
|
244
345
|
await this.setStepScheduled(stepState.stepId);
|
|
245
|
-
// Enqueue step worker
|
|
246
|
-
if (this.singletonServices.queueService) {
|
|
346
|
+
// Enqueue step worker (unless inline mode)
|
|
347
|
+
if (!this.isInline(runId) && this.singletonServices.queueService) {
|
|
247
348
|
// Map step retry options to queue job options
|
|
248
349
|
const retries = stepOptions?.retries ?? 0;
|
|
249
350
|
const retryDelay = stepOptions?.retryDelay;
|
|
@@ -262,41 +363,47 @@ export class PikkuWorkflowService {
|
|
|
262
363
|
? 'exponential'
|
|
263
364
|
: undefined,
|
|
264
365
|
});
|
|
366
|
+
// Pause workflow - step will callback when done
|
|
367
|
+
throw new WorkflowAsyncException(runId, stepName);
|
|
265
368
|
}
|
|
266
369
|
else {
|
|
267
|
-
//
|
|
370
|
+
// Inline or no queue service - execute locally with retry loop
|
|
268
371
|
const retries = stepOptions?.retries ?? 0;
|
|
269
372
|
const retryDelay = stepOptions?.retryDelay;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
//
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
373
|
+
let currentStepState = stepState;
|
|
374
|
+
while (true) {
|
|
375
|
+
try {
|
|
376
|
+
await this.setStepRunning(currentStepState.stepId);
|
|
377
|
+
const result = await rpcService.rpcWithWire(rpcName, data, {
|
|
378
|
+
workflowStep: {
|
|
379
|
+
runId,
|
|
380
|
+
stepId: currentStepState.stepId,
|
|
381
|
+
attemptCount: currentStepState.attemptCount,
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
await this.setStepResult(currentStepState.stepId, result);
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
catch (error) {
|
|
388
|
+
// Record the error (marks step as failed)
|
|
389
|
+
await this.setStepError(currentStepState.stepId, error);
|
|
390
|
+
// Check if we should retry
|
|
391
|
+
if (currentStepState.attemptCount < retries) {
|
|
392
|
+
// Create a new pending retry attempt
|
|
393
|
+
currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
394
|
+
// Wait for retry delay if specified
|
|
395
|
+
if (retryDelay) {
|
|
396
|
+
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
|
|
397
|
+
}
|
|
398
|
+
// Continue loop to retry
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
// No more retries, fail the workflow
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
293
404
|
}
|
|
294
|
-
// No more retries, fail the workflow
|
|
295
|
-
throw error;
|
|
296
405
|
}
|
|
297
406
|
}
|
|
298
|
-
// Pause workflow - step will callback when done
|
|
299
|
-
throw new WorkflowAsyncException(runId, stepName);
|
|
300
407
|
}
|
|
301
408
|
async inlineStep(runId, stepName, fn, stepOptions) {
|
|
302
409
|
// Check if step already exists
|
|
@@ -304,7 +411,7 @@ export class PikkuWorkflowService {
|
|
|
304
411
|
try {
|
|
305
412
|
stepState = await this.getStepState(runId, stepName);
|
|
306
413
|
}
|
|
307
|
-
catch
|
|
414
|
+
catch {
|
|
308
415
|
// Step doesn't exist - create it (inline, no RPC)
|
|
309
416
|
stepState = await this.insertStepState(runId, stepName, null, // No RPC for inline steps
|
|
310
417
|
null, // No data for inline steps
|
|
@@ -317,26 +424,60 @@ export class PikkuWorkflowService {
|
|
|
317
424
|
// Execute inline function
|
|
318
425
|
const retries = stepOptions?.retries ?? this.getConfig().retries;
|
|
319
426
|
const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
427
|
+
let currentStepState = stepState;
|
|
428
|
+
// Check if we're running inline (in-memory) or remote (queue-based)
|
|
429
|
+
if (this.isInline(runId)) {
|
|
430
|
+
// Inline mode - execute with retry loop
|
|
431
|
+
while (true) {
|
|
432
|
+
try {
|
|
433
|
+
await this.setStepRunning(currentStepState.stepId);
|
|
434
|
+
const result = await fn();
|
|
435
|
+
await this.setStepResult(currentStepState.stepId, result);
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
catch (error) {
|
|
439
|
+
// Record the error (marks step as failed)
|
|
440
|
+
await this.setStepError(currentStepState.stepId, error);
|
|
441
|
+
// Check if we should retry
|
|
442
|
+
if (currentStepState.attemptCount < retries) {
|
|
443
|
+
// Create a new pending retry attempt
|
|
444
|
+
currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
445
|
+
// Wait for retry delay if specified
|
|
446
|
+
if (retryDelay) {
|
|
447
|
+
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
|
|
448
|
+
}
|
|
449
|
+
// Continue loop to retry
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
// No more retries, fail the workflow
|
|
453
|
+
throw error;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
325
457
|
}
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
|
|
458
|
+
else {
|
|
459
|
+
// Remote mode - use queue-based retry
|
|
460
|
+
try {
|
|
461
|
+
await this.setStepRunning(currentStepState.stepId);
|
|
462
|
+
const result = await fn();
|
|
463
|
+
await this.setStepResult(currentStepState.stepId, result);
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
catch (error) {
|
|
467
|
+
// Record the error (marks step as failed)
|
|
468
|
+
await this.setStepError(currentStepState.stepId, error);
|
|
469
|
+
// Check if we should retry
|
|
470
|
+
if (currentStepState.attemptCount < retries) {
|
|
471
|
+
// Create a new pending retry attempt (copies metadata from failed step)
|
|
472
|
+
await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
473
|
+
// Schedule orchestrator to retry after delay
|
|
474
|
+
await this.scheduleOrchestratorRetry(runId, retryDelay);
|
|
475
|
+
// Pause workflow - orchestrator will replay and pick up new attempt
|
|
476
|
+
throw new WorkflowAsyncException(runId, stepName);
|
|
477
|
+
}
|
|
478
|
+
// No more retries, fail the workflow
|
|
479
|
+
throw error;
|
|
337
480
|
}
|
|
338
|
-
// No more retries, fail the workflow
|
|
339
|
-
throw error;
|
|
340
481
|
}
|
|
341
482
|
}
|
|
342
483
|
async sleepStep(runId, stepName, duration) {
|
|
@@ -345,7 +486,7 @@ export class PikkuWorkflowService {
|
|
|
345
486
|
try {
|
|
346
487
|
stepState = await this.getStepState(runId, stepName);
|
|
347
488
|
}
|
|
348
|
-
catch
|
|
489
|
+
catch {
|
|
349
490
|
// Step doesn't exist - create it (sleep step, no RPC)
|
|
350
491
|
stepState = await this.insertStepState(runId, stepName, null, // No RPC for sleep steps
|
|
351
492
|
{ duration }, // Store duration as data
|
|
@@ -362,13 +503,15 @@ export class PikkuWorkflowService {
|
|
|
362
503
|
}
|
|
363
504
|
// Step is pending - schedule it
|
|
364
505
|
await this.setStepScheduled(stepState.stepId);
|
|
365
|
-
// Check if
|
|
366
|
-
if (this.singletonServices.schedulerService) {
|
|
367
|
-
// Remote mode -
|
|
506
|
+
// Check if inline mode or no scheduler service
|
|
507
|
+
if (!this.isInline(runId) && this.singletonServices.schedulerService) {
|
|
508
|
+
// Remote mode - schedule sleep via scheduler service
|
|
368
509
|
await this.singletonServices.schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, {
|
|
369
510
|
runId,
|
|
370
511
|
stepId: stepState.stepId,
|
|
371
512
|
});
|
|
513
|
+
// Pause workflow - sleep will callback when done
|
|
514
|
+
throw new WorkflowAsyncException(runId, stepName);
|
|
372
515
|
}
|
|
373
516
|
else {
|
|
374
517
|
// Inline mode - use setTimeout with actual duration
|
|
@@ -376,18 +519,6 @@ export class PikkuWorkflowService {
|
|
|
376
519
|
await this.setStepResult(stepState.stepId, null);
|
|
377
520
|
return;
|
|
378
521
|
}
|
|
379
|
-
// Pause workflow - sleep will callback when done
|
|
380
|
-
throw new WorkflowAsyncException(runId, stepName);
|
|
381
|
-
}
|
|
382
|
-
async cancelWorkflow(runId, reason) {
|
|
383
|
-
// Update workflow run status to cancelled
|
|
384
|
-
await this.updateRunStatus(runId, 'cancelled', undefined, {
|
|
385
|
-
message: reason || 'Workflow cancelled by user',
|
|
386
|
-
stack: '',
|
|
387
|
-
code: 'WORKFLOW_CANCELLED',
|
|
388
|
-
});
|
|
389
|
-
// Throw cancellation exception to stop workflow execution
|
|
390
|
-
throw new WorkflowCancelledException(runId, reason);
|
|
391
522
|
}
|
|
392
523
|
createWorkflowWire(workflowName, runId, rpcService) {
|
|
393
524
|
const workflowWire = {
|
|
@@ -409,10 +540,6 @@ export class PikkuWorkflowService {
|
|
|
409
540
|
this.verifyStepName(stepName);
|
|
410
541
|
await this.sleepStep(runId, stepName, getDurationInMilliseconds(duration));
|
|
411
542
|
},
|
|
412
|
-
// Implement workflow.cancel()
|
|
413
|
-
cancel: async (reason) => {
|
|
414
|
-
await this.cancelWorkflow(runId, reason);
|
|
415
|
-
},
|
|
416
543
|
};
|
|
417
544
|
return workflowWire;
|
|
418
545
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { WorkflowWires } from './workflow.types.js';
|
|
2
|
+
import type { GraphNodeConfig } from './graph/workflow-graph.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Workflow definition with DSL function
|
|
5
|
+
*/
|
|
6
|
+
export interface WorkflowDefinitionFunc {
|
|
7
|
+
wires: WorkflowWires;
|
|
8
|
+
func: {
|
|
9
|
+
func: (...args: any[]) => any;
|
|
10
|
+
} | ((...args: any[]) => any);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Workflow definition with graph
|
|
14
|
+
*/
|
|
15
|
+
export interface WorkflowDefinitionGraph<T extends Record<string, GraphNodeConfig<string>> = Record<string, GraphNodeConfig<string>>> {
|
|
16
|
+
wires: WorkflowWires;
|
|
17
|
+
graph: T;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Union type for workflow definitions
|
|
21
|
+
*/
|
|
22
|
+
export type WorkflowDefinition<T extends Record<string, GraphNodeConfig<string>> = Record<string, GraphNodeConfig<string>>> = WorkflowDefinitionFunc | WorkflowDefinitionGraph<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Wire a workflow with triggers.
|
|
25
|
+
* Accepts either a DSL function (func) or a graph definition (graph).
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```typescript
|
|
29
|
+
* // DSL workflow
|
|
30
|
+
* wireWorkflow({
|
|
31
|
+
* wires: { http: { route: '/start', method: 'post' } },
|
|
32
|
+
* func: myWorkflowFunc,
|
|
33
|
+
* })
|
|
34
|
+
*
|
|
35
|
+
* // Graph workflow
|
|
36
|
+
* wireWorkflow({
|
|
37
|
+
* wires: { http: { route: '/graph-start', method: 'post' } },
|
|
38
|
+
* graph: myGraphWorkflow,
|
|
39
|
+
* })
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare function wireWorkflow<T extends Record<string, GraphNodeConfig<string>>>(definition: WorkflowDefinitionFunc | WorkflowDefinitionGraph<T>): void;
|