@bool-ts/core 1.3.2 → 1.4.1

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 (68) hide show
  1. package/.prettierrc +11 -0
  2. package/LICENSE +21 -21
  3. package/__test/controller.ts +66 -79
  4. package/__test/index.ts +8 -11
  5. package/__test/interfaces.ts +7 -7
  6. package/__test/module.ts +16 -17
  7. package/__test/repository.ts +16 -16
  8. package/__test/service.ts +20 -20
  9. package/bun.lockb +0 -0
  10. package/dist/decorators/arguments.d.ts +43 -0
  11. package/dist/decorators/arguments.js +97 -0
  12. package/dist/decorators/controller.d.ts +2 -2
  13. package/dist/decorators/controller.js +6 -10
  14. package/dist/decorators/http.d.ts +1 -1
  15. package/dist/decorators/http.js +16 -103
  16. package/dist/decorators/index.d.ts +1 -0
  17. package/dist/decorators/index.js +7 -26
  18. package/dist/decorators/inject.js +5 -9
  19. package/dist/decorators/injectable.d.ts +2 -2
  20. package/dist/decorators/injectable.js +4 -8
  21. package/dist/decorators/module.d.ts +4 -2
  22. package/dist/decorators/module.js +4 -8
  23. package/dist/decorators/zodSchema.d.ts +1 -1
  24. package/dist/decorators/zodSchema.js +5 -8
  25. package/dist/entities/index.d.ts +3 -0
  26. package/dist/entities/index.js +3 -0
  27. package/dist/entities/route.d.ts +101 -0
  28. package/dist/entities/route.js +257 -0
  29. package/dist/entities/router.d.ts +10 -0
  30. package/dist/entities/router.js +25 -0
  31. package/dist/entities/routerGroup.d.ts +14 -0
  32. package/dist/entities/routerGroup.js +24 -0
  33. package/dist/hooks/factory.d.ts +12 -12
  34. package/dist/hooks/factory.js +202 -150
  35. package/dist/hooks/index.js +2 -7
  36. package/dist/hooks/injector.js +7 -10
  37. package/dist/http/clientError.d.ts +1 -1
  38. package/dist/http/clientError.js +3 -7
  39. package/dist/http/index.d.ts +12 -2
  40. package/dist/http/index.js +34 -73
  41. package/dist/http/serverError.js +3 -7
  42. package/dist/index.js +5 -21
  43. package/dist/interfaces/index.js +1 -3
  44. package/dist/ultils/asyncFunction.js +1 -4
  45. package/dist/ultils/index.js +1 -17
  46. package/package.json +30 -33
  47. package/src/decorators/arguments.ts +186 -0
  48. package/src/decorators/controller.ts +14 -18
  49. package/src/decorators/http.ts +81 -195
  50. package/src/decorators/index.ts +7 -6
  51. package/src/decorators/inject.ts +13 -19
  52. package/src/decorators/injectable.ts +11 -12
  53. package/src/decorators/module.ts +21 -22
  54. package/src/decorators/zodSchema.ts +20 -27
  55. package/src/entities/index.ts +3 -0
  56. package/src/entities/route.ts +328 -0
  57. package/src/entities/router.ts +35 -0
  58. package/src/entities/routerGroup.ts +34 -0
  59. package/src/hooks/factory.ts +319 -205
  60. package/src/hooks/index.ts +2 -2
  61. package/src/hooks/injector.ts +43 -43
  62. package/src/http/clientError.ts +45 -56
  63. package/src/http/index.ts +63 -72
  64. package/src/http/serverError.ts +38 -38
  65. package/src/index.ts +6 -6
  66. package/src/interfaces/index.ts +3 -3
  67. package/test.http +30 -28
  68. package/tsconfig.json +107 -109
@@ -1,205 +1,319 @@
1
- import "reflect-metadata";
2
- import "colors";
3
-
4
- import * as Qs from "qs";
5
- import * as ResponseTime from "response-time";
6
-
7
- import { type IControllerRoute, type TModuleOptions, controllerKey, controllerRoutesKey, moduleKey } from "../decorators";
8
- import { default as ExpressApp, Router, json, urlencoded, Request, Response, NextFunction, Errback } from "express";
9
- import { Injector } from "./injector";
10
- import { errorInfer } from "../http";
11
-
12
-
13
- export type TBoolFactoryOptions = Partial<{
14
- debug: boolean;
15
- log: Partial<{
16
- methods: Array<"GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS">;
17
- }>;
18
- queryParser: Partial<{
19
- depth: 10,
20
- arrayLimit: 50
21
- }>;
22
- prefix: string;
23
- }>;
24
-
25
-
26
- /**
27
- *
28
- * @param target
29
- * @param router
30
- */
31
- const controllerCreator = (
32
- controllerConstructor: new (...args: any[]) => unknown,
33
- parentRouter: Router = Router()
34
- ) => {
35
- if (!Reflect.getOwnMetadataKeys(controllerConstructor).includes(controllerKey)) {
36
- throw Error(`${controllerConstructor.name} is not a controller.`);
37
- }
38
-
39
- const controller = Injector.get(controllerConstructor);
40
-
41
- if (!controller) {
42
- throw Error("Can not initialize controller.");
43
- }
44
-
45
- const controllerMetadata = Reflect.getOwnMetadata(controllerKey, controllerConstructor) || "/";
46
- const routesMetadata = (Reflect.getOwnMetadata(controllerRoutesKey, controllerConstructor) || []) as Array<IControllerRoute>;
47
- const router = Router();
48
-
49
- routesMetadata.forEach(routeMetadata => {
50
- if (typeof routeMetadata.descriptor.value !== "function") {
51
- return;
52
- }
53
-
54
- const route = router.route(routeMetadata.path);
55
-
56
- switch (routeMetadata.httpMethod) {
57
- case "GET":
58
- return route.get(routeMetadata.descriptor.value.bind(controller));
59
- case "POST":
60
- return route.post(routeMetadata.descriptor.value.bind(controller));
61
- case "PUT":
62
- return route.put(routeMetadata.descriptor.value.bind(controller));
63
- case "PATCH":
64
- return route.patch(routeMetadata.descriptor.value.bind(controller));
65
- case "DELETE":
66
- return route.delete(routeMetadata.descriptor.value.bind(controller));
67
- case "OPTIONS":
68
- return route.options(routeMetadata.descriptor.value.bind(controller));
69
- }
70
- });
71
-
72
- return parentRouter.use(controllerMetadata, router);
73
- }
74
-
75
-
76
- /**
77
- *
78
- * @param target
79
- */
80
- export const BoolFactory = (
81
- target: new (...args: any[]) => unknown,
82
- options?: TBoolFactoryOptions
83
- ) => {
84
- if (!Reflect.getOwnMetadataKeys(target).includes(moduleKey)) {
85
- throw Error(`${target.name} is not a module.`);
86
- }
87
-
88
- const metadata = Reflect.getOwnMetadata(moduleKey, target) as TModuleOptions;
89
- const allowOrigins = !metadata?.allowOrigins ?
90
- ["*"] : typeof metadata.allowOrigins !== "string" ?
91
- metadata.allowOrigins : [metadata.allowOrigins];
92
- const allowMethods = !metadata?.allowMethods ?
93
- ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] : metadata.allowMethods;
94
- const app = ExpressApp();
95
- const factoryOptions = Object.freeze({
96
- allowLogsMethods: !options?.log?.methods ? ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] : options.log.methods
97
- });
98
- const routers = !metadata?.controllers ?
99
- [] : metadata.controllers.map(controllerConstructor => controllerCreator(controllerConstructor));
100
-
101
- app.set("etag", "strong");
102
- app.set("query parser", (query: string) => Qs.parse(query, {
103
- depth: !options?.queryParser?.depth || options.queryParser.depth < 0 ? 10 : options.queryParser.depth,
104
- arrayLimit: !options?.queryParser?.arrayLimit || options.queryParser.arrayLimit < 0 ? 50 : options.queryParser.arrayLimit
105
- }));
106
-
107
- app.use(
108
- urlencoded({
109
- extended: true,
110
- inflate: true,
111
- limit: "1mb",
112
- parameterLimit: 20,
113
- type: "application/x-www-form-urlencoded",
114
- verify: undefined
115
- }),
116
- json({
117
- inflate: true,
118
- limit: "5mb",
119
- reviver: undefined,
120
- strict: true,
121
- type: "application/json",
122
- verify: undefined
123
- }),
124
- // Headers parser
125
- (req: Request, res: Response, next: NextFunction) => {
126
- for (const [key, value] of Object.entries(req.headers)) {
127
- req.headers[key] = typeof value !== "string" ? value : decodeURI(value)
128
- }
129
-
130
- next();
131
- },
132
- // Body parser
133
- (req: Request, res: Response, next: NextFunction) => {
134
- if (typeof req.body !== "object" || !req.body) {
135
- req.body = Object.freeze({});
136
- }
137
-
138
- next();
139
- },
140
- // Response time log
141
- ResponseTime.default((req: Request, res: Response, time: number) => {
142
- const requestMethod = req.method.toUpperCase();
143
-
144
- if (!factoryOptions.allowLogsMethods.includes(requestMethod)) {
145
- return;
146
- }
147
-
148
- const convertedMethod = `${requestMethod.yellow}`.bgBlue;
149
- const convertedPID = `${process.pid}`.yellow;
150
- const convertedReqIp = `${req.headers["x-forwarded-for"] || req.headers["x-real-ip"] || req.ip || "<Unknown>"}`.yellow;
151
- const convertedTime = `${Math.round((time + Number.EPSILON) * 10 ** 2) / 10 ** 2}ms`.yellow;
152
-
153
- console.info(`PID: ${convertedPID} - Method: ${convertedMethod} - IP: ${convertedReqIp} - ${req.originalUrl.blue} - Time: ${convertedTime}`);
154
- })
155
- );
156
-
157
- app.use((req: Request, res: Response, next: NextFunction) => {
158
- if (!allowOrigins.includes("*")) {
159
- if (!allowOrigins.includes(req.headers.origin || "*")) {
160
- return res.status(403).json({
161
- httpCode: 403,
162
- data: {
163
- origin: {
164
- code: "origin:invalid:0x00001",
165
- message: "Invalid origin."
166
- }
167
- }
168
- });
169
- }
170
- }
171
-
172
- res.header("Access-Control-Allow-Origin", req.headers.origin || "*");
173
- res.header("Access-Control-Allow-Headers", "*");
174
- res.header("Access-Control-Allow-Credentials", "true");
175
- res.header("Access-Control-Allow-Methods", allowMethods.join(", "));
176
-
177
- next();
178
- });
179
-
180
- if (routers.length > 0) {
181
- !metadata?.prefix ?
182
- app.use(routers) : app.use(!metadata.prefix.startsWith("/") ?
183
- `/${metadata.prefix}` : metadata.prefix, routers);
184
- }
185
-
186
- // Register error catcher
187
- app.use(
188
- // Error catcher
189
- (err: Errback, req: Request, res: Response, next: NextFunction) => {
190
- errorInfer(res, err);
191
-
192
- if (!options?.debug) {
193
- return;
194
- }
195
-
196
- console.info("Headers:", JSON.stringify(req.headers, null, 4), "\nBody:", JSON.stringify(req.body, null, 4));
197
- console.error("Error:");
198
- console.error(err);
199
- }
200
- );
201
-
202
- return app;
203
- }
204
-
205
- export default BoolFactory;
1
+ import "colors";
2
+ import "reflect-metadata";
3
+
4
+ import Qs from "qs";
5
+ import * as Zod from "zod";
6
+
7
+ import { Router, RouterGroup } from "../entities";
8
+ import { type IControllerRoute, type TModuleOptions, controllerKey, controllerRoutesKey, moduleKey } from "../decorators";
9
+ import { HttpClientError, HttpServerError, jsonErrorInfer, type THttpMethods } from "../http";
10
+ import { Injector } from "./injector";
11
+ import { controllerActionArgumentsKey, EArgumentTypes, type TMetadata as TArgumentsMetadata } from "../decorators/arguments";
12
+
13
+ export type TBoolFactoryOptions = Required<{
14
+ port: number;
15
+ }> &
16
+ Partial<{
17
+ prefix: string;
18
+ debug: boolean;
19
+ log: Partial<{
20
+ methods: Array<"GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS">;
21
+ }>;
22
+ queryParser: Parameters<typeof Qs.parse>[1];
23
+ }>;
24
+
25
+ export const controllerCreator = (controllerConstructor: new (...args: any[]) => unknown, group: RouterGroup) => {
26
+ if (!Reflect.getOwnMetadataKeys(controllerConstructor).includes(controllerKey)) {
27
+ throw Error(`${controllerConstructor.name} is not a controller.`);
28
+ }
29
+
30
+ const controller = Injector.get(controllerConstructor);
31
+
32
+ if (!controller) {
33
+ throw Error("Can not initialize controller.");
34
+ }
35
+
36
+ const controllerMetadata = Reflect.getOwnMetadata(controllerKey, controllerConstructor) || "/";
37
+ const routesMetadata = (Reflect.getOwnMetadata(controllerRoutesKey, controllerConstructor) ||
38
+ []) as Array<IControllerRoute>;
39
+ const router = new Router(controllerMetadata);
40
+
41
+ routesMetadata.forEach((routeMetadata) => {
42
+ if (typeof routeMetadata.descriptor.value !== "function") {
43
+ return;
44
+ }
45
+
46
+ const route = router.route(`/${routeMetadata.path}`);
47
+ const handler = routeMetadata.descriptor.value.bind(controller);
48
+ const routeArgument = {
49
+ constructor: controllerConstructor,
50
+ funcName: routeMetadata.methodName,
51
+ func: handler
52
+ };
53
+
54
+ switch (routeMetadata.httpMethod) {
55
+ case "GET":
56
+ return route.get(routeArgument);
57
+ case "POST":
58
+ return route.post(routeArgument);
59
+ case "PUT":
60
+ return route.put(routeArgument);
61
+ case "PATCH":
62
+ return route.patch(routeArgument);
63
+ case "DELETE":
64
+ return route.delete(routeArgument);
65
+ case "OPTIONS":
66
+ return route.options(routeArgument);
67
+ }
68
+ });
69
+
70
+ return group.add(router);
71
+ };
72
+
73
+ export const controllerActionArgumentsResolution = async (
74
+ data: unknown,
75
+ zodSchema: Zod.Schema,
76
+ argumentIndex: number,
77
+ funcName: string | symbol
78
+ ) => {
79
+ try {
80
+ const validation = await zodSchema.safeParseAsync(data);
81
+
82
+ if (!validation.success) {
83
+ throw new HttpClientError({
84
+ httpCode: 400,
85
+ message: `Validation at the [${funcName.toString()}] method fails at positional argument [${argumentIndex}].`,
86
+ data: validation.error.issues
87
+ });
88
+ }
89
+
90
+ return validation.data;
91
+ } catch (error) {
92
+ if (error instanceof HttpClientError) {
93
+ throw error;
94
+ }
95
+
96
+ throw new HttpServerError({
97
+ httpCode: 500,
98
+ message: `Validation at the [${funcName.toString()}] method error at positional argument [${argumentIndex}].`,
99
+ data: !(error instanceof Error)
100
+ ? error
101
+ : [
102
+ {
103
+ message: error.message,
104
+ code: error.name,
105
+ cause: error.cause
106
+ }
107
+ ]
108
+ });
109
+ }
110
+ };
111
+
112
+ export const BoolFactory = (target: new (...args: any[]) => unknown, options: TBoolFactoryOptions) => {
113
+ if (!Reflect.getOwnMetadataKeys(target).includes(moduleKey)) {
114
+ throw Error(`${target.name} is not a module.`);
115
+ }
116
+
117
+ const moduleMetadata = Reflect.getOwnMetadata(moduleKey, target) as TModuleOptions;
118
+ const allowOrigins = !moduleMetadata?.allowOrigins
119
+ ? ["*"]
120
+ : typeof moduleMetadata.allowOrigins !== "string"
121
+ ? moduleMetadata.allowOrigins
122
+ : [moduleMetadata.allowOrigins];
123
+ const allowMethods = !moduleMetadata?.allowMethods
124
+ ? ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"]
125
+ : moduleMetadata.allowMethods;
126
+ const { allowLogsMethods } = Object.freeze({
127
+ allowLogsMethods: !options?.log?.methods ? ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"] : options.log.methods
128
+ });
129
+
130
+ const routerGroup = new RouterGroup();
131
+
132
+ moduleMetadata?.controllers &&
133
+ moduleMetadata.controllers.map((controllerConstructor) => controllerCreator(controllerConstructor, routerGroup));
134
+
135
+ Bun.serve({
136
+ port: options.port,
137
+ async fetch(request) {
138
+ const start = performance.now();
139
+ const url = new URL(request.url);
140
+
141
+ try {
142
+ const reqHeaders = request.headers;
143
+ const origin = reqHeaders.get("origin");
144
+
145
+ if (!allowOrigins.includes("*")) {
146
+ if (!origin) {
147
+ throw new HttpClientError({
148
+ httpCode: 403,
149
+ message: "Origin not found.",
150
+ data: {
151
+ origin: {
152
+ code: "origin:invalid:0x00001",
153
+ message: "Origin not found."
154
+ }
155
+ }
156
+ });
157
+ }
158
+
159
+ if (!allowOrigins.includes(origin)) {
160
+ throw new HttpClientError({
161
+ httpCode: 403,
162
+ message: "Invalid origin.",
163
+ data: {
164
+ origin: {
165
+ code: "origin:invalid:0x00002",
166
+ message: "Invalid origin."
167
+ }
168
+ }
169
+ });
170
+ }
171
+ }
172
+
173
+ if (!allowMethods.includes(request.method.toUpperCase())) {
174
+ throw new HttpClientError({
175
+ httpCode: 405,
176
+ message: "Method Not Allowed.",
177
+ data: undefined
178
+ });
179
+ }
180
+
181
+ const result = routerGroup.find(url.pathname, request.method as keyof THttpMethods);
182
+
183
+ if (!result) {
184
+ throw new HttpClientError({
185
+ httpCode: 404,
186
+ message: "Route not found.",
187
+ data: undefined
188
+ });
189
+ }
190
+
191
+ const params = result.params;
192
+ const query = Qs.parse(url.search, options.queryParser);
193
+
194
+ let responseBody = undefined;
195
+
196
+ for (let i = 0; i < result.handlers.length; i++) {
197
+ const handler = result.handlers[i];
198
+ const handlerMetadata = (Reflect.getOwnMetadata(
199
+ controllerActionArgumentsKey,
200
+ handler.constructor,
201
+ handler.funcName
202
+ ) || {}) as Record<string, TArgumentsMetadata>;
203
+
204
+ const controllerActionArguments = [];
205
+
206
+ if (handlerMetadata) {
207
+ for (const [_key, argsMetadata] of Object.entries(handlerMetadata)) {
208
+ switch (argsMetadata.type) {
209
+ case EArgumentTypes.headers:
210
+ controllerActionArguments[argsMetadata.index] = !argsMetadata.zodSchema
211
+ ? reqHeaders
212
+ : await controllerActionArgumentsResolution(
213
+ reqHeaders,
214
+ argsMetadata.zodSchema,
215
+ argsMetadata.index,
216
+ handler.funcName
217
+ );
218
+ break;
219
+ case EArgumentTypes.body:
220
+ controllerActionArguments[argsMetadata.index] = !argsMetadata.zodSchema
221
+ ? await request[argsMetadata.parser || "json"]()
222
+ : await controllerActionArgumentsResolution(
223
+ await request[argsMetadata.parser || "json"](),
224
+ argsMetadata.zodSchema,
225
+ argsMetadata.index,
226
+ handler.funcName
227
+ );
228
+ break;
229
+ case EArgumentTypes.params:
230
+ controllerActionArguments[argsMetadata.index] = !argsMetadata.zodSchema
231
+ ? params
232
+ : await controllerActionArgumentsResolution(
233
+ params,
234
+ argsMetadata.zodSchema,
235
+ argsMetadata.index,
236
+ handler.funcName
237
+ );
238
+ break;
239
+ case EArgumentTypes.query:
240
+ controllerActionArguments[argsMetadata.index] = !argsMetadata.zodSchema
241
+ ? query
242
+ : await controllerActionArgumentsResolution(
243
+ query,
244
+ argsMetadata.zodSchema,
245
+ argsMetadata.index,
246
+ handler.funcName
247
+ );
248
+ break;
249
+ case EArgumentTypes.param:
250
+ controllerActionArguments[argsMetadata.index] = !argsMetadata.zodSchema
251
+ ? !(argsMetadata.key in params)
252
+ ? undefined
253
+ : params[argsMetadata.key]
254
+ : await controllerActionArgumentsResolution(
255
+ query,
256
+ argsMetadata.zodSchema,
257
+ argsMetadata.index,
258
+ handler.funcName
259
+ );
260
+ break;
261
+ case EArgumentTypes.request:
262
+ controllerActionArguments[argsMetadata.index] = request;
263
+ break;
264
+ }
265
+ }
266
+ }
267
+
268
+ const responseData = await handler.func(...controllerActionArguments);
269
+
270
+ if (responseData instanceof Response) {
271
+ return responseData;
272
+ }
273
+
274
+ responseBody = responseData;
275
+ }
276
+
277
+ const resHeaders = new Headers({
278
+ "Access-Control-Allow-Origin": origin || "*",
279
+ "Access-Control-Allow-Headers": "*",
280
+ "Access-Control-Allow-Credentials": "true",
281
+ "Access-Control-Allow-Methods": allowMethods.join(", "),
282
+ "Content-Type": "application/json"
283
+ });
284
+
285
+ const response = new Response(
286
+ JSON.stringify({
287
+ httpCode: 200,
288
+ message: "Success",
289
+ data: responseBody
290
+ }),
291
+ {
292
+ status: 200,
293
+ statusText: "Success",
294
+ headers: resHeaders
295
+ }
296
+ );
297
+
298
+ return response;
299
+ } catch (error) {
300
+ return jsonErrorInfer(error);
301
+ } finally {
302
+ const end = performance.now();
303
+ const convertedPID = `${process.pid}`.yellow;
304
+ const convertedMethod = `${request.method.yellow}`.bgBlue;
305
+ const convertedReqIp = `${
306
+ request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || "<Unknown>"
307
+ }`.yellow;
308
+ const convertedTime = `${Math.round((end - start + Number.EPSILON) * 10 ** 2) / 10 ** 2}ms`.yellow;
309
+
310
+ allowLogsMethods.includes(request.method.toUpperCase()) &&
311
+ console.info(
312
+ `PID: ${convertedPID} - Method: ${convertedMethod} - IP: ${convertedReqIp} - ${url.pathname.blue} - Time: ${convertedTime}`
313
+ );
314
+ }
315
+ }
316
+ });
317
+ };
318
+
319
+ export default BoolFactory;
@@ -1,2 +1,2 @@
1
- export { BoolFactory } from "./factory";
2
- export { Injector } from "./injector";
1
+ export { BoolFactory } from "./factory";
2
+ export { Injector } from "./injector";
@@ -1,43 +1,43 @@
1
- import "reflect-metadata";
2
-
3
- import { injectableKey, injectKey } from "../decorators";
4
-
5
-
6
- interface IInjector {
7
- get<T>(classDefinition: { new(...args: any[]): T }): T
8
- }
9
-
10
- export const Injector: IInjector = new class {
11
-
12
- private readonly _mapper: Map<Function, any> = new Map();
13
-
14
- /**
15
- *
16
- * @param constructor
17
- */
18
- get<T>(
19
- classDefinition: { new(...args: any[]): T }
20
- ) {
21
- if (this._mapper.has(classDefinition)) {
22
- return this._mapper.get(classDefinition) as T;
23
- }
24
-
25
- const ownMetadataKeys = Reflect.getMetadataKeys(classDefinition);
26
-
27
- if (!ownMetadataKeys.includes(injectableKey)) {
28
- throw Error("Missing dependency declaration, please check @Injectable() used on dependency(ies).");
29
- }
30
-
31
- // Initialize dependencies injection
32
- const dependencies: any[] = Reflect.getOwnMetadata(injectKey, classDefinition) || [];
33
- const injections: any[] = dependencies.map(dependency => Injector.get(dependency));
34
- const instance = new classDefinition(...injections);
35
-
36
- this._mapper.set(classDefinition, instance);
37
-
38
- return instance;
39
- }
40
-
41
- }
42
-
43
- export default Injector;
1
+ import "reflect-metadata";
2
+
3
+ import { injectableKey, injectKey } from "../decorators";
4
+
5
+
6
+ interface IInjector {
7
+ get<T>(classDefinition: { new(...args: any[]): T }): T
8
+ }
9
+
10
+ export const Injector: IInjector = new class {
11
+
12
+ private readonly _mapper: Map<Function, any> = new Map();
13
+
14
+ /**
15
+ *
16
+ * @param constructor
17
+ */
18
+ get<T>(
19
+ classDefinition: { new(...args: any[]): T }
20
+ ) {
21
+ if (this._mapper.has(classDefinition)) {
22
+ return this._mapper.get(classDefinition) as T;
23
+ }
24
+
25
+ const ownMetadataKeys = Reflect.getMetadataKeys(classDefinition);
26
+
27
+ if (!ownMetadataKeys.includes(injectableKey)) {
28
+ throw Error("Missing dependency declaration, please check @Injectable() used on dependency(ies).");
29
+ }
30
+
31
+ // Initialize dependencies injection
32
+ const dependencies: any[] = Reflect.getOwnMetadata(injectKey, classDefinition) || [];
33
+ const injections: any[] = dependencies.map(dependency => Injector.get(dependency));
34
+ const instance = new classDefinition(...injections);
35
+
36
+ this._mapper.set(classDefinition, instance);
37
+
38
+ return instance;
39
+ }
40
+
41
+ }
42
+
43
+ export default Injector;