@bluelibs/runner 1.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.
Files changed (66) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +797 -0
  3. package/dist/DependencyProcessor.d.ts +49 -0
  4. package/dist/DependencyProcessor.js +178 -0
  5. package/dist/DependencyProcessor.js.map +1 -0
  6. package/dist/EventManager.d.ts +13 -0
  7. package/dist/EventManager.js +58 -0
  8. package/dist/EventManager.js.map +1 -0
  9. package/dist/ResourceInitializer.d.ts +13 -0
  10. package/dist/ResourceInitializer.js +54 -0
  11. package/dist/ResourceInitializer.js.map +1 -0
  12. package/dist/Store.d.ts +62 -0
  13. package/dist/Store.js +186 -0
  14. package/dist/Store.js.map +1 -0
  15. package/dist/TaskRunner.d.ts +22 -0
  16. package/dist/TaskRunner.js +93 -0
  17. package/dist/TaskRunner.js.map +1 -0
  18. package/dist/define.d.ts +10 -0
  19. package/dist/define.js +111 -0
  20. package/dist/define.js.map +1 -0
  21. package/dist/defs.d.ts +127 -0
  22. package/dist/defs.js +12 -0
  23. package/dist/defs.js.map +1 -0
  24. package/dist/errors.d.ts +8 -0
  25. package/dist/errors.js +12 -0
  26. package/dist/errors.js.map +1 -0
  27. package/dist/globalEvents.d.ts +36 -0
  28. package/dist/globalEvents.js +45 -0
  29. package/dist/globalEvents.js.map +1 -0
  30. package/dist/globalResources.d.ts +8 -0
  31. package/dist/globalResources.js +19 -0
  32. package/dist/globalResources.js.map +1 -0
  33. package/dist/index.d.ts +49 -0
  34. package/dist/index.js +25 -0
  35. package/dist/index.js.map +1 -0
  36. package/dist/run.d.ts +32 -0
  37. package/dist/run.js +39 -0
  38. package/dist/run.js.map +1 -0
  39. package/dist/tools/findCircularDependencies.d.ts +16 -0
  40. package/dist/tools/findCircularDependencies.js +53 -0
  41. package/dist/tools/findCircularDependencies.js.map +1 -0
  42. package/package.json +50 -0
  43. package/src/DependencyProcessor.ts +243 -0
  44. package/src/EventManager.ts +84 -0
  45. package/src/ResourceInitializer.ts +69 -0
  46. package/src/Store.ts +250 -0
  47. package/src/TaskRunner.ts +135 -0
  48. package/src/__tests__/EventManager.test.ts +96 -0
  49. package/src/__tests__/ResourceInitializer.test.ts +109 -0
  50. package/src/__tests__/Store.test.ts +143 -0
  51. package/src/__tests__/TaskRunner.test.ts +135 -0
  52. package/src/__tests__/benchmark/benchmark.test.ts +146 -0
  53. package/src/__tests__/errors.test.ts +268 -0
  54. package/src/__tests__/globalEvents.test.ts +99 -0
  55. package/src/__tests__/index.ts +9 -0
  56. package/src/__tests__/run.hooks.test.ts +110 -0
  57. package/src/__tests__/run.test.ts +614 -0
  58. package/src/__tests__/tools/findCircularDependencies.test.ts +217 -0
  59. package/src/define.ts +142 -0
  60. package/src/defs.ts +221 -0
  61. package/src/errors.ts +22 -0
  62. package/src/globalEvents.ts +64 -0
  63. package/src/globalResources.ts +19 -0
  64. package/src/index.ts +28 -0
  65. package/src/run.ts +98 -0
  66. package/src/tools/findCircularDependencies.ts +69 -0
@@ -0,0 +1,143 @@
1
+ import { EventManager } from "../EventManager";
2
+ import { Store } from "../Store";
3
+ import {
4
+ defineTask,
5
+ defineResource,
6
+ defineEvent,
7
+ defineMiddleware,
8
+ } from "../define";
9
+
10
+ describe("Store", () => {
11
+ let store: Store;
12
+ let eventManager: EventManager;
13
+
14
+ beforeEach(() => {
15
+ eventManager = new EventManager();
16
+ store = new Store(eventManager);
17
+ });
18
+
19
+ it("should register an task", () => {
20
+ const task = defineTask({
21
+ id: "testTask",
22
+ run: async () => {},
23
+ });
24
+
25
+ store.computeRegisterOfResource(
26
+ defineResource({
27
+ id: "root",
28
+ register: [task],
29
+ })
30
+ );
31
+
32
+ expect(store.tasks.get("testTask")).toBeDefined();
33
+ expect(store.tasks.get("testTask")?.task).toBe(task);
34
+ });
35
+
36
+ it("should register a resource", () => {
37
+ const resource = defineResource({
38
+ id: "testResource",
39
+ });
40
+
41
+ store.computeRegisterOfResource(
42
+ defineResource({
43
+ id: "root",
44
+ register: [resource],
45
+ })
46
+ );
47
+
48
+ expect(store.resources.get("testResource")).toBeDefined();
49
+ expect(store.resources.get("testResource")?.resource).toBe(resource);
50
+ });
51
+
52
+ it("should register an event", () => {
53
+ const event = defineEvent({
54
+ id: "testEvent",
55
+ });
56
+
57
+ store.computeRegisterOfResource(
58
+ defineResource({
59
+ id: "root",
60
+ register: [event],
61
+ })
62
+ );
63
+
64
+ expect(store.events.get("testEvent")).toBeDefined();
65
+ expect(store.events.get("testEvent")?.event).toBe(event);
66
+ });
67
+
68
+ it("should register a middleware", () => {
69
+ const middleware = defineMiddleware({
70
+ id: "testMiddleware",
71
+ run: async () => {},
72
+ });
73
+
74
+ store.computeRegisterOfResource(
75
+ defineResource({
76
+ id: "root",
77
+ register: [middleware],
78
+ })
79
+ );
80
+
81
+ expect(store.middlewares.get("testMiddleware")).toBeDefined();
82
+ expect(store.middlewares.get("testMiddleware")?.middleware).toBe(
83
+ middleware
84
+ );
85
+ });
86
+
87
+ it("should throw an error when registering duplicate items", () => {
88
+ const task = defineTask({
89
+ id: "duplicateItem",
90
+ run: async () => {},
91
+ });
92
+
93
+ store.computeRegisterOfResource(
94
+ defineResource({
95
+ id: "root",
96
+ register: [task],
97
+ })
98
+ );
99
+
100
+ expect(() => {
101
+ store.computeRegisterOfResource(
102
+ defineResource({
103
+ id: "anotherRoot",
104
+ register: [task],
105
+ })
106
+ );
107
+ }).toThrow('Task "duplicateItem" already registered');
108
+ });
109
+
110
+ it("should return dependent nodes", () => {
111
+ const task1 = defineTask({
112
+ id: "task1",
113
+ dependencies: { dep1: {} as any },
114
+ run: async () => {},
115
+ });
116
+
117
+ const task2 = defineTask({
118
+ id: "task2",
119
+ dependencies: { dep2: {} as any },
120
+ run: async () => {},
121
+ });
122
+
123
+ store.computeRegisterOfResource(
124
+ defineResource({
125
+ id: "root",
126
+ register: [task1, task2],
127
+ })
128
+ );
129
+
130
+ const dependentNodes = store.getDependentNodes();
131
+
132
+ // global store, global event manager
133
+ expect(dependentNodes).toHaveLength(2);
134
+ expect(dependentNodes).toContainEqual({
135
+ id: "task1",
136
+ dependencies: { dep1: {} },
137
+ });
138
+ expect(dependentNodes).toContainEqual({
139
+ id: "task2",
140
+ dependencies: { dep2: {} },
141
+ });
142
+ });
143
+ });
@@ -0,0 +1,135 @@
1
+ import { TaskRunner } from "../TaskRunner";
2
+ import { Store } from "../Store";
3
+ import { EventManager } from "../EventManager";
4
+ import { defineTask, defineResource, defineMiddleware } from "../define";
5
+ import { ITask } from "../defs";
6
+
7
+ describe("TaskRunner", () => {
8
+ let store: Store;
9
+ let eventManager: EventManager;
10
+ let taskRunner: TaskRunner;
11
+
12
+ beforeEach(() => {
13
+ eventManager = new EventManager();
14
+ store = new Store(eventManager);
15
+ taskRunner = new TaskRunner(store, eventManager);
16
+ });
17
+
18
+ it("should run an task without middleware", async () => {
19
+ const app = defineResource({
20
+ id: "app",
21
+ register: () => [task],
22
+ });
23
+
24
+ const task = defineTask({
25
+ id: "testTask",
26
+ run: async (input: number) => input * 2,
27
+ });
28
+
29
+ store.tasks.set(task.id, {
30
+ task,
31
+ computedDependencies: {},
32
+ isInitialized: false,
33
+ });
34
+
35
+ const result = await taskRunner.run(task, 5);
36
+ expect(result).toBe(10);
37
+ });
38
+
39
+ it("should run an task with middleware", async () => {
40
+ const middleware1 = defineMiddleware({
41
+ id: "middleware1",
42
+ run: async ({ input, next }) => {
43
+ const result = await next(input);
44
+ return result + 1;
45
+ },
46
+ });
47
+
48
+ const middleware2 = defineMiddleware({
49
+ id: "middleware2",
50
+ run: async ({ input, next }) => {
51
+ const result = await next(input);
52
+ return result * 2;
53
+ },
54
+ });
55
+
56
+ const task = defineTask({
57
+ id: "testTask",
58
+ middleware: [middleware1, middleware2],
59
+ run: async (input: number) => input + 5,
60
+ });
61
+
62
+ store.tasks.set(task.id, {
63
+ task,
64
+ computedDependencies: {},
65
+ isInitialized: true,
66
+ });
67
+ store.middlewares.set(middleware1.id, {
68
+ middleware: middleware1,
69
+ computedDependencies: {},
70
+ });
71
+ store.middlewares.set(middleware2.id, {
72
+ middleware: middleware2,
73
+ computedDependencies: {},
74
+ });
75
+
76
+ const result = await taskRunner.run(task, 5);
77
+ expect(result).toBe(21); // ((5 + 5) * 2) + 1
78
+ });
79
+
80
+ it("should emit events during task execution", async () => {
81
+ const task = defineTask({
82
+ id: "testTask",
83
+ run: async (input: number) => input * 2,
84
+ });
85
+
86
+ store.tasks.set(task.id, {
87
+ task,
88
+ computedDependencies: {},
89
+ isInitialized: false,
90
+ });
91
+
92
+ const beforeRunSpy = jest.fn();
93
+ const afterRunSpy = jest.fn();
94
+
95
+ eventManager.addListener(task.events.beforeRun, beforeRunSpy);
96
+ eventManager.addListener(task.events.afterRun, afterRunSpy);
97
+
98
+ await taskRunner.run(task, 5);
99
+
100
+ expect(beforeRunSpy).toHaveBeenCalledWith(
101
+ expect.objectContaining({ data: { input: 5 } })
102
+ );
103
+ expect(afterRunSpy).toHaveBeenCalledWith(
104
+ expect.objectContaining({ data: { input: 5, output: 10 } })
105
+ );
106
+ });
107
+
108
+ it("should handle errors and emit onError event", async () => {
109
+ const error = new Error("Test error");
110
+ const task = defineTask({
111
+ id: "testTask",
112
+ run: async () => {
113
+ throw error;
114
+ },
115
+ });
116
+
117
+ store.tasks.set(task.id, {
118
+ task,
119
+ computedDependencies: {},
120
+ isInitialized: false,
121
+ });
122
+
123
+ const onErrorSpy = jest.fn();
124
+ eventManager.addListener(task.events.onError, onErrorSpy);
125
+
126
+ expect(taskRunner.run(task, undefined)).rejects.toThrow(error);
127
+
128
+ // since it quickly throws and is not run asnc we might need to wait a bit
129
+ await new Promise((resolve) => setTimeout(resolve, 100));
130
+
131
+ expect(onErrorSpy).toHaveBeenCalledWith(
132
+ expect.objectContaining({ data: { error } })
133
+ );
134
+ });
135
+ });
@@ -0,0 +1,146 @@
1
+ import * as Benchmark from "benchmark";
2
+ import {
3
+ defineTask,
4
+ defineResource,
5
+ defineEvent,
6
+ defineMiddleware,
7
+ } from "../../define";
8
+ import { run } from "../../run";
9
+
10
+ describe("Benchmarks", () => {
11
+ let suite: Benchmark.Suite;
12
+
13
+ beforeEach(() => {
14
+ suite = new Benchmark.Suite();
15
+ });
16
+
17
+ it("should benchmark task execution", (done) => {
18
+ const testTask = defineTask({
19
+ id: "test.task",
20
+ run: async () => "Hello, World!",
21
+ });
22
+
23
+ const app = defineResource({
24
+ id: "app",
25
+ dependencies: { testTask },
26
+ register: [testTask],
27
+ async init(_, { testTask }) {
28
+ await testTask();
29
+ },
30
+ });
31
+
32
+ suite.add("Task Execution", {
33
+ defer: true,
34
+ fn: (deferred: { resolve: () => void }) => {
35
+ run(app).then(() => deferred.resolve());
36
+ },
37
+ });
38
+
39
+ suite.on("complete", function () {
40
+ console.log("Task Execution:", this[0].hz.toFixed(2), "ops/sec");
41
+ done();
42
+ });
43
+
44
+ suite.run({ async: true });
45
+ });
46
+
47
+ it("should benchmark resource initialization", (done) => {
48
+ const testResource = defineResource({
49
+ id: "test.resource",
50
+ init: async () => "Resource Value",
51
+ });
52
+
53
+ const app = defineResource({
54
+ id: "app",
55
+ register: [testResource],
56
+ dependencies: { testResource },
57
+ async init(_, { testResource }) {
58
+ expect(testResource).toBe("Resource Value");
59
+ },
60
+ });
61
+
62
+ suite.add("Resource Initialization", {
63
+ defer: true,
64
+ fn: (deferred: { resolve: () => void }) => {
65
+ run(app).then(() => deferred.resolve());
66
+ },
67
+ });
68
+
69
+ suite.on("complete", function () {
70
+ console.log("Resource Initialization:", this[0].hz.toFixed(2), "ops/sec");
71
+ done();
72
+ });
73
+
74
+ suite.run({ async: true });
75
+ });
76
+
77
+ it("should benchmark event emission", (done) => {
78
+ const testEvent = defineEvent<{ message: string }>({ id: "test.event" });
79
+ const eventHandler = jest.fn();
80
+
81
+ const app = defineResource({
82
+ id: "app",
83
+ register: [testEvent],
84
+ dependencies: { testEvent },
85
+ hooks: [
86
+ {
87
+ event: testEvent,
88
+ run: eventHandler,
89
+ },
90
+ ],
91
+ async init(_, { testEvent }) {
92
+ await testEvent({ message: "Event emitted" });
93
+ },
94
+ });
95
+
96
+ suite.add("Event Emission", {
97
+ defer: true,
98
+ fn: (deferred: { resolve: () => void }) => {
99
+ run(app).then(() => deferred.resolve());
100
+ },
101
+ });
102
+
103
+ suite.on("complete", function () {
104
+ console.log("Event Emission:", this[0].hz.toFixed(2), "ops/sec");
105
+ done();
106
+ });
107
+
108
+ suite.run({ async: true });
109
+ });
110
+
111
+ it("should benchmark dependency resolution", (done) => {
112
+ const dep1 = defineResource({
113
+ id: "dep1",
114
+ init: async () => "Dep1 Value",
115
+ });
116
+
117
+ const dep2 = defineResource({
118
+ id: "dep2",
119
+ dependencies: { dep1 },
120
+ init: async (_, { dep1 }) => `Dep2 Value: ${dep1}`,
121
+ });
122
+
123
+ const app = defineResource({
124
+ id: "app",
125
+ register: [dep1, dep2],
126
+ dependencies: { dep2 },
127
+ async init(_, { dep2 }) {
128
+ expect(dep2).toBe("Dep2 Value: Dep1 Value");
129
+ },
130
+ });
131
+
132
+ suite.add("Dependency Resolution", {
133
+ defer: true,
134
+ fn: (deferred: { resolve: () => void }) => {
135
+ run(app).then(() => deferred.resolve());
136
+ },
137
+ });
138
+
139
+ suite.on("complete", function () {
140
+ console.log("Dependency Resolution:", this[0].hz.toFixed(2), "ops/sec");
141
+ done();
142
+ });
143
+
144
+ suite.run({ async: true });
145
+ });
146
+ });
@@ -0,0 +1,268 @@
1
+ import {
2
+ defineTask,
3
+ defineResource,
4
+ defineEvent,
5
+ defineMiddleware,
6
+ } from "../define";
7
+ import { run } from "../run";
8
+ import { Errors } from "../errors";
9
+
10
+ describe("Errors", () => {
11
+ it("should throw duplicateRegistration error", async () => {
12
+ const task1 = defineTask({ id: "test.task", run: async () => {} });
13
+ const task2 = defineTask({ id: "test.task", run: async () => {} });
14
+
15
+ const app = defineResource({
16
+ id: "app",
17
+ register: [task1, task2],
18
+ });
19
+
20
+ await expect(run(app)).rejects.toThrow(
21
+ Errors.duplicateRegistration("Task", "test.task").message
22
+ );
23
+ });
24
+
25
+ it("should throw unknown item type error at task level", async () => {
26
+ const task = defineTask({
27
+ id: "test.task",
28
+ dependencies: { nonExistentDep: {} as any },
29
+ run: async () => {},
30
+ });
31
+
32
+ const app = defineResource({
33
+ id: "app",
34
+ register: [task],
35
+ dependencies: { task },
36
+ async init(_, { task }) {
37
+ await task();
38
+ },
39
+ });
40
+
41
+ await expect(run(app)).rejects.toThrow(Errors.unknownItemType({}).message);
42
+ });
43
+
44
+ it("should throw unknown item type error at resource level", async () => {
45
+ const app = defineResource({
46
+ id: "app",
47
+ register: [
48
+ {
49
+ id: "nonExistent",
50
+ } as any,
51
+ ],
52
+ async init(_, {}) {},
53
+ });
54
+
55
+ await expect(run(app)).rejects.toThrow(Errors.unknownItemType({}).message);
56
+ });
57
+
58
+ it("should throw circularDependencies error", async () => {
59
+ const task1 = defineTask({
60
+ id: "task1",
61
+ dependencies: () => ({ task2 }),
62
+ run: async () => {},
63
+ });
64
+
65
+ const task2 = defineTask({
66
+ id: "task2",
67
+ dependencies: { task1 },
68
+ run: async () => {},
69
+ });
70
+
71
+ const app = defineResource({
72
+ id: "app",
73
+ register: [task1, task2],
74
+ });
75
+
76
+ await expect(run(app)).rejects.toThrow(/Circular dependencies detected/);
77
+ });
78
+
79
+ it("should throw eventNotFound error", async () => {
80
+ const nonExistentEvent = { id: "non.existent.event" } as any;
81
+
82
+ const task = defineTask({
83
+ id: "test.task",
84
+ on: nonExistentEvent,
85
+ run: async () => {},
86
+ });
87
+
88
+ const app = defineResource({
89
+ id: "app",
90
+ register: [task],
91
+ });
92
+
93
+ await expect(run(app)).rejects.toThrow(
94
+ Errors.eventNotFound("non.existent.event").message
95
+ );
96
+ });
97
+
98
+ it("should throw taskError", async () => {
99
+ const errorTask = defineTask({
100
+ id: "error.task",
101
+ run: async () => {
102
+ throw new Error("Task error");
103
+ },
104
+ });
105
+
106
+ const app = defineResource({
107
+ id: "app",
108
+ register: [errorTask],
109
+ dependencies: { errorTask },
110
+ async init(_, { errorTask }) {
111
+ await errorTask();
112
+ },
113
+ });
114
+
115
+ await expect(run(app)).rejects.toThrow(/Task error/);
116
+ });
117
+
118
+ it("should throw resourceError", async () => {
119
+ const errorResource = defineResource({
120
+ id: "error.resource",
121
+ init: async () => {
122
+ throw new Error("Resource error");
123
+ },
124
+ });
125
+
126
+ const app = defineResource({
127
+ id: "app",
128
+ register: [errorResource],
129
+ });
130
+
131
+ await expect(run(app)).rejects.toThrow(/Resource error/);
132
+ });
133
+
134
+ it("should throw an ambigous one", async () => {
135
+ const res1 = defineResource({
136
+ id: "res1",
137
+ });
138
+
139
+ const ev1 = defineEvent({
140
+ id: "res1",
141
+ });
142
+
143
+ const app = defineResource({
144
+ id: "app",
145
+ register: [res1, ev1],
146
+ });
147
+
148
+ await expect(run(app)).rejects.toThrow(
149
+ Errors.duplicateRegistration("Resource", "res1").message
150
+ );
151
+ });
152
+
153
+ it("Should throw duplicate error for middlewares with the same id", async () => {
154
+ const middleware1 = defineMiddleware({
155
+ id: "middlewarex",
156
+ run: async () => {},
157
+ });
158
+
159
+ const middleware2 = defineMiddleware({
160
+ id: "middlewarex",
161
+ run: async () => {},
162
+ });
163
+
164
+ const app = defineResource({
165
+ id: "app",
166
+ register: [middleware1, middleware2],
167
+ });
168
+
169
+ await expect(run(app)).rejects.toThrow(
170
+ Errors.duplicateRegistration("Middleware", "middlewarex").message
171
+ );
172
+ });
173
+
174
+ it("Should throw duplicate error for events with the same id", async () => {
175
+ const ev1 = defineEvent({
176
+ id: "ev1",
177
+ });
178
+ const ev2 = defineEvent({
179
+ id: "ev1",
180
+ });
181
+
182
+ const app = defineResource({
183
+ id: "app",
184
+ register: [ev1, ev2],
185
+ });
186
+
187
+ await expect(run(app)).rejects.toThrow(
188
+ Errors.duplicateRegistration("Event", "ev1").message
189
+ );
190
+ });
191
+
192
+ it("should throw an error if a hook tries to use a non-reigstered event", async () => {
193
+ const hook = { event: { id: "non.existent.event" } as any, run: () => {} };
194
+
195
+ const app = defineResource({
196
+ id: "app",
197
+ hooks: [hook],
198
+ });
199
+
200
+ await expect(run(app)).rejects.toThrow(
201
+ Errors.eventNotFound("non.existent.event").message
202
+ );
203
+ });
204
+
205
+ it("should throw an error when a task depends on a non-registered task", async () => {
206
+ const offTheGrid = defineTask({
207
+ id: "test.off.the.grid",
208
+ dependencies: { nonExistentTask: { id: "non" } as any },
209
+ run: async () => {},
210
+ });
211
+
212
+ const task = defineTask({
213
+ id: "test.task",
214
+ dependencies: { offTheGrid },
215
+ run: async (_, deps) => {
216
+ throw "Should not even be here";
217
+ },
218
+ });
219
+
220
+ const app = defineResource({
221
+ id: "app",
222
+ register: [task],
223
+ dependencies: { task },
224
+ async init(_, { task }) {
225
+ await task();
226
+ },
227
+ });
228
+
229
+ await expect(run(app)).rejects.toThrow(
230
+ Errors.dependencyNotFound("Task test.off.the.grid").message
231
+ );
232
+ });
233
+
234
+ it("should throw when a task depends on a non-registered resource", async () => {
235
+ const offTheGrid = defineResource({
236
+ id: "test.off.the.grid",
237
+ init: async () => {},
238
+ });
239
+
240
+ const task = defineTask({
241
+ id: "test.task",
242
+ dependencies: { offTheGrid },
243
+ run: async () => {
244
+ throw "Should not even be here";
245
+ },
246
+ });
247
+
248
+ const app = defineResource({
249
+ id: "app",
250
+ register: [task],
251
+ dependencies: { task },
252
+ async init(_, { task }) {
253
+ await task();
254
+ },
255
+ });
256
+
257
+ await expect(run(app)).rejects.toThrow(
258
+ Errors.dependencyNotFound("Resource test.off.the.grid").message
259
+ );
260
+ });
261
+
262
+ it("should throw error, when a double global() is used on a middleware", async () => {
263
+ const first = defineMiddleware({ id: "x", run: async () => {} }).global();
264
+ expect(() => first.global()).toThrow(
265
+ Errors.middlewareAlreadyGlobal("x").message
266
+ );
267
+ });
268
+ });