1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const common_1 = require("@nestjs/common");
|
4 | const constants_1 = require("@nestjs/common/constants");
|
5 | const interfaces_1 = require("@nestjs/common/interfaces");
|
6 | const random_string_generator_util_1 = require("@nestjs/common/utils/random-string-generator.util");
|
7 | const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
8 | const application_config_1 = require("./application-config");
|
9 | const constants_2 = require("./constants");
|
10 | const circular_dependency_exception_1 = require("./errors/exceptions/circular-dependency.exception");
|
11 | const get_class_scope_1 = require("./helpers/get-class-scope");
|
12 | class DependenciesScanner {
|
13 | constructor(container, metadataScanner, applicationConfig = new application_config_1.ApplicationConfig()) {
|
14 | this.container = container;
|
15 | this.metadataScanner = metadataScanner;
|
16 | this.applicationConfig = applicationConfig;
|
17 | this.applicationProvidersApplyMap = [];
|
18 | }
|
19 | async scan(module) {
|
20 | await this.registerCoreModule();
|
21 | await this.scanForModules(module);
|
22 | await this.scanModulesForDependencies();
|
23 | this.addScopedEnhancersMetadata();
|
24 | this.container.bindGlobalScope();
|
25 | }
|
26 | async scanForModules(module, scope = [], ctxRegistry = []) {
|
27 | const moduleInstance = await this.insertModule(module, scope);
|
28 | ctxRegistry.push(module);
|
29 | if (this.isForwardReference(module)) {
|
30 | module = module.forwardRef();
|
31 | }
|
32 | const modules = !this.isDynamicModule(module)
|
33 | ? this.reflectMetadata(module, constants_1.MODULE_METADATA.IMPORTS)
|
34 | : [
|
35 | ...this.reflectMetadata(module.module, constants_1.MODULE_METADATA.IMPORTS),
|
36 | ...(module.imports || []),
|
37 | ];
|
38 | for (const innerModule of modules) {
|
39 | if (ctxRegistry.includes(innerModule)) {
|
40 | continue;
|
41 | }
|
42 | await this.scanForModules(innerModule, [].concat(scope, module), ctxRegistry);
|
43 | }
|
44 | return moduleInstance;
|
45 | }
|
46 | async insertModule(module, scope) {
|
47 | if (module && module.forwardRef) {
|
48 | return this.container.addModule(module.forwardRef(), scope);
|
49 | }
|
50 | return this.container.addModule(module, scope);
|
51 | }
|
52 | async scanModulesForDependencies() {
|
53 | const modules = this.container.getModules();
|
54 | for (const [token, { metatype }] of modules) {
|
55 | await this.reflectImports(metatype, token, metatype.name);
|
56 | this.reflectProviders(metatype, token);
|
57 | this.reflectControllers(metatype, token);
|
58 | this.reflectExports(metatype, token);
|
59 | }
|
60 | this.calculateModulesDistance(modules);
|
61 | }
|
62 | async reflectImports(module, token, context) {
|
63 | const modules = [
|
64 | ...this.reflectMetadata(module, constants_1.MODULE_METADATA.IMPORTS),
|
65 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.IMPORTS),
|
66 | ];
|
67 | for (const related of modules) {
|
68 | await this.insertImport(related, token, context);
|
69 | }
|
70 | }
|
71 | reflectProviders(module, token) {
|
72 | const providers = [
|
73 | ...this.reflectMetadata(module, constants_1.MODULE_METADATA.PROVIDERS),
|
74 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.PROVIDERS),
|
75 | ];
|
76 | providers.forEach(provider => {
|
77 | this.insertProvider(provider, token);
|
78 | this.reflectDynamicMetadata(provider, token);
|
79 | });
|
80 | }
|
81 | reflectControllers(module, token) {
|
82 | const controllers = [
|
83 | ...this.reflectMetadata(module, constants_1.MODULE_METADATA.CONTROLLERS),
|
84 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.CONTROLLERS),
|
85 | ];
|
86 | controllers.forEach(item => {
|
87 | this.insertController(item, token);
|
88 | this.reflectDynamicMetadata(item, token);
|
89 | });
|
90 | }
|
91 | reflectDynamicMetadata(obj, token) {
|
92 | if (!obj || !obj.prototype) {
|
93 | return;
|
94 | }
|
95 | this.reflectInjectables(obj, token, constants_1.GUARDS_METADATA);
|
96 | this.reflectInjectables(obj, token, constants_1.INTERCEPTORS_METADATA);
|
97 | this.reflectInjectables(obj, token, constants_1.EXCEPTION_FILTERS_METADATA);
|
98 | this.reflectInjectables(obj, token, constants_1.PIPES_METADATA);
|
99 | this.reflectParamInjectables(obj, token, constants_1.ROUTE_ARGS_METADATA);
|
100 | }
|
101 | reflectExports(module, token) {
|
102 | const exports = [
|
103 | ...this.reflectMetadata(module, constants_1.MODULE_METADATA.EXPORTS),
|
104 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.EXPORTS),
|
105 | ];
|
106 | exports.forEach(exportedProvider => this.insertExportedProvider(exportedProvider, token));
|
107 | }
|
108 | reflectInjectables(component, token, metadataKey) {
|
109 | const controllerInjectables = this.reflectMetadata(component, metadataKey);
|
110 | const methodsInjectables = this.metadataScanner.scanFromPrototype(null, component.prototype, this.reflectKeyMetadata.bind(this, component, metadataKey));
|
111 | const flattenMethodsInjectables = this.flatten(methodsInjectables);
|
112 | const combinedInjectables = [
|
113 | ...controllerInjectables,
|
114 | ...flattenMethodsInjectables,
|
115 | ].filter(shared_utils_1.isFunction);
|
116 | const injectables = Array.from(new Set(combinedInjectables));
|
117 | injectables.forEach(injectable => this.insertInjectable(injectable, token, component));
|
118 | }
|
119 | reflectParamInjectables(component, token, metadataKey) {
|
120 | const paramsMetadata = this.metadataScanner.scanFromPrototype(null, component.prototype, method => Reflect.getMetadata(metadataKey, component, method));
|
121 | const paramsInjectables = this.flatten(paramsMetadata).map((param) => common_1.flatten(Object.keys(param).map(k => param[k].pipes)).filter(shared_utils_1.isFunction));
|
122 | common_1.flatten(paramsInjectables).forEach((injectable) => this.insertInjectable(injectable, token, component));
|
123 | }
|
124 | reflectKeyMetadata(component, key, method) {
|
125 | let prototype = component.prototype;
|
126 | do {
|
127 | const descriptor = Reflect.getOwnPropertyDescriptor(prototype, method);
|
128 | if (!descriptor) {
|
129 | continue;
|
130 | }
|
131 | return Reflect.getMetadata(key, descriptor.value);
|
132 | } while ((prototype = Reflect.getPrototypeOf(prototype)) &&
|
133 | prototype !== Object.prototype &&
|
134 | prototype);
|
135 | return undefined;
|
136 | }
|
137 | async calculateModulesDistance(modules) {
|
138 | const modulesGenerator = modules.values();
|
139 | const rootModule = modulesGenerator.next().value;
|
140 | const modulesStack = [rootModule];
|
141 | const calculateDistance = (moduleRef, distance = 1) => {
|
142 | if (modulesStack.includes(moduleRef)) {
|
143 | return;
|
144 | }
|
145 | modulesStack.push(moduleRef);
|
146 | const moduleImports = rootModule.relatedModules;
|
147 | moduleImports.forEach(module => {
|
148 | module.distance = distance;
|
149 | calculateDistance(module, distance + 1);
|
150 | });
|
151 | };
|
152 | calculateDistance(rootModule);
|
153 | }
|
154 | async insertImport(related, token, context) {
|
155 | if (shared_utils_1.isUndefined(related)) {
|
156 | throw new circular_dependency_exception_1.CircularDependencyException(context);
|
157 | }
|
158 | if (related && related.forwardRef) {
|
159 | return this.container.addImport(related.forwardRef(), token);
|
160 | }
|
161 | await this.container.addImport(related, token);
|
162 | }
|
163 | isCustomProvider(provider) {
|
164 | return provider && !shared_utils_1.isNil(provider.provide);
|
165 | }
|
166 | insertProvider(provider, token) {
|
167 | const isCustomProvider = this.isCustomProvider(provider);
|
168 | if (!isCustomProvider) {
|
169 | return this.container.addProvider(provider, token);
|
170 | }
|
171 | const applyProvidersMap = this.getApplyProvidersMap();
|
172 | const providersKeys = Object.keys(applyProvidersMap);
|
173 | const type = provider.provide;
|
174 | if (!providersKeys.includes(type)) {
|
175 | return this.container.addProvider(provider, token);
|
176 | }
|
177 | const providerToken = `${type} (UUID: ${random_string_generator_util_1.randomStringGenerator()})`;
|
178 | let scope = provider.scope;
|
179 | if (shared_utils_1.isNil(scope) && provider.useClass) {
|
180 | scope = get_class_scope_1.getClassScope(provider.useClass);
|
181 | }
|
182 | this.applicationProvidersApplyMap.push({
|
183 | type,
|
184 | moduleKey: token,
|
185 | providerKey: providerToken,
|
186 | scope,
|
187 | });
|
188 | const newProvider = Object.assign(Object.assign({}, provider), { provide: providerToken, scope });
|
189 | if (this.isRequestOrTransient(newProvider.scope)) {
|
190 | return this.container.addInjectable(newProvider, token);
|
191 | }
|
192 | this.container.addProvider(newProvider, token);
|
193 | }
|
194 | insertInjectable(injectable, token, host) {
|
195 | this.container.addInjectable(injectable, token, host);
|
196 | }
|
197 | insertExportedProvider(exportedProvider, token) {
|
198 | this.container.addExportedProvider(exportedProvider, token);
|
199 | }
|
200 | insertController(controller, token) {
|
201 | this.container.addController(controller, token);
|
202 | }
|
203 | reflectMetadata(metatype, metadataKey) {
|
204 | return Reflect.getMetadata(metadataKey, metatype) || [];
|
205 | }
|
206 | async registerCoreModule() {
|
207 | const module = this.container.createCoreModule();
|
208 | const instance = await this.scanForModules(module);
|
209 | this.container.registerCoreModuleRef(instance);
|
210 | }
|
211 | |
212 |
|
213 |
|
214 |
|
215 | addScopedEnhancersMetadata() {
|
216 | const scopedGlobalProviders = this.applicationProvidersApplyMap.filter(wrapper => this.isRequestOrTransient(wrapper.scope));
|
217 | scopedGlobalProviders.forEach(({ moduleKey, providerKey }) => {
|
218 | const modulesContainer = this.container.getModules();
|
219 | const { injectables } = modulesContainer.get(moduleKey);
|
220 | const instanceWrapper = injectables.get(providerKey);
|
221 | const modules = [...modulesContainer.values()];
|
222 | const controllersArray = modules.map(module => [
|
223 | ...module.controllers.values(),
|
224 | ]);
|
225 | const controllers = this.flatten(controllersArray);
|
226 | controllers.forEach(controller => controller.addEnhancerMetadata(instanceWrapper));
|
227 | });
|
228 | }
|
229 | applyApplicationProviders() {
|
230 | const applyProvidersMap = this.getApplyProvidersMap();
|
231 | const applyRequestProvidersMap = this.getApplyRequestProvidersMap();
|
232 | const getInstanceWrapper = (moduleKey, providerKey, collectionKey) => {
|
233 | const modules = this.container.getModules();
|
234 | const collection = modules.get(moduleKey)[collectionKey];
|
235 | return collection.get(providerKey);
|
236 | };
|
237 |
|
238 | this.applicationProvidersApplyMap.forEach(({ moduleKey, providerKey, type, scope }) => {
|
239 | let instanceWrapper;
|
240 | if (this.isRequestOrTransient(scope)) {
|
241 | instanceWrapper = getInstanceWrapper(moduleKey, providerKey, 'injectables');
|
242 | return applyRequestProvidersMap[type](instanceWrapper);
|
243 | }
|
244 | instanceWrapper = getInstanceWrapper(moduleKey, providerKey, 'providers');
|
245 | applyProvidersMap[type](instanceWrapper.instance);
|
246 | });
|
247 | }
|
248 | getApplyProvidersMap() {
|
249 | return {
|
250 | [constants_2.APP_INTERCEPTOR]: (interceptor) => this.applicationConfig.addGlobalInterceptor(interceptor),
|
251 | [constants_2.APP_PIPE]: (pipe) => this.applicationConfig.addGlobalPipe(pipe),
|
252 | [constants_2.APP_GUARD]: (guard) => this.applicationConfig.addGlobalGuard(guard),
|
253 | [constants_2.APP_FILTER]: (filter) => this.applicationConfig.addGlobalFilter(filter),
|
254 | };
|
255 | }
|
256 | getApplyRequestProvidersMap() {
|
257 | return {
|
258 | [constants_2.APP_INTERCEPTOR]: (interceptor) => this.applicationConfig.addGlobalRequestInterceptor(interceptor),
|
259 | [constants_2.APP_PIPE]: (pipe) => this.applicationConfig.addGlobalRequestPipe(pipe),
|
260 | [constants_2.APP_GUARD]: (guard) => this.applicationConfig.addGlobalRequestGuard(guard),
|
261 | [constants_2.APP_FILTER]: (filter) => this.applicationConfig.addGlobalRequestFilter(filter),
|
262 | };
|
263 | }
|
264 | isDynamicModule(module) {
|
265 | return module && !!module.module;
|
266 | }
|
267 | isForwardReference(module) {
|
268 | return module && !!module.forwardRef;
|
269 | }
|
270 | flatten(arr) {
|
271 | return arr.reduce((a, b) => a.concat(b), []);
|
272 | }
|
273 | isRequestOrTransient(scope) {
|
274 | return scope === interfaces_1.Scope.REQUEST || scope === interfaces_1.Scope.TRANSIENT;
|
275 | }
|
276 | }
|
277 | exports.DependenciesScanner = DependenciesScanner;
|