clever-queue 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +111 -16
  2. package/dist/engine/index.d.ts +1 -1
  3. package/dist/engine/index.js +2 -2
  4. package/dist/engine/index.js.map +1 -1
  5. package/dist/helpers/errors.js +1 -1
  6. package/dist/helpers/errors.js.map +1 -1
  7. package/dist/queues/index.d.ts +1 -1
  8. package/dist/queues/index.js +13 -2
  9. package/dist/queues/index.js.map +1 -1
  10. package/dist/queues/interfaces.d.ts +1 -1
  11. package/dist/queues/interfaces.js +10 -2
  12. package/dist/queues/interfaces.js.map +1 -1
  13. package/dist/runners/index.d.ts +1 -1
  14. package/dist/runners/index.js +1 -1
  15. package/dist/runners/index.js.map +1 -1
  16. package/dist/tasks/index.d.ts +1 -1
  17. package/dist/tasks/index.js +2 -2
  18. package/dist/tasks/index.js.map +1 -1
  19. package/documentation/demo_1E_2Q_1R_8T_Weight.svg +1 -0
  20. package/documentation/demo_1E_2Q_2R_8T_ProrityRunner.svg +1 -0
  21. package/documentation/demo_1E_3Q_1R_12T_Priority.svg +1 -0
  22. package/examples/src/demo_1E_2Q_1R_8T_Weight.ts +40 -0
  23. package/examples/src/demo_1E_2Q_2R_8T_ProrityRunner.ts +41 -0
  24. package/examples/src/demo_1E_3Q_1R_12T_Priority.ts +45 -0
  25. package/examples/src/example00.ts +1 -1
  26. package/examples/src/helpers/animations.ts +2 -2
  27. package/examples/src/path-expression-matcher.d.ts +6 -0
  28. package/package.json +2 -2
  29. package/src/engine/index.ts +3 -3
  30. package/src/helpers/errors.ts +1 -1
  31. package/src/queues/index.ts +11 -3
  32. package/src/queues/interfaces.ts +16 -3
  33. package/src/runners/index.ts +2 -2
  34. package/src/tasks/index.ts +3 -3
  35. package/test/behavior/scheduler.mjs +124 -0
  36. package/test/issues/00008-error-contract.mjs +52 -0
  37. package/test/issues/00009-weight-semantics.mjs +54 -0
  38. package/test/issues/00010-lifecycle-semantics.mjs +101 -0
  39. package/test/issues/00012-wording-cleanup.mjs +30 -0
  40. package/test/miscellaneous/test.mjs +16 -9
  41. package/test/units/engine.mjs +2 -2
  42. package/test/units/queue.mjs +4 -4
  43. package/test/units/task.mjs +13 -12
@@ -0,0 +1,52 @@
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
+ };
9
+
10
+ async function runFailingTask(thrownValue) {
11
+ const engine = cleverQueue.createEngine(engineOptions);
12
+ const queue = engine.createQueue({
13
+ priority: cleverQueue.queues.Priorities.Absolute,
14
+ weight: 50,
15
+ });
16
+
17
+ try {
18
+ await queue.enqueue(
19
+ engine.createTask(async () => {
20
+ throw thrownValue;
21
+ }, {}),
22
+ );
23
+ throw new Error("task was expected to reject");
24
+ } finally {
25
+ engine.stop();
26
+ }
27
+ }
28
+
29
+ describe("Issue #8 - task error propagation contract", () => {
30
+ it("rejects with the original Error instance when a task throws an Error", async () => {
31
+ const originalError = new Error("original error");
32
+
33
+ await assert.rejects(() => runFailingTask(originalError), (error) => {
34
+ assert.equal(error, originalError);
35
+ return true;
36
+ });
37
+ });
38
+
39
+ it("wraps thrown strings in the documented CQError fallback", async () => {
40
+ await assert.rejects(() => runFailingTask("boom"), {
41
+ name: cleverQueue.tasks.ErrorsList.FunctionRaisedAnHundledException.name,
42
+ message: cleverQueue.tasks.ErrorsList.FunctionRaisedAnHundledException.message,
43
+ });
44
+ });
45
+
46
+ it("wraps thrown objects in the documented CQError fallback", async () => {
47
+ await assert.rejects(() => runFailingTask({ code: "E_OBJECT" }), {
48
+ name: cleverQueue.tasks.ErrorsList.FunctionRaisedAnHundledException.name,
49
+ message: cleverQueue.tasks.ErrorsList.FunctionRaisedAnHundledException.message,
50
+ });
51
+ });
52
+ });
@@ -0,0 +1,54 @@
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
+ async function dequeueIds(engine, count) {
7
+ const ids = [];
8
+ for (let index = 0; index < count; index += 1) {
9
+ const task = await engine.getNextTask(cleverQueue.queues.Priorities.BestEffort);
10
+ assert.ok(task);
11
+ ids.push(task.options.id);
12
+ }
13
+
14
+ return ids;
15
+ }
16
+
17
+ describe("Issue #9 - queue weight semantics", () => {
18
+ it("exposes 1 as the lowest valid queue weight", () => {
19
+ assert.equal(cleverQueue.queues.Weights.Lowest, 1);
20
+ });
21
+
22
+ it("rejects weight 0 because active queues must have a positive scheduling weight", () => {
23
+ const engine = cleverQueue.createEngine({ bestEffortRunners: 1 });
24
+
25
+ assert.throws(
26
+ () =>
27
+ engine.createQueue({
28
+ priority: cleverQueue.queues.Priorities.Standard,
29
+ weight: 0,
30
+ }),
31
+ {
32
+ name: cleverQueue.queues.ErrorsList.NoWeightOptionsOnQueueInitialization.name,
33
+ message: cleverQueue.queues.ErrorsList.NoWeightOptionsOnQueueInitialization.message,
34
+ },
35
+ );
36
+
37
+ engine.stop();
38
+ });
39
+
40
+ it("uses larger weights to receive proportionally more scheduling turns over time", async () => {
41
+ const engine = cleverQueue.createEngine({ autostart: false, bestEffortRunners: 1 });
42
+ const heavyQueue = engine.createQueue({ priority: cleverQueue.queues.Priorities.Standard, weight: 2 });
43
+ const lightQueue = engine.createQueue({ priority: cleverQueue.queues.Priorities.Standard, weight: 1 });
44
+
45
+ for (let index = 0; index < 4; index += 1) heavyQueue.enqueue(engine.createTask(() => undefined, { id: `heavy-${index}` }));
46
+ for (let index = 0; index < 2; index += 1) lightQueue.enqueue(engine.createTask(() => undefined, { id: `light-${index}` }));
47
+
48
+ engine.start();
49
+ const ids = await dequeueIds(engine, 6);
50
+
51
+ assert.deepEqual(ids, ["heavy-0", "heavy-1", "light-0", "heavy-2", "heavy-3", "light-1"]);
52
+ engine.stop();
53
+ });
54
+ });
@@ -0,0 +1,101 @@
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
+ function createDeferred() {
7
+ /** @type {(value?: unknown) => void} */
8
+ let resolve;
9
+ const promise = new Promise((resolver) => {
10
+ resolve = resolver;
11
+ });
12
+
13
+ return { promise, resolve };
14
+ }
15
+
16
+ describe("Issue #10 - lifecycle semantics", () => {
17
+ it("rejects queued tasks when the engine is stopped, while letting the in-flight task finish", async () => {
18
+ const blocker = createDeferred();
19
+ const engine = cleverQueue.createEngine({ bestEffortRunners: 1 });
20
+ const queue = engine.createQueue({ priority: cleverQueue.queues.Priorities.Standard, weight: 1 });
21
+
22
+ const firstTask = queue.enqueue(
23
+ engine.createTask(async () => {
24
+ await blocker.promise;
25
+ return "first";
26
+ }, {}),
27
+ );
28
+ const secondTask = queue.enqueue(engine.createTask(() => "second", {}));
29
+
30
+ await new Promise((resolve) => setTimeout(resolve, 25));
31
+ engine.stop();
32
+ blocker.resolve();
33
+
34
+ assert.equal(await firstTask, "first");
35
+ await assert.rejects(secondTask, {
36
+ name: cleverQueue.queues.ErrorsList.QueueIsStopping.name,
37
+ });
38
+ });
39
+
40
+ it("rejects pending tasks immediately when a queue is stopped", async () => {
41
+ const engine = cleverQueue.createEngine({ bestEffortRunners: 1 });
42
+ const queue = engine.createQueue({ priority: cleverQueue.queues.Priorities.Standard, weight: 1 });
43
+ const pendingTask = queue.enqueue(engine.createTask(() => "never runs", {}));
44
+
45
+ queue.stop();
46
+
47
+ await assert.rejects(pendingTask, {
48
+ name: cleverQueue.queues.ErrorsList.QueueIsStopping.name,
49
+ });
50
+ engine.stop();
51
+ });
52
+
53
+ it("lets a runner finish its current task before stopping without starting the next one", async () => {
54
+ const blocker = createDeferred();
55
+ const engine = cleverQueue.createEngine({ autostart: false, bestEffortRunners: 1 });
56
+ const queue = engine.createQueue({ priority: cleverQueue.queues.Priorities.Standard, weight: 1 });
57
+ const runner = engine.runners[0];
58
+
59
+ const firstTask = queue.enqueue(
60
+ engine.createTask(async () => {
61
+ await blocker.promise;
62
+ return "first";
63
+ }, {}),
64
+ );
65
+ const secondTask = queue.enqueue(engine.createTask(() => "second", {}));
66
+
67
+ engine.start();
68
+ await new Promise((resolve) => setTimeout(resolve, 25));
69
+ runner.stop();
70
+ blocker.resolve();
71
+
72
+ assert.equal(await firstTask, "first");
73
+ assert.equal(runner.statistics().status, "stopping");
74
+
75
+ await new Promise((resolve) => setTimeout(resolve, 25));
76
+ assert.equal(queue.statistics().tasks, 1);
77
+
78
+ queue.stop();
79
+ await assert.rejects(secondTask, {
80
+ name: cleverQueue.queues.ErrorsList.QueueIsStopping.name,
81
+ });
82
+ engine.stop();
83
+ });
84
+
85
+ it("rejects new tasks after a queue has been stopped", async () => {
86
+ const engine = cleverQueue.createEngine({ bestEffortRunners: 1 });
87
+ const queue = engine.createQueue({ priority: cleverQueue.queues.Priorities.Standard, weight: 1 });
88
+
89
+ queue.stop();
90
+
91
+ await assert.rejects(
92
+ queue.enqueue(engine.createTask(() => "late", {})),
93
+ {
94
+ name: cleverQueue.queues.ErrorsList.QueueIsStopping.name,
95
+ message: cleverQueue.queues.ErrorsList.QueueIsStopping.message,
96
+ },
97
+ );
98
+
99
+ engine.stop();
100
+ });
101
+ });
@@ -0,0 +1,30 @@
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
+ describe("Issue #12 - wording and status cleanup", () => {
7
+ it("uses the normalized initializing status wording across runtime objects", () => {
8
+ const task = new cleverQueue.tasks.Task(() => undefined, { autostart: false });
9
+ assert.equal(task.statistics().status, "initializing");
10
+
11
+ const engine = cleverQueue.createEngine({ autostart: false, bestEffortRunners: 1 });
12
+ const queue = engine.createQueue({ autostart: false, priority: cleverQueue.queues.Priorities.Standard, weight: 1 });
13
+ const runner = engine.createRunner({ autostart: false, priority: cleverQueue.queues.Priorities.BestEffort });
14
+
15
+ assert.equal(engine.statistics().engine.status, "initializing");
16
+ assert.equal(queue.statistics().status, "initializing");
17
+ assert.equal(runner.statistics().status, "initializing");
18
+ engine.stop();
19
+ });
20
+
21
+ it("reports completed instead of runned after successful task execution", async () => {
22
+ const task = new cleverQueue.tasks.Task(() => "done", {});
23
+ task.resolver = () => {};
24
+ task.rejecter = () => {};
25
+
26
+ await task.run();
27
+
28
+ assert.equal(task.statistics().status, "completed");
29
+ });
30
+ });
@@ -8,27 +8,32 @@ const engineOptions = {
8
8
  logFunction: undefined,
9
9
  };
10
10
 
11
+ function trackEnqueue(promises, queue, task) {
12
+ promises.push(queue.enqueue(task).catch(() => undefined));
13
+ }
14
+
11
15
  describe("Scheduler Unit Tests", () => {
12
16
  it("Priority Test", () => {
17
+ const pending = [];
13
18
  const engine = cleverQueue.createEngine(engineOptions);
14
19
  const queue1 = engine.createQueue({
15
20
  name: "A",
16
21
  priority: cleverQueue.queues.Priorities.Absolute,
17
22
  weight: 50,
18
23
  });
19
- for (let index = 0; index < 100; index++) queue1.enqueue(engine.createTask(() => {}, {}));
24
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue1, engine.createTask(() => {}, {}));
20
25
  const queue2 = engine.createQueue({
21
26
  name: "B",
22
27
  priority: cleverQueue.queues.Priorities.Standard,
23
28
  weight: 50,
24
29
  });
25
- for (let index = 0; index < 100; index++) queue2.enqueue(engine.createTask(() => {}, {}));
30
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue2, engine.createTask(() => {}, {}));
26
31
  const queue3 = engine.createQueue({
27
32
  name: "C",
28
33
  priority: cleverQueue.queues.Priorities.BestEffort,
29
34
  weight: 50,
30
35
  });
31
- for (let index = 0; index < 100; index++) queue3.enqueue(engine.createTask(() => {}, {}));
36
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue3, engine.createTask(() => {}, {}));
32
37
 
33
38
  const engineStatistics = engine.statistics();
34
39
  assert.equal(engineStatistics.engine.queues, 3);
@@ -36,25 +41,26 @@ describe("Scheduler Unit Tests", () => {
36
41
  });
37
42
 
38
43
  it("Weighted Test", () => {
44
+ const pending = [];
39
45
  const engine = cleverQueue.createEngine(engineOptions);
40
46
  const queue1 = engine.createQueue({
41
47
  name: "A",
42
48
  priority: cleverQueue.queues.Priorities.Absolute,
43
49
  weight: 80,
44
50
  });
45
- for (let index = 0; index < 100; index++) queue1.enqueue(engine.createTask(() => {}, {}));
51
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue1, engine.createTask(() => {}, {}));
46
52
  const queue2 = engine.createQueue({
47
53
  name: "B",
48
54
  priority: cleverQueue.queues.Priorities.Absolute,
49
55
  weight: 50,
50
56
  });
51
- for (let index = 0; index < 100; index++) queue2.enqueue(engine.createTask(() => {}, {}));
57
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue2, engine.createTask(() => {}, {}));
52
58
  const queue3 = engine.createQueue({
53
59
  name: "C",
54
60
  priority: cleverQueue.queues.Priorities.Absolute,
55
61
  weight: 20,
56
62
  });
57
- for (let index = 0; index < 100; index++) queue3.enqueue(engine.createTask(() => {}, {}));
63
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue3, engine.createTask(() => {}, {}));
58
64
 
59
65
  const engineStatistics = engine.statistics();
60
66
  assert.equal(engineStatistics.engine.queues, 3);
@@ -62,6 +68,7 @@ describe("Scheduler Unit Tests", () => {
62
68
  });
63
69
 
64
70
  it("Combine Test", () => {
71
+ const pending = [];
65
72
  const engine = cleverQueue.createEngine(engineOptions);
66
73
 
67
74
  const queue1 = engine.createQueue({
@@ -69,21 +76,21 @@ describe("Scheduler Unit Tests", () => {
69
76
  priority: cleverQueue.queues.Priorities.Absolute,
70
77
  weight: 80,
71
78
  });
72
- for (let index = 0; index < 100; index++) queue1.enqueue(engine.createTask(() => {}, {}));
79
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue1, engine.createTask(() => {}, {}));
73
80
 
74
81
  const queue2 = engine.createQueue({
75
82
  name: "B - Standard - 50",
76
83
  priority: cleverQueue.queues.Priorities.Standard,
77
84
  weight: 50,
78
85
  });
79
- for (let index = 0; index < 100; index++) queue2.enqueue(engine.createTask(() => {}, {}));
86
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue2, engine.createTask(() => {}, {}));
80
87
 
81
88
  const queue3 = engine.createQueue({
82
89
  name: "C- Standard - 20",
83
90
  priority: cleverQueue.queues.Priorities.Standard,
84
91
  weight: 20,
85
92
  });
86
- for (let index = 0; index < 100; index++) queue3.enqueue(engine.createTask(() => {}, {}));
93
+ for (let index = 0; index < 100; index++) trackEnqueue(pending, queue3, engine.createTask(() => {}, {}));
87
94
 
88
95
  const engineStatistics = engine.statistics();
89
96
  assert.equal(engineStatistics.engine.queues, 3);
@@ -4,7 +4,7 @@ import { describe, it } from "node:test";
4
4
  import * as cleverQueue from "../../dist/index.js";
5
5
 
6
6
  describe("Engine Class Unit Tests", () => {
7
- it("Successfull Initialisation without any option (use hardcoded default options)", () => {
7
+ it("Successful Initialisation without any option (use hardcoded default options)", () => {
8
8
  const engine = cleverQueue.createEngine();
9
9
  const statistics = engine.statistics();
10
10
  assert.equal(statistics.engine.runners, 1);
@@ -22,7 +22,7 @@ describe("Engine Class Unit Tests", () => {
22
22
  );
23
23
  });
24
24
 
25
- it("Successfull Initialisation with 3 Runners", () => {
25
+ it("Successful Initialisation with 3 Runners", () => {
26
26
  const engineOptions = {
27
27
  bestEffortRunners: 3,
28
28
  };
@@ -9,7 +9,7 @@ const engineOptions = {
9
9
  };
10
10
 
11
11
  describe("Queue Class Unit Tests", () => {
12
- it("Successfull Initialisation without any option (use hardcoded default options)", () => {
12
+ it("Successful Initialisation without any option (use hardcoded default options)", () => {
13
13
  const engine = cleverQueue.createEngine();
14
14
  const statistics = engine.statistics();
15
15
  assert.equal(statistics.engine.runners, 1);
@@ -17,7 +17,7 @@ describe("Queue Class Unit Tests", () => {
17
17
  engine.stop();
18
18
  });
19
19
 
20
- it("Successfull Initialisation", () => {
20
+ it("Successful Initialisation", () => {
21
21
  const engine = cleverQueue.createEngine(engineOptions);
22
22
  const queueOptions = {
23
23
  priority: cleverQueue.queues.Priorities.Standard,
@@ -45,7 +45,7 @@ describe("Queue Class Unit Tests", () => {
45
45
  engine.stop();
46
46
  });
47
47
 
48
- it("Keeps the exported lowest weight constant at zero", () => {
49
- assert.equal(cleverQueue.queues.Weights.Lowest, 0);
48
+ it("Keeps the exported lowest weight constant at one", () => {
49
+ assert.equal(cleverQueue.queues.Weights.Lowest, 1);
50
50
  });
51
51
  });
@@ -9,28 +9,29 @@ const engineOptions = {
9
9
  };
10
10
 
11
11
  const task = async (string_) => {
12
- // console.log(string_, "Start", new Date());
13
12
  await new Promise((resolve) => setTimeout(resolve, 2000));
14
- // console.log(string_, "End", new Date());
15
13
  return string_;
16
14
  };
17
15
 
18
16
  describe("Scheduler Unit Tests", () => {
19
- it("Exception if function passed is not a function (but a result of an executed function)", () => {
17
+ it("Exception if function passed is not a function (but a result of an executed function)", async () => {
20
18
  const engine = cleverQueue.createEngine(engineOptions);
21
19
  const queue1 = engine.createQueue({
22
20
  name: "A",
23
21
  priority: cleverQueue.queues.Priorities.Absolute,
24
22
  weight: 50,
25
23
  });
26
- assert.rejects(
27
- async () => {
28
- const myTask = engine.createTask(task("myTask"), {});
29
- await queue1.enqueue(myTask);
30
- },
31
- { name: cleverQueue.tasks.ErrorsList.FunctionIsNotAFunction.name },
32
- );
33
- engine.stop();
24
+ try {
25
+ await assert.rejects(
26
+ async () => {
27
+ const myTask = engine.createTask(task("myTask"), {});
28
+ await queue1.enqueue(myTask);
29
+ },
30
+ { name: cleverQueue.tasks.ErrorsList.FunctionIsNotAFunction.name },
31
+ );
32
+ } finally {
33
+ engine.stop();
34
+ }
34
35
  });
35
36
 
36
37
  it("Priority Test", () => {
@@ -41,7 +42,7 @@ describe("Scheduler Unit Tests", () => {
41
42
  weight: 50,
42
43
  });
43
44
 
44
- for (let index = 0; index < 8; index++) queue1.enqueue(engine.createTask(() => task("Task " + index), {}));
45
+ for (let index = 0; index < 8; index++) queue1.enqueue(engine.createTask(() => task(`Task ${index}`), {})).catch(() => undefined);
45
46
 
46
47
  const engineStatistics = engine.statistics();
47
48
  assert.equal(engineStatistics.engine.queues, 1);