builderman 1.4.0 → 1.5.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 +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 +104 -82
- 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
|
@@ -1,56 +1,45 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
|
-
import { $TASK_INTERNAL } from "
|
|
4
|
-
import {
|
|
5
|
-
import { PipelineError } from "../pipeline-error.js";
|
|
3
|
+
import { $TASK_INTERNAL, $PIPELINE_INTERNAL } from "./constants.js";
|
|
4
|
+
import { PipelineError } from "../errors.js";
|
|
6
5
|
/**
|
|
7
6
|
* Executes a task (either a regular task or a nested pipeline).
|
|
8
7
|
*/
|
|
9
|
-
export function executeTask(task,
|
|
8
|
+
export function executeTask(task, context, callbacks) {
|
|
10
9
|
// Check if signal is aborted before starting new tasks
|
|
11
|
-
if (
|
|
12
|
-
|
|
10
|
+
if (context.signal?.aborted) {
|
|
11
|
+
callbacks.onTaskFailed(task[$TASK_INTERNAL].id, new PipelineError("Aborted", PipelineError.Aborted));
|
|
13
12
|
return;
|
|
14
13
|
}
|
|
15
|
-
const { name: taskName, [$TASK_INTERNAL]: { id
|
|
16
|
-
if (executorConfig.runningTasks.has(taskId))
|
|
17
|
-
return;
|
|
14
|
+
const { name: taskName, [$TASK_INTERNAL]: { id, pipeline }, } = task;
|
|
18
15
|
// Handle pipeline tasks
|
|
19
|
-
if (
|
|
20
|
-
executeNestedPipeline(task,
|
|
16
|
+
if (pipeline) {
|
|
17
|
+
executeNestedPipeline(task, id, taskName, pipeline, context, callbacks);
|
|
21
18
|
return;
|
|
22
19
|
}
|
|
23
20
|
// Regular task execution
|
|
24
|
-
executeRegularTask(task,
|
|
21
|
+
executeRegularTask(task, id, taskName, context, callbacks);
|
|
25
22
|
}
|
|
26
|
-
function executeNestedPipeline(task, taskId, taskName, nestedPipeline,
|
|
27
|
-
const { config
|
|
23
|
+
function executeNestedPipeline(task, taskId, taskName, nestedPipeline, context, callbacks) {
|
|
24
|
+
const { config } = context;
|
|
28
25
|
// Track nested pipeline state for skip behavior
|
|
29
26
|
let nestedSkippedCount = 0;
|
|
30
27
|
let nestedCompletedCount = 0;
|
|
31
28
|
// Get total tasks from nested pipeline
|
|
32
|
-
const
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// Mark as ready immediately (pipeline entry nodes will handle their own ready state)
|
|
29
|
+
const nestedTotalTasks = nestedPipeline[$PIPELINE_INTERNAL].graph.nodes.size;
|
|
30
|
+
const commandName = config?.command ?? (process.env.NODE_ENV === "production" ? "build" : "dev");
|
|
31
|
+
// Mark as running - nested pipeline tasks handle their own ready state
|
|
32
|
+
// We don't call onTaskReady here because dependent tasks should wait for
|
|
33
|
+
// the nested pipeline to complete, not just start
|
|
38
34
|
const startedAt = Date.now();
|
|
39
|
-
updateTaskStatus(taskId, {
|
|
35
|
+
context.updateTaskStatus(taskId, {
|
|
40
36
|
status: "running",
|
|
41
37
|
command: commandName,
|
|
42
38
|
startedAt,
|
|
43
39
|
});
|
|
44
|
-
advanceScheduler({ type: "ready", taskId });
|
|
45
40
|
config?.onTaskBegin?.(taskName);
|
|
46
41
|
// Create an abort controller to stop the nested pipeline if needed
|
|
47
42
|
let pipelineStopped = false;
|
|
48
|
-
const stopPipeline = () => {
|
|
49
|
-
pipelineStopped = true;
|
|
50
|
-
// The nested pipeline will continue running, but we've marked it as stopped
|
|
51
|
-
// In a more sophisticated implementation, we could propagate stop signals
|
|
52
|
-
};
|
|
53
|
-
runningPipelines.set(taskId, { stop: stopPipeline });
|
|
54
43
|
// Merge environment variables: pipeline.env -> task.env (from pipeline.toTask config)
|
|
55
44
|
const taskEnv = task[$TASK_INTERNAL].env;
|
|
56
45
|
const pipelineEnv = config?.env ?? {};
|
|
@@ -59,12 +48,14 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, executorC
|
|
|
59
48
|
...taskEnv,
|
|
60
49
|
};
|
|
61
50
|
// Run the nested pipeline with signal propagation
|
|
51
|
+
// Pass the command from parent pipeline to nested pipeline
|
|
52
|
+
// If undefined, nested pipeline will use its own default (build/dev based on NODE_ENV)
|
|
62
53
|
nestedPipeline
|
|
63
54
|
.run({
|
|
64
|
-
spawn:
|
|
65
|
-
command: config?.command,
|
|
55
|
+
spawn: context.spawn,
|
|
56
|
+
command: config?.command, // Pass command to nested pipeline
|
|
66
57
|
strict: config?.strict,
|
|
67
|
-
signal:
|
|
58
|
+
signal: context.signal, // Pass signal to nested pipeline
|
|
68
59
|
env: mergedEnv, // Pass merged env to nested pipeline
|
|
69
60
|
onTaskBegin: (nestedTaskName) => {
|
|
70
61
|
if (pipelineStopped)
|
|
@@ -87,17 +78,16 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, executorC
|
|
|
87
78
|
.then((result) => {
|
|
88
79
|
if (pipelineStopped)
|
|
89
80
|
return;
|
|
90
|
-
runningPipelines.delete(taskId);
|
|
91
81
|
if (!result.ok) {
|
|
92
82
|
// Nested pipeline failed
|
|
93
83
|
const finishedAt = Date.now();
|
|
94
|
-
updateTaskStatus(taskId, {
|
|
84
|
+
context.updateTaskStatus(taskId, {
|
|
95
85
|
status: "failed",
|
|
96
86
|
finishedAt,
|
|
97
87
|
durationMs: finishedAt - startedAt,
|
|
98
88
|
error: result.error,
|
|
99
89
|
});
|
|
100
|
-
|
|
90
|
+
callbacks.onTaskFailed(taskId, result.error);
|
|
101
91
|
return;
|
|
102
92
|
}
|
|
103
93
|
// Determine nested pipeline result based on skip behavior:
|
|
@@ -107,33 +97,34 @@ function executeNestedPipeline(task, taskId, taskName, nestedPipeline, executorC
|
|
|
107
97
|
if (nestedSkippedCount === nestedTotalTasks && nestedTotalTasks > 0) {
|
|
108
98
|
// All tasks were skipped
|
|
109
99
|
const finishedAt = Date.now();
|
|
110
|
-
updateTaskStatus(taskId, {
|
|
100
|
+
context.updateTaskStatus(taskId, {
|
|
111
101
|
status: "skipped",
|
|
112
102
|
finishedAt,
|
|
113
103
|
durationMs: finishedAt - startedAt,
|
|
114
104
|
});
|
|
115
105
|
config?.onTaskSkipped?.(taskName, commandName);
|
|
116
106
|
setImmediate(() => {
|
|
117
|
-
|
|
107
|
+
callbacks.onTaskSkipped(taskId);
|
|
118
108
|
});
|
|
119
109
|
}
|
|
120
110
|
else {
|
|
121
111
|
// Some tasks ran (and completed successfully)
|
|
122
112
|
const finishedAt = Date.now();
|
|
123
|
-
updateTaskStatus(taskId, {
|
|
113
|
+
context.updateTaskStatus(taskId, {
|
|
124
114
|
status: "completed",
|
|
125
115
|
finishedAt,
|
|
126
116
|
durationMs: finishedAt - startedAt,
|
|
127
117
|
});
|
|
128
118
|
config?.onTaskComplete?.(taskName);
|
|
129
|
-
|
|
119
|
+
// Mark as complete - this will update dependent tasks and allow them to start
|
|
120
|
+
callbacks.onTaskComplete(taskId);
|
|
130
121
|
}
|
|
131
122
|
});
|
|
132
123
|
}
|
|
133
|
-
function executeRegularTask(task, taskId, taskName,
|
|
134
|
-
const { spawn: spawnFn, signal,
|
|
124
|
+
function executeRegularTask(task, taskId, taskName, context, callbacks) {
|
|
125
|
+
const { config, spawn: spawnFn, signal, teardownManager, timeoutManager, } = context;
|
|
135
126
|
const { allowSkip, commands, cwd, env: taskEnv } = task[$TASK_INTERNAL];
|
|
136
|
-
const commandName = config?.command ?? process.env.NODE_ENV === "production" ? "build" : "dev";
|
|
127
|
+
const commandName = config?.command ?? (process.env.NODE_ENV === "production" ? "build" : "dev");
|
|
137
128
|
const commandConfig = commands[commandName];
|
|
138
129
|
// Check if command exists
|
|
139
130
|
if (commandConfig === undefined) {
|
|
@@ -142,18 +133,18 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
142
133
|
if (strict && !allowSkip) {
|
|
143
134
|
const error = new PipelineError(`[${taskName}] No command for "${commandName}" and strict mode is enabled`, PipelineError.TaskFailed, taskName);
|
|
144
135
|
const finishedAt = Date.now();
|
|
145
|
-
updateTaskStatus(taskId, {
|
|
136
|
+
context.updateTaskStatus(taskId, {
|
|
146
137
|
status: "failed",
|
|
147
138
|
command: commandName,
|
|
148
139
|
finishedAt,
|
|
149
140
|
error,
|
|
150
141
|
});
|
|
151
|
-
|
|
142
|
+
callbacks.onTaskFailed(taskId, error);
|
|
152
143
|
return;
|
|
153
144
|
}
|
|
154
145
|
// Skip the task
|
|
155
146
|
const finishedAt = Date.now();
|
|
156
|
-
updateTaskStatus(taskId, {
|
|
147
|
+
context.updateTaskStatus(taskId, {
|
|
157
148
|
status: "skipped",
|
|
158
149
|
command: commandName,
|
|
159
150
|
finishedAt,
|
|
@@ -163,13 +154,14 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
163
154
|
// Mark as skipped - this satisfies dependencies and unblocks dependents
|
|
164
155
|
// Use setImmediate to ensure scheduler is at idle yield before receiving skip
|
|
165
156
|
setImmediate(() => {
|
|
166
|
-
|
|
157
|
+
callbacks.onTaskSkipped(taskId);
|
|
167
158
|
});
|
|
168
159
|
return;
|
|
169
160
|
}
|
|
170
161
|
let command;
|
|
171
162
|
let readyWhen;
|
|
172
163
|
let readyTimeout = Infinity;
|
|
164
|
+
let completedTimeout = Infinity;
|
|
173
165
|
let teardown;
|
|
174
166
|
let commandEnv = {};
|
|
175
167
|
if (typeof commandConfig === "string") {
|
|
@@ -179,6 +171,7 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
179
171
|
command = commandConfig.run;
|
|
180
172
|
readyWhen = commandConfig.readyWhen;
|
|
181
173
|
readyTimeout = commandConfig.readyTimeout ?? Infinity;
|
|
174
|
+
completedTimeout = commandConfig.completedTimeout ?? Infinity;
|
|
182
175
|
teardown = commandConfig.teardown;
|
|
183
176
|
commandEnv = commandConfig.env ?? {};
|
|
184
177
|
}
|
|
@@ -186,14 +179,14 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
186
179
|
if (!fs.existsSync(taskCwd)) {
|
|
187
180
|
const finishedAt = Date.now();
|
|
188
181
|
const pipelineError = new PipelineError(`[${taskName}] Working directory does not exist: ${taskCwd}`, PipelineError.InvalidTask, taskName);
|
|
189
|
-
updateTaskStatus(taskId, {
|
|
182
|
+
context.updateTaskStatus(taskId, {
|
|
190
183
|
status: "failed",
|
|
191
184
|
command: commandName,
|
|
192
185
|
finishedAt,
|
|
193
186
|
durationMs: 0,
|
|
194
187
|
error: pipelineError,
|
|
195
188
|
});
|
|
196
|
-
|
|
189
|
+
callbacks.onTaskFailed(taskId, pipelineError);
|
|
197
190
|
return;
|
|
198
191
|
}
|
|
199
192
|
const accumulatedPath = [
|
|
@@ -218,9 +211,13 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
218
211
|
shell: true,
|
|
219
212
|
env: accumulatedEnv,
|
|
220
213
|
});
|
|
221
|
-
|
|
214
|
+
// Update the running task execution with the process
|
|
215
|
+
const runningTasks = context.queueManager.getRunningTasks();
|
|
216
|
+
const execution = runningTasks.get(taskId);
|
|
217
|
+
execution.process = child;
|
|
218
|
+
context.queueManager.markTaskRunning(taskId, execution);
|
|
222
219
|
const startedAt = Date.now();
|
|
223
|
-
updateTaskStatus(taskId, {
|
|
220
|
+
context.updateTaskStatus(taskId, {
|
|
224
221
|
status: "running",
|
|
225
222
|
command: commandName,
|
|
226
223
|
startedAt,
|
|
@@ -235,26 +232,53 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
235
232
|
}
|
|
236
233
|
config?.onTaskBegin?.(taskName);
|
|
237
234
|
let didMarkReady = false;
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
// Set up timeout for readyWhen condition
|
|
245
|
-
readyTimeoutId = setTimeout(() => {
|
|
235
|
+
// For tasks without readyWhen, we wait for the process to exit successfully
|
|
236
|
+
// before allowing dependent tasks to start. This ensures dependencies are
|
|
237
|
+
// fully completed before dependents begin execution.
|
|
238
|
+
if (readyWhen && readyTimeout !== Infinity) {
|
|
239
|
+
// Set up timeout for readyWhen condition using TimeoutManager
|
|
240
|
+
timeoutManager.setReadyTimeout(taskId, readyTimeout, () => {
|
|
246
241
|
if (!didMarkReady) {
|
|
247
242
|
const finishedAt = Date.now();
|
|
248
|
-
const pipelineError = new PipelineError(`[${taskName}] Task did not become ready within ${readyTimeout}ms`, PipelineError.
|
|
249
|
-
|
|
243
|
+
const pipelineError = new PipelineError(`[${taskName}] Task did not become ready within ${readyTimeout}ms`, PipelineError.TaskReadyTimeout, taskName);
|
|
244
|
+
// Clear completion timeout if it exists
|
|
245
|
+
timeoutManager.clearCompletionTimeout(taskId);
|
|
246
|
+
// Kill the process since it didn't become ready in time
|
|
247
|
+
try {
|
|
248
|
+
child.kill("SIGTERM");
|
|
249
|
+
}
|
|
250
|
+
catch { }
|
|
251
|
+
context.updateTaskStatus(taskId, {
|
|
250
252
|
status: "failed",
|
|
251
253
|
finishedAt,
|
|
252
254
|
durationMs: finishedAt - startedAt,
|
|
253
255
|
error: pipelineError,
|
|
254
256
|
});
|
|
255
|
-
|
|
257
|
+
callbacks.onTaskFailed(taskId, pipelineError);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
// Set up timeout for task completion using TimeoutManager
|
|
262
|
+
if (completedTimeout !== Infinity) {
|
|
263
|
+
timeoutManager.setCompletionTimeout(taskId, completedTimeout, () => {
|
|
264
|
+
// Task didn't complete within the timeout
|
|
265
|
+
const finishedAt = Date.now();
|
|
266
|
+
const pipelineError = new PipelineError(`[${taskName}] Task did not complete within ${completedTimeout}ms`, PipelineError.TaskCompletedTimeout, taskName);
|
|
267
|
+
// Clear ready timeout if it exists (to prevent it from firing after we've already failed)
|
|
268
|
+
timeoutManager.clearReadyTimeout(taskId);
|
|
269
|
+
// Kill the process since it didn't complete in time
|
|
270
|
+
try {
|
|
271
|
+
child.kill("SIGTERM");
|
|
256
272
|
}
|
|
257
|
-
|
|
273
|
+
catch { }
|
|
274
|
+
context.updateTaskStatus(taskId, {
|
|
275
|
+
status: "failed",
|
|
276
|
+
finishedAt,
|
|
277
|
+
durationMs: finishedAt - startedAt,
|
|
278
|
+
error: pipelineError,
|
|
279
|
+
});
|
|
280
|
+
callbacks.onTaskFailed(taskId, pipelineError);
|
|
281
|
+
});
|
|
258
282
|
}
|
|
259
283
|
let output = "";
|
|
260
284
|
child.stdout?.on("data", (buf) => {
|
|
@@ -266,11 +290,8 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
266
290
|
output += chunk;
|
|
267
291
|
process.stdout.write(chunk);
|
|
268
292
|
if (!didMarkReady && readyWhen && readyWhen(output)) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
readyTimeoutId = null;
|
|
272
|
-
}
|
|
273
|
-
advanceScheduler({ type: "ready", taskId });
|
|
293
|
+
timeoutManager.clearReadyTimeout(taskId);
|
|
294
|
+
callbacks.onTaskReady(taskId);
|
|
274
295
|
didMarkReady = true;
|
|
275
296
|
}
|
|
276
297
|
});
|
|
@@ -281,27 +302,28 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
281
302
|
// Task failed before entering running state, so don't execute teardown
|
|
282
303
|
// Remove teardown from map since it was never actually running
|
|
283
304
|
teardownManager.unregister(taskId);
|
|
284
|
-
// Clear
|
|
285
|
-
|
|
286
|
-
clearTimeout(readyTimeoutId);
|
|
287
|
-
readyTimeoutId = null;
|
|
288
|
-
}
|
|
305
|
+
// Clear all timeouts for this task
|
|
306
|
+
timeoutManager.clearTaskTimeouts(taskId);
|
|
289
307
|
const finishedAt = Date.now();
|
|
290
308
|
const pipelineError = new PipelineError(`[${taskName}] Failed to start: ${error.message}`, PipelineError.TaskFailed, taskName);
|
|
291
|
-
updateTaskStatus(taskId, {
|
|
309
|
+
context.updateTaskStatus(taskId, {
|
|
292
310
|
status: "failed",
|
|
293
311
|
finishedAt,
|
|
294
312
|
durationMs: finishedAt - startedAt,
|
|
295
313
|
error: pipelineError,
|
|
296
314
|
});
|
|
297
|
-
|
|
315
|
+
callbacks.onTaskFailed(taskId, pipelineError);
|
|
298
316
|
});
|
|
299
317
|
child.on("exit", (code, signal) => {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if (
|
|
303
|
-
|
|
304
|
-
|
|
318
|
+
// Clear all timeouts for this task
|
|
319
|
+
timeoutManager.clearTaskTimeouts(taskId);
|
|
320
|
+
// Check if task was already marked as failed (e.g., by timeout handlers)
|
|
321
|
+
// If so, don't process the exit event to avoid conflicts
|
|
322
|
+
const currentStats = context.taskStats.get(taskId);
|
|
323
|
+
if (currentStats?.status === "failed" && currentStats?.error) {
|
|
324
|
+
// Task was already failed, likely by a timeout handler
|
|
325
|
+
// Just return to avoid duplicate error handling
|
|
326
|
+
return;
|
|
305
327
|
}
|
|
306
328
|
const finishedAt = Date.now();
|
|
307
329
|
const durationMs = finishedAt - startedAt;
|
|
@@ -309,7 +331,7 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
309
331
|
// when the pipeline completes or fails
|
|
310
332
|
if (code !== 0 || signal) {
|
|
311
333
|
const pipelineError = new PipelineError(`[${taskName}] Task failed with non-zero exit code: ${code ?? signal}`, PipelineError.TaskFailed, taskName);
|
|
312
|
-
updateTaskStatus(taskId, {
|
|
334
|
+
context.updateTaskStatus(taskId, {
|
|
313
335
|
status: "failed",
|
|
314
336
|
finishedAt,
|
|
315
337
|
durationMs,
|
|
@@ -317,10 +339,10 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
317
339
|
signal: signal ?? undefined,
|
|
318
340
|
error: pipelineError,
|
|
319
341
|
});
|
|
320
|
-
|
|
342
|
+
callbacks.onTaskFailed(taskId, pipelineError);
|
|
321
343
|
return;
|
|
322
344
|
}
|
|
323
|
-
updateTaskStatus(taskId, {
|
|
345
|
+
context.updateTaskStatus(taskId, {
|
|
324
346
|
status: "completed",
|
|
325
347
|
finishedAt,
|
|
326
348
|
durationMs,
|
|
@@ -328,6 +350,6 @@ function executeRegularTask(task, taskId, taskName, executorConfig) {
|
|
|
328
350
|
});
|
|
329
351
|
config?.onTaskComplete?.(taskName);
|
|
330
352
|
// 🔑 Notify scheduler and drain newly runnable tasks
|
|
331
|
-
|
|
353
|
+
callbacks.onTaskComplete(taskId);
|
|
332
354
|
});
|
|
333
355
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized timeout management for task execution
|
|
3
|
+
* Handles readyWhen and completion timeouts with proper cleanup
|
|
4
|
+
*/
|
|
5
|
+
export interface TimeoutManager {
|
|
6
|
+
/**
|
|
7
|
+
* Set a timeout for a task's readyWhen condition
|
|
8
|
+
*/
|
|
9
|
+
setReadyTimeout(taskId: string, timeout: number, callback: () => void): void;
|
|
10
|
+
/**
|
|
11
|
+
* Set a timeout for a task's completion
|
|
12
|
+
*/
|
|
13
|
+
setCompletionTimeout(taskId: string, timeout: number, callback: () => void): void;
|
|
14
|
+
/**
|
|
15
|
+
* Clear the ready timeout for a specific task
|
|
16
|
+
*/
|
|
17
|
+
clearReadyTimeout(taskId: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Clear the completion timeout for a specific task
|
|
20
|
+
*/
|
|
21
|
+
clearCompletionTimeout(taskId: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* Clear all timeouts for a specific task
|
|
24
|
+
*/
|
|
25
|
+
clearTaskTimeouts(taskId: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Clear all timeouts (used during cancellation)
|
|
28
|
+
*/
|
|
29
|
+
clearAllTimeouts(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a timeout manager for task execution
|
|
33
|
+
*/
|
|
34
|
+
export declare function createTimeoutManager(): TimeoutManager;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a timeout manager for task execution
|
|
3
|
+
*/
|
|
4
|
+
export function createTimeoutManager() {
|
|
5
|
+
const readyTimeouts = new Map();
|
|
6
|
+
const completionTimeouts = new Map();
|
|
7
|
+
return {
|
|
8
|
+
/**
|
|
9
|
+
* Set a timeout for a task's readyWhen condition
|
|
10
|
+
*/
|
|
11
|
+
setReadyTimeout(taskId, timeout, callback) {
|
|
12
|
+
// Clear any existing ready timeout for this task
|
|
13
|
+
const timeoutId = readyTimeouts.get(taskId);
|
|
14
|
+
if (timeoutId) {
|
|
15
|
+
clearTimeout(timeoutId);
|
|
16
|
+
readyTimeouts.delete(taskId);
|
|
17
|
+
}
|
|
18
|
+
const newTimeoutId = setTimeout(callback, timeout);
|
|
19
|
+
readyTimeouts.set(taskId, newTimeoutId);
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* Set a timeout for a task's completion
|
|
23
|
+
*/
|
|
24
|
+
setCompletionTimeout(taskId, timeout, callback) {
|
|
25
|
+
// Clear any existing completion timeout for this task
|
|
26
|
+
const timeoutId = completionTimeouts.get(taskId);
|
|
27
|
+
if (timeoutId) {
|
|
28
|
+
clearTimeout(timeoutId);
|
|
29
|
+
completionTimeouts.delete(taskId);
|
|
30
|
+
}
|
|
31
|
+
const newTimeoutId = setTimeout(callback, timeout);
|
|
32
|
+
completionTimeouts.set(taskId, newTimeoutId);
|
|
33
|
+
},
|
|
34
|
+
/**
|
|
35
|
+
* Clear the ready timeout for a specific task
|
|
36
|
+
*/
|
|
37
|
+
clearReadyTimeout(taskId) {
|
|
38
|
+
const timeoutId = readyTimeouts.get(taskId);
|
|
39
|
+
if (timeoutId) {
|
|
40
|
+
clearTimeout(timeoutId);
|
|
41
|
+
readyTimeouts.delete(taskId);
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
/**
|
|
45
|
+
* Clear the completion timeout for a specific task
|
|
46
|
+
*/
|
|
47
|
+
clearCompletionTimeout(taskId) {
|
|
48
|
+
const timeoutId = completionTimeouts.get(taskId);
|
|
49
|
+
if (timeoutId) {
|
|
50
|
+
clearTimeout(timeoutId);
|
|
51
|
+
completionTimeouts.delete(taskId);
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
/**
|
|
55
|
+
* Clear all timeouts for a specific task
|
|
56
|
+
*/
|
|
57
|
+
clearTaskTimeouts(taskId) {
|
|
58
|
+
const readyTimeoutId = readyTimeouts.get(taskId);
|
|
59
|
+
if (readyTimeoutId) {
|
|
60
|
+
clearTimeout(readyTimeoutId);
|
|
61
|
+
readyTimeouts.delete(taskId);
|
|
62
|
+
}
|
|
63
|
+
const completionTimeoutId = completionTimeouts.get(taskId);
|
|
64
|
+
if (completionTimeoutId) {
|
|
65
|
+
clearTimeout(completionTimeoutId);
|
|
66
|
+
completionTimeouts.delete(taskId);
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
/**
|
|
70
|
+
* Clear all timeouts (used during cancellation)
|
|
71
|
+
*/
|
|
72
|
+
clearAllTimeouts() {
|
|
73
|
+
// Clear all ready timeouts
|
|
74
|
+
for (const timeoutId of readyTimeouts.values()) {
|
|
75
|
+
clearTimeout(timeoutId);
|
|
76
|
+
}
|
|
77
|
+
readyTimeouts.clear();
|
|
78
|
+
// Clear all completion timeouts
|
|
79
|
+
for (const timeoutId of completionTimeouts.values()) {
|
|
80
|
+
clearTimeout(timeoutId);
|
|
81
|
+
}
|
|
82
|
+
completionTimeouts.clear();
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type { Task } from "
|
|
1
|
+
import type { Task } from "../types.js";
|
|
2
2
|
export declare function validateTasks(tasks?: Task[]): void;
|