@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
|
@@ -9,21 +9,61 @@ import {
|
|
|
9
9
|
SerializedError,
|
|
10
10
|
} from '../../types/core.types.js'
|
|
11
11
|
import { QueueService } from '../queue/queue.types.js'
|
|
12
|
-
import {
|
|
13
|
-
WorkflowAsyncException,
|
|
14
|
-
WorkflowCancelledException,
|
|
15
|
-
WorkflowNotFoundError,
|
|
16
|
-
WorkflowRunNotFound,
|
|
17
|
-
} from './workflow-runner.js'
|
|
18
12
|
import type {
|
|
19
13
|
PikkuWorkflowWire,
|
|
20
14
|
StepState,
|
|
21
15
|
WorkflowRun,
|
|
22
16
|
WorkflowStatus,
|
|
23
|
-
WorkflowService,
|
|
24
17
|
WorkflowServiceConfig,
|
|
25
18
|
WorkflowStepOptions,
|
|
26
19
|
} from './workflow.types.js'
|
|
20
|
+
import { executeGraphStep, runWorkflowGraph } from './graph/graph-runner.js'
|
|
21
|
+
import { WorkflowService } from '../../services/workflow-service.js'
|
|
22
|
+
import { PikkuError } from '../../errors/error-handler.js'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Exception thrown when workflow needs to pause for async step
|
|
26
|
+
*/
|
|
27
|
+
export class WorkflowAsyncException extends Error {
|
|
28
|
+
constructor(
|
|
29
|
+
public readonly runId: string,
|
|
30
|
+
public readonly stepName: string
|
|
31
|
+
) {
|
|
32
|
+
super(`Workflow paused at step: ${stepName}`)
|
|
33
|
+
this.name = 'WorkflowAsyncException'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Exception thrown when workflow is cancelled
|
|
39
|
+
*/
|
|
40
|
+
export class WorkflowCancelledException extends Error {
|
|
41
|
+
constructor(
|
|
42
|
+
public readonly runId: string,
|
|
43
|
+
public readonly reason?: string
|
|
44
|
+
) {
|
|
45
|
+
super(reason || 'Workflow cancelled')
|
|
46
|
+
this.name = 'WorkflowCancelledException'
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Error class for workflow not found
|
|
52
|
+
*/
|
|
53
|
+
export class WorkflowNotFoundError extends PikkuError {
|
|
54
|
+
constructor(name: string) {
|
|
55
|
+
super(`Workflow not found: ${name}`)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Error class for workflow not found
|
|
61
|
+
*/
|
|
62
|
+
export class WorkflowRunNotFound extends PikkuError {
|
|
63
|
+
constructor(runId: string) {
|
|
64
|
+
super(`Workflow run not found: ${runId}`)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
27
67
|
|
|
28
68
|
export class WorkflowServiceNotInitialized extends Error {}
|
|
29
69
|
export class WorkflowStepNameNotString extends Error {
|
|
@@ -41,9 +81,31 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
41
81
|
private config: WorkflowServiceConfig | undefined
|
|
42
82
|
private singletonServices: CoreSingletonServices | undefined
|
|
43
83
|
private createWireServices: CreateWireServices | undefined
|
|
84
|
+
private inlineRuns = new Set<string>()
|
|
44
85
|
|
|
45
86
|
constructor() {}
|
|
46
87
|
|
|
88
|
+
/**
|
|
89
|
+
* Check if a run is executing inline (without queues)
|
|
90
|
+
*/
|
|
91
|
+
protected isInline(runId: string): boolean {
|
|
92
|
+
return this.inlineRuns.has(runId)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Register a run as inline (for graph-runner to use)
|
|
97
|
+
*/
|
|
98
|
+
public registerInlineRun(runId: string): void {
|
|
99
|
+
this.inlineRuns.add(runId)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Unregister a run from inline tracking
|
|
104
|
+
*/
|
|
105
|
+
public unregisterInlineRun(runId: string): void {
|
|
106
|
+
this.inlineRuns.delete(runId)
|
|
107
|
+
}
|
|
108
|
+
|
|
47
109
|
public setServices(
|
|
48
110
|
singletonServices: CoreSingletonServices,
|
|
49
111
|
createWireServices: CreateWireServices,
|
|
@@ -58,7 +120,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
58
120
|
workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
|
|
59
121
|
stepWorkerQueueName:
|
|
60
122
|
workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
|
|
61
|
-
sleeperRPCName: workflow?.sleeperRPCName ?? '
|
|
123
|
+
sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
|
|
62
124
|
}
|
|
63
125
|
}
|
|
64
126
|
|
|
@@ -68,7 +130,11 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
68
130
|
* @param input - Input data for the workflow
|
|
69
131
|
* @returns Run ID
|
|
70
132
|
*/
|
|
71
|
-
abstract createRun(
|
|
133
|
+
abstract createRun(
|
|
134
|
+
workflowName: string,
|
|
135
|
+
input: any,
|
|
136
|
+
inline?: boolean
|
|
137
|
+
): Promise<string>
|
|
72
138
|
|
|
73
139
|
/**
|
|
74
140
|
* Get a workflow run by ID
|
|
@@ -193,6 +259,68 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
193
259
|
*/
|
|
194
260
|
abstract close(): Promise<void>
|
|
195
261
|
|
|
262
|
+
// ============================================================================
|
|
263
|
+
// Workflow Graph Methods
|
|
264
|
+
// ============================================================================
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get completed graph state (lightweight - no results)
|
|
268
|
+
* @param runId - Run ID
|
|
269
|
+
* @returns Completed node IDs and their branch keys
|
|
270
|
+
*/
|
|
271
|
+
abstract getCompletedGraphState(runId: string): Promise<{
|
|
272
|
+
completedNodeIds: string[]
|
|
273
|
+
branchKeys: Record<string, string>
|
|
274
|
+
}>
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Filter candidate nodes to only those without existing steps
|
|
278
|
+
* @param runId - Run ID
|
|
279
|
+
* @param nodeIds - Candidate node IDs to check
|
|
280
|
+
* @returns Node IDs that don't have a step yet
|
|
281
|
+
*/
|
|
282
|
+
abstract getNodesWithoutSteps(
|
|
283
|
+
runId: string,
|
|
284
|
+
nodeIds: string[]
|
|
285
|
+
): Promise<string[]>
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get results for specific nodes
|
|
289
|
+
* @param runId - Run ID
|
|
290
|
+
* @param nodeIds - Node IDs to fetch results for
|
|
291
|
+
* @returns Map of nodeId to result
|
|
292
|
+
*/
|
|
293
|
+
abstract getNodeResults(
|
|
294
|
+
runId: string,
|
|
295
|
+
nodeIds: string[]
|
|
296
|
+
): Promise<Record<string, any>>
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Set the branch key for a graph node step
|
|
300
|
+
* @param stepId - Step ID
|
|
301
|
+
* @param branchKey - Branch key selected by graph.branch()
|
|
302
|
+
*/
|
|
303
|
+
abstract setBranchTaken(stepId: string, branchKey: string): Promise<void>
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Update a state variable in the workflow run's state
|
|
307
|
+
* @param runId - Run ID
|
|
308
|
+
* @param name - Variable name
|
|
309
|
+
* @param value - Value to store
|
|
310
|
+
*/
|
|
311
|
+
abstract updateRunState(
|
|
312
|
+
runId: string,
|
|
313
|
+
name: string,
|
|
314
|
+
value: unknown
|
|
315
|
+
): Promise<void>
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get the entire state object for a workflow run
|
|
319
|
+
* @param runId - Run ID
|
|
320
|
+
* @returns The state object with all variables
|
|
321
|
+
*/
|
|
322
|
+
abstract getRunState(runId: string): Promise<Record<string, unknown>>
|
|
323
|
+
|
|
196
324
|
// ============================================================================
|
|
197
325
|
// Workflow Lifecycle Methods
|
|
198
326
|
// ============================================================================
|
|
@@ -211,7 +339,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
211
339
|
* Sets the step result to null and resumes the workflow
|
|
212
340
|
* @param data - Sleeper input data
|
|
213
341
|
*/
|
|
214
|
-
public async
|
|
342
|
+
public async executeWorkflowSleepCompleted(
|
|
215
343
|
runId: string,
|
|
216
344
|
stepId: string
|
|
217
345
|
): Promise<void> {
|
|
@@ -238,13 +366,39 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
238
366
|
|
|
239
367
|
/**
|
|
240
368
|
* Start a new workflow run
|
|
369
|
+
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
370
|
+
* @param options.inline - If true, execute workflow directly without queue service
|
|
371
|
+
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
241
372
|
*/
|
|
242
373
|
public async startWorkflow<I>(
|
|
243
374
|
name: string,
|
|
244
375
|
input: I,
|
|
245
|
-
rpcService: any
|
|
376
|
+
rpcService: any,
|
|
377
|
+
options?: { inline?: boolean; startNode?: string }
|
|
246
378
|
): Promise<{ runId: string }> {
|
|
247
|
-
|
|
379
|
+
// Check meta to determine workflow type
|
|
380
|
+
const meta = pikkuState(null, 'workflows', 'meta')
|
|
381
|
+
const workflowMeta = meta[name]
|
|
382
|
+
|
|
383
|
+
if (!workflowMeta) {
|
|
384
|
+
throw new WorkflowNotFoundError(name)
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Check if this is a graph workflow (source === 'graph')
|
|
388
|
+
if (workflowMeta.source === 'graph') {
|
|
389
|
+
// Graph workflows use the graph scheduler
|
|
390
|
+
return runWorkflowGraph(
|
|
391
|
+
this,
|
|
392
|
+
name,
|
|
393
|
+
input,
|
|
394
|
+
rpcService,
|
|
395
|
+
options?.inline,
|
|
396
|
+
options?.startNode
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// DSL workflow - check registration exists
|
|
401
|
+
const registrations = pikkuState(null, 'workflows', 'registrations')
|
|
248
402
|
const workflow = registrations.get(name)
|
|
249
403
|
|
|
250
404
|
if (!workflow) {
|
|
@@ -252,19 +406,29 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
252
406
|
}
|
|
253
407
|
|
|
254
408
|
// Create workflow run in state
|
|
255
|
-
const runId = await this.createRun(name, input)
|
|
409
|
+
const runId = await this.createRun(name, input, options?.inline)
|
|
256
410
|
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Queue orchestrator to start the workflow
|
|
261
|
-
await this.resumeWorkflow(runId)
|
|
262
|
-
} else {
|
|
263
|
-
// No queue service - execute directly via runWorkflowJob
|
|
264
|
-
await this.runWorkflowJob(runId, rpcService)
|
|
411
|
+
// Register inline run for fast lookup
|
|
412
|
+
if (options?.inline) {
|
|
413
|
+
this.inlineRuns.add(runId)
|
|
265
414
|
}
|
|
266
415
|
|
|
267
|
-
|
|
416
|
+
try {
|
|
417
|
+
// If inline mode or no queue service, execute directly
|
|
418
|
+
// Otherwise, use remote execution (queue-based)
|
|
419
|
+
if (options?.inline || !this.singletonServices?.queueService) {
|
|
420
|
+
await this.runWorkflowJob(runId, rpcService)
|
|
421
|
+
} else {
|
|
422
|
+
await this.resumeWorkflow(runId)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
return { runId }
|
|
426
|
+
} finally {
|
|
427
|
+
// Clean up inline tracking
|
|
428
|
+
if (options?.inline) {
|
|
429
|
+
this.inlineRuns.delete(runId)
|
|
430
|
+
}
|
|
431
|
+
}
|
|
268
432
|
}
|
|
269
433
|
|
|
270
434
|
public async runWorkflowJob(runId: string, rpcService: any): Promise<void> {
|
|
@@ -273,14 +437,14 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
273
437
|
throw new WorkflowRunNotFound(runId)
|
|
274
438
|
}
|
|
275
439
|
|
|
276
|
-
const registrations = pikkuState('workflows', 'registrations')
|
|
440
|
+
const registrations = pikkuState(null, 'workflows', 'registrations')
|
|
277
441
|
const workflow = registrations.get(run.workflow)
|
|
278
442
|
if (!workflow) {
|
|
279
443
|
throw new WorkflowNotFoundError(run.workflow)
|
|
280
444
|
}
|
|
281
445
|
|
|
282
446
|
await this.withRunLock(runId, async () => {
|
|
283
|
-
const meta = pikkuState('workflows', 'meta')
|
|
447
|
+
const meta = pikkuState(null, 'workflows', 'meta')
|
|
284
448
|
const workflowMeta = meta[run.workflow]
|
|
285
449
|
|
|
286
450
|
const workflowWire = this.createWorkflowWire(
|
|
@@ -292,7 +456,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
292
456
|
try {
|
|
293
457
|
const result = await runPikkuFunc(
|
|
294
458
|
'workflow',
|
|
295
|
-
workflowMeta.
|
|
459
|
+
workflowMeta.name,
|
|
296
460
|
workflowMeta.pikkuFuncName,
|
|
297
461
|
{
|
|
298
462
|
singletonServices: this.singletonServices!,
|
|
@@ -311,7 +475,12 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
311
475
|
|
|
312
476
|
// Check if it's a WorkflowCancelledException
|
|
313
477
|
if (error instanceof WorkflowCancelledException) {
|
|
314
|
-
// Workflow was cancelled - status
|
|
478
|
+
// Workflow was cancelled - update status and rethrow
|
|
479
|
+
await this.updateRunStatus(runId, 'cancelled', undefined, {
|
|
480
|
+
message: error.message || 'Workflow cancelled',
|
|
481
|
+
stack: '',
|
|
482
|
+
code: 'WORKFLOW_CANCELLED',
|
|
483
|
+
})
|
|
315
484
|
throw error
|
|
316
485
|
}
|
|
317
486
|
|
|
@@ -364,24 +533,44 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
364
533
|
}
|
|
365
534
|
|
|
366
535
|
try {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
536
|
+
let result: any
|
|
537
|
+
|
|
538
|
+
// Check if this is a graph node step (step name starts with 'node:')
|
|
539
|
+
if (stepName.startsWith('node:')) {
|
|
540
|
+
// Get the graph name from the workflow run
|
|
541
|
+
const run = await this.getRun(runId)
|
|
542
|
+
if (!run) {
|
|
543
|
+
throw new Error(`Workflow run not found: ${runId}`)
|
|
544
|
+
}
|
|
545
|
+
const graphName = run.workflow
|
|
546
|
+
|
|
547
|
+
// Execute as graph step with graph wire context
|
|
548
|
+
result = await executeGraphStep(
|
|
549
|
+
this,
|
|
550
|
+
rpcService,
|
|
370
551
|
runId,
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
552
|
+
stepState.stepId,
|
|
553
|
+
stepName,
|
|
554
|
+
rpcName,
|
|
555
|
+
data,
|
|
556
|
+
graphName
|
|
557
|
+
)
|
|
558
|
+
} else {
|
|
559
|
+
// Execute RPC with workflow step context (regular workflow)
|
|
560
|
+
result = await rpcService.rpcWithWire(rpcName, data, {
|
|
561
|
+
workflowStep: {
|
|
562
|
+
runId,
|
|
563
|
+
stepId: stepState.stepId,
|
|
564
|
+
attemptCount: stepState.attemptCount,
|
|
565
|
+
},
|
|
566
|
+
})
|
|
567
|
+
}
|
|
375
568
|
|
|
376
569
|
// Store result and mark succeeded
|
|
377
570
|
await this.setStepResult(stepState.stepId, result)
|
|
378
571
|
|
|
379
572
|
// Resume orchestrator to continue workflow
|
|
380
|
-
|
|
381
|
-
await this.resumeWorkflow(runId)
|
|
382
|
-
} catch (resumeError: any) {
|
|
383
|
-
throw resumeError
|
|
384
|
-
}
|
|
573
|
+
await this.resumeWorkflow(runId)
|
|
385
574
|
} catch (error: any) {
|
|
386
575
|
// Store error and mark failed
|
|
387
576
|
await this.setStepError(stepState.stepId, error)
|
|
@@ -451,7 +640,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
451
640
|
let stepState: StepState
|
|
452
641
|
try {
|
|
453
642
|
stepState = await this.getStepState(runId, stepName)
|
|
454
|
-
} catch
|
|
643
|
+
} catch {
|
|
455
644
|
// Step doesn't exist - create it
|
|
456
645
|
stepState = await this.insertStepState(
|
|
457
646
|
runId,
|
|
@@ -488,8 +677,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
488
677
|
// Step is pending - schedule it
|
|
489
678
|
await this.setStepScheduled(stepState.stepId)
|
|
490
679
|
|
|
491
|
-
// Enqueue step worker
|
|
492
|
-
if (this.singletonServices!.queueService) {
|
|
680
|
+
// Enqueue step worker (unless inline mode)
|
|
681
|
+
if (!this.isInline(runId) && this.singletonServices!.queueService) {
|
|
493
682
|
// Map step retry options to queue job options
|
|
494
683
|
const retries = stepOptions?.retries ?? 0
|
|
495
684
|
const retryDelay = stepOptions?.retryDelay
|
|
@@ -514,45 +703,52 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
514
703
|
: undefined,
|
|
515
704
|
}
|
|
516
705
|
)
|
|
706
|
+
// Pause workflow - step will callback when done
|
|
707
|
+
throw new WorkflowAsyncException(runId, stepName)
|
|
517
708
|
} else {
|
|
518
|
-
//
|
|
709
|
+
// Inline or no queue service - execute locally with retry loop
|
|
519
710
|
const retries = stepOptions?.retries ?? 0
|
|
520
711
|
const retryDelay = stepOptions?.retryDelay
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
try {
|
|
524
|
-
const result = await rpcService.rpcWithWire(rpcName, data, {
|
|
525
|
-
workflowStep: {
|
|
526
|
-
runId,
|
|
527
|
-
stepId: stepState.stepId,
|
|
528
|
-
attemptCount: stepState.attemptCount,
|
|
529
|
-
},
|
|
530
|
-
})
|
|
531
|
-
await this.setStepResult(stepState.stepId, result)
|
|
532
|
-
return result
|
|
533
|
-
} catch (error: any) {
|
|
534
|
-
// Record the error (marks step as failed)
|
|
535
|
-
await this.setStepError(stepState.stepId, error)
|
|
712
|
+
let currentStepState = stepState
|
|
536
713
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
await
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
714
|
+
while (true) {
|
|
715
|
+
try {
|
|
716
|
+
await this.setStepRunning(currentStepState.stepId)
|
|
717
|
+
const result = await rpcService.rpcWithWire(rpcName, data, {
|
|
718
|
+
workflowStep: {
|
|
719
|
+
runId,
|
|
720
|
+
stepId: currentStepState.stepId,
|
|
721
|
+
attemptCount: currentStepState.attemptCount,
|
|
722
|
+
},
|
|
723
|
+
})
|
|
724
|
+
await this.setStepResult(currentStepState.stepId, result)
|
|
725
|
+
return result
|
|
726
|
+
} catch (error: any) {
|
|
727
|
+
// Record the error (marks step as failed)
|
|
728
|
+
await this.setStepError(currentStepState.stepId, error)
|
|
729
|
+
|
|
730
|
+
// Check if we should retry
|
|
731
|
+
if (currentStepState.attemptCount < retries) {
|
|
732
|
+
// Create a new pending retry attempt
|
|
733
|
+
currentStepState = await this.createRetryAttempt(
|
|
734
|
+
currentStepState.stepId,
|
|
735
|
+
'pending'
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
// Wait for retry delay if specified
|
|
739
|
+
if (retryDelay) {
|
|
740
|
+
await new Promise((resolve) =>
|
|
741
|
+
setTimeout(resolve, getDurationInMilliseconds(retryDelay))
|
|
742
|
+
)
|
|
743
|
+
}
|
|
744
|
+
// Continue loop to retry
|
|
745
|
+
} else {
|
|
746
|
+
// No more retries, fail the workflow
|
|
747
|
+
throw error
|
|
748
|
+
}
|
|
547
749
|
}
|
|
548
|
-
|
|
549
|
-
// No more retries, fail the workflow
|
|
550
|
-
throw error
|
|
551
750
|
}
|
|
552
751
|
}
|
|
553
|
-
|
|
554
|
-
// Pause workflow - step will callback when done
|
|
555
|
-
throw new WorkflowAsyncException(runId, stepName)
|
|
556
752
|
}
|
|
557
753
|
|
|
558
754
|
private async inlineStep(
|
|
@@ -565,7 +761,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
565
761
|
let stepState: StepState
|
|
566
762
|
try {
|
|
567
763
|
stepState = await this.getStepState(runId, stepName)
|
|
568
|
-
} catch
|
|
764
|
+
} catch {
|
|
569
765
|
// Step doesn't exist - create it (inline, no RPC)
|
|
570
766
|
stepState = await this.insertStepState(
|
|
571
767
|
runId,
|
|
@@ -584,30 +780,68 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
584
780
|
// Execute inline function
|
|
585
781
|
const retries = stepOptions?.retries ?? this.getConfig().retries
|
|
586
782
|
const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay
|
|
587
|
-
|
|
783
|
+
let currentStepState = stepState
|
|
588
784
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
785
|
+
// Check if we're running inline (in-memory) or remote (queue-based)
|
|
786
|
+
if (this.isInline(runId)) {
|
|
787
|
+
// Inline mode - execute with retry loop
|
|
788
|
+
while (true) {
|
|
789
|
+
try {
|
|
790
|
+
await this.setStepRunning(currentStepState.stepId)
|
|
791
|
+
const result = await fn()
|
|
792
|
+
await this.setStepResult(currentStepState.stepId, result)
|
|
793
|
+
return result
|
|
794
|
+
} catch (error: any) {
|
|
795
|
+
// Record the error (marks step as failed)
|
|
796
|
+
await this.setStepError(currentStepState.stepId, error)
|
|
797
|
+
|
|
798
|
+
// Check if we should retry
|
|
799
|
+
if (currentStepState.attemptCount < retries) {
|
|
800
|
+
// Create a new pending retry attempt
|
|
801
|
+
currentStepState = await this.createRetryAttempt(
|
|
802
|
+
currentStepState.stepId,
|
|
803
|
+
'pending'
|
|
804
|
+
)
|
|
805
|
+
|
|
806
|
+
// Wait for retry delay if specified
|
|
807
|
+
if (retryDelay) {
|
|
808
|
+
await new Promise((resolve) =>
|
|
809
|
+
setTimeout(resolve, getDurationInMilliseconds(retryDelay))
|
|
810
|
+
)
|
|
811
|
+
}
|
|
812
|
+
// Continue loop to retry
|
|
813
|
+
} else {
|
|
814
|
+
// No more retries, fail the workflow
|
|
815
|
+
throw error
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
} else {
|
|
820
|
+
// Remote mode - use queue-based retry
|
|
821
|
+
try {
|
|
822
|
+
await this.setStepRunning(currentStepState.stepId)
|
|
823
|
+
const result = await fn()
|
|
824
|
+
await this.setStepResult(currentStepState.stepId, result)
|
|
825
|
+
return result
|
|
826
|
+
} catch (error: any) {
|
|
827
|
+
// Record the error (marks step as failed)
|
|
828
|
+
await this.setStepError(currentStepState.stepId, error)
|
|
829
|
+
|
|
830
|
+
// Check if we should retry
|
|
831
|
+
if (currentStepState.attemptCount < retries) {
|
|
832
|
+
// Create a new pending retry attempt (copies metadata from failed step)
|
|
833
|
+
await this.createRetryAttempt(currentStepState.stepId, 'pending')
|
|
596
834
|
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
// Create a new pending retry attempt (copies metadata from failed step)
|
|
600
|
-
await this.createRetryAttempt(stepState.stepId, 'pending')
|
|
835
|
+
// Schedule orchestrator to retry after delay
|
|
836
|
+
await this.scheduleOrchestratorRetry(runId, retryDelay)
|
|
601
837
|
|
|
602
|
-
|
|
603
|
-
|
|
838
|
+
// Pause workflow - orchestrator will replay and pick up new attempt
|
|
839
|
+
throw new WorkflowAsyncException(runId, stepName)
|
|
840
|
+
}
|
|
604
841
|
|
|
605
|
-
//
|
|
606
|
-
throw
|
|
842
|
+
// No more retries, fail the workflow
|
|
843
|
+
throw error
|
|
607
844
|
}
|
|
608
|
-
|
|
609
|
-
// No more retries, fail the workflow
|
|
610
|
-
throw error
|
|
611
845
|
}
|
|
612
846
|
}
|
|
613
847
|
|
|
@@ -616,7 +850,7 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
616
850
|
let stepState: StepState
|
|
617
851
|
try {
|
|
618
852
|
stepState = await this.getStepState(runId, stepName)
|
|
619
|
-
} catch
|
|
853
|
+
} catch {
|
|
620
854
|
// Step doesn't exist - create it (sleep step, no RPC)
|
|
621
855
|
stepState = await this.insertStepState(
|
|
622
856
|
runId,
|
|
@@ -640,9 +874,9 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
640
874
|
// Step is pending - schedule it
|
|
641
875
|
await this.setStepScheduled(stepState.stepId)
|
|
642
876
|
|
|
643
|
-
// Check if
|
|
644
|
-
if (this.singletonServices!.schedulerService) {
|
|
645
|
-
// Remote mode -
|
|
877
|
+
// Check if inline mode or no scheduler service
|
|
878
|
+
if (!this.isInline(runId) && this.singletonServices!.schedulerService) {
|
|
879
|
+
// Remote mode - schedule sleep via scheduler service
|
|
646
880
|
await this.singletonServices!.schedulerService.scheduleRPC(
|
|
647
881
|
duration,
|
|
648
882
|
this.getConfig().sleeperRPCName,
|
|
@@ -651,6 +885,8 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
651
885
|
stepId: stepState.stepId,
|
|
652
886
|
}
|
|
653
887
|
)
|
|
888
|
+
// Pause workflow - sleep will callback when done
|
|
889
|
+
throw new WorkflowAsyncException(runId, stepName)
|
|
654
890
|
} else {
|
|
655
891
|
// Inline mode - use setTimeout with actual duration
|
|
656
892
|
await new Promise((resolve) =>
|
|
@@ -659,21 +895,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
659
895
|
await this.setStepResult(stepState.stepId, null)
|
|
660
896
|
return
|
|
661
897
|
}
|
|
662
|
-
|
|
663
|
-
// Pause workflow - sleep will callback when done
|
|
664
|
-
throw new WorkflowAsyncException(runId, stepName)
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
private async cancelWorkflow(runId: string, reason?: string) {
|
|
668
|
-
// Update workflow run status to cancelled
|
|
669
|
-
await this.updateRunStatus(runId, 'cancelled', undefined, {
|
|
670
|
-
message: reason || 'Workflow cancelled by user',
|
|
671
|
-
stack: '',
|
|
672
|
-
code: 'WORKFLOW_CANCELLED',
|
|
673
|
-
})
|
|
674
|
-
|
|
675
|
-
// Throw cancellation exception to stop workflow execution
|
|
676
|
-
throw new WorkflowCancelledException(runId, reason)
|
|
677
898
|
}
|
|
678
899
|
|
|
679
900
|
private createWorkflowWire(
|
|
@@ -722,11 +943,6 @@ export abstract class PikkuWorkflowService implements WorkflowService {
|
|
|
722
943
|
getDurationInMilliseconds(duration)
|
|
723
944
|
)
|
|
724
945
|
},
|
|
725
|
-
|
|
726
|
-
// Implement workflow.cancel()
|
|
727
|
-
cancel: async (reason?: string): Promise<void> => {
|
|
728
|
-
await this.cancelWorkflow(runId, reason)
|
|
729
|
-
},
|
|
730
946
|
}
|
|
731
947
|
return workflowWire
|
|
732
948
|
}
|