@push.rocks/taskbuffer 4.2.1 → 5.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @push.rocks/taskbuffer 🚀
2
2
 
3
- > **Modern TypeScript task orchestration with smart buffering, scheduling, labels, and real-time event streaming**
3
+ > **Modern TypeScript task orchestration with constraint-based concurrency, smart buffering, scheduling, labels, and real-time event streaming**
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/@push.rocks/taskbuffer.svg)](https://www.npmjs.com/package/@push.rocks/taskbuffer)
6
6
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org/)
@@ -13,6 +13,7 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
13
13
  ## 🌟 Features
14
14
 
15
15
  - **🎯 Type-Safe Task Management** — Full TypeScript support with generics and type inference
16
+ - **🔒 Constraint-Based Concurrency** — Per-key mutual exclusion, group concurrency limits, and cooldown enforcement via `TaskConstraintGroup`
16
17
  - **📊 Real-Time Progress Tracking** — Step-based progress with percentage weights
17
18
  - **⚡ Smart Buffering** — Intelligent request debouncing and batching
18
19
  - **⏰ Cron Scheduling** — Schedule tasks with cron expressions
@@ -49,6 +50,24 @@ const result = await greetTask.trigger('World');
49
50
  console.log(result); // "Hello, World!"
50
51
  ```
51
52
 
53
+ ### Task with Typed Data 📦
54
+
55
+ Every task can carry a typed data bag — perfect for constraint matching, routing, and metadata:
56
+
57
+ ```typescript
58
+ const task = new Task<undefined, [], { domain: string; priority: number }>({
59
+ name: 'update-dns',
60
+ data: { domain: 'example.com', priority: 1 },
61
+ taskFunction: async () => {
62
+ // task.data is fully typed here
63
+ console.log(`Updating DNS for ${task.data.domain}`);
64
+ },
65
+ });
66
+
67
+ task.data.domain; // string — fully typed
68
+ task.data.priority; // number — fully typed
69
+ ```
70
+
52
71
  ### Task with Steps & Progress 📊
53
72
 
54
73
  ```typescript
@@ -84,6 +103,132 @@ console.log(deployTask.getStepsMetadata()); // Step details with status
84
103
 
85
104
  > **Note:** `notifyStep()` is fully type-safe — TypeScript only accepts step names you declared in the `steps` array when you use `as const`.
86
105
 
106
+ ## 🔒 Task Constraints — Concurrency, Mutual Exclusion & Cooldowns
107
+
108
+ `TaskConstraintGroup` is the unified mechanism for controlling how tasks run relative to each other. It replaces older patterns like task runners, blocking tasks, and execution delays with a single, composable, key-based constraint system.
109
+
110
+ ### Per-Key Mutual Exclusion
111
+
112
+ Ensure only one task runs at a time for a given key (e.g. per domain, per tenant, per resource):
113
+
114
+ ```typescript
115
+ import { Task, TaskManager, TaskConstraintGroup } from '@push.rocks/taskbuffer';
116
+
117
+ const manager = new TaskManager();
118
+
119
+ // Only one DNS update per domain at a time
120
+ const domainMutex = new TaskConstraintGroup<{ domain: string }>({
121
+ name: 'domain-mutex',
122
+ maxConcurrent: 1,
123
+ constraintKeyForTask: (task) => task.data.domain,
124
+ });
125
+
126
+ manager.addConstraintGroup(domainMutex);
127
+
128
+ const task1 = new Task<undefined, [], { domain: string }>({
129
+ name: 'update-a.com',
130
+ data: { domain: 'a.com' },
131
+ taskFunction: async () => { /* update DNS for a.com */ },
132
+ });
133
+
134
+ const task2 = new Task<undefined, [], { domain: string }>({
135
+ name: 'update-a.com-2',
136
+ data: { domain: 'a.com' },
137
+ taskFunction: async () => { /* another update for a.com */ },
138
+ });
139
+
140
+ manager.addTask(task1);
141
+ manager.addTask(task2);
142
+
143
+ // task2 waits until task1 finishes (same domain key)
144
+ await Promise.all([
145
+ manager.triggerTask(task1),
146
+ manager.triggerTask(task2),
147
+ ]);
148
+ ```
149
+
150
+ ### Group Concurrency Limits
151
+
152
+ Cap how many tasks can run concurrently across a group:
153
+
154
+ ```typescript
155
+ // Max 3 DNS updaters running globally at once
156
+ const dnsLimit = new TaskConstraintGroup<{ group: string }>({
157
+ name: 'dns-concurrency',
158
+ maxConcurrent: 3,
159
+ constraintKeyForTask: (task) =>
160
+ task.data.group === 'dns' ? 'dns' : null, // null = skip constraint
161
+ });
162
+
163
+ manager.addConstraintGroup(dnsLimit);
164
+ ```
165
+
166
+ ### Cooldowns (Rate Limiting)
167
+
168
+ Enforce a minimum time gap between consecutive executions for the same key:
169
+
170
+ ```typescript
171
+ // No more than one API call per domain every 11 seconds
172
+ const rateLimiter = new TaskConstraintGroup<{ domain: string }>({
173
+ name: 'api-rate-limit',
174
+ maxConcurrent: 1,
175
+ cooldownMs: 11000,
176
+ constraintKeyForTask: (task) => task.data.domain,
177
+ });
178
+
179
+ manager.addConstraintGroup(rateLimiter);
180
+ ```
181
+
182
+ ### Global Concurrency Cap
183
+
184
+ Limit total concurrent tasks system-wide:
185
+
186
+ ```typescript
187
+ const globalCap = new TaskConstraintGroup({
188
+ name: 'global-cap',
189
+ maxConcurrent: 10,
190
+ constraintKeyForTask: () => 'all', // same key = shared limit
191
+ });
192
+
193
+ manager.addConstraintGroup(globalCap);
194
+ ```
195
+
196
+ ### Composing Multiple Constraints
197
+
198
+ Multiple constraint groups stack — a task only runs when **all** applicable constraints allow it:
199
+
200
+ ```typescript
201
+ manager.addConstraintGroup(globalCap); // max 10 globally
202
+ manager.addConstraintGroup(domainMutex); // max 1 per domain
203
+ manager.addConstraintGroup(rateLimiter); // 11s cooldown per domain
204
+
205
+ // A task must satisfy ALL three constraints before it starts
206
+ await manager.triggerTask(dnsTask);
207
+ ```
208
+
209
+ ### Selective Constraints
210
+
211
+ Return `null` from `constraintKeyForTask` to exempt a task from a constraint group:
212
+
213
+ ```typescript
214
+ const constraint = new TaskConstraintGroup<{ priority: string }>({
215
+ name: 'low-priority-limit',
216
+ maxConcurrent: 2,
217
+ constraintKeyForTask: (task) =>
218
+ task.data.priority === 'low' ? 'low-priority' : null, // high priority tasks skip this constraint
219
+ });
220
+ ```
221
+
222
+ ### How It Works
223
+
224
+ When you trigger a task through `TaskManager` (via `triggerTask`, `triggerTaskByName`, `addExecuteRemoveTask`, or cron), the manager:
225
+
226
+ 1. Evaluates all registered constraint groups against the task
227
+ 2. If no constraints apply (all matchers return `null`) → runs immediately
228
+ 3. If all applicable constraints have capacity → acquires slots and runs
229
+ 4. If any constraint blocks → enqueues the task; when a running task completes, the queue is drained
230
+ 5. Cooldown-blocked tasks auto-retry after the shortest remaining cooldown expires
231
+
87
232
  ## 🎯 Core Concepts
88
233
 
89
234
  ### Task Buffering — Intelligent Request Management
@@ -95,7 +240,6 @@ const apiTask = new Task({
95
240
  name: 'APIRequest',
96
241
  buffered: true,
97
242
  bufferMax: 5, // Maximum 5 concurrent executions
98
- execDelay: 100, // Minimum 100ms between executions
99
243
  taskFunction: async (endpoint) => {
100
244
  return await fetch(endpoint).then((r) => r.json());
101
245
  },
@@ -156,9 +300,9 @@ console.log(`Saved ${savedCount} items`);
156
300
  Taskchain also supports dynamic mutation:
157
301
 
158
302
  ```typescript
159
- pipeline.addTask(newTask); // Append to chain
160
- pipeline.removeTask(oldTask); // Remove by reference (returns boolean)
161
- pipeline.shiftTask(); // Remove & return first task
303
+ pipeline.addTask(newTask); // Append to chain
304
+ pipeline.removeTask(oldTask); // Remove by reference (returns boolean)
305
+ pipeline.shiftTask(); // Remove & return first task
162
306
  ```
163
307
 
164
308
  Error context is rich — a chain failure includes the chain name, failing task name, task index, and preserves the original error as `.cause`.
@@ -220,27 +364,6 @@ await initTask.trigger(); // No-op
220
364
  console.log(initTask.hasTriggered); // true
221
365
  ```
222
366
 
223
- ### TaskRunner — Managed Queue with Concurrency Control
224
-
225
- Process a queue of tasks with a configurable parallelism limit:
226
-
227
- ```typescript
228
- import { TaskRunner } from '@push.rocks/taskbuffer';
229
-
230
- const runner = new TaskRunner();
231
- runner.setMaxParallelJobs(3); // Run up to 3 tasks concurrently
232
-
233
- await runner.start();
234
-
235
- runner.addTask(taskA);
236
- runner.addTask(taskB);
237
- runner.addTask(taskC);
238
- runner.addTask(taskD); // Queued until a slot opens
239
-
240
- // When done:
241
- await runner.stop();
242
- ```
243
-
244
367
  ## 🏷️ Labels — Multi-Tenant Task Filtering
245
368
 
246
369
  Attach arbitrary key-value labels to any task for filtering, grouping, or multi-tenant isolation:
@@ -411,6 +534,10 @@ manager.addTask(deployTask);
411
534
  manager.addAndScheduleTask(backupTask, '0 2 * * *'); // Daily at 2 AM
412
535
  manager.addAndScheduleTask(healthCheck, '*/5 * * * *'); // Every 5 minutes
413
536
 
537
+ // Register constraint groups
538
+ manager.addConstraintGroup(globalCap);
539
+ manager.addConstraintGroup(perDomainMutex);
540
+
414
541
  // Query metadata
415
542
  const meta = manager.getTaskMetadata('Deploy');
416
543
  console.log(meta);
@@ -433,7 +560,7 @@ const allMeta = manager.getAllTasksMetadata();
433
560
  const scheduled = manager.getScheduledTasks();
434
561
  const nextRuns = manager.getNextScheduledRuns(5);
435
562
 
436
- // Trigger by name
563
+ // Trigger by name (routes through constraints)
437
564
  await manager.triggerTaskByName('Deploy');
438
565
 
439
566
  // One-shot: add, execute, collect report, remove
@@ -462,6 +589,12 @@ manager.removeTask(task); // Removes from map and unsubscribes event forwarding
462
589
  manager.descheduleTaskByName('Deploy'); // Remove cron schedule only
463
590
  ```
464
591
 
592
+ ### Remove Constraint Groups
593
+
594
+ ```typescript
595
+ manager.removeConstraintGroup('domain-mutex'); // By name
596
+ ```
597
+
465
598
  ## 🎨 Web Component Dashboard
466
599
 
467
600
  Visualize your tasks in real-time with the included Lit-based web component:
@@ -570,32 +703,6 @@ await task.trigger('SELECT * FROM users'); // Setup runs here
570
703
  await task.trigger('SELECT * FROM orders'); // Setup skipped, pool reused
571
704
  ```
572
705
 
573
- ### Blocking Tasks
574
-
575
- Make one task wait for another to finish before executing:
576
-
577
- ```typescript
578
- const initTask = new Task({
579
- name: 'Init',
580
- taskFunction: async () => {
581
- await initializeSystem();
582
- },
583
- });
584
-
585
- const workerTask = new Task({
586
- name: 'Worker',
587
- taskFunction: async () => {
588
- await doWork();
589
- },
590
- });
591
-
592
- workerTask.blockingTasks.push(initTask);
593
-
594
- // Triggering worker will automatically wait for init to complete
595
- initTask.trigger();
596
- workerTask.trigger(); // Waits until initTask.finished resolves
597
- ```
598
-
599
706
  ### Database Migration Pipeline
600
707
 
601
708
  ```typescript
@@ -616,15 +723,24 @@ try {
616
723
 
617
724
  ### Multi-Tenant SaaS Monitoring
618
725
 
619
- Combine labels + events for a real-time multi-tenant dashboard:
726
+ Combine labels + events + constraints for a real-time multi-tenant system:
620
727
 
621
728
  ```typescript
622
729
  const manager = new TaskManager();
623
730
 
731
+ // Per-tenant concurrency limit
732
+ const tenantLimit = new TaskConstraintGroup<{ tenantId: string }>({
733
+ name: 'tenant-concurrency',
734
+ maxConcurrent: 2,
735
+ constraintKeyForTask: (task) => task.data.tenantId,
736
+ });
737
+ manager.addConstraintGroup(tenantLimit);
738
+
624
739
  // Create tenant-scoped tasks
625
740
  function createTenantTask(tenantId: string, taskName: string, fn: () => Promise<any>) {
626
- const task = new Task({
741
+ const task = new Task<undefined, [], { tenantId: string }>({
627
742
  name: `${tenantId}:${taskName}`,
743
+ data: { tenantId },
628
744
  labels: { tenantId },
629
745
  taskFunction: fn,
630
746
  });
@@ -653,15 +769,31 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
653
769
 
654
770
  | Class | Description |
655
771
  | --- | --- |
656
- | `Task<T, TSteps>` | Core task unit with optional step tracking, labels, and event streaming |
657
- | `TaskManager` | Centralized orchestrator with scheduling, label queries, and aggregated events |
772
+ | `Task<T, TSteps, TData>` | Core task unit with typed data, optional step tracking, labels, and event streaming |
773
+ | `TaskManager` | Centralized orchestrator with constraint groups, scheduling, label queries, and aggregated events |
774
+ | `TaskConstraintGroup<TData>` | Concurrency, mutual exclusion, and cooldown constraints with key-based grouping |
658
775
  | `Taskchain` | Sequential task executor with data flow between tasks |
659
776
  | `Taskparallel` | Concurrent task executor via `Promise.all()` |
660
777
  | `TaskOnce` | Single-execution guard |
661
778
  | `TaskDebounced` | Debounced task using rxjs |
662
- | `TaskRunner` | Sequential queue with configurable parallelism |
663
779
  | `TaskStep` | Step tracking unit (internal, exposed via metadata) |
664
780
 
781
+ ### Task Constructor Options
782
+
783
+ | Option | Type | Default | Description |
784
+ | --- | --- | --- | --- |
785
+ | `taskFunction` | `ITaskFunction<T>` | *required* | The async function to execute |
786
+ | `name` | `string` | — | Task identifier (required for TaskManager) |
787
+ | `data` | `TData` | `{}` | Typed data bag for constraint matching and routing |
788
+ | `steps` | `ReadonlyArray<{name, description, percentage}>` | — | Step definitions for progress tracking |
789
+ | `buffered` | `boolean` | — | Enable request buffering |
790
+ | `bufferMax` | `number` | — | Max buffered calls |
791
+ | `preTask` | `Task \| () => Task` | — | Task to run before |
792
+ | `afterTask` | `Task \| () => Task` | — | Task to run after |
793
+ | `taskSetup` | `() => Promise<T>` | — | One-time setup function |
794
+ | `catchErrors` | `boolean` | `false` | Swallow errors instead of rejecting |
795
+ | `labels` | `Record<string, string>` | `{}` | Initial labels |
796
+
665
797
  ### Task Methods
666
798
 
667
799
  | Method | Returns | Description |
@@ -682,6 +814,7 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
682
814
  | Property | Type | Description |
683
815
  | --- | --- | --- |
684
816
  | `name` | `string` | Task identifier |
817
+ | `data` | `TData` | Typed data bag |
685
818
  | `running` | `boolean` | Whether the task is currently executing |
686
819
  | `idle` | `boolean` | Inverse of `running` |
687
820
  | `labels` | `Record<string, string>` | Attached labels |
@@ -690,7 +823,27 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
690
823
  | `errorCount` | `number` | Total error count across all runs |
691
824
  | `runCount` | `number` | Total execution count |
692
825
  | `lastRun` | `Date \| undefined` | Timestamp of last execution |
693
- | `blockingTasks` | `Task[]` | Tasks that must finish before this one starts |
826
+
827
+ ### TaskConstraintGroup Constructor Options
828
+
829
+ | Option | Type | Default | Description |
830
+ | --- | --- | --- | --- |
831
+ | `name` | `string` | *required* | Constraint group identifier |
832
+ | `constraintKeyForTask` | `(task) => string \| null` | *required* | Returns key for grouping, or `null` to skip |
833
+ | `maxConcurrent` | `number` | `Infinity` | Max concurrent tasks per key |
834
+ | `cooldownMs` | `number` | `0` | Minimum ms between completions per key |
835
+
836
+ ### TaskConstraintGroup Methods
837
+
838
+ | Method | Returns | Description |
839
+ | --- | --- | --- |
840
+ | `getConstraintKey(task)` | `string \| null` | Get the constraint key for a task |
841
+ | `canRun(key)` | `boolean` | Check if a slot is available |
842
+ | `acquireSlot(key)` | `void` | Claim a running slot |
843
+ | `releaseSlot(key)` | `void` | Release a slot and record completion time |
844
+ | `getCooldownRemaining(key)` | `number` | Milliseconds until cooldown expires |
845
+ | `getRunningCount(key)` | `number` | Current running count for key |
846
+ | `reset()` | `void` | Clear all state |
694
847
 
695
848
  ### TaskManager Methods
696
849
 
@@ -699,7 +852,11 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
699
852
  | `addTask(task)` | `void` | Register a task (wires event forwarding) |
700
853
  | `removeTask(task)` | `void` | Remove task and unsubscribe events |
701
854
  | `getTaskByName(name)` | `Task \| undefined` | Look up by name |
702
- | `triggerTaskByName(name)` | `Promise<any>` | Trigger by name |
855
+ | `triggerTaskByName(name)` | `Promise<any>` | Trigger by name (routes through constraints) |
856
+ | `triggerTask(task)` | `Promise<any>` | Trigger directly (routes through constraints) |
857
+ | `triggerTaskConstrained(task, input?)` | `Promise<any>` | Core constraint evaluation entry point |
858
+ | `addConstraintGroup(group)` | `void` | Register a constraint group |
859
+ | `removeConstraintGroup(name)` | `void` | Remove a constraint group by name |
703
860
  | `addAndScheduleTask(task, cron)` | `void` | Register + schedule |
704
861
  | `scheduleTaskByName(name, cron)` | `void` | Schedule existing task |
705
862
  | `descheduleTaskByName(name)` | `void` | Remove schedule |
@@ -719,6 +876,7 @@ const acmeTasks = manager.getTasksMetadataByLabel('tenantId', 'acme');
719
876
  | --- | --- | --- |
720
877
  | `taskSubject` | `Subject<ITaskEvent>` | Aggregated events from all added tasks |
721
878
  | `taskMap` | `ObjectMap<Task>` | Internal task registry |
879
+ | `constraintGroups` | `TaskConstraintGroup[]` | Registered constraint groups |
722
880
 
723
881
  ### Exported Types
724
882
 
@@ -731,13 +889,14 @@ import type {
731
889
  TTaskEventType,
732
890
  ITaskStep,
733
891
  ITaskFunction,
892
+ ITaskConstraintGroupOptions,
734
893
  StepNames,
735
894
  } from '@push.rocks/taskbuffer';
736
895
  ```
737
896
 
738
897
  ## License and Legal Information
739
898
 
740
- This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./license.md) file.
899
+ This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
741
900
 
742
901
  **Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
743
902
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/taskbuffer',
6
- version: '4.2.1',
6
+ version: '5.0.0',
7
7
  description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.'
8
8
  }
package/ts/index.ts CHANGED
@@ -4,15 +4,15 @@ export { Taskchain } from './taskbuffer.classes.taskchain.js';
4
4
  export { Taskparallel } from './taskbuffer.classes.taskparallel.js';
5
5
  export { TaskManager } from './taskbuffer.classes.taskmanager.js';
6
6
  export { TaskOnce } from './taskbuffer.classes.taskonce.js';
7
- export { TaskRunner } from './taskbuffer.classes.taskrunner.js';
8
7
  export { TaskDebounced } from './taskbuffer.classes.taskdebounced.js';
8
+ export { TaskConstraintGroup } from './taskbuffer.classes.taskconstraintgroup.js';
9
9
 
10
10
  // Task step system
11
11
  export { TaskStep } from './taskbuffer.classes.taskstep.js';
12
12
  export type { ITaskStep } from './taskbuffer.classes.taskstep.js';
13
13
 
14
14
  // Metadata interfaces
15
- export type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent, TTaskEventType } from './taskbuffer.interfaces.js';
15
+ export type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent, TTaskEventType, ITaskConstraintGroupOptions } from './taskbuffer.interfaces.js';
16
16
 
17
17
  import * as distributedCoordination from './taskbuffer.classes.distributedcoordinator.js';
18
18
  export { distributedCoordination };
@@ -6,7 +6,7 @@ export class BufferRunner {
6
6
  // initialize by default
7
7
  public bufferCounter: number = 0;
8
8
 
9
- constructor(taskArg: Task<any>) {
9
+ constructor(taskArg: Task<any, any, any>) {
10
10
  this.task = taskArg;
11
11
  }
12
12
 
@@ -9,7 +9,7 @@ export interface ICycleObject {
9
9
  export class CycleCounter {
10
10
  public task: Task;
11
11
  public cycleObjectArray: ICycleObject[] = [];
12
- constructor(taskArg: Task<any>) {
12
+ constructor(taskArg: Task<any, any, any>) {
13
13
  this.task = taskArg;
14
14
  }
15
15
  public getPromiseForCycle(cycleCountArg: number) {
@@ -19,18 +19,18 @@ export type TPreOrAfterTaskFunction = () => Task<any>;
19
19
  // Type helper to extract step names from array
20
20
  export type StepNames<T> = T extends ReadonlyArray<{ name: infer N }> ? N : never;
21
21
 
22
- export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []> {
22
+ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = [], TData extends Record<string, unknown> = Record<string, unknown>> {
23
23
  public static extractTask<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
24
- preOrAfterTaskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
25
- ): Task<T, TSteps> {
24
+ preOrAfterTaskArg: Task<T, TSteps, any> | TPreOrAfterTaskFunction,
25
+ ): Task<T, TSteps, any> {
26
26
  switch (true) {
27
27
  case !preOrAfterTaskArg:
28
28
  return null;
29
29
  case preOrAfterTaskArg instanceof Task:
30
- return preOrAfterTaskArg as Task<T, TSteps>;
30
+ return preOrAfterTaskArg as Task<T, TSteps, any>;
31
31
  case typeof preOrAfterTaskArg === 'function':
32
32
  const taskFunction = preOrAfterTaskArg as TPreOrAfterTaskFunction;
33
- return taskFunction() as unknown as Task<T, TSteps>;
33
+ return taskFunction() as unknown as Task<T, TSteps, any>;
34
34
  default:
35
35
  return null;
36
36
  }
@@ -42,7 +42,7 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
42
42
  return done.promise;
43
43
  };
44
44
 
45
- public static isTask = (taskArg: Task<any>): boolean => {
45
+ public static isTask = (taskArg: Task<any, any, any>): boolean => {
46
46
  if (taskArg instanceof Task && typeof taskArg.taskFunction === 'function') {
47
47
  return true;
48
48
  } else {
@@ -51,8 +51,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
51
51
  };
52
52
 
53
53
  public static isTaskTouched<T = undefined, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
54
- taskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
55
- touchedTasksArray: Task<T, TSteps>[],
54
+ taskArg: Task<T, TSteps, any> | TPreOrAfterTaskFunction,
55
+ touchedTasksArray: Task<T, TSteps, any>[],
56
56
  ): boolean {
57
57
  const taskToCheck = Task.extractTask(taskArg);
58
58
  let result = false;
@@ -65,25 +65,16 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
65
65
  }
66
66
 
67
67
  public static runTask = async <T, TSteps extends ReadonlyArray<{ name: string; description: string; percentage: number }> = []>(
68
- taskArg: Task<T, TSteps> | TPreOrAfterTaskFunction,
69
- optionsArg: { x?: any; touchedTasksArray?: Task<T, TSteps>[] },
68
+ taskArg: Task<T, TSteps, any> | TPreOrAfterTaskFunction,
69
+ optionsArg: { x?: any; touchedTasksArray?: Task<T, TSteps, any>[] },
70
70
  ) => {
71
71
  const taskToRun = Task.extractTask(taskArg);
72
72
  const done = plugins.smartpromise.defer();
73
73
 
74
- // Wait for all blocking tasks to finish
75
- for (const task of taskToRun.blockingTasks) {
76
- await task.finished;
77
- }
78
-
79
74
  if (!taskToRun.setupValue && taskToRun.taskSetup) {
80
75
  taskToRun.setupValue = await taskToRun.taskSetup();
81
76
  }
82
77
 
83
- if (taskToRun.execDelay) {
84
- await plugins.smartdelay.delayFor(taskToRun.execDelay);
85
- }
86
-
87
78
  taskToRun.running = true;
88
79
  taskToRun.runCount++;
89
80
  taskToRun.lastRun = new Date();
@@ -100,26 +91,10 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
100
91
  // Complete all steps when task finishes
101
92
  taskToRun.completeAllSteps();
102
93
  taskToRun.emitEvent(taskToRun.lastError ? 'failed' : 'completed');
103
-
104
- // When the task has finished running, resolve the finished promise
105
- taskToRun.resolveFinished();
106
-
107
- // Create a new finished promise for the next run
108
- taskToRun.finished = new Promise((resolve) => {
109
- taskToRun.resolveFinished = resolve;
110
- });
111
94
  })
112
95
  .catch((err) => {
113
96
  taskToRun.running = false;
114
97
  taskToRun.emitEvent('failed', { error: err instanceof Error ? err.message : String(err) });
115
-
116
- // Resolve finished so blocking dependants don't hang
117
- taskToRun.resolveFinished();
118
-
119
- // Create a new finished promise for the next run
120
- taskToRun.finished = new Promise((resolve) => {
121
- taskToRun.resolveFinished = resolve;
122
- });
123
98
  });
124
99
 
125
100
  const options = {
@@ -127,7 +102,7 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
127
102
  ...optionsArg,
128
103
  };
129
104
  const x = options.x;
130
- const touchedTasksArray: Task<T, TSteps>[] = options.touchedTasksArray;
105
+ const touchedTasksArray: Task<T, TSteps, any>[] = options.touchedTasksArray;
131
106
 
132
107
  touchedTasksArray.push(taskToRun);
133
108
 
@@ -198,18 +173,12 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
198
173
  public cronJob: plugins.smarttime.CronJob;
199
174
 
200
175
  public bufferMax: number;
201
- public execDelay: number;
202
176
  public timeout: number;
203
177
 
204
- public preTask: Task<T, any> | TPreOrAfterTaskFunction;
205
- public afterTask: Task<T, any> | TPreOrAfterTaskFunction;
178
+ public data: TData;
206
179
 
207
- // Add a list to store the blocking tasks
208
- public blockingTasks: Task[] = [];
209
-
210
- // Add a promise that will resolve when the task has finished
211
- private finished: Promise<void>;
212
- private resolveFinished: () => void;
180
+ public preTask: Task<T, any, any> | TPreOrAfterTaskFunction;
181
+ public afterTask: Task<T, any, any> | TPreOrAfterTaskFunction;
213
182
 
214
183
  public running: boolean = false;
215
184
  public bufferRunner = new BufferRunner(this);
@@ -275,12 +244,12 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
275
244
 
276
245
  constructor(optionsArg: {
277
246
  taskFunction: ITaskFunction<T>;
278
- preTask?: Task<T, any> | TPreOrAfterTaskFunction;
279
- afterTask?: Task<T, any> | TPreOrAfterTaskFunction;
247
+ preTask?: Task<T, any, any> | TPreOrAfterTaskFunction;
248
+ afterTask?: Task<T, any, any> | TPreOrAfterTaskFunction;
280
249
  buffered?: boolean;
281
250
  bufferMax?: number;
282
- execDelay?: number;
283
251
  name?: string;
252
+ data?: TData;
284
253
  taskSetup?: ITaskSetupFunction<T>;
285
254
  steps?: TSteps;
286
255
  catchErrors?: boolean;
@@ -291,8 +260,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
291
260
  this.afterTask = optionsArg.afterTask;
292
261
  this.buffered = optionsArg.buffered;
293
262
  this.bufferMax = optionsArg.bufferMax;
294
- this.execDelay = optionsArg.execDelay;
295
263
  this.name = optionsArg.name;
264
+ this.data = optionsArg.data ?? ({} as TData);
296
265
  this.taskSetup = optionsArg.taskSetup;
297
266
  this.catchErrors = optionsArg.catchErrors ?? false;
298
267
  this.labels = optionsArg.labels ? { ...optionsArg.labels } : {};
@@ -309,11 +278,6 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
309
278
  this.steps.set(stepConfig.name, step);
310
279
  }
311
280
  }
312
-
313
- // Create the finished promise
314
- this.finished = new Promise((resolve) => {
315
- this.resolveFinished = resolve;
316
- });
317
281
  }
318
282
 
319
283
  public trigger(x?: any): Promise<any> {