@push.rocks/taskbuffer 3.5.0 → 4.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 (47) hide show
  1. package/dist_ts/00_commitinfo_data.js +1 -1
  2. package/dist_ts/index.d.ts +1 -1
  3. package/dist_ts/taskbuffer.classes.bufferrunner.js +17 -4
  4. package/dist_ts/taskbuffer.classes.cyclecounter.d.ts +1 -0
  5. package/dist_ts/taskbuffer.classes.cyclecounter.js +14 -1
  6. package/dist_ts/taskbuffer.classes.task.d.ts +14 -1
  7. package/dist_ts/taskbuffer.classes.task.js +89 -8
  8. package/dist_ts/taskbuffer.classes.taskchain.d.ts +2 -2
  9. package/dist_ts/taskbuffer.classes.taskchain.js +14 -5
  10. package/dist_ts/taskbuffer.classes.taskdebounced.js +14 -3
  11. package/dist_ts/taskbuffer.classes.taskmanager.d.ts +6 -1
  12. package/dist_ts/taskbuffer.classes.taskmanager.js +39 -9
  13. package/dist_ts/taskbuffer.classes.taskparallel.js +4 -2
  14. package/dist_ts/taskbuffer.classes.taskrunner.d.ts +6 -6
  15. package/dist_ts/taskbuffer.classes.taskrunner.js +19 -13
  16. package/dist_ts/taskbuffer.interfaces.d.ts +11 -0
  17. package/dist_ts_web/ts/index.d.ts +1 -1
  18. package/dist_ts_web/ts/taskbuffer.classes.bufferrunner.js +17 -4
  19. package/dist_ts_web/ts/taskbuffer.classes.cyclecounter.d.ts +1 -0
  20. package/dist_ts_web/ts/taskbuffer.classes.cyclecounter.js +14 -1
  21. package/dist_ts_web/ts/taskbuffer.classes.task.d.ts +14 -1
  22. package/dist_ts_web/ts/taskbuffer.classes.task.js +89 -8
  23. package/dist_ts_web/ts/taskbuffer.classes.taskchain.d.ts +2 -2
  24. package/dist_ts_web/ts/taskbuffer.classes.taskchain.js +14 -5
  25. package/dist_ts_web/ts/taskbuffer.classes.taskdebounced.js +14 -3
  26. package/dist_ts_web/ts/taskbuffer.classes.taskmanager.d.ts +6 -1
  27. package/dist_ts_web/ts/taskbuffer.classes.taskmanager.js +39 -9
  28. package/dist_ts_web/ts/taskbuffer.classes.taskparallel.js +4 -2
  29. package/dist_ts_web/ts/taskbuffer.classes.taskrunner.d.ts +6 -6
  30. package/dist_ts_web/ts/taskbuffer.classes.taskrunner.js +19 -13
  31. package/dist_ts_web/ts/taskbuffer.interfaces.d.ts +11 -0
  32. package/dist_ts_web/ts_web/00_commitinfo_data.js +1 -1
  33. package/npmextra.json +14 -8
  34. package/package.json +10 -6
  35. package/readme.hints.md +42 -1
  36. package/ts/00_commitinfo_data.ts +1 -1
  37. package/ts/index.ts +1 -1
  38. package/ts/taskbuffer.classes.bufferrunner.ts +14 -3
  39. package/ts/taskbuffer.classes.cyclecounter.ts +12 -0
  40. package/ts/taskbuffer.classes.task.ts +111 -20
  41. package/ts/taskbuffer.classes.taskchain.ts +17 -10
  42. package/ts/taskbuffer.classes.taskdebounced.ts +12 -2
  43. package/ts/taskbuffer.classes.taskmanager.ts +41 -9
  44. package/ts/taskbuffer.classes.taskparallel.ts +3 -1
  45. package/ts/taskbuffer.classes.taskrunner.ts +17 -12
  46. package/ts/taskbuffer.interfaces.ts +13 -0
  47. package/ts_web/00_commitinfo_data.ts +1 -1
@@ -1,28 +1,29 @@
1
1
  import * as plugins from './taskbuffer.plugins.js';
2
2
  import { Task } from './taskbuffer.classes.task.js';
3
+ import { logger } from './taskbuffer.logging.js';
3
4
  export class TaskRunner {
4
5
  constructor() {
5
- this.maxParrallelJobs = 1;
6
+ this.maxParallelJobs = 1;
6
7
  this.status = 'stopped';
7
8
  this.runningTasks = new plugins.lik.ObjectMap();
8
- this.qeuedTasks = [];
9
+ this.queuedTasks = [];
9
10
  this.runningTasks.eventSubject.subscribe(async (eventArg) => {
10
11
  this.checkExecution();
11
12
  });
12
13
  }
13
14
  /**
14
- * adds a task to the qeue
15
+ * adds a task to the queue
15
16
  */
16
17
  addTask(taskArg) {
17
- this.qeuedTasks.push(taskArg);
18
+ this.queuedTasks.push(taskArg);
18
19
  this.checkExecution();
19
20
  }
20
21
  /**
21
22
  * set amount of parallel tasks
22
- * be careful, you might loose dependability of tasks
23
+ * be careful, you might lose dependability of tasks
23
24
  */
24
- setMaxParallelJobs(maxParrallelJobsArg) {
25
- this.maxParrallelJobs = maxParrallelJobsArg;
25
+ setMaxParallelJobs(maxParallelJobsArg) {
26
+ this.maxParallelJobs = maxParallelJobsArg;
26
27
  }
27
28
  /**
28
29
  * starts the task queue
@@ -31,15 +32,20 @@ export class TaskRunner {
31
32
  this.status = 'running';
32
33
  }
33
34
  /**
34
- * checks wether execution is on point
35
+ * checks whether execution is on point
35
36
  */
36
37
  async checkExecution() {
37
- if (this.runningTasks.getArray().length < this.maxParrallelJobs &&
38
+ if (this.runningTasks.getArray().length < this.maxParallelJobs &&
38
39
  this.status === 'running' &&
39
- this.qeuedTasks.length > 0) {
40
- const nextJob = this.qeuedTasks.shift();
40
+ this.queuedTasks.length > 0) {
41
+ const nextJob = this.queuedTasks.shift();
41
42
  this.runningTasks.add(nextJob);
42
- await nextJob.trigger();
43
+ try {
44
+ await nextJob.trigger();
45
+ }
46
+ catch (err) {
47
+ logger.log('error', `TaskRunner: task "${nextJob.name || 'unnamed'}" failed: ${err instanceof Error ? err.message : String(err)}`);
48
+ }
43
49
  this.runningTasks.remove(nextJob);
44
50
  this.checkExecution();
45
51
  }
@@ -51,4 +57,4 @@ export class TaskRunner {
51
57
  this.status = 'stopped';
52
58
  }
53
59
  }
54
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFza2J1ZmZlci5jbGFzc2VzLnRhc2tydW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy90YXNrYnVmZmVyLmNsYXNzZXMudGFza3J1bm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBRW5ELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUVwRCxNQUFNLE9BQU8sVUFBVTtJQU9yQjtRQU5PLHFCQUFnQixHQUFXLENBQUMsQ0FBQztRQUM3QixXQUFNLEdBQTBCLFNBQVMsQ0FBQztRQUMxQyxpQkFBWSxHQUNqQixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFRLENBQUM7UUFDN0IsZUFBVSxHQUFXLEVBQUUsQ0FBQztRQUc3QixJQUFJLENBQUMsWUFBWSxDQUFDLFlBQVksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLFFBQVEsRUFBRSxFQUFFO1lBQzFELElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUN4QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFRDs7T0FFRztJQUNJLE9BQU8sQ0FBQyxPQUFhO1FBQzFCLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBQzlCLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztJQUN4QixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksa0JBQWtCLENBQUMsbUJBQTJCO1FBQ25ELElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxtQkFBbUIsQ0FBQztJQUM5QyxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsS0FBSztRQUNoQixJQUFJLENBQUMsTUFBTSxHQUFHLFNBQVMsQ0FBQztJQUMxQixDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsY0FBYztRQUN6QixJQUNFLElBQUksQ0FBQyxZQUFZLENBQUMsUUFBUSxFQUFFLENBQUMsTUFBTSxHQUFHLElBQUksQ0FBQyxnQkFBZ0I7WUFDM0QsSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO1lBQ3pCLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxHQUFHLENBQUMsRUFDMUIsQ0FBQztZQUNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDeEMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDL0IsTUFBTSxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7WUFDeEIsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDbEMsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3hCLENBQUM7SUFDSCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxLQUFLLENBQUMsSUFBSTtRQUNmLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBQzFCLENBQUM7Q0FDRiJ9
60
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGFza2J1ZmZlci5jbGFzc2VzLnRhc2tydW5uZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy90YXNrYnVmZmVyLmNsYXNzZXMudGFza3J1bm5lci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLHlCQUF5QixDQUFDO0FBRW5ELE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztBQUNwRCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFFakQsTUFBTSxPQUFPLFVBQVU7SUFPckI7UUFOTyxvQkFBZSxHQUFXLENBQUMsQ0FBQztRQUM1QixXQUFNLEdBQTBCLFNBQVMsQ0FBQztRQUMxQyxpQkFBWSxHQUNqQixJQUFJLE9BQU8sQ0FBQyxHQUFHLENBQUMsU0FBUyxFQUFRLENBQUM7UUFDN0IsZ0JBQVcsR0FBVyxFQUFFLENBQUM7UUFHOUIsSUFBSSxDQUFDLFlBQVksQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDLEtBQUssRUFBRSxRQUFRLEVBQUUsRUFBRTtZQUMxRCxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDeEIsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRUQ7O09BRUc7SUFDSSxPQUFPLENBQUMsT0FBYTtRQUMxQixJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUMvQixJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7SUFDeEIsQ0FBQztJQUVEOzs7T0FHRztJQUNJLGtCQUFrQixDQUFDLGtCQUEwQjtRQUNsRCxJQUFJLENBQUMsZUFBZSxHQUFHLGtCQUFrQixDQUFDO0lBQzVDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxLQUFLO1FBQ2hCLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFDO0lBQzFCLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxjQUFjO1FBQ3pCLElBQ0UsSUFBSSxDQUFDLFlBQVksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxNQUFNLEdBQUcsSUFBSSxDQUFDLGVBQWU7WUFDMUQsSUFBSSxDQUFDLE1BQU0sS0FBSyxTQUFTO1lBQ3pCLElBQUksQ0FBQyxXQUFXLENBQUMsTUFBTSxHQUFHLENBQUMsRUFDM0IsQ0FBQztZQUNELE1BQU0sT0FBTyxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDekMsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDL0IsSUFBSSxDQUFDO2dCQUNILE1BQU0sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO1lBQzFCLENBQUM7WUFBQyxPQUFPLEdBQUcsRUFBRSxDQUFDO2dCQUNiLE1BQU0sQ0FBQyxHQUFHLENBQUMsT0FBTyxFQUFFLHFCQUFxQixPQUFPLENBQUMsSUFBSSxJQUFJLFNBQVMsYUFBYSxHQUFHLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ3JJLENBQUM7WUFDRCxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQztZQUNsQyxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDeEIsQ0FBQztJQUNILENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxJQUFJO1FBQ2YsSUFBSSxDQUFDLE1BQU0sR0FBRyxTQUFTLENBQUM7SUFDMUIsQ0FBQztDQUNGIn0=
@@ -14,6 +14,9 @@ export interface ITaskMetadata {
14
14
  buffered?: boolean;
15
15
  bufferMax?: number;
16
16
  timeout?: number;
17
+ lastError?: string;
18
+ errorCount?: number;
19
+ labels?: Record<string, string>;
17
20
  }
18
21
  export interface ITaskExecutionReport {
19
22
  taskName: string;
@@ -34,3 +37,11 @@ export interface IScheduledTaskInfo {
34
37
  steps?: ITaskStep[];
35
38
  metadata?: ITaskMetadata;
36
39
  }
40
+ export type TTaskEventType = 'started' | 'step' | 'completed' | 'failed';
41
+ export interface ITaskEvent {
42
+ type: TTaskEventType;
43
+ task: ITaskMetadata;
44
+ timestamp: number;
45
+ stepName?: string;
46
+ error?: string;
47
+ }
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/taskbuffer',
6
- version: '3.5.0',
6
+ version: '4.1.0',
7
7
  description: 'A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.'
8
8
  };
9
9
  //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vdHNfd2ViLzAwX2NvbW1pdGluZm9fZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUNILE1BQU0sQ0FBQyxNQUFNLFVBQVUsR0FBRztJQUN4QixJQUFJLEVBQUUsd0JBQXdCO0lBQzlCLE9BQU8sRUFBRSxPQUFPO0lBQ2hCLFdBQVcsRUFBRSw4SUFBOEk7Q0FDNUosQ0FBQSJ9
package/npmextra.json CHANGED
@@ -1,10 +1,5 @@
1
1
  {
2
- "npmci": {
3
- "npmGlobalTools": [],
4
- "npmAccessLevel": "public",
5
- "npmRegistryUrl": "registry.npmjs.org"
6
- },
7
- "gitzone": {
2
+ "@git.zone/cli": {
8
3
  "projectType": "npm",
9
4
  "module": {
10
5
  "githost": "code.foss.global",
@@ -25,9 +20,20 @@
25
20
  "debounced tasks",
26
21
  "distributed coordination"
27
22
  ]
23
+ },
24
+ "release": {
25
+ "registries": [
26
+ "https://verdaccio.lossless.digital",
27
+ "https://registry.npmjs.org"
28
+ ],
29
+ "accessLevel": "public"
28
30
  }
29
31
  },
30
- "tsdoc": {
32
+ "@git.zone/tsdoc": {
31
33
  "legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**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.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
34
+ },
35
+ "@ship.zone/szci": {
36
+ "npmGlobalTools": [],
37
+ "npmRegistryUrl": "registry.npmjs.org"
32
38
  }
33
- }
39
+ }
package/package.json CHANGED
@@ -1,11 +1,16 @@
1
1
  {
2
2
  "name": "@push.rocks/taskbuffer",
3
- "version": "3.5.0",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "description": "A flexible task management library supporting TypeScript, allowing for task buffering, scheduling, and execution with dependency management.",
6
6
  "main": "dist_ts/index.js",
7
7
  "typings": "dist_ts/index.d.ts",
8
8
  "type": "module",
9
+ "scripts": {
10
+ "test": "(tstest test/ --verbose --logfile --timeout 120)",
11
+ "build": "(tsbuild tsfolders)",
12
+ "buildDocs": "tsdoc"
13
+ },
9
14
  "repository": {
10
15
  "type": "git",
11
16
  "url": "https://code.foss.global/push.rocks/taskbuffer.git"
@@ -60,9 +65,8 @@
60
65
  "browserslist": [
61
66
  "last 1 chrome versions"
62
67
  ],
63
- "scripts": {
64
- "test": "(tstest test/ --verbose --logfile --timeout 120)",
65
- "build": "(tsbuild tsfolders)",
66
- "buildDocs": "tsdoc"
68
+ "packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
69
+ "pnpm": {
70
+ "overrides": {}
67
71
  }
68
- }
72
+ }
package/readme.hints.md CHANGED
@@ -1 +1,42 @@
1
-
1
+ # Taskbuffer Hints
2
+
3
+ ## Error Handling (v3.6.0+)
4
+ - `Task` now has `catchErrors` constructor option (default: `false`)
5
+ - Default behavior: `trigger()` rejects when taskFunction throws (breaking change from pre-3.6)
6
+ - Set `catchErrors: true` to swallow errors (old behavior) - returns `undefined` on error
7
+ - Error state tracked via `lastError?: Error`, `errorCount: number`, `clearError()`
8
+ - `getMetadata()` status uses all four values: `'idle'` | `'running'` | `'completed'` | `'failed'`
9
+ - All peripheral classes (Taskchain, Taskparallel, TaskRunner, BufferRunner, TaskDebounced, TaskManager) have proper error propagation/handling
10
+ - `console.log` calls replaced with `logger.log()` throughout
11
+
12
+ ## Breaking API Rename (TaskRunner)
13
+ - `maxParrallelJobs` → `maxParallelJobs`
14
+ - `qeuedTasks` → `queuedTasks`
15
+ - JSDoc typos fixed: "qeue" → "queue", "wether" → "whether", "loose" → "lose"
16
+ - The `setMaxParallelJobs()` parameter also renamed from `maxParrallelJobsArg` to `maxParallelJobsArg`
17
+
18
+ ## Error Context Improvements
19
+ - **TaskChain**: Errors now wrap the original with context: chain name, failing task name, and task index. Original error preserved via `.cause`
20
+ - **BufferRunner**: When `catchErrors: false`, buffered task errors now reject the trigger promise (via `CycleCounter.informOfCycleError`) instead of silently resolving with `undefined`
21
+ - **TaskChain stubs completed**: `removeTask(task)` returns `boolean`, `shiftTask()` returns `Task | undefined`
22
+
23
+ ## Task Labels (v4.1.0+)
24
+ - `Task` constructor accepts optional `labels?: Record<string, string>`
25
+ - Helper methods: `setLabel(key, value)`, `getLabel(key)`, `removeLabel(key)`, `hasLabel(key, value?)`
26
+ - `getMetadata()` includes `labels` (shallow copy)
27
+ - `TaskManager.getTasksByLabel(key, value)` returns matching `Task[]`
28
+ - `TaskManager.getTasksMetadataByLabel(key, value)` returns matching `ITaskMetadata[]`
29
+
30
+ ## Push-Based Events (v4.1.0+)
31
+ - `Task.eventSubject`: rxjs `Subject<ITaskEvent>` emitting `'started'`, `'step'`, `'completed'`, `'failed'` events
32
+ - `TaskManager.taskSubject`: aggregated `Subject<ITaskEvent>` from all added tasks
33
+ - `TaskManager.removeTask(task)` unsubscribes and removes from map
34
+ - `TaskManager.stop()` cleans up all event subscriptions
35
+ - Exported types: `ITaskEvent`, `TTaskEventType`
36
+
37
+ ## Project Structure
38
+ - Source in `ts/`, web components in `ts_web/`
39
+ - Tests in `test/` - naming: `*.node.ts`, `*.browser.ts`, `*.both.ts`
40
+ - Logger: `ts/taskbuffer.logging.ts` exports `logger` (ConsoleLog from smartlog)
41
+ - Build: `pnpm build` (tsbuild tsfolders)
42
+ - Test: `pnpm test` or `tstest test/test.XX.name.ts --verbose`
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/taskbuffer',
6
- version: '3.5.0',
6
+ version: '4.1.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
@@ -12,7 +12,7 @@ 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 } from './taskbuffer.interfaces.js';
15
+ export type { ITaskMetadata, ITaskExecutionReport, IScheduledTaskInfo, ITaskEvent, TTaskEventType } from './taskbuffer.interfaces.js';
16
16
 
17
17
  import * as distributedCoordination from './taskbuffer.classes.distributedcoordinator.js';
18
18
  export { distributedCoordination };
@@ -1,4 +1,5 @@
1
1
  import { Task } from './taskbuffer.classes.task.js';
2
+ import { logger } from './taskbuffer.logging.js';
2
3
 
3
4
  export class BufferRunner {
4
5
  public task: Task;
@@ -24,9 +25,19 @@ export class BufferRunner {
24
25
  private async _run(x: any) {
25
26
  this.task.running = true;
26
27
  while (this.bufferCounter > 0) {
27
- const result = await Task.runTask(this.task, { x: x });
28
- this.bufferCounter--;
29
- this.task.cycleCounter.informOfCycle(result);
28
+ try {
29
+ const result = await Task.runTask(this.task, { x: x });
30
+ this.bufferCounter--;
31
+ this.task.cycleCounter.informOfCycle(result);
32
+ } catch (err) {
33
+ logger.log('error', `BufferRunner: task "${this.task.name || 'unnamed'}" failed: ${err instanceof Error ? err.message : String(err)}`);
34
+ this.bufferCounter--;
35
+ if (this.task.catchErrors) {
36
+ this.task.cycleCounter.informOfCycle(undefined);
37
+ } else {
38
+ this.task.cycleCounter.informOfCycleError(err instanceof Error ? err : new Error(String(err)));
39
+ }
40
+ }
30
41
  }
31
42
  this.task.running = false;
32
43
  }
@@ -33,4 +33,16 @@ export class CycleCounter {
33
33
  });
34
34
  this.cycleObjectArray = newCycleObjectArray;
35
35
  }
36
+ public informOfCycleError(err: Error) {
37
+ const newCycleObjectArray: ICycleObject[] = [];
38
+ this.cycleObjectArray.forEach((cycleObjectArg) => {
39
+ cycleObjectArg.cycleCounter--;
40
+ if (cycleObjectArg.cycleCounter <= 0) {
41
+ cycleObjectArg.deferred.reject(err);
42
+ } else {
43
+ newCycleObjectArray.push(cycleObjectArg);
44
+ }
45
+ });
46
+ this.cycleObjectArray = newCycleObjectArray;
47
+ }
36
48
  }
@@ -2,7 +2,7 @@ import * as plugins from './taskbuffer.plugins.js';
2
2
  import { BufferRunner } from './taskbuffer.classes.bufferrunner.js';
3
3
  import { CycleCounter } from './taskbuffer.classes.cyclecounter.js';
4
4
  import { TaskStep, type ITaskStep } from './taskbuffer.classes.taskstep.js';
5
- import type { ITaskMetadata } from './taskbuffer.interfaces.js';
5
+ import type { ITaskMetadata, ITaskEvent, TTaskEventType } from './taskbuffer.interfaces.js';
6
6
 
7
7
  import { logger } from './taskbuffer.logging.js';
8
8
 
@@ -87,24 +87,40 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
87
87
  taskToRun.running = true;
88
88
  taskToRun.runCount++;
89
89
  taskToRun.lastRun = new Date();
90
-
91
- // Reset steps at the beginning of task execution
90
+
91
+ // Reset steps and error state at the beginning of task execution
92
92
  taskToRun.resetSteps();
93
+ taskToRun.lastError = undefined;
94
+ taskToRun.emitEvent('started');
95
+
96
+ done.promise
97
+ .then(async () => {
98
+ taskToRun.running = false;
93
99
 
94
- done.promise.then(async () => {
95
- taskToRun.running = false;
96
-
97
- // Complete all steps when task finishes
98
- taskToRun.completeAllSteps();
100
+ // Complete all steps when task finishes
101
+ taskToRun.completeAllSteps();
102
+ taskToRun.emitEvent(taskToRun.lastError ? 'failed' : 'completed');
99
103
 
100
- // When the task has finished running, resolve the finished promise
101
- taskToRun.resolveFinished();
104
+ // When the task has finished running, resolve the finished promise
105
+ taskToRun.resolveFinished();
102
106
 
103
- // Create a new finished promise for the next run
104
- taskToRun.finished = new Promise((resolve) => {
105
- taskToRun.resolveFinished = resolve;
107
+ // Create a new finished promise for the next run
108
+ taskToRun.finished = new Promise((resolve) => {
109
+ taskToRun.resolveFinished = resolve;
110
+ });
111
+ })
112
+ .catch((err) => {
113
+ taskToRun.running = false;
114
+ 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
+ });
106
123
  });
107
- });
108
124
 
109
125
  const options = {
110
126
  ...{ x: undefined, touchedTasksArray: [] },
@@ -133,7 +149,13 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
133
149
  try {
134
150
  return await taskToRun.taskFunction(x, taskToRun.setupValue);
135
151
  } catch (e) {
136
- console.log(e);
152
+ taskToRun.lastError = e instanceof Error ? e : new Error(String(e));
153
+ taskToRun.errorCount++;
154
+ logger.log('error', `Task "${taskToRun.name || 'unnamed'}" failed: ${taskToRun.lastError.message}`);
155
+ if (taskToRun.catchErrors) {
156
+ return undefined;
157
+ }
158
+ throw e;
137
159
  }
138
160
  })
139
161
  .then((x) => {
@@ -155,10 +177,18 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
155
177
  done.resolve(x);
156
178
  })
157
179
  .catch((err) => {
158
- console.log(err);
180
+ done.reject(err);
159
181
  });
160
182
  localDeferred.resolve();
161
- return await done.promise;
183
+
184
+ try {
185
+ return await done.promise;
186
+ } catch (err) {
187
+ if (taskToRun.catchErrors) {
188
+ return undefined;
189
+ }
190
+ throw err;
191
+ }
162
192
  };
163
193
 
164
194
  public name: string;
@@ -187,10 +217,53 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
187
217
  public lastRun?: Date;
188
218
  public runCount: number = 0;
189
219
 
220
+ // Error handling
221
+ public catchErrors: boolean = false;
222
+ public lastError?: Error;
223
+ public errorCount: number = 0;
224
+ public labels: Record<string, string> = {};
225
+ public readonly eventSubject = new plugins.smartrx.rxjs.Subject<ITaskEvent>();
226
+
190
227
  public get idle() {
191
228
  return !this.running;
192
229
  }
193
230
 
231
+ public clearError(): void {
232
+ this.lastError = undefined;
233
+ }
234
+
235
+ public setLabel(key: string, value: string): void {
236
+ this.labels[key] = value;
237
+ }
238
+
239
+ public getLabel(key: string): string | undefined {
240
+ return this.labels[key];
241
+ }
242
+
243
+ public removeLabel(key: string): boolean {
244
+ if (key in this.labels) {
245
+ delete this.labels[key];
246
+ return true;
247
+ }
248
+ return false;
249
+ }
250
+
251
+ public hasLabel(key: string, value?: string): boolean {
252
+ if (value !== undefined) {
253
+ return this.labels[key] === value;
254
+ }
255
+ return key in this.labels;
256
+ }
257
+
258
+ private emitEvent(type: TTaskEventType, extra?: Partial<ITaskEvent>): void {
259
+ this.eventSubject.next({
260
+ type,
261
+ task: this.getMetadata(),
262
+ timestamp: Date.now(),
263
+ ...extra,
264
+ });
265
+ }
266
+
194
267
  public taskSetup: ITaskSetupFunction<T>;
195
268
  public setupValue: T;
196
269
 
@@ -210,6 +283,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
210
283
  name?: string;
211
284
  taskSetup?: ITaskSetupFunction<T>;
212
285
  steps?: TSteps;
286
+ catchErrors?: boolean;
287
+ labels?: Record<string, string>;
213
288
  }) {
214
289
  this.taskFunction = optionsArg.taskFunction;
215
290
  this.preTask = optionsArg.preTask;
@@ -219,6 +294,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
219
294
  this.execDelay = optionsArg.execDelay;
220
295
  this.name = optionsArg.name;
221
296
  this.taskSetup = optionsArg.taskSetup;
297
+ this.catchErrors = optionsArg.catchErrors ?? false;
298
+ this.labels = optionsArg.labels ? { ...optionsArg.labels } : {};
222
299
 
223
300
  // Initialize steps if provided
224
301
  if (optionsArg.steps) {
@@ -271,8 +348,8 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
271
348
  if (step) {
272
349
  step.start();
273
350
  this.currentStepName = stepName as string;
274
-
275
- // Emit event for frontend updates (could be enhanced with event emitter)
351
+ this.emitEvent('step', { stepName: stepName as string });
352
+
276
353
  if (this.name) {
277
354
  logger.log('info', `Task ${this.name}: Starting step "${stepName}" - ${step.description}`);
278
355
  }
@@ -306,10 +383,21 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
306
383
 
307
384
  // Get task metadata
308
385
  public getMetadata(): ITaskMetadata {
386
+ let status: 'idle' | 'running' | 'completed' | 'failed';
387
+ if (this.running) {
388
+ status = 'running';
389
+ } else if (this.lastError) {
390
+ status = 'failed';
391
+ } else if (this.runCount > 0) {
392
+ status = 'completed';
393
+ } else {
394
+ status = 'idle';
395
+ }
396
+
309
397
  return {
310
398
  name: this.name || 'unnamed',
311
399
  version: this.version,
312
- status: this.running ? 'running' : 'idle',
400
+ status,
313
401
  steps: this.getStepsMetadata(),
314
402
  currentStep: this.currentStepName,
315
403
  currentProgress: this.getProgress(),
@@ -318,6 +406,9 @@ export class Task<T = undefined, TSteps extends ReadonlyArray<{ name: string; de
318
406
  bufferMax: this.bufferMax,
319
407
  timeout: this.timeout,
320
408
  cronSchedule: this.cronJob?.cronExpression,
409
+ lastError: this.lastError?.message,
410
+ errorCount: this.errorCount,
411
+ labels: { ...this.labels },
321
412
  };
322
413
  }
323
414
 
@@ -27,18 +27,20 @@ export class Taskchain extends Task {
27
27
  let taskCounter = 0; // counter for iterating async over the taskArray
28
28
  const iterateTasks = (x: any) => {
29
29
  if (typeof this.taskArray[taskCounter] !== 'undefined') {
30
- console.log(
31
- this.name + ' running: Task' + this.taskArray[taskCounter].name,
32
- );
30
+ logger.log('info', `${this.name} running: Task ${this.taskArray[taskCounter].name}`);
33
31
  this.taskArray[taskCounter].trigger(x).then((x) => {
34
32
  logger.log('info', this.taskArray[taskCounter].name);
35
33
  taskCounter++;
36
34
  iterateTasks(x);
35
+ }).catch((err) => {
36
+ const chainError = new Error(
37
+ `Taskchain "${this.name}": task "${this.taskArray[taskCounter].name || 'unnamed'}" (index ${taskCounter}) failed: ${err instanceof Error ? err.message : String(err)}`
38
+ );
39
+ (chainError as any).cause = err;
40
+ done.reject(chainError);
37
41
  });
38
42
  } else {
39
- console.log(
40
- 'Taskchain "' + this.name + '" completed successfully',
41
- );
43
+ logger.log('info', `Taskchain "${this.name}" completed successfully`);
42
44
  done.resolve(x);
43
45
  }
44
46
  };
@@ -53,10 +55,15 @@ export class Taskchain extends Task {
53
55
  addTask(taskArg: Task) {
54
56
  this.taskArray.push(taskArg);
55
57
  }
56
- removeTask(taskArg: Task) {
57
- // TODO:
58
+ removeTask(taskArg: Task): boolean {
59
+ const index = this.taskArray.indexOf(taskArg);
60
+ if (index === -1) {
61
+ return false;
62
+ }
63
+ this.taskArray.splice(index, 1);
64
+ return true;
58
65
  }
59
- shiftTask() {
60
- // TODO:
66
+ shiftTask(): Task | undefined {
67
+ return this.taskArray.shift();
61
68
  }
62
69
  }
@@ -1,6 +1,7 @@
1
1
  import * as plugins from './taskbuffer.plugins.js';
2
2
 
3
3
  import { Task, type ITaskFunction } from './taskbuffer.classes.task.js';
4
+ import { logger } from './taskbuffer.logging.js';
4
5
 
5
6
  export class TaskDebounced<T = unknown> extends Task {
6
7
  private _debouncedTaskFunction: ITaskFunction;
@@ -22,8 +23,17 @@ export class TaskDebounced<T = unknown> extends Task {
22
23
  .pipe(
23
24
  plugins.smartrx.rxjs.ops.debounceTime(optionsArg.debounceTimeInMillis),
24
25
  )
25
- .subscribe((x) => {
26
- this.taskFunction(x);
26
+ .subscribe({
27
+ next: async (x) => {
28
+ try {
29
+ await this.taskFunction(x);
30
+ } catch (err) {
31
+ logger.log('error', `TaskDebounced "${this.name || 'unnamed'}" failed: ${err instanceof Error ? err.message : String(err)}`);
32
+ }
33
+ },
34
+ error: (err) => {
35
+ logger.log('error', `TaskDebounced "${this.name || 'unnamed'}" observable error: ${err instanceof Error ? err.message : String(err)}`);
36
+ },
27
37
  });
28
38
  }
29
39
  }