@calmo/task-runner 2.0.0 → 3.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.
Files changed (92) hide show
  1. package/.jules/nexus.md +5 -0
  2. package/AGENTS.md +49 -0
  3. package/CHANGELOG.md +33 -0
  4. package/README.md +1 -1
  5. package/coverage/coverage-final.json +9 -4
  6. package/coverage/index.html +24 -9
  7. package/coverage/lcov-report/index.html +24 -9
  8. package/coverage/lcov-report/src/EventBus.ts.html +8 -8
  9. package/coverage/lcov-report/src/TaskGraphValidator.ts.html +53 -53
  10. package/coverage/lcov-report/src/TaskRunner.ts.html +377 -20
  11. package/coverage/lcov-report/src/TaskRunnerBuilder.ts.html +313 -0
  12. package/coverage/lcov-report/src/TaskRunnerExecutionConfig.ts.html +139 -0
  13. package/coverage/lcov-report/src/TaskStateManager.ts.html +511 -0
  14. package/coverage/lcov-report/src/WorkflowExecutor.ts.html +119 -137
  15. package/coverage/lcov-report/src/contracts/RunnerEvents.ts.html +1 -1
  16. package/coverage/lcov-report/src/contracts/index.html +1 -1
  17. package/coverage/lcov-report/src/index.html +56 -11
  18. package/coverage/lcov-report/src/strategies/DryRunExecutionStrategy.ts.html +178 -0
  19. package/coverage/lcov-report/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  20. package/coverage/lcov-report/src/strategies/index.html +131 -0
  21. package/coverage/lcov.info +419 -204
  22. package/coverage/src/EventBus.ts.html +8 -8
  23. package/coverage/src/TaskGraphValidator.ts.html +53 -53
  24. package/coverage/src/TaskRunner.ts.html +377 -20
  25. package/coverage/src/TaskRunnerBuilder.ts.html +313 -0
  26. package/coverage/src/TaskRunnerExecutionConfig.ts.html +139 -0
  27. package/coverage/src/TaskStateManager.ts.html +511 -0
  28. package/coverage/src/WorkflowExecutor.ts.html +119 -137
  29. package/coverage/src/contracts/RunnerEvents.ts.html +1 -1
  30. package/coverage/src/contracts/index.html +1 -1
  31. package/coverage/src/index.html +56 -11
  32. package/coverage/src/strategies/DryRunExecutionStrategy.ts.html +178 -0
  33. package/coverage/src/strategies/StandardExecutionStrategy.ts.html +190 -0
  34. package/coverage/src/strategies/index.html +131 -0
  35. package/dist/TaskRunner.d.ts +24 -2
  36. package/dist/TaskRunner.js +99 -3
  37. package/dist/TaskRunner.js.map +1 -1
  38. package/dist/TaskRunnerBuilder.d.ts +33 -0
  39. package/dist/TaskRunnerBuilder.js +54 -0
  40. package/dist/TaskRunnerBuilder.js.map +1 -0
  41. package/dist/TaskRunnerExecutionConfig.d.ts +18 -0
  42. package/dist/TaskRunnerExecutionConfig.js +2 -0
  43. package/dist/TaskRunnerExecutionConfig.js.map +1 -0
  44. package/dist/TaskStateManager.d.ts +54 -0
  45. package/dist/TaskStateManager.js +130 -0
  46. package/dist/TaskStateManager.js.map +1 -0
  47. package/dist/TaskStatus.d.ts +1 -1
  48. package/dist/TaskStep.d.ts +2 -1
  49. package/dist/WorkflowExecutor.d.ts +11 -17
  50. package/dist/WorkflowExecutor.js +67 -69
  51. package/dist/WorkflowExecutor.js.map +1 -1
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +3 -0
  54. package/dist/index.js.map +1 -1
  55. package/dist/strategies/DryRunExecutionStrategy.d.ts +16 -0
  56. package/dist/strategies/DryRunExecutionStrategy.js +25 -0
  57. package/dist/strategies/DryRunExecutionStrategy.js.map +1 -0
  58. package/dist/strategies/IExecutionStrategy.d.ts +16 -0
  59. package/dist/strategies/IExecutionStrategy.js +2 -0
  60. package/dist/strategies/IExecutionStrategy.js.map +1 -0
  61. package/dist/strategies/StandardExecutionStrategy.d.ts +9 -0
  62. package/dist/strategies/StandardExecutionStrategy.js +25 -0
  63. package/dist/strategies/StandardExecutionStrategy.js.map +1 -0
  64. package/openspec/changes/add-concurrency-control/proposal.md +14 -0
  65. package/openspec/changes/add-concurrency-control/tasks.md +9 -0
  66. package/openspec/changes/add-task-retry-policy/proposal.md +16 -0
  67. package/openspec/changes/add-task-retry-policy/tasks.md +10 -0
  68. package/openspec/changes/add-workflow-preview/proposal.md +13 -0
  69. package/openspec/changes/add-workflow-preview/tasks.md +7 -0
  70. package/openspec/changes/archive/2026-01-18-add-external-task-cancellation/tasks.md +10 -0
  71. package/openspec/changes/archive/2026-01-18-add-integration-tests/proposal.md +18 -0
  72. package/openspec/changes/archive/2026-01-18-add-integration-tests/tasks.md +17 -0
  73. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/proposal.md +14 -0
  74. package/openspec/changes/archive/2026-01-18-refactor-core-architecture/tasks.md +8 -0
  75. package/openspec/changes/feat-per-task-timeout/proposal.md +25 -0
  76. package/openspec/changes/feat-per-task-timeout/tasks.md +27 -0
  77. package/package.json +1 -1
  78. package/src/TaskRunner.ts +123 -4
  79. package/src/TaskRunnerBuilder.ts +76 -0
  80. package/src/TaskRunnerExecutionConfig.ts +18 -0
  81. package/src/TaskStateManager.ts +142 -0
  82. package/src/TaskStatus.ts +1 -1
  83. package/src/TaskStep.ts +2 -1
  84. package/src/WorkflowExecutor.ts +77 -83
  85. package/src/index.ts +4 -0
  86. package/src/strategies/DryRunExecutionStrategy.ts +31 -0
  87. package/src/strategies/IExecutionStrategy.ts +21 -0
  88. package/src/strategies/StandardExecutionStrategy.ts +35 -0
  89. package/test-report.xml +139 -45
  90. package/GEMINI.md +0 -48
  91. package/openspec/changes/add-external-task-cancellation/tasks.md +0 -10
  92. /package/openspec/changes/{add-external-task-cancellation → archive/2026-01-18-add-external-task-cancellation}/proposal.md +0 -0
@@ -1,6 +1,9 @@
1
1
  import { TaskGraphValidator } from "./TaskGraphValidator.js";
2
2
  import { EventBus } from "./EventBus.js";
3
3
  import { WorkflowExecutor } from "./WorkflowExecutor.js";
4
+ import { TaskStateManager } from "./TaskStateManager.js";
5
+ import { StandardExecutionStrategy } from "./strategies/StandardExecutionStrategy.js";
6
+ import { DryRunExecutionStrategy } from "./strategies/DryRunExecutionStrategy.js";
4
7
  /**
5
8
  * The main class that orchestrates the execution of a list of tasks
6
9
  * based on their dependencies, with support for parallel execution.
@@ -10,6 +13,7 @@ export class TaskRunner {
10
13
  context;
11
14
  eventBus = new EventBus();
12
15
  validator = new TaskGraphValidator();
16
+ executionStrategy = new StandardExecutionStrategy();
13
17
  /**
14
18
  * @param context The shared context object to be passed to each task.
15
19
  */
@@ -30,16 +34,67 @@ export class TaskRunner {
30
34
  * @param callback The callback to remove.
31
35
  */
32
36
  off(event, callback) {
37
+ /* v8 ignore next 1 */
33
38
  this.eventBus.off(event, callback);
34
39
  }
40
+ /**
41
+ * Sets the execution strategy to be used.
42
+ * @param strategy The execution strategy.
43
+ * @returns The TaskRunner instance for chaining.
44
+ */
45
+ setExecutionStrategy(strategy) {
46
+ /* v8 ignore next 2 */
47
+ this.executionStrategy = strategy;
48
+ return this;
49
+ }
50
+ /**
51
+ * Generates a Mermaid.js graph representation of the task workflow.
52
+ * @param steps The list of tasks to visualize.
53
+ * @returns A string containing the Mermaid graph definition.
54
+ */
55
+ static getMermaidGraph(steps) {
56
+ const graphLines = ["graph TD"];
57
+ // Helper to sanitize node names or wrap them if needed
58
+ // For simplicity, we just wrap in quotes and use the name as ID if it's simple
59
+ // or generate an ID if strictly needed. Here we assume names are unique IDs.
60
+ // We will wrap names in quotes for the label, but use the name as the ID.
61
+ // Actually, Mermaid ID cannot have spaces without quotes.
62
+ const safeId = (name) => JSON.stringify(name);
63
+ const sanitize = (name) => this.sanitizeMermaidId(name);
64
+ // Add all nodes first to ensure they exist
65
+ for (const step of steps) {
66
+ // Using the name as both ID and Label for simplicity
67
+ // Format: ID["Label"]
68
+ // safeId returns a quoted string (e.g. "Task Name"), so we use it directly as the label
69
+ graphLines.push(` ${sanitize(step.name)}[${safeId(step.name)}]`);
70
+ }
71
+ // Add edges
72
+ for (const step of steps) {
73
+ if (step.dependencies) {
74
+ for (const dep of step.dependencies) {
75
+ graphLines.push(` ${sanitize(dep)} --> ${sanitize(step.name)}`);
76
+ }
77
+ }
78
+ }
79
+ return [...new Set(graphLines)].join("\n");
80
+ }
81
+ /**
82
+ * Sanitizes a string for use as a Mermaid node ID.
83
+ * @param id The string to sanitize.
84
+ * @returns The sanitized string.
85
+ */
86
+ static sanitizeMermaidId(id) {
87
+ return id.replaceAll(/ /g, "_").replaceAll(/:/g, "_").replaceAll(/"/g, "_");
88
+ }
35
89
  /**
36
90
  * Executes a list of tasks, respecting their dependencies and running
37
91
  * independent tasks in parallel.
38
92
  * @param steps An array of TaskStep objects to be executed.
93
+ * @param config Optional configuration for execution (timeout, cancellation).
39
94
  * @returns A Promise that resolves to a Map where keys are task names
40
95
  * and values are the corresponding TaskResult objects.
41
96
  */
42
- async execute(steps) {
97
+ async execute(steps, config) {
43
98
  // Validate the task graph before execution
44
99
  const taskGraph = {
45
100
  tasks: steps.map((step) => ({
@@ -51,8 +106,49 @@ export class TaskRunner {
51
106
  if (!validationResult.isValid) {
52
107
  throw new Error(this.validator.createErrorMessage(validationResult));
53
108
  }
54
- const executor = new WorkflowExecutor(this.context, this.eventBus);
55
- return executor.execute(steps);
109
+ const stateManager = new TaskStateManager(this.eventBus);
110
+ let strategy = this.executionStrategy;
111
+ if (config?.dryRun) {
112
+ strategy = new DryRunExecutionStrategy();
113
+ }
114
+ const executor = new WorkflowExecutor(this.context, this.eventBus, stateManager, strategy);
115
+ // We need to handle the timeout cleanup properly.
116
+ if (config?.timeout !== undefined) {
117
+ const controller = new AbortController();
118
+ const timeoutId = setTimeout(() => {
119
+ controller.abort(new Error(`Workflow timed out after ${config.timeout}ms`));
120
+ }, config.timeout);
121
+ let effectiveSignal = controller.signal;
122
+ let onAbort;
123
+ // Handle combination of signals if user provided one
124
+ if (config.signal) {
125
+ if (config.signal.aborted) {
126
+ // If already aborted, use it directly (WorkflowExecutor handles early abort)
127
+ // We can cancel timeout immediately
128
+ clearTimeout(timeoutId);
129
+ effectiveSignal = config.signal;
130
+ }
131
+ else {
132
+ // Listen to user signal to abort our controller
133
+ onAbort = () => {
134
+ controller.abort(config.signal?.reason);
135
+ };
136
+ config.signal.addEventListener("abort", onAbort);
137
+ }
138
+ }
139
+ try {
140
+ return await executor.execute(steps, effectiveSignal);
141
+ }
142
+ finally {
143
+ clearTimeout(timeoutId);
144
+ if (config.signal && onAbort) {
145
+ config.signal.removeEventListener("abort", onAbort);
146
+ }
147
+ }
148
+ }
149
+ else {
150
+ return executor.execute(steps, config?.signal);
151
+ }
56
152
  }
57
153
  }
58
154
  //# sourceMappingURL=TaskRunner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAG7D,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAKzD;;;;GAIG;AACH,MAAM,OAAO,UAAU;IAOD;IANZ,QAAQ,GAAG,IAAI,QAAQ,EAAY,CAAC;IACpC,SAAS,GAAG,IAAI,kBAAkB,EAAE,CAAC;IAE7C;;OAEG;IACH,YAAoB,OAAiB;QAAjB,YAAO,GAAP,OAAO,CAAU;IAAG,CAAC;IAEzC;;;;OAIG;IACI,EAAE,CACP,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED;;;;OAIG;IACI,GAAG,CACR,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED;;;;;;OAMG;IACH,KAAK,CAAC,OAAO,CAAC,KAA2B;QACvC,2CAA2C;QAC3C,MAAM,SAAS,GAAc;YAC3B,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC1B,EAAE,EAAE,IAAI,CAAC,IAAI;gBACb,YAAY,EAAE,IAAI,CAAC,YAAY,IAAI,EAAE;aACtC,CAAC,CAAC;SACJ,CAAC;QAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC5D,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,QAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnE,OAAO,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;CACF"}
1
+ {"version":3,"file":"TaskRunner.js","sourceRoot":"","sources":["../src/TaskRunner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { TaskRunner } from "./TaskRunner.js";
2
+ import { RunnerEventPayloads, RunnerEventListener } from "./contracts/RunnerEvents.js";
3
+ import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
4
+ /**
5
+ * A builder for configuring and creating TaskRunner instances.
6
+ */
7
+ export declare class TaskRunnerBuilder<TContext> {
8
+ private context;
9
+ private strategy?;
10
+ private listeners;
11
+ /**
12
+ * @param context The shared context object.
13
+ */
14
+ constructor(context: TContext);
15
+ /**
16
+ * Sets the execution strategy.
17
+ * @param strategy The execution strategy to use.
18
+ * @returns The builder instance.
19
+ */
20
+ useStrategy(strategy: IExecutionStrategy<TContext>): this;
21
+ /**
22
+ * Adds an event listener.
23
+ * @param event The event name.
24
+ * @param callback The callback to execute.
25
+ * @returns The builder instance.
26
+ */
27
+ on<K extends keyof RunnerEventPayloads<TContext>>(event: K, callback: RunnerEventListener<TContext, K>): this;
28
+ /**
29
+ * Builds the TaskRunner instance.
30
+ * @returns A configured TaskRunner.
31
+ */
32
+ build(): TaskRunner<TContext>;
33
+ }
@@ -0,0 +1,54 @@
1
+ import { TaskRunner } from "./TaskRunner.js";
2
+ /**
3
+ * A builder for configuring and creating TaskRunner instances.
4
+ */
5
+ export class TaskRunnerBuilder {
6
+ context;
7
+ strategy;
8
+ listeners = {};
9
+ /**
10
+ * @param context The shared context object.
11
+ */
12
+ constructor(context) {
13
+ this.context = context;
14
+ }
15
+ /**
16
+ * Sets the execution strategy.
17
+ * @param strategy The execution strategy to use.
18
+ * @returns The builder instance.
19
+ */
20
+ useStrategy(strategy) {
21
+ this.strategy = strategy;
22
+ return this;
23
+ }
24
+ /**
25
+ * Adds an event listener.
26
+ * @param event The event name.
27
+ * @param callback The callback to execute.
28
+ * @returns The builder instance.
29
+ */
30
+ on(event, callback) {
31
+ if (!this.listeners[event]) {
32
+ this.listeners[event] = [];
33
+ }
34
+ this.listeners[event].push(callback);
35
+ return this;
36
+ }
37
+ /**
38
+ * Builds the TaskRunner instance.
39
+ * @returns A configured TaskRunner.
40
+ */
41
+ build() {
42
+ const runner = new TaskRunner(this.context);
43
+ if (this.strategy) {
44
+ runner.setExecutionStrategy(this.strategy);
45
+ }
46
+ Object.keys(this.listeners).forEach((event) => {
47
+ const callbacks = this.listeners[event];
48
+ // callbacks is always defined because we are iterating keys of the object
49
+ callbacks.forEach((callback) => runner.on(event, callback));
50
+ });
51
+ return runner;
52
+ }
53
+ }
54
+ //# sourceMappingURL=TaskRunnerBuilder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskRunnerBuilder.js","sourceRoot":"","sources":["../src/TaskRunnerBuilder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAI7C;;GAEG;AACH,MAAM,OAAO,iBAAiB;IACpB,OAAO,CAAW;IAClB,QAAQ,CAAgC;IACxC,SAAS,GAEb,EAAE,CAAC;IAEP;;OAEG;IACH,YAAY,OAAiB;QAC3B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED;;;;OAIG;IACI,WAAW,CAAC,QAAsC;QACvD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;;;OAKG;IACI,EAAE,CACP,KAAQ,EACR,QAA0C;QAE1C,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC;YAC3B,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;QAC7B,CAAC;QACD,IAAI,CAAC,SAAS,CAAC,KAAK,CAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAED;;;OAGG;IACI,KAAK;QACV,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAE5C,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7C,CAAC;QAEA,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAgD,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YAC5F,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACxC,0EAA0E;YAC1E,SAAU,CAAC,OAAO,CAAC,CAAC,QAAQ,EAAE,EAAE,CAC9B,MAAM,CAAC,EAAE,CACP,KAAK,EACL,QAGC,CACF,CACF,CAAC;QACJ,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC;IAChB,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Configuration options for TaskRunner execution.
3
+ */
4
+ export interface TaskRunnerExecutionConfig {
5
+ /**
6
+ * An AbortSignal to cancel the workflow externally.
7
+ */
8
+ signal?: AbortSignal;
9
+ /**
10
+ * A timeout in milliseconds for the entire workflow.
11
+ */
12
+ timeout?: number;
13
+ /**
14
+ * If true, the runner will simulate execution without running the actual tasks.
15
+ * Useful for verifying the execution order and graph structure.
16
+ */
17
+ dryRun?: boolean;
18
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TaskRunnerExecutionConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskRunnerExecutionConfig.js","sourceRoot":"","sources":["../src/TaskRunnerExecutionConfig.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { TaskStep } from "./TaskStep.js";
2
+ import { TaskResult } from "./TaskResult.js";
3
+ import { EventBus } from "./EventBus.js";
4
+ /**
5
+ * Manages the state of the task execution, including results, pending steps, and running tasks.
6
+ * Handles dependency resolution and event emission for state changes.
7
+ */
8
+ export declare class TaskStateManager<TContext> {
9
+ private eventBus;
10
+ private results;
11
+ private pendingSteps;
12
+ private running;
13
+ constructor(eventBus: EventBus<TContext>);
14
+ /**
15
+ * Initializes the state with the given steps.
16
+ * @param steps The steps to execute.
17
+ */
18
+ initialize(steps: TaskStep<TContext>[]): void;
19
+ /**
20
+ * Processes the pending steps to identify tasks that can be started or must be skipped.
21
+ * Emits `taskSkipped` for skipped tasks.
22
+ * @returns An array of tasks that are ready to run.
23
+ */
24
+ processDependencies(): TaskStep<TContext>[];
25
+ /**
26
+ * Marks a task as running and emits `taskStart`.
27
+ * @param step The task that is starting.
28
+ */
29
+ markRunning(step: TaskStep<TContext>): void;
30
+ /**
31
+ * Marks a task as completed (success, failure, or cancelled during execution)
32
+ * and emits `taskEnd`.
33
+ * @param step The task that completed.
34
+ * @param result The result of the task.
35
+ */
36
+ markCompleted(step: TaskStep<TContext>, result: TaskResult): void;
37
+ /**
38
+ * Cancels all pending tasks that haven't started yet.
39
+ * @param message The cancellation message.
40
+ */
41
+ cancelAllPending(message: string): void;
42
+ /**
43
+ * Returns the current results map.
44
+ */
45
+ getResults(): Map<string, TaskResult>;
46
+ /**
47
+ * Checks if there are any tasks currently running.
48
+ */
49
+ hasRunningTasks(): boolean;
50
+ /**
51
+ * Checks if there are any pending tasks.
52
+ */
53
+ hasPendingTasks(): boolean;
54
+ }
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Manages the state of the task execution, including results, pending steps, and running tasks.
3
+ * Handles dependency resolution and event emission for state changes.
4
+ */
5
+ export class TaskStateManager {
6
+ eventBus;
7
+ results = new Map();
8
+ pendingSteps = new Set();
9
+ running = new Set();
10
+ constructor(eventBus) {
11
+ this.eventBus = eventBus;
12
+ }
13
+ /**
14
+ * Initializes the state with the given steps.
15
+ * @param steps The steps to execute.
16
+ */
17
+ initialize(steps) {
18
+ this.pendingSteps = new Set(steps);
19
+ this.results.clear();
20
+ this.running.clear();
21
+ }
22
+ /**
23
+ * Processes the pending steps to identify tasks that can be started or must be skipped.
24
+ * Emits `taskSkipped` for skipped tasks.
25
+ * @returns An array of tasks that are ready to run.
26
+ */
27
+ processDependencies() {
28
+ const toRemove = [];
29
+ const toRun = [];
30
+ for (const step of this.pendingSteps) {
31
+ const deps = step.dependencies ?? [];
32
+ let blocked = false;
33
+ let failedDep;
34
+ for (const dep of deps) {
35
+ const depResult = this.results.get(dep);
36
+ if (!depResult) {
37
+ // Dependency not finished yet
38
+ blocked = true;
39
+ }
40
+ else if (depResult.status !== "success") {
41
+ failedDep = dep;
42
+ break;
43
+ }
44
+ }
45
+ if (failedDep) {
46
+ const depResult = this.results.get(failedDep);
47
+ const depError = depResult?.error ? `: ${depResult.error}` : "";
48
+ const result = {
49
+ status: "skipped",
50
+ message: `Skipped because dependency '${failedDep}' failed${depError}`,
51
+ };
52
+ this.results.set(step.name, result);
53
+ this.eventBus.emit("taskSkipped", { step, result });
54
+ toRemove.push(step);
55
+ }
56
+ else if (!blocked) {
57
+ toRun.push(step);
58
+ toRemove.push(step);
59
+ }
60
+ }
61
+ // Cleanup pending set
62
+ for (const step of toRemove) {
63
+ this.pendingSteps.delete(step);
64
+ }
65
+ return toRun;
66
+ }
67
+ /**
68
+ * Marks a task as running and emits `taskStart`.
69
+ * @param step The task that is starting.
70
+ */
71
+ markRunning(step) {
72
+ this.running.add(step.name);
73
+ this.eventBus.emit("taskStart", { step });
74
+ }
75
+ /**
76
+ * Marks a task as completed (success, failure, or cancelled during execution)
77
+ * and emits `taskEnd`.
78
+ * @param step The task that completed.
79
+ * @param result The result of the task.
80
+ */
81
+ markCompleted(step, result) {
82
+ this.running.delete(step.name);
83
+ this.results.set(step.name, result);
84
+ this.eventBus.emit("taskEnd", { step, result });
85
+ }
86
+ /**
87
+ * Cancels all pending tasks that haven't started yet.
88
+ * @param message The cancellation message.
89
+ */
90
+ cancelAllPending(message) {
91
+ // Iterate over pendingSteps to cancel them
92
+ for (const step of this.pendingSteps) {
93
+ // Also check running? No, running tasks are handled by AbortSignal in Executor.
94
+ // We only cancel what is pending and hasn't started.
95
+ /* v8 ignore next 1 */
96
+ if (!this.results.has(step.name) && !this.running.has(step.name)) {
97
+ const result = {
98
+ status: "cancelled",
99
+ message,
100
+ };
101
+ this.results.set(step.name, result);
102
+ }
103
+ }
104
+ // Clear pending set as they are now "done" (cancelled)
105
+ // Wait, if we clear pending steps, processDependencies won't pick them up.
106
+ // The loop in Executor relies on results.size or pendingSteps.
107
+ // The previous implementation iterated `steps` (all steps) to cancel.
108
+ // Here we iterate `pendingSteps`.
109
+ this.pendingSteps.clear();
110
+ }
111
+ /**
112
+ * Returns the current results map.
113
+ */
114
+ getResults() {
115
+ return this.results;
116
+ }
117
+ /**
118
+ * Checks if there are any tasks currently running.
119
+ */
120
+ hasRunningTasks() {
121
+ return this.running.size > 0;
122
+ }
123
+ /**
124
+ * Checks if there are any pending tasks.
125
+ */
126
+ hasPendingTasks() {
127
+ return this.pendingSteps.size > 0;
128
+ }
129
+ }
130
+ //# sourceMappingURL=TaskStateManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TaskStateManager.js","sourceRoot":"","sources":["../src/TaskStateManager.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
1
  /**
2
2
  * Represents the completion status of a task.
3
3
  */
4
- export type TaskStatus = "success" | "failure" | "skipped";
4
+ export type TaskStatus = "success" | "failure" | "skipped" | "cancelled";
@@ -11,7 +11,8 @@ export interface TaskStep<TContext> {
11
11
  /**
12
12
  * The core logic of the task.
13
13
  * @param context The shared context object, allowing for state to be passed between tasks.
14
+ * @param signal An optional AbortSignal to listen for cancellation.
14
15
  * @returns A Promise that resolves to a TaskResult.
15
16
  */
16
- run(context: TContext): Promise<TaskResult>;
17
+ run(context: TContext, signal?: AbortSignal): Promise<TaskResult>;
17
18
  }
@@ -1,6 +1,8 @@
1
1
  import { TaskStep } from "./TaskStep.js";
2
2
  import { TaskResult } from "./TaskResult.js";
3
3
  import { EventBus } from "./EventBus.js";
4
+ import { TaskStateManager } from "./TaskStateManager.js";
5
+ import { IExecutionStrategy } from "./strategies/IExecutionStrategy.js";
4
6
  /**
5
7
  * Handles the execution of the workflow steps.
6
8
  * @template TContext The shape of the shared context object.
@@ -8,32 +10,24 @@ import { EventBus } from "./EventBus.js";
8
10
  export declare class WorkflowExecutor<TContext> {
9
11
  private context;
10
12
  private eventBus;
11
- private running;
13
+ private stateManager;
14
+ private strategy;
12
15
  /**
13
16
  * @param context The shared context object.
14
17
  * @param eventBus The event bus to emit events.
18
+ * @param stateManager Manages execution state.
19
+ * @param strategy Execution strategy.
15
20
  */
16
- constructor(context: TContext, eventBus: EventBus<TContext>);
21
+ constructor(context: TContext, eventBus: EventBus<TContext>, stateManager: TaskStateManager<TContext>, strategy: IExecutionStrategy<TContext>);
17
22
  /**
18
23
  * Executes the given steps.
19
24
  * @param steps The list of steps to execute.
25
+ * @param signal Optional AbortSignal for cancellation.
20
26
  * @returns A Promise that resolves to a map of task results.
21
27
  */
22
- execute(steps: TaskStep<TContext>[]): Promise<Map<string, TaskResult>>;
28
+ execute(steps: TaskStep<TContext>[], signal?: AbortSignal): Promise<Map<string, TaskResult>>;
23
29
  /**
24
- * Logic to identify tasks that can be started or must be skipped.
30
+ * Logic to identify tasks that can be started and run them.
25
31
  */
26
- private processQueue;
27
- /**
28
- * Identifies steps that cannot run because a dependency failed.
29
- */
30
- private handleSkippedTasks;
31
- /**
32
- * Returns steps where all dependencies have finished successfully.
33
- */
34
- private getReadySteps;
35
- /**
36
- * Handles the lifecycle of a single task execution.
37
- */
38
- private runStep;
32
+ private processLoop;
39
33
  }