@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.
- package/LICENSE.md +7 -0
- package/README.md +797 -0
- package/dist/DependencyProcessor.d.ts +49 -0
- package/dist/DependencyProcessor.js +178 -0
- package/dist/DependencyProcessor.js.map +1 -0
- package/dist/EventManager.d.ts +13 -0
- package/dist/EventManager.js +58 -0
- package/dist/EventManager.js.map +1 -0
- package/dist/ResourceInitializer.d.ts +13 -0
- package/dist/ResourceInitializer.js +54 -0
- package/dist/ResourceInitializer.js.map +1 -0
- package/dist/Store.d.ts +62 -0
- package/dist/Store.js +186 -0
- package/dist/Store.js.map +1 -0
- package/dist/TaskRunner.d.ts +22 -0
- package/dist/TaskRunner.js +93 -0
- package/dist/TaskRunner.js.map +1 -0
- package/dist/define.d.ts +10 -0
- package/dist/define.js +111 -0
- package/dist/define.js.map +1 -0
- package/dist/defs.d.ts +127 -0
- package/dist/defs.js +12 -0
- package/dist/defs.js.map +1 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +12 -0
- package/dist/errors.js.map +1 -0
- package/dist/globalEvents.d.ts +36 -0
- package/dist/globalEvents.js +45 -0
- package/dist/globalEvents.js.map +1 -0
- package/dist/globalResources.d.ts +8 -0
- package/dist/globalResources.js +19 -0
- package/dist/globalResources.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/run.d.ts +32 -0
- package/dist/run.js +39 -0
- package/dist/run.js.map +1 -0
- package/dist/tools/findCircularDependencies.d.ts +16 -0
- package/dist/tools/findCircularDependencies.js +53 -0
- package/dist/tools/findCircularDependencies.js.map +1 -0
- package/package.json +50 -0
- package/src/DependencyProcessor.ts +243 -0
- package/src/EventManager.ts +84 -0
- package/src/ResourceInitializer.ts +69 -0
- package/src/Store.ts +250 -0
- package/src/TaskRunner.ts +135 -0
- package/src/__tests__/EventManager.test.ts +96 -0
- package/src/__tests__/ResourceInitializer.test.ts +109 -0
- package/src/__tests__/Store.test.ts +143 -0
- package/src/__tests__/TaskRunner.test.ts +135 -0
- package/src/__tests__/benchmark/benchmark.test.ts +146 -0
- package/src/__tests__/errors.test.ts +268 -0
- package/src/__tests__/globalEvents.test.ts +99 -0
- package/src/__tests__/index.ts +9 -0
- package/src/__tests__/run.hooks.test.ts +110 -0
- package/src/__tests__/run.test.ts +614 -0
- package/src/__tests__/tools/findCircularDependencies.test.ts +217 -0
- package/src/define.ts +142 -0
- package/src/defs.ts +221 -0
- package/src/errors.ts +22 -0
- package/src/globalEvents.ts +64 -0
- package/src/globalResources.ts +19 -0
- package/src/index.ts +28 -0
- package/src/run.ts +98 -0
- package/src/tools/findCircularDependencies.ts +69 -0
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findCircularDependencies,
|
|
3
|
+
IDependentNode,
|
|
4
|
+
} from "../../tools/findCircularDependencies";
|
|
5
|
+
|
|
6
|
+
describe("checkCircularDependencies", () => {
|
|
7
|
+
test("should detect a simple cycle (A -> B -> C -> A)", () => {
|
|
8
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
9
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
10
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
11
|
+
|
|
12
|
+
nodeA.dependencies["B"] = nodeB;
|
|
13
|
+
nodeB.dependencies["C"] = nodeC;
|
|
14
|
+
nodeC.dependencies["A"] = nodeA;
|
|
15
|
+
|
|
16
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC];
|
|
17
|
+
const result = findCircularDependencies(nodes);
|
|
18
|
+
|
|
19
|
+
expect(result.cycles).toEqual(["A -> B -> C -> A"]);
|
|
20
|
+
expect(result.missingDependencies).toEqual([]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("should detect multiple cycles", () => {
|
|
24
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
25
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
26
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
27
|
+
const nodeD: IDependentNode = { id: "D", dependencies: {} };
|
|
28
|
+
|
|
29
|
+
nodeA.dependencies["B"] = nodeB;
|
|
30
|
+
nodeB.dependencies["C"] = nodeC;
|
|
31
|
+
nodeC.dependencies["A"] = nodeA; // Cycle: A -> B -> C -> A
|
|
32
|
+
nodeC.dependencies["D"] = nodeD;
|
|
33
|
+
nodeD.dependencies["B"] = nodeB; // Cycle: B -> C -> D -> B
|
|
34
|
+
|
|
35
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC, nodeD];
|
|
36
|
+
const result = findCircularDependencies(nodes);
|
|
37
|
+
|
|
38
|
+
// The order of cycles in the result array might vary
|
|
39
|
+
expect(result.cycles).toContain("A -> B -> C -> A");
|
|
40
|
+
expect(result.cycles).toContain("B -> C -> D -> B");
|
|
41
|
+
expect(result.cycles.length).toBe(2);
|
|
42
|
+
expect(result.missingDependencies).toEqual([]);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("should return empty arrays when there are no cycles", () => {
|
|
46
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
47
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
48
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
49
|
+
|
|
50
|
+
nodeA.dependencies["B"] = nodeB;
|
|
51
|
+
nodeB.dependencies["C"] = nodeC;
|
|
52
|
+
|
|
53
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC];
|
|
54
|
+
const result = findCircularDependencies(nodes);
|
|
55
|
+
|
|
56
|
+
expect(result.cycles).toEqual([]);
|
|
57
|
+
expect(result.missingDependencies).toEqual([]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("should detect a self-referential cycle (A -> A)", () => {
|
|
61
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
62
|
+
nodeA.dependencies["A"] = nodeA; // Cycle: A -> A
|
|
63
|
+
|
|
64
|
+
const nodes: IDependentNode[] = [nodeA];
|
|
65
|
+
const result = findCircularDependencies(nodes);
|
|
66
|
+
|
|
67
|
+
expect(result.cycles).toEqual(["A -> A"]);
|
|
68
|
+
expect(result.missingDependencies).toEqual([]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should handle complex graphs with shared nodes", () => {
|
|
72
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
73
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
74
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
75
|
+
const nodeD: IDependentNode = { id: "D", dependencies: {} };
|
|
76
|
+
const nodeE: IDependentNode = { id: "E", dependencies: {} };
|
|
77
|
+
|
|
78
|
+
nodeA.dependencies["B"] = nodeB;
|
|
79
|
+
nodeB.dependencies["C"] = nodeC;
|
|
80
|
+
nodeC.dependencies["D"] = nodeD;
|
|
81
|
+
nodeD.dependencies["B"] = nodeB; // Cycle: B -> C -> D -> B
|
|
82
|
+
nodeC.dependencies["E"] = nodeE;
|
|
83
|
+
nodeE.dependencies["C"] = nodeC; // Cycle: C -> E -> C
|
|
84
|
+
|
|
85
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC, nodeD, nodeE];
|
|
86
|
+
const result = findCircularDependencies(nodes);
|
|
87
|
+
|
|
88
|
+
expect(result.cycles).toContain("B -> C -> D -> B");
|
|
89
|
+
expect(result.cycles).toContain("C -> E -> C");
|
|
90
|
+
expect(result.cycles.length).toBe(2);
|
|
91
|
+
expect(result.missingDependencies).toEqual([]);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("should handle multiple independent cycles", () => {
|
|
95
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
96
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
97
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
98
|
+
const nodeD: IDependentNode = { id: "D", dependencies: {} };
|
|
99
|
+
const nodeE: IDependentNode = { id: "E", dependencies: {} };
|
|
100
|
+
const nodeF: IDependentNode = { id: "F", dependencies: {} };
|
|
101
|
+
|
|
102
|
+
// Cycle 1: A -> B -> A
|
|
103
|
+
nodeA.dependencies["B"] = nodeB;
|
|
104
|
+
nodeB.dependencies["A"] = nodeA;
|
|
105
|
+
|
|
106
|
+
// Cycle 2: C -> D -> E -> C
|
|
107
|
+
nodeC.dependencies["D"] = nodeD;
|
|
108
|
+
nodeD.dependencies["E"] = nodeE;
|
|
109
|
+
nodeE.dependencies["C"] = nodeC;
|
|
110
|
+
|
|
111
|
+
// Cycle 3: F -> F
|
|
112
|
+
nodeF.dependencies["F"] = nodeF;
|
|
113
|
+
|
|
114
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC, nodeD, nodeE, nodeF];
|
|
115
|
+
const result = findCircularDependencies(nodes);
|
|
116
|
+
|
|
117
|
+
expect(result.cycles).toContain("A -> B -> A");
|
|
118
|
+
expect(result.cycles).toContain("C -> D -> E -> C");
|
|
119
|
+
expect(result.cycles).toContain("F -> F");
|
|
120
|
+
expect(result.cycles.length).toBe(3);
|
|
121
|
+
expect(result.missingDependencies).toEqual([]);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("should handle disconnected graphs", () => {
|
|
125
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
126
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
127
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
128
|
+
const nodeD: IDependentNode = { id: "D", dependencies: {} };
|
|
129
|
+
|
|
130
|
+
// Cycle 1: A -> B -> A
|
|
131
|
+
nodeA.dependencies["B"] = nodeB;
|
|
132
|
+
nodeB.dependencies["A"] = nodeA;
|
|
133
|
+
|
|
134
|
+
// Cycle 2: C -> D -> C
|
|
135
|
+
nodeC.dependencies["D"] = nodeD;
|
|
136
|
+
nodeD.dependencies["C"] = nodeC;
|
|
137
|
+
|
|
138
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC, nodeD];
|
|
139
|
+
const result = findCircularDependencies(nodes);
|
|
140
|
+
|
|
141
|
+
expect(result.cycles).toContain("A -> B -> A");
|
|
142
|
+
expect(result.cycles).toContain("C -> D -> C");
|
|
143
|
+
expect(result.cycles.length).toBe(2);
|
|
144
|
+
expect(result.missingDependencies).toEqual([]);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should handle nodes with no dependencies", () => {
|
|
148
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
149
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
150
|
+
|
|
151
|
+
const nodes: IDependentNode[] = [nodeA, nodeB];
|
|
152
|
+
const result = findCircularDependencies(nodes);
|
|
153
|
+
|
|
154
|
+
expect(result.cycles).toEqual([]);
|
|
155
|
+
expect(result.missingDependencies).toEqual([]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("should handle dependencies that do not form cycles", () => {
|
|
159
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
160
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
161
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
162
|
+
const nodeD: IDependentNode = { id: "D", dependencies: {} };
|
|
163
|
+
|
|
164
|
+
nodeA.dependencies["B"] = nodeB;
|
|
165
|
+
nodeB.dependencies["C"] = nodeC;
|
|
166
|
+
nodeC.dependencies["D"] = nodeD;
|
|
167
|
+
|
|
168
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC, nodeD];
|
|
169
|
+
const result = findCircularDependencies(nodes);
|
|
170
|
+
|
|
171
|
+
expect(result.cycles).toEqual([]);
|
|
172
|
+
expect(result.missingDependencies).toEqual([]);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("should report missing dependencies", () => {
|
|
176
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
177
|
+
// nodeB is missing
|
|
178
|
+
nodeA.dependencies["B"] = undefined as any; // Force undefined dependency
|
|
179
|
+
|
|
180
|
+
const nodes: IDependentNode[] = [nodeA];
|
|
181
|
+
const result = findCircularDependencies(nodes);
|
|
182
|
+
|
|
183
|
+
expect(result.cycles).toEqual([]);
|
|
184
|
+
expect(result.missingDependencies).toEqual([
|
|
185
|
+
{ nodeId: "A", dependencyId: "B" },
|
|
186
|
+
]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("should handle complex graph with multiple overlapping cycles", () => {
|
|
190
|
+
const nodeA: IDependentNode = { id: "A", dependencies: {} };
|
|
191
|
+
const nodeB: IDependentNode = { id: "B", dependencies: {} };
|
|
192
|
+
const nodeC: IDependentNode = { id: "C", dependencies: {} };
|
|
193
|
+
const nodeD: IDependentNode = { id: "D", dependencies: {} };
|
|
194
|
+
const nodeE: IDependentNode = { id: "E", dependencies: {} };
|
|
195
|
+
const nodeF: IDependentNode = { id: "F", dependencies: {} };
|
|
196
|
+
|
|
197
|
+
// Construct cycles:
|
|
198
|
+
// A -> B -> C -> A
|
|
199
|
+
// C -> D -> E -> F -> C
|
|
200
|
+
|
|
201
|
+
nodeA.dependencies["B"] = nodeB;
|
|
202
|
+
nodeB.dependencies["C"] = nodeC;
|
|
203
|
+
nodeC.dependencies["A"] = nodeA;
|
|
204
|
+
nodeC.dependencies["D"] = nodeD;
|
|
205
|
+
nodeD.dependencies["E"] = nodeE;
|
|
206
|
+
nodeE.dependencies["F"] = nodeF;
|
|
207
|
+
nodeF.dependencies["C"] = nodeC;
|
|
208
|
+
|
|
209
|
+
const nodes: IDependentNode[] = [nodeA, nodeB, nodeC, nodeD, nodeE, nodeF];
|
|
210
|
+
const result = findCircularDependencies(nodes);
|
|
211
|
+
|
|
212
|
+
expect(result.cycles).toContain("A -> B -> C -> A");
|
|
213
|
+
expect(result.cycles).toContain("C -> D -> E -> F -> C");
|
|
214
|
+
expect(result.cycles.length).toBe(2);
|
|
215
|
+
expect(result.missingDependencies).toEqual([]);
|
|
216
|
+
});
|
|
217
|
+
});
|
package/src/define.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ITask,
|
|
3
|
+
ITaskDefinition,
|
|
4
|
+
IResource,
|
|
5
|
+
IResourceWithConfig,
|
|
6
|
+
IResourceDefinintion,
|
|
7
|
+
IEventDefinition,
|
|
8
|
+
IMiddlewareDefinition,
|
|
9
|
+
symbols,
|
|
10
|
+
DependencyMapType,
|
|
11
|
+
DependencyValuesType,
|
|
12
|
+
IMiddleware,
|
|
13
|
+
} from "./defs";
|
|
14
|
+
import { Errors } from "./errors";
|
|
15
|
+
|
|
16
|
+
export function defineTask<
|
|
17
|
+
Input = undefined,
|
|
18
|
+
Output extends Promise<any> = any,
|
|
19
|
+
Deps extends DependencyMapType = any,
|
|
20
|
+
Test = any
|
|
21
|
+
>(
|
|
22
|
+
config: ITaskDefinition<Input, Output, Deps, Test>
|
|
23
|
+
): ITask<Input, Output, Deps, Test> {
|
|
24
|
+
const autorun: any = {};
|
|
25
|
+
// if (config.autorun) {
|
|
26
|
+
// if (config.autorun.on) {
|
|
27
|
+
// autorun.on = Array.isArray(config.autorun.on);
|
|
28
|
+
// autorun.schedule = config.autorun.schedule;
|
|
29
|
+
// }
|
|
30
|
+
// }
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
[symbols.task]: true,
|
|
34
|
+
id: config.id,
|
|
35
|
+
dependencies: config.dependencies || ({} as Deps),
|
|
36
|
+
middleware: config.middleware || [],
|
|
37
|
+
run: config.run,
|
|
38
|
+
on: config.on,
|
|
39
|
+
events: {
|
|
40
|
+
beforeRun: defineEvent({
|
|
41
|
+
id: `${config.id}.beforeRun`,
|
|
42
|
+
}),
|
|
43
|
+
afterRun: defineEvent({
|
|
44
|
+
id: `${config.id}.afterRun`,
|
|
45
|
+
}),
|
|
46
|
+
onError: defineEvent({
|
|
47
|
+
id: `${config.id}.onError`,
|
|
48
|
+
}),
|
|
49
|
+
},
|
|
50
|
+
// autorun,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function defineResource<
|
|
55
|
+
TConfig = void,
|
|
56
|
+
TValue = any,
|
|
57
|
+
TDeps extends DependencyMapType = {}
|
|
58
|
+
>(
|
|
59
|
+
constConfig: IResourceDefinintion<TConfig, TValue, TDeps>
|
|
60
|
+
): IResource<TConfig, TValue, TDeps> {
|
|
61
|
+
return {
|
|
62
|
+
[symbols.resource]: true,
|
|
63
|
+
id: constConfig.id,
|
|
64
|
+
dependencies: constConfig.dependencies,
|
|
65
|
+
hooks: constConfig.hooks || [],
|
|
66
|
+
register: constConfig.register || [],
|
|
67
|
+
init: constConfig.init,
|
|
68
|
+
with: function (config: TConfig) {
|
|
69
|
+
return {
|
|
70
|
+
[symbols.resourceWithConfig]: true,
|
|
71
|
+
resource: this,
|
|
72
|
+
config,
|
|
73
|
+
};
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
events: {
|
|
77
|
+
beforeInit: defineEvent({
|
|
78
|
+
id: `${constConfig.id}.beforeInit`,
|
|
79
|
+
}),
|
|
80
|
+
afterInit: defineEvent({
|
|
81
|
+
id: `${constConfig.id}.afterInit`,
|
|
82
|
+
}),
|
|
83
|
+
onError: defineEvent({
|
|
84
|
+
id: `${constConfig.id}.onError`,
|
|
85
|
+
}),
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function defineEvent<TPayload = any>(
|
|
91
|
+
config: IEventDefinition<TPayload>
|
|
92
|
+
): IEventDefinition<TPayload> {
|
|
93
|
+
return {
|
|
94
|
+
[symbols.event]: true,
|
|
95
|
+
...config,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function defineMiddleware<TDeps extends DependencyMapType = {}>(
|
|
100
|
+
config: IMiddlewareDefinition<TDeps>
|
|
101
|
+
): IMiddleware<TDeps> {
|
|
102
|
+
const object = {
|
|
103
|
+
[symbols.middleware]: true,
|
|
104
|
+
...config,
|
|
105
|
+
dependencies: config.dependencies || ({} as TDeps),
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
...object,
|
|
110
|
+
global() {
|
|
111
|
+
return {
|
|
112
|
+
...object,
|
|
113
|
+
[symbols.middlewareGlobal]: true,
|
|
114
|
+
global() {
|
|
115
|
+
throw Errors.middlewareAlreadyGlobal(config.id);
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function isTask(definition: any): definition is ITask {
|
|
123
|
+
return definition && definition[symbols.task];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function isResource(definition: any): definition is IResource {
|
|
127
|
+
return definition && definition[symbols.resource];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function isResourceWithConfig(
|
|
131
|
+
definition: any
|
|
132
|
+
): definition is IResourceWithConfig {
|
|
133
|
+
return definition && definition[symbols.resourceWithConfig];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function isEvent(definition: any): definition is IEventDefinition {
|
|
137
|
+
return definition && definition[symbols.event];
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function isMiddleware(definition: any): definition is IMiddleware {
|
|
141
|
+
return definition && definition[symbols.middleware];
|
|
142
|
+
}
|
package/src/defs.ts
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
export const symbols = {
|
|
2
|
+
task: Symbol("task"),
|
|
3
|
+
resource: Symbol("resource"),
|
|
4
|
+
resourceWithConfig: Symbol("resourceWithConfig"),
|
|
5
|
+
event: Symbol("event"),
|
|
6
|
+
middleware: Symbol("middleware"),
|
|
7
|
+
middlewareGlobal: Symbol("middlewareGlobal"),
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export interface IMeta {
|
|
11
|
+
title?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
tags: string[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ITaskMeta extends IMeta {}
|
|
17
|
+
export interface IResourceMeta extends IMeta {}
|
|
18
|
+
export interface IEventMeta extends IMeta {}
|
|
19
|
+
export interface IMiddlewareMeta extends IMeta {}
|
|
20
|
+
|
|
21
|
+
// DependencyMap types
|
|
22
|
+
export type DependencyMapType = Record<
|
|
23
|
+
string,
|
|
24
|
+
ITask | IResource | IEventDefinition
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
export type DependencyValueType<T> = T extends ITask<
|
|
28
|
+
infer I,
|
|
29
|
+
infer O,
|
|
30
|
+
/** The infer D, while not used is crucial for making this work correctly, otherwise it forces input: unknown to a dependency that has a dependency. */
|
|
31
|
+
infer D
|
|
32
|
+
>
|
|
33
|
+
? (...args: I extends unknown ? [] : [I]) => O
|
|
34
|
+
: T extends IResource<any, infer V>
|
|
35
|
+
? V
|
|
36
|
+
: T extends IEventDefinition<infer P>
|
|
37
|
+
? (input: P) => Promise<void> | never
|
|
38
|
+
: never;
|
|
39
|
+
|
|
40
|
+
export type DependencyValuesType<T extends DependencyMapType> = {
|
|
41
|
+
[K in keyof T]: DependencyValueType<T[K]>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// RegisterableItems Type with Conditional Inclusion
|
|
45
|
+
export type RegisterableItems =
|
|
46
|
+
| IResource<void> // Always include IResource<void>
|
|
47
|
+
| IResourceWithConfig<any>
|
|
48
|
+
| ITaskDefinition
|
|
49
|
+
| IMiddlewareDefinition
|
|
50
|
+
| IEventDefinition;
|
|
51
|
+
|
|
52
|
+
export interface ITaskDefinition<
|
|
53
|
+
TInput = any,
|
|
54
|
+
TOutput extends Promise<any> = any,
|
|
55
|
+
TDependencies extends DependencyMapType = {},
|
|
56
|
+
TEventDefinitionInput = null
|
|
57
|
+
> {
|
|
58
|
+
id: string;
|
|
59
|
+
dependencies?: TDependencies | (() => TDependencies);
|
|
60
|
+
middleware?: IMiddlewareDefinition[];
|
|
61
|
+
on?: IEventDefinition<TEventDefinitionInput>;
|
|
62
|
+
meta?: ITaskMeta;
|
|
63
|
+
run: (
|
|
64
|
+
input: TEventDefinitionInput extends null ? TInput : TEventDefinitionInput,
|
|
65
|
+
dependencies: DependencyValuesType<TDependencies>
|
|
66
|
+
) => TOutput;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type BeforeRunEventPayload<TInput> = {
|
|
70
|
+
input: TInput;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type AfterRunEventPayload<TInput, TOutput> = {
|
|
74
|
+
input: TInput;
|
|
75
|
+
output: TOutput;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export type OnErrorEventPayload = {
|
|
79
|
+
error: any;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export type BeforeInitEventPayload<TConfig> = {
|
|
83
|
+
config: TConfig;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
export type AfterInitEventPayload<TConfig, TValue> = {
|
|
87
|
+
config: TConfig;
|
|
88
|
+
value: TValue;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* This is the response after the definition has been prepared. TODO: better naming?
|
|
93
|
+
*/
|
|
94
|
+
export interface ITask<
|
|
95
|
+
TInput = any,
|
|
96
|
+
TOutput extends Promise<any> = any,
|
|
97
|
+
TDependencies extends DependencyMapType = {},
|
|
98
|
+
TEventDefinitionInput = null
|
|
99
|
+
> extends ITaskDefinition<
|
|
100
|
+
TInput,
|
|
101
|
+
TOutput,
|
|
102
|
+
TDependencies,
|
|
103
|
+
TEventDefinitionInput
|
|
104
|
+
> {
|
|
105
|
+
dependencies: TDependencies | (() => TDependencies);
|
|
106
|
+
computedDependencies?: DependencyValuesType<TDependencies>;
|
|
107
|
+
middleware: IMiddlewareDefinition[];
|
|
108
|
+
/**
|
|
109
|
+
* These events are automatically populated after the task has been defined.
|
|
110
|
+
*/
|
|
111
|
+
events: {
|
|
112
|
+
beforeRun: IEventDefinition<BeforeRunEventPayload<TInput>>;
|
|
113
|
+
afterRun: IEventDefinition<AfterRunEventPayload<TInput, TOutput>>;
|
|
114
|
+
onError: IEventDefinition<OnErrorEventPayload>;
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
// Resource interfaces
|
|
118
|
+
export interface IResourceDefinintion<
|
|
119
|
+
TConfig = void,
|
|
120
|
+
TValue = unknown,
|
|
121
|
+
TDependencies extends DependencyMapType = {}
|
|
122
|
+
> {
|
|
123
|
+
id: string;
|
|
124
|
+
dependencies?: TDependencies | ((config: TConfig) => TDependencies);
|
|
125
|
+
hooks?:
|
|
126
|
+
| IHookDefinition<TDependencies>[]
|
|
127
|
+
| ((config: TConfig) => IHookDefinition<TDependencies>[]);
|
|
128
|
+
register?:
|
|
129
|
+
| Array<RegisterableItems>
|
|
130
|
+
| ((config: TConfig) => Array<RegisterableItems>);
|
|
131
|
+
init?: (
|
|
132
|
+
config: TConfig,
|
|
133
|
+
dependencies: DependencyValuesType<TDependencies>
|
|
134
|
+
) => Promise<TValue>;
|
|
135
|
+
meta?: IResourceMeta;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export interface IResource<
|
|
139
|
+
TConfig = void,
|
|
140
|
+
TValue = any,
|
|
141
|
+
TDependencies extends DependencyMapType = any
|
|
142
|
+
> extends IResourceDefinintion<TConfig, TValue, TDependencies> {
|
|
143
|
+
with(config: TConfig): IResourceWithConfig<TConfig, TValue, TDependencies>;
|
|
144
|
+
register:
|
|
145
|
+
| Array<RegisterableItems>
|
|
146
|
+
| ((config: TConfig) => Array<RegisterableItems>);
|
|
147
|
+
/**
|
|
148
|
+
* These events are automatically populated after the task has been defined.
|
|
149
|
+
*/
|
|
150
|
+
events: {
|
|
151
|
+
beforeInit: IEventDefinition<BeforeInitEventPayload<TConfig>>;
|
|
152
|
+
afterInit: IEventDefinition<AfterInitEventPayload<TConfig, TValue>>;
|
|
153
|
+
onError: IEventDefinition<OnErrorEventPayload>;
|
|
154
|
+
};
|
|
155
|
+
hooks:
|
|
156
|
+
| IHookDefinition<TDependencies>[]
|
|
157
|
+
| ((config: TConfig) => IHookDefinition<TDependencies>[]);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export interface IResourceWithConfig<
|
|
161
|
+
TConfig = any,
|
|
162
|
+
TValue = any,
|
|
163
|
+
TDependencies extends DependencyMapType = any
|
|
164
|
+
> {
|
|
165
|
+
resource: IResource<TConfig, TValue, TDependencies>;
|
|
166
|
+
config: TConfig;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export interface IEvent<TPayload = any> {
|
|
170
|
+
id: string;
|
|
171
|
+
data: TPayload;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type EventHandlerType<T = any> = (
|
|
175
|
+
event: IEvent<T>
|
|
176
|
+
) => any | Promise<any>;
|
|
177
|
+
|
|
178
|
+
// Other necessary interfaces
|
|
179
|
+
export interface IEventDefinition<TPayload = void> {
|
|
180
|
+
id: string;
|
|
181
|
+
meta?: IEventMeta;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface IMiddlewareDefinition<
|
|
185
|
+
TDependencies extends DependencyMapType = any
|
|
186
|
+
> {
|
|
187
|
+
id: string;
|
|
188
|
+
dependencies?: TDependencies | (() => TDependencies);
|
|
189
|
+
run: (
|
|
190
|
+
input: IMiddlewareExecutionInput,
|
|
191
|
+
dependencies: DependencyValuesType<TDependencies>
|
|
192
|
+
) => Promise<any>;
|
|
193
|
+
meta?: IMiddlewareMeta;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface IMiddleware<TDependencies extends DependencyMapType = any>
|
|
197
|
+
extends IMiddlewareDefinition<TDependencies> {
|
|
198
|
+
dependencies: TDependencies | (() => TDependencies);
|
|
199
|
+
global(): IMiddleware<TDependencies>;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface IMiddlewareDefinitionConfigured<
|
|
203
|
+
C extends Record<string, any> = {}
|
|
204
|
+
> {
|
|
205
|
+
middleware: IMiddleware<C>;
|
|
206
|
+
config?: C;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface IMiddlewareExecutionInput {
|
|
210
|
+
taskDefinition?: ITask;
|
|
211
|
+
input: any;
|
|
212
|
+
next: (input?: any) => Promise<any>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export interface IHookDefinition<D extends DependencyMapType = {}> {
|
|
216
|
+
event: IEventDefinition;
|
|
217
|
+
run: (
|
|
218
|
+
event: IEvent,
|
|
219
|
+
dependencies: DependencyValuesType<D>
|
|
220
|
+
) => Promise<void> | void;
|
|
221
|
+
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { ITask, IResource } from "./defs";
|
|
2
|
+
|
|
3
|
+
export const Errors = {
|
|
4
|
+
duplicateRegistration: (type: string, id: string) =>
|
|
5
|
+
new Error(`${type} "${id}" already registered`),
|
|
6
|
+
|
|
7
|
+
dependencyNotFound: (key: string) =>
|
|
8
|
+
new Error(
|
|
9
|
+
`Dependency ${key} not found. Did you forget to register it through a resource?`
|
|
10
|
+
),
|
|
11
|
+
|
|
12
|
+
unknownItemType: (item: any) => new Error(`Unknown item type: ${item}`),
|
|
13
|
+
|
|
14
|
+
circularDependencies: (cycles: string[]) =>
|
|
15
|
+
new Error(`Circular dependencies detected: ${cycles.join(", ")}`),
|
|
16
|
+
|
|
17
|
+
eventNotFound: (id: string) =>
|
|
18
|
+
new Error(`Event "${id}" not found. Did you forget to register it?`),
|
|
19
|
+
|
|
20
|
+
middlewareAlreadyGlobal: (id: string) =>
|
|
21
|
+
new Error("Cannot call global on a global middleware: " + id),
|
|
22
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { defineEvent } from "./define";
|
|
2
|
+
import { ITask, IResource } from "./defs";
|
|
3
|
+
|
|
4
|
+
export const globalEvents = {
|
|
5
|
+
beforeInit: defineEvent({
|
|
6
|
+
id: "global.beforeInit",
|
|
7
|
+
}),
|
|
8
|
+
afterInit: defineEvent({
|
|
9
|
+
id: "global.afterInit",
|
|
10
|
+
}),
|
|
11
|
+
tasks: {
|
|
12
|
+
beforeRun: defineEvent<{
|
|
13
|
+
task: ITask<any, any, any>;
|
|
14
|
+
input: any;
|
|
15
|
+
}>({
|
|
16
|
+
id: "global.tasks.beforeRun",
|
|
17
|
+
}),
|
|
18
|
+
afterRun: defineEvent<{
|
|
19
|
+
task: ITask<any, any, any>;
|
|
20
|
+
input: any;
|
|
21
|
+
output: any;
|
|
22
|
+
}>({
|
|
23
|
+
id: "global.tasks.afterRun",
|
|
24
|
+
}),
|
|
25
|
+
onError: defineEvent<{
|
|
26
|
+
error: Error;
|
|
27
|
+
task: ITask<any, any, any>;
|
|
28
|
+
}>({
|
|
29
|
+
id: "global.tasks.onError",
|
|
30
|
+
}),
|
|
31
|
+
},
|
|
32
|
+
resources: {
|
|
33
|
+
beforeInit: defineEvent<{
|
|
34
|
+
resource: IResource<any, any, any>;
|
|
35
|
+
config: any;
|
|
36
|
+
}>({
|
|
37
|
+
id: "global.resources.beforeInit",
|
|
38
|
+
}),
|
|
39
|
+
afterInit: defineEvent<{
|
|
40
|
+
resource: IResource<any, any, any>;
|
|
41
|
+
config: any;
|
|
42
|
+
value: any;
|
|
43
|
+
}>({
|
|
44
|
+
id: "global.resources.afterInit",
|
|
45
|
+
}),
|
|
46
|
+
onError: defineEvent<{
|
|
47
|
+
error: Error;
|
|
48
|
+
resource: IResource<any, any, any>;
|
|
49
|
+
}>({
|
|
50
|
+
id: "global.resources.onError",
|
|
51
|
+
}),
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const globalEventsArray = [
|
|
56
|
+
globalEvents.beforeInit,
|
|
57
|
+
globalEvents.afterInit,
|
|
58
|
+
globalEvents.tasks.beforeRun,
|
|
59
|
+
globalEvents.tasks.afterRun,
|
|
60
|
+
globalEvents.tasks.onError,
|
|
61
|
+
globalEvents.resources.beforeInit,
|
|
62
|
+
globalEvents.resources.afterInit,
|
|
63
|
+
globalEvents.resources.onError,
|
|
64
|
+
];
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineResource } from "./define";
|
|
2
|
+
import { EventManager } from "./EventManager";
|
|
3
|
+
import { Store } from "./Store";
|
|
4
|
+
import { TaskRunner } from "./TaskRunner";
|
|
5
|
+
|
|
6
|
+
export const globalResources = {
|
|
7
|
+
store: defineResource<Store>({
|
|
8
|
+
id: "global.store",
|
|
9
|
+
init: async (store) => store,
|
|
10
|
+
}),
|
|
11
|
+
eventManager: defineResource<EventManager>({
|
|
12
|
+
id: "global.eventManager",
|
|
13
|
+
init: async (em) => em,
|
|
14
|
+
}),
|
|
15
|
+
taskRunner: defineResource<TaskRunner>({
|
|
16
|
+
id: "global.taskRunner",
|
|
17
|
+
init: async (runner) => runner,
|
|
18
|
+
}),
|
|
19
|
+
};
|