@qaick/executor 0.1.0-alpha.3 → 0.1.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/dist/executor.js +47 -10
- package/dist/migrated-executor.d.ts +2 -0
- package/dist/migrated-executor.js +24 -7
- package/package.json +3 -3
package/dist/executor.js
CHANGED
|
@@ -132,11 +132,18 @@ export class DefaultExecutor {
|
|
|
132
132
|
const definition = findDefinition(run.request.executionPackage.steps, stepId, invocationPath);
|
|
133
133
|
if (!located || !definition?.children?.length || definition.control || definition.kind !== 'operation')
|
|
134
134
|
return;
|
|
135
|
-
const
|
|
135
|
+
const arguments_ = resolveDeclaredInputs(definition.inputDeclarations, located.inputs);
|
|
136
|
+
if (arguments_.issues.length)
|
|
137
|
+
return;
|
|
138
|
+
const childScope = { ...run.request.inputs, ...arguments_.inputs };
|
|
139
|
+
const nested = await this.executeFlow(run, definition.children, childScope, [...(located.invocationPath ?? []).slice(0, -1), definition.id], new Date().toISOString());
|
|
140
|
+
const outputs = definition.outputBindings && nested.state === 'completed'
|
|
141
|
+
? { ...definition.staticOutputs, ...resolveBindings(definition.outputBindings, childScope, new Map(nested.steps.map(child => [child.id, child.outputs]))) }
|
|
142
|
+
: nested.steps.at(-1)?.outputs ?? {};
|
|
136
143
|
const previous = located.attempts ?? [];
|
|
137
|
-
located.attempts = [...previous, { attempt: previous.length + 1, kind: 'interactive', state: nested.state === 'completed' ? 'finished' : 'failed', startedAt: nested.startedAt, finishedAt: nested.finishedAt, inputs: located.inputs, outputs
|
|
144
|
+
located.attempts = [...previous, { attempt: previous.length + 1, kind: 'interactive', state: nested.state === 'completed' ? 'finished' : 'failed', startedAt: nested.startedAt, finishedAt: nested.finishedAt, inputs: located.inputs, outputs, durationMs: nested.steps.reduce((sum, child) => sum + child.durationMs, 0), children: nested.steps, ...(nested.error ? { error: nested.error } : {}) }];
|
|
138
145
|
located.children = nested.steps;
|
|
139
|
-
located.outputs =
|
|
146
|
+
located.outputs = outputs;
|
|
140
147
|
located.state = nested.state === 'completed' ? 'finished' : nested.state === 'cancelled' ? 'cancelled' : 'failed';
|
|
141
148
|
located.terminalReason = nested.terminalReason ?? (located.state === 'finished' ? 'completed' : 'failed');
|
|
142
149
|
located.error = nested.error;
|
|
@@ -238,7 +245,7 @@ export class DefaultExecutor {
|
|
|
238
245
|
const results = [];
|
|
239
246
|
let state = 'completed';
|
|
240
247
|
let error;
|
|
241
|
-
for (const step of orderSteps(steps)) {
|
|
248
|
+
for (const step of run.request.executionPackage.ordering === 'routine' ? steps : orderSteps(steps)) {
|
|
242
249
|
if (run.cancelled || signal.aborted)
|
|
243
250
|
throw new Error('Execution cancelled.');
|
|
244
251
|
await this.boundary(run, step, [...path, step.id], signal);
|
|
@@ -276,8 +283,17 @@ export class DefaultExecutor {
|
|
|
276
283
|
return { ...base, ...controlResult, inputs, durationMs: Date.now() - startedMs };
|
|
277
284
|
}
|
|
278
285
|
if (step.kind === 'operation' && step.children) {
|
|
279
|
-
const
|
|
280
|
-
|
|
286
|
+
const arguments_ = resolveDeclaredInputs(step.inputDeclarations, inputs);
|
|
287
|
+
if (arguments_.issues.length)
|
|
288
|
+
throw failure('BINDING_RESOLUTION_ERROR', arguments_.issues.map(issue => issue.message).join(' '));
|
|
289
|
+
const childScope = { ...scope, ...arguments_.inputs };
|
|
290
|
+
for (const declaration of step.inputDeclarations ?? [])
|
|
291
|
+
if (!Object.hasOwn(arguments_.inputs, declaration.name))
|
|
292
|
+
delete childScope[declaration.name];
|
|
293
|
+
const nested = await this.executeFlow(run, step.children, childScope, [...path, step.id], new Date().toISOString(), run.request.executionPackage.target, signal);
|
|
294
|
+
const outputs = step.outputBindings && nested.state === 'completed'
|
|
295
|
+
? { ...step.staticOutputs, ...resolveBindings(step.outputBindings, childScope, new Map(nested.steps.map(child => [child.id, child.outputs]))) }
|
|
296
|
+
: nested.steps.at(-1)?.outputs ?? {};
|
|
281
297
|
const result = { ...base, state: nested.state === 'completed' ? 'finished' : nested.state, inputs, outputs, durationMs: Date.now() - startedMs, children: nested.steps, terminalReason: nested.terminalReason ?? (nested.state === 'completed' ? 'completed' : 'failed'), error: nested.error };
|
|
282
298
|
this.emit(run, 'step_finished', result.state, `${step.name} ${result.state}.`, step);
|
|
283
299
|
return result;
|
|
@@ -304,9 +320,12 @@ export class DefaultExecutor {
|
|
|
304
320
|
if (control.condition !== undefined && !evaluateCondition(control.condition, { ...scope, ...inputs }, outputs))
|
|
305
321
|
return { state: 'skipped', outputs: {}, terminalReason: 'condition-skipped' };
|
|
306
322
|
if (control.type === 'repeat') {
|
|
307
|
-
const total = control.iterations ?? 1;
|
|
323
|
+
const total = run.request.executionPackage.ordering === 'routine' ? Math.max(1, Math.min(control.iterations ?? (control.durationMs !== undefined ? 32 : 1), 32)) : control.iterations ?? 1;
|
|
324
|
+
const loopStarted = Date.now();
|
|
308
325
|
const children = [];
|
|
309
326
|
for (let current = 1; current <= total; current++) {
|
|
327
|
+
if (run.request.executionPackage.ordering === 'routine' && control.durationMs !== undefined && Date.now() - loopStarted >= control.durationMs)
|
|
328
|
+
break;
|
|
310
329
|
const child = await this.executeFlow(run, step.children ?? [], { ...scope, ...inputs }, [...path, step.id, String(current)], new Date().toISOString(), run.request.executionPackage.target, signal);
|
|
311
330
|
children.push(...child.steps.map(item => ({ ...item, iteration: { loopId: step.id, current, total } })));
|
|
312
331
|
if (child.state !== 'completed' && control.onError !== 'continue')
|
|
@@ -316,6 +335,18 @@ export class DefaultExecutor {
|
|
|
316
335
|
}
|
|
317
336
|
return { state: 'finished', outputs: children.at(-1)?.outputs ?? {}, children, terminalReason: 'completed' };
|
|
318
337
|
}
|
|
338
|
+
if (control.type === 'branch' && run.request.executionPackage.ordering === 'routine') {
|
|
339
|
+
const children = [];
|
|
340
|
+
for (const branch of control.branches ?? []) {
|
|
341
|
+
if (!evaluateCondition(branch.condition, { ...scope, ...inputs }, outputs))
|
|
342
|
+
continue;
|
|
343
|
+
const nested = await this.executeFlow(run, branch.children, { ...scope, ...inputs }, [...path, step.id, branch.id], new Date().toISOString(), run.request.executionPackage.target, signal);
|
|
344
|
+
children.push(...nested.steps);
|
|
345
|
+
if (nested.state !== 'completed' && control.onError !== 'continue')
|
|
346
|
+
return { state: nested.state, outputs: nested.steps.at(-1)?.outputs ?? {}, children, terminalReason: nested.terminalReason, error: nested.error };
|
|
347
|
+
}
|
|
348
|
+
return { state: children.length ? 'finished' : 'skipped', outputs: children.at(-1)?.outputs ?? {}, children, terminalReason: children.length ? 'completed' : 'branch-skipped' };
|
|
349
|
+
}
|
|
319
350
|
if (control.type === 'branch') {
|
|
320
351
|
const branch = control.branches?.find(candidate => evaluateCondition(candidate.condition, { ...scope, ...inputs }, outputs));
|
|
321
352
|
if (!branch)
|
|
@@ -325,13 +356,18 @@ export class DefaultExecutor {
|
|
|
325
356
|
}
|
|
326
357
|
if (control.type === 'parallel') {
|
|
327
358
|
const branches = control.branches ?? [];
|
|
359
|
+
const join = run.request.executionPackage.ordering === 'routine' ? 'all' : control.join;
|
|
328
360
|
const selected = branches.filter(branch => evaluateCondition(branch.condition, { ...scope, ...inputs }, outputs));
|
|
329
361
|
const controllers = selected.map(() => new AbortController());
|
|
362
|
+
const abortBranches = () => controllers.forEach(controller => controller.abort());
|
|
363
|
+
signal.addEventListener('abort', abortBranches, { once: true });
|
|
364
|
+
if (signal.aborted)
|
|
365
|
+
abortBranches();
|
|
330
366
|
let winnerIndex;
|
|
331
367
|
const branchRuns = await Promise.all(selected.map(async (branch, index) => {
|
|
332
368
|
try {
|
|
333
369
|
const result = await this.executeFlow(run, branch.children, { ...scope, ...inputs }, [...path, step.id, branch.id], new Date().toISOString(), run.request.executionPackage.target, controllers[index].signal);
|
|
334
|
-
if (
|
|
370
|
+
if (join === 'first' && result.state === 'completed' && winnerIndex === undefined) {
|
|
335
371
|
winnerIndex = index;
|
|
336
372
|
controllers.forEach((controller, candidate) => { if (candidate !== index)
|
|
337
373
|
controller.abort(); });
|
|
@@ -342,12 +378,13 @@ export class DefaultExecutor {
|
|
|
342
378
|
return { branch, result: this.baseResult(run, new Date().toISOString(), controllers[index].signal.aborted ? 'cancelled' : 'failed', this.failure(cause, run, controllers[index].signal)) };
|
|
343
379
|
}
|
|
344
380
|
}));
|
|
381
|
+
signal.removeEventListener('abort', abortBranches);
|
|
345
382
|
const nested = branchRuns.map(item => item.result);
|
|
346
383
|
const children = nested.flatMap(item => item.steps);
|
|
347
|
-
const considered =
|
|
384
|
+
const considered = join === 'first' && winnerIndex !== undefined ? [branchRuns[winnerIndex]] : branchRuns;
|
|
348
385
|
const failed = considered.find(item => item.result.state !== 'completed');
|
|
349
386
|
const branchOutputs = considered.map(({ branch, result }) => ({ id: branch.id, outputs: result.steps.at(-1)?.outputs ?? {} }));
|
|
350
|
-
const mergedOutputs = mergeBranchOutputs(branchOutputs, control.mergeBindings, control.merge ?? (
|
|
387
|
+
const mergedOutputs = mergeBranchOutputs(branchOutputs, control.mergeBindings, control.merge ?? (join === 'first' ? 'first' : 'collect'));
|
|
351
388
|
return { state: failed ? invocationState(failed.result.state) : 'finished', outputs: mergedOutputs, children, terminalReason: failed?.result.terminalReason ?? 'parallel-joined', error: failed?.result.error };
|
|
352
389
|
}
|
|
353
390
|
const nested = await this.executeFlow(run, step.children ?? [], { ...scope, ...inputs }, [...path, step.id], new Date().toISOString(), run.request.executionPackage.target, signal);
|
|
@@ -6,6 +6,8 @@ export declare function createMigratedExecutor(configuration?: ExecutorConfigura
|
|
|
6
6
|
* remains the migrated Runner implementation.
|
|
7
7
|
*/
|
|
8
8
|
export declare class MigratedExecutor implements Executor {
|
|
9
|
+
private readonly routineRuns;
|
|
10
|
+
private readonly routine;
|
|
9
11
|
private readonly adapters;
|
|
10
12
|
private readonly engine;
|
|
11
13
|
constructor(configuration?: ExecutorConfiguration);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createExecutor } from './executor.js';
|
|
1
2
|
import { MigratedRunnerEngine } from './migrated-runner-engine.js';
|
|
2
3
|
import { MigratedProjectRunnerEngine } from './migrated-project-engine.js';
|
|
3
4
|
export function createMigratedExecutor(configuration = {}) {
|
|
@@ -9,20 +10,30 @@ export function createMigratedExecutor(configuration = {}) {
|
|
|
9
10
|
* remains the migrated Runner implementation.
|
|
10
11
|
*/
|
|
11
12
|
export class MigratedExecutor {
|
|
13
|
+
routineRuns = new Set();
|
|
14
|
+
routine;
|
|
12
15
|
adapters;
|
|
13
16
|
engine;
|
|
14
17
|
constructor(configuration = {}) {
|
|
18
|
+
this.routine = createExecutor(configuration);
|
|
15
19
|
this.adapters = (configuration.actionExecutors ?? []).map(toActionAdapter);
|
|
16
20
|
this.engine = new MigratedProjectRunnerEngine(new MigratedRunnerEngine(this.adapters));
|
|
17
21
|
}
|
|
18
|
-
registerActionExecutor(executor) { this.adapters.push(toActionAdapter(executor)); }
|
|
22
|
+
registerActionExecutor(executor) { this.routine.registerActionExecutor(executor); this.adapters.push(toActionAdapter(executor)); }
|
|
19
23
|
async execute(request) {
|
|
24
|
+
if (request.executionPackage.ordering === 'routine') {
|
|
25
|
+
this.routineRuns.add(request.requestId);
|
|
26
|
+
return this.routine.execute(request);
|
|
27
|
+
}
|
|
20
28
|
const runnerRequest = request;
|
|
21
29
|
await this.engine.submit(runnerRequest);
|
|
22
30
|
return { requestId: request.requestId, result: this.result(request.requestId) };
|
|
23
31
|
}
|
|
24
|
-
async cancel(requestId) {
|
|
32
|
+
async cancel(requestId) { if (this.routineRuns.has(requestId))
|
|
33
|
+
return this.routine.cancel(requestId); await this.engine.cancel(requestId); }
|
|
25
34
|
async control(requestId, command) {
|
|
35
|
+
if (this.routineRuns.has(requestId))
|
|
36
|
+
return this.routine.control(requestId, command);
|
|
26
37
|
if (command.type === 'pause')
|
|
27
38
|
return this.engine.pauseExecution(requestId);
|
|
28
39
|
if (command.type === 'resume')
|
|
@@ -34,12 +45,18 @@ export class MigratedExecutor {
|
|
|
34
45
|
if (command.type === 'stop-after')
|
|
35
46
|
return;
|
|
36
47
|
}
|
|
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); }
|
|
48
|
+
controlState(requestId) { return this.routineRuns.has(requestId) ? this.routine.controlState(requestId) : this.engine.controlState(requestId); }
|
|
49
|
+
async *events(requestId) { yield* (this.routineRuns.has(requestId) ? this.routine.events(requestId) : this.engine.events(requestId)); }
|
|
50
|
+
async result(requestId) { return (this.routineRuns.has(requestId) ? this.routine.result(requestId) : this.engine.result(requestId)); }
|
|
51
|
+
async retryAction(requestId, stepId, invocationPath) { await (this.routineRuns.has(requestId) ? this.routine.retryAction(requestId, stepId, invocationPath) : this.engine.retryAction(requestId, stepId, invocationPath)); }
|
|
52
|
+
async retryOperation(requestId, stepId, invocationPath) { await (this.routineRuns.has(requestId) ? this.routine.retryOperation(requestId, stepId, invocationPath) : this.engine.retryOperation(requestId, stepId, invocationPath)); }
|
|
42
53
|
async runFromHere(requestId, stepId, invocationPath) {
|
|
54
|
+
if (this.routineRuns.has(requestId)) {
|
|
55
|
+
const targeted = await this.routine.runFromHere(requestId, stepId, invocationPath);
|
|
56
|
+
if (targeted)
|
|
57
|
+
this.routineRuns.add(targeted.requestId);
|
|
58
|
+
return targeted;
|
|
59
|
+
}
|
|
43
60
|
const accepted = await this.engine.runFromHere(requestId, stepId, invocationPath);
|
|
44
61
|
return accepted ? { requestId: accepted.requestId, result: this.result(accepted.requestId) } : undefined;
|
|
45
62
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qaick/executor",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Host-neutral QAick action execution primitives.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
},
|
|
12
12
|
"publishConfig": {
|
|
13
13
|
"access": "public",
|
|
14
|
-
"tag": "
|
|
14
|
+
"tag": "latest"
|
|
15
15
|
},
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@qaick/contract": "0.2.
|
|
37
|
+
"@qaick/contract": "0.2.13"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@types/node": "^24.13.3",
|