@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
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
@@ -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
+ }