@pikku/core 0.11.1 → 0.12.0
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 +55 -0
- package/dist/crypto-utils.d.ts +2 -0
- package/dist/crypto-utils.js +78 -0
- package/dist/errors/error-handler.js +3 -3
- package/dist/errors/errors.d.ts +8 -0
- package/dist/errors/errors.js +16 -0
- package/dist/errors/index.d.ts +0 -1
- package/dist/errors/index.js +0 -1
- package/dist/function/function-runner.d.ts +10 -3
- package/dist/function/function-runner.js +136 -31
- package/dist/function/functions.types.d.ts +35 -12
- package/dist/function/functions.types.js +16 -6
- package/dist/function/index.d.ts +2 -2
- package/dist/function/index.js +1 -2
- package/dist/handle-error.js +1 -10
- package/dist/index.d.ts +30 -24
- package/dist/index.js +19 -27
- package/dist/middleware/auth-apikey.d.ts +1 -1
- package/dist/middleware/auth-apikey.js +3 -3
- package/dist/middleware/auth-bearer.d.ts +1 -1
- package/dist/middleware/auth-bearer.js +3 -6
- package/dist/middleware/auth-cookie.d.ts +1 -1
- package/dist/middleware/auth-cookie.js +6 -8
- package/dist/middleware/cors.d.ts +46 -0
- package/dist/middleware/cors.js +73 -0
- package/dist/middleware/index.d.ts +5 -3
- package/dist/middleware/index.js +5 -3
- package/dist/middleware/remote-auth.d.ts +5 -0
- package/dist/middleware/remote-auth.js +55 -0
- package/dist/middleware/timeout.d.ts +1 -1
- package/dist/middleware-runner.d.ts +3 -14
- package/dist/middleware-runner.js +14 -16
- package/dist/permissions.d.ts +3 -86
- package/dist/permissions.js +20 -80
- package/dist/pikku-state.d.ts +33 -104
- package/dist/pikku-state.js +150 -66
- package/dist/schema.d.ts +8 -6
- package/dist/schema.js +31 -18
- package/dist/services/ai-agent-runner-service.d.ts +24 -0
- package/dist/services/ai-agent-runner-service.js +1 -0
- package/dist/services/ai-run-state-service.d.ts +13 -0
- package/dist/services/ai-run-state-service.js +1 -0
- package/dist/services/ai-storage-service.d.ts +18 -0
- package/dist/services/ai-storage-service.js +1 -0
- package/dist/services/deployment-service.d.ts +19 -0
- package/dist/services/deployment-service.js +1 -0
- package/dist/services/gopass-secrets.d.ts +47 -0
- package/dist/services/gopass-secrets.js +94 -0
- package/dist/services/in-memory-trigger-service.d.ts +23 -0
- package/dist/services/in-memory-trigger-service.js +50 -0
- package/dist/services/in-memory-workflow-service.d.ts +59 -0
- package/dist/services/in-memory-workflow-service.js +287 -0
- package/dist/services/index.d.ts +25 -11
- package/dist/services/index.js +10 -13
- package/dist/services/local-secrets.d.ts +23 -0
- package/dist/services/local-secrets.js +45 -0
- package/dist/services/local-variables.d.ts +5 -0
- package/dist/services/local-variables.js +18 -0
- package/dist/services/scheduler-service.d.ts +21 -1
- package/dist/services/scheduler-service.js +14 -0
- package/dist/services/schema-service.d.ts +6 -0
- package/dist/services/scoped-secret-service.d.ts +16 -0
- package/dist/services/scoped-secret-service.js +35 -0
- package/dist/services/secret-service.d.ts +25 -4
- package/dist/services/trigger-service.d.ts +23 -0
- package/dist/services/trigger-service.js +1 -0
- package/dist/services/typed-secret-service.d.ts +30 -0
- package/dist/services/typed-secret-service.js +40 -0
- package/dist/services/typed-variables-service.d.ts +27 -0
- package/dist/services/typed-variables-service.js +46 -0
- package/dist/services/user-session-service.d.ts +13 -0
- package/dist/services/user-session-service.js +17 -0
- package/dist/services/variables-service.d.ts +5 -0
- package/dist/services/workflow-service.d.ts +43 -0
- package/dist/services/workflow-service.js +1 -0
- package/dist/types/core.types.d.ts +54 -10
- package/dist/types/core.types.js +7 -0
- package/dist/types/state.types.d.ts +127 -0
- package/dist/types/state.types.js +1 -0
- package/dist/utils.d.ts +10 -1
- package/dist/utils.js +60 -3
- package/dist/version.d.ts +6 -0
- package/dist/version.js +19 -0
- package/dist/wirings/ai-agent/ai-agent-memory.d.ts +29 -0
- package/dist/wirings/ai-agent/ai-agent-memory.js +199 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.d.ts +9 -0
- package/dist/wirings/ai-agent/ai-agent-model-config.js +36 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.d.ts +60 -0
- package/dist/wirings/ai-agent/ai-agent-prepare.js +312 -0
- package/dist/wirings/ai-agent/ai-agent-registry.d.ts +15 -0
- package/dist/wirings/ai-agent/ai-agent-registry.js +47 -0
- package/dist/wirings/ai-agent/ai-agent-runner.d.ts +3 -0
- package/dist/wirings/ai-agent/ai-agent-runner.js +96 -0
- package/dist/wirings/ai-agent/ai-agent-stream.d.ts +11 -0
- package/dist/wirings/ai-agent/ai-agent-stream.js +503 -0
- package/dist/wirings/ai-agent/ai-agent.types.d.ts +254 -0
- package/dist/wirings/ai-agent/ai-agent.types.js +1 -0
- package/dist/wirings/ai-agent/index.d.ts +5 -0
- package/dist/wirings/ai-agent/index.js +4 -0
- package/dist/wirings/channel/channel-common.d.ts +7 -4
- package/dist/wirings/channel/channel-common.js +15 -8
- package/dist/wirings/channel/channel-handler.js +13 -4
- package/dist/wirings/channel/channel-middleware-runner.d.ts +9 -0
- package/dist/wirings/channel/channel-middleware-runner.js +73 -0
- package/dist/wirings/channel/channel-runner.js +12 -12
- package/dist/wirings/channel/channel.types.d.ts +5 -1
- package/dist/wirings/channel/define-channel-routes.d.ts +21 -0
- package/dist/wirings/channel/define-channel-routes.js +23 -0
- package/dist/wirings/channel/index.d.ts +10 -8
- package/dist/wirings/channel/index.js +7 -8
- package/dist/wirings/channel/local/index.d.ts +3 -3
- package/dist/wirings/channel/local/index.js +3 -3
- package/dist/wirings/channel/local/local-channel-runner.js +18 -6
- package/dist/wirings/channel/log-channels.js +1 -1
- package/dist/wirings/channel/serverless/index.d.ts +1 -1
- package/dist/wirings/channel/serverless/index.js +1 -1
- package/dist/wirings/channel/serverless/serverless-channel-runner.js +17 -6
- package/dist/wirings/cli/channel/cli-channel-runner.js +3 -2
- package/dist/wirings/cli/channel/index.d.ts +1 -1
- package/dist/wirings/cli/channel/index.js +1 -1
- package/dist/wirings/cli/cli-runner.d.ts +1 -1
- package/dist/wirings/cli/cli-runner.js +22 -18
- package/dist/wirings/cli/cli.types.d.ts +7 -6
- package/dist/wirings/cli/define-cli-commands.d.ts +19 -0
- package/dist/wirings/cli/define-cli-commands.js +20 -0
- package/dist/wirings/cli/index.d.ts +2 -3
- package/dist/wirings/cli/index.js +1 -3
- package/dist/wirings/http/http-routes.d.ts +69 -0
- package/dist/wirings/http/http-routes.js +139 -0
- package/dist/wirings/http/http-runner.d.ts +2 -2
- package/dist/wirings/http/http-runner.js +62 -27
- package/dist/wirings/http/http.types.d.ts +77 -24
- package/dist/wirings/http/index.d.ts +5 -4
- package/dist/wirings/http/index.js +4 -3
- 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/index.d.ts +4 -3
- package/dist/wirings/mcp/index.js +3 -3
- package/dist/wirings/mcp/mcp-runner.d.ts +1 -3
- package/dist/wirings/mcp/mcp-runner.js +42 -56
- package/dist/wirings/mcp/mcp.types.d.ts +3 -3
- package/dist/wirings/node/index.d.ts +2 -0
- package/dist/wirings/node/index.js +1 -0
- package/dist/wirings/node/node.types.d.ts +20 -0
- package/dist/wirings/node/node.types.js +1 -0
- package/dist/wirings/oauth2/index.d.ts +3 -0
- package/dist/wirings/oauth2/index.js +2 -0
- package/dist/wirings/oauth2/oauth2-client.d.ts +47 -0
- package/dist/wirings/oauth2/oauth2-client.js +267 -0
- package/dist/wirings/oauth2/oauth2.types.d.ts +65 -0
- package/dist/wirings/oauth2/oauth2.types.js +4 -0
- package/dist/wirings/oauth2/wire-oauth2-credential.d.ts +20 -0
- package/dist/wirings/oauth2/wire-oauth2-credential.js +21 -0
- package/dist/wirings/queue/index.d.ts +2 -2
- package/dist/wirings/queue/index.js +1 -1
- package/dist/wirings/queue/queue-runner.d.ts +7 -0
- package/dist/wirings/queue/queue-runner.js +23 -12
- package/dist/wirings/queue/queue.types.d.ts +8 -2
- 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 +40 -13
- package/dist/wirings/rpc/rpc-runner.js +191 -25
- package/dist/wirings/rpc/rpc-types.d.ts +15 -4
- package/dist/wirings/scheduler/index.d.ts +3 -3
- package/dist/wirings/scheduler/index.js +2 -3
- package/dist/wirings/scheduler/log-schedulers.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.d.ts +5 -0
- package/dist/wirings/scheduler/scheduler-runner.js +28 -10
- package/dist/wirings/secret/index.d.ts +3 -0
- package/dist/wirings/secret/index.js +2 -0
- package/dist/wirings/secret/secret.types.d.ts +27 -0
- package/dist/wirings/secret/secret.types.js +1 -0
- package/dist/wirings/secret/validate-secret-definitions.d.ts +6 -0
- package/dist/wirings/secret/validate-secret-definitions.js +55 -0
- package/dist/wirings/trigger/index.d.ts +3 -0
- package/dist/wirings/trigger/index.js +2 -0
- package/dist/wirings/trigger/pikku-trigger-service.d.ts +26 -0
- package/dist/wirings/trigger/pikku-trigger-service.js +75 -0
- package/dist/wirings/trigger/trigger-runner.d.ts +42 -0
- package/dist/wirings/trigger/trigger-runner.js +85 -0
- package/dist/wirings/trigger/trigger.types.d.ts +130 -0
- package/dist/wirings/trigger/trigger.types.js +40 -0
- package/dist/wirings/variable/index.d.ts +3 -0
- package/dist/wirings/variable/index.js +2 -0
- package/dist/wirings/variable/validate-variable-definitions.d.ts +6 -0
- package/dist/wirings/variable/validate-variable-definitions.js +45 -0
- package/dist/wirings/variable/variable.types.d.ts +18 -0
- package/dist/wirings/variable/variable.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 +292 -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 +23 -0
- package/dist/wirings/workflow/graph/graph-node.d.ts +117 -0
- package/dist/wirings/workflow/graph/graph-node.js +58 -0
- package/dist/wirings/workflow/graph/graph-runner.d.ts +9 -0
- package/dist/wirings/workflow/graph/graph-runner.js +540 -0
- package/dist/wirings/workflow/graph/index.d.ts +4 -0
- package/dist/wirings/workflow/graph/index.js +3 -0
- package/dist/wirings/workflow/graph/template.d.ts +15 -0
- package/dist/wirings/workflow/graph/template.js +27 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.d.ts +15 -0
- package/dist/wirings/workflow/graph/wire-workflow-graph.js +8 -0
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +82 -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 +8 -11
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +114 -10
- package/dist/wirings/workflow/pikku-workflow-service.js +386 -115
- package/dist/wirings/workflow/workflow-helpers.d.ts +36 -0
- package/dist/wirings/workflow/workflow-helpers.js +38 -0
- package/dist/wirings/workflow/workflow-utils.d.ts +1 -0
- package/dist/wirings/workflow/workflow-utils.js +1 -0
- package/dist/wirings/workflow/workflow.types.d.ts +83 -214
- package/package.json +13 -6
- package/run-tests.sh +4 -1
- package/src/crypto-utils.ts +99 -0
- package/src/errors/error-handler.ts +3 -3
- package/src/errors/errors.ts +17 -0
- package/src/errors/index.ts +0 -1
- package/src/factory-functions.test.ts +42 -2
- package/src/function/function-runner.test.ts +5 -12
- package/src/function/function-runner.ts +195 -40
- package/src/function/functions.types.ts +66 -14
- package/src/function/index.ts +5 -2
- package/src/handle-error.ts +2 -10
- package/src/index.ts +106 -27
- package/src/middleware/auth-apikey.test.ts +15 -12
- package/src/middleware/auth-apikey.ts +3 -3
- package/src/middleware/auth-bearer.test.ts +17 -14
- package/src/middleware/auth-bearer.ts +25 -27
- package/src/middleware/auth-cookie.test.ts +16 -13
- package/src/middleware/auth-cookie.ts +6 -8
- package/src/middleware/cors.ts +96 -0
- package/src/middleware/index.ts +5 -3
- package/src/middleware/remote-auth.ts +68 -0
- package/src/middleware-runner.test.ts +122 -0
- package/src/middleware-runner.ts +24 -17
- package/src/permissions.test.ts +102 -86
- package/src/permissions.ts +32 -93
- package/src/pikku-state.ts +168 -201
- package/src/run-tests-script.test.ts +49 -0
- package/src/schema.ts +50 -18
- package/src/services/ai-agent-runner-service.ts +29 -0
- package/src/services/ai-run-state-service.ts +20 -0
- package/src/services/ai-storage-service.ts +39 -0
- package/src/services/deployment-service.ts +22 -0
- package/src/services/gopass-secrets.ts +95 -0
- package/src/services/in-memory-trigger-service.ts +68 -0
- package/src/services/in-memory-workflow-service.ts +395 -0
- package/src/services/index.ts +49 -14
- package/src/services/local-secrets.ts +51 -0
- package/src/services/local-variables.ts +22 -0
- package/src/services/scheduler-service.ts +29 -1
- package/src/services/schema-service.ts +7 -0
- package/src/services/scoped-secret-service.ts +41 -0
- package/src/services/secret-service.ts +25 -4
- package/src/services/trigger-service.ts +38 -0
- package/src/services/typed-secret-service.ts +70 -0
- package/src/services/typed-variables-service.ts +81 -0
- package/src/services/user-session-service.ts +23 -0
- package/src/services/variables-service.ts +5 -0
- package/src/services/workflow-service.ts +94 -0
- package/src/types/core.types.ts +82 -8
- package/src/types/state.types.ts +169 -0
- package/src/utils.ts +74 -5
- package/src/version.test.ts +80 -0
- package/src/version.ts +25 -0
- package/src/wirings/ai-agent/ai-agent-memory.ts +300 -0
- package/src/wirings/ai-agent/ai-agent-model-config.ts +43 -0
- package/src/wirings/ai-agent/ai-agent-prepare.ts +470 -0
- package/src/wirings/ai-agent/ai-agent-registry.test.ts +37 -0
- package/src/wirings/ai-agent/ai-agent-registry.ts +82 -0
- package/src/wirings/ai-agent/ai-agent-runner.test.ts +75 -0
- package/src/wirings/ai-agent/ai-agent-runner.ts +137 -0
- package/src/wirings/ai-agent/ai-agent-stream.test.ts +246 -0
- package/src/wirings/ai-agent/ai-agent-stream.ts +774 -0
- package/src/wirings/ai-agent/ai-agent.types.ts +273 -0
- package/src/wirings/ai-agent/index.ts +28 -0
- package/src/wirings/channel/channel-common.ts +32 -19
- package/src/wirings/channel/channel-handler.ts +18 -5
- package/src/wirings/channel/channel-middleware-runner.ts +119 -0
- package/src/wirings/channel/channel-runner.ts +14 -15
- package/src/wirings/channel/channel.types.ts +17 -1
- package/src/wirings/channel/define-channel-routes.ts +25 -0
- package/src/wirings/channel/index.ts +23 -8
- package/src/wirings/channel/local/index.ts +3 -3
- package/src/wirings/channel/local/local-channel-runner.test.ts +47 -2
- package/src/wirings/channel/local/local-channel-runner.ts +22 -6
- package/src/wirings/channel/log-channels.ts +1 -1
- package/src/wirings/channel/serverless/index.ts +5 -1
- package/src/wirings/channel/serverless/serverless-channel-runner.ts +22 -6
- package/src/wirings/cli/channel/cli-channel-runner.ts +4 -3
- package/src/wirings/cli/channel/index.ts +1 -1
- package/src/wirings/cli/cli-runner.test.ts +28 -28
- package/src/wirings/cli/cli-runner.ts +27 -19
- package/src/wirings/cli/cli.types.ts +12 -11
- package/src/wirings/cli/command-parser.test.ts +8 -8
- package/src/wirings/cli/define-cli-commands.ts +24 -0
- package/src/wirings/cli/index.ts +10 -4
- package/src/wirings/http/http-routes.test.ts +322 -0
- package/src/wirings/http/http-routes.ts +198 -0
- package/src/wirings/http/http-runner.test.ts +5 -5
- package/src/wirings/http/http-runner.ts +80 -29
- package/src/wirings/http/http.types.ts +121 -23
- package/src/wirings/http/index.ts +16 -4
- 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/index.ts +27 -3
- package/src/wirings/mcp/mcp-endpoint-registry.test.ts +3 -3
- package/src/wirings/mcp/mcp-runner.ts +56 -67
- package/src/wirings/mcp/mcp.types.ts +3 -3
- package/src/wirings/node/index.ts +2 -0
- package/src/wirings/node/node.types.ts +23 -0
- package/src/wirings/oauth2/index.ts +3 -0
- package/src/wirings/oauth2/oauth2-client.test.ts +929 -0
- package/src/wirings/oauth2/oauth2-client.ts +335 -0
- package/src/wirings/oauth2/oauth2.types.ts +69 -0
- package/src/wirings/oauth2/wire-oauth2-credential.ts +23 -0
- package/src/wirings/queue/index.ts +14 -2
- package/src/wirings/queue/queue-runner.test.ts +115 -73
- package/src/wirings/queue/queue-runner.ts +39 -13
- package/src/wirings/queue/queue.types.ts +12 -2
- package/src/wirings/rpc/index.ts +1 -1
- package/src/wirings/rpc/rpc-runner.ts +270 -34
- package/src/wirings/rpc/rpc-types.ts +21 -4
- package/src/wirings/scheduler/index.ts +7 -4
- package/src/wirings/scheduler/log-schedulers.ts +1 -1
- package/src/wirings/scheduler/scheduler-runner.test.ts +97 -53
- package/src/wirings/scheduler/scheduler-runner.ts +50 -10
- package/src/wirings/secret/index.ts +9 -0
- package/src/wirings/secret/secret.types.ts +32 -0
- package/src/wirings/secret/validate-secret-definitions.ts +82 -0
- package/src/wirings/trigger/index.ts +10 -0
- package/src/wirings/trigger/pikku-trigger-service.ts +134 -0
- package/src/wirings/trigger/trigger-runner.ts +138 -0
- package/src/wirings/trigger/trigger.types.ts +178 -0
- package/src/wirings/variable/index.ts +8 -0
- package/src/wirings/variable/validate-variable-definitions.ts +69 -0
- package/src/wirings/variable/variable.types.ts +22 -0
- package/src/wirings/workflow/dsl/index.ts +31 -0
- package/src/wirings/workflow/dsl/workflow-dsl.types.ts +320 -0
- package/src/wirings/workflow/dsl/workflow-runner.ts +28 -0
- package/src/wirings/workflow/graph/graph-node.ts +222 -0
- package/src/wirings/workflow/graph/graph-runner.test.ts +495 -0
- package/src/wirings/workflow/graph/graph-runner.ts +816 -0
- package/src/wirings/workflow/graph/index.ts +14 -0
- package/src/wirings/workflow/graph/template.ts +42 -0
- package/src/wirings/workflow/graph/wire-workflow-graph.ts +29 -0
- package/src/wirings/workflow/graph/workflow-graph.types.ts +104 -0
- package/src/wirings/workflow/index.ts +64 -26
- package/src/wirings/workflow/pikku-workflow-service.test.ts +212 -0
- package/src/wirings/workflow/pikku-workflow-service.ts +571 -136
- package/src/wirings/workflow/workflow-helpers.ts +79 -0
- package/src/wirings/workflow/workflow-utils.ts +0 -0
- package/src/wirings/workflow/workflow.types.ts +123 -279
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/wirings/workflow/workflow-runner.d.ts +0 -34
- package/dist/wirings/workflow/workflow-runner.js +0 -65
- package/src/function/function-runner.ts.bak +0 -188
- package/src/wirings/workflow/workflow-runner.ts +0 -72
|
@@ -1,7 +1,69 @@
|
|
|
1
1
|
import { runPikkuFunc } from '../../function/function-runner.js';
|
|
2
2
|
import { pikkuState } from '../../pikku-state.js';
|
|
3
3
|
import { getDurationInMilliseconds } from '../../time-utils.js';
|
|
4
|
-
import {
|
|
4
|
+
import { continueGraph, executeGraphStep, runWorkflowGraph, runFromMeta, } from './graph/graph-runner.js';
|
|
5
|
+
import { PikkuError, addError } from '../../errors/error-handler.js';
|
|
6
|
+
import { RPCNotFoundError } from '../rpc/rpc-runner.js';
|
|
7
|
+
/**
|
|
8
|
+
* Exception thrown when workflow needs to pause for async step
|
|
9
|
+
*/
|
|
10
|
+
export class WorkflowAsyncException extends Error {
|
|
11
|
+
runId;
|
|
12
|
+
stepName;
|
|
13
|
+
constructor(runId, stepName) {
|
|
14
|
+
super(`Workflow paused at step: ${stepName}`);
|
|
15
|
+
this.runId = runId;
|
|
16
|
+
this.stepName = stepName;
|
|
17
|
+
this.name = 'WorkflowAsyncException';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Exception thrown when workflow is cancelled
|
|
22
|
+
*/
|
|
23
|
+
export class WorkflowCancelledException extends Error {
|
|
24
|
+
runId;
|
|
25
|
+
reason;
|
|
26
|
+
constructor(runId, reason) {
|
|
27
|
+
super(reason || 'Workflow cancelled');
|
|
28
|
+
this.runId = runId;
|
|
29
|
+
this.reason = reason;
|
|
30
|
+
this.name = 'WorkflowCancelledException';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Exception thrown when workflow is suspended
|
|
35
|
+
*/
|
|
36
|
+
export class WorkflowSuspendedException extends Error {
|
|
37
|
+
runId;
|
|
38
|
+
reason;
|
|
39
|
+
constructor(runId, reason) {
|
|
40
|
+
super(reason || 'Workflow suspended');
|
|
41
|
+
this.runId = runId;
|
|
42
|
+
this.reason = reason;
|
|
43
|
+
this.name = 'WorkflowSuspendedException';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Error class for workflow not found
|
|
48
|
+
*/
|
|
49
|
+
export class WorkflowNotFoundError extends PikkuError {
|
|
50
|
+
constructor(name) {
|
|
51
|
+
super(`Workflow not found: ${name}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
addError(WorkflowNotFoundError, {
|
|
55
|
+
status: 404,
|
|
56
|
+
message: 'Workflow not found.',
|
|
57
|
+
});
|
|
58
|
+
export class WorkflowRunNotFoundError extends PikkuError {
|
|
59
|
+
constructor(runId) {
|
|
60
|
+
super(`Workflow run not found: ${runId}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
addError(WorkflowRunNotFoundError, {
|
|
64
|
+
status: 404,
|
|
65
|
+
message: 'Workflow run not found.',
|
|
66
|
+
});
|
|
5
67
|
export class WorkflowServiceNotInitialized extends Error {
|
|
6
68
|
}
|
|
7
69
|
export class WorkflowStepNameNotString extends Error {
|
|
@@ -9,6 +71,12 @@ export class WorkflowStepNameNotString extends Error {
|
|
|
9
71
|
super(`Workflow step name must be a string. Received: ${typeof stepName}`);
|
|
10
72
|
}
|
|
11
73
|
}
|
|
74
|
+
const WORKFLOW_END_STATES = new Set([
|
|
75
|
+
'completed',
|
|
76
|
+
'failed',
|
|
77
|
+
'cancelled',
|
|
78
|
+
'suspended',
|
|
79
|
+
]);
|
|
12
80
|
/**
|
|
13
81
|
* Abstract workflow state service
|
|
14
82
|
* Implementations provide pluggable storage backends (SQLite, PostgreSQL, etc.)
|
|
@@ -18,7 +86,26 @@ export class PikkuWorkflowService {
|
|
|
18
86
|
config;
|
|
19
87
|
singletonServices;
|
|
20
88
|
createWireServices;
|
|
89
|
+
inlineRuns = new Set();
|
|
21
90
|
constructor() { }
|
|
91
|
+
/**
|
|
92
|
+
* Check if a run is executing inline (without queues)
|
|
93
|
+
*/
|
|
94
|
+
isInline(runId) {
|
|
95
|
+
return this.inlineRuns.has(runId);
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Register a run as inline (for graph-runner to use)
|
|
99
|
+
*/
|
|
100
|
+
registerInlineRun(runId) {
|
|
101
|
+
this.inlineRuns.add(runId);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Unregister a run from inline tracking
|
|
105
|
+
*/
|
|
106
|
+
unregisterInlineRun(runId) {
|
|
107
|
+
this.inlineRuns.delete(runId);
|
|
108
|
+
}
|
|
22
109
|
setServices(singletonServices, createWireServices, { workflow }) {
|
|
23
110
|
this.singletonServices = singletonServices;
|
|
24
111
|
this.createWireServices = createWireServices;
|
|
@@ -27,9 +114,17 @@ export class PikkuWorkflowService {
|
|
|
27
114
|
retryDelay: workflow?.retryDelay ?? 0,
|
|
28
115
|
orchestratorQueueName: workflow?.orchestratorQueueName ?? 'pikku-workflow-orchestrator',
|
|
29
116
|
stepWorkerQueueName: workflow?.stepWorkerQueueName ?? 'pikku-workflow-step-worker',
|
|
30
|
-
sleeperRPCName: workflow?.sleeperRPCName ?? '
|
|
117
|
+
sleeperRPCName: workflow?.sleeperRPCName ?? 'pikkuWorkflowSleeper',
|
|
31
118
|
};
|
|
32
119
|
}
|
|
120
|
+
async registerWorkflowVersions() {
|
|
121
|
+
const allMeta = pikkuState(null, 'workflows', 'meta');
|
|
122
|
+
for (const [name, meta] of Object.entries(allMeta)) {
|
|
123
|
+
if (!meta.graphHash)
|
|
124
|
+
continue;
|
|
125
|
+
await this.upsertWorkflowVersion(name, meta.graphHash, meta, meta.source);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
33
128
|
// ============================================================================
|
|
34
129
|
// Workflow Lifecycle Methods
|
|
35
130
|
// ============================================================================
|
|
@@ -41,12 +136,21 @@ export class PikkuWorkflowService {
|
|
|
41
136
|
const queueService = this.verifyQueueService();
|
|
42
137
|
await queueService.add(this.getConfig().orchestratorQueueName, { runId });
|
|
43
138
|
}
|
|
139
|
+
async queueStepWorker(runId, stepName, rpcName, data) {
|
|
140
|
+
const queueService = this.verifyQueueService();
|
|
141
|
+
await queueService.add(this.getConfig().stepWorkerQueueName, {
|
|
142
|
+
runId,
|
|
143
|
+
stepName,
|
|
144
|
+
rpcName,
|
|
145
|
+
data,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
44
148
|
/**
|
|
45
149
|
* Execute a workflow sleep step completion
|
|
46
150
|
* Sets the step result to null and resumes the workflow
|
|
47
151
|
* @param data - Sleeper input data
|
|
48
152
|
*/
|
|
49
|
-
async
|
|
153
|
+
async executeWorkflowSleepCompleted(runId, stepId) {
|
|
50
154
|
await this.setStepResult(stepId, null);
|
|
51
155
|
await this.resumeWorkflow(runId);
|
|
52
156
|
}
|
|
@@ -61,44 +165,96 @@ export class PikkuWorkflowService {
|
|
|
61
165
|
}
|
|
62
166
|
/**
|
|
63
167
|
* Start a new workflow run
|
|
168
|
+
* Automatically detects workflow type (DSL or graph) from meta and executes accordingly
|
|
169
|
+
* @param options.inline - If true, execute workflow directly without queue service
|
|
170
|
+
* @param options.startNode - Starting node ID for graph workflows (from wire config)
|
|
64
171
|
*/
|
|
65
|
-
async startWorkflow(name, input, rpcService) {
|
|
66
|
-
|
|
172
|
+
async startWorkflow(name, input, wire, rpcService, options) {
|
|
173
|
+
// Check meta to determine workflow type
|
|
174
|
+
const meta = pikkuState(null, 'workflows', 'meta');
|
|
175
|
+
const workflowMeta = meta[name];
|
|
176
|
+
if (!workflowMeta) {
|
|
177
|
+
throw new WorkflowNotFoundError(name);
|
|
178
|
+
}
|
|
179
|
+
// Check if this is a graph workflow (source === 'graph')
|
|
180
|
+
if (workflowMeta.source === 'graph') {
|
|
181
|
+
const shouldInline = options?.inline || !this.singletonServices?.queueService;
|
|
182
|
+
return runWorkflowGraph(this, name, input, rpcService, shouldInline, options?.startNode, wire);
|
|
183
|
+
}
|
|
184
|
+
// DSL workflow - check registration exists
|
|
185
|
+
const registrations = pikkuState(null, 'workflows', 'registrations');
|
|
67
186
|
const workflow = registrations.get(name);
|
|
68
187
|
if (!workflow) {
|
|
69
188
|
throw new WorkflowNotFoundError(name);
|
|
70
189
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
190
|
+
if (!workflowMeta.graphHash) {
|
|
191
|
+
throw new Error(`Missing workflow graphHash for '${name}'`);
|
|
192
|
+
}
|
|
193
|
+
const runId = await this.createRun(name, input, options?.inline ?? false, workflowMeta.graphHash, wire);
|
|
194
|
+
if (options?.inline) {
|
|
195
|
+
this.inlineRuns.add(runId);
|
|
196
|
+
}
|
|
197
|
+
if (options?.inline || !this.singletonServices?.queueService) {
|
|
198
|
+
this.runWorkflowJob(runId, rpcService)
|
|
199
|
+
.catch(() => { })
|
|
200
|
+
.finally(() => {
|
|
201
|
+
if (options?.inline) {
|
|
202
|
+
this.inlineRuns.delete(runId);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
78
205
|
}
|
|
79
206
|
else {
|
|
80
|
-
|
|
81
|
-
await this.runWorkflowJob(runId, rpcService);
|
|
207
|
+
await this.resumeWorkflow(runId);
|
|
82
208
|
}
|
|
83
209
|
return { runId };
|
|
84
210
|
}
|
|
211
|
+
async runToCompletion(name, input, rpcService, options) {
|
|
212
|
+
const pollInterval = options?.pollIntervalMs ?? 1000;
|
|
213
|
+
const { runId } = await this.startWorkflow(name, input, options?.wire ?? { type: 'internal' }, rpcService, { inline: true });
|
|
214
|
+
while (true) {
|
|
215
|
+
const run = await this.getRun(runId);
|
|
216
|
+
if (!run) {
|
|
217
|
+
throw new WorkflowRunNotFoundError(runId);
|
|
218
|
+
}
|
|
219
|
+
if (WORKFLOW_END_STATES.has(run.status)) {
|
|
220
|
+
if (run.status === 'failed') {
|
|
221
|
+
throw new Error(run.error?.message || 'Workflow failed');
|
|
222
|
+
}
|
|
223
|
+
if (run.status === 'cancelled') {
|
|
224
|
+
throw new Error('Workflow was cancelled');
|
|
225
|
+
}
|
|
226
|
+
return run.output;
|
|
227
|
+
}
|
|
228
|
+
await new Promise((resolve) => setTimeout(resolve, pollInterval));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
85
231
|
async runWorkflowJob(runId, rpcService) {
|
|
86
232
|
const run = await this.getRun(runId);
|
|
87
233
|
if (!run) {
|
|
88
|
-
throw new
|
|
234
|
+
throw new WorkflowRunNotFoundError(runId);
|
|
235
|
+
}
|
|
236
|
+
const meta = pikkuState(null, 'workflows', 'meta');
|
|
237
|
+
const workflowMeta = meta[run.workflow];
|
|
238
|
+
if (run.graphHash &&
|
|
239
|
+
workflowMeta?.graphHash &&
|
|
240
|
+
run.graphHash !== workflowMeta.graphHash) {
|
|
241
|
+
await this.runVersionMismatchFallback(run, workflowMeta, rpcService);
|
|
242
|
+
return;
|
|
89
243
|
}
|
|
90
|
-
|
|
244
|
+
if (workflowMeta?.source === 'graph') {
|
|
245
|
+
await continueGraph(this, runId, run.workflow);
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const registrations = pikkuState(null, 'workflows', 'registrations');
|
|
91
249
|
const workflow = registrations.get(run.workflow);
|
|
92
250
|
if (!workflow) {
|
|
93
251
|
throw new WorkflowNotFoundError(run.workflow);
|
|
94
252
|
}
|
|
95
253
|
await this.withRunLock(runId, async () => {
|
|
96
|
-
const meta = pikkuState('workflows', 'meta');
|
|
97
|
-
const workflowMeta = meta[run.workflow];
|
|
98
254
|
const workflowWire = this.createWorkflowWire(run.workflow, runId, rpcService);
|
|
99
255
|
const wire = { workflow: workflowWire };
|
|
100
256
|
try {
|
|
101
|
-
const result = await runPikkuFunc('workflow', workflowMeta.
|
|
257
|
+
const result = await runPikkuFunc('workflow', workflowMeta.name, workflowMeta.pikkuFuncId, {
|
|
102
258
|
singletonServices: this.singletonServices,
|
|
103
259
|
wire,
|
|
104
260
|
createWireServices: this.createWireServices,
|
|
@@ -108,12 +264,22 @@ export class PikkuWorkflowService {
|
|
|
108
264
|
}
|
|
109
265
|
catch (error) {
|
|
110
266
|
if (error instanceof WorkflowAsyncException) {
|
|
111
|
-
// Normal - workflow paused for step execution
|
|
112
267
|
throw error;
|
|
113
268
|
}
|
|
114
|
-
// Check if it's a WorkflowCancelledException
|
|
115
269
|
if (error instanceof WorkflowCancelledException) {
|
|
116
|
-
|
|
270
|
+
await this.updateRunStatus(runId, 'cancelled', undefined, {
|
|
271
|
+
message: error.message || 'Workflow cancelled',
|
|
272
|
+
stack: '',
|
|
273
|
+
code: 'WORKFLOW_CANCELLED',
|
|
274
|
+
});
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
if (error instanceof WorkflowSuspendedException) {
|
|
278
|
+
await this.updateRunStatus(runId, 'suspended', undefined, {
|
|
279
|
+
message: error.message || 'Workflow suspended',
|
|
280
|
+
stack: '',
|
|
281
|
+
code: 'WORKFLOW_SUSPENDED',
|
|
282
|
+
});
|
|
117
283
|
throw error;
|
|
118
284
|
}
|
|
119
285
|
await this.updateRunStatus(runId, 'failed', undefined, {
|
|
@@ -125,6 +291,27 @@ export class PikkuWorkflowService {
|
|
|
125
291
|
}
|
|
126
292
|
});
|
|
127
293
|
}
|
|
294
|
+
async runVersionMismatchFallback(run, currentMeta, rpcService) {
|
|
295
|
+
const source = currentMeta.source;
|
|
296
|
+
if (source === 'complex') {
|
|
297
|
+
await this.updateRunStatus(run.id, 'failed', undefined, {
|
|
298
|
+
message: `Workflow '${run.workflow}' definition changed. Complex workflows with inline steps cannot be migrated.`,
|
|
299
|
+
stack: '',
|
|
300
|
+
code: 'VERSION_CONFLICT',
|
|
301
|
+
});
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const version = await this.getWorkflowVersion(run.workflow, run.graphHash);
|
|
305
|
+
if (!version) {
|
|
306
|
+
await this.updateRunStatus(run.id, 'failed', undefined, {
|
|
307
|
+
message: `Workflow '${run.workflow}' version '${run.graphHash}' not found. Cannot resume with changed definition.`,
|
|
308
|
+
stack: '',
|
|
309
|
+
code: 'VERSION_NOT_FOUND',
|
|
310
|
+
});
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
await runFromMeta(this, run.id, version.graph, rpcService);
|
|
314
|
+
}
|
|
128
315
|
/**
|
|
129
316
|
* Execute a single workflow step (called by worker)
|
|
130
317
|
* Handles idempotency, RPC execution, result storage, retry logic, and orchestrator triggering
|
|
@@ -147,30 +334,44 @@ export class PikkuWorkflowService {
|
|
|
147
334
|
if (stepState.status === 'failed') {
|
|
148
335
|
stepState = await this.createRetryAttempt(stepState.stepId, 'running');
|
|
149
336
|
}
|
|
150
|
-
if (stepState.status
|
|
151
|
-
// Mark step as running
|
|
337
|
+
if (stepState.status === 'pending') {
|
|
338
|
+
// Mark pending step as running before execution
|
|
152
339
|
await this.setStepRunning(stepState.stepId);
|
|
153
340
|
}
|
|
154
341
|
try {
|
|
155
|
-
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
342
|
+
let result;
|
|
343
|
+
const run = await this.getRun(runId);
|
|
344
|
+
if (!run) {
|
|
345
|
+
throw new Error(`Workflow run not found: ${runId}`);
|
|
346
|
+
}
|
|
347
|
+
const meta = pikkuState(null, 'workflows', 'meta');
|
|
348
|
+
const workflowMeta = meta[run.workflow];
|
|
349
|
+
if (workflowMeta?.nodes && stepName in workflowMeta.nodes) {
|
|
350
|
+
result = await executeGraphStep(this, rpcService, runId, stepState.stepId, stepName, rpcName, data, run.workflow);
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
result = await rpcService.rpcWithWire(rpcName, data, {
|
|
354
|
+
workflowStep: {
|
|
355
|
+
runId,
|
|
356
|
+
stepId: stepState.stepId,
|
|
357
|
+
attemptCount: stepState.attemptCount,
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
}
|
|
163
361
|
// Store result and mark succeeded
|
|
164
362
|
await this.setStepResult(stepState.stepId, result);
|
|
165
363
|
// Resume orchestrator to continue workflow
|
|
166
|
-
|
|
167
|
-
await this.resumeWorkflow(runId);
|
|
168
|
-
}
|
|
169
|
-
catch (resumeError) {
|
|
170
|
-
throw resumeError;
|
|
171
|
-
}
|
|
364
|
+
await this.resumeWorkflow(runId);
|
|
172
365
|
}
|
|
173
366
|
catch (error) {
|
|
367
|
+
if (error instanceof RPCNotFoundError) {
|
|
368
|
+
await this.setStepError(stepState.stepId, error);
|
|
369
|
+
await this.updateRunStatus(runId, 'suspended', undefined, {
|
|
370
|
+
message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
|
|
371
|
+
code: 'RPC_NOT_FOUND',
|
|
372
|
+
});
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
174
375
|
// Store error and mark failed
|
|
175
376
|
await this.setStepError(stepState.stepId, error);
|
|
176
377
|
const maxAttempts = (stepState.retries ?? 0) + 1;
|
|
@@ -195,7 +396,8 @@ export class PikkuWorkflowService {
|
|
|
195
396
|
}
|
|
196
397
|
catch (error) {
|
|
197
398
|
if (error.name === 'WorkflowAsyncException' ||
|
|
198
|
-
error.name === 'WorkflowCancelledException'
|
|
399
|
+
error.name === 'WorkflowCancelledException' ||
|
|
400
|
+
error.name === 'WorkflowSuspendedException') {
|
|
199
401
|
return;
|
|
200
402
|
}
|
|
201
403
|
await this.updateRunStatus(runId, 'failed', undefined, {
|
|
@@ -218,7 +420,7 @@ export class PikkuWorkflowService {
|
|
|
218
420
|
try {
|
|
219
421
|
stepState = await this.getStepState(runId, stepName);
|
|
220
422
|
}
|
|
221
|
-
catch
|
|
423
|
+
catch {
|
|
222
424
|
// Step doesn't exist - create it
|
|
223
425
|
stepState = await this.insertStepState(runId, stepName, rpcName, data, stepOptions);
|
|
224
426
|
}
|
|
@@ -242,8 +444,8 @@ export class PikkuWorkflowService {
|
|
|
242
444
|
}
|
|
243
445
|
// Step is pending - schedule it
|
|
244
446
|
await this.setStepScheduled(stepState.stepId);
|
|
245
|
-
// Enqueue step worker
|
|
246
|
-
if (this.singletonServices.queueService) {
|
|
447
|
+
// Enqueue step worker (unless inline mode)
|
|
448
|
+
if (!this.isInline(runId) && this.singletonServices.queueService) {
|
|
247
449
|
// Map step retry options to queue job options
|
|
248
450
|
const retries = stepOptions?.retries ?? 0;
|
|
249
451
|
const retryDelay = stepOptions?.retryDelay;
|
|
@@ -262,41 +464,54 @@ export class PikkuWorkflowService {
|
|
|
262
464
|
? 'exponential'
|
|
263
465
|
: undefined,
|
|
264
466
|
});
|
|
467
|
+
// Pause workflow - step will callback when done
|
|
468
|
+
throw new WorkflowAsyncException(runId, stepName);
|
|
265
469
|
}
|
|
266
470
|
else {
|
|
267
|
-
//
|
|
471
|
+
// Inline or no queue service - execute locally with retry loop
|
|
268
472
|
const retries = stepOptions?.retries ?? 0;
|
|
269
473
|
const retryDelay = stepOptions?.retryDelay;
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
474
|
+
let currentStepState = stepState;
|
|
475
|
+
while (true) {
|
|
476
|
+
try {
|
|
477
|
+
await this.setStepRunning(currentStepState.stepId);
|
|
478
|
+
const result = await rpcService.rpcWithWire(rpcName, data, {
|
|
479
|
+
workflowStep: {
|
|
480
|
+
runId,
|
|
481
|
+
stepId: currentStepState.stepId,
|
|
482
|
+
attemptCount: currentStepState.attemptCount,
|
|
483
|
+
},
|
|
484
|
+
});
|
|
485
|
+
await this.setStepResult(currentStepState.stepId, result);
|
|
486
|
+
return result;
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
if (error instanceof RPCNotFoundError) {
|
|
490
|
+
await this.updateRunStatus(runId, 'suspended', undefined, {
|
|
491
|
+
message: `RPC '${rpcName}' not found. Deploy the missing function and resume.`,
|
|
492
|
+
code: 'RPC_NOT_FOUND',
|
|
493
|
+
});
|
|
494
|
+
throw error;
|
|
495
|
+
}
|
|
496
|
+
// Record the error (marks step as failed)
|
|
497
|
+
await this.setStepError(currentStepState.stepId, error);
|
|
498
|
+
// Check if we should retry
|
|
499
|
+
if (currentStepState.attemptCount < retries) {
|
|
500
|
+
// Create a new pending retry attempt
|
|
501
|
+
currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
502
|
+
// Wait for retry delay if specified
|
|
503
|
+
if (retryDelay) {
|
|
504
|
+
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
|
|
505
|
+
}
|
|
506
|
+
// Continue loop to retry
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
// No more retries, fail the workflow
|
|
510
|
+
throw error;
|
|
511
|
+
}
|
|
293
512
|
}
|
|
294
|
-
// No more retries, fail the workflow
|
|
295
|
-
throw error;
|
|
296
513
|
}
|
|
297
514
|
}
|
|
298
|
-
// Pause workflow - step will callback when done
|
|
299
|
-
throw new WorkflowAsyncException(runId, stepName);
|
|
300
515
|
}
|
|
301
516
|
async inlineStep(runId, stepName, fn, stepOptions) {
|
|
302
517
|
// Check if step already exists
|
|
@@ -304,11 +519,9 @@ export class PikkuWorkflowService {
|
|
|
304
519
|
try {
|
|
305
520
|
stepState = await this.getStepState(runId, stepName);
|
|
306
521
|
}
|
|
307
|
-
catch
|
|
522
|
+
catch {
|
|
308
523
|
// Step doesn't exist - create it (inline, no RPC)
|
|
309
|
-
stepState = await this.insertStepState(runId, stepName, null,
|
|
310
|
-
null, // No data for inline steps
|
|
311
|
-
stepOptions);
|
|
524
|
+
stepState = await this.insertStepState(runId, stepName, null, null, stepOptions);
|
|
312
525
|
}
|
|
313
526
|
if (stepState.status === 'succeeded') {
|
|
314
527
|
// Return cached result
|
|
@@ -317,26 +530,60 @@ export class PikkuWorkflowService {
|
|
|
317
530
|
// Execute inline function
|
|
318
531
|
const retries = stepOptions?.retries ?? this.getConfig().retries;
|
|
319
532
|
const retryDelay = stepOptions?.retryDelay ?? this.getConfig().retryDelay;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
533
|
+
let currentStepState = stepState;
|
|
534
|
+
// Check if we're running inline (in-memory) or remote (queue-based)
|
|
535
|
+
if (this.isInline(runId)) {
|
|
536
|
+
// Inline mode - execute with retry loop
|
|
537
|
+
while (true) {
|
|
538
|
+
try {
|
|
539
|
+
await this.setStepRunning(currentStepState.stepId);
|
|
540
|
+
const result = await fn();
|
|
541
|
+
await this.setStepResult(currentStepState.stepId, result);
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
// Record the error (marks step as failed)
|
|
546
|
+
await this.setStepError(currentStepState.stepId, error);
|
|
547
|
+
// Check if we should retry
|
|
548
|
+
if (currentStepState.attemptCount < retries) {
|
|
549
|
+
// Create a new pending retry attempt
|
|
550
|
+
currentStepState = await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
551
|
+
// Wait for retry delay if specified
|
|
552
|
+
if (retryDelay) {
|
|
553
|
+
await new Promise((resolve) => setTimeout(resolve, getDurationInMilliseconds(retryDelay)));
|
|
554
|
+
}
|
|
555
|
+
// Continue loop to retry
|
|
556
|
+
}
|
|
557
|
+
else {
|
|
558
|
+
// No more retries, fail the workflow
|
|
559
|
+
throw error;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
325
563
|
}
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
//
|
|
336
|
-
|
|
564
|
+
else {
|
|
565
|
+
// Remote mode - use queue-based retry
|
|
566
|
+
try {
|
|
567
|
+
await this.setStepRunning(currentStepState.stepId);
|
|
568
|
+
const result = await fn();
|
|
569
|
+
await this.setStepResult(currentStepState.stepId, result);
|
|
570
|
+
return result;
|
|
571
|
+
}
|
|
572
|
+
catch (error) {
|
|
573
|
+
// Record the error (marks step as failed)
|
|
574
|
+
await this.setStepError(currentStepState.stepId, error);
|
|
575
|
+
// Check if we should retry
|
|
576
|
+
if (currentStepState.attemptCount < retries) {
|
|
577
|
+
// Create a new pending retry attempt (copies metadata from failed step)
|
|
578
|
+
await this.createRetryAttempt(currentStepState.stepId, 'pending');
|
|
579
|
+
// Schedule orchestrator to retry after delay
|
|
580
|
+
await this.scheduleOrchestratorRetry(runId, retryDelay);
|
|
581
|
+
// Pause workflow - orchestrator will replay and pick up new attempt
|
|
582
|
+
throw new WorkflowAsyncException(runId, stepName);
|
|
583
|
+
}
|
|
584
|
+
// No more retries, fail the workflow
|
|
585
|
+
throw error;
|
|
337
586
|
}
|
|
338
|
-
// No more retries, fail the workflow
|
|
339
|
-
throw error;
|
|
340
587
|
}
|
|
341
588
|
}
|
|
342
589
|
async sleepStep(runId, stepName, duration) {
|
|
@@ -345,12 +592,11 @@ export class PikkuWorkflowService {
|
|
|
345
592
|
try {
|
|
346
593
|
stepState = await this.getStepState(runId, stepName);
|
|
347
594
|
}
|
|
348
|
-
catch
|
|
595
|
+
catch {
|
|
349
596
|
// Step doesn't exist - create it (sleep step, no RPC)
|
|
350
|
-
stepState = await this.insertStepState(runId, stepName, null,
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
);
|
|
597
|
+
stepState = await this.insertStepState(runId, stepName, null, {
|
|
598
|
+
duration,
|
|
599
|
+
});
|
|
354
600
|
}
|
|
355
601
|
if (stepState.status === 'succeeded') {
|
|
356
602
|
// Sleep already completed, return immediately
|
|
@@ -362,13 +608,15 @@ export class PikkuWorkflowService {
|
|
|
362
608
|
}
|
|
363
609
|
// Step is pending - schedule it
|
|
364
610
|
await this.setStepScheduled(stepState.stepId);
|
|
365
|
-
// Check if
|
|
366
|
-
if (this.singletonServices.schedulerService) {
|
|
367
|
-
// Remote mode -
|
|
611
|
+
// Check if inline mode or no scheduler service
|
|
612
|
+
if (!this.isInline(runId) && this.singletonServices.schedulerService) {
|
|
613
|
+
// Remote mode - schedule sleep via scheduler service
|
|
368
614
|
await this.singletonServices.schedulerService.scheduleRPC(duration, this.getConfig().sleeperRPCName, {
|
|
369
615
|
runId,
|
|
370
616
|
stepId: stepState.stepId,
|
|
371
617
|
});
|
|
618
|
+
// Pause workflow - sleep will callback when done
|
|
619
|
+
throw new WorkflowAsyncException(runId, stepName);
|
|
372
620
|
}
|
|
373
621
|
else {
|
|
374
622
|
// Inline mode - use setTimeout with actual duration
|
|
@@ -376,22 +624,46 @@ export class PikkuWorkflowService {
|
|
|
376
624
|
await this.setStepResult(stepState.stepId, null);
|
|
377
625
|
return;
|
|
378
626
|
}
|
|
379
|
-
// Pause workflow - sleep will callback when done
|
|
380
|
-
throw new WorkflowAsyncException(runId, stepName);
|
|
381
627
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
628
|
+
getSuspendStepName(reason) {
|
|
629
|
+
if (!reason) {
|
|
630
|
+
return '__workflow_suspend';
|
|
631
|
+
}
|
|
632
|
+
return '__workflow_suspend';
|
|
633
|
+
}
|
|
634
|
+
async suspendStep(runId, reason) {
|
|
635
|
+
const stepName = this.getSuspendStepName(reason);
|
|
636
|
+
await this.withStepLock(runId, stepName, async () => {
|
|
637
|
+
let stepState;
|
|
638
|
+
try {
|
|
639
|
+
stepState = await this.getStepState(runId, stepName);
|
|
640
|
+
}
|
|
641
|
+
catch {
|
|
642
|
+
stepState = await this.insertStepState(runId, stepName, 'pikkuWorkflowSuspend', {
|
|
643
|
+
reason,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (!stepState.stepId) {
|
|
647
|
+
stepState = await this.insertStepState(runId, stepName, 'pikkuWorkflowSuspend', {
|
|
648
|
+
reason,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
if (stepState.status === 'succeeded') {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (stepState.status === 'pending') {
|
|
655
|
+
await this.setStepRunning(stepState.stepId);
|
|
656
|
+
}
|
|
657
|
+
await this.setStepResult(stepState.stepId, {
|
|
658
|
+
reason,
|
|
659
|
+
suspendedAt: new Date().toISOString(),
|
|
660
|
+
});
|
|
661
|
+
throw new WorkflowSuspendedException(runId, reason);
|
|
388
662
|
});
|
|
389
|
-
// Throw cancellation exception to stop workflow execution
|
|
390
|
-
throw new WorkflowCancelledException(runId, reason);
|
|
391
663
|
}
|
|
392
|
-
createWorkflowWire(
|
|
664
|
+
createWorkflowWire(name, runId, rpcService) {
|
|
393
665
|
const workflowWire = {
|
|
394
|
-
|
|
666
|
+
name,
|
|
395
667
|
runId,
|
|
396
668
|
getRun: async () => (await this.getRun(runId)),
|
|
397
669
|
// Implement workflow.do() - RPC form
|
|
@@ -409,9 +681,8 @@ export class PikkuWorkflowService {
|
|
|
409
681
|
this.verifyStepName(stepName);
|
|
410
682
|
await this.sleepStep(runId, stepName, getDurationInMilliseconds(duration));
|
|
411
683
|
},
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
await this.cancelWorkflow(runId, reason);
|
|
684
|
+
suspend: async (reason) => {
|
|
685
|
+
await this.suspendStep(runId, reason || 'Workflow suspended');
|
|
415
686
|
},
|
|
416
687
|
};
|
|
417
688
|
return workflowWire;
|