builderman 1.4.0 → 1.5.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/README.md +36 -7
- package/dist/errors.d.ts +12 -0
- package/dist/{pipeline-error.js → errors.js} +12 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/constants.d.ts +2 -0
- package/dist/internal/constants.js +2 -0
- package/dist/internal/execution-context.d.ts +33 -0
- package/dist/internal/execution-context.js +30 -0
- package/dist/{graph.d.ts → internal/graph.d.ts} +1 -1
- package/dist/internal/queue-manager.d.ts +24 -0
- package/dist/internal/queue-manager.js +182 -0
- package/dist/{modules → internal}/signal-handler.js +5 -2
- package/dist/internal/task-executor.d.ts +33 -0
- package/dist/{modules → internal}/task-executor.js +111 -79
- package/dist/internal/timeout-manager.d.ts +34 -0
- package/dist/internal/timeout-manager.js +85 -0
- package/dist/{util.d.ts → internal/util.d.ts} +1 -1
- package/dist/pipeline.js +150 -95
- package/dist/task.js +14 -4
- package/dist/types.d.ts +21 -2
- package/package.json +2 -2
- package/dist/constants.d.ts +0 -1
- package/dist/constants.js +0 -1
- package/dist/modules/task-executor.d.ts +0 -25
- package/dist/pipeline-error.d.ts +0 -11
- package/dist/scheduler.d.ts +0 -20
- package/dist/scheduler.js +0 -40
- /package/dist/{graph.js → internal/graph.js} +0 -0
- /package/dist/{modules → internal}/signal-handler.d.ts +0 -0
- /package/dist/{modules → internal}/teardown-manager.d.ts +0 -0
- /package/dist/{modules → internal}/teardown-manager.js +0 -0
- /package/dist/{util.js → internal/util.js} +0 -0
package/README.md
CHANGED
|
@@ -19,7 +19,8 @@ It is designed for monorepos, long-running development processes, and CI/CD pipe
|
|
|
19
19
|
> - [Environment Variables](#environment-variables)
|
|
20
20
|
> - [Dependencies](#dependencies)
|
|
21
21
|
> - [Pipelines](#pipelines)
|
|
22
|
-
>
|
|
22
|
+
> - [Concurrency Control](#concurrency-control)
|
|
23
|
+
> - [Pipeline Composition](#pipeline-composition)
|
|
23
24
|
> - [Error Handling Guarantees](#error-handling-guarantees)
|
|
24
25
|
> - [Cancellation](#cancellation)
|
|
25
26
|
> - [Teardown](#teardown)
|
|
@@ -256,6 +257,31 @@ const result = await pipeline([libTask, consumerTask]).run({
|
|
|
256
257
|
})
|
|
257
258
|
```
|
|
258
259
|
|
|
260
|
+
#### Concurrency Control
|
|
261
|
+
|
|
262
|
+
By default, pipelines run as many tasks concurrently as possible (limited only by dependencies). You can limit concurrent execution using `maxConcurrency`:
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
const result = await pipeline([task1, task2, task3, task4, task5]).run({
|
|
266
|
+
maxConcurrency: 2, // At most 2 tasks will run simultaneously
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
When `maxConcurrency` is set:
|
|
271
|
+
|
|
272
|
+
- Tasks that are ready to run (dependencies satisfied) will start up to the limit
|
|
273
|
+
- As tasks complete, new ready tasks will start to maintain the concurrency limit
|
|
274
|
+
- Dependencies are still respected — a task won't start until its dependencies complete
|
|
275
|
+
|
|
276
|
+
This is useful for:
|
|
277
|
+
|
|
278
|
+
- Limiting resource usage (CPU, memory, network)
|
|
279
|
+
- Controlling database connection pools
|
|
280
|
+
- Managing API rate limits
|
|
281
|
+
- Reducing system load in CI environments
|
|
282
|
+
|
|
283
|
+
If `maxConcurrency` is not specified, there is no limit (tasks run concurrently as dependencies allow).
|
|
284
|
+
|
|
259
285
|
---
|
|
260
286
|
|
|
261
287
|
### Pipeline Composition
|
|
@@ -306,16 +332,19 @@ if (!result.ok) {
|
|
|
306
332
|
console.error("Pipeline was cancelled")
|
|
307
333
|
break
|
|
308
334
|
case PipelineError.TaskFailed:
|
|
309
|
-
console.error(
|
|
335
|
+
console.error("Task failed:", result.error.message)
|
|
336
|
+
break
|
|
337
|
+
case PipelineError.TaskReadyTimeout:
|
|
338
|
+
console.error("Task was not ready in time:", result.error.message)
|
|
339
|
+
break
|
|
340
|
+
case PipelineError.TaskCompletedTimeout:
|
|
341
|
+
console.error("Task did not complete in time:", result.error.message)
|
|
310
342
|
break
|
|
311
343
|
case PipelineError.ProcessTerminated:
|
|
312
|
-
console.error("Process
|
|
344
|
+
console.error("Process terminated:", result.error.message)
|
|
313
345
|
break
|
|
314
346
|
case PipelineError.InvalidTask:
|
|
315
|
-
console.error(
|
|
316
|
-
break
|
|
317
|
-
case PipelineError.InvalidSignal:
|
|
318
|
-
console.error("Invalid abort signal")
|
|
347
|
+
console.error("Invalid task configuration:", result.error.message)
|
|
319
348
|
break
|
|
320
349
|
}
|
|
321
350
|
}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type PipelineErrorCode = typeof PipelineError.Aborted | typeof PipelineError.ProcessTerminated | typeof PipelineError.TaskFailed | typeof PipelineError.TaskCompletedTimeout | typeof PipelineError.TaskReadyTimeout | typeof PipelineError.InvalidTask;
|
|
2
|
+
export declare class PipelineError extends Error {
|
|
3
|
+
readonly code: PipelineErrorCode;
|
|
4
|
+
readonly taskName?: string;
|
|
5
|
+
constructor(message: string, code: PipelineErrorCode, taskName?: string);
|
|
6
|
+
static Aborted: "aborted";
|
|
7
|
+
static ProcessTerminated: "process-terminated";
|
|
8
|
+
static TaskFailed: "task-failed";
|
|
9
|
+
static TaskReadyTimeout: "task-ready-timeout";
|
|
10
|
+
static TaskCompletedTimeout: "task-completed-timeout";
|
|
11
|
+
static InvalidTask: "invalid-task";
|
|
12
|
+
}
|
|
@@ -22,29 +22,35 @@ Object.defineProperty(PipelineError, "Aborted", {
|
|
|
22
22
|
enumerable: true,
|
|
23
23
|
configurable: true,
|
|
24
24
|
writable: true,
|
|
25
|
-
value:
|
|
25
|
+
value: "aborted"
|
|
26
26
|
});
|
|
27
27
|
Object.defineProperty(PipelineError, "ProcessTerminated", {
|
|
28
28
|
enumerable: true,
|
|
29
29
|
configurable: true,
|
|
30
30
|
writable: true,
|
|
31
|
-
value:
|
|
31
|
+
value: "process-terminated"
|
|
32
32
|
});
|
|
33
33
|
Object.defineProperty(PipelineError, "TaskFailed", {
|
|
34
34
|
enumerable: true,
|
|
35
35
|
configurable: true,
|
|
36
36
|
writable: true,
|
|
37
|
-
value:
|
|
37
|
+
value: "task-failed"
|
|
38
38
|
});
|
|
39
|
-
Object.defineProperty(PipelineError, "
|
|
39
|
+
Object.defineProperty(PipelineError, "TaskReadyTimeout", {
|
|
40
40
|
enumerable: true,
|
|
41
41
|
configurable: true,
|
|
42
42
|
writable: true,
|
|
43
|
-
value:
|
|
43
|
+
value: "task-ready-timeout"
|
|
44
|
+
});
|
|
45
|
+
Object.defineProperty(PipelineError, "TaskCompletedTimeout", {
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
writable: true,
|
|
49
|
+
value: "task-completed-timeout"
|
|
44
50
|
});
|
|
45
51
|
Object.defineProperty(PipelineError, "InvalidTask", {
|
|
46
52
|
enumerable: true,
|
|
47
53
|
configurable: true,
|
|
48
54
|
writable: true,
|
|
49
|
-
value:
|
|
55
|
+
value: "invalid-task"
|
|
50
56
|
});
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { task } from "./task.js";
|
|
2
2
|
export { pipeline } from "./pipeline.js";
|
|
3
|
-
export { PipelineError, type PipelineErrorCode } from "./
|
|
3
|
+
export { PipelineError, type PipelineErrorCode } from "./errors.js";
|
|
4
4
|
export type { Task, Pipeline, TaskConfig, Command, CommandConfig, Commands, PipelineRunConfig, PipelineTaskConfig, RunResult, PipelineStats, TaskStats, TaskStatus, } from "./types.js";
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PipelineRunConfig, TaskStats } from "../types.js";
|
|
2
|
+
import type { TeardownManager } from "./teardown-manager.js";
|
|
3
|
+
import type { TimeoutManager } from "./timeout-manager.js";
|
|
4
|
+
import type { QueueManager } from "./queue-manager.js";
|
|
5
|
+
/**
|
|
6
|
+
* Represents a task execution in progress
|
|
7
|
+
*/
|
|
8
|
+
export interface TaskExecution {
|
|
9
|
+
taskId: string;
|
|
10
|
+
taskName: string;
|
|
11
|
+
process?: import("node:child_process").ChildProcess;
|
|
12
|
+
startedAt: number;
|
|
13
|
+
readyAt?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Centralized execution context that replaces scattered configuration parameters
|
|
17
|
+
* and provides unified access to execution state and helper methods
|
|
18
|
+
*/
|
|
19
|
+
export interface ExecutionContext {
|
|
20
|
+
config: PipelineRunConfig;
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
spawn: typeof import("node:child_process").spawn;
|
|
23
|
+
teardownManager: TeardownManager;
|
|
24
|
+
timeoutManager: TimeoutManager;
|
|
25
|
+
queueManager: QueueManager;
|
|
26
|
+
taskStats: Map<string, TaskStats>;
|
|
27
|
+
updateTaskStatus: (taskId: string, updates: Partial<TaskStats>) => void;
|
|
28
|
+
isAborted: () => boolean;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates an execution context for pipeline execution
|
|
32
|
+
*/
|
|
33
|
+
export declare function createExecutionContext(config: PipelineRunConfig, teardownManager: TeardownManager, timeoutManager: TimeoutManager, queueManager: QueueManager, taskStats: Map<string, TaskStats>): ExecutionContext;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
/**
|
|
3
|
+
* Creates an execution context for pipeline execution
|
|
4
|
+
*/
|
|
5
|
+
export function createExecutionContext(config, teardownManager, timeoutManager, queueManager, taskStats) {
|
|
6
|
+
// Use dynamic import only if spawn is not provided
|
|
7
|
+
const spawnFn = config.spawn ?? spawn;
|
|
8
|
+
return {
|
|
9
|
+
config,
|
|
10
|
+
signal: config.signal,
|
|
11
|
+
spawn: spawnFn,
|
|
12
|
+
teardownManager,
|
|
13
|
+
timeoutManager,
|
|
14
|
+
queueManager,
|
|
15
|
+
taskStats,
|
|
16
|
+
updateTaskStatus(taskId, updates) {
|
|
17
|
+
const currentStats = taskStats.get(taskId);
|
|
18
|
+
const updatedStats = { ...currentStats, ...updates };
|
|
19
|
+
// Calculate duration if both start and finish times are available
|
|
20
|
+
if (updatedStats.startedAt && updatedStats.finishedAt) {
|
|
21
|
+
updatedStats.durationMs =
|
|
22
|
+
updatedStats.finishedAt - updatedStats.startedAt;
|
|
23
|
+
}
|
|
24
|
+
taskStats.set(taskId, updatedStats);
|
|
25
|
+
},
|
|
26
|
+
isAborted() {
|
|
27
|
+
return config.signal?.aborted ?? false;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { TaskGraph, Task } from "
|
|
1
|
+
import type { TaskGraph, Task } from "../types.js";
|
|
2
2
|
export declare function createTaskGraph(tasks: Task[]): TaskGraph;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { TaskGraph } from "../types.js";
|
|
2
|
+
import type { TaskExecution } from "./execution-context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Queue manager interface returned by createQueueManager
|
|
5
|
+
*/
|
|
6
|
+
export interface QueueManager {
|
|
7
|
+
getNextReadyTask(): string | null;
|
|
8
|
+
markRunningTaskReady(taskId: string): void;
|
|
9
|
+
markTaskComplete(taskId: string): void;
|
|
10
|
+
markTaskFailed(taskId: string): void;
|
|
11
|
+
markTaskSkipped(taskId: string): void;
|
|
12
|
+
markTaskRunning(taskId: string, execution: TaskExecution): void;
|
|
13
|
+
canExecuteMore(): boolean;
|
|
14
|
+
isComplete(): boolean;
|
|
15
|
+
hasFailed(): boolean;
|
|
16
|
+
getRunningTasks(): Map<string, TaskExecution>;
|
|
17
|
+
clearQueues(): void;
|
|
18
|
+
abortAllRunningTasks(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Creates a queue manager that replaces the generator-based scheduler
|
|
22
|
+
* with explicit queue-based execution state management
|
|
23
|
+
*/
|
|
24
|
+
export declare function createQueueManager(graph: TaskGraph, maxConcurrency?: number): QueueManager;
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a queue manager that replaces the generator-based scheduler
|
|
3
|
+
* with explicit queue-based execution state management
|
|
4
|
+
*/
|
|
5
|
+
export function createQueueManager(graph, maxConcurrency) {
|
|
6
|
+
const readyQueue = [];
|
|
7
|
+
const waitingQueue = new Map();
|
|
8
|
+
const runningTasks = new Map();
|
|
9
|
+
const completedTasks = new Set();
|
|
10
|
+
const failedTasks = new Set();
|
|
11
|
+
const skippedTasks = new Set();
|
|
12
|
+
const maxConcurrencyLimit = maxConcurrency ?? Infinity;
|
|
13
|
+
let status = "running";
|
|
14
|
+
for (const [taskId, node] of graph.nodes) {
|
|
15
|
+
const depCount = node.dependencies.size;
|
|
16
|
+
if (depCount === 0) {
|
|
17
|
+
readyQueue.push(taskId);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
waitingQueue.set(taskId, depCount);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
const updateDependentTasks = (completedTaskId) => {
|
|
24
|
+
const node = graph.nodes.get(completedTaskId);
|
|
25
|
+
if (!node)
|
|
26
|
+
return;
|
|
27
|
+
for (const dependentId of node.dependents) {
|
|
28
|
+
const currentCount = waitingQueue.get(dependentId);
|
|
29
|
+
if (currentCount === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
const newCount = currentCount - 1;
|
|
32
|
+
if (newCount > 0) {
|
|
33
|
+
waitingQueue.set(dependentId, newCount);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
waitingQueue.delete(dependentId);
|
|
37
|
+
readyQueue.push(dependentId);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const allTasksFinished = () => {
|
|
41
|
+
const totalTasks = graph.nodes.size;
|
|
42
|
+
const finishedTasks = completedTasks.size + failedTasks.size + skippedTasks.size;
|
|
43
|
+
return finishedTasks === totalTasks;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Update execution status based on current queue state
|
|
47
|
+
*/
|
|
48
|
+
const updateExecutionStatus = () => {
|
|
49
|
+
if (status === "failed" || status === "aborted") {
|
|
50
|
+
return; // Don't change from terminal states
|
|
51
|
+
}
|
|
52
|
+
if (allTasksFinished()) {
|
|
53
|
+
status = "completed";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
return {
|
|
57
|
+
/**
|
|
58
|
+
* Get the next ready task for execution, respecting concurrency limits
|
|
59
|
+
* Returns null if pipeline has failed or been aborted
|
|
60
|
+
*/
|
|
61
|
+
getNextReadyTask() {
|
|
62
|
+
// Don't return tasks if pipeline has failed or been aborted
|
|
63
|
+
if (status === "failed" || status === "aborted") {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (readyQueue.length === 0)
|
|
67
|
+
return null;
|
|
68
|
+
if (runningTasks.size >= maxConcurrencyLimit)
|
|
69
|
+
return null;
|
|
70
|
+
// Additional check: if we have failed tasks, don't process new ones
|
|
71
|
+
if (failedTasks.size > 0) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
const taskId = readyQueue.shift() ?? null;
|
|
75
|
+
if (taskId) {
|
|
76
|
+
// Final check before returning - prevent race conditions
|
|
77
|
+
// Check failed tasks to prevent race conditions (status check already done above)
|
|
78
|
+
if (failedTasks.size > 0) {
|
|
79
|
+
// Put it back if we detected failure
|
|
80
|
+
readyQueue.unshift(taskId);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return taskId;
|
|
85
|
+
},
|
|
86
|
+
/**
|
|
87
|
+
* Mark a running task as ready (via readyWhen) and update dependent tasks
|
|
88
|
+
*/
|
|
89
|
+
markRunningTaskReady(taskId) {
|
|
90
|
+
if (runningTasks.has(taskId)) {
|
|
91
|
+
updateDependentTasks(taskId);
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
/**
|
|
95
|
+
* Mark a task as complete and update dependent tasks
|
|
96
|
+
*/
|
|
97
|
+
markTaskComplete(taskId) {
|
|
98
|
+
runningTasks.delete(taskId);
|
|
99
|
+
completedTasks.add(taskId);
|
|
100
|
+
updateDependentTasks(taskId);
|
|
101
|
+
updateExecutionStatus();
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* Mark a task as failed
|
|
105
|
+
* When a task fails, we clear the ready queue immediately to prevent dependent tasks from starting.
|
|
106
|
+
* Note: This clears only the ready queue. The full cleanup (including waiting queue) happens
|
|
107
|
+
* in failPipeline via clearQueues(). This immediate ready queue clearing is critical to prevent
|
|
108
|
+
* race conditions where dependent tasks might be in the ready queue when a dependency fails.
|
|
109
|
+
* Failed tasks don't update dependents as they block the pipeline.
|
|
110
|
+
*/
|
|
111
|
+
markTaskFailed(taskId) {
|
|
112
|
+
runningTasks.delete(taskId);
|
|
113
|
+
failedTasks.add(taskId);
|
|
114
|
+
status = "failed";
|
|
115
|
+
// Clear ready queue immediately to prevent any dependent tasks from starting
|
|
116
|
+
// This is critical to prevent race conditions where dependent tasks might
|
|
117
|
+
// be in the ready queue when a dependency fails
|
|
118
|
+
readyQueue.length = 0;
|
|
119
|
+
},
|
|
120
|
+
/**
|
|
121
|
+
* Mark a task as skipped and update dependent tasks
|
|
122
|
+
*/
|
|
123
|
+
markTaskSkipped(taskId) {
|
|
124
|
+
runningTasks.delete(taskId);
|
|
125
|
+
skippedTasks.add(taskId);
|
|
126
|
+
updateDependentTasks(taskId);
|
|
127
|
+
updateExecutionStatus();
|
|
128
|
+
},
|
|
129
|
+
/**
|
|
130
|
+
* Mark a task as running
|
|
131
|
+
*/
|
|
132
|
+
markTaskRunning(taskId, execution) {
|
|
133
|
+
runningTasks.set(taskId, execution);
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* Check if there are more tasks that can be executed
|
|
137
|
+
*/
|
|
138
|
+
canExecuteMore() {
|
|
139
|
+
return readyQueue.length > 0 && runningTasks.size < maxConcurrencyLimit;
|
|
140
|
+
},
|
|
141
|
+
/**
|
|
142
|
+
* Check if all tasks are complete (either completed, failed, or skipped)
|
|
143
|
+
*/
|
|
144
|
+
isComplete: allTasksFinished,
|
|
145
|
+
/**
|
|
146
|
+
* Check if any tasks have failed
|
|
147
|
+
*/
|
|
148
|
+
hasFailed() {
|
|
149
|
+
return failedTasks.size > 0;
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* Get current running tasks
|
|
153
|
+
*/
|
|
154
|
+
getRunningTasks() {
|
|
155
|
+
return new Map(runningTasks);
|
|
156
|
+
},
|
|
157
|
+
/**
|
|
158
|
+
* Clear all queues (used during cancellation)
|
|
159
|
+
* This prevents any pending tasks from starting, but preserves their status
|
|
160
|
+
* as "pending" in the task stats (they are not marked as aborted)
|
|
161
|
+
*/
|
|
162
|
+
clearQueues() {
|
|
163
|
+
readyQueue.length = 0;
|
|
164
|
+
waitingQueue.clear();
|
|
165
|
+
// Keep running tasks for cleanup, but mark them for termination
|
|
166
|
+
// Note: Tasks in waitingQueue remain with "pending" status - they are not
|
|
167
|
+
// moved to failed/aborted state since they never started
|
|
168
|
+
},
|
|
169
|
+
/**
|
|
170
|
+
* Mark all running tasks as aborted and clear them from running tasks
|
|
171
|
+
* Used during cancellation to ensure proper state consistency
|
|
172
|
+
*/
|
|
173
|
+
abortAllRunningTasks() {
|
|
174
|
+
// Move all running tasks to failed state
|
|
175
|
+
for (const taskId of runningTasks.keys()) {
|
|
176
|
+
failedTasks.add(taskId);
|
|
177
|
+
}
|
|
178
|
+
runningTasks.clear();
|
|
179
|
+
status = "aborted";
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
}
|
|
@@ -21,9 +21,12 @@ export function createSignalHandler({ abortSignal, onAborted, onProcessTerminate
|
|
|
21
21
|
// Handle abort signal if provided
|
|
22
22
|
let signalCleanup = null;
|
|
23
23
|
if (abortSignal) {
|
|
24
|
-
|
|
24
|
+
const handleAbort = () => {
|
|
25
|
+
onAborted();
|
|
26
|
+
};
|
|
27
|
+
abortSignal.addEventListener("abort", handleAbort);
|
|
25
28
|
signalCleanup = () => {
|
|
26
|
-
abortSignal.removeEventListener("abort",
|
|
29
|
+
abortSignal.removeEventListener("abort", handleAbort);
|
|
27
30
|
};
|
|
28
31
|
}
|
|
29
32
|
return {
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Task } from "../types.js";
|
|
2
|
+
import type { ExecutionContext } from "./execution-context.js";
|
|
3
|
+
/**
|
|
4
|
+
* Callbacks for task execution events that replace scheduler coordination.
|
|
5
|
+
* These callbacks are invoked by the task executor to notify the queue manager
|
|
6
|
+
* of task state changes, enabling queue-based execution flow.
|
|
7
|
+
*/
|
|
8
|
+
export interface TaskExecutionCallbacks {
|
|
9
|
+
/**
|
|
10
|
+
* Called when a task becomes ready (e.g., via readyWhen condition).
|
|
11
|
+
* This allows dependent tasks to start executing.
|
|
12
|
+
*/
|
|
13
|
+
onTaskReady: (taskId: string) => void;
|
|
14
|
+
/**
|
|
15
|
+
* Called when a task completes successfully.
|
|
16
|
+
* Updates dependent task dependency counts and moves ready tasks to execution queue.
|
|
17
|
+
*/
|
|
18
|
+
onTaskComplete: (taskId: string) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Called when a task fails.
|
|
21
|
+
* Triggers pipeline failure and cleanup.
|
|
22
|
+
*/
|
|
23
|
+
onTaskFailed: (taskId: string, error: Error) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Called when a task is skipped (e.g., missing command in non-strict mode).
|
|
26
|
+
* Treated similarly to completion for dependency resolution.
|
|
27
|
+
*/
|
|
28
|
+
onTaskSkipped: (taskId: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Executes a task (either a regular task or a nested pipeline).
|
|
32
|
+
*/
|
|
33
|
+
export declare function executeTask(task: Task, context: ExecutionContext, callbacks: TaskExecutionCallbacks): void;
|