@qaick/executor 0.1.0-alpha.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 +15 -0
- package/dist/executor.d.ts +30 -0
- package/dist/executor.js +556 -0
- package/dist/in-memory-action-executor.d.ts +5 -0
- package/dist/in-memory-action-executor.js +6 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/migrated-adapter.d.ts +5 -0
- package/dist/migrated-adapter.js +4 -0
- package/dist/migrated-artifact-hash.d.ts +3 -0
- package/dist/migrated-artifact-hash.js +19 -0
- package/dist/migrated-executor.d.ts +22 -0
- package/dist/migrated-executor.js +61 -0
- package/dist/migrated-project-engine.d.ts +21 -0
- package/dist/migrated-project-engine.js +118 -0
- package/dist/migrated-runner-engine.d.ts +33 -0
- package/dist/migrated-runner-engine.js +705 -0
- package/dist/migrated-types.d.ts +77 -0
- package/dist/migrated-types.js +4 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.js +1 -0
- package/package.json +36 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Stable, dependency-free content identity for the normalized execution package. */
|
|
2
|
+
export function hashExecutionPackage(pack) {
|
|
3
|
+
const serialized = stableStringify(pack);
|
|
4
|
+
let hash = 0xcbf29ce484222325n;
|
|
5
|
+
for (let index = 0; index < serialized.length; index += 1) {
|
|
6
|
+
hash ^= BigInt(serialized.charCodeAt(index));
|
|
7
|
+
hash = BigInt.asUintN(64, hash * 0x100000001b3n);
|
|
8
|
+
}
|
|
9
|
+
return `fnv1a64:${hash.toString(16).padStart(16, '0')}`;
|
|
10
|
+
}
|
|
11
|
+
function stableStringify(value) {
|
|
12
|
+
if (value === undefined)
|
|
13
|
+
return 'undefined';
|
|
14
|
+
if (value === null || typeof value !== 'object')
|
|
15
|
+
return JSON.stringify(value) ?? 'undefined';
|
|
16
|
+
if (Array.isArray(value))
|
|
17
|
+
return `[${value.map(stableStringify).join(',')}]`;
|
|
18
|
+
return `{${Object.keys(value).sort().map(key => `${JSON.stringify(key)}:${stableStringify(value[key])}`).join(',')}}`;
|
|
19
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ActionExecutor, Executor, ExecutorConfiguration, ExecutorEvent, ExecutorRequest, ExecutorResult, ExecutorRun, ControlCommand, ExecutionControlState } from './types.js';
|
|
2
|
+
export declare function createMigratedExecutor(configuration?: ExecutorConfiguration): Executor;
|
|
3
|
+
/**
|
|
4
|
+
* Compatibility facade over the extracted Runner engine. The facade owns the
|
|
5
|
+
* package-neutral Action Executor registration while the execution algorithm
|
|
6
|
+
* remains the migrated Runner implementation.
|
|
7
|
+
*/
|
|
8
|
+
export declare class MigratedExecutor implements Executor {
|
|
9
|
+
private readonly adapters;
|
|
10
|
+
private readonly engine;
|
|
11
|
+
constructor(configuration?: ExecutorConfiguration);
|
|
12
|
+
registerActionExecutor(executor: ActionExecutor): void;
|
|
13
|
+
execute(request: ExecutorRequest): Promise<ExecutorRun>;
|
|
14
|
+
cancel(requestId: string): Promise<void>;
|
|
15
|
+
control(requestId: string, command: ControlCommand): Promise<void>;
|
|
16
|
+
controlState(requestId: string): ExecutionControlState | undefined;
|
|
17
|
+
events(requestId: string): AsyncIterable<ExecutorEvent>;
|
|
18
|
+
result(requestId: string): Promise<ExecutorResult>;
|
|
19
|
+
retryAction(requestId: string, stepId: string, invocationPath?: string[]): Promise<void>;
|
|
20
|
+
retryOperation(requestId: string, stepId: string, invocationPath?: string[]): Promise<void>;
|
|
21
|
+
runFromHere(requestId: string, stepId: string, invocationPath?: string[]): Promise<ExecutorRun | undefined>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { MigratedRunnerEngine } from './migrated-runner-engine.js';
|
|
2
|
+
import { MigratedProjectRunnerEngine } from './migrated-project-engine.js';
|
|
3
|
+
export function createMigratedExecutor(configuration = {}) {
|
|
4
|
+
return new MigratedExecutor(configuration);
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Compatibility facade over the extracted Runner engine. The facade owns the
|
|
8
|
+
* package-neutral Action Executor registration while the execution algorithm
|
|
9
|
+
* remains the migrated Runner implementation.
|
|
10
|
+
*/
|
|
11
|
+
export class MigratedExecutor {
|
|
12
|
+
adapters;
|
|
13
|
+
engine;
|
|
14
|
+
constructor(configuration = {}) {
|
|
15
|
+
this.adapters = (configuration.actionExecutors ?? []).map(toActionAdapter);
|
|
16
|
+
this.engine = new MigratedProjectRunnerEngine(new MigratedRunnerEngine(this.adapters));
|
|
17
|
+
}
|
|
18
|
+
registerActionExecutor(executor) { this.adapters.push(toActionAdapter(executor)); }
|
|
19
|
+
async execute(request) {
|
|
20
|
+
const runnerRequest = request;
|
|
21
|
+
await this.engine.submit(runnerRequest);
|
|
22
|
+
return { requestId: request.requestId, result: this.result(request.requestId) };
|
|
23
|
+
}
|
|
24
|
+
async cancel(requestId) { await this.engine.cancel(requestId); }
|
|
25
|
+
async control(requestId, command) {
|
|
26
|
+
if (command.type === 'pause')
|
|
27
|
+
return this.engine.pauseExecution(requestId);
|
|
28
|
+
if (command.type === 'resume')
|
|
29
|
+
return this.engine.resumeExecution(requestId);
|
|
30
|
+
if (command.type === 'step')
|
|
31
|
+
return this.engine.stepExecution(requestId);
|
|
32
|
+
if (command.type === 'stop')
|
|
33
|
+
return this.engine.cancel(requestId);
|
|
34
|
+
if (command.type === 'stop-after')
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
controlState(requestId) { return this.engine.controlState(requestId); }
|
|
38
|
+
async *events(requestId) { yield* this.engine.events(requestId); }
|
|
39
|
+
async result(requestId) { return this.engine.result(requestId); }
|
|
40
|
+
async retryAction(requestId, stepId, invocationPath) { await this.engine.retryAction(requestId, stepId, invocationPath); }
|
|
41
|
+
async retryOperation(requestId, stepId, invocationPath) { await this.engine.retryOperation(requestId, stepId, invocationPath); }
|
|
42
|
+
async runFromHere(requestId, stepId, invocationPath) {
|
|
43
|
+
const accepted = await this.engine.runFromHere(requestId, stepId, invocationPath);
|
|
44
|
+
return accepted ? { requestId: accepted.requestId, result: this.result(accepted.requestId) } : undefined;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function toActionAdapter(executor) {
|
|
48
|
+
return {
|
|
49
|
+
supports: action => executor.supports(action),
|
|
50
|
+
execute: async (context) => {
|
|
51
|
+
const result = await executor.execute({
|
|
52
|
+
...context,
|
|
53
|
+
runtimeValues: context.runtimeValues ?? {},
|
|
54
|
+
});
|
|
55
|
+
return isActionResult(result) ? result.outputs : result;
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function isActionResult(value) {
|
|
60
|
+
return typeof value === 'object' && value !== null && 'outputs' in value && typeof value.outputs === 'object' && value.outputs !== null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { MigratedRunnerEngine } from './migrated-runner-engine.js';
|
|
2
|
+
import type { ExecutionControlState, ExecutionEvent, ExecutionResult, RunnerEngine, RunnerRequest, RunAccepted } from './migrated-types.js';
|
|
3
|
+
export declare class MigratedProjectRunnerEngine implements RunnerEngine {
|
|
4
|
+
private readonly flowEngine;
|
|
5
|
+
private readonly runs;
|
|
6
|
+
constructor(flowEngine?: MigratedRunnerEngine);
|
|
7
|
+
submit(request: RunnerRequest): Promise<RunAccepted>;
|
|
8
|
+
cancel(requestId: string): Promise<void>;
|
|
9
|
+
pauseExecution(requestId: string): Promise<void>;
|
|
10
|
+
resumeExecution(requestId: string): Promise<void>;
|
|
11
|
+
stepExecution(requestId: string): Promise<void>;
|
|
12
|
+
retryAction(requestId: string, stepId: string, invocationPath?: string[]): Promise<void>;
|
|
13
|
+
retryOperation(requestId: string, stepId: string, invocationPath?: string[]): Promise<void>;
|
|
14
|
+
runFromHere(requestId: string, stepId: string, invocationPath?: string[]): Promise<RunAccepted | undefined>;
|
|
15
|
+
controlState(requestId: string): ExecutionControlState | undefined;
|
|
16
|
+
events(requestId: string): AsyncIterable<ExecutionEvent>;
|
|
17
|
+
result(requestId: string): Promise<ExecutionResult>;
|
|
18
|
+
private executeProject;
|
|
19
|
+
private emit;
|
|
20
|
+
private finish;
|
|
21
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { MigratedRunnerEngine } from './migrated-runner-engine.js';
|
|
2
|
+
import { hashExecutionPackage } from './migrated-artifact-hash.js';
|
|
3
|
+
export class MigratedProjectRunnerEngine {
|
|
4
|
+
flowEngine;
|
|
5
|
+
runs = new Map();
|
|
6
|
+
constructor(flowEngine = new MigratedRunnerEngine()) { this.flowEngine = flowEngine; }
|
|
7
|
+
async submit(request) {
|
|
8
|
+
if (request.executionPackage.target.kind !== 'project')
|
|
9
|
+
return this.flowEngine.submit(request);
|
|
10
|
+
let resolve;
|
|
11
|
+
const run = { request, events: [], listeners: [], resolve, cancelled: false };
|
|
12
|
+
const resultPromise = new Promise(promiseResolve => { resolve = promiseResolve; run.resolve = promiseResolve; });
|
|
13
|
+
this.runs.set(request.requestId, run);
|
|
14
|
+
void this.executeProject(run, resultPromise);
|
|
15
|
+
return { requestId: request.requestId };
|
|
16
|
+
}
|
|
17
|
+
async cancel(requestId) {
|
|
18
|
+
const run = this.runs.get(requestId);
|
|
19
|
+
if (run) {
|
|
20
|
+
run.cancelled = true;
|
|
21
|
+
await this.flowEngine.cancel(run.activeRequestId ?? requestId);
|
|
22
|
+
run.listeners.splice(0).forEach(resolve => resolve());
|
|
23
|
+
}
|
|
24
|
+
else
|
|
25
|
+
await this.flowEngine.cancel(requestId);
|
|
26
|
+
}
|
|
27
|
+
async pauseExecution(requestId) { const run = this.runs.get(requestId); if (run)
|
|
28
|
+
await this.flowEngine.pauseExecution(run.activeRequestId ?? requestId);
|
|
29
|
+
else
|
|
30
|
+
await this.flowEngine.pauseExecution(requestId); }
|
|
31
|
+
async resumeExecution(requestId) { const run = this.runs.get(requestId); if (run)
|
|
32
|
+
await this.flowEngine.resumeExecution(run.activeRequestId ?? requestId);
|
|
33
|
+
else
|
|
34
|
+
await this.flowEngine.resumeExecution(requestId); }
|
|
35
|
+
async stepExecution(requestId) { const run = this.runs.get(requestId); if (run)
|
|
36
|
+
await this.flowEngine.stepExecution(run.activeRequestId ?? requestId);
|
|
37
|
+
else
|
|
38
|
+
await this.flowEngine.stepExecution(requestId); }
|
|
39
|
+
async retryAction(requestId, stepId, invocationPath) { const run = this.runs.get(requestId); if (run)
|
|
40
|
+
await this.flowEngine.retryAction(run.activeRequestId ?? requestId, stepId, invocationPath);
|
|
41
|
+
else
|
|
42
|
+
await this.flowEngine.retryAction(requestId, stepId, invocationPath); }
|
|
43
|
+
async retryOperation(requestId, stepId, invocationPath) { const run = this.runs.get(requestId); if (run)
|
|
44
|
+
await this.flowEngine.retryOperation(run.activeRequestId ?? requestId, stepId, invocationPath);
|
|
45
|
+
else
|
|
46
|
+
await this.flowEngine.retryOperation(requestId, stepId, invocationPath); }
|
|
47
|
+
async runFromHere(requestId, stepId, invocationPath) { const run = this.runs.get(requestId); return this.flowEngine.runFromHere(run?.activeRequestId ?? requestId, stepId, invocationPath); }
|
|
48
|
+
controlState(requestId) { const run = this.runs.get(requestId); return this.flowEngine.controlState(run?.activeRequestId ?? requestId); }
|
|
49
|
+
async *events(requestId) {
|
|
50
|
+
const project = this.runs.get(requestId);
|
|
51
|
+
if (!project) {
|
|
52
|
+
yield* this.flowEngine.events(requestId);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
let index = 0;
|
|
56
|
+
while (true) {
|
|
57
|
+
while (index < project.events.length)
|
|
58
|
+
yield project.events[index++];
|
|
59
|
+
if (project.result)
|
|
60
|
+
return;
|
|
61
|
+
await new Promise(resolve => project.listeners.push(resolve));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async result(requestId) {
|
|
65
|
+
const project = this.runs.get(requestId);
|
|
66
|
+
if (!project)
|
|
67
|
+
return this.flowEngine.result(requestId);
|
|
68
|
+
if (project.result)
|
|
69
|
+
return project.result;
|
|
70
|
+
return new Promise(resolve => { const previous = project.resolve; project.resolve = result => { previous(result); resolve(result); }; });
|
|
71
|
+
}
|
|
72
|
+
async executeProject(run, resultPromise) {
|
|
73
|
+
const { request } = run;
|
|
74
|
+
const startedAt = new Date().toISOString();
|
|
75
|
+
const flows = request.executionPackage.flows ?? [];
|
|
76
|
+
this.emit(run, 'run_started', 'created', 'Project run accepted.');
|
|
77
|
+
this.emit(run, 'preflight_started', 'preflighting', `Validating ${flows.length} Project Flows.`);
|
|
78
|
+
const invalidFlow = flows.length === 0 || flows.find(flow => !request.executionPackage.definitions.some(definition => definition.kind === 'flow' && definition.id === flow.id));
|
|
79
|
+
if (invalidFlow) {
|
|
80
|
+
const error = { code: 'INVALID_PROJECT', message: 'Project Flow definitions are missing or unresolved.' };
|
|
81
|
+
this.finish(run, { requestId: request.requestId, state: 'failed', target: request.executionPackage.target, environment: request.environment, startedAt, finishedAt: new Date().toISOString(), steps: [], error });
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const children = [];
|
|
85
|
+
let inputs = { ...request.inputs };
|
|
86
|
+
for (const flow of flows) {
|
|
87
|
+
if (run.cancelled) {
|
|
88
|
+
this.finish(run, { requestId: request.requestId, state: 'cancelled', target: request.executionPackage.target, environment: request.environment, startedAt, finishedAt: new Date().toISOString(), steps: [], children });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const childRequestId = `${request.requestId}:${flow.id}`;
|
|
92
|
+
run.activeRequestId = childRequestId;
|
|
93
|
+
const childPackage = { ...request.executionPackage, target: { kind: 'flow', id: flow.id, name: flow.name }, steps: flow.steps, flows: undefined, requiredInputs: flow.requiredInputs };
|
|
94
|
+
await this.flowEngine.submit({ requestId: childRequestId, executionPackage: childPackage, inputs, environment: request.environment, runtimeValues: request.runtimeValues, options: request.options, debugSession: request.debugSession });
|
|
95
|
+
for await (const event of this.flowEngine.events(childRequestId))
|
|
96
|
+
this.emit(run, event.type, event.state, `[${flow.name}] ${event.message}`, event.stepId ? { stepId: event.stepId, stepName: event.stepName } : undefined, event.parallel, event.control);
|
|
97
|
+
const childResult = await this.flowEngine.result(childRequestId);
|
|
98
|
+
children.push(childResult);
|
|
99
|
+
const lastStep = childResult.steps.at(-1);
|
|
100
|
+
if (childResult.state !== 'completed') {
|
|
101
|
+
const result = { requestId: request.requestId, state: childResult.state, target: request.executionPackage.target, environment: request.environment, startedAt, finishedAt: new Date().toISOString(), steps: [], children, error: childResult.error, issues: children.flatMap(child => child.issues ?? []) };
|
|
102
|
+
this.finish(run, result);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const outputBindings = flow.outputBindings ?? {};
|
|
106
|
+
const mappedOutputs = Object.fromEntries(Object.entries(outputBindings).flatMap(([name, reference]) => {
|
|
107
|
+
const match = /^\$outputs\.(.+)$/.exec(reference);
|
|
108
|
+
return match && lastStep?.outputs[match[1]] !== undefined ? [[name, lastStep.outputs[match[1]]]] : [];
|
|
109
|
+
}));
|
|
110
|
+
inputs = { ...inputs, ...(Object.keys(outputBindings).length ? mappedOutputs : lastStep?.outputs) };
|
|
111
|
+
}
|
|
112
|
+
const result = { requestId: request.requestId, state: 'completed', target: request.executionPackage.target, environment: request.environment, startedAt, finishedAt: new Date().toISOString(), steps: [], children, issues: children.flatMap(child => child.issues ?? []) };
|
|
113
|
+
this.finish(run, result);
|
|
114
|
+
await resultPromise;
|
|
115
|
+
}
|
|
116
|
+
emit(run, type, state, message, step, parallel, control) { run.events.push({ requestId: run.request.requestId, sequence: run.events.length + 1, type, state, stepId: step?.stepId, stepName: step?.stepName, message, timestamp: new Date().toISOString(), ...(parallel ? { parallel } : {}), ...(control ? { control } : {}) }); run.listeners.splice(0).forEach(resolve => resolve()); }
|
|
117
|
+
finish(run, result) { const terminalReason = result.terminalReason ?? (result.error?.code === 'TIMEOUT' ? 'timeout' : result.state === 'completed' ? 'completed' : result.state === 'cancelled' ? 'cancelled' : 'failed'); run.result = { ...result, artifactHash: result.artifactHash ?? hashExecutionPackage(run.request.executionPackage), terminalReason }; this.emit(run, 'run_finished', result.state, result.error?.message ?? `Project run ${result.state}.`); run.resolve(run.result); }
|
|
118
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ActionAdapter, ExecutionControlState, ExecutionEvent, ExecutionResult, RunnerDebugSession, RunnerEngine, RunnerRequest, RunAccepted } from './migrated-types.js';
|
|
2
|
+
export declare class MigratedRunnerEngine implements RunnerEngine {
|
|
3
|
+
private readonly adapters;
|
|
4
|
+
private readonly runs;
|
|
5
|
+
constructor(adapters?: ActionAdapter[]);
|
|
6
|
+
submit(request: RunnerRequest): Promise<RunAccepted>;
|
|
7
|
+
cancel(requestId: string): Promise<void>;
|
|
8
|
+
pauseExecution(requestId: string): Promise<void>;
|
|
9
|
+
resumeExecution(requestId: string): Promise<void>;
|
|
10
|
+
stepExecution(requestId: string): Promise<void>;
|
|
11
|
+
retryAction(requestId: string, stepId: string, invocationPath?: string[]): Promise<void>;
|
|
12
|
+
retryOperation(requestId: string, stepId: string, invocationPath?: string[]): Promise<void>;
|
|
13
|
+
runFromHere(requestId: string, stepId: string, invocationPath?: string[]): Promise<RunAccepted | undefined>;
|
|
14
|
+
controlState(requestId: string): ExecutionControlState | undefined;
|
|
15
|
+
debugSession(requestId: string): RunnerDebugSession | undefined;
|
|
16
|
+
events(requestId: string): AsyncIterable<ExecutionEvent>;
|
|
17
|
+
result(requestId: string): Promise<ExecutionResult>;
|
|
18
|
+
private execute;
|
|
19
|
+
private validateAll;
|
|
20
|
+
private validateBrowserWarnings;
|
|
21
|
+
private flattenSteps;
|
|
22
|
+
private flattenStepsWithParallel;
|
|
23
|
+
private executeParallelGroup;
|
|
24
|
+
private orderSteps;
|
|
25
|
+
private normalizeResults;
|
|
26
|
+
private resolveInputs;
|
|
27
|
+
private emit;
|
|
28
|
+
private finish;
|
|
29
|
+
private waitForSafeBoundary;
|
|
30
|
+
private pauseAfterStopAfter;
|
|
31
|
+
private finishCancelled;
|
|
32
|
+
private pause;
|
|
33
|
+
}
|