@gobing-ai/ts-dual-workflow-engine 0.2.1
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/README.md +4 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +51 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +25 -0
- package/dist/host.d.ts +34 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +93 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/persistence.d.ts +58 -0
- package/dist/persistence.d.ts.map +1 -0
- package/dist/persistence.js +98 -0
- package/dist/schema-sql.d.ts +2 -0
- package/dist/schema-sql.d.ts.map +1 -0
- package/dist/schema-sql.js +48 -0
- package/dist/schema.d.ts +140 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +54 -0
- package/dist/service.d.ts +17 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +31 -0
- package/dist/state-machine.d.ts +18 -0
- package/dist/state-machine.d.ts.map +1 -0
- package/dist/state-machine.js +123 -0
- package/dist/transition-flow.d.ts +17 -0
- package/dist/transition-flow.d.ts.map +1 -0
- package/dist/transition-flow.js +114 -0
- package/dist/types.d.ts +141 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/dist/variables.d.ts +14 -0
- package/dist/variables.d.ts.map +1 -0
- package/dist/variables.js +45 -0
- package/package.json +61 -0
- package/src/config.ts +57 -0
- package/src/errors.ts +29 -0
- package/src/host.ts +100 -0
- package/src/index.ts +48 -0
- package/src/persistence.ts +155 -0
- package/src/schema-sql.ts +48 -0
- package/src/schema.ts +67 -0
- package/src/service.ts +39 -0
- package/src/state-machine.ts +223 -0
- package/src/transition-flow.ts +178 -0
- package/src/types.ts +160 -0
- package/src/variables.ts +57 -0
package/dist/schema.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Zod schema for workflow action definitions. */
|
|
3
|
+
export const ActionDefSchema = z.object({
|
|
4
|
+
kind: z.string().min(1),
|
|
5
|
+
options: z.record(z.string(), z.unknown()).optional(),
|
|
6
|
+
});
|
|
7
|
+
/** Zod schema for workflow guard definitions. */
|
|
8
|
+
export const GuardDefSchema = z.object({
|
|
9
|
+
kind: z.string().min(1),
|
|
10
|
+
options: z.record(z.string(), z.unknown()).optional(),
|
|
11
|
+
});
|
|
12
|
+
/** Zod schema for state-machine workflow definitions. */
|
|
13
|
+
export const StateMachineWorkflowDefSchema = z.object({
|
|
14
|
+
kind: z.literal('state-machine').optional(),
|
|
15
|
+
name: z.string().min(1),
|
|
16
|
+
initialState: z.string().min(1),
|
|
17
|
+
terminalStates: z.array(z.string().min(1)).optional(),
|
|
18
|
+
iterationBound: z.number().int().positive().optional(),
|
|
19
|
+
vars: z.record(z.string(), z.string()).optional(),
|
|
20
|
+
env: z.object({ allow: z.array(z.string()).optional() }).optional(),
|
|
21
|
+
states: z.array(z.object({
|
|
22
|
+
id: z.string().min(1),
|
|
23
|
+
onEnter: z.array(ActionDefSchema).optional(),
|
|
24
|
+
onExit: z.array(ActionDefSchema).optional(),
|
|
25
|
+
})),
|
|
26
|
+
transitions: z.array(z.object({
|
|
27
|
+
from: z.string().min(1),
|
|
28
|
+
to: z.string().min(1),
|
|
29
|
+
trigger: z.string().optional(),
|
|
30
|
+
guard: GuardDefSchema.optional(),
|
|
31
|
+
})),
|
|
32
|
+
});
|
|
33
|
+
/** Zod schema for transition-flow workflow definitions. */
|
|
34
|
+
export const TransitionFlowWorkflowDefSchema = z.object({
|
|
35
|
+
kind: z.literal('transition-flow'),
|
|
36
|
+
name: z.string().min(1),
|
|
37
|
+
initialNode: z.string().min(1),
|
|
38
|
+
terminalNodes: z.array(z.string().min(1)).optional(),
|
|
39
|
+
iterationBound: z.number().int().positive().optional(),
|
|
40
|
+
vars: z.record(z.string(), z.string()).optional(),
|
|
41
|
+
env: z.object({ allow: z.array(z.string()).optional() }).optional(),
|
|
42
|
+
nodes: z.array(z.object({
|
|
43
|
+
id: z.string().min(1),
|
|
44
|
+
type: z.enum(['action', 'gate', 'parallel', 'decision']).optional(),
|
|
45
|
+
action: ActionDefSchema.optional(),
|
|
46
|
+
})),
|
|
47
|
+
edges: z.array(z.object({
|
|
48
|
+
from: z.string().min(1),
|
|
49
|
+
to: z.string().min(1),
|
|
50
|
+
condition: GuardDefSchema.optional(),
|
|
51
|
+
})),
|
|
52
|
+
});
|
|
53
|
+
/** Zod schema for either supported workflow definition shape. */
|
|
54
|
+
export const WorkflowDefSchema = z.union([StateMachineWorkflowDefSchema, TransitionFlowWorkflowDefSchema]);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WorkflowEngineHost } from './host';
|
|
2
|
+
import type { WorkflowDef, WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunResult } from './types';
|
|
3
|
+
/** High-level workflow service for loading, running, and listing persisted workflow runs. */
|
|
4
|
+
export declare class WorkflowService {
|
|
5
|
+
private readonly host;
|
|
6
|
+
private readonly persistence;
|
|
7
|
+
constructor(host: WorkflowEngineHost, persistence: WorkflowPersistenceAdapter);
|
|
8
|
+
/** Load a workflow file and validate it. */
|
|
9
|
+
load(path: string): Promise<WorkflowDef>;
|
|
10
|
+
/** Run an already-loaded workflow definition. */
|
|
11
|
+
run(workflow: WorkflowDef, options?: WorkflowRunOptions): Promise<WorkflowRunResult>;
|
|
12
|
+
/** Load and run a workflow file. */
|
|
13
|
+
runFile(path: string, options?: WorkflowRunOptions): Promise<WorkflowRunResult>;
|
|
14
|
+
/** List persisted workflow runs. */
|
|
15
|
+
listRuns(): Promise<readonly import("./types").WorkflowRunRecord[]>;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAGjD,OAAO,KAAK,EAAE,WAAW,EAAE,0BAA0B,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAE9G,6FAA6F;AAC7F,qBAAa,eAAe;IAEpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,WAAW;gBADX,IAAI,EAAE,kBAAkB,EACxB,WAAW,EAAE,0BAA0B;IAG5D,4CAA4C;IACtC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAI9C,iDAAiD;IAC3C,GAAG,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAU9F,oCAAoC;IAC9B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAIzF,oCAAoC;IAC9B,QAAQ;CAGjB"}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { loadWorkflowDef } from './config.js';
|
|
2
|
+
import { StateMachineDriver } from './state-machine.js';
|
|
3
|
+
import { TransitionFlowDriver } from './transition-flow.js';
|
|
4
|
+
/** High-level workflow service for loading, running, and listing persisted workflow runs. */
|
|
5
|
+
export class WorkflowService {
|
|
6
|
+
host;
|
|
7
|
+
persistence;
|
|
8
|
+
constructor(host, persistence) {
|
|
9
|
+
this.host = host;
|
|
10
|
+
this.persistence = persistence;
|
|
11
|
+
}
|
|
12
|
+
/** Load a workflow file and validate it. */
|
|
13
|
+
async load(path) {
|
|
14
|
+
return await loadWorkflowDef(path);
|
|
15
|
+
}
|
|
16
|
+
/** Run an already-loaded workflow definition. */
|
|
17
|
+
async run(workflow, options = {}) {
|
|
18
|
+
if (workflow.kind === 'transition-flow') {
|
|
19
|
+
return await new TransitionFlowDriver({ host: this.host, persistence: this.persistence }).run(workflow, options);
|
|
20
|
+
}
|
|
21
|
+
return await new StateMachineDriver({ host: this.host, persistence: this.persistence }).run(workflow, options);
|
|
22
|
+
}
|
|
23
|
+
/** Load and run a workflow file. */
|
|
24
|
+
async runFile(path, options = {}) {
|
|
25
|
+
return await this.run(await this.load(path), options);
|
|
26
|
+
}
|
|
27
|
+
/** List persisted workflow runs. */
|
|
28
|
+
async listRuns() {
|
|
29
|
+
return await this.persistence.listRuns();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { WorkflowEngineHost } from './host';
|
|
2
|
+
import type { StateMachineWorkflowDef, WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunResult } from './types';
|
|
3
|
+
/** Dependencies required by the state-machine driver. */
|
|
4
|
+
export interface StateMachineDriverOptions {
|
|
5
|
+
readonly host: WorkflowEngineHost;
|
|
6
|
+
readonly persistence: WorkflowPersistenceAdapter;
|
|
7
|
+
}
|
|
8
|
+
/** State-machine workflow driver with an R7 single control function. */
|
|
9
|
+
export declare class StateMachineDriver {
|
|
10
|
+
private readonly options;
|
|
11
|
+
constructor(options: StateMachineDriverOptions);
|
|
12
|
+
/** Run a state-machine workflow to completion or failure. */
|
|
13
|
+
run(workflow: StateMachineWorkflowDef, options?: WorkflowRunOptions): Promise<WorkflowRunResult>;
|
|
14
|
+
private runActions;
|
|
15
|
+
private done;
|
|
16
|
+
private fail;
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=state-machine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state-machine.d.ts","sourceRoot":"","sources":["../src/state-machine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,KAAK,EAGR,uBAAuB,EACvB,0BAA0B,EAC1B,kBAAkB,EAElB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,yDAAyD;AACzD,MAAM,WAAW,yBAAyB;IACtC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CACpD;AAED,wEAAwE;AACxE,qBAAa,kBAAkB;IACf,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,yBAAyB;IAE/D,6DAA6D;IACvD,GAAG,CAAC,QAAQ,EAAE,uBAAuB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;YA4G5F,UAAU;YA6BV,IAAI;YAYJ,IAAI;CAYrB"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getProcessEnv } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { FSMError } from './errors.js';
|
|
3
|
+
import { mergeVars, resolveTemplates } from './variables.js';
|
|
4
|
+
/** State-machine workflow driver with an R7 single control function. */
|
|
5
|
+
export class StateMachineDriver {
|
|
6
|
+
options;
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
}
|
|
10
|
+
/** Run a state-machine workflow to completion or failure. */
|
|
11
|
+
async run(workflow, options = {}) {
|
|
12
|
+
const runId = options.runId ?? crypto.randomUUID();
|
|
13
|
+
const startedAt = new Date().toISOString();
|
|
14
|
+
const mode = 'state-machine';
|
|
15
|
+
await this.options.persistence.createRun(runRecord(runId, workflow.name, mode, startedAt, options.metadata));
|
|
16
|
+
const states = new Map(workflow.states.map((state) => [state.id, state]));
|
|
17
|
+
const terminal = new Set(workflow.terminalStates ?? []);
|
|
18
|
+
const vars = mergeVars(workflow.vars, options.vars);
|
|
19
|
+
const env = allowedEnv(workflow.env?.allow ?? [], options.env ?? getProcessEnv());
|
|
20
|
+
let current = states.get(workflow.initialState);
|
|
21
|
+
let transitionsTaken = 0;
|
|
22
|
+
let lastActionResult;
|
|
23
|
+
const iterationBound = workflow.iterationBound ?? 50;
|
|
24
|
+
if (current === undefined)
|
|
25
|
+
throw new FSMError(`Initial state "${workflow.initialState}" is not declared`);
|
|
26
|
+
while (true) {
|
|
27
|
+
// 1. Persist current state snapshot before work starts.
|
|
28
|
+
await this.options.persistence.saveWorkflowState(runId, current.id, { transitionsTaken });
|
|
29
|
+
await this.options.persistence.savePhase(runId, current.id, 'running');
|
|
30
|
+
// 2. Execute this state's on-enter actions in declaration order.
|
|
31
|
+
lastActionResult = await this.runActions(current.onEnter ?? [], workflow.name, current.id, runId, vars, env, options);
|
|
32
|
+
if (lastActionResult?.ok === false)
|
|
33
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, lastActionResult.error);
|
|
34
|
+
// 3. Stop immediately when an action explicitly declares terminal success.
|
|
35
|
+
if (lastActionResult?.terminal === true) {
|
|
36
|
+
return await this.done(runId, workflow.name, mode, current.id, transitionsTaken);
|
|
37
|
+
}
|
|
38
|
+
// 4. Stop when the current state is terminal or has no outbound transitions.
|
|
39
|
+
const outbound = workflow.transitions.filter((transition) => transition.from === current?.id);
|
|
40
|
+
if (terminal.has(current.id) || outbound.length === 0) {
|
|
41
|
+
return await this.done(runId, workflow.name, mode, current.id, transitionsTaken);
|
|
42
|
+
}
|
|
43
|
+
// 5. Evaluate transition guards in declaration order and pick the first passing transition.
|
|
44
|
+
const nextTransition = await firstPassingTransition(outbound, this.options.host, {
|
|
45
|
+
runId,
|
|
46
|
+
current: current.id,
|
|
47
|
+
vars,
|
|
48
|
+
lastActionResult,
|
|
49
|
+
});
|
|
50
|
+
if (nextTransition === undefined) {
|
|
51
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, 'no-passing-transition');
|
|
52
|
+
}
|
|
53
|
+
// 6. Execute this state's on-exit actions before changing state.
|
|
54
|
+
const exitResult = await this.runActions(current.onExit ?? [], workflow.name, current.id, runId, vars, env, options);
|
|
55
|
+
if (exitResult?.ok === false)
|
|
56
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, exitResult.error);
|
|
57
|
+
// 7. Persist transition and move to the target state.
|
|
58
|
+
transitionsTaken += 1;
|
|
59
|
+
await this.options.persistence.saveTransition(runId, current.id, nextTransition.to, nextTransition.trigger ?? null);
|
|
60
|
+
if (transitionsTaken > iterationBound) {
|
|
61
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, 'iteration-bound-exceeded');
|
|
62
|
+
}
|
|
63
|
+
const nextState = states.get(nextTransition.to);
|
|
64
|
+
if (nextState === undefined)
|
|
65
|
+
throw new FSMError(`Transition target "${nextTransition.to}" is not declared`);
|
|
66
|
+
current = nextState;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async runActions(actions, workflowName, stateId, runId, vars, env, options) {
|
|
70
|
+
let last;
|
|
71
|
+
for (const action of actions) {
|
|
72
|
+
const resolved = resolveTemplates(action.options ?? {}, {
|
|
73
|
+
vars,
|
|
74
|
+
env,
|
|
75
|
+
builtins: { workflow: workflowName, state: stateId, runId },
|
|
76
|
+
});
|
|
77
|
+
last = await this.options.host.runAction(action.kind, resolved, {
|
|
78
|
+
runId,
|
|
79
|
+
workdir: options.workdir,
|
|
80
|
+
stateOrNodeId: stateId,
|
|
81
|
+
vars,
|
|
82
|
+
env,
|
|
83
|
+
metadata: options.metadata,
|
|
84
|
+
});
|
|
85
|
+
if (!last.ok || last.terminal === true)
|
|
86
|
+
return last;
|
|
87
|
+
}
|
|
88
|
+
return last;
|
|
89
|
+
}
|
|
90
|
+
async done(runId, workflowName, mode, finalState, transitionsTaken) {
|
|
91
|
+
await this.options.persistence.savePhase(runId, finalState, 'done');
|
|
92
|
+
await this.options.persistence.finalizeRun(runId, 'done', new Date().toISOString());
|
|
93
|
+
return { runId, workflowName, mode, status: 'done', finalState, transitionsTaken };
|
|
94
|
+
}
|
|
95
|
+
async fail(runId, workflowName, mode, finalState, transitionsTaken, reason = 'failed') {
|
|
96
|
+
await this.options.persistence.savePhase(runId, finalState, 'failed');
|
|
97
|
+
await this.options.persistence.finalizeRun(runId, 'failed', new Date().toISOString());
|
|
98
|
+
return { runId, workflowName, mode, status: 'failed', finalState, transitionsTaken, reason };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async function firstPassingTransition(transitions, host, context) {
|
|
102
|
+
for (const transition of transitions) {
|
|
103
|
+
if (transition.guard === undefined)
|
|
104
|
+
return transition;
|
|
105
|
+
if (await host.evaluateGuard(transition.guard.kind, transition.guard.options ?? {}, context))
|
|
106
|
+
return transition;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
function allowedEnv(names, source) {
|
|
111
|
+
return Object.fromEntries(names.flatMap((name) => (source[name] === undefined ? [] : [[name, source[name]]])));
|
|
112
|
+
}
|
|
113
|
+
function runRecord(runId, workflowName, mode, startedAt, metadata) {
|
|
114
|
+
return {
|
|
115
|
+
id: runId,
|
|
116
|
+
workflow_name: workflowName,
|
|
117
|
+
mode,
|
|
118
|
+
status: 'running',
|
|
119
|
+
started_at: startedAt,
|
|
120
|
+
completed_at: null,
|
|
121
|
+
metadata_json: JSON.stringify(metadata ?? {}),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { WorkflowEngineHost } from './host';
|
|
2
|
+
import type { TransitionFlowWorkflowDef, WorkflowPersistenceAdapter, WorkflowRunOptions, WorkflowRunResult } from './types';
|
|
3
|
+
/** Dependencies required by the transition-flow driver. */
|
|
4
|
+
export interface TransitionFlowDriverOptions {
|
|
5
|
+
readonly host: WorkflowEngineHost;
|
|
6
|
+
readonly persistence: WorkflowPersistenceAdapter;
|
|
7
|
+
}
|
|
8
|
+
/** Transition-flow workflow driver with an R7 single control function. */
|
|
9
|
+
export declare class TransitionFlowDriver {
|
|
10
|
+
private readonly options;
|
|
11
|
+
constructor(options: TransitionFlowDriverOptions);
|
|
12
|
+
/** Run a transition-flow workflow to completion or failure. */
|
|
13
|
+
run(workflow: TransitionFlowWorkflowDef, options?: WorkflowRunOptions): Promise<WorkflowRunResult>;
|
|
14
|
+
private done;
|
|
15
|
+
private fail;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=transition-flow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transition-flow.d.ts","sourceRoot":"","sources":["../src/transition-flow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,KAAK,EAER,yBAAyB,EACzB,0BAA0B,EAC1B,kBAAkB,EAElB,iBAAiB,EACpB,MAAM,SAAS,CAAC;AAGjB,2DAA2D;AAC3D,MAAM,WAAW,2BAA2B;IACxC,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,0BAA0B,CAAC;CACpD;AAED,0EAA0E;AAC1E,qBAAa,oBAAoB;IACjB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,2BAA2B;IAEjE,+DAA+D;IACzD,GAAG,CAAC,QAAQ,EAAE,yBAAyB,EAAE,OAAO,GAAE,kBAAuB,GAAG,OAAO,CAAC,iBAAiB,CAAC;YA8F9F,IAAI;YAYJ,IAAI;CAYrB"}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { getProcessEnv } from '@gobing-ai/ts-runtime';
|
|
2
|
+
import { mergeVars, resolveTemplates } from './variables.js';
|
|
3
|
+
/** Transition-flow workflow driver with an R7 single control function. */
|
|
4
|
+
export class TransitionFlowDriver {
|
|
5
|
+
options;
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.options = options;
|
|
8
|
+
}
|
|
9
|
+
/** Run a transition-flow workflow to completion or failure. */
|
|
10
|
+
async run(workflow, options = {}) {
|
|
11
|
+
const runId = options.runId ?? crypto.randomUUID();
|
|
12
|
+
const startedAt = new Date().toISOString();
|
|
13
|
+
const mode = 'transition-flow';
|
|
14
|
+
await this.options.persistence.createRun(runRecord(runId, workflow.name, mode, startedAt, options.metadata));
|
|
15
|
+
const nodes = new Map(workflow.nodes.map((node) => [node.id, node]));
|
|
16
|
+
const terminal = new Set(workflow.terminalNodes ?? []);
|
|
17
|
+
const vars = mergeVars(workflow.vars, options.vars);
|
|
18
|
+
const env = allowedEnv(workflow.env?.allow ?? [], options.env ?? getProcessEnv());
|
|
19
|
+
let current = nodes.get(workflow.initialNode);
|
|
20
|
+
let transitionsTaken = 0;
|
|
21
|
+
let lastActionResult;
|
|
22
|
+
const iterationBound = workflow.iterationBound ?? 50;
|
|
23
|
+
if (current === undefined) {
|
|
24
|
+
throw new Error(`Initial node "${workflow.initialNode}" is not declared`);
|
|
25
|
+
}
|
|
26
|
+
while (true) {
|
|
27
|
+
// 1. Persist current node snapshot before action execution.
|
|
28
|
+
await this.options.persistence.saveWorkflowState(runId, current.id, { transitionsTaken });
|
|
29
|
+
await this.options.persistence.savePhase(runId, current.id, 'running');
|
|
30
|
+
// 2. Execute the node action when one is configured.
|
|
31
|
+
if (current.action !== undefined) {
|
|
32
|
+
const resolved = resolveTemplates(current.action.options ?? {}, {
|
|
33
|
+
vars,
|
|
34
|
+
env,
|
|
35
|
+
builtins: { workflow: workflow.name, node: current.id, runId },
|
|
36
|
+
});
|
|
37
|
+
lastActionResult = await this.options.host.runAction(current.action.kind, resolved, {
|
|
38
|
+
runId,
|
|
39
|
+
workdir: options.workdir,
|
|
40
|
+
stateOrNodeId: current.id,
|
|
41
|
+
vars,
|
|
42
|
+
env,
|
|
43
|
+
metadata: options.metadata,
|
|
44
|
+
});
|
|
45
|
+
if (!lastActionResult.ok) {
|
|
46
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, lastActionResult.error);
|
|
47
|
+
}
|
|
48
|
+
if (lastActionResult.terminal === true) {
|
|
49
|
+
return await this.done(runId, workflow.name, mode, current.id, transitionsTaken);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// 3. Stop when the node is terminal or no outgoing edge exists.
|
|
53
|
+
const outbound = workflow.edges.filter((edge) => edge.from === current?.id);
|
|
54
|
+
if (terminal.has(current.id) || outbound.length === 0) {
|
|
55
|
+
return await this.done(runId, workflow.name, mode, current.id, transitionsTaken);
|
|
56
|
+
}
|
|
57
|
+
// 4. Evaluate edge conditions in declaration order and pick the first passing edge.
|
|
58
|
+
const edge = await firstPassingEdge(outbound, this.options.host, {
|
|
59
|
+
runId,
|
|
60
|
+
current: current.id,
|
|
61
|
+
vars,
|
|
62
|
+
lastActionResult,
|
|
63
|
+
});
|
|
64
|
+
if (edge === undefined) {
|
|
65
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, 'no-passing-edge');
|
|
66
|
+
}
|
|
67
|
+
// 5. Persist the edge transition.
|
|
68
|
+
transitionsTaken += 1;
|
|
69
|
+
await this.options.persistence.saveTransition(runId, current.id, edge.to, edge.condition?.kind ?? null);
|
|
70
|
+
// 6. Enforce the iteration bound after taking the transition.
|
|
71
|
+
if (transitionsTaken > iterationBound) {
|
|
72
|
+
return await this.fail(runId, workflow.name, mode, current.id, transitionsTaken, 'iteration-bound-exceeded');
|
|
73
|
+
}
|
|
74
|
+
// 7. Move to the target node and repeat.
|
|
75
|
+
const nextNode = nodes.get(edge.to);
|
|
76
|
+
if (nextNode === undefined)
|
|
77
|
+
throw new Error(`Edge target "${edge.to}" is not declared`);
|
|
78
|
+
current = nextNode;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async done(runId, workflowName, mode, finalState, transitionsTaken) {
|
|
82
|
+
await this.options.persistence.savePhase(runId, finalState, 'done');
|
|
83
|
+
await this.options.persistence.finalizeRun(runId, 'done', new Date().toISOString());
|
|
84
|
+
return { runId, workflowName, mode, status: 'done', finalState, transitionsTaken };
|
|
85
|
+
}
|
|
86
|
+
async fail(runId, workflowName, mode, finalState, transitionsTaken, reason = 'failed') {
|
|
87
|
+
await this.options.persistence.savePhase(runId, finalState, 'failed');
|
|
88
|
+
await this.options.persistence.finalizeRun(runId, 'failed', new Date().toISOString());
|
|
89
|
+
return { runId, workflowName, mode, status: 'failed', finalState, transitionsTaken, reason };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async function firstPassingEdge(edges, host, context) {
|
|
93
|
+
for (const edge of edges) {
|
|
94
|
+
if (edge.condition === undefined)
|
|
95
|
+
return edge;
|
|
96
|
+
if (await host.evaluateGuard(edge.condition.kind, edge.condition.options ?? {}, context))
|
|
97
|
+
return edge;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
function allowedEnv(names, source) {
|
|
102
|
+
return Object.fromEntries(names.flatMap((name) => (source[name] === undefined ? [] : [[name, source[name]]])));
|
|
103
|
+
}
|
|
104
|
+
function runRecord(runId, workflowName, mode, startedAt, metadata) {
|
|
105
|
+
return {
|
|
106
|
+
id: runId,
|
|
107
|
+
workflow_name: workflowName,
|
|
108
|
+
mode,
|
|
109
|
+
status: 'running',
|
|
110
|
+
started_at: startedAt,
|
|
111
|
+
completed_at: null,
|
|
112
|
+
metadata_json: JSON.stringify(metadata ?? {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/** Workflow execution status persisted for runs and phases. */
|
|
2
|
+
export type WorkflowStatus = 'running' | 'done' | 'failed';
|
|
3
|
+
/** Runtime variables and user variables available to workflow definitions. */
|
|
4
|
+
export type Vars = Record<string, string>;
|
|
5
|
+
/** Environment allowlist carried by a workflow definition. */
|
|
6
|
+
export interface Env {
|
|
7
|
+
readonly allow?: readonly string[];
|
|
8
|
+
}
|
|
9
|
+
/** Workflow action definition executed by a host action runner. */
|
|
10
|
+
export interface ActionDef {
|
|
11
|
+
readonly kind: string;
|
|
12
|
+
readonly options?: Record<string, unknown>;
|
|
13
|
+
}
|
|
14
|
+
/** Guard predicate definition used by state-machine transitions and transition-flow edges. */
|
|
15
|
+
export interface GuardDef {
|
|
16
|
+
readonly kind: string;
|
|
17
|
+
readonly options?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/** One state in a state-machine workflow. */
|
|
20
|
+
export interface StateDef {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly onEnter?: readonly ActionDef[];
|
|
23
|
+
readonly onExit?: readonly ActionDef[];
|
|
24
|
+
}
|
|
25
|
+
/** One transition in a state-machine workflow. */
|
|
26
|
+
export interface TransitionDef {
|
|
27
|
+
readonly from: string;
|
|
28
|
+
readonly to: string;
|
|
29
|
+
readonly trigger?: string;
|
|
30
|
+
readonly guard?: GuardDef;
|
|
31
|
+
}
|
|
32
|
+
/** State-machine workflow definition. */
|
|
33
|
+
export interface StateMachineWorkflowDef {
|
|
34
|
+
readonly kind?: 'state-machine';
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly initialState: string;
|
|
37
|
+
readonly terminalStates?: readonly string[];
|
|
38
|
+
readonly iterationBound?: number;
|
|
39
|
+
readonly vars?: Vars;
|
|
40
|
+
readonly env?: Env;
|
|
41
|
+
readonly states: readonly StateDef[];
|
|
42
|
+
readonly transitions: readonly TransitionDef[];
|
|
43
|
+
}
|
|
44
|
+
/** Transition-flow node definition. */
|
|
45
|
+
export interface FlowNodeDef {
|
|
46
|
+
readonly id: string;
|
|
47
|
+
readonly type?: 'action' | 'gate' | 'parallel' | 'decision';
|
|
48
|
+
readonly action?: ActionDef;
|
|
49
|
+
}
|
|
50
|
+
/** Transition-flow edge definition. */
|
|
51
|
+
export interface FlowEdgeDef {
|
|
52
|
+
readonly from: string;
|
|
53
|
+
readonly to: string;
|
|
54
|
+
readonly condition?: GuardDef;
|
|
55
|
+
}
|
|
56
|
+
/** Transition-flow workflow definition. */
|
|
57
|
+
export interface TransitionFlowWorkflowDef {
|
|
58
|
+
readonly kind: 'transition-flow';
|
|
59
|
+
readonly name: string;
|
|
60
|
+
readonly initialNode: string;
|
|
61
|
+
readonly terminalNodes?: readonly string[];
|
|
62
|
+
readonly iterationBound?: number;
|
|
63
|
+
readonly vars?: Vars;
|
|
64
|
+
readonly env?: Env;
|
|
65
|
+
readonly nodes: readonly FlowNodeDef[];
|
|
66
|
+
readonly edges: readonly FlowEdgeDef[];
|
|
67
|
+
}
|
|
68
|
+
/** Discriminated workflow definition union. */
|
|
69
|
+
export type WorkflowDef = StateMachineWorkflowDef | TransitionFlowWorkflowDef;
|
|
70
|
+
/** Action execution context passed to action runners. */
|
|
71
|
+
export interface ActionRunContext {
|
|
72
|
+
readonly runId: string;
|
|
73
|
+
readonly workdir?: string;
|
|
74
|
+
readonly stateOrNodeId: string;
|
|
75
|
+
readonly vars: Vars;
|
|
76
|
+
readonly env: Record<string, string>;
|
|
77
|
+
readonly metadata?: Record<string, unknown>;
|
|
78
|
+
}
|
|
79
|
+
/** Result of a single action execution. */
|
|
80
|
+
export interface ActionResult {
|
|
81
|
+
readonly ok: boolean;
|
|
82
|
+
readonly data?: Record<string, unknown>;
|
|
83
|
+
readonly error?: string;
|
|
84
|
+
readonly terminal?: boolean;
|
|
85
|
+
}
|
|
86
|
+
/** Action runner implementation registered in the workflow host. */
|
|
87
|
+
export interface ActionRunner {
|
|
88
|
+
readonly kind: string;
|
|
89
|
+
execute(options: Record<string, unknown>, context: ActionRunContext): Promise<ActionResult>;
|
|
90
|
+
}
|
|
91
|
+
/** Guard evaluation context. */
|
|
92
|
+
export interface GuardContext {
|
|
93
|
+
readonly runId: string;
|
|
94
|
+
readonly current: string;
|
|
95
|
+
readonly vars: Vars;
|
|
96
|
+
readonly lastActionResult?: ActionResult;
|
|
97
|
+
}
|
|
98
|
+
/** Guard runner implementation registered in the workflow host. */
|
|
99
|
+
export interface GuardRunner {
|
|
100
|
+
readonly kind: string;
|
|
101
|
+
evaluate(options: Record<string, unknown>, context: GuardContext): Promise<boolean>;
|
|
102
|
+
}
|
|
103
|
+
/** Input for running a workflow. */
|
|
104
|
+
export interface WorkflowRunOptions {
|
|
105
|
+
readonly runId?: string;
|
|
106
|
+
readonly workdir?: string;
|
|
107
|
+
readonly vars?: Vars;
|
|
108
|
+
readonly env?: Record<string, string | undefined>;
|
|
109
|
+
readonly metadata?: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
/** Result returned by both driver loops. */
|
|
112
|
+
export interface WorkflowRunResult {
|
|
113
|
+
readonly runId: string;
|
|
114
|
+
readonly workflowName: string;
|
|
115
|
+
readonly mode: 'state-machine' | 'transition-flow';
|
|
116
|
+
readonly status: WorkflowStatus;
|
|
117
|
+
readonly finalState: string;
|
|
118
|
+
readonly transitionsTaken: number;
|
|
119
|
+
readonly reason?: string;
|
|
120
|
+
}
|
|
121
|
+
/** Persisted workflow run record. */
|
|
122
|
+
export interface WorkflowRunRecord {
|
|
123
|
+
readonly id: string;
|
|
124
|
+
readonly workflow_name: string;
|
|
125
|
+
readonly mode: string;
|
|
126
|
+
readonly status: WorkflowStatus;
|
|
127
|
+
readonly started_at: string;
|
|
128
|
+
readonly completed_at: string | null;
|
|
129
|
+
readonly metadata_json: string;
|
|
130
|
+
}
|
|
131
|
+
/** Persistence adapter implemented by DB-backed and test stores. */
|
|
132
|
+
export interface WorkflowPersistenceAdapter {
|
|
133
|
+
createRun(record: WorkflowRunRecord): Promise<void>;
|
|
134
|
+
finalizeRun(runId: string, status: WorkflowStatus, completedAt: string): Promise<void>;
|
|
135
|
+
savePhase(runId: string, phase: string, status: WorkflowStatus): Promise<void>;
|
|
136
|
+
saveTransition(runId: string, from: string, to: string, trigger: string | null): Promise<void>;
|
|
137
|
+
saveWorkflowState(runId: string, state: string, data: Record<string, unknown>): Promise<void>;
|
|
138
|
+
loadRun(runId: string): Promise<WorkflowRunRecord | undefined>;
|
|
139
|
+
listRuns(): Promise<readonly WorkflowRunRecord[]>;
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Vars } from './types';
|
|
2
|
+
/** Runtime context used for workflow variable interpolation. */
|
|
3
|
+
export interface VariableContext {
|
|
4
|
+
readonly vars: Vars;
|
|
5
|
+
readonly env: Record<string, string | undefined>;
|
|
6
|
+
readonly builtins?: Record<string, string | number | undefined>;
|
|
7
|
+
}
|
|
8
|
+
/** Merge workflow vars with caller overrides; caller values win. */
|
|
9
|
+
export declare function mergeVars(workflowVars?: Vars, overrideVars?: Vars): Vars;
|
|
10
|
+
/** Resolve templates inside an unknown options value. */
|
|
11
|
+
export declare function resolveTemplates<T>(value: T, context: VariableContext): T;
|
|
12
|
+
/** Resolve a single template string. */
|
|
13
|
+
export declare function resolveTemplateString(value: string, context: VariableContext): string;
|
|
14
|
+
//# sourceMappingURL=variables.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"variables.d.ts","sourceRoot":"","sources":["../src/variables.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAIpC,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC,CAAC;CACnE;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,YAAY,GAAE,IAAS,EAAE,YAAY,GAAE,IAAS,GAAG,IAAI,CAEhF;AAED,yDAAyD;AACzD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,eAAe,GAAG,CAAC,CAgBzE;AAED,wCAAwC;AACxC,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,MAAM,CAmBrF"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WorkflowValidationError } from './errors.js';
|
|
2
|
+
const TEMPLATE_REF = /\$\{([^}]+)\}/g;
|
|
3
|
+
/** Merge workflow vars with caller overrides; caller values win. */
|
|
4
|
+
export function mergeVars(workflowVars = {}, overrideVars = {}) {
|
|
5
|
+
return { ...workflowVars, ...overrideVars };
|
|
6
|
+
}
|
|
7
|
+
/** Resolve templates inside an unknown options value. */
|
|
8
|
+
export function resolveTemplates(value, context) {
|
|
9
|
+
if (typeof value === 'string') {
|
|
10
|
+
return resolveTemplateString(value, context);
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return value.map((entry) => resolveTemplates(entry, context));
|
|
14
|
+
}
|
|
15
|
+
if (value !== null && typeof value === 'object') {
|
|
16
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
17
|
+
key,
|
|
18
|
+
resolveTemplates(entry, context),
|
|
19
|
+
]));
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
/** Resolve a single template string. */
|
|
24
|
+
export function resolveTemplateString(value, context) {
|
|
25
|
+
return value.replace(TEMPLATE_REF, (_match, name) => {
|
|
26
|
+
if (name.startsWith('vars.')) {
|
|
27
|
+
const key = name.slice('vars.'.length);
|
|
28
|
+
const resolved = context.vars[key];
|
|
29
|
+
if (resolved === undefined)
|
|
30
|
+
throw new WorkflowValidationError(`Workflow variable "${key}" is not defined`);
|
|
31
|
+
return resolved;
|
|
32
|
+
}
|
|
33
|
+
if (name.startsWith('env.')) {
|
|
34
|
+
const key = name.slice('env.'.length);
|
|
35
|
+
const resolved = context.env[key];
|
|
36
|
+
if (resolved === undefined)
|
|
37
|
+
throw new WorkflowValidationError(`Environment variable "${key}" is not defined`);
|
|
38
|
+
return resolved;
|
|
39
|
+
}
|
|
40
|
+
const resolved = context.builtins?.[name];
|
|
41
|
+
if (resolved === undefined)
|
|
42
|
+
throw new WorkflowValidationError(`Workflow builtin "${name}" is not defined`);
|
|
43
|
+
return String(resolved);
|
|
44
|
+
});
|
|
45
|
+
}
|