clever-queue 0.1.1 → 0.2.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 (79) hide show
  1. package/.gitlab-ci.yml +80 -0
  2. package/README.md +66 -6
  3. package/dist/engine/index.d.ts +14 -10
  4. package/dist/engine/index.js +45 -46
  5. package/dist/engine/index.js.map +1 -1
  6. package/dist/engine/interfaces.d.ts +3 -2
  7. package/dist/engine/interfaces.js.map +1 -1
  8. package/dist/helpers/id.d.ts +2 -0
  9. package/dist/helpers/id.js +7 -0
  10. package/dist/helpers/id.js.map +1 -0
  11. package/dist/helpers/index.d.ts +1 -0
  12. package/dist/helpers/index.js +19 -8
  13. package/dist/helpers/index.js.map +1 -1
  14. package/dist/index.d.ts +6 -6
  15. package/dist/index.js +25 -15
  16. package/dist/index.js.map +1 -1
  17. package/dist/{queue → queues}/index.d.ts +8 -5
  18. package/dist/{queue → queues}/index.js +27 -33
  19. package/dist/queues/index.js.map +1 -0
  20. package/dist/{queue → queues}/interfaces.d.ts +9 -4
  21. package/dist/{queue → queues}/interfaces.js +4 -2
  22. package/dist/queues/interfaces.js.map +1 -0
  23. package/dist/{runner → runners}/index.d.ts +7 -3
  24. package/dist/{runner → runners}/index.js +33 -15
  25. package/dist/runners/index.js.map +1 -0
  26. package/dist/{runner → runners}/interfaces.d.ts +2 -1
  27. package/dist/{runner → runners}/interfaces.js +1 -1
  28. package/dist/runners/interfaces.js.map +1 -0
  29. package/dist/{task → tasks}/index.d.ts +8 -2
  30. package/dist/{task → tasks}/index.js +35 -16
  31. package/dist/tasks/index.js.map +1 -0
  32. package/dist/{task → tasks}/interfaces.d.ts +2 -1
  33. package/dist/{task → tasks}/interfaces.js +4 -0
  34. package/dist/tasks/interfaces.js.map +1 -0
  35. package/documentation/demo_1E_1Q_1R_4T.svg +1 -0
  36. package/documentation/demo_1E_1Q_2R_4T.svg +1 -0
  37. package/documentation/demo_1E_2Q_1R_8T.svg +1 -0
  38. package/eslint.config.mjs +68 -68
  39. package/examples/package-lock.json +19 -0
  40. package/examples/package.json +15 -0
  41. package/examples/src/demo_1E_1Q_1R_4T.ts +37 -0
  42. package/examples/src/demo_1E_1Q_2R_4T.ts +36 -0
  43. package/examples/src/demo_1E_2Q_1R_8T.ts +40 -0
  44. package/examples/src/example00.ts +16 -0
  45. package/examples/src/helpers/animations.ts +295 -0
  46. package/examples/src/helpers/index.ts +2 -0
  47. package/examples/src/helpers/myAsyncTaskToExecute.ts +6 -0
  48. package/examples/src/helpers/svg.ts +155 -0
  49. package/examples/src/tsconfig.json +24 -0
  50. package/package.json +65 -55
  51. package/src/engine/index.ts +37 -48
  52. package/src/engine/interfaces.ts +3 -2
  53. package/src/helpers/id.ts +4 -0
  54. package/src/helpers/index.ts +1 -0
  55. package/src/index.ts +7 -7
  56. package/src/{queue → queues}/index.ts +11 -28
  57. package/src/{queue → queues}/interfaces.ts +4 -3
  58. package/src/{runner → runners}/index.ts +17 -12
  59. package/src/{runner → runners}/interfaces.ts +2 -1
  60. package/src/{task → tasks}/index.ts +18 -11
  61. package/src/{task → tasks}/interfaces.ts +6 -1
  62. package/test/issues/00001.mjs +35 -0
  63. package/test/miscellaneous/test.mjs +13 -13
  64. package/test/units/engine.mjs +5 -5
  65. package/test/units/queue.mjs +4 -4
  66. package/test/units/task.mjs +6 -6
  67. package/dist/errors.d.ts +0 -13
  68. package/dist/errors.js +0 -42
  69. package/dist/errors.js.map +0 -1
  70. package/dist/queue/index.js.map +0 -1
  71. package/dist/queue/interfaces.js.map +0 -1
  72. package/dist/runner/index.js.map +0 -1
  73. package/dist/runner/interfaces.js.map +0 -1
  74. package/dist/task/index.js.map +0 -1
  75. package/dist/task/interfaces.js.map +0 -1
  76. package/exemples/index01.js +0 -116
  77. package/exemples/index01.ts +0 -98
  78. package/exemples/index02.mjs +0 -13
  79. package/exemples/tsconfig.json +0 -24
package/package.json CHANGED
@@ -1,55 +1,65 @@
1
- {
2
- "name": "clever-queue",
3
- "version": "0.1.1",
4
- "description": "Queuing system for promises that handle concurring, throttling, weighting and prioritizing in a clever fashion.",
5
- "main": "dist/index.js",
6
- "scripts": {
7
- "build": "npx tsc -p src/ --declaration",
8
- "build:watch": "npx tsc -p src/ -w",
9
- "prestart": "npx npm run build",
10
- "start": "node dist/index.js",
11
- "debug": "node --nolazy --inspect-brk=9229 dist/index.js",
12
- "test": "node --test ./test/**/*.mjs",
13
- "test:unit:watch": "node --test --watch ./test/units/*.mjs"
14
- },
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://gitlab.com/job-so/clever-queue.git"
18
- },
19
- "keywords": [
20
- "queue",
21
- "clever",
22
- "concurrency",
23
- "throttling",
24
- "priority",
25
- "weight",
26
- "promise"
27
- ],
28
- "author": "Olivier GROSJEANNE",
29
- "license": "ISC",
30
- "bugs": {
31
- "url": "https://gitlab.com/job-so/clever-queue/issues"
32
- },
33
- "homepage": "https://gitlab.com/job-so/clever-queue#readme",
34
- "devDependencies": {
35
- "@eslint/eslintrc": "^3.1",
36
- "@eslint/js": "^9.8",
37
- "@opentelemetry/api": "^1.9.0",
38
- "@opentelemetry/sdk-metrics": "^1.26",
39
- "@opentelemetry/sdk-node": "^0.53",
40
- "@opentelemetry/sdk-trace-node": "^1.26",
41
- "@types/eslint__js": "^8.42",
42
- "@types/node": "^22.1",
43
- "eslint": "^9.8",
44
- "eslint-config-prettier": "^9.1",
45
- "eslint-plugin-node": "^11.1",
46
- "eslint-plugin-prettier": "^5.1",
47
- "eslint-plugin-promise": "^6.4",
48
- "eslint-plugin-security": "^3.0",
49
- "eslint-plugin-unicorn": "^54.0",
50
- "globals": "^15.9",
51
- "node": "^22.4",
52
- "typescript": "^5.5",
53
- "typescript-eslint": "^8.0"
54
- }
55
- }
1
+ {
2
+ "name": "clever-queue",
3
+ "version": "0.2.0",
4
+ "description": "Queuing system for promises that handle concurring, throttling, weighting and prioritizing in a clever fashion.",
5
+ "main": "dist/index.js",
6
+ "scripts": {
7
+ "build": "npx tsc -p src/ --declaration",
8
+ "build:examples": "npx tsc -p examples/src/",
9
+ "build:watch": "npx tsc -p src/ -w",
10
+ "prestart": "npx npm run build",
11
+ "start": "node dist/index.js",
12
+ "debug": "node --nolazy --inspect-brk=9229 dist/index.js",
13
+ "test": "node --test ./test/**/*.mjs",
14
+ "test:junit": "node --test --test-reporter=junit --test-reporter-destination=junit.xml ./test/**/*.mjs",
15
+ "test:unit:watch": "node --test --watch ./test/units/*.mjs",
16
+ "semantic-release": "npx semantic-release"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://gitlab.com/job-so/clever-queue.git"
21
+ },
22
+ "release": {
23
+ "branches": [
24
+ "v0"
25
+ ]
26
+ },
27
+ "keywords": [
28
+ "queue",
29
+ "clever",
30
+ "concurrency",
31
+ "throttling",
32
+ "priority",
33
+ "weight",
34
+ "promise"
35
+ ],
36
+ "author": "Olivier GROSJEANNE",
37
+ "license": "ISC",
38
+ "bugs": {
39
+ "url": "https://gitlab.com/job-so/clever-queue/issues"
40
+ },
41
+ "homepage": "https://gitlab.com/job-so/clever-queue#readme",
42
+ "devDependencies": {
43
+ "@eslint/eslintrc": "^3.1",
44
+ "@eslint/js": "^9.8",
45
+ "@opentelemetry/api": "^1.9.0",
46
+ "@opentelemetry/sdk-metrics": "^1.26",
47
+ "@opentelemetry/sdk-node": "^0.53",
48
+ "@opentelemetry/sdk-trace-node": "^1.26",
49
+ "@types/eslint__js": "^8.42",
50
+ "@types/node": "^22.1",
51
+ "eslint": "^9.8",
52
+ "eslint-config-prettier": "^9.1",
53
+ "eslint-plugin-node": "^11.1",
54
+ "eslint-plugin-prettier": "^5.1",
55
+ "eslint-plugin-promise": "^6.4",
56
+ "eslint-plugin-security": "^3.0",
57
+ "eslint-plugin-unicorn": "^54.0",
58
+ "fast-xml-parser": "^5.0.9",
59
+ "globals": "^15.9",
60
+ "node": "^24.12",
61
+ "semantic-release": "^25.0.2",
62
+ "typescript": "^5.9",
63
+ "typescript-eslint": "^8.50"
64
+ }
65
+ }
@@ -1,13 +1,13 @@
1
1
  // clever-queue
2
2
 
3
3
  import * as _ from "../helpers";
4
- import * as Queue from "../queue";
5
- import * as Runner from "../runner";
6
- import * as Task from "../task";
4
+ import * as cqQueue from "../queues";
5
+ import * as cqRunner from "../runners";
6
+ import * as cqTask from "../tasks";
7
7
 
8
8
  type Status = "unknown" | "initialiazing" | "running" | "stopping" | "destroying";
9
9
 
10
- const TICK_START = 42;
10
+ const TICK_START = 42; // RANDOM ? ;-) value to avoid zero division
11
11
 
12
12
  import { Options, Statistics, ErrorsList } from "./interfaces";
13
13
 
@@ -19,18 +19,21 @@ const defaultOptions: Options = {
19
19
 
20
20
  class Engine {
21
21
  status: Status = "unknown";
22
- readonly runners: Runner.Runner[] = [];
23
- readonly queues: Queue.Queue[] = [];
22
+ readonly options: Options;
23
+ tags: { [key: string]: string | number | boolean } = {};
24
+ readonly runners: cqRunner.Runner[] = [];
25
+ readonly queues: cqQueue.Queue[] = [];
24
26
 
25
27
  constructor(opt: Options) {
26
28
  this.status = "initialiazing";
27
- const options = { ...defaultOptions, ...opt };
28
- this.#checkOptionsConsistancy(options);
29
+ this.options = { ...defaultOptions, ...opt };
30
+ if (!this.options.id) this.options.id = _.Id.generate();
31
+ this.#checkOptionsConsistancy(this.options);
29
32
 
30
33
  // create runners
31
- for (let index = 0; index < (options.bestEffortRunners ?? 0); index++) this.createRunner({ priority: Runner.Priorities.BestEffort });
32
- if (options.logFunction) options.logFunction({ body: "engine has been initialized" });
33
- if (options.autostart) this.start();
34
+ for (let index = 0; index < (this.options.bestEffortRunners ?? 0); index++) this.createRunner({ priority: cqRunner.Priorities.BestEffort });
35
+ if (this.options.logFunction) this.options.logFunction({ body: "engine has been initialized" });
36
+ if (this.options.autostart) this.start();
34
37
  }
35
38
 
36
39
  start(): void {
@@ -43,21 +46,21 @@ class Engine {
43
46
  for (const runner of this.runners) runner.stop();
44
47
  }
45
48
 
46
- createQueue(options: Queue.Options): Queue.Queue {
47
- const queue = new Queue.Queue({ logFunction: options.logFunction, ...options });
49
+ createQueue(options: cqQueue.Options = { priority: cqQueue.Priorities.Standard, weight: cqQueue.Weights.Default }): cqQueue.Queue {
50
+ const queue = new cqQueue.Queue({ logFunction: options.logFunction, ...options });
48
51
  this.queues.push(queue);
49
52
  this.#resetLastTick(options.priority);
50
53
  return queue;
51
54
  }
52
55
 
53
- createRunner(options: Runner.Options): Runner.Runner {
54
- const runner = new Runner.Runner({ logFunction: options.logFunction, ...options }, this);
56
+ createRunner(options: cqRunner.Options): cqRunner.Runner {
57
+ const runner = new cqRunner.Runner({ logFunction: options.logFunction, ...options }, this);
55
58
  this.runners.push(runner);
56
59
  return runner;
57
60
  }
58
61
 
59
- createTask(function_: Task.FunctionToExecute, options: Task.Options): Task.Task {
60
- return new Task.Task(function_, { logFunction: options.logFunction, ...options });
62
+ createTask(function_: cqTask.FunctionToExecute, options: cqTask.Options): cqTask.Task {
63
+ return new cqTask.Task(function_, { logFunction: options.logFunction, ...options });
61
64
  }
62
65
 
63
66
  #checkOptionsConsistancy(options: Options): void {
@@ -65,19 +68,25 @@ class Engine {
65
68
  if (!options.bestEffortRunners || options.bestEffortRunners < 1) throw new _.Errors.CQError(ErrorsList.bestEffortRunnersOptionMustBeGreaterThanZero);
66
69
  }
67
70
 
68
- #getSumWeightFunction: Queue.GetSumWeightFunction = (priority: number): number => {
71
+ #getSumWeightFunction: cqQueue.GetSumWeightFunction = (priority: number): number => {
69
72
  let result = 0;
70
73
  for (const q of this.queues) if (q.options.priority === priority) result += q.options.weight;
71
74
  return result;
72
75
  };
73
76
 
74
77
  #resetLastTick(priority: number): void {
75
- for (const q of this.queues) if (q.options.priority === priority) q.tick = TICK_START;
78
+ for (let i = 0; i < this.queues.length; i++) {
79
+ const q = this.queues[i];
80
+ if (q.options.priority === priority)
81
+ q.tick = TICK_START
82
+ + this.#getSumWeightFunction(priority) / q.options.weight // set tick to max weight / queue weight to favorize low weight queues
83
+ + i / 255; // slight increment to favorize first created queues
84
+ }
76
85
  }
77
86
 
78
- #getNextWeightedQueue(priority: number): Queue.Queue | undefined {
87
+ #getNextWeightedQueue(priority: number): cqQueue.Queue | undefined {
79
88
  let minTick = Infinity;
80
- let result: Queue.Queue | undefined = undefined;
89
+ let result: cqQueue.Queue | undefined = undefined;
81
90
  for (const q of this.queues)
82
91
  if (q.options.priority === priority && q.tick <= minTick) {
83
92
  minTick = q.tick;
@@ -91,32 +100,12 @@ class Engine {
91
100
  for (const q of this.queues) if (q.options.priority === priority) result += q.tasks.length;
92
101
  return result;
93
102
  }
94
- /*
95
- async getNextTask(priorityGreaterOrEqualTo: number): Promise<Task.Task> {
96
- for (let priority = Queue.Priorities.Absolute; priority <= priorityGreaterOrEqualTo; priority++) {
97
- // Iterate on Each PriorityLevel From Absolute to param function
98
- while (this.#howMuchTasksWaitingInQueues(priority) > 0) {
99
- let task: Task.Task | undefined;
100
- while (!task) {
101
- const nextPriorityQueue = this.#getNextQueue(priority); // Find Next Priority Queue to process
102
- if (nextPriorityQueue) {
103
- nextPriorityQueue.tick += this.#getSumWeightFunction(priority) / nextPriorityQueue.options.weight; // Update tick with related weight
104
- if (nextPriorityQueue.tasks.length > 0) {
105
- task = nextPriorityQueue.tasks.shift(); // If queue is not empty, this is our next Task to process
106
- if (task) task.runner();
107
- console.log(nextPriorityQueue.options.name, nextPriorityQueue.tick);
108
- }
109
- }
110
- }
111
- }
112
- }
113
- }
114
- */
115
103
 
116
- async getNextTask(priorityGreaterOrEqualTo: number): Promise<Task.Task | undefined> {
117
- let task: Task.Task | undefined = undefined;
104
+ async getNextTask(priorityGreaterOrEqualTo: number): Promise<cqTask.Task | undefined> {
105
+ if (this.status !== "running") return undefined;
106
+ let task: cqTask.Task | undefined = undefined;
118
107
  // Iterate on Each PriorityLevel From Absolute to parameter
119
- for (let priority = Queue.Priorities.Absolute; priority <= priorityGreaterOrEqualTo; priority++) {
108
+ for (let priority = cqQueue.Priorities.Absolute; priority >= priorityGreaterOrEqualTo; priority--) {
120
109
  // Check if there is task waiting of this priority level
121
110
  while (this.#howMuchTasksWaitingInQueues(priority) > 0) {
122
111
  // Find Next Weighted Queue of this priority level to check
@@ -138,8 +127,8 @@ class Engine {
138
127
 
139
128
  statistics(): Statistics {
140
129
  let taskCount = 0;
141
- const runners: Runner.Statistics[] = [];
142
- const queues: Queue.Statistics[] = [];
130
+ const runners: cqRunner.Statistics[] = [];
131
+ const queues: cqQueue.Statistics[] = [];
143
132
  for (const runner of this.runners) {
144
133
  if (runner.status == "running") taskCount += 1;
145
134
  runners.push(runner.statistics());
@@ -162,5 +151,5 @@ class Engine {
162
151
  }
163
152
  }
164
153
 
165
- export { Options, Statistics, ErrorsList } from "./interfaces";
154
+ export { Options, Statistics, ErrorsList, LogFunction } from "./interfaces";
166
155
  export { Engine };
@@ -1,9 +1,10 @@
1
1
  import * as _ from "../helpers";
2
2
 
3
- import * as Runner from "../runner/interfaces";
4
- import * as Queue from "../runner/interfaces";
3
+ import * as Runner from "../runners/interfaces";
4
+ import * as Queue from "../runners/interfaces";
5
5
 
6
6
  interface Options {
7
+ id?: string;
7
8
  autostart?: boolean;
8
9
  bestEffortRunners?: number;
9
10
  logFunction?: _.Logs.LogFunction;
@@ -0,0 +1,4 @@
1
+ function generate(): string {
2
+ return Math.floor(Math.random() * Date.now()).toString(16);
3
+ }
4
+ export { generate };
@@ -1,3 +1,4 @@
1
1
  export * as Errors from "./errors";
2
2
  export * as Logs from "./logs";
3
3
  export * as Traces from "./traces";
4
+ export * as Id from "./id";
package/src/index.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  // clever-queue
2
- import * as Engine from "./engine";
2
+ import * as engine from "./engine";
3
3
 
4
- function createEngine(options: Engine.Options): Engine.Engine {
5
- return new Engine.Engine(options);
4
+ function createEngine(options: engine.Options = {}): engine.Engine {
5
+ return new engine.Engine(options);
6
6
  }
7
7
 
8
- export * as Engine from "./engine/interfaces";
9
- export * as Runner from "./runner/interfaces";
10
- export * as Queue from "./queue/interfaces";
11
- export * as Task from "./task/interfaces";
8
+ export * as engine from "./engine";
9
+ export * as runners from "./runners";
10
+ export * as queues from "./queues";
11
+ export * as tasks from "./tasks";
12
12
 
13
13
  export { createEngine };
@@ -1,7 +1,7 @@
1
1
  // clever-queue
2
2
 
3
3
  import * as _ from "../helpers";
4
- import * as Task from "../task";
4
+ import * as cqTask from "../tasks";
5
5
  import { Options, Statistics, ErrorsList, Priorities } from "./interfaces";
6
6
 
7
7
  type GetSumWeightFunction = (priority: number) => number;
@@ -18,12 +18,15 @@ const defaultOptions: Options = {
18
18
  class Queue {
19
19
  status: Status = "unknown";
20
20
  readonly options: Options;
21
+ tags: { [key: string]: string | number | boolean } = {};
22
+
21
23
  tick: number = 0;
22
- tasks: Task.Task[] = [];
24
+ tasks: cqTask.Task[] = [];
23
25
 
24
26
  constructor(opt: Options) {
25
27
  this.status = "initialiazing";
26
28
  this.options = this.#checkOptionsConsistancy({ ...defaultOptions, ...opt });
29
+ if (!this.options.id) this.options.id = _.Id.generate();
27
30
  if (this.options.autostart) this.start();
28
31
  }
29
32
 
@@ -43,34 +46,14 @@ class Queue {
43
46
  this.status = "stopping";
44
47
  }
45
48
 
46
- /*
47
- async createTask(function_: Task._function): Promise<unknown> {
48
- if (typeof function_ !== "function") {
49
- console.error("raisong Task.ErrorsList.FunctionIsNotAFunction", Task.ErrorsList.FunctionIsNotAFunction);
50
- throw new _.Errors.CQError(Task.ErrorsList.FunctionIsNotAFunction);
51
- }
52
- return new Promise((resolve, reject) => {
53
- try {
54
- const taskRunner = async (): Promise<void> => {
55
- console.log("running Task");
56
- const result = await function_();
57
- console.log("task runned", result);
58
- resolve(result);
59
- };
60
- const task = new Task.Task(taskRunner, {});
61
- this.tasks.push(task);
62
- } catch (error) {
63
- reject(error);
64
- }
65
- });
66
- }
67
- */
68
- async createTaskAndEnqueue(function_: Task.FunctionToExecute, options: Task.Options): Promise<unknown> {
69
- const task = new Task.Task(function_, options);
49
+ async createTaskAndEnqueue(function_: cqTask.FunctionToExecute, options: cqTask.Options = {}): Promise<unknown> {
50
+ const task = new cqTask.Task(function_, options);
70
51
  return this.enqueue(task);
71
52
  }
72
53
 
73
- async enqueue(task: Task.Task): Promise<unknown> {
54
+ async enqueue(task: cqTask.Task): Promise<unknown> {
55
+ task.status = "queued";
56
+ task.queueId = this.options.id; // store queue that handle task for future references
74
57
  return new Promise((resolve, reject) => {
75
58
  task.resolver = resolve;
76
59
  task.rejecter = reject;
@@ -89,5 +72,5 @@ class Queue {
89
72
  }
90
73
  }
91
74
 
92
- export { Options, Statistics, ErrorsList, Priorities } from "./interfaces";
75
+ export { Options, Statistics, ErrorsList, Priorities, Weights } from "./interfaces";
93
76
  export { Queue, GetSumWeightFunction };
@@ -1,9 +1,10 @@
1
1
  import * as _ from "../helpers";
2
2
 
3
- const Priorities = { Absolute: 0, Standard: 128, BestEffort: 255 };
3
+ const Priorities = { BestEffort: 0, Standard: 128, Absolute: 255};
4
+ const Weights = { Lowest: 0, Default: 128, Highest: 255 };
4
5
 
5
6
  interface Options {
6
- name?: string;
7
+ id?: string;
7
8
  priority: number;
8
9
  weight: number;
9
10
  autostart?: boolean;
@@ -35,4 +36,4 @@ const ErrorsList: {
35
36
  NoOptionsOnQueueInitialization: { name: "NoOptionsOnQueueInitialization", message: "You must provide an options parameter to Queue" },
36
37
  };
37
38
 
38
- export { Options, Statistics, ErrorsList, Priorities };
39
+ export { Options, Statistics, ErrorsList, Priorities, Weights };
@@ -2,11 +2,11 @@
2
2
 
3
3
  import * as _ from "../helpers";
4
4
  import { Options, Statistics, ErrorsList, Priorities } from "./interfaces";
5
- import { Engine } from "../engine";
5
+ import * as cqEngine from "../engine";
6
6
 
7
7
  export * from "./interfaces";
8
8
 
9
- type Status = "unknown" | "initialiazing" | "running" | "stopping" | "destroying";
9
+ type Status = "unknown" | "initialiazing" | "idle" | "running" | "stopping" | "destroying";
10
10
 
11
11
  const defaultOptions: Options = {
12
12
  priority: Priorities.BestEffort,
@@ -16,14 +16,16 @@ const defaultOptions: Options = {
16
16
 
17
17
  class Runner {
18
18
  status: Status = "unknown";
19
- readonly #options: Options;
20
- readonly #engine: Engine;
19
+ readonly options: Options;
20
+ tags: { [key: string]: string | number | boolean } = {};
21
+ readonly #engine: cqEngine.Engine;
21
22
 
22
- constructor(opt: Options, engine: Engine) {
23
+ constructor(opt: Options, engine: cqEngine.Engine) {
23
24
  this.status = "initialiazing";
24
- this.#options = this.#checkOptionsConsistancy({ ...defaultOptions, ...opt });
25
+ this.options = this.#checkOptionsConsistancy({ ...defaultOptions, ...opt });
26
+ if (!this.options.id) this.options.id = _.Id.generate();
25
27
  this.#engine = engine; // Store parent engine for later callbacks
26
- if (this.#options.autostart) this.start();
28
+ if (this.options.autostart) this.start();
27
29
  }
28
30
 
29
31
  #checkOptionsConsistancy(options: Options): Options {
@@ -32,18 +34,21 @@ class Runner {
32
34
  }
33
35
 
34
36
  async #run(): Promise<void> {
35
- while (this.status === "running") {
37
+ while (["idle", "running"].includes(this.status)) {
36
38
  await new Promise((resolve) => setTimeout(resolve, 10)); // Do not freeze the event loop with infinite loop.
37
39
  // get the next task to run according to this runner priority
38
- const task = await this.#engine.getNextTask(this.#options.priority);
40
+ const task = await this.#engine.getNextTask(this.options.priority);
39
41
  if (task) {
40
- await task.runner();
42
+ if (this.status === "idle") this.status = "running";
43
+ task.runnerId = this.options.id; // store runner that handle task for future references
44
+ await task.run();
45
+ if (this.status === "running") this.status = "idle";
41
46
  }
42
47
  }
43
48
  }
44
49
 
45
50
  start(): void {
46
- this.status = "running";
51
+ this.status = "idle";
47
52
  this.#run();
48
53
  }
49
54
 
@@ -54,7 +59,7 @@ class Runner {
54
59
  statistics(): Statistics {
55
60
  const stat: Statistics = {
56
61
  status: this.status,
57
- priority: this.#options.priority,
62
+ priority: this.options.priority,
58
63
  };
59
64
  return stat;
60
65
  }
@@ -1,6 +1,7 @@
1
1
  import * as _ from "../helpers";
2
2
 
3
3
  interface Options {
4
+ id?: string;
4
5
  autostart?: boolean;
5
6
  priority: number;
6
7
  logFunction?: _.Logs.LogFunction;
@@ -20,5 +21,5 @@ const ErrorsList: {
20
21
  },
21
22
  };
22
23
 
23
- export { Priorities } from "../queue/interfaces";
24
+ export { Priorities } from "../queues/interfaces";
24
25
  export { Options, Statistics, ErrorsList };
@@ -3,7 +3,7 @@
3
3
  import * as _ from "../helpers";
4
4
  import { Options, Statistics, ErrorsList, FunctionToExecute } from "./interfaces";
5
5
 
6
- type Status = "unknown" | "initialiazing" | "running" | "stopping" | "destroying";
6
+ type Status = "unknown" | "initialiazing" | "queued" | "running" | "runned" | "exception" | "destroying";
7
7
 
8
8
  const defaultOptions: Options = {
9
9
  logFunction: undefined,
@@ -11,10 +11,14 @@ const defaultOptions: Options = {
11
11
 
12
12
  class Task {
13
13
  status: Status = "unknown";
14
- readonly #options: Options;
14
+ readonly options: Options;
15
+ tags: { [key: string]: string | number | boolean } = {};
16
+ queueId: string | undefined;
17
+ runnerId: string | undefined;
18
+
15
19
  #functionToExecute: FunctionToExecute;
16
20
  result: unknown | undefined = undefined;
17
- readonly runner: () => Promise<unknown>;
21
+ readonly run: () => Promise<unknown>;
18
22
 
19
23
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
24
  resolver: (value: any | PromiseLike<any>) => void;
@@ -30,21 +34,24 @@ class Task {
30
34
 
31
35
  constructor(function_: FunctionToExecute, opt: Options) {
32
36
  this.status = "initialiazing";
33
- this.#options = this.#checkOptionsConsistancy({ ...defaultOptions, ...opt });
34
-
37
+ this.options = this.#checkOptionsConsistancy({ ...defaultOptions, ...opt });
38
+ if (!this.options.id) this.options.id = _.Id.generate();
35
39
  if (typeof function_ !== "function") throw new _.Errors.CQError(ErrorsList.FunctionIsNotAFunction);
36
40
 
37
41
  this.resolver = this.#defaultResolver;
38
42
  this.rejecter = this.#defaultRejecter;
39
43
  this.#functionToExecute = function_;
40
44
 
41
- this.runner = async (): Promise<void> => {
45
+ this.run = async (): Promise<void> => {
42
46
  this.status = "running";
43
- // console.log("running Task");
44
- this.result = await this.#functionToExecute();
45
- // console.log("task runned", this.result);
46
- this.status = "stopping";
47
- this.resolver(this.result);
47
+ try {
48
+ this.result = await this.#functionToExecute();
49
+ this.status = "runned";
50
+ this.resolver(this.result);
51
+ } catch (error) {
52
+ this.status = "exception";
53
+ this.rejecter(new _.Errors.CQError(ErrorsList.FunctionRaisedAnHundledException));
54
+ }
48
55
  };
49
56
  }
50
57
 
@@ -4,6 +4,7 @@ import * as _ from "../helpers";
4
4
  type FunctionToExecute = (...arguments_: any[]) => Promise<any> | void;
5
5
 
6
6
  interface Options {
7
+ id?: string;
7
8
  timeout?: number;
8
9
  logFunction?: _.Logs.LogFunction;
9
10
  }
@@ -12,7 +13,7 @@ interface Statistics {
12
13
  status: string;
13
14
  }
14
15
 
15
- type ErrorListKeys = "NoOptionsOnTaskInitialization" | "FunctionIsNotAFunction" | "TaskRunnerNotInitialized";
16
+ type ErrorListKeys = "NoOptionsOnTaskInitialization" | "FunctionIsNotAFunction" | "FunctionRaisedAnHundledException" | "TaskRunnerNotInitialized";
16
17
 
17
18
  const ErrorsList: {
18
19
  [index in ErrorListKeys]: { name: string; message: string };
@@ -25,6 +26,10 @@ const ErrorsList: {
25
26
  name: "FunctionIsNotAFunction",
26
27
  message: "FunctionIsNotAFunction",
27
28
  },
29
+ FunctionRaisedAnHundledException: {
30
+ name: "FunctionRaisedAnHundledException",
31
+ message: "FunctionRaisedAnHundledException",
32
+ },
28
33
  TaskRunnerNotInitialized: {
29
34
  name: "TaskRunnerNotInitialized",
30
35
  message: "TaskRunnerNotInitialized",
@@ -0,0 +1,35 @@
1
+ import assert from "node:assert/strict";
2
+ import { describe, it } from "node:test";
3
+
4
+ import * as cleverQueue from "../../dist/index.js";
5
+
6
+ const engineOptions = {
7
+ bestEffortRunners: 1,
8
+ logFunction: undefined,
9
+ };
10
+
11
+ const task = async (string_) => {
12
+ // console.log(string_, "Start", new Date());
13
+ await new Promise((resolve, reject) => setTimeout(reject(string_), 2000));
14
+ // console.log(string_, "End", new Date());
15
+ return string_;
16
+ };
17
+
18
+ describe("Issue #1", () => {
19
+ it("Exception if function passed raised an exception", () => {
20
+ const engine = cleverQueue.createEngine(engineOptions);
21
+ const queue1 = engine.createQueue({
22
+ name: "A",
23
+ priority: cleverQueue.queues.Priorities.Absolute,
24
+ weight: 50,
25
+ });
26
+ assert.rejects(
27
+ async () => {
28
+ const myTask = engine.createTask(() => task("taskMustFail"), {});
29
+ await queue1.enqueue(myTask);
30
+ },
31
+ { name: "FunctionRaisedAnHundledException" },
32
+ );
33
+ engine.stop();
34
+ });
35
+ });