@pikku/core 0.11.1 → 0.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/errors/error-handler.js +3 -3
  3. package/dist/function/function-runner.d.ts +4 -3
  4. package/dist/function/function-runner.js +60 -14
  5. package/dist/function/functions.types.d.ts +84 -5
  6. package/dist/function/functions.types.js +41 -1
  7. package/dist/index.d.ts +6 -2
  8. package/dist/index.js +5 -2
  9. package/dist/middleware/auth-apikey.d.ts +1 -1
  10. package/dist/middleware/auth-bearer.d.ts +1 -1
  11. package/dist/middleware/auth-cookie.d.ts +1 -1
  12. package/dist/middleware/timeout.d.ts +1 -1
  13. package/dist/middleware-runner.d.ts +3 -14
  14. package/dist/middleware-runner.js +8 -8
  15. package/dist/permissions.d.ts +3 -86
  16. package/dist/permissions.js +11 -67
  17. package/dist/pikku-state.d.ts +32 -104
  18. package/dist/pikku-state.js +140 -66
  19. package/dist/schema.d.ts +8 -6
  20. package/dist/schema.js +25 -13
  21. package/dist/services/workflow-service.d.ts +38 -0
  22. package/dist/services/workflow-service.js +1 -0
  23. package/dist/types/core.types.d.ts +12 -4
  24. package/dist/types/core.types.js +1 -1
  25. package/dist/types/state.types.d.ts +135 -0
  26. package/dist/types/state.types.js +1 -0
  27. package/dist/utils.d.ts +8 -0
  28. package/dist/utils.js +45 -0
  29. package/dist/wirings/channel/channel-handler.js +1 -1
  30. package/dist/wirings/channel/channel-runner.js +5 -5
  31. package/dist/wirings/channel/log-channels.js +1 -1
  32. package/dist/wirings/channel/serverless/serverless-channel-runner.js +2 -2
  33. package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
  34. package/dist/wirings/cli/cli-runner.js +10 -10
  35. package/dist/wirings/cli/cli.types.d.ts +1 -0
  36. package/dist/wirings/forge-node/forge-node.types.d.ts +120 -0
  37. package/dist/wirings/forge-node/forge-node.types.js +38 -0
  38. package/dist/wirings/forge-node/index.d.ts +1 -0
  39. package/dist/wirings/forge-node/index.js +1 -0
  40. package/dist/wirings/http/http-runner.d.ts +2 -2
  41. package/dist/wirings/http/http-runner.js +21 -12
  42. package/dist/wirings/http/http.types.d.ts +14 -1
  43. package/dist/wirings/http/log-http-routes.js +1 -1
  44. package/dist/wirings/http/routers/path-to-regex.js +2 -2
  45. package/dist/wirings/mcp/mcp-runner.js +21 -21
  46. package/dist/wirings/queue/queue-runner.js +6 -6
  47. package/dist/wirings/rpc/index.d.ts +1 -1
  48. package/dist/wirings/rpc/index.js +1 -1
  49. package/dist/wirings/rpc/rpc-runner.d.ts +7 -8
  50. package/dist/wirings/rpc/rpc-runner.js +62 -14
  51. package/dist/wirings/rpc/rpc-types.d.ts +7 -0
  52. package/dist/wirings/scheduler/log-schedulers.js +1 -1
  53. package/dist/wirings/scheduler/scheduler-runner.js +5 -5
  54. package/dist/wirings/trigger/index.d.ts +2 -0
  55. package/dist/wirings/trigger/index.js +2 -0
  56. package/dist/wirings/trigger/trigger-runner.d.ts +29 -0
  57. package/dist/wirings/trigger/trigger-runner.js +57 -0
  58. package/dist/wirings/trigger/trigger.types.d.ts +42 -0
  59. package/dist/wirings/trigger/trigger.types.js +1 -0
  60. package/dist/wirings/workflow/dsl/index.d.ts +5 -0
  61. package/dist/wirings/workflow/dsl/index.js +4 -0
  62. package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +286 -0
  63. package/dist/wirings/workflow/dsl/workflow-dsl.types.js +5 -0
  64. package/dist/wirings/workflow/dsl/workflow-runner.d.ts +5 -0
  65. package/dist/wirings/workflow/dsl/workflow-runner.js +22 -0
  66. package/dist/wirings/workflow/graph/graph-node.d.ts +122 -0
  67. package/dist/wirings/workflow/graph/graph-node.js +58 -0
  68. package/dist/wirings/workflow/graph/graph-runner.d.ts +35 -0
  69. package/dist/wirings/workflow/graph/graph-runner.js +452 -0
  70. package/dist/wirings/workflow/graph/index.d.ts +3 -0
  71. package/dist/wirings/workflow/graph/index.js +3 -0
  72. package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +95 -0
  73. package/dist/wirings/workflow/graph/workflow-graph.types.js +15 -0
  74. package/dist/wirings/workflow/index.d.ts +7 -12
  75. package/dist/wirings/workflow/index.js +6 -11
  76. package/dist/wirings/workflow/pikku-workflow-service.d.ts +96 -6
  77. package/dist/wirings/workflow/pikku-workflow-service.js +230 -103
  78. package/dist/wirings/workflow/wire-workflow.d.ts +42 -0
  79. package/dist/wirings/workflow/wire-workflow.js +53 -0
  80. package/dist/wirings/workflow/workflow-utils.d.ts +23 -0
  81. package/dist/wirings/workflow/workflow-utils.js +66 -0
  82. package/dist/wirings/workflow/workflow.types.d.ts +133 -211
  83. package/package.json +10 -1
  84. package/src/errors/error-handler.ts +3 -3
  85. package/src/function/function-runner.test.ts +1 -1
  86. package/src/function/function-runner.ts +86 -16
  87. package/src/function/functions.types.ts +126 -4
  88. package/src/index.ts +10 -5
  89. package/src/middleware-runner.ts +16 -9
  90. package/src/permissions.test.ts +28 -86
  91. package/src/permissions.ts +21 -80
  92. package/src/pikku-state.ts +156 -201
  93. package/src/schema.ts +42 -13
  94. package/src/services/workflow-service.ts +74 -0
  95. package/src/types/core.types.ts +12 -3
  96. package/src/types/state.types.ts +195 -0
  97. package/src/utils.ts +55 -0
  98. package/src/wirings/channel/channel-handler.ts +1 -1
  99. package/src/wirings/channel/channel-runner.ts +5 -5
  100. package/src/wirings/channel/local/local-channel-runner.test.ts +3 -1
  101. package/src/wirings/channel/log-channels.ts +1 -1
  102. package/src/wirings/channel/serverless/serverless-channel-runner.ts +2 -2
  103. package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
  104. package/src/wirings/cli/cli-runner.test.ts +19 -19
  105. package/src/wirings/cli/cli-runner.ts +10 -9
  106. package/src/wirings/cli/cli.types.ts +1 -0
  107. package/src/wirings/forge-node/forge-node.types.ts +135 -0
  108. package/src/wirings/forge-node/index.ts +1 -0
  109. package/src/wirings/http/http-runner.test.ts +2 -2
  110. package/src/wirings/http/http-runner.ts +30 -13
  111. package/src/wirings/http/http.types.ts +14 -0
  112. package/src/wirings/http/log-http-routes.ts +1 -1
  113. package/src/wirings/http/routers/path-to-regex.test.ts +30 -19
  114. package/src/wirings/http/routers/path-to-regex.ts +2 -2
  115. package/src/wirings/mcp/mcp-runner.ts +21 -21
  116. package/src/wirings/queue/queue-runner.test.ts +19 -19
  117. package/src/wirings/queue/queue-runner.ts +6 -6
  118. package/src/wirings/rpc/index.ts +1 -1
  119. package/src/wirings/rpc/rpc-runner.ts +88 -22
  120. package/src/wirings/rpc/rpc-types.ts +8 -0
  121. package/src/wirings/scheduler/log-schedulers.ts +1 -1
  122. package/src/wirings/scheduler/scheduler-runner.test.ts +27 -27
  123. package/src/wirings/scheduler/scheduler-runner.ts +5 -5
  124. package/src/wirings/trigger/index.ts +2 -0
  125. package/src/wirings/trigger/trigger-runner.ts +96 -0
  126. package/src/wirings/trigger/trigger.types.ts +56 -0
  127. package/src/wirings/workflow/dsl/index.ts +30 -0
  128. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +312 -0
  129. package/src/wirings/workflow/dsl/workflow-runner.ts +27 -0
  130. package/src/wirings/workflow/graph/graph-node.ts +227 -0
  131. package/src/wirings/workflow/graph/graph-runner.ts +694 -0
  132. package/src/wirings/workflow/graph/index.ts +3 -0
  133. package/src/wirings/workflow/graph/workflow-graph.types.ts +126 -0
  134. package/src/wirings/workflow/index.ts +53 -26
  135. package/src/wirings/workflow/pikku-workflow-service.ts +333 -117
  136. package/src/wirings/workflow/wire-workflow.ts +94 -0
  137. package/src/wirings/workflow/workflow-utils.ts +120 -0
  138. package/src/wirings/workflow/workflow.types.ts +166 -277
  139. package/tsconfig.tsbuildinfo +1 -1
  140. package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
  141. package/dist/wirings/workflow/workflow-runner.js +0 -65
  142. package/src/function/function-runner.ts.bak +0 -188
  143. package/src/wirings/workflow/workflow-runner.ts +0 -72
@@ -1,5 +1,35 @@
1
1
  import { CoreConfig, CoreSingletonServices, CreateWireServices, SerializedError } from '../../types/core.types.js';
2
- import type { StepState, WorkflowRun, WorkflowStatus, WorkflowService, WorkflowStepOptions } from './workflow.types.js';
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
- executeWorkflowSleep(runId: string, stepId: string): Promise<void>;
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
- startWorkflow<I>(name: string, input: I, rpcService: any): Promise<{
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 { WorkflowAsyncException, WorkflowCancelledException, WorkflowNotFoundError, WorkflowRunNotFound, } from './workflow-runner.js';
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 ?? 'pikkuWorkflowStepSleeper',
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 executeWorkflowSleep(runId, stepId) {
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
- const registrations = pikkuState('workflows', 'registrations');
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
- // If queue service is available, use remote execution (queue-based)
74
- // Otherwise, execute directly (inline/synchronous)
75
- if (this.singletonServices?.queueService) {
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
- else {
80
- // No queue service - execute directly via runWorkflowJob
81
- await this.runWorkflowJob(runId, rpcService);
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.workflowName, workflowMeta.pikkuFuncName, {
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 already updated, just rethrow
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
- // Execute RPC with workflow step context
156
- const result = await rpcService.rpcWithWire(rpcName, data, {
157
- workflowStep: {
158
- runId,
159
- stepId: stepState.stepId,
160
- attemptCount: stepState.attemptCount,
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
- try {
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 (error) {
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
- // No queue service - execute locally
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
- const attemptCount = stepState.attemptCount;
271
- try {
272
- const result = await rpcService.rpcWithWire(rpcName, data, {
273
- workflowStep: {
274
- runId,
275
- stepId: stepState.stepId,
276
- attemptCount: stepState.attemptCount,
277
- },
278
- });
279
- await this.setStepResult(stepState.stepId, result);
280
- return result;
281
- }
282
- catch (error) {
283
- // Record the error (marks step as failed)
284
- await this.setStepError(stepState.stepId, error);
285
- // Check if we should retry
286
- if (attemptCount < retries) {
287
- // Create a new pending retry attempt (copies metadata from failed step)
288
- await this.createRetryAttempt(stepState.stepId, 'pending');
289
- // Schedule orchestrator to retry after delay
290
- await this.scheduleOrchestratorRetry(runId, retryDelay);
291
- // Pause workflow - orchestrator will replay and pick up new attempt
292
- throw new WorkflowAsyncException(runId, stepName);
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 (error) {
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
- const attemptCount = stepState.attemptCount;
321
- try {
322
- const result = await fn();
323
- await this.setStepResult(stepState.stepId, result);
324
- return result;
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
- catch (error) {
327
- // Record the error (marks step as failed)
328
- await this.setStepError(stepState.stepId, error);
329
- // Check if we should retry
330
- if (attemptCount < retries) {
331
- // Create a new pending retry attempt (copies metadata from failed step)
332
- await this.createRetryAttempt(stepState.stepId, 'pending');
333
- // Schedule orchestrator to retry after delay
334
- await this.scheduleOrchestratorRetry(runId, retryDelay);
335
- // Pause workflow - orchestrator will replay and pick up new attempt
336
- throw new WorkflowAsyncException(runId, stepName);
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 (error) {
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 we have queue service (remote mode) or inline mode
366
- if (this.singletonServices.schedulerService) {
367
- // Remote mode - enqueue sleep worker with delay
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;