1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | exports.DependenciesScanner = void 0;
|
4 | const constants_1 = require("@nestjs/common/constants");
|
5 | const interfaces_1 = require("@nestjs/common/interfaces");
|
6 | const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
|
7 | const iterare_1 = require("iterare");
|
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 invalid_class_module_exception_1 = require("./errors/exceptions/invalid-class-module.exception");
|
12 | const invalid_module_exception_1 = require("./errors/exceptions/invalid-module.exception");
|
13 | const undefined_module_exception_1 = require("./errors/exceptions/undefined-module.exception");
|
14 | const get_class_scope_1 = require("./helpers/get-class-scope");
|
15 | const internal_core_module_factory_1 = require("./injector/internal-core-module/internal-core-module-factory");
|
16 | const uuid_factory_1 = require("./inspector/uuid-factory");
|
17 | class DependenciesScanner {
|
18 | constructor(container, metadataScanner, graphInspector, applicationConfig = new application_config_1.ApplicationConfig()) {
|
19 | this.container = container;
|
20 | this.metadataScanner = metadataScanner;
|
21 | this.graphInspector = graphInspector;
|
22 | this.applicationConfig = applicationConfig;
|
23 | this.applicationProvidersApplyMap = [];
|
24 | }
|
25 | async scan(module, options) {
|
26 | await this.registerCoreModule(options?.overrides);
|
27 | await this.scanForModules({
|
28 | moduleDefinition: module,
|
29 | overrides: options?.overrides,
|
30 | });
|
31 | await this.scanModulesForDependencies();
|
32 | this.calculateModulesDistance();
|
33 | this.addScopedEnhancersMetadata();
|
34 | this.container.bindGlobalScope();
|
35 | }
|
36 | async scanForModules({ moduleDefinition, lazy, scope = [], ctxRegistry = [], overrides = [], }) {
|
37 | const { moduleRef: moduleInstance, inserted: moduleInserted } = (await this.insertOrOverrideModule(moduleDefinition, overrides, scope)) ??
|
38 | {};
|
39 | moduleDefinition =
|
40 | this.getOverrideModuleByModule(moduleDefinition, overrides)?.newModule ??
|
41 | moduleDefinition;
|
42 | moduleDefinition =
|
43 | moduleDefinition instanceof Promise
|
44 | ? await moduleDefinition
|
45 | : moduleDefinition;
|
46 | ctxRegistry.push(moduleDefinition);
|
47 | if (this.isForwardReference(moduleDefinition)) {
|
48 | moduleDefinition = moduleDefinition.forwardRef();
|
49 | }
|
50 | const modules = !this.isDynamicModule(moduleDefinition)
|
51 | ? this.reflectMetadata(constants_1.MODULE_METADATA.IMPORTS, moduleDefinition)
|
52 | : [
|
53 | ...this.reflectMetadata(constants_1.MODULE_METADATA.IMPORTS, moduleDefinition.module),
|
54 | ...(moduleDefinition.imports || []),
|
55 | ];
|
56 | let registeredModuleRefs = [];
|
57 | for (const [index, innerModule] of modules.entries()) {
|
58 |
|
59 | if (innerModule === undefined) {
|
60 | throw new undefined_module_exception_1.UndefinedModuleException(moduleDefinition, index, scope);
|
61 | }
|
62 | if (!innerModule) {
|
63 | throw new invalid_module_exception_1.InvalidModuleException(moduleDefinition, index, scope);
|
64 | }
|
65 | if (ctxRegistry.includes(innerModule)) {
|
66 | continue;
|
67 | }
|
68 | const moduleRefs = await this.scanForModules({
|
69 | moduleDefinition: innerModule,
|
70 | scope: [].concat(scope, moduleDefinition),
|
71 | ctxRegistry,
|
72 | overrides,
|
73 | lazy,
|
74 | });
|
75 | registeredModuleRefs = registeredModuleRefs.concat(moduleRefs);
|
76 | }
|
77 | if (!moduleInstance) {
|
78 | return registeredModuleRefs;
|
79 | }
|
80 | if (lazy && moduleInserted) {
|
81 | this.container.bindGlobalsToImports(moduleInstance);
|
82 | }
|
83 | return [moduleInstance].concat(registeredModuleRefs);
|
84 | }
|
85 | async insertModule(moduleDefinition, scope) {
|
86 | const moduleToAdd = this.isForwardReference(moduleDefinition)
|
87 | ? moduleDefinition.forwardRef()
|
88 | : moduleDefinition;
|
89 | if (this.isInjectable(moduleToAdd) ||
|
90 | this.isController(moduleToAdd) ||
|
91 | this.isExceptionFilter(moduleToAdd)) {
|
92 | throw new invalid_class_module_exception_1.InvalidClassModuleException(moduleDefinition, scope);
|
93 | }
|
94 | return this.container.addModule(moduleToAdd, scope);
|
95 | }
|
96 | async scanModulesForDependencies(modules = this.container.getModules()) {
|
97 | for (const [token, { metatype }] of modules) {
|
98 | await this.reflectImports(metatype, token, metatype.name);
|
99 | this.reflectProviders(metatype, token);
|
100 | this.reflectControllers(metatype, token);
|
101 | this.reflectExports(metatype, token);
|
102 | }
|
103 | }
|
104 | async reflectImports(module, token, context) {
|
105 | const modules = [
|
106 | ...this.reflectMetadata(constants_1.MODULE_METADATA.IMPORTS, module),
|
107 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.IMPORTS),
|
108 | ];
|
109 | for (const related of modules) {
|
110 | await this.insertImport(related, token, context);
|
111 | }
|
112 | }
|
113 | reflectProviders(module, token) {
|
114 | const providers = [
|
115 | ...this.reflectMetadata(constants_1.MODULE_METADATA.PROVIDERS, module),
|
116 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.PROVIDERS),
|
117 | ];
|
118 | providers.forEach(provider => {
|
119 | this.insertProvider(provider, token);
|
120 | this.reflectDynamicMetadata(provider, token);
|
121 | });
|
122 | }
|
123 | reflectControllers(module, token) {
|
124 | const controllers = [
|
125 | ...this.reflectMetadata(constants_1.MODULE_METADATA.CONTROLLERS, module),
|
126 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.CONTROLLERS),
|
127 | ];
|
128 | controllers.forEach(item => {
|
129 | this.insertController(item, token);
|
130 | this.reflectDynamicMetadata(item, token);
|
131 | });
|
132 | }
|
133 | reflectDynamicMetadata(cls, token) {
|
134 | if (!cls || !cls.prototype) {
|
135 | return;
|
136 | }
|
137 | this.reflectInjectables(cls, token, constants_1.GUARDS_METADATA);
|
138 | this.reflectInjectables(cls, token, constants_1.INTERCEPTORS_METADATA);
|
139 | this.reflectInjectables(cls, token, constants_1.EXCEPTION_FILTERS_METADATA);
|
140 | this.reflectInjectables(cls, token, constants_1.PIPES_METADATA);
|
141 | this.reflectParamInjectables(cls, token, constants_1.ROUTE_ARGS_METADATA);
|
142 | }
|
143 | reflectExports(module, token) {
|
144 | const exports = [
|
145 | ...this.reflectMetadata(constants_1.MODULE_METADATA.EXPORTS, module),
|
146 | ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.EXPORTS),
|
147 | ];
|
148 | exports.forEach(exportedProvider => this.insertExportedProvider(exportedProvider, token));
|
149 | }
|
150 | reflectInjectables(component, token, metadataKey) {
|
151 | const controllerInjectables = this.reflectMetadata(metadataKey, component);
|
152 | const methodInjectables = this.metadataScanner
|
153 | .getAllMethodNames(component.prototype)
|
154 | .reduce((acc, method) => {
|
155 | const methodInjectable = this.reflectKeyMetadata(component, metadataKey, method);
|
156 | if (methodInjectable) {
|
157 | acc.push(methodInjectable);
|
158 | }
|
159 | return acc;
|
160 | }, []);
|
161 | controllerInjectables.forEach(injectable => this.insertInjectable(injectable, token, component, constants_1.ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey]));
|
162 | methodInjectables.forEach(methodInjectable => {
|
163 | methodInjectable.metadata.forEach(injectable => this.insertInjectable(injectable, token, component, constants_1.ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey], methodInjectable.methodKey));
|
164 | });
|
165 | }
|
166 | reflectParamInjectables(component, token, metadataKey) {
|
167 | const paramsMethods = this.metadataScanner.getAllMethodNames(component.prototype);
|
168 | paramsMethods.forEach(methodKey => {
|
169 | const metadata = Reflect.getMetadata(metadataKey, component, methodKey);
|
170 | if (!metadata) {
|
171 | return;
|
172 | }
|
173 | const params = Object.values(metadata);
|
174 | params
|
175 | .map(item => item.pipes)
|
176 | .flat(1)
|
177 | .forEach(injectable => this.insertInjectable(injectable, token, component, 'pipe', methodKey));
|
178 | });
|
179 | }
|
180 | reflectKeyMetadata(component, key, methodKey) {
|
181 | let prototype = component.prototype;
|
182 | do {
|
183 | const descriptor = Reflect.getOwnPropertyDescriptor(prototype, methodKey);
|
184 | if (!descriptor) {
|
185 | continue;
|
186 | }
|
187 | const metadata = Reflect.getMetadata(key, descriptor.value);
|
188 | if (!metadata) {
|
189 | return;
|
190 | }
|
191 | return { methodKey, metadata };
|
192 | } while ((prototype = Reflect.getPrototypeOf(prototype)) &&
|
193 | prototype !== Object.prototype &&
|
194 | prototype);
|
195 | return undefined;
|
196 | }
|
197 | calculateModulesDistance() {
|
198 | const modulesGenerator = this.container.getModules().values();
|
199 |
|
200 | modulesGenerator.next();
|
201 | const modulesStack = [];
|
202 | const calculateDistance = (moduleRef, distance = 1) => {
|
203 | if (!moduleRef || modulesStack.includes(moduleRef)) {
|
204 | return;
|
205 | }
|
206 | modulesStack.push(moduleRef);
|
207 | const moduleImports = moduleRef.imports;
|
208 | moduleImports.forEach(importedModuleRef => {
|
209 | if (importedModuleRef) {
|
210 | if (distance > importedModuleRef.distance) {
|
211 | importedModuleRef.distance = distance;
|
212 | }
|
213 | calculateDistance(importedModuleRef, distance + 1);
|
214 | }
|
215 | });
|
216 | };
|
217 | const rootModule = modulesGenerator.next().value;
|
218 | calculateDistance(rootModule);
|
219 | }
|
220 | async insertImport(related, token, context) {
|
221 | if ((0, shared_utils_1.isUndefined)(related)) {
|
222 | throw new circular_dependency_exception_1.CircularDependencyException(context);
|
223 | }
|
224 | if (this.isForwardReference(related)) {
|
225 | return this.container.addImport(related.forwardRef(), token);
|
226 | }
|
227 | await this.container.addImport(related, token);
|
228 | }
|
229 | isCustomProvider(provider) {
|
230 | return provider && !(0, shared_utils_1.isNil)(provider.provide);
|
231 | }
|
232 | insertProvider(provider, token) {
|
233 | const isCustomProvider = this.isCustomProvider(provider);
|
234 | if (!isCustomProvider) {
|
235 | return this.container.addProvider(provider, token);
|
236 | }
|
237 | const applyProvidersMap = this.getApplyProvidersMap();
|
238 | const providersKeys = Object.keys(applyProvidersMap);
|
239 | const type = provider.provide;
|
240 | if (!providersKeys.includes(type)) {
|
241 | return this.container.addProvider(provider, token);
|
242 | }
|
243 | const uuid = uuid_factory_1.UuidFactory.get(type.toString());
|
244 | const providerToken = `${type} (UUID: ${uuid})`;
|
245 | let scope = provider.scope;
|
246 | if ((0, shared_utils_1.isNil)(scope) && provider.useClass) {
|
247 | scope = (0, get_class_scope_1.getClassScope)(provider.useClass);
|
248 | }
|
249 | this.applicationProvidersApplyMap.push({
|
250 | type,
|
251 | moduleKey: token,
|
252 | providerKey: providerToken,
|
253 | scope,
|
254 | });
|
255 | const newProvider = {
|
256 | ...provider,
|
257 | provide: providerToken,
|
258 | scope,
|
259 | };
|
260 | const enhancerSubtype = constants_2.ENHANCER_TOKEN_TO_SUBTYPE_MAP[type];
|
261 | const factoryOrClassProvider = newProvider;
|
262 | if (this.isRequestOrTransient(factoryOrClassProvider.scope)) {
|
263 | return this.container.addInjectable(newProvider, token, enhancerSubtype);
|
264 | }
|
265 | this.container.addProvider(newProvider, token, enhancerSubtype);
|
266 | }
|
267 | insertInjectable(injectable, token, host, subtype, methodKey) {
|
268 | if ((0, shared_utils_1.isFunction)(injectable)) {
|
269 | const instanceWrapper = this.container.addInjectable(injectable, token, subtype, host);
|
270 | this.graphInspector.insertEnhancerMetadataCache({
|
271 | moduleToken: token,
|
272 | classRef: host,
|
273 | enhancerInstanceWrapper: instanceWrapper,
|
274 | targetNodeId: instanceWrapper.id,
|
275 | subtype,
|
276 | methodKey,
|
277 | });
|
278 | return instanceWrapper;
|
279 | }
|
280 | else {
|
281 | this.graphInspector.insertEnhancerMetadataCache({
|
282 | moduleToken: token,
|
283 | classRef: host,
|
284 | enhancerRef: injectable,
|
285 | methodKey,
|
286 | subtype,
|
287 | });
|
288 | }
|
289 | }
|
290 | insertExportedProvider(exportedProvider, token) {
|
291 | this.container.addExportedProvider(exportedProvider, token);
|
292 | }
|
293 | insertController(controller, token) {
|
294 | this.container.addController(controller, token);
|
295 | }
|
296 | insertOrOverrideModule(moduleDefinition, overrides, scope) {
|
297 | const overrideModule = this.getOverrideModuleByModule(moduleDefinition, overrides);
|
298 | if (overrideModule !== undefined) {
|
299 | return this.overrideModule(moduleDefinition, overrideModule.newModule, scope);
|
300 | }
|
301 | return this.insertModule(moduleDefinition, scope);
|
302 | }
|
303 | getOverrideModuleByModule(module, overrides) {
|
304 | if (this.isForwardReference(module)) {
|
305 | return overrides.find(moduleToOverride => {
|
306 | return (moduleToOverride.moduleToReplace === module.forwardRef() ||
|
307 | moduleToOverride.moduleToReplace.forwardRef?.() === module.forwardRef());
|
308 | });
|
309 | }
|
310 | return overrides.find(moduleToOverride => moduleToOverride.moduleToReplace === module);
|
311 | }
|
312 | async overrideModule(moduleToOverride, newModule, scope) {
|
313 | return this.container.replaceModule(this.isForwardReference(moduleToOverride)
|
314 | ? moduleToOverride.forwardRef()
|
315 | : moduleToOverride, this.isForwardReference(newModule) ? newModule.forwardRef() : newModule, scope);
|
316 | }
|
317 | reflectMetadata(metadataKey, metatype) {
|
318 | return Reflect.getMetadata(metadataKey, metatype) || [];
|
319 | }
|
320 | async registerCoreModule(overrides) {
|
321 | const moduleDefinition = internal_core_module_factory_1.InternalCoreModuleFactory.create(this.container, this, this.container.getModuleCompiler(), this.container.getHttpAdapterHostRef(), this.graphInspector, overrides);
|
322 | const [instance] = await this.scanForModules({
|
323 | moduleDefinition,
|
324 | overrides,
|
325 | });
|
326 | this.container.registerCoreModuleRef(instance);
|
327 | }
|
328 | |
329 |
|
330 |
|
331 |
|
332 | addScopedEnhancersMetadata() {
|
333 | (0, iterare_1.iterate)(this.applicationProvidersApplyMap)
|
334 | .filter(wrapper => this.isRequestOrTransient(wrapper.scope))
|
335 | .forEach(({ moduleKey, providerKey }) => {
|
336 | const modulesContainer = this.container.getModules();
|
337 | const { injectables } = modulesContainer.get(moduleKey);
|
338 | const instanceWrapper = injectables.get(providerKey);
|
339 | const iterableIterator = modulesContainer.values();
|
340 | (0, iterare_1.iterate)(iterableIterator)
|
341 | .map(moduleRef => Array.from(moduleRef.controllers.values()).concat(moduleRef.entryProviders))
|
342 | .flatten()
|
343 | .forEach(controllerOrEntryProvider => controllerOrEntryProvider.addEnhancerMetadata(instanceWrapper));
|
344 | });
|
345 | }
|
346 | applyApplicationProviders() {
|
347 | const applyProvidersMap = this.getApplyProvidersMap();
|
348 | const applyRequestProvidersMap = this.getApplyRequestProvidersMap();
|
349 | const getInstanceWrapper = (moduleKey, providerKey, collectionKey) => {
|
350 | const modules = this.container.getModules();
|
351 | const collection = modules.get(moduleKey)[collectionKey];
|
352 | return collection.get(providerKey);
|
353 | };
|
354 |
|
355 | this.applicationProvidersApplyMap.forEach(({ moduleKey, providerKey, type, scope }) => {
|
356 | let instanceWrapper;
|
357 | if (this.isRequestOrTransient(scope)) {
|
358 | instanceWrapper = getInstanceWrapper(moduleKey, providerKey, 'injectables');
|
359 | this.graphInspector.insertAttachedEnhancer(instanceWrapper);
|
360 | return applyRequestProvidersMap[type](instanceWrapper);
|
361 | }
|
362 | instanceWrapper = getInstanceWrapper(moduleKey, providerKey, 'providers');
|
363 | this.graphInspector.insertAttachedEnhancer(instanceWrapper);
|
364 | applyProvidersMap[type](instanceWrapper.instance);
|
365 | });
|
366 | }
|
367 | getApplyProvidersMap() {
|
368 | return {
|
369 | [constants_2.APP_INTERCEPTOR]: (interceptor) => this.applicationConfig.addGlobalInterceptor(interceptor),
|
370 | [constants_2.APP_PIPE]: (pipe) => this.applicationConfig.addGlobalPipe(pipe),
|
371 | [constants_2.APP_GUARD]: (guard) => this.applicationConfig.addGlobalGuard(guard),
|
372 | [constants_2.APP_FILTER]: (filter) => this.applicationConfig.addGlobalFilter(filter),
|
373 | };
|
374 | }
|
375 | getApplyRequestProvidersMap() {
|
376 | return {
|
377 | [constants_2.APP_INTERCEPTOR]: (interceptor) => this.applicationConfig.addGlobalRequestInterceptor(interceptor),
|
378 | [constants_2.APP_PIPE]: (pipe) => this.applicationConfig.addGlobalRequestPipe(pipe),
|
379 | [constants_2.APP_GUARD]: (guard) => this.applicationConfig.addGlobalRequestGuard(guard),
|
380 | [constants_2.APP_FILTER]: (filter) => this.applicationConfig.addGlobalRequestFilter(filter),
|
381 | };
|
382 | }
|
383 | isDynamicModule(module) {
|
384 | return module && !!module.module;
|
385 | }
|
386 | |
387 |
|
388 |
|
389 |
|
390 | isInjectable(metatype) {
|
391 | return !!Reflect.getMetadata(constants_1.INJECTABLE_WATERMARK, metatype);
|
392 | }
|
393 | |
394 |
|
395 |
|
396 |
|
397 | isController(metatype) {
|
398 | return !!Reflect.getMetadata(constants_1.CONTROLLER_WATERMARK, metatype);
|
399 | }
|
400 | |
401 |
|
402 |
|
403 |
|
404 | isExceptionFilter(metatype) {
|
405 | return !!Reflect.getMetadata(constants_1.CATCH_WATERMARK, metatype);
|
406 | }
|
407 | isForwardReference(module) {
|
408 | return module && !!module.forwardRef;
|
409 | }
|
410 | isRequestOrTransient(scope) {
|
411 | return scope === interfaces_1.Scope.REQUEST || scope === interfaces_1.Scope.TRANSIENT;
|
412 | }
|
413 | }
|
414 | exports.DependenciesScanner = DependenciesScanner;
|