@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
package/src/Store.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DependencyMapType,
|
|
3
|
+
DependencyValuesType,
|
|
4
|
+
IMiddlewareDefinition,
|
|
5
|
+
IEventDefinition,
|
|
6
|
+
IResource,
|
|
7
|
+
ITask,
|
|
8
|
+
IResourceWithConfig,
|
|
9
|
+
RegisterableItems,
|
|
10
|
+
symbols,
|
|
11
|
+
IMiddleware,
|
|
12
|
+
} from "./defs";
|
|
13
|
+
import * as utils from "./define";
|
|
14
|
+
import { IDependentNode } from "./tools/findCircularDependencies";
|
|
15
|
+
import { globalEventsArray } from "./globalEvents";
|
|
16
|
+
import { Errors } from "./errors";
|
|
17
|
+
import { globalResources } from "./globalResources";
|
|
18
|
+
import { EventManager } from "./EventManager";
|
|
19
|
+
import { TaskRunner } from "./TaskRunner";
|
|
20
|
+
|
|
21
|
+
export type ResourceStoreElementType<
|
|
22
|
+
C = any,
|
|
23
|
+
V = any,
|
|
24
|
+
D extends DependencyMapType = {}
|
|
25
|
+
> = {
|
|
26
|
+
resource: IResource<C, V, D>;
|
|
27
|
+
computedDependencies?: DependencyValuesType<D>;
|
|
28
|
+
config: C;
|
|
29
|
+
value: V;
|
|
30
|
+
isInitialized?: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export type TaskStoreElementType<
|
|
34
|
+
Input = any,
|
|
35
|
+
Output extends Promise<any> = any,
|
|
36
|
+
D extends DependencyMapType = any
|
|
37
|
+
> = {
|
|
38
|
+
task: ITask<Input, Output, D>;
|
|
39
|
+
computedDependencies: DependencyValuesType<D>;
|
|
40
|
+
isInitialized: boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type MiddlewareStoreElementType<TDeps extends DependencyMapType = any> =
|
|
44
|
+
{
|
|
45
|
+
middleware: IMiddleware<TDeps>;
|
|
46
|
+
computedDependencies: DependencyValuesType<TDeps>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type EventStoreElementType = {
|
|
50
|
+
event: IEventDefinition;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* @internal This should be used for testing purposes only.
|
|
55
|
+
*/
|
|
56
|
+
export class Store {
|
|
57
|
+
root!: ResourceStoreElementType;
|
|
58
|
+
public tasks: Map<string, TaskStoreElementType> = new Map();
|
|
59
|
+
public resources: Map<string, ResourceStoreElementType> = new Map();
|
|
60
|
+
public events: Map<string, EventStoreElementType> = new Map();
|
|
61
|
+
public middlewares: Map<string, MiddlewareStoreElementType> = new Map();
|
|
62
|
+
|
|
63
|
+
constructor(protected readonly eventManager: EventManager) {}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Store the root before beginning registration
|
|
67
|
+
* @param root
|
|
68
|
+
* @param config
|
|
69
|
+
*/
|
|
70
|
+
initializeStore(root: IResource<any>, config: any) {
|
|
71
|
+
this.storeGenericItem(globalResources.eventManager.with(this.eventManager));
|
|
72
|
+
this.storeGenericItem(globalResources.store.with(this));
|
|
73
|
+
|
|
74
|
+
root.dependencies =
|
|
75
|
+
typeof root.dependencies === "function"
|
|
76
|
+
? root.dependencies(config)
|
|
77
|
+
: root.dependencies;
|
|
78
|
+
|
|
79
|
+
this.root = {
|
|
80
|
+
resource: root,
|
|
81
|
+
computedDependencies: {},
|
|
82
|
+
config,
|
|
83
|
+
value: undefined,
|
|
84
|
+
isInitialized: false,
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// register global events
|
|
88
|
+
globalEventsArray.forEach((event) => {
|
|
89
|
+
this.storeEvent(event);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
this.resources.set(root.id, this.root);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Beginning with the root, we perform registering to the container all the resources, tasks, middleware and events.
|
|
97
|
+
* @param element
|
|
98
|
+
* @param config
|
|
99
|
+
*/
|
|
100
|
+
computeRegisterOfResource<C>(element: IResource<C>, config?: C) {
|
|
101
|
+
const items =
|
|
102
|
+
typeof element.register === "function"
|
|
103
|
+
? element.register(config as C)
|
|
104
|
+
: element.register;
|
|
105
|
+
|
|
106
|
+
for (const item of items) {
|
|
107
|
+
this.storeGenericItem<C>(item);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* middlewares are already stored in their final form and the check for them would be redundant
|
|
113
|
+
* @param id
|
|
114
|
+
*/
|
|
115
|
+
protected checkIfIDExists(id: string): void | never {
|
|
116
|
+
if (this.tasks.has(id)) {
|
|
117
|
+
throw Errors.duplicateRegistration("Task", id);
|
|
118
|
+
}
|
|
119
|
+
if (this.resources.has(id)) {
|
|
120
|
+
throw Errors.duplicateRegistration("Resource", id);
|
|
121
|
+
}
|
|
122
|
+
if (this.events.has(id)) {
|
|
123
|
+
throw Errors.duplicateRegistration("Event", id);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public getGlobalMiddlewares(excludingIds: string[]): IMiddleware[] {
|
|
128
|
+
return Array.from(this.middlewares.values())
|
|
129
|
+
.filter((x) => x.middleware[symbols.middlewareGlobal])
|
|
130
|
+
.filter((x) => !excludingIds.includes(x.middleware.id))
|
|
131
|
+
.map((x) => x.middleware);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* If you want to register something to the store you can use this function.
|
|
136
|
+
* @param item
|
|
137
|
+
*/
|
|
138
|
+
public storeGenericItem<C>(item: RegisterableItems) {
|
|
139
|
+
if (utils.isTask(item)) {
|
|
140
|
+
this.storeTask<C>(item);
|
|
141
|
+
} else if (utils.isResource(item)) {
|
|
142
|
+
// Registration a simple resource, which is interpreted as a resource with no configuration.
|
|
143
|
+
this.storeResource<C>(item);
|
|
144
|
+
} else if (utils.isEvent(item)) {
|
|
145
|
+
this.storeEvent<C>(item);
|
|
146
|
+
} else if (utils.isMiddleware(item)) {
|
|
147
|
+
if (this.middlewares.has(item.id)) {
|
|
148
|
+
throw Errors.duplicateRegistration("Middleware", item.id);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
item.dependencies =
|
|
152
|
+
typeof item.dependencies === "function"
|
|
153
|
+
? item.dependencies()
|
|
154
|
+
: item.dependencies;
|
|
155
|
+
|
|
156
|
+
this.middlewares.set(item.id, {
|
|
157
|
+
middleware: item,
|
|
158
|
+
computedDependencies: {},
|
|
159
|
+
});
|
|
160
|
+
} else if (utils.isResourceWithConfig(item)) {
|
|
161
|
+
this.storeResourceWithConfig<C>(item);
|
|
162
|
+
} else {
|
|
163
|
+
throw Errors.unknownItemType(item);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
public storeEvent<C>(item: IEventDefinition<void>) {
|
|
168
|
+
this.checkIfIDExists(item.id);
|
|
169
|
+
|
|
170
|
+
this.events.set(item.id, { event: item });
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private storeResourceWithConfig<C>(item: IResourceWithConfig<any, any, any>) {
|
|
174
|
+
this.checkIfIDExists(item.resource.id);
|
|
175
|
+
|
|
176
|
+
this.resources.set(item.resource.id, {
|
|
177
|
+
resource: item.resource,
|
|
178
|
+
config: item.config,
|
|
179
|
+
value: undefined,
|
|
180
|
+
isInitialized: false,
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
this.computeRegisterOfResource(item.resource, item.config);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
private storeResource<C>(item: IResource<any, any, any>) {
|
|
187
|
+
this.checkIfIDExists(item.id);
|
|
188
|
+
|
|
189
|
+
this.storeEvent(item.events.beforeInit);
|
|
190
|
+
this.storeEvent(item.events.afterInit);
|
|
191
|
+
this.storeEvent(item.events.onError);
|
|
192
|
+
|
|
193
|
+
item.dependencies =
|
|
194
|
+
typeof item.dependencies === "function"
|
|
195
|
+
? item.dependencies()
|
|
196
|
+
: item.dependencies;
|
|
197
|
+
|
|
198
|
+
this.resources.set(item.id, {
|
|
199
|
+
resource: item,
|
|
200
|
+
config: {},
|
|
201
|
+
value: undefined,
|
|
202
|
+
isInitialized: false,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
this.computeRegisterOfResource(item, {});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private storeTask<C>(item: ITask<any, any, {}>) {
|
|
209
|
+
this.checkIfIDExists(item.id);
|
|
210
|
+
|
|
211
|
+
item.dependencies =
|
|
212
|
+
typeof item.dependencies === "function"
|
|
213
|
+
? item.dependencies()
|
|
214
|
+
: item.dependencies;
|
|
215
|
+
|
|
216
|
+
this.storeEvent(item.events.beforeRun);
|
|
217
|
+
this.storeEvent(item.events.afterRun);
|
|
218
|
+
this.storeEvent(item.events.onError);
|
|
219
|
+
|
|
220
|
+
this.tasks.set(item.id, {
|
|
221
|
+
task: item,
|
|
222
|
+
computedDependencies: {},
|
|
223
|
+
isInitialized: false,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
getDependentNodes(): IDependentNode[] {
|
|
228
|
+
const depenedants: IDependentNode[] = [];
|
|
229
|
+
for (const task of this.tasks.values()) {
|
|
230
|
+
depenedants.push({
|
|
231
|
+
id: task.task.id,
|
|
232
|
+
dependencies: task.task.dependencies,
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
for (const middleware of this.middlewares.values()) {
|
|
236
|
+
depenedants.push({
|
|
237
|
+
id: middleware.middleware.id,
|
|
238
|
+
dependencies: middleware.middleware.dependencies,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
for (const resource of this.resources.values()) {
|
|
242
|
+
depenedants.push({
|
|
243
|
+
id: resource.resource.id,
|
|
244
|
+
dependencies: resource.resource.dependencies || {},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return depenedants;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { DependencyMapType, DependencyValuesType, ITask } from "./defs";
|
|
2
|
+
import { Errors } from "./errors";
|
|
3
|
+
import { EventManager } from "./EventManager";
|
|
4
|
+
import { globalEvents } from "./globalEvents";
|
|
5
|
+
import {
|
|
6
|
+
MiddlewareStoreElementType,
|
|
7
|
+
Store,
|
|
8
|
+
TaskStoreElementType,
|
|
9
|
+
} from "./Store";
|
|
10
|
+
|
|
11
|
+
export class TaskRunner {
|
|
12
|
+
protected readonly runnerStore = new Map<
|
|
13
|
+
string,
|
|
14
|
+
(input: any) => Promise<any>
|
|
15
|
+
>();
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
protected readonly store: Store,
|
|
19
|
+
protected readonly eventManager: EventManager
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Begins the execution of an task. These are registered tasks and all sanity checks have been performed at this stage to ensure consistency of the object.
|
|
24
|
+
* This function can throw only if any of the event listeners or run function throws
|
|
25
|
+
*/
|
|
26
|
+
public async run<
|
|
27
|
+
TInput,
|
|
28
|
+
TOutput extends Promise<any>,
|
|
29
|
+
TDeps extends DependencyMapType
|
|
30
|
+
>(
|
|
31
|
+
task: ITask<TInput, TOutput, TDeps>,
|
|
32
|
+
input: TInput,
|
|
33
|
+
taskDependencies?: DependencyValuesType<TDeps>
|
|
34
|
+
): Promise<TOutput | undefined> {
|
|
35
|
+
let runner = this.runnerStore.get(task.id);
|
|
36
|
+
if (!runner) {
|
|
37
|
+
const storeTask = this.store.tasks.get(task.id) as TaskStoreElementType;
|
|
38
|
+
const deps = taskDependencies || storeTask.computedDependencies;
|
|
39
|
+
|
|
40
|
+
runner = this.createRunnerWithMiddleware<TInput, TOutput, TDeps>(
|
|
41
|
+
task,
|
|
42
|
+
deps
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
this.runnerStore.set(task.id, runner);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// begin by dispatching the event of creating it.
|
|
49
|
+
// then ensure the hooks are called
|
|
50
|
+
// then ensure the middleware are called
|
|
51
|
+
await this.eventManager.emit(task.events.beforeRun, { input });
|
|
52
|
+
await this.eventManager.emit(globalEvents.tasks.beforeRun, {
|
|
53
|
+
task,
|
|
54
|
+
input,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
let error;
|
|
58
|
+
try {
|
|
59
|
+
// craft the next function starting from the first next function
|
|
60
|
+
const output = await runner(input);
|
|
61
|
+
|
|
62
|
+
await this.eventManager.emit(task.events.afterRun, { input, output });
|
|
63
|
+
await this.eventManager.emit(globalEvents.tasks.afterRun, {
|
|
64
|
+
task,
|
|
65
|
+
input,
|
|
66
|
+
output,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return output;
|
|
70
|
+
} catch (e) {
|
|
71
|
+
error = e;
|
|
72
|
+
|
|
73
|
+
// If you want to rewthrow the error, this should be done inside the onError event.
|
|
74
|
+
await this.eventManager.emit(task.events.onError, { error });
|
|
75
|
+
await this.eventManager.emit(globalEvents.tasks.onError, {
|
|
76
|
+
task,
|
|
77
|
+
error,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
throw e;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Creates the function with the chain of middleware.
|
|
86
|
+
* @param task
|
|
87
|
+
* @param input
|
|
88
|
+
* @param taskDependencies
|
|
89
|
+
* @returns
|
|
90
|
+
*/
|
|
91
|
+
protected createRunnerWithMiddleware<
|
|
92
|
+
TInput,
|
|
93
|
+
TOutput extends Promise<any>,
|
|
94
|
+
TDeps extends DependencyMapType
|
|
95
|
+
>(
|
|
96
|
+
task: ITask<TInput, TOutput, TDeps>,
|
|
97
|
+
taskDependencies: DependencyValuesType<{}>
|
|
98
|
+
) {
|
|
99
|
+
// this is the final next()
|
|
100
|
+
let next = async (input) => {
|
|
101
|
+
return task.run.call(null, input, taskDependencies as any);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const existingMiddlewares = task.middleware;
|
|
105
|
+
const createdMiddlewares = [
|
|
106
|
+
...this.store.getGlobalMiddlewares(existingMiddlewares.map((x) => x.id)),
|
|
107
|
+
...existingMiddlewares,
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
if (createdMiddlewares.length > 0) {
|
|
111
|
+
// we need to run the middleware in reverse order
|
|
112
|
+
// so we can chain the next function
|
|
113
|
+
for (let i = createdMiddlewares.length - 1; i >= 0; i--) {
|
|
114
|
+
const middleware = createdMiddlewares[i];
|
|
115
|
+
const storeMiddleware = this.store.middlewares.get(
|
|
116
|
+
middleware.id
|
|
117
|
+
) as MiddlewareStoreElementType; // we know it exists because at this stage all sanity checks have been done.
|
|
118
|
+
|
|
119
|
+
const nextFunction = next;
|
|
120
|
+
next = async (input) => {
|
|
121
|
+
return storeMiddleware.middleware.run(
|
|
122
|
+
{
|
|
123
|
+
taskDefinition: task as any,
|
|
124
|
+
input,
|
|
125
|
+
next: nextFunction,
|
|
126
|
+
},
|
|
127
|
+
storeMiddleware.computedDependencies
|
|
128
|
+
);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return next;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { IEvent, IEventDefinition } from "../defs";
|
|
2
|
+
import { EventManager } from "../EventManager";
|
|
3
|
+
|
|
4
|
+
describe("EventManager", () => {
|
|
5
|
+
let eventManager: EventManager;
|
|
6
|
+
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
eventManager = new EventManager();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const testEvent: IEventDefinition<string> = {
|
|
12
|
+
id: "test-event",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const createEvent = (data: string): IEvent<string> => ({
|
|
16
|
+
id: testEvent.id,
|
|
17
|
+
data,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe("emit", () => {
|
|
21
|
+
it("should emit an event and call the appropriate listeners", async () => {
|
|
22
|
+
const listener1 = jest.fn();
|
|
23
|
+
const listener2 = jest.fn();
|
|
24
|
+
|
|
25
|
+
eventManager.addListener(testEvent, listener1);
|
|
26
|
+
eventManager.addListener(testEvent, listener2);
|
|
27
|
+
|
|
28
|
+
await eventManager.emit(testEvent, "test data");
|
|
29
|
+
|
|
30
|
+
expect(listener1).toHaveBeenCalledWith(createEvent("test data"));
|
|
31
|
+
expect(listener2).toHaveBeenCalledWith(createEvent("test data"));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("should respect the order of listeners", async () => {
|
|
35
|
+
const calls: number[] = [];
|
|
36
|
+
const listener1 = jest.fn(() => calls.push(1));
|
|
37
|
+
const listener2 = jest.fn(() => calls.push(2));
|
|
38
|
+
const listener3 = jest.fn(() => calls.push(3));
|
|
39
|
+
|
|
40
|
+
eventManager.addListener(testEvent, listener2, { order: 2 });
|
|
41
|
+
eventManager.addListener(testEvent, listener1, { order: 1 });
|
|
42
|
+
eventManager.addListener(testEvent, listener3, { order: 3 });
|
|
43
|
+
|
|
44
|
+
await eventManager.emit(testEvent, "test data");
|
|
45
|
+
|
|
46
|
+
expect(calls).toEqual([1, 2, 3]);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("should apply filters to listeners", async () => {
|
|
50
|
+
const listener = jest.fn();
|
|
51
|
+
const filter = jest.fn((event: IEvent<string>) => event.data === "pass");
|
|
52
|
+
|
|
53
|
+
eventManager.addListener(testEvent, listener, { filter });
|
|
54
|
+
|
|
55
|
+
await eventManager.emit(testEvent, "pass");
|
|
56
|
+
await eventManager.emit(testEvent, "fail");
|
|
57
|
+
|
|
58
|
+
expect(listener).toHaveBeenCalledTimes(1);
|
|
59
|
+
expect(listener).toHaveBeenCalledWith(createEvent("pass"));
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("addListener", () => {
|
|
64
|
+
it("should add a listener for a single event", () => {
|
|
65
|
+
const listener = jest.fn();
|
|
66
|
+
eventManager.addListener(testEvent, listener);
|
|
67
|
+
|
|
68
|
+
// @ts-ignore: Accessing private property for testing
|
|
69
|
+
expect(eventManager.listeners.get(testEvent.id)).toHaveLength(1);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("should add a listener for multiple events", () => {
|
|
73
|
+
const listener = jest.fn();
|
|
74
|
+
const event1: IEventDefinition = { id: "event1" };
|
|
75
|
+
const event2: IEventDefinition = { id: "event2" };
|
|
76
|
+
|
|
77
|
+
eventManager.addListener([event1, event2], listener);
|
|
78
|
+
|
|
79
|
+
// @ts-ignore: Accessing private property for testing
|
|
80
|
+
expect(eventManager.listeners.get(event1.id)).toHaveLength(1);
|
|
81
|
+
// @ts-ignore: Accessing private property for testing
|
|
82
|
+
expect(eventManager.listeners.get(event2.id)).toHaveLength(1);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
describe("addGlobalListener", () => {
|
|
87
|
+
it("should add a global listener", async () => {
|
|
88
|
+
const globalListener = jest.fn();
|
|
89
|
+
eventManager.addGlobalListener(globalListener);
|
|
90
|
+
|
|
91
|
+
await eventManager.emit(testEvent, "test data");
|
|
92
|
+
|
|
93
|
+
expect(globalListener).toHaveBeenCalledWith(createEvent("test data"));
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ResourceInitializer } from "../ResourceInitializer";
|
|
2
|
+
import { Store } from "../Store";
|
|
3
|
+
import { EventManager } from "../EventManager";
|
|
4
|
+
import { defineResource } from "../define";
|
|
5
|
+
|
|
6
|
+
describe("ResourceInitializer", () => {
|
|
7
|
+
let store: Store;
|
|
8
|
+
let eventManager: EventManager;
|
|
9
|
+
let resourceInitializer: ResourceInitializer;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
eventManager = new EventManager();
|
|
13
|
+
store = new Store(eventManager);
|
|
14
|
+
resourceInitializer = new ResourceInitializer(store, eventManager);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("should initialize a resource and emit events", async () => {
|
|
18
|
+
const mockResource = defineResource({
|
|
19
|
+
id: "testResource",
|
|
20
|
+
init: jest.fn().mockResolvedValue("initialized value"),
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const mockConfig = undefined;
|
|
24
|
+
const mockDependencies = {};
|
|
25
|
+
|
|
26
|
+
const emitSpy = jest.spyOn(eventManager, "emit");
|
|
27
|
+
|
|
28
|
+
const result = await resourceInitializer.initializeResource(
|
|
29
|
+
mockResource,
|
|
30
|
+
mockConfig,
|
|
31
|
+
mockDependencies
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(result).toBe("initialized value");
|
|
35
|
+
expect(mockResource.init).toHaveBeenCalledWith(
|
|
36
|
+
mockConfig,
|
|
37
|
+
mockDependencies
|
|
38
|
+
);
|
|
39
|
+
expect(emitSpy).toHaveBeenCalledWith(mockResource.events.beforeInit, {
|
|
40
|
+
config: mockConfig,
|
|
41
|
+
});
|
|
42
|
+
expect(emitSpy).toHaveBeenCalledWith(mockResource.events.afterInit, {
|
|
43
|
+
config: mockConfig,
|
|
44
|
+
value: "initialized value",
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("should handle errors and emit onError event", async () => {
|
|
49
|
+
const mockError = new Error("Initialization error");
|
|
50
|
+
const mockResource = defineResource({
|
|
51
|
+
id: "testResource",
|
|
52
|
+
init: jest.fn().mockRejectedValue(mockError),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const mockConfig = undefined;
|
|
56
|
+
const mockDependencies = {};
|
|
57
|
+
|
|
58
|
+
const emitSpy = jest.spyOn(eventManager, "emit");
|
|
59
|
+
|
|
60
|
+
let result;
|
|
61
|
+
try {
|
|
62
|
+
result = await resourceInitializer.initializeResource(
|
|
63
|
+
mockResource,
|
|
64
|
+
mockConfig,
|
|
65
|
+
mockDependencies
|
|
66
|
+
);
|
|
67
|
+
} catch (e) {}
|
|
68
|
+
|
|
69
|
+
expect(result).toBeUndefined();
|
|
70
|
+
expect(mockResource.init).toHaveBeenCalledWith(
|
|
71
|
+
mockConfig,
|
|
72
|
+
mockDependencies
|
|
73
|
+
);
|
|
74
|
+
expect(emitSpy).toHaveBeenCalledTimes(4);
|
|
75
|
+
expect(emitSpy).toHaveBeenCalledWith(mockResource.events.beforeInit, {
|
|
76
|
+
config: mockConfig,
|
|
77
|
+
});
|
|
78
|
+
expect(emitSpy).toHaveBeenCalledWith(mockResource.events.onError, {
|
|
79
|
+
error: mockError,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should handle resources without init function", async () => {
|
|
84
|
+
const mockResource = defineResource<number>({
|
|
85
|
+
id: "testResource",
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const mockConfig = 42;
|
|
89
|
+
const mockDependencies = {};
|
|
90
|
+
|
|
91
|
+
const emitSpy = jest.spyOn(eventManager, "emit");
|
|
92
|
+
|
|
93
|
+
const result = await resourceInitializer.initializeResource(
|
|
94
|
+
mockResource,
|
|
95
|
+
mockConfig,
|
|
96
|
+
mockDependencies
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
expect(result).toBeUndefined();
|
|
100
|
+
expect(emitSpy).toHaveBeenCalledTimes(4);
|
|
101
|
+
expect(emitSpy).toHaveBeenCalledWith(mockResource.events.beforeInit, {
|
|
102
|
+
config: mockConfig,
|
|
103
|
+
});
|
|
104
|
+
expect(emitSpy).toHaveBeenCalledWith(mockResource.events.afterInit, {
|
|
105
|
+
config: mockConfig,
|
|
106
|
+
value: undefined,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|