Spaces:
Sleeping
Sleeping
| ; | |
| Object.defineProperty(exports, "__esModule", { value: true }); | |
| exports.DependenciesScanner = void 0; | |
| const constants_1 = require("@nestjs/common/constants"); | |
| const interfaces_1 = require("@nestjs/common/interfaces"); | |
| const shared_utils_1 = require("@nestjs/common/utils/shared.utils"); | |
| const iterare_1 = require("iterare"); | |
| const application_config_1 = require("./application-config"); | |
| const constants_2 = require("./constants"); | |
| const circular_dependency_exception_1 = require("./errors/exceptions/circular-dependency.exception"); | |
| const invalid_class_module_exception_1 = require("./errors/exceptions/invalid-class-module.exception"); | |
| const invalid_module_exception_1 = require("./errors/exceptions/invalid-module.exception"); | |
| const undefined_module_exception_1 = require("./errors/exceptions/undefined-module.exception"); | |
| const get_class_scope_1 = require("./helpers/get-class-scope"); | |
| const internal_core_module_factory_1 = require("./injector/internal-core-module/internal-core-module-factory"); | |
| const topology_tree_1 = require("./injector/topology-tree/topology-tree"); | |
| const uuid_factory_1 = require("./inspector/uuid-factory"); | |
| class DependenciesScanner { | |
| constructor(container, metadataScanner, graphInspector, applicationConfig = new application_config_1.ApplicationConfig()) { | |
| this.container = container; | |
| this.metadataScanner = metadataScanner; | |
| this.graphInspector = graphInspector; | |
| this.applicationConfig = applicationConfig; | |
| this.applicationProvidersApplyMap = []; | |
| } | |
| async scan(module, options) { | |
| await this.registerCoreModule(options?.overrides); | |
| await this.scanForModules({ | |
| moduleDefinition: module, | |
| overrides: options?.overrides, | |
| }); | |
| await this.scanModulesForDependencies(); | |
| this.addScopedEnhancersMetadata(); | |
| // Modules distance calculation should be done after all modules are scanned | |
| // but before global modules are registered (linked to all modules). | |
| // Global modules have their distance set to MAX anyway. | |
| this.calculateModulesDistance(); | |
| this.container.bindGlobalScope(); | |
| } | |
| async scanForModules({ moduleDefinition, lazy, scope = [], ctxRegistry = [], overrides = [], }) { | |
| const { moduleRef: moduleInstance, inserted: moduleInserted } = (await this.insertOrOverrideModule(moduleDefinition, overrides, scope)) ?? | |
| {}; | |
| moduleDefinition = | |
| this.getOverrideModuleByModule(moduleDefinition, overrides)?.newModule ?? | |
| moduleDefinition; | |
| moduleDefinition = | |
| moduleDefinition instanceof Promise | |
| ? await moduleDefinition | |
| : moduleDefinition; | |
| ctxRegistry.push(moduleDefinition); | |
| if (this.isForwardReference(moduleDefinition)) { | |
| moduleDefinition = moduleDefinition.forwardRef(); | |
| } | |
| const modules = !this.isDynamicModule(moduleDefinition) | |
| ? this.reflectMetadata(constants_1.MODULE_METADATA.IMPORTS, moduleDefinition) | |
| : [ | |
| ...this.reflectMetadata(constants_1.MODULE_METADATA.IMPORTS, moduleDefinition.module), | |
| ...(moduleDefinition.imports || []), | |
| ]; | |
| let registeredModuleRefs = []; | |
| for (const [index, innerModule] of modules.entries()) { | |
| // In case of a circular dependency (ES module system), JavaScript will resolve the type to `undefined`. | |
| if (innerModule === undefined) { | |
| throw new undefined_module_exception_1.UndefinedModuleException(moduleDefinition, index, scope); | |
| } | |
| if (!innerModule) { | |
| throw new invalid_module_exception_1.InvalidModuleException(moduleDefinition, index, scope, innerModule); | |
| } | |
| if (ctxRegistry.includes(innerModule)) { | |
| continue; | |
| } | |
| const moduleRefs = await this.scanForModules({ | |
| moduleDefinition: innerModule, | |
| scope: [].concat(scope, moduleDefinition), | |
| ctxRegistry, | |
| overrides, | |
| lazy, | |
| }); | |
| registeredModuleRefs = registeredModuleRefs.concat(moduleRefs); | |
| } | |
| if (!moduleInstance) { | |
| return registeredModuleRefs; | |
| } | |
| if (lazy && moduleInserted) { | |
| this.container.bindGlobalsToImports(moduleInstance); | |
| } | |
| return [moduleInstance].concat(registeredModuleRefs); | |
| } | |
| async insertModule(moduleDefinition, scope) { | |
| const moduleToAdd = this.isForwardReference(moduleDefinition) | |
| ? moduleDefinition.forwardRef() | |
| : moduleDefinition; | |
| if (this.isInjectable(moduleToAdd)) { | |
| throw new invalid_class_module_exception_1.InvalidClassModuleException(moduleDefinition, scope, 'provider'); | |
| } | |
| if (this.isController(moduleToAdd)) { | |
| throw new invalid_class_module_exception_1.InvalidClassModuleException(moduleDefinition, scope, 'controller'); | |
| } | |
| if (this.isExceptionFilter(moduleToAdd)) { | |
| throw new invalid_class_module_exception_1.InvalidClassModuleException(moduleDefinition, scope, 'filter'); | |
| } | |
| return this.container.addModule(moduleToAdd, scope); | |
| } | |
| async scanModulesForDependencies(modules = this.container.getModules()) { | |
| for (const [token, { metatype }] of modules) { | |
| await this.reflectImports(metatype, token, metatype.name); | |
| this.reflectProviders(metatype, token); | |
| this.reflectControllers(metatype, token); | |
| this.reflectExports(metatype, token); | |
| } | |
| } | |
| async reflectImports(module, token, context) { | |
| const modules = [ | |
| ...this.reflectMetadata(constants_1.MODULE_METADATA.IMPORTS, module), | |
| ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.IMPORTS), | |
| ]; | |
| for (const related of modules) { | |
| await this.insertImport(related, token, context); | |
| } | |
| } | |
| reflectProviders(module, token) { | |
| const providers = [ | |
| ...this.reflectMetadata(constants_1.MODULE_METADATA.PROVIDERS, module), | |
| ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.PROVIDERS), | |
| ]; | |
| providers.forEach(provider => { | |
| this.insertProvider(provider, token); | |
| this.reflectDynamicMetadata(provider, token); | |
| }); | |
| } | |
| reflectControllers(module, token) { | |
| const controllers = [ | |
| ...this.reflectMetadata(constants_1.MODULE_METADATA.CONTROLLERS, module), | |
| ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.CONTROLLERS), | |
| ]; | |
| controllers.forEach(item => { | |
| this.insertController(item, token); | |
| this.reflectDynamicMetadata(item, token); | |
| }); | |
| } | |
| reflectDynamicMetadata(cls, token) { | |
| if (!cls || !cls.prototype) { | |
| return; | |
| } | |
| this.reflectInjectables(cls, token, constants_1.GUARDS_METADATA); | |
| this.reflectInjectables(cls, token, constants_1.INTERCEPTORS_METADATA); | |
| this.reflectInjectables(cls, token, constants_1.EXCEPTION_FILTERS_METADATA); | |
| this.reflectInjectables(cls, token, constants_1.PIPES_METADATA); | |
| this.reflectParamInjectables(cls, token, constants_1.ROUTE_ARGS_METADATA); | |
| } | |
| reflectExports(module, token) { | |
| const exports = [ | |
| ...this.reflectMetadata(constants_1.MODULE_METADATA.EXPORTS, module), | |
| ...this.container.getDynamicMetadataByToken(token, constants_1.MODULE_METADATA.EXPORTS), | |
| ]; | |
| exports.forEach(exportedProvider => this.insertExportedProviderOrModule(exportedProvider, token)); | |
| } | |
| reflectInjectables(component, token, metadataKey) { | |
| const controllerInjectables = this.reflectMetadata(metadataKey, component); | |
| const methodInjectables = this.metadataScanner | |
| .getAllMethodNames(component.prototype) | |
| .reduce((acc, method) => { | |
| const methodInjectable = this.reflectKeyMetadata(component, metadataKey, method); | |
| if (methodInjectable) { | |
| acc.push(methodInjectable); | |
| } | |
| return acc; | |
| }, []); | |
| controllerInjectables.forEach(injectable => this.insertInjectable(injectable, token, component, constants_1.ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey])); | |
| methodInjectables.forEach(methodInjectable => { | |
| methodInjectable.metadata.forEach(injectable => this.insertInjectable(injectable, token, component, constants_1.ENHANCER_KEY_TO_SUBTYPE_MAP[metadataKey], methodInjectable.methodKey)); | |
| }); | |
| } | |
| reflectParamInjectables(component, token, metadataKey) { | |
| const paramsMethods = this.metadataScanner.getAllMethodNames(component.prototype); | |
| paramsMethods.forEach(methodKey => { | |
| const metadata = Reflect.getMetadata(metadataKey, component, methodKey); | |
| if (!metadata) { | |
| return; | |
| } | |
| const params = Object.values(metadata); | |
| params | |
| .map(item => item.pipes) | |
| .flat(1) | |
| .forEach(injectable => this.insertInjectable(injectable, token, component, 'pipe', methodKey)); | |
| }); | |
| } | |
| reflectKeyMetadata(component, key, methodKey) { | |
| let prototype = component.prototype; | |
| do { | |
| const descriptor = Reflect.getOwnPropertyDescriptor(prototype, methodKey); | |
| if (!descriptor) { | |
| continue; | |
| } | |
| const metadata = Reflect.getMetadata(key, descriptor.value); | |
| if (!metadata) { | |
| return; | |
| } | |
| return { methodKey, metadata }; | |
| } while ((prototype = Reflect.getPrototypeOf(prototype)) && | |
| prototype !== Object.prototype && | |
| prototype); | |
| return undefined; | |
| } | |
| calculateModulesDistance() { | |
| const modulesGenerator = this.container.getModules().values(); | |
| // Skip "InternalCoreModule" | |
| // The second element is the actual root module | |
| modulesGenerator.next(); | |
| const rootModule = modulesGenerator.next().value; | |
| if (!rootModule) { | |
| return; | |
| } | |
| // Convert modules to an acyclic connected graph | |
| const tree = new topology_tree_1.TopologyTree(rootModule); | |
| tree.walk((moduleRef, depth) => { | |
| if (moduleRef.isGlobal) { | |
| return; | |
| } | |
| moduleRef.distance = depth; | |
| }); | |
| } | |
| async insertImport(related, token, context) { | |
| if ((0, shared_utils_1.isUndefined)(related)) { | |
| throw new circular_dependency_exception_1.CircularDependencyException(context); | |
| } | |
| if (this.isForwardReference(related)) { | |
| return this.container.addImport(related.forwardRef(), token); | |
| } | |
| await this.container.addImport(related, token); | |
| } | |
| isCustomProvider(provider) { | |
| return provider && !(0, shared_utils_1.isNil)(provider.provide); | |
| } | |
| insertProvider(provider, token) { | |
| const isCustomProvider = this.isCustomProvider(provider); | |
| if (!isCustomProvider) { | |
| return this.container.addProvider(provider, token); | |
| } | |
| const applyProvidersMap = this.getApplyProvidersMap(); | |
| const providersKeys = Object.keys(applyProvidersMap); | |
| const type = provider.provide; | |
| if (!providersKeys.includes(type)) { | |
| return this.container.addProvider(provider, token); | |
| } | |
| const uuid = uuid_factory_1.UuidFactory.get(type.toString()); | |
| const providerToken = `${type} (UUID: ${uuid})`; | |
| let scope = provider.scope; | |
| if ((0, shared_utils_1.isNil)(scope) && provider.useClass) { | |
| scope = (0, get_class_scope_1.getClassScope)(provider.useClass); | |
| } | |
| this.applicationProvidersApplyMap.push({ | |
| type, | |
| moduleKey: token, | |
| providerKey: providerToken, | |
| scope, | |
| }); | |
| const newProvider = { | |
| ...provider, | |
| provide: providerToken, | |
| scope, | |
| }; | |
| const enhancerSubtype = constants_2.ENHANCER_TOKEN_TO_SUBTYPE_MAP[type]; | |
| const factoryOrClassProvider = newProvider; | |
| if (this.isRequestOrTransient(factoryOrClassProvider.scope)) { | |
| return this.container.addInjectable(newProvider, token, enhancerSubtype); | |
| } | |
| this.container.addProvider(newProvider, token, enhancerSubtype); | |
| } | |
| insertInjectable(injectable, token, host, subtype, methodKey) { | |
| if ((0, shared_utils_1.isFunction)(injectable)) { | |
| const instanceWrapper = this.container.addInjectable(injectable, token, subtype, host); | |
| this.graphInspector.insertEnhancerMetadataCache({ | |
| moduleToken: token, | |
| classRef: host, | |
| enhancerInstanceWrapper: instanceWrapper, | |
| targetNodeId: instanceWrapper.id, | |
| subtype, | |
| methodKey, | |
| }); | |
| return instanceWrapper; | |
| } | |
| else { | |
| this.graphInspector.insertEnhancerMetadataCache({ | |
| moduleToken: token, | |
| classRef: host, | |
| enhancerRef: injectable, | |
| methodKey, | |
| subtype, | |
| }); | |
| } | |
| } | |
| insertExportedProviderOrModule(toExport, token) { | |
| const fulfilledProvider = this.isForwardReference(toExport) | |
| ? toExport.forwardRef() | |
| : toExport; | |
| this.container.addExportedProviderOrModule(fulfilledProvider, token); | |
| } | |
| insertController(controller, token) { | |
| this.container.addController(controller, token); | |
| } | |
| insertOrOverrideModule(moduleDefinition, overrides, scope) { | |
| const overrideModule = this.getOverrideModuleByModule(moduleDefinition, overrides); | |
| if (overrideModule !== undefined) { | |
| return this.overrideModule(moduleDefinition, overrideModule.newModule, scope); | |
| } | |
| return this.insertModule(moduleDefinition, scope); | |
| } | |
| getOverrideModuleByModule(module, overrides) { | |
| if (this.isForwardReference(module)) { | |
| return overrides.find(moduleToOverride => { | |
| return (moduleToOverride.moduleToReplace === module.forwardRef() || | |
| moduleToOverride.moduleToReplace.forwardRef?.() === module.forwardRef()); | |
| }); | |
| } | |
| return overrides.find(moduleToOverride => moduleToOverride.moduleToReplace === module); | |
| } | |
| async overrideModule(moduleToOverride, newModule, scope) { | |
| return this.container.replaceModule(this.isForwardReference(moduleToOverride) | |
| ? moduleToOverride.forwardRef() | |
| : moduleToOverride, this.isForwardReference(newModule) ? newModule.forwardRef() : newModule, scope); | |
| } | |
| reflectMetadata(metadataKey, metatype) { | |
| return Reflect.getMetadata(metadataKey, metatype) || []; | |
| } | |
| async registerCoreModule(overrides) { | |
| const moduleDefinition = internal_core_module_factory_1.InternalCoreModuleFactory.create(this.container, this, this.container.getModuleCompiler(), this.container.getHttpAdapterHostRef(), this.graphInspector, overrides); | |
| const [instance] = await this.scanForModules({ | |
| moduleDefinition, | |
| overrides, | |
| }); | |
| this.container.registerCoreModuleRef(instance); | |
| } | |
| /** | |
| * Add either request or transient globally scoped enhancers | |
| * to all controllers metadata storage | |
| */ | |
| addScopedEnhancersMetadata() { | |
| (0, iterare_1.iterate)(this.applicationProvidersApplyMap) | |
| .filter(wrapper => this.isRequestOrTransient(wrapper.scope)) | |
| .forEach(({ moduleKey, providerKey }) => { | |
| const modulesContainer = this.container.getModules(); | |
| const { injectables } = modulesContainer.get(moduleKey); | |
| const instanceWrapper = injectables.get(providerKey); | |
| const iterableIterator = modulesContainer.values(); | |
| (0, iterare_1.iterate)(iterableIterator) | |
| .map(moduleRef => Array.from(moduleRef.controllers.values()).concat(moduleRef.entryProviders)) | |
| .flatten() | |
| .forEach(controllerOrEntryProvider => controllerOrEntryProvider.addEnhancerMetadata(instanceWrapper)); | |
| }); | |
| } | |
| applyApplicationProviders() { | |
| const applyProvidersMap = this.getApplyProvidersMap(); | |
| const applyRequestProvidersMap = this.getApplyRequestProvidersMap(); | |
| const getInstanceWrapper = (moduleKey, providerKey, collectionKey) => { | |
| const modules = this.container.getModules(); | |
| const collection = modules.get(moduleKey)[collectionKey]; | |
| return collection.get(providerKey); | |
| }; | |
| // Add global enhancers to the application config | |
| this.applicationProvidersApplyMap.forEach(({ moduleKey, providerKey, type, scope }) => { | |
| let instanceWrapper; | |
| if (this.isRequestOrTransient(scope)) { | |
| instanceWrapper = getInstanceWrapper(moduleKey, providerKey, 'injectables'); | |
| this.graphInspector.insertAttachedEnhancer(instanceWrapper); | |
| return applyRequestProvidersMap[type](instanceWrapper); | |
| } | |
| instanceWrapper = getInstanceWrapper(moduleKey, providerKey, 'providers'); | |
| this.graphInspector.insertAttachedEnhancer(instanceWrapper); | |
| applyProvidersMap[type](instanceWrapper.instance); | |
| }); | |
| } | |
| getApplyProvidersMap() { | |
| return { | |
| [constants_2.APP_INTERCEPTOR]: (interceptor) => this.applicationConfig.addGlobalInterceptor(interceptor), | |
| [constants_2.APP_PIPE]: (pipe) => this.applicationConfig.addGlobalPipe(pipe), | |
| [constants_2.APP_GUARD]: (guard) => this.applicationConfig.addGlobalGuard(guard), | |
| [constants_2.APP_FILTER]: (filter) => this.applicationConfig.addGlobalFilter(filter), | |
| }; | |
| } | |
| getApplyRequestProvidersMap() { | |
| return { | |
| [constants_2.APP_INTERCEPTOR]: (interceptor) => this.applicationConfig.addGlobalRequestInterceptor(interceptor), | |
| [constants_2.APP_PIPE]: (pipe) => this.applicationConfig.addGlobalRequestPipe(pipe), | |
| [constants_2.APP_GUARD]: (guard) => this.applicationConfig.addGlobalRequestGuard(guard), | |
| [constants_2.APP_FILTER]: (filter) => this.applicationConfig.addGlobalRequestFilter(filter), | |
| }; | |
| } | |
| isDynamicModule(module) { | |
| return module && !!module.module; | |
| } | |
| /** | |
| * @param metatype | |
| * @returns `true` if `metatype` is annotated with the `@Injectable()` decorator. | |
| */ | |
| isInjectable(metatype) { | |
| return !!Reflect.getMetadata(constants_1.INJECTABLE_WATERMARK, metatype); | |
| } | |
| /** | |
| * @param metatype | |
| * @returns `true` if `metatype` is annotated with the `@Controller()` decorator. | |
| */ | |
| isController(metatype) { | |
| return !!Reflect.getMetadata(constants_1.CONTROLLER_WATERMARK, metatype); | |
| } | |
| /** | |
| * @param metatype | |
| * @returns `true` if `metatype` is annotated with the `@Catch()` decorator. | |
| */ | |
| isExceptionFilter(metatype) { | |
| return !!Reflect.getMetadata(constants_1.CATCH_WATERMARK, metatype); | |
| } | |
| isForwardReference(module) { | |
| return module && !!module.forwardRef; | |
| } | |
| isRequestOrTransient(scope) { | |
| return scope === interfaces_1.Scope.REQUEST || scope === interfaces_1.Scope.TRANSIENT; | |
| } | |
| } | |
| exports.DependenciesScanner = DependenciesScanner; | |