@pikku/core 0.12.2 → 0.12.4

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 (282) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/crypto-utils.d.ts +10 -0
  3. package/dist/crypto-utils.js +62 -0
  4. package/dist/function/function-runner.js +4 -4
  5. package/dist/function/functions.types.d.ts +26 -1
  6. package/dist/function/functions.types.js +16 -0
  7. package/dist/handle-error.d.ts +1 -1
  8. package/dist/handle-error.js +7 -3
  9. package/dist/index.d.ts +14 -2
  10. package/dist/index.js +1 -1
  11. package/dist/pikku-state.js +3 -1
  12. package/dist/schema.js +1 -1
  13. package/dist/services/in-memory-workflow-service.d.ts +22 -3
  14. package/dist/services/in-memory-workflow-service.js +77 -1
  15. package/dist/services/workflow-service.d.ts +8 -2
  16. package/dist/testing/index.d.ts +2 -0
  17. package/dist/testing/index.js +1 -0
  18. package/dist/testing/service-tests.d.ts +31 -0
  19. package/dist/testing/service-tests.js +598 -0
  20. package/dist/types/core.types.d.ts +9 -1
  21. package/dist/types/state.types.d.ts +4 -2
  22. package/dist/utils/hash.d.ts +2 -0
  23. package/dist/utils/hash.js +23 -0
  24. package/dist/wirings/ai-agent/agent-dynamic-workflow.d.ts +6 -0
  25. package/dist/wirings/ai-agent/agent-dynamic-workflow.js +348 -0
  26. package/dist/wirings/ai-agent/ai-agent-memory.d.ts +1 -1
  27. package/dist/wirings/ai-agent/ai-agent-memory.js +6 -5
  28. package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +10 -7
  29. package/dist/wirings/ai-agent/ai-agent-prepare.js +127 -47
  30. package/dist/wirings/ai-agent/ai-agent-runner.d.ts +4 -0
  31. package/dist/wirings/ai-agent/ai-agent-runner.js +381 -27
  32. package/dist/wirings/ai-agent/ai-agent-stream.d.ts +7 -2
  33. package/dist/wirings/ai-agent/ai-agent-stream.js +109 -46
  34. package/dist/wirings/ai-agent/ai-agent.types.d.ts +22 -1
  35. package/dist/wirings/ai-agent/index.d.ts +1 -2
  36. package/dist/wirings/ai-agent/index.js +1 -2
  37. package/dist/wirings/channel/channel-handler.js +5 -1
  38. package/dist/wirings/channel/channel.types.d.ts +1 -7
  39. package/dist/wirings/channel/local/local-channel-handler.d.ts +1 -1
  40. package/dist/wirings/channel/local/local-channel-handler.js +5 -3
  41. package/dist/wirings/channel/local/local-channel-runner.js +14 -4
  42. package/dist/wirings/channel/serverless/serverless-channel-runner.js +4 -1
  43. package/dist/wirings/cli/cli-runner.js +10 -2
  44. package/dist/wirings/cli/cli.types.d.ts +1 -0
  45. package/dist/wirings/http/http-routes.js +0 -1
  46. package/dist/wirings/http/http-runner.d.ts +1 -1
  47. package/dist/wirings/http/http-runner.js +25 -3
  48. package/dist/wirings/http/http.types.d.ts +3 -11
  49. package/dist/wirings/http/index.d.ts +2 -1
  50. package/dist/wirings/http/index.js +1 -0
  51. package/dist/wirings/http/pikku-fetch-http-request.d.ts +1 -0
  52. package/dist/wirings/http/pikku-fetch-http-request.js +3 -0
  53. package/dist/wirings/http/pikku-fetch-http-response.d.ts +1 -0
  54. package/dist/wirings/http/pikku-fetch-http-response.js +4 -4
  55. package/dist/wirings/http/web-request.d.ts +12 -0
  56. package/dist/wirings/http/web-request.js +85 -0
  57. package/dist/wirings/mcp/mcp-endpoint-registry.js +1 -1
  58. package/dist/wirings/mcp/mcp-runner.js +13 -1
  59. package/dist/wirings/rpc/rpc-runner.d.ts +30 -6
  60. package/dist/wirings/rpc/rpc-runner.js +36 -14
  61. package/dist/wirings/rpc/rpc-types.d.ts +6 -1
  62. package/dist/wirings/rpc/wire-addon.d.ts +1 -0
  63. package/dist/wirings/rpc/wire-addon.js +1 -1
  64. package/dist/wirings/scheduler/scheduler-runner.js +1 -0
  65. package/dist/wirings/workflow/graph/graph-validation.d.ts +3 -0
  66. package/dist/wirings/workflow/graph/graph-validation.js +175 -0
  67. package/dist/wirings/workflow/graph/index.d.ts +1 -0
  68. package/dist/wirings/workflow/graph/index.js +1 -0
  69. package/dist/wirings/workflow/index.d.ts +1 -1
  70. package/dist/wirings/workflow/pikku-workflow-service.d.ts +8 -2
  71. package/dist/wirings/workflow/pikku-workflow-service.js +1 -2
  72. package/dist/wirings/workflow/workflow.types.d.ts +10 -1
  73. package/lcov.info +18891 -0
  74. package/package.json +4 -2
  75. package/dist/wirings/ai-agent/ai-agent-assistant-ui.d.ts +0 -5
  76. package/dist/wirings/ai-agent/ai-agent-assistant-ui.js +0 -168
  77. package/src/crypto-utils.test.ts +0 -116
  78. package/src/crypto-utils.ts +0 -99
  79. package/src/errors/error-handler.ts +0 -62
  80. package/src/errors/error.test.ts +0 -195
  81. package/src/errors/errors.ts +0 -374
  82. package/src/errors/index.ts +0 -2
  83. package/src/factory-functions.test.ts +0 -109
  84. package/src/function/function-runner.test.ts +0 -536
  85. package/src/function/function-runner.ts +0 -365
  86. package/src/function/functions.types.ts +0 -267
  87. package/src/function/index.ts +0 -5
  88. package/src/handle-error.test.ts +0 -361
  89. package/src/handle-error.ts +0 -65
  90. package/src/index.ts +0 -104
  91. package/src/internal.ts +0 -6
  92. package/src/middleware/auth-apikey.test.ts +0 -363
  93. package/src/middleware/auth-apikey.ts +0 -47
  94. package/src/middleware/auth-bearer.test.ts +0 -450
  95. package/src/middleware/auth-bearer.ts +0 -72
  96. package/src/middleware/auth-cookie.test.ts +0 -528
  97. package/src/middleware/auth-cookie.ts +0 -80
  98. package/src/middleware/cors.test.ts +0 -424
  99. package/src/middleware/cors.ts +0 -104
  100. package/src/middleware/index.ts +0 -5
  101. package/src/middleware/remote-auth.test.ts +0 -488
  102. package/src/middleware/remote-auth.ts +0 -68
  103. package/src/middleware/timeout.ts +0 -15
  104. package/src/middleware-runner.test.ts +0 -418
  105. package/src/middleware-runner.ts +0 -234
  106. package/src/permissions.test.ts +0 -434
  107. package/src/permissions.ts +0 -319
  108. package/src/pikku-request.ts +0 -23
  109. package/src/pikku-response.ts +0 -5
  110. package/src/pikku-state.test.ts +0 -224
  111. package/src/pikku-state.ts +0 -214
  112. package/src/run-tests-script.test.ts +0 -49
  113. package/src/schema.test.ts +0 -249
  114. package/src/schema.ts +0 -151
  115. package/src/services/ai-agent-runner-service.ts +0 -41
  116. package/src/services/ai-run-state-service.ts +0 -20
  117. package/src/services/ai-storage-service.ts +0 -39
  118. package/src/services/content-service.ts +0 -76
  119. package/src/services/deployment-service.ts +0 -22
  120. package/src/services/gateway-service.ts +0 -20
  121. package/src/services/gopass-secrets.ts +0 -98
  122. package/src/services/in-memory-ai-run-state-service.ts +0 -73
  123. package/src/services/in-memory-trigger-service.ts +0 -64
  124. package/src/services/in-memory-workflow-service.test.ts +0 -351
  125. package/src/services/in-memory-workflow-service.ts +0 -395
  126. package/src/services/index.ts +0 -56
  127. package/src/services/jwt-service.ts +0 -30
  128. package/src/services/local-content.ts +0 -120
  129. package/src/services/local-gateway-service.ts +0 -62
  130. package/src/services/local-secrets.test.ts +0 -80
  131. package/src/services/local-secrets.ts +0 -94
  132. package/src/services/local-variables.test.ts +0 -61
  133. package/src/services/local-variables.ts +0 -39
  134. package/src/services/logger-console.test.ts +0 -118
  135. package/src/services/logger-console.ts +0 -98
  136. package/src/services/logger.ts +0 -57
  137. package/src/services/scheduler-service.ts +0 -86
  138. package/src/services/schema-service.ts +0 -33
  139. package/src/services/scoped-secret-service.test.ts +0 -74
  140. package/src/services/scoped-secret-service.ts +0 -41
  141. package/src/services/secret-service.ts +0 -38
  142. package/src/services/trigger-service.ts +0 -17
  143. package/src/services/typed-secret-service.test.ts +0 -93
  144. package/src/services/typed-secret-service.ts +0 -70
  145. package/src/services/typed-variables-service.test.ts +0 -73
  146. package/src/services/typed-variables-service.ts +0 -81
  147. package/src/services/user-session-service.test.ts +0 -113
  148. package/src/services/user-session-service.ts +0 -86
  149. package/src/services/variables-service.ts +0 -11
  150. package/src/services/workflow-service.ts +0 -84
  151. package/src/time-utils.test.ts +0 -56
  152. package/src/time-utils.ts +0 -107
  153. package/src/types/core.types.ts +0 -470
  154. package/src/types/state.types.ts +0 -186
  155. package/src/utils.test.ts +0 -350
  156. package/src/utils.ts +0 -129
  157. package/src/version.test.ts +0 -80
  158. package/src/version.ts +0 -25
  159. package/src/wirings/ai-agent/ai-agent-assistant-ui.test.ts +0 -363
  160. package/src/wirings/ai-agent/ai-agent-assistant-ui.ts +0 -238
  161. package/src/wirings/ai-agent/ai-agent-helpers.test.ts +0 -152
  162. package/src/wirings/ai-agent/ai-agent-helpers.ts +0 -76
  163. package/src/wirings/ai-agent/ai-agent-memory.ts +0 -309
  164. package/src/wirings/ai-agent/ai-agent-model-config.test.ts +0 -115
  165. package/src/wirings/ai-agent/ai-agent-model-config.ts +0 -43
  166. package/src/wirings/ai-agent/ai-agent-prepare.ts +0 -522
  167. package/src/wirings/ai-agent/ai-agent-registry.test.ts +0 -37
  168. package/src/wirings/ai-agent/ai-agent-registry.ts +0 -82
  169. package/src/wirings/ai-agent/ai-agent-runner.test.ts +0 -317
  170. package/src/wirings/ai-agent/ai-agent-runner.ts +0 -251
  171. package/src/wirings/ai-agent/ai-agent-stream.test.ts +0 -652
  172. package/src/wirings/ai-agent/ai-agent-stream.ts +0 -1060
  173. package/src/wirings/ai-agent/ai-agent.types.ts +0 -360
  174. package/src/wirings/ai-agent/index.ts +0 -41
  175. package/src/wirings/channel/channel-common.ts +0 -90
  176. package/src/wirings/channel/channel-handler.ts +0 -241
  177. package/src/wirings/channel/channel-middleware-runner.ts +0 -120
  178. package/src/wirings/channel/channel-runner.ts +0 -166
  179. package/src/wirings/channel/channel-store.ts +0 -29
  180. package/src/wirings/channel/channel.types.ts +0 -178
  181. package/src/wirings/channel/define-channel-routes.ts +0 -25
  182. package/src/wirings/channel/eventhub-service.ts +0 -34
  183. package/src/wirings/channel/eventhub-store.ts +0 -13
  184. package/src/wirings/channel/index.ts +0 -24
  185. package/src/wirings/channel/local/index.ts +0 -3
  186. package/src/wirings/channel/local/local-channel-handler.ts +0 -79
  187. package/src/wirings/channel/local/local-channel-runner.test.ts +0 -215
  188. package/src/wirings/channel/local/local-channel-runner.ts +0 -200
  189. package/src/wirings/channel/local/local-eventhub-service.test.ts +0 -95
  190. package/src/wirings/channel/local/local-eventhub-service.ts +0 -101
  191. package/src/wirings/channel/log-channels.ts +0 -20
  192. package/src/wirings/channel/pikku-abstract-channel-handler.test.ts +0 -54
  193. package/src/wirings/channel/pikku-abstract-channel-handler.ts +0 -45
  194. package/src/wirings/channel/serverless/index.ts +0 -5
  195. package/src/wirings/channel/serverless/serverless-channel-runner.ts +0 -286
  196. package/src/wirings/cli/channel/cli-channel-runner.ts +0 -136
  197. package/src/wirings/cli/channel/index.ts +0 -1
  198. package/src/wirings/cli/cli-runner.test.ts +0 -403
  199. package/src/wirings/cli/cli-runner.ts +0 -519
  200. package/src/wirings/cli/cli.types.ts +0 -357
  201. package/src/wirings/cli/command-parser.test.ts +0 -445
  202. package/src/wirings/cli/command-parser.ts +0 -504
  203. package/src/wirings/cli/define-cli-commands.ts +0 -24
  204. package/src/wirings/cli/index.ts +0 -19
  205. package/src/wirings/gateway/gateway-runner.test.ts +0 -474
  206. package/src/wirings/gateway/gateway-runner.ts +0 -411
  207. package/src/wirings/gateway/gateway.types.ts +0 -149
  208. package/src/wirings/gateway/index.ts +0 -13
  209. package/src/wirings/http/http-routes.test.ts +0 -322
  210. package/src/wirings/http/http-routes.ts +0 -198
  211. package/src/wirings/http/http-runner.test.ts +0 -144
  212. package/src/wirings/http/http-runner.ts +0 -563
  213. package/src/wirings/http/http.types.ts +0 -378
  214. package/src/wirings/http/index.ts +0 -25
  215. package/src/wirings/http/log-http-routes.ts +0 -22
  216. package/src/wirings/http/pikku-fetch-http-request.test.ts +0 -237
  217. package/src/wirings/http/pikku-fetch-http-request.ts +0 -166
  218. package/src/wirings/http/pikku-fetch-http-response.test.ts +0 -82
  219. package/src/wirings/http/pikku-fetch-http-response.ts +0 -147
  220. package/src/wirings/http/routers/http-router.ts +0 -13
  221. package/src/wirings/http/routers/path-to-regex.test.ts +0 -319
  222. package/src/wirings/http/routers/path-to-regex.ts +0 -126
  223. package/src/wirings/mcp/index.ts +0 -27
  224. package/src/wirings/mcp/mcp-endpoint-registry.test.ts +0 -389
  225. package/src/wirings/mcp/mcp-endpoint-registry.ts +0 -159
  226. package/src/wirings/mcp/mcp-runner.ts +0 -305
  227. package/src/wirings/mcp/mcp.types.ts +0 -216
  228. package/src/wirings/node/index.ts +0 -2
  229. package/src/wirings/node/node.types.ts +0 -23
  230. package/src/wirings/oauth2/index.ts +0 -3
  231. package/src/wirings/oauth2/oauth2-client.test.ts +0 -929
  232. package/src/wirings/oauth2/oauth2-client.ts +0 -335
  233. package/src/wirings/oauth2/oauth2.types.ts +0 -69
  234. package/src/wirings/oauth2/wire-oauth2-credential.ts +0 -23
  235. package/src/wirings/queue/index.ts +0 -31
  236. package/src/wirings/queue/queue-runner.test.ts +0 -710
  237. package/src/wirings/queue/queue-runner.ts +0 -192
  238. package/src/wirings/queue/queue.types.ts +0 -189
  239. package/src/wirings/queue/register-queue-helper.ts +0 -60
  240. package/src/wirings/queue/validate-worker-config.test.ts +0 -108
  241. package/src/wirings/queue/validate-worker-config.ts +0 -116
  242. package/src/wirings/rpc/index.ts +0 -4
  243. package/src/wirings/rpc/rpc-runner.ts +0 -439
  244. package/src/wirings/rpc/rpc-types.ts +0 -51
  245. package/src/wirings/rpc/wire-addon.ts +0 -20
  246. package/src/wirings/scheduler/index.ts +0 -11
  247. package/src/wirings/scheduler/log-schedulers.ts +0 -20
  248. package/src/wirings/scheduler/scheduler-runner.test.ts +0 -660
  249. package/src/wirings/scheduler/scheduler-runner.ts +0 -132
  250. package/src/wirings/scheduler/scheduler.types.ts +0 -53
  251. package/src/wirings/secret/index.ts +0 -9
  252. package/src/wirings/secret/secret.types.ts +0 -32
  253. package/src/wirings/secret/validate-secret-definitions.test.ts +0 -140
  254. package/src/wirings/secret/validate-secret-definitions.ts +0 -82
  255. package/src/wirings/trigger/index.ts +0 -10
  256. package/src/wirings/trigger/pikku-trigger-service.ts +0 -112
  257. package/src/wirings/trigger/trigger-runner.test.ts +0 -79
  258. package/src/wirings/trigger/trigger-runner.ts +0 -135
  259. package/src/wirings/trigger/trigger.types.ts +0 -178
  260. package/src/wirings/variable/index.ts +0 -8
  261. package/src/wirings/variable/validate-variable-definitions.test.ts +0 -91
  262. package/src/wirings/variable/validate-variable-definitions.ts +0 -69
  263. package/src/wirings/variable/variable.types.ts +0 -22
  264. package/src/wirings/workflow/dsl/index.ts +0 -31
  265. package/src/wirings/workflow/dsl/workflow-dsl.types.ts +0 -320
  266. package/src/wirings/workflow/dsl/workflow-runner.ts +0 -28
  267. package/src/wirings/workflow/graph/graph-node.ts +0 -222
  268. package/src/wirings/workflow/graph/graph-runner.test.ts +0 -487
  269. package/src/wirings/workflow/graph/graph-runner.ts +0 -816
  270. package/src/wirings/workflow/graph/index.ts +0 -14
  271. package/src/wirings/workflow/graph/template.test.ts +0 -49
  272. package/src/wirings/workflow/graph/template.ts +0 -42
  273. package/src/wirings/workflow/graph/wire-workflow-graph.ts +0 -29
  274. package/src/wirings/workflow/graph/workflow-graph.types.ts +0 -104
  275. package/src/wirings/workflow/index.ts +0 -81
  276. package/src/wirings/workflow/pikku-workflow-service.test.ts +0 -200
  277. package/src/wirings/workflow/pikku-workflow-service.ts +0 -1165
  278. package/src/wirings/workflow/workflow-helpers.test.ts +0 -129
  279. package/src/wirings/workflow/workflow-helpers.ts +0 -79
  280. package/src/wirings/workflow/workflow.types.ts +0 -266
  281. package/tsconfig.json +0 -14
  282. package/tsconfig.tsbuildinfo +0 -1
@@ -1,1165 +0,0 @@
1
- import { runPikkuFunc } from '../../function/function-runner.js'
2
- import {
3
- getSingletonServices,
4
- getCreateWireServices,
5
- pikkuState,
6
- } from '../../pikku-state.js'
7
- import { getDurationInMilliseconds } from '../../time-utils.js'
8
- import type { PikkuWire, SerializedError } from '../../types/core.types.js'
9
- import type { QueueService } from '../queue/queue.types.js'
10
- import type {
11
- PikkuWorkflowWire,
12
- StepState,
13
- WorkflowRun,
14
- WorkflowRunWire,
15
- WorkflowStatus,
16
- WorkflowServiceConfig,
17
- WorkflowStepOptions,
18
- } from './workflow.types.js'
19
- import {
20
- continueGraph,
21
- executeGraphStep,
22
- runWorkflowGraph,
23
- runFromMeta,
24
- } from './graph/graph-runner.js'
25
- import type { WorkflowService } from '../../services/workflow-service.js'
26
- import { PikkuError, addError } from '../../errors/error-handler.js'
27
- import { RPCNotFoundError } from '../rpc/rpc-runner.js'
28
-
29
- /**
30
- * Exception thrown when workflow needs to pause for async step
31
- */
32
- export class WorkflowAsyncException extends Error {
33
- constructor(
34
- public readonly runId: string,
35
- public readonly stepName: string
36
- ) {
37
- super(`Workflow paused at step: ${stepName}`)
38
- this.name = 'WorkflowAsyncException'
39
- }
40
- }
41
-
42
- /**
43
- * Exception thrown when workflow is cancelled
44
- */
45
- export class WorkflowCancelledException extends Error {
46
- constructor(
47
- public readonly runId: string,
48
- public readonly reason?: string
49
- ) {
50
- super(reason || 'Workflow cancelled')
51
- this.name = 'WorkflowCancelledException'
52
- }
53
- }
54
-
55
- /**
56
- * Exception thrown when workflow is suspended
57
- */
58
- export class WorkflowSuspendedException extends Error {
59
- constructor(
60
- public readonly runId: string,
61
- public readonly reason: string
62
- ) {
63
- super(reason || 'Workflow suspended')
64
- this.name = 'WorkflowSuspendedException'
65
- }
66
- }
67
-
68
- /**
69
- * Error class for workflow not found
70
- */
71
- export class WorkflowNotFoundError extends PikkuError {
72
- constructor(name: string) {
73
- super(`Workflow not found: ${name}`)
74
- }
75
- }
76
- addError(WorkflowNotFoundError, {
77
- status: 404,
78
- message: 'Workflow not found.',
79
- })
80
-
81
- export class WorkflowRunNotFoundError extends PikkuError {
82
- constructor(runId: string) {
83
- super(`Workflow run not found: ${runId}`)
84
- }
85
- }
86
- addError(WorkflowRunNotFoundError, {
87
- status: 404,
88
- message: 'Workflow run not found.',
89
- })
90
-
91
- export class WorkflowServiceNotInitialized extends Error {}
92
- export class WorkflowStepNameNotString extends Error {
93
- constructor(stepName: any) {
94
- super(`Workflow step name must be a string. Received: ${typeof stepName}`)
95
- }
96
- }
97
-
98
- const WORKFLOW_END_STATES: ReadonlySet<string> = new Set([
99
- 'completed',
100
- 'failed',
101
- 'cancelled',
102
- 'suspended',
103
- ])
104
-
105
- /**
106
- * Abstract workflow state service
107
- * Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
108
- * Combines orchestration and step execution
109
- */
110
- export abstract class PikkuWorkflowService implements WorkflowService {
111
- private inlineRuns = new Set<string>()
112
-
113
- constructor() {}
114
-
115
- /**
116
- * Check if a run is executing inline (without queues)
117
- */
118
- protected isInline(runId: string): boolean {
119
- return this.inlineRuns.has(runId)
120
- }
121
-
122
- /**
123
- * Register a run as inline (for graph-runner to use)
124
- */
125
- public registerInlineRun(runId: string): void {
126
- this.inlineRuns.add(runId)
127
- }
128
-
129
- /**
130
- * Unregister a run from inline tracking
131
- */
132
- public unregisterInlineRun(runId: string): void {
133
- this.inlineRuns.delete(runId)
134
- }
135
-
136
- public async registerWorkflowVersions(): Promise<void> {
137
- const allMeta = pikkuState(null, 'workflows', 'meta')
138
- for (const [name, meta] of Object.entries(allMeta)) {
139
- if (!meta.graphHash) continue
140
- await this.upsertWorkflowVersion(name, meta.graphHash, meta, meta.source)
141
- }
142
- }
143
-
144
- abstract createRun(
145
- workflowName: string,
146
- input: any,
147
- inline: boolean,
148
- graphHash: string,
149
- wire: WorkflowRunWire
150
- ): Promise<string>
151
-
152
- /**
153
- * Get a workflow run by ID
154
- * @param id - Run ID
155
- * @returns Workflow run or null if not found
156
- */
157
- abstract getRun(id: string): Promise<WorkflowRun | null>
158
-
159
- /**
160
- * Get workflow run history (all step attempts in chronological order)
161
- * @param runId - Run ID
162
- * @returns Array of step states with step names, ordered oldest to newest
163
- */
164
- abstract getRunHistory(
165
- runId: string
166
- ): Promise<Array<StepState & { stepName: string }>>
167
-
168
- /**
169
- * Update workflow run status
170
- * @param id - Run ID
171
- * @param status - New status
172
- */
173
- abstract updateRunStatus(
174
- id: string,
175
- status: WorkflowStatus,
176
- output?: any,
177
- error?: SerializedError
178
- ): Promise<void>
179
-
180
- /**
181
- * Insert initial step state (called by orchestrator)
182
- * Creates pending step in both workflow_step and workflow_step_history
183
- * @param runId - Run ID
184
- * @param stepName - Step cache key
185
- * @param rpcName - RPC function name
186
- * @param data - Step input data
187
- * @param stepOptions - Step options (retries, retryDelay)
188
- * @returns Step state with generated stepId
189
- */
190
- abstract insertStepState(
191
- runId: string,
192
- stepName: string,
193
- rpcName: string | null,
194
- data: any,
195
- stepOptions?: WorkflowStepOptions
196
- ): Promise<StepState>
197
-
198
- /**
199
- * Get step state by cache key (read-only)
200
- * @param runId - Run ID
201
- * @param stepName - Step cache key (from workflow.do)
202
- * @returns Step state with attemptCount calculated from history
203
- */
204
- abstract getStepState(runId: string, stepName: string): Promise<StepState>
205
-
206
- /**
207
- * Mark step as running
208
- * Updates both workflow_step and workflow_step_history
209
- * @param stepId - Step ID
210
- */
211
- abstract setStepRunning(stepId: string): Promise<void>
212
-
213
- /**
214
- * Mark step as scheduled (queued for execution)
215
- * Updates both workflow_step and workflow_step_history
216
- * @param stepId - Step ID
217
- */
218
- abstract setStepScheduled(stepId: string): Promise<void>
219
-
220
- /**
221
- * Store step result and mark as succeeded
222
- * Updates both workflow_step and workflow_step_history
223
- * @param stepId - Step ID
224
- * @param result - Step result
225
- */
226
- abstract setStepResult(stepId: string, result: any): Promise<void>
227
-
228
- /**
229
- * Store step error and mark as failed
230
- * Updates both workflow_step and workflow_step_history
231
- * @param stepId - Step ID
232
- * @param error - Error object
233
- */
234
- abstract setStepError(stepId: string, error: Error): Promise<void>
235
-
236
- /**
237
- * Create a new retry attempt for a failed step
238
- * Inserts new pending step in both workflow_step and workflow_step_history
239
- * Resets status to 'pending' with new stepId
240
- * Copies metadata (rpcName, data, retries, retryDelay) from failed attempt
241
- * @param failedStepId - Failed step ID to copy from
242
- * @returns New step state for the retry attempt
243
- */
244
- abstract createRetryAttempt(
245
- failedStepId: string,
246
- status: 'pending' | 'running'
247
- ): Promise<StepState>
248
-
249
- /**
250
- * Execute function within a run lock to prevent concurrent modifications
251
- * @param id - Run ID
252
- * @param fn - Function to execute
253
- * @returns Function result
254
- */
255
- abstract withRunLock<T>(id: string, fn: () => Promise<T>): Promise<T>
256
-
257
- /**
258
- * Execute function within a step lock to prevent concurrent step execution
259
- * @param runId - Run ID
260
- * @param stepName - Step name
261
- * @param fn - Function to execute
262
- * @returns Function result
263
- */
264
- abstract withStepLock<T>(
265
- runId: string,
266
- stepName: string,
267
- fn: () => Promise<T>
268
- ): Promise<T>
269
-
270
- /**
271
- * Close any open connections
272
- */
273
- abstract close(): Promise<void>
274
-
275
- // ============================================================================
276
- // Workflow Graph Methods
277
- // ============================================================================
278
-
279
- /**
280
- * Get completed graph state (lightweight - no results)
281
- * @param runId - Run ID
282
- * @returns Completed node IDs and their branch keys
283
- */
284
- abstract getCompletedGraphState(runId: string): Promise<{
285
- completedNodeIds: string[]
286
- failedNodeIds: string[]
287
- branchKeys: Record<string, string>
288
- }>
289
-
290
- /**
291
- * Filter candidate nodes to only those without existing steps
292
- * @param runId - Run ID
293
- * @param nodeIds - Candidate node IDs to check
294
- * @returns Node IDs that don't have a step yet
295
- */
296
- abstract getNodesWithoutSteps(
297
- runId: string,
298
- nodeIds: string[]
299
- ): Promise<string[]>
300
-
301
- /**
302
- * Get results for specific nodes
303
- * @param runId - Run ID
304
- * @param nodeIds - Node IDs to fetch results for
305
- * @returns Map of nodeId to result
306
- */
307
- abstract getNodeResults(
308
- runId: string,
309
- nodeIds: string[]
310
- ): Promise<Record<string, any>>
311
-
312
- /**
313
- * Set the branch key for a graph node step
314
- * @param stepId - Step ID
315
- * @param branchKey - Branch key selected by graph.branch()
316
- */
317
- abstract setBranchTaken(stepId: string, branchKey: string): Promise<void>
318
-
319
- /**
320
- * Update a state variable in the workflow run's state
321
- * @param runId - Run ID
322
- * @param name - Variable name
323
- * @param value - Value to store
324
- */
325
- abstract updateRunState(
326
- runId: string,
327
- name: string,
328
- value: unknown
329
- ): Promise<void>
330
-
331
- /**
332
- * Get the entire state object for a workflow run
333
- * @param runId - Run ID
334
- * @returns The state object with all variables
335
- */
336
- abstract getRunState(runId: string): Promise<Record<string, unknown>>
337
-
338
- abstract upsertWorkflowVersion(
339
- name: string,
340
- graphHash: string,
341
- graph: any,
342
- source: string
343
- ): Promise<void>
344
-
345
- abstract getWorkflowVersion(
346
- name: string,
347
- graphHash: string
348
- ): Promise<{ graph: any; source: string } | null>
349
-
350
- // ============================================================================
351
- // Workflow Lifecycle Methods
352
- // ============================================================================
353
-
354
- /**
355
- * Resume a paused workflow by triggering the orchestrator
356
- * @param runId - Run ID
357
- */
358
- public async resumeWorkflow(runId: string): Promise<void> {
359
- const queueService = this.verifyQueueService()
360
- await queueService.add(this.getConfig().orchestratorQueueName, { runId })
361
- }
362
-
363
- public async queueStepWorker(
364
- runId: string,
365
- stepName: string,
366
- rpcName: string,
367
- data: any
368
- ): Promise<void> {
369
- const queueService = this.verifyQueueService()
370
- await queueService.add(this.getConfig().stepWorkerQueueName, {
371
- runId,
372
- stepName,
373
- rpcName,
374
- data,
375
- })
376
- }
377
-
378
- /**
379
- * Execute a workflow sleep step completion
380
- * Sets the step result to null and resumes the workflow
381
- * @param data - Sleeper input data
382
- */
383
- public async executeWorkflowSleepCompleted(
384
- runId: string,
385
- stepId: string
386
- ): Promise<void> {
387
- await this.setStepResult(stepId, null)
388
- await this.resumeWorkflow(runId)
389
- }
390
-
391
- /**
392
- * Schedule orchestrator retry with delay
393
- * @param runId - Run ID
394
- * @param retryDelay - Delay in milliseconds or duration string (optional)
395
- */
396
- protected async scheduleOrchestratorRetry(
397
- runId: string,
398
- retryDelay?: number | string
399
- ): Promise<void> {
400
- const queueService = this.verifyQueueService()
401
- await queueService.add(
402
- this.getConfig().orchestratorQueueName,
403
- { runId },
404
- retryDelay ? { delay: getDurationInMilliseconds(retryDelay) } : undefined
405
- )
406
- }
407
-
408
- /**
409
- * Start a new workflow run
410
- * Automatically detects workflow type (DSL or graph) from meta and executes accordingly
411
- * @param options.inline - If true, execute workflow directly without queue service
412
- * @param options.startNode - Starting node ID for graph workflows (from wire config)
413
- */
414
- public async startWorkflow<I>(
415
- name: string,
416
- input: I,
417
- wire: WorkflowRunWire,
418
- rpcService: any,
419
- options?: { inline?: boolean; startNode?: string }
420
- ): Promise<{ runId: string }> {
421
- // Check meta to determine workflow type
422
- const meta = pikkuState(null, 'workflows', 'meta')
423
- const workflowMeta = meta[name]
424
-
425
- if (!workflowMeta) {
426
- throw new WorkflowNotFoundError(name)
427
- }
428
-
429
- // Check if this is a graph workflow (source === 'graph')
430
- if (workflowMeta.source === 'graph') {
431
- const shouldInline =
432
- options?.inline || !getSingletonServices()?.queueService
433
- return runWorkflowGraph(
434
- this,
435
- name,
436
- input,
437
- rpcService,
438
- shouldInline,
439
- options?.startNode,
440
- wire
441
- )
442
- }
443
-
444
- // DSL workflow - check registration exists
445
- const registrations = pikkuState(null, 'workflows', 'registrations')
446
- const workflow = registrations.get(name)
447
-
448
- if (!workflow) {
449
- throw new WorkflowNotFoundError(name)
450
- }
451
-
452
- if (!workflowMeta.graphHash) {
453
- throw new Error(`Missing workflow graphHash for '${name}'`)
454
- }
455
-
456
- const runId = await this.createRun(
457
- name,
458
- input,
459
- options?.inline ?? false,
460
- workflowMeta.graphHash,
461
- wire
462
- )
463
-
464
- if (options?.inline) {
465
- this.inlineRuns.add(runId)
466
- }
467
-
468
- if (options?.inline || !getSingletonServices()?.queueService) {
469
- this.runWorkflowJob(runId, rpcService)
470
- .catch(() => {})
471
- .finally(() => {
472
- if (options?.inline) {
473
- this.inlineRuns.delete(runId)
474
- }
475
- })
476
- } else {
477
- await this.resumeWorkflow(runId)
478
- }
479
-
480
- return { runId }
481
- }
482
-
483
- public async runToCompletion<I>(
484
- name: string,
485
- input: I,
486
- rpcService: any,
487
- options?: { pollIntervalMs?: number; wire?: WorkflowRunWire }
488
- ): Promise<any> {
489
- const pollInterval = options?.pollIntervalMs ?? 1000
490
- const { runId } = await this.startWorkflow(
491
- name,
492
- input,
493
- options?.wire ?? { type: 'internal' },
494
- rpcService,
495
- { inline: true }
496
- )
497
- while (true) {
498
- const run = await this.getRun(runId)
499
- if (!run) {
500
- throw new WorkflowRunNotFoundError(runId)
501
- }
502
- if (WORKFLOW_END_STATES.has(run.status)) {
503
- if (run.status === 'failed') {
504
- throw new Error(run.error?.message || 'Workflow failed')
505
- }
506
- if (run.status === 'cancelled') {
507
- throw new Error('Workflow was cancelled')
508
- }
509
- return run.output
510
- }
511
- await new Promise((resolve) => setTimeout(resolve, pollInterval))
512
- }
513
- }
514
-
515
- public async runWorkflowJob(runId: string, rpcService: any): Promise<void> {
516
- const run = await this.getRun(runId)
517
- if (!run) {
518
- throw new WorkflowRunNotFoundError(runId)
519
- }
520
-
521
- const meta = pikkuState(null, 'workflows', 'meta')
522
- const workflowMeta = meta[run.workflow]
523
-
524
- if (
525
- run.graphHash &&
526
- workflowMeta?.graphHash &&
527
- run.graphHash !== workflowMeta.graphHash
528
- ) {
529
- await this.runVersionMismatchFallback(run, workflowMeta, rpcService)
530
- return
531
- }
532
-
533
- if (workflowMeta?.source === 'graph') {
534
- await continueGraph(this, runId, run.workflow)
535
- return
536
- }
537
-
538
- const registrations = pikkuState(null, 'workflows', 'registrations')
539
- const workflow = registrations.get(run.workflow)
540
- if (!workflow) {
541
- throw new WorkflowNotFoundError(run.workflow)
542
- }
543
-
544
- await this.withRunLock(runId, async () => {
545
- const workflowWire = this.createWorkflowWire(
546
- run.workflow,
547
- runId,
548
- rpcService
549
- )
550
- const wire: PikkuWire = { workflow: workflowWire }
551
- try {
552
- const result = await runPikkuFunc(
553
- 'workflow',
554
- workflowMeta.name,
555
- workflowMeta.pikkuFuncId,
556
- {
557
- singletonServices: getSingletonServices()!,
558
- wire,
559
- createWireServices: getCreateWireServices(),
560
- data: () => run.input,
561
- }
562
- )
563
-
564
- await this.updateRunStatus(runId, 'completed', result)
565
- } catch (error: any) {
566
- if (error instanceof WorkflowAsyncException) {
567
- throw error
568
- }
569
-
570
- if (error instanceof WorkflowCancelledException) {
571
- await this.updateRunStatus(runId, 'cancelled', undefined, {
572
- message: error.message || 'Workflow cancelled',
573
- stack: '',
574
- code: 'WORKFLOW_CANCELLED',
575
- })
576
- throw error
577
- }
578
-
579
- if (error instanceof WorkflowSuspendedException) {
580
- await this.updateRunStatus(runId, 'suspended', undefined, {
581
- message: error.message || 'Workflow suspended',
582
- stack: '',
583
- code: 'WORKFLOW_SUSPENDED',
584
- })
585
- throw error
586
- }
587
-
588
- await this.updateRunStatus(runId, 'failed', undefined, {
589
- message: error.message,
590
- stack: error.stack,
591
- code: error.code,
592
- })
593
-
594
- throw error
595
- }
596
- })
597
- }
598
-
599
- private async runVersionMismatchFallback(
600
- run: WorkflowRun,
601
- currentMeta: { source: string },
602
- rpcService: any
603
- ): Promise<void> {
604
- const source = currentMeta.source
605
-
606
- if (source === 'complex') {
607
- await this.updateRunStatus(run.id, 'failed', undefined, {
608
- message: `Workflow '${run.workflow}' definition changed. Complex workflows with inline steps cannot be migrated.`,
609
- stack: '',
610
- code: 'VERSION_CONFLICT',
611
- })
612
- return
613
- }
614
-
615
- const version = await this.getWorkflowVersion(run.workflow, run.graphHash!)
616
- if (!version) {
617
- await this.updateRunStatus(run.id, 'failed', undefined, {
618
- message: `Workflow '${run.workflow}' version '${run.graphHash}' not found. Cannot resume with changed definition.`,
619
- stack: '',
620
- code: 'VERSION_NOT_FOUND',
621
- })
622
- return
623
- }
624
-
625
- await runFromMeta(this, run.id, version.graph, rpcService)
626
- }
627
-
628
- /**
629
- * Execute a single workflow step (called by worker)
630
- * Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
631
- */
632
- public async executeWorkflowStep(
633
- runId: string,
634
- stepName: string,
635
- rpcName: string,
636
- data: any,
637
- rpcService: any
638
- ): Promise<void> {
639
- // Use step-level lock to prevent concurrent execution of same step
640
- await this.withStepLock(runId, stepName, async () => {
641
- // Get step state
642
- let stepState = await this.getStepState(runId, stepName)
643
-
644
- // Idempotency - if already succeeded, resume orchestrator and return
645
- if (stepState.status === 'succeeded') {
646
- await this.resumeWorkflow(runId)
647
- return
648
- }
649
-
650
- // Log warning if already running (race condition)
651
- if (stepState.status === 'running') {
652
- return
653
- }
654
-
655
- // If status is 'failed', this is a retry - create new attempt history
656
- if (stepState.status === 'failed') {
657
- stepState = await this.createRetryAttempt(stepState.stepId, 'running')
658
- }
659
-
660
- if (stepState.status === 'pending') {
661
- // Mark pending step as running before execution
662
- await this.setStepRunning(stepState.stepId)
663
- }
664
-
665
- try {
666
- let result: any
667
-
668
- const run = await this.getRun(runId)
669
- if (!run) {
670
- throw new Error(`Workflow run not found: ${runId}`)
671
- }
672
-
673
- const meta = pikkuState(null, 'workflows', 'meta')
674
- const workflowMeta = meta[run.workflow]
675
-
676
- if (workflowMeta?.nodes && stepName in workflowMeta.nodes) {
677
- result = await executeGraphStep(
678
- this,
679
- rpcService,
680
- runId,
681
- stepState.stepId,
682
- stepName,
683
- rpcName,
684
- data,
685
- run.workflow
686
- )
687
- } else {
688
- result = await rpcService.rpcWithWire(rpcName, data, {
689
- workflowStep: {
690
- runId,
691
- stepId: stepState.stepId,
692
- attemptCount: stepState.attemptCount,
693
- },
694
- })
695
- }
696
-
697
- // Store result and mark succeeded
698
- await this.setStepResult(stepState.stepId, result)
699
-
700
- // Resume orchestrator to continue workflow
701
- await this.resumeWorkflow(runId)
702
- } catch (error: any) {
703
- if (error instanceof RPCNotFoundError) {
704
- await this.setStepError(stepState.stepId, error)
705
- await this.updateRunStatus(runId, 'suspended', undefined, {
706
- message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
707
- code: 'RPC_NOT_FOUND',
708
- })
709
- return
710
- }
711
-
712
- // Store error and mark failed
713
- await this.setStepError(stepState.stepId, error)
714
-
715
- const maxAttempts = (stepState.retries ?? 0) + 1
716
- const retriesExhausted = stepState.attemptCount >= maxAttempts
717
-
718
- if (retriesExhausted) {
719
- // No more retries - resume orchestrator to mark workflow as failed
720
- await this.resumeWorkflow(runId)
721
- }
722
-
723
- // Always throw so queue knows the job failed and can retry if needed
724
- throw error
725
- }
726
- })
727
- }
728
-
729
- /**
730
- * Orchestrate workflow execution (called by orchestrator)
731
- * Runs workflow job and handles async exceptions
732
- */
733
- public async orchestrateWorkflow(
734
- runId: string,
735
- rpcService: any
736
- ): Promise<void> {
737
- try {
738
- // Run workflow job (replays with caching)
739
- await this.runWorkflowJob(runId, rpcService)
740
- } catch (error: any) {
741
- if (
742
- error.name === 'WorkflowAsyncException' ||
743
- error.name === 'WorkflowCancelledException' ||
744
- error.name === 'WorkflowSuspendedException'
745
- ) {
746
- return
747
- }
748
-
749
- await this.updateRunStatus(runId, 'failed', undefined, {
750
- message: error.message,
751
- stack: error.stack,
752
- code: error.code,
753
- })
754
-
755
- throw error
756
- }
757
- }
758
-
759
- private verifyQueueService(): QueueService {
760
- if (!getSingletonServices()?.queueService) {
761
- throw new Error(
762
- 'QueueService not configured. Remote workflows require a queue service.'
763
- )
764
- }
765
-
766
- return getSingletonServices()!.queueService!
767
- }
768
-
769
- private async rpcStep(
770
- runId: string,
771
- stepName: string,
772
- rpcName: string,
773
- data: any,
774
- rpcService: any,
775
- stepOptions?: WorkflowStepOptions
776
- ): Promise<any> {
777
- // Check if step already exists
778
- let stepState: StepState
779
- try {
780
- stepState = await this.getStepState(runId, stepName)
781
- } catch {
782
- // Step doesn't exist - create it
783
- stepState = await this.insertStepState(
784
- runId,
785
- stepName,
786
- rpcName,
787
- data,
788
- stepOptions
789
- )
790
- }
791
-
792
- if (stepState.status === 'succeeded') {
793
- // Return cached result
794
- return stepState.result
795
- }
796
-
797
- if (stepState.status === 'failed') {
798
- // Step failed with retries exhausted - throw error to fail the workflow
799
- const error = new Error(
800
- stepState.error?.message ||
801
- `Step '${stepName}' failed after exhausting all retries`
802
- )
803
- // Preserve original error properties if available
804
- if (stepState.error) {
805
- Object.assign(error, stepState.error)
806
- }
807
- throw error
808
- }
809
-
810
- if (stepState.status === 'scheduled') {
811
- // Step is already scheduled, pause workflow
812
- throw new WorkflowAsyncException(runId, stepName)
813
- }
814
-
815
- // Step is pending - schedule it
816
- await this.setStepScheduled(stepState.stepId)
817
-
818
- // Enqueue step worker (unless inline mode)
819
- if (!this.isInline(runId) && getSingletonServices()!.queueService) {
820
- // Map step retry options to queue job options
821
- const retries = stepOptions?.retries ?? 0
822
- const retryDelay = stepOptions?.retryDelay
823
-
824
- await getSingletonServices()!.queueService!.add(
825
- this.getConfig().stepWorkerQueueName,
826
- {
827
- runId,
828
- stepName,
829
- rpcName,
830
- data,
831
- },
832
- {
833
- // attempts includes initial attempt, retries doesn't
834
- attempts: retries + 1,
835
- // Map retry delay to backoff
836
- backoff:
837
- typeof retryDelay === 'number'
838
- ? { type: 'fixed', delay: retryDelay }
839
- : retryDelay === 'exponential'
840
- ? 'exponential'
841
- : undefined,
842
- }
843
- )
844
- // Pause workflow - step will callback when done
845
- throw new WorkflowAsyncException(runId, stepName)
846
- } else {
847
- // Inline or no queue service - execute locally with retry loop
848
- const retries = stepOptions?.retries ?? 0
849
- const retryDelay = stepOptions?.retryDelay
850
- let currentStepState = stepState
851
-
852
- while (true) {
853
- try {
854
- await this.setStepRunning(currentStepState.stepId)
855
- const result = await rpcService.rpcWithWire(rpcName, data, {
856
- workflowStep: {
857
- runId,
858
- stepId: currentStepState.stepId,
859
- attemptCount: currentStepState.attemptCount,
860
- },
861
- })
862
- await this.setStepResult(currentStepState.stepId, result)
863
- return result
864
- } catch (error: any) {
865
- if (error instanceof RPCNotFoundError) {
866
- await this.updateRunStatus(runId, 'suspended', undefined, {
867
- message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
868
- code: 'RPC_NOT_FOUND',
869
- })
870
- throw error
871
- }
872
-
873
- // Record the error (marks step as failed)
874
- await this.setStepError(currentStepState.stepId, error)
875
-
876
- // Check if we should retry
877
- if (currentStepState.attemptCount < retries) {
878
- // Create a new pending retry attempt
879
- currentStepState = await this.createRetryAttempt(
880
- currentStepState.stepId,
881
- 'pending'
882
- )
883
-
884
- // Wait for retry delay if specified
885
- if (retryDelay) {
886
- await new Promise((resolve) =>
887
- setTimeout(resolve, getDurationInMilliseconds(retryDelay))
888
- )
889
- }
890
- // Continue loop to retry
891
- } else {
892
- // No more retries, fail the workflow
893
- throw error
894
- }
895
- }
896
- }
897
- }
898
- }
899
-
900
- private async inlineStep(
901
- runId: string,
902
- stepName: string,
903
- fn: Function,
904
- stepOptions?: WorkflowStepOptions
905
- ): Promise<any> {
906
- // Check if step already exists
907
- let stepState: StepState
908
- try {
909
- stepState = await this.getStepState(runId, stepName)
910
- } catch {
911
- // Step doesn't exist - create it (inline, no RPC)
912
- stepState = await this.insertStepState(
913
- runId,
914
- stepName,
915
- null,
916
- null,
917
- stepOptions
918
- )
919
- }
920
-
921
- if (stepState.status === 'succeeded') {
922
- // Return cached result
923
- return stepState.result
924
- }
925
-
926
- // Execute inline function
927
- const retries = stepOptions?.retries ?? this.getConfig().retries
928
- const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay
929
- let currentStepState = stepState
930
-
931
- // Check if we're running inline (in-memory) or remote (queue-based)
932
- if (this.isInline(runId)) {
933
- // Inline mode - execute with retry loop
934
- while (true) {
935
- try {
936
- await this.setStepRunning(currentStepState.stepId)
937
- const result = await fn()
938
- await this.setStepResult(currentStepState.stepId, result)
939
- return result
940
- } catch (error: any) {
941
- // Record the error (marks step as failed)
942
- await this.setStepError(currentStepState.stepId, error)
943
-
944
- // Check if we should retry
945
- if (currentStepState.attemptCount < retries) {
946
- // Create a new pending retry attempt
947
- currentStepState = await this.createRetryAttempt(
948
- currentStepState.stepId,
949
- 'pending'
950
- )
951
-
952
- // Wait for retry delay if specified
953
- if (retryDelay) {
954
- await new Promise((resolve) =>
955
- setTimeout(resolve, getDurationInMilliseconds(retryDelay))
956
- )
957
- }
958
- // Continue loop to retry
959
- } else {
960
- // No more retries, fail the workflow
961
- throw error
962
- }
963
- }
964
- }
965
- } else {
966
- // Remote mode - use queue-based retry
967
- try {
968
- await this.setStepRunning(currentStepState.stepId)
969
- const result = await fn()
970
- await this.setStepResult(currentStepState.stepId, result)
971
- return result
972
- } catch (error: any) {
973
- // Record the error (marks step as failed)
974
- await this.setStepError(currentStepState.stepId, error)
975
-
976
- // Check if we should retry
977
- if (currentStepState.attemptCount < retries) {
978
- // Create a new pending retry attempt (copies metadata from failed step)
979
- await this.createRetryAttempt(currentStepState.stepId, 'pending')
980
-
981
- // Schedule orchestrator to retry after delay
982
- await this.scheduleOrchestratorRetry(runId, retryDelay)
983
-
984
- // Pause workflow - orchestrator will replay and pick up new attempt
985
- throw new WorkflowAsyncException(runId, stepName)
986
- }
987
-
988
- // No more retries, fail the workflow
989
- throw error
990
- }
991
- }
992
- }
993
-
994
- private async sleepStep(runId: string, stepName: string, duration: number) {
995
- // Check if step already exists
996
- let stepState: StepState
997
- try {
998
- stepState = await this.getStepState(runId, stepName)
999
- } catch {
1000
- // Step doesn't exist - create it (sleep step, no RPC)
1001
- stepState = await this.insertStepState(runId, stepName, null, {
1002
- duration,
1003
- })
1004
- }
1005
-
1006
- if (stepState.status === 'succeeded') {
1007
- // Sleep already completed, return immediately
1008
- return
1009
- }
1010
-
1011
- if (stepState.status === 'scheduled') {
1012
- // Sleep is already scheduled, pause workflow
1013
- throw new WorkflowAsyncException(runId, stepName)
1014
- }
1015
-
1016
- // Step is pending - schedule it
1017
- await this.setStepScheduled(stepState.stepId)
1018
-
1019
- // Check if inline mode or no scheduler service
1020
- if (!this.isInline(runId) && getSingletonServices()!.schedulerService) {
1021
- // Remote mode - schedule sleep via scheduler service
1022
- await getSingletonServices()!.schedulerService!.scheduleRPC(
1023
- duration,
1024
- this.getConfig().sleeperRPCName,
1025
- {
1026
- runId,
1027
- stepId: stepState.stepId,
1028
- }
1029
- )
1030
- // Pause workflow - sleep will callback when done
1031
- throw new WorkflowAsyncException(runId, stepName)
1032
- } else {
1033
- // Inline mode - use setTimeout with actual duration
1034
- await new Promise((resolve) =>
1035
- setTimeout(resolve, getDurationInMilliseconds(duration))
1036
- )
1037
- await this.setStepResult(stepState.stepId, null)
1038
- return
1039
- }
1040
- }
1041
-
1042
- private getSuspendStepName(reason: string): string {
1043
- if (!reason) {
1044
- return '__workflow_suspend'
1045
- }
1046
- return '__workflow_suspend'
1047
- }
1048
-
1049
- private async suspendStep(runId: string, reason: string): Promise<void> {
1050
- const stepName = this.getSuspendStepName(reason)
1051
- await this.withStepLock(runId, stepName, async () => {
1052
- let stepState: StepState
1053
- try {
1054
- stepState = await this.getStepState(runId, stepName)
1055
- } catch {
1056
- stepState = await this.insertStepState(
1057
- runId,
1058
- stepName,
1059
- 'pikkuWorkflowSuspend',
1060
- {
1061
- reason,
1062
- }
1063
- )
1064
- }
1065
- if (!stepState.stepId) {
1066
- stepState = await this.insertStepState(
1067
- runId,
1068
- stepName,
1069
- 'pikkuWorkflowSuspend',
1070
- {
1071
- reason,
1072
- }
1073
- )
1074
- }
1075
-
1076
- if (stepState.status === 'succeeded') {
1077
- return
1078
- }
1079
-
1080
- if (stepState.status === 'pending') {
1081
- await this.setStepRunning(stepState.stepId)
1082
- }
1083
-
1084
- await this.setStepResult(stepState.stepId, {
1085
- reason,
1086
- suspendedAt: new Date().toISOString(),
1087
- })
1088
- throw new WorkflowSuspendedException(runId, reason)
1089
- })
1090
- }
1091
-
1092
- private createWorkflowWire(
1093
- name: string,
1094
- runId: string,
1095
- rpcService: any
1096
- ): PikkuWorkflowWire {
1097
- const workflowWire: PikkuWorkflowWire = {
1098
- name,
1099
- runId,
1100
- getRun: async () => (await this.getRun(runId)) as WorkflowRun,
1101
-
1102
- // Implement workflow.do() - RPC form
1103
- do: async (
1104
- stepName: string,
1105
- rpcNameOrFn: any,
1106
- dataOrOptions?: any,
1107
- options?: any
1108
- ) => {
1109
- this.verifyStepName(stepName)
1110
- if (typeof rpcNameOrFn === 'string') {
1111
- return await this.rpcStep(
1112
- runId,
1113
- stepName,
1114
- rpcNameOrFn,
1115
- dataOrOptions,
1116
- rpcService,
1117
- options
1118
- )
1119
- } else {
1120
- return await this.inlineStep(
1121
- runId,
1122
- stepName,
1123
- rpcNameOrFn,
1124
- dataOrOptions
1125
- )
1126
- }
1127
- },
1128
-
1129
- // Implement workflow.sleep()
1130
- sleep: async (stepName: string, duration: string | number) => {
1131
- this.verifyStepName(stepName)
1132
- await this.sleepStep(
1133
- runId,
1134
- stepName,
1135
- getDurationInMilliseconds(duration)
1136
- )
1137
- },
1138
-
1139
- suspend: async (reason: string) => {
1140
- await this.suspendStep(runId, reason || 'Workflow suspended')
1141
- },
1142
- }
1143
- return workflowWire
1144
- }
1145
-
1146
- private verifyStepName(stepName: string) {
1147
- if (typeof stepName !== 'string') {
1148
- throw new WorkflowStepNameNotString(stepName)
1149
- }
1150
- }
1151
-
1152
- private getConfig(): WorkflowServiceConfig {
1153
- const singletonServices = getSingletonServices()
1154
- const workflow = singletonServices.config?.workflow
1155
- return {
1156
- retries: workflow?.retries ?? 0,
1157
- retryDelay: workflow?.retryDelay ?? 0,
1158
- orchestratorQueueName:
1159
- workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
1160
- stepWorkerQueueName:
1161
- workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
1162
- sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
1163
- }
1164
- }
1165
- }