-
Notifications
You must be signed in to change notification settings - Fork 0
/
container.ts
125 lines (108 loc) · 5.09 KB
/
container.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// deno-lint-ignore-file no-explicit-any
import { Reflect } from "./deps.ts";
import { Decorator, Interceptor, Route, RouteHandler } from "./types.ts";
import { Server } from "./server.ts";
/**
* Dependency Injection Container
* is a singleton in the application
* @Author metadream
* @Since 2022-11-09
*/
export const container = new class Container {
errorHandler?: RouteHandler;
attributes: Record<string, any> = {};
interceptors: Interceptor[] = [];
routes: Route[] = [];
private modules: Set<any> = new Set();
private server = new Server();
// Register decorators
register(target: any, decorator: Decorator) {
// Start the application and trigger all decorators
if (decorator.name === "@Bootstrap") {
new target(this.server);
return this.server.run();
}
// If there are parameters, use them to create the instance
// Otherwise create it by the default constructor
const paramTypes = Reflect.getMetadata("design:paramtypes", target) as any[];
if (paramTypes) {
const params = paramTypes.map((v) => v.instance);
target.instance = target.instance || new target(...params);
} else {
target.instance = target.instance || new target();
}
// Define metadata to target
// and add target to modules
this.defineMetadata(target, decorator);
this.modules.add(target);
}
// Inject module
inject(target: any) {
const found = this.modules.has(target);
if (!found) throw "The module '" + target.name + "' may not be registered.";
const interceptor = this.getMetadata(target, "@Interceptor")[0];
const component = this.getMetadata(target, "@Component")[0];
const controller = this.getMetadata(target, "@Controller")[0];
const errorHandler = this.getMetadata(target, "@ErrorHandler")[0];
const requests = this.getMetadata(target, "@Request");
const views = this.getMetadata(target, "@View");
const properties = this.getMetadata(target, "@Autowired");
const attributes = this.getMetadata(target, "@Attribute");
// Extract all methods of the interceptor
if (interceptor) {
const members = Object.getOwnPropertyNames(target.prototype);
for (const member of members) {
if (member !== "constructor") {
const handler = target.instance[member].bind(target.instance);
this.interceptors.push(handler);
}
}
}
// Set error handler
if (errorHandler) {
if (!component) throw "The module of '" + target.name + "' must be decorated.";
if (!errorHandler.fn) throw "@ErrorHandler decorator must be added to a method.";
this.errorHandler = errorHandler.fn.bind(target.instance);
}
// Inject autowired properties
for (const prop of properties) {
const decorated = component || controller || interceptor;
const relname = prop.relname as string;
const reltype = prop.reltype;
if (!decorated) throw "The module of '" + target.name + "' must be decorated.";
if (!relname) throw "@Autowired decorator must be added to a property.";
if (!reltype) throw "@Autowired decorator must declare a type.";
if (!this.modules.has(reltype)) throw "Undefined module '" + relname + "' in '" + target.name + "'.";
target.instance[relname] = reltype.instance;
}
// Inject attributes for template
for (const attr of attributes) {
if (!component) throw "The module of '" + target.name + "' must be decorated.";
if (!attr.fn) throw "@Attribute decorator must be added to a method.";
const relname = attr.relname as string;
this.attributes[relname] = attr.fn.bind(target.instance);
}
// Parse request routes
for (const req of requests) {
if (!controller) throw "The module '" + target.name + "' must be decorated with @Controller.";
if (!req.fn) throw "Request decorator must be added to a method.";
if (!req.method) throw "Request decorator must have a method name.";
const handler = req.fn.bind(target.instance);
const path = ("/" + controller.param + req.param).replace(/[\/]+/g, "/");
const view = views.find((v) => v.fn === req.fn);
const template = view ? view.param : undefined;
this.routes.push({ method: req.method, path, handler, template });
}
}
// Define multiple metadata on the same target
private defineMetadata(target: any, decorator: Decorator): void {
const key = decorator.name;
const decorators: Decorator[] = Reflect.getMetadata(key, target) || [];
decorators.push(decorator);
Reflect.defineMetadata(key, decorators, target);
}
// Get metadata by key
private getMetadata(target: any, key: string): Decorator[] {
return Reflect.getMetadata(key, target) || [];
}
}();