@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/dist/run.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { TaskRunner } from "./TaskRunner";
|
|
2
|
+
import { DependencyMapType, ITaskDefinition, IResourceDefinintion, IEventDefinition, IMiddlewareDefinition, DependencyValuesType, IResource } from "./defs";
|
|
3
|
+
import { EventManager } from "./EventManager";
|
|
4
|
+
import { Store } from "./Store";
|
|
5
|
+
export type ResourcesStoreElementType<C = any, V = any, D extends DependencyMapType = {}> = {
|
|
6
|
+
resource: IResourceDefinintion<C, V, D>;
|
|
7
|
+
computedDependencies?: DependencyValuesType<D>;
|
|
8
|
+
config: C;
|
|
9
|
+
value: V;
|
|
10
|
+
};
|
|
11
|
+
export type TasksStoreElementType<Input = any, Output extends Promise<any> = any, D extends DependencyMapType = {}> = {
|
|
12
|
+
task: ITaskDefinition<Input, Output, D>;
|
|
13
|
+
computedDependencies?: DependencyValuesType<D>;
|
|
14
|
+
};
|
|
15
|
+
export type MiddlewareStoreElementType = {
|
|
16
|
+
middleware: IMiddlewareDefinition;
|
|
17
|
+
};
|
|
18
|
+
export type EventStoreElementType = {
|
|
19
|
+
event: IEventDefinition;
|
|
20
|
+
};
|
|
21
|
+
export type RunnerState = {
|
|
22
|
+
tasks: Record<string, TasksStoreElementType>;
|
|
23
|
+
resources: Record<string, ResourcesStoreElementType>;
|
|
24
|
+
events: Record<string, EventStoreElementType>;
|
|
25
|
+
middleware: Record<string, MiddlewareStoreElementType>;
|
|
26
|
+
};
|
|
27
|
+
export type RunnerType = {
|
|
28
|
+
store: Store;
|
|
29
|
+
eventManager: EventManager;
|
|
30
|
+
taskRunner: TaskRunner;
|
|
31
|
+
};
|
|
32
|
+
export declare function run<C, V>(resource: IResource<C>, config?: C): Promise<V>;
|
package/dist/run.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.run = run;
|
|
4
|
+
const TaskRunner_1 = require("./TaskRunner");
|
|
5
|
+
const DependencyProcessor_1 = require("./DependencyProcessor");
|
|
6
|
+
const EventManager_1 = require("./EventManager");
|
|
7
|
+
const globalEvents_1 = require("./globalEvents");
|
|
8
|
+
const Store_1 = require("./Store");
|
|
9
|
+
const findCircularDependencies_1 = require("./tools/findCircularDependencies");
|
|
10
|
+
const errors_1 = require("./errors");
|
|
11
|
+
const globalResources_1 = require("./globalResources");
|
|
12
|
+
async function run(resource, config) {
|
|
13
|
+
const eventManager = new EventManager_1.EventManager();
|
|
14
|
+
const store = new Store_1.Store(eventManager);
|
|
15
|
+
const taskRunner = new TaskRunner_1.TaskRunner(store, eventManager);
|
|
16
|
+
const processor = new DependencyProcessor_1.DependencyProcessor(store, eventManager, taskRunner);
|
|
17
|
+
// In the registration phase we register deeply all the resources, tasks, middleware and events
|
|
18
|
+
store.initializeStore(resource, config);
|
|
19
|
+
store.storeGenericItem(globalResources_1.globalResources.taskRunner.with(taskRunner));
|
|
20
|
+
store.computeRegisterOfResource(resource, config);
|
|
21
|
+
// We verify that there isn't any circular dependencies before we begin computing the dependencies
|
|
22
|
+
const dependentNodes = store.getDependentNodes();
|
|
23
|
+
const circularDependencies = (0, findCircularDependencies_1.findCircularDependencies)(dependentNodes);
|
|
24
|
+
if (circularDependencies.cycles.length > 0) {
|
|
25
|
+
throw errors_1.Errors.circularDependencies(circularDependencies.cycles);
|
|
26
|
+
}
|
|
27
|
+
await processor.processHooks();
|
|
28
|
+
// Now we can safely compute dependencies without being afraid of an infinite loop.
|
|
29
|
+
// The hooking part is done here.
|
|
30
|
+
await eventManager.emit(globalEvents_1.globalEvents.beforeInit);
|
|
31
|
+
await processor.computeAllDependencies();
|
|
32
|
+
// leftovers that were registered but not depended upon, except root
|
|
33
|
+
await processor.initializeUninitializedResources();
|
|
34
|
+
// Now we can initialise the root resource
|
|
35
|
+
await processor.initializeRoot();
|
|
36
|
+
await eventManager.emit(globalEvents_1.globalEvents.afterInit);
|
|
37
|
+
return store.root.value;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=run.js.map
|
package/dist/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":";;AA2DA,kBAsCC;AAjGD,6CAA0C;AAU1C,+DAA4D;AAC5D,iDAA8C;AAC9C,iDAA8C;AAC9C,mCAAgC;AAChC,+EAA4E;AAC5E,qCAAkC;AAClC,uDAAoD;AA2C7C,KAAK,UAAU,GAAG,CACvB,QAAsB,EACtB,MAAU;IAEV,MAAM,YAAY,GAAG,IAAI,2BAAY,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,aAAK,CAAC,YAAY,CAAC,CAAC;IACtC,MAAM,UAAU,GAAG,IAAI,uBAAU,CAAC,KAAK,EAAE,YAAY,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,yCAAmB,CAAC,KAAK,EAAE,YAAY,EAAE,UAAU,CAAC,CAAC;IAE3E,+FAA+F;IAC/F,KAAK,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACxC,KAAK,CAAC,gBAAgB,CAAC,iCAAe,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACpE,KAAK,CAAC,yBAAyB,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAElD,kGAAkG;IAClG,MAAM,cAAc,GAAG,KAAK,CAAC,iBAAiB,EAAE,CAAC;IACjD,MAAM,oBAAoB,GAAG,IAAA,mDAAwB,EAAC,cAAc,CAAC,CAAC;IACtE,IAAI,oBAAoB,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3C,MAAM,eAAM,CAAC,oBAAoB,CAAC,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,SAAS,CAAC,YAAY,EAAE,CAAC;IAE/B,mFAAmF;IACnF,iCAAiC;IACjC,MAAM,YAAY,CAAC,IAAI,CAAC,2BAAY,CAAC,UAAU,CAAC,CAAC;IAEjD,MAAM,SAAS,CAAC,sBAAsB,EAAE,CAAC;IAEzC,oEAAoE;IACpE,MAAM,SAAS,CAAC,gCAAgC,EAAE,CAAC;IAEnD,0CAA0C;IAC1C,MAAM,SAAS,CAAC,cAAc,EAAE,CAAC;IAEjC,MAAM,YAAY,CAAC,IAAI,CAAC,2BAAY,CAAC,SAAS,CAAC,CAAC;IAEhD,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC;AAC1B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A node that has dependencies.
|
|
3
|
+
*/
|
|
4
|
+
export interface IDependentNode {
|
|
5
|
+
id: string;
|
|
6
|
+
dependencies: Record<string, IDependentNode>;
|
|
7
|
+
}
|
|
8
|
+
interface FindCircularDependenciesResult {
|
|
9
|
+
cycles: string[];
|
|
10
|
+
missingDependencies: Array<{
|
|
11
|
+
nodeId: string;
|
|
12
|
+
dependencyId: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
export declare function findCircularDependencies(nodes: IDependentNode[]): FindCircularDependenciesResult;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findCircularDependencies = findCircularDependencies;
|
|
4
|
+
function findCircularDependencies(nodes) {
|
|
5
|
+
const result = {
|
|
6
|
+
cycles: [],
|
|
7
|
+
missingDependencies: [],
|
|
8
|
+
};
|
|
9
|
+
const visited = new Set();
|
|
10
|
+
const stack = new Set();
|
|
11
|
+
const path = [];
|
|
12
|
+
function dfs(node) {
|
|
13
|
+
if (stack.has(node.id)) {
|
|
14
|
+
const cycleStartIndex = path.indexOf(node.id);
|
|
15
|
+
const cycle = path.slice(cycleStartIndex).concat(node.id).join(" -> ");
|
|
16
|
+
result.cycles.push(cycle);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
if (visited.has(node.id))
|
|
20
|
+
return;
|
|
21
|
+
visited.add(node.id);
|
|
22
|
+
stack.add(node.id);
|
|
23
|
+
path.push(node.id);
|
|
24
|
+
if (node.dependencies && typeof node.dependencies === "object") {
|
|
25
|
+
for (const [depKey, dependentNode] of Object.entries(node.dependencies)) {
|
|
26
|
+
if (!dependentNode) {
|
|
27
|
+
result.missingDependencies.push({
|
|
28
|
+
nodeId: node.id,
|
|
29
|
+
dependencyId: depKey,
|
|
30
|
+
});
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
dfs(dependentNode);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
result.missingDependencies.push({
|
|
38
|
+
nodeId: node.id,
|
|
39
|
+
dependencyId: "unknown",
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
stack.delete(node.id);
|
|
43
|
+
path.pop();
|
|
44
|
+
}
|
|
45
|
+
for (const node of nodes) {
|
|
46
|
+
if (!visited.has(node.id)) {
|
|
47
|
+
dfs(node);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
result.cycles = Array.from(new Set(result.cycles)); // Remove duplicate cycles
|
|
51
|
+
return result;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=findCircularDependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"findCircularDependencies.js","sourceRoot":"","sources":["../../src/tools/findCircularDependencies.ts"],"names":[],"mappings":";;AAaA,4DAuDC;AAvDD,SAAgB,wBAAwB,CACtC,KAAuB;IAEvB,MAAM,MAAM,GAAmC;QAC7C,MAAM,EAAE,EAAE;QACV,mBAAmB,EAAE,EAAE;KACxB,CAAC;IACF,MAAM,OAAO,GAAgB,IAAI,GAAG,EAAE,CAAC;IACvC,MAAM,KAAK,GAAgB,IAAI,GAAG,EAAE,CAAC;IACrC,MAAM,IAAI,GAAa,EAAE,CAAC;IAE1B,SAAS,GAAG,CAAC,IAAoB;QAC/B,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YACvB,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9C,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACvE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO;QACT,CAAC;QAED,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO;QAEjC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnB,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAEnB,IAAI,IAAI,CAAC,YAAY,IAAI,OAAO,IAAI,CAAC,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC/D,KAAK,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBACxE,IAAI,CAAC,aAAa,EAAE,CAAC;oBACnB,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC;wBAC9B,MAAM,EAAE,IAAI,CAAC,EAAE;wBACf,YAAY,EAAE,MAAM;qBACrB,CAAC,CAAC;oBACH,SAAS;gBACX,CAAC;gBACD,GAAG,CAAC,aAAa,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,mBAAmB,CAAC,IAAI,CAAC;gBAC9B,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,YAAY,EAAE,SAAS;aACxB,CAAC,CAAC;QACL,CAAC;QAED,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtB,IAAI,CAAC,GAAG,EAAE,CAAC;IACb,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;YAC1B,GAAG,CAAC,IAAI,CAAC,CAAC;QACZ,CAAC;IACH,CAAC;IAED,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,0BAA0B;IAC9E,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bluelibs/runner",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "BlueLibs Runner",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/bluelibs/bluelibs"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"watch": "tsc -w",
|
|
13
|
+
"pretest": "npm run build",
|
|
14
|
+
"test": "jest --verbose dist/__tests__/index.js",
|
|
15
|
+
"test:dev": "jest --verbose src/__tests__/index.ts --watch",
|
|
16
|
+
"coverage": "jest --verbose src/__tests__/index.ts --coverage",
|
|
17
|
+
"test:clean": "jest --clearCache",
|
|
18
|
+
"testonly": "npm test",
|
|
19
|
+
"test:ci": "npm run coverage -- --ci --maxWorkers=2 --reporters=default --reporters=jest-junit",
|
|
20
|
+
"coverage:upload": "codecov",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"typedoc": "typedoc",
|
|
23
|
+
"benchmark": "jest --testMatch=\"**/__tests__/benchmark/benchmark.test.ts\" --testTimeout 10000"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/benchmark": "^2.1.5",
|
|
27
|
+
"@types/graphql": "^0.11.3",
|
|
28
|
+
"@types/jest": "^27.0.0",
|
|
29
|
+
"@types/node": "^20.0.0",
|
|
30
|
+
"@typescript-eslint/eslint-plugin": "2.3.0",
|
|
31
|
+
"@typescript-eslint/parser": "2.3.0",
|
|
32
|
+
"benchmark": "^2.1.4",
|
|
33
|
+
"eslint": "^6.6.0",
|
|
34
|
+
"eslint-config-prettier": "6.3.0",
|
|
35
|
+
"eslint-plugin-prettier": "3.1.1",
|
|
36
|
+
"jest": "^29.0.0",
|
|
37
|
+
"jest-junit": "^10.0.0",
|
|
38
|
+
"prettier": "^2.0.5",
|
|
39
|
+
"reflect-metadata": "^0.2.2",
|
|
40
|
+
"source-map-support": "^0.5.13",
|
|
41
|
+
"ts-jest": "^29.0.0",
|
|
42
|
+
"typedoc": "^0.26.7",
|
|
43
|
+
"typescript": "^5.6.2"
|
|
44
|
+
},
|
|
45
|
+
"typings": "dist/index.d.ts",
|
|
46
|
+
"typescript": {
|
|
47
|
+
"definition": "dist/index.d.ts"
|
|
48
|
+
},
|
|
49
|
+
"license": "MIT"
|
|
50
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DependencyMapType,
|
|
3
|
+
DependencyValuesType,
|
|
4
|
+
ITask,
|
|
5
|
+
IResource,
|
|
6
|
+
IHookDefinition,
|
|
7
|
+
IEventDefinition,
|
|
8
|
+
} from "./defs";
|
|
9
|
+
import { ResourceStoreElementType, Store } from "./Store";
|
|
10
|
+
import * as utils from "./define";
|
|
11
|
+
import { EventManager } from "./EventManager";
|
|
12
|
+
import { ResourceInitializer } from "./ResourceInitializer";
|
|
13
|
+
import { TaskRunner } from "./TaskRunner";
|
|
14
|
+
import { Errors } from "./errors";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* This class is responsible of setting up dependencies with their respective computedValues.
|
|
18
|
+
* Note that all elements must have been previously registered otherwise errors will be thrown
|
|
19
|
+
* when trying to depend on something not in the store.
|
|
20
|
+
*/
|
|
21
|
+
export class DependencyProcessor {
|
|
22
|
+
protected readonly resourceInitializer: ResourceInitializer;
|
|
23
|
+
|
|
24
|
+
constructor(
|
|
25
|
+
protected readonly store: Store,
|
|
26
|
+
protected readonly eventManager: EventManager,
|
|
27
|
+
protected readonly taskRunner: TaskRunner
|
|
28
|
+
) {
|
|
29
|
+
this.resourceInitializer = new ResourceInitializer(store, eventManager);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* This function is going to go through all the resources, tasks and middleware to compute their required dependencies.
|
|
34
|
+
*/
|
|
35
|
+
async computeAllDependencies() {
|
|
36
|
+
for (const middleware of this.store.middlewares.values()) {
|
|
37
|
+
const deps = middleware.middleware.dependencies as DependencyMapType;
|
|
38
|
+
middleware.computedDependencies = await this.extractDependencies(deps);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
for (const task of this.store.tasks.values()) {
|
|
42
|
+
const deps = task.task.dependencies as DependencyMapType;
|
|
43
|
+
task.computedDependencies = await this.extractDependencies(deps);
|
|
44
|
+
|
|
45
|
+
let eventDefinition = task.task.on;
|
|
46
|
+
if (eventDefinition) {
|
|
47
|
+
if (this.store.events.get(eventDefinition.id) === undefined) {
|
|
48
|
+
throw Errors.eventNotFound(eventDefinition.id);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.eventManager.addListener(
|
|
52
|
+
eventDefinition,
|
|
53
|
+
async (receivedEvent) => {
|
|
54
|
+
return this.taskRunner.run(
|
|
55
|
+
task.task,
|
|
56
|
+
receivedEvent,
|
|
57
|
+
task.computedDependencies
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const resource of this.store.resources.values()) {
|
|
65
|
+
await this.processResourceDependencies(resource);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Most likely these are resources that no-one has dependencies towards
|
|
70
|
+
// We need to ensure they work too!
|
|
71
|
+
public async initializeUninitializedResources() {
|
|
72
|
+
for (const resource of this.store.resources.values()) {
|
|
73
|
+
if (
|
|
74
|
+
resource.isInitialized === false &&
|
|
75
|
+
// The root is the last one to be initialized and is done in a separate process.
|
|
76
|
+
resource.resource.id !== this.store.root.resource.id
|
|
77
|
+
) {
|
|
78
|
+
await this.processResourceDependencies(resource);
|
|
79
|
+
resource.value = await this.resourceInitializer.initializeResource(
|
|
80
|
+
resource.resource,
|
|
81
|
+
resource.config,
|
|
82
|
+
resource.computedDependencies as DependencyValuesType<{}>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Processes dependencies and hooks
|
|
90
|
+
* @param resource
|
|
91
|
+
*/
|
|
92
|
+
protected async processResourceDependencies(
|
|
93
|
+
resource: ResourceStoreElementType<any, any, {}>
|
|
94
|
+
) {
|
|
95
|
+
const deps = resource.resource.dependencies as DependencyMapType;
|
|
96
|
+
resource.computedDependencies = await this.extractDependencies(deps);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public async initializeRoot() {
|
|
100
|
+
const storeResource = this.store.root;
|
|
101
|
+
|
|
102
|
+
storeResource.value = await this.resourceInitializer.initializeResource(
|
|
103
|
+
storeResource.resource,
|
|
104
|
+
storeResource.config,
|
|
105
|
+
// They are already computed
|
|
106
|
+
storeResource.computedDependencies as DependencyValuesType<{}>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
storeResource.isInitialized = true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Processes all hooks, should run before emission of any event.
|
|
114
|
+
* @returns
|
|
115
|
+
*/
|
|
116
|
+
public processHooks() {
|
|
117
|
+
// iterate through resources and send them to processHooks
|
|
118
|
+
for (const resource of this.store.resources.values()) {
|
|
119
|
+
if (resource.resource.hooks) {
|
|
120
|
+
this.processHooksForResource(resource);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Processes the hooks for resources
|
|
127
|
+
* @param hooks
|
|
128
|
+
* @param deps
|
|
129
|
+
*/
|
|
130
|
+
public processHooksForResource(
|
|
131
|
+
resourceStoreElement: ResourceStoreElementType<any, any, {}>
|
|
132
|
+
) {
|
|
133
|
+
let hooks = resourceStoreElement.resource.hooks;
|
|
134
|
+
if (typeof hooks === "function") {
|
|
135
|
+
hooks = hooks(resourceStoreElement.config);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (hooks.length === 0) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
for (const hook of hooks) {
|
|
143
|
+
const event = hook.event;
|
|
144
|
+
if (this.store.events.has(event.id) === false) {
|
|
145
|
+
throw Errors.eventNotFound(event.id);
|
|
146
|
+
}
|
|
147
|
+
this.eventManager.addListener(event, async (receivedEvent) => {
|
|
148
|
+
return hook.run(
|
|
149
|
+
receivedEvent,
|
|
150
|
+
resourceStoreElement.computedDependencies as DependencyValuesType<{}>
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async extractDependencies<T extends DependencyMapType>(
|
|
157
|
+
map: T
|
|
158
|
+
): Promise<DependencyValuesType<T>> {
|
|
159
|
+
const object = {} as DependencyValuesType<T>;
|
|
160
|
+
|
|
161
|
+
for (const key in map) {
|
|
162
|
+
object[key] = await this.extractDependency(map[key]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return object;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async extractDependency(object) {
|
|
169
|
+
if (utils.isResource(object)) {
|
|
170
|
+
return this.extractResourceDependency(object);
|
|
171
|
+
} else if (utils.isTask(object)) {
|
|
172
|
+
return this.extractTaskDependency(object);
|
|
173
|
+
} else if (utils.isEvent(object)) {
|
|
174
|
+
return this.extractEventDependency(object);
|
|
175
|
+
} else {
|
|
176
|
+
throw Errors.unknownItemType(object);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Converts the event into a running functions with real inputs
|
|
182
|
+
* @param object
|
|
183
|
+
* @returns
|
|
184
|
+
*/
|
|
185
|
+
extractEventDependency(object: IEventDefinition<Record<string, any>>) {
|
|
186
|
+
return async (input) => {
|
|
187
|
+
return this.eventManager.emit(object, input);
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async extractTaskDependency(object: ITask<any, any, {}>) {
|
|
192
|
+
const storeTask = this.store.tasks.get(object.id);
|
|
193
|
+
if (storeTask === undefined) {
|
|
194
|
+
throw Errors.dependencyNotFound(`Task ${object.id}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (!storeTask.isInitialized) {
|
|
198
|
+
storeTask.isInitialized = true;
|
|
199
|
+
|
|
200
|
+
// it's sanitised
|
|
201
|
+
const dependencies = object.dependencies as DependencyMapType;
|
|
202
|
+
|
|
203
|
+
storeTask.computedDependencies = await this.extractDependencies(
|
|
204
|
+
dependencies
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return (input) => {
|
|
209
|
+
return this.taskRunner.run(
|
|
210
|
+
storeTask.task,
|
|
211
|
+
input,
|
|
212
|
+
storeTask.computedDependencies
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async extractResourceDependency(object: IResource<any, any, any>) {
|
|
218
|
+
// check if it exists in the store with the value
|
|
219
|
+
const storeResource = this.store.resources.get(object.id);
|
|
220
|
+
if (storeResource === undefined) {
|
|
221
|
+
throw Errors.dependencyNotFound(`Resource ${object.id}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const { resource, config } = storeResource;
|
|
225
|
+
if (storeResource.isInitialized) {
|
|
226
|
+
return storeResource.value;
|
|
227
|
+
} else {
|
|
228
|
+
// we need to initialize the resource
|
|
229
|
+
storeResource.isInitialized = true;
|
|
230
|
+
|
|
231
|
+
// check if it has an initialisation function that provides the value
|
|
232
|
+
if (resource.init) {
|
|
233
|
+
storeResource.value = await this.resourceInitializer.initializeResource(
|
|
234
|
+
resource,
|
|
235
|
+
config,
|
|
236
|
+
await this.extractDependencies(resource.dependencies || {})
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return storeResource.value;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { EventHandlerType, IEvent, IEventDefinition } from "./defs";
|
|
2
|
+
|
|
3
|
+
const HandlerOptionsDefaults = { order: 0 };
|
|
4
|
+
|
|
5
|
+
interface IListenerStorage {
|
|
6
|
+
order: number;
|
|
7
|
+
filter?: (event: IEvent<any>) => boolean;
|
|
8
|
+
handler: EventHandlerType;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface IEventHandlerOptions<T = any> {
|
|
12
|
+
order?: number;
|
|
13
|
+
filter?: (event: IEvent<T>) => boolean;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class EventManager {
|
|
17
|
+
private listeners: Map<string, IListenerStorage[]> = new Map();
|
|
18
|
+
private globalListeners: IListenerStorage[] = [];
|
|
19
|
+
|
|
20
|
+
async emit<TInput>(
|
|
21
|
+
eventDefinition: IEventDefinition<TInput>,
|
|
22
|
+
...args: TInput extends void ? [] : [TInput]
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const data = args[0];
|
|
25
|
+
const eventListeners = this.listeners.get(eventDefinition.id) || [];
|
|
26
|
+
const allListeners = this.sortListeners([
|
|
27
|
+
...eventListeners,
|
|
28
|
+
...this.globalListeners,
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const event: IEvent = {
|
|
32
|
+
id: eventDefinition.id,
|
|
33
|
+
data,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (const listener of allListeners) {
|
|
37
|
+
const ok = !listener.filter || listener.filter(event);
|
|
38
|
+
if (ok) {
|
|
39
|
+
await listener.handler(event);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
addListener<T>(
|
|
45
|
+
event: IEventDefinition | Array<IEventDefinition>,
|
|
46
|
+
handler: EventHandlerType<T>,
|
|
47
|
+
options: IEventHandlerOptions<T> = HandlerOptionsDefaults
|
|
48
|
+
): void {
|
|
49
|
+
if (Array.isArray(event)) {
|
|
50
|
+
event.forEach((id) => this.addListener(id, handler, options));
|
|
51
|
+
} else {
|
|
52
|
+
const eventId = event.id;
|
|
53
|
+
const listeners = this.listeners.get(eventId) || [];
|
|
54
|
+
const newListener: IListenerStorage = {
|
|
55
|
+
handler,
|
|
56
|
+
order: options.order || 0,
|
|
57
|
+
filter: options.filter,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const newListeners = this.sortListeners([...listeners, newListener]);
|
|
61
|
+
this.listeners.set(eventId, newListeners);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
addGlobalListener(
|
|
66
|
+
handler: EventHandlerType,
|
|
67
|
+
options: IEventHandlerOptions = HandlerOptionsDefaults
|
|
68
|
+
): void {
|
|
69
|
+
const newListener: IListenerStorage = {
|
|
70
|
+
handler,
|
|
71
|
+
order: options.order || 0,
|
|
72
|
+
filter: options.filter,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
this.globalListeners = this.sortListeners([
|
|
76
|
+
...this.globalListeners,
|
|
77
|
+
newListener,
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private sortListeners(listeners: IListenerStorage[]): IListenerStorage[] {
|
|
82
|
+
return [...listeners].sort((a, b) => a.order - b.order);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DependencyMapType,
|
|
3
|
+
DependencyValuesType,
|
|
4
|
+
ITask,
|
|
5
|
+
IResource,
|
|
6
|
+
} from "./defs";
|
|
7
|
+
import { EventManager } from "./EventManager";
|
|
8
|
+
import { globalEvents } from "./globalEvents";
|
|
9
|
+
import { Store } from "./Store";
|
|
10
|
+
|
|
11
|
+
export class ResourceInitializer {
|
|
12
|
+
constructor(
|
|
13
|
+
protected readonly store: Store,
|
|
14
|
+
protected readonly eventManager: EventManager
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 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.
|
|
19
|
+
* This function can throw only if any of the event listeners or run function throws
|
|
20
|
+
*/
|
|
21
|
+
public async initializeResource<
|
|
22
|
+
TConfig = null,
|
|
23
|
+
TValue = any,
|
|
24
|
+
TDeps extends DependencyMapType = {}
|
|
25
|
+
>(
|
|
26
|
+
resource: IResource<TConfig, TValue, TDeps>,
|
|
27
|
+
config: TConfig,
|
|
28
|
+
dependencies: DependencyValuesType<TDeps>
|
|
29
|
+
): Promise<TValue | undefined> {
|
|
30
|
+
// begin by dispatching the event of creating it.
|
|
31
|
+
// then ensure the hooks are called
|
|
32
|
+
// then ensure the middleware are called
|
|
33
|
+
await this.eventManager.emit(globalEvents.resources.beforeInit, {
|
|
34
|
+
config,
|
|
35
|
+
resource,
|
|
36
|
+
});
|
|
37
|
+
await this.eventManager.emit(resource.events.beforeInit, { config });
|
|
38
|
+
|
|
39
|
+
let error, value;
|
|
40
|
+
try {
|
|
41
|
+
if (resource.init) {
|
|
42
|
+
value = await resource.init(config, dependencies);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await this.eventManager.emit(resource.events.afterInit, {
|
|
46
|
+
config,
|
|
47
|
+
value,
|
|
48
|
+
});
|
|
49
|
+
await this.eventManager.emit(globalEvents.resources.afterInit, {
|
|
50
|
+
config,
|
|
51
|
+
resource,
|
|
52
|
+
value,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
return value;
|
|
56
|
+
} catch (e) {
|
|
57
|
+
error = e;
|
|
58
|
+
|
|
59
|
+
// If you want to rewthrow the error, this should be done inside the onError event.
|
|
60
|
+
await this.eventManager.emit(resource.events.onError, { error });
|
|
61
|
+
await this.eventManager.emit(globalEvents.resources.onError, {
|
|
62
|
+
error,
|
|
63
|
+
resource,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|