"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("@nestjs/common/constants");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const runtime_exception_1 = require("../errors/exceptions/runtime.exception");
const undefined_dependency_exception_1 = require("../errors/exceptions/undefined-dependency.exception");
const unknown_dependencies_exception_1 = require("../errors/exceptions/unknown-dependencies.exception");
const constants_2 = require("./constants");
const inquirer_1 = require("./inquirer");
const instance_wrapper_1 = require("./instance-wrapper");
const iterare_1 = require("iterare");
class Injector {
async loadMiddleware(wrapper, collection, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
const { metatype } = wrapper;
const targetWrapper = collection.get(metatype.name);
if (!shared_utils_1.isUndefined(targetWrapper.instance)) {
return;
}
const loadInstance = (instances) => {
targetWrapper.instance = targetWrapper.isDependencyTreeStatic()
? new metatype(...instances)
: Object.create(metatype.prototype);
};
await this.resolveConstructorParams(wrapper, moduleRef, null, loadInstance, contextId, inquirer);
}
async loadController(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT) {
const controllers = moduleRef.controllers;
await this.loadInstance(wrapper, controllers, moduleRef, contextId, wrapper);
await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
}
async loadInjectable(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
const injectables = moduleRef.injectables;
await this.loadInstance(wrapper, injectables, moduleRef, contextId, inquirer);
}
async loadProvider(wrapper, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
const providers = moduleRef.providers;
await this.loadInstance(wrapper, providers, moduleRef, contextId, inquirer);
await this.loadEnhancersPerContext(wrapper, contextId, wrapper);
}
loadPrototype({ name }, collection, contextId = constants_2.STATIC_CONTEXT) {
if (!collection) {
return;
}
const target = collection.get(name);
const instance = target.createPrototype(contextId);
if (instance) {
const wrapper = new instance_wrapper_1.InstanceWrapper(Object.assign(Object.assign({}, target), { instance }));
collection.set(name, wrapper);
}
}
applyDoneHook(wrapper) {
let done;
wrapper.donePromise = new Promise((resolve, reject) => {
done = resolve;
});
wrapper.isPending = true;
return done;
}
async loadInstance(wrapper, collection, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer) {
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = wrapper.getInstanceByContextId(contextId, inquirerId);
if (instanceHost.isPending) {
return instanceHost.donePromise;
}
const done = this.applyDoneHook(instanceHost);
const { name, inject } = wrapper;
const targetWrapper = collection.get(name);
if (shared_utils_1.isUndefined(targetWrapper)) {
throw new runtime_exception_1.RuntimeException();
}
if (instanceHost.isResolved) {
return done();
}
const callback = async (instances) => {
const properties = await this.resolveProperties(wrapper, moduleRef, inject, contextId, wrapper, inquirer);
const instance = await this.instantiateClass(instances, wrapper, targetWrapper, contextId, inquirer);
this.applyProperties(instance, properties);
done();
};
await this.resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId, wrapper, inquirer);
}
async resolveConstructorParams(wrapper, moduleRef, inject, callback, contextId = constants_2.STATIC_CONTEXT, inquirer, parentInquirer) {
const inquirerId = this.getInquirerId(inquirer);
const metadata = wrapper.getCtorMetadata();
if (metadata && contextId !== constants_2.STATIC_CONTEXT) {
const deps = await this.loadCtorMetadata(metadata, contextId, inquirer, parentInquirer);
return callback(deps);
}
const dependencies = shared_utils_1.isNil(inject)
? this.reflectConstructorParams(wrapper.metatype)
: inject;
const optionalDependenciesIds = shared_utils_1.isNil(inject)
? this.reflectOptionalParams(wrapper.metatype)
: [];
let isResolved = true;
const resolveParam = async (param, index) => {
try {
if (this.isInquirer(param, parentInquirer)) {
return parentInquirer && parentInquirer.instance;
}
const paramWrapper = await this.resolveSingleParam(wrapper, param, { index, dependencies }, moduleRef, contextId, inquirer, index);
const instanceHost = paramWrapper.getInstanceByContextId(contextId, inquirerId);
if (!instanceHost.isResolved && !paramWrapper.forwardRef) {
isResolved = false;
}
return instanceHost && instanceHost.instance;
}
catch (err) {
const isOptional = optionalDependenciesIds.includes(index);
if (!isOptional) {
throw err;
}
return undefined;
}
};
const instances = await Promise.all(dependencies.map(resolveParam));
isResolved && (await callback(instances));
}
reflectConstructorParams(type) {
const paramtypes = Reflect.getMetadata(constants_1.PARAMTYPES_METADATA, type) || [];
const selfParams = this.reflectSelfParams(type);
selfParams.forEach(({ index, param }) => (paramtypes[index] = param));
return paramtypes;
}
reflectOptionalParams(type) {
return Reflect.getMetadata(constants_1.OPTIONAL_DEPS_METADATA, type) || [];
}
reflectSelfParams(type) {
return Reflect.getMetadata(constants_1.SELF_DECLARED_DEPS_METADATA, type) || [];
}
async resolveSingleParam(wrapper, param, dependencyContext, moduleRef, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
if (shared_utils_1.isUndefined(param)) {
throw new undefined_dependency_exception_1.UndefinedDependencyException(wrapper.name, dependencyContext, moduleRef);
}
const token = this.resolveParamToken(wrapper, param);
return this.resolveComponentInstance(moduleRef, shared_utils_1.isFunction(token) ? token.name : token, dependencyContext, wrapper, contextId, inquirer, keyOrIndex);
}
resolveParamToken(wrapper, param) {
if (!param.forwardRef) {
return param;
}
wrapper.forwardRef = true;
return param.forwardRef();
}
async resolveComponentInstance(moduleRef, name, dependencyContext, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
const providers = moduleRef.providers;
const instanceWrapper = await this.lookupComponent(providers, moduleRef, Object.assign(Object.assign({}, dependencyContext), { name }), wrapper, contextId, inquirer, keyOrIndex);
return this.resolveComponentHost(moduleRef, instanceWrapper, contextId, inquirer);
}
async resolveComponentHost(moduleRef, instanceWrapper, contextId = constants_2.STATIC_CONTEXT, inquirer) {
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = instanceWrapper.getInstanceByContextId(contextId, inquirerId);
if (!instanceHost.isResolved && !instanceWrapper.forwardRef) {
await this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer);
}
else if (!instanceHost.isResolved &&
instanceWrapper.forwardRef &&
(contextId !== constants_2.STATIC_CONTEXT || !!inquirerId)) {
/**
* When circular dependency has been detected between
* either request/transient providers, we have to asynchronously
* resolve instance host for a specific contextId or inquirer, to ensure
* that eventual lazily created instance will be merged with the prototype
* instantiated beforehand.
*/
instanceHost.donePromise &&
instanceHost.donePromise.then(() => this.loadProvider(instanceWrapper, moduleRef, contextId, inquirer));
}
if (instanceWrapper.async) {
const host = instanceWrapper.getInstanceByContextId(contextId, inquirerId);
host.instance = await host.instance;
instanceWrapper.setInstanceByContextId(contextId, host, inquirerId);
}
return instanceWrapper;
}
async lookupComponent(providers, moduleRef, dependencyContext, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
const { name } = dependencyContext;
if (wrapper && wrapper.name === name) {
throw new unknown_dependencies_exception_1.UnknownDependenciesException(wrapper.name, dependencyContext, moduleRef);
}
if (providers.has(name)) {
const instanceWrapper = providers.get(name);
this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapper);
return instanceWrapper;
}
return this.lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId, inquirer, keyOrIndex);
}
async lookupComponentInParentModules(dependencyContext, moduleRef, wrapper, contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex) {
const instanceWrapper = await this.lookupComponentInImports(moduleRef, dependencyContext.name, wrapper, [], contextId, inquirer, keyOrIndex);
if (shared_utils_1.isNil(instanceWrapper)) {
throw new unknown_dependencies_exception_1.UnknownDependenciesException(wrapper.name, dependencyContext, moduleRef);
}
return instanceWrapper;
}
async lookupComponentInImports(moduleRef, name, wrapper, moduleRegistry = [], contextId = constants_2.STATIC_CONTEXT, inquirer, keyOrIndex, isTraversing) {
let instanceWrapperRef = null;
const imports = moduleRef.imports || new Set();
const identity = (item) => item;
let children = [...imports.values()].filter(identity);
if (isTraversing) {
const contextModuleExports = moduleRef.exports;
children = children.filter(child => contextModuleExports.has(child.metatype && child.metatype.name));
}
for (const relatedModule of children) {
if (moduleRegistry.includes(relatedModule.id)) {
continue;
}
moduleRegistry.push(relatedModule.id);
const { providers, exports } = relatedModule;
if (!exports.has(name) || !providers.has(name)) {
const instanceRef = await this.lookupComponentInImports(relatedModule, name, wrapper, moduleRegistry, contextId, inquirer, keyOrIndex, true);
if (instanceRef) {
this.addDependencyMetadata(keyOrIndex, wrapper, instanceRef);
return instanceRef;
}
continue;
}
instanceWrapperRef = providers.get(name);
this.addDependencyMetadata(keyOrIndex, wrapper, instanceWrapperRef);
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = instanceWrapperRef.getInstanceByContextId(contextId, inquirerId);
if (!instanceHost.isResolved && !instanceWrapperRef.forwardRef) {
await this.loadProvider(instanceWrapperRef, relatedModule, contextId, wrapper);
break;
}
}
return instanceWrapperRef;
}
async resolveProperties(wrapper, moduleRef, inject, contextId = constants_2.STATIC_CONTEXT, inquirer, parentInquirer) {
if (!shared_utils_1.isNil(inject)) {
return [];
}
const metadata = wrapper.getPropertiesMetadata();
if (metadata && contextId !== constants_2.STATIC_CONTEXT) {
return this.loadPropertiesMetadata(metadata, contextId, inquirer);
}
const properties = this.reflectProperties(wrapper.metatype);
const instances = await Promise.all(properties.map(async (item) => {
try {
const dependencyContext = {
key: item.key,
name: item.name,
};
if (this.isInquirer(item.name, parentInquirer)) {
return parentInquirer && parentInquirer.instance;
}
const paramWrapper = await this.resolveSingleParam(wrapper, item.name, dependencyContext, moduleRef, contextId, inquirer, item.key);
if (!paramWrapper) {
return undefined;
}
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = paramWrapper.getInstanceByContextId(contextId, inquirerId);
return instanceHost.instance;
}
catch (err) {
if (!item.isOptional) {
throw err;
}
return undefined;
}
}));
return properties.map((item, index) => (Object.assign(Object.assign({}, item), { instance: instances[index] })));
}
reflectProperties(type) {
const properties = Reflect.getMetadata(constants_1.PROPERTY_DEPS_METADATA, type) || [];
const optionalKeys = Reflect.getMetadata(constants_1.OPTIONAL_PROPERTY_DEPS_METADATA, type) || [];
return properties.map((item) => (Object.assign(Object.assign({}, item), { name: item.type, isOptional: optionalKeys.includes(item.key) })));
}
applyProperties(instance, properties) {
if (!shared_utils_1.isObject(instance)) {
return undefined;
}
iterare_1.iterate(properties)
.filter(item => !shared_utils_1.isNil(item.instance))
.forEach(item => (instance[item.key] = item.instance));
}
async instantiateClass(instances, wrapper, targetMetatype, contextId = constants_2.STATIC_CONTEXT, inquirer) {
const { metatype, inject } = wrapper;
const inquirerId = this.getInquirerId(inquirer);
const instanceHost = targetMetatype.getInstanceByContextId(contextId, inquirerId);
const isStatic = wrapper.isStatic(contextId, inquirer);
const isInRequestScope = wrapper.isInRequestScope(contextId, inquirer);
const isLazyTransient = wrapper.isLazyTransient(contextId, inquirer);
const isExplicitlyRequested = wrapper.isExplicitlyRequested(contextId, inquirer);
const isInContext = isStatic || isInRequestScope || isLazyTransient || isExplicitlyRequested;
if (shared_utils_1.isNil(inject) && isInContext) {
instanceHost.instance = wrapper.forwardRef
? Object.assign(instanceHost.instance, new metatype(...instances))
: new metatype(...instances);
}
else if (isInContext) {
const factoryReturnValue = targetMetatype.metatype(...instances);
instanceHost.instance = await factoryReturnValue;
}
instanceHost.isResolved = true;
return instanceHost.instance;
}
async loadPerContext(instance, moduleRef, collection, ctx, wrapper) {
if (!wrapper) {
const ctor = instance.constructor;
wrapper = collection.get(ctor && ctor.name);
}
await this.loadInstance(wrapper, collection, moduleRef, ctx, wrapper);
await this.loadEnhancersPerContext(wrapper, ctx, wrapper);
const host = wrapper.getInstanceByContextId(ctx, wrapper.id);
return host && host.instance;
}
async loadEnhancersPerContext(wrapper, ctx, inquirer) {
const enhancers = wrapper.getEnhancersMetadata() || [];
const loadEnhancer = (item) => {
const hostModule = item.host;
return this.loadInstance(item, hostModule.injectables, hostModule, ctx, inquirer);
};
await Promise.all(enhancers.map(loadEnhancer));
}
async loadCtorMetadata(metadata, contextId, inquirer, parentInquirer) {
const hosts = await Promise.all(metadata.map(async (item) => this.resolveScopedComponentHost(item, contextId, inquirer, parentInquirer)));
const inquirerId = this.getInquirerId(inquirer);
return hosts.map(item => item.getInstanceByContextId(contextId, inquirerId).instance);
}
async loadPropertiesMetadata(metadata, contextId, inquirer) {
const dependenciesHosts = await Promise.all(metadata.map(async ({ wrapper: item, key }) => ({
key,
host: await this.resolveComponentHost(item.host, item, contextId, inquirer),
})));
const inquirerId = this.getInquirerId(inquirer);
return dependenciesHosts.map(({ key, host }) => ({
key,
name: key,
instance: host.getInstanceByContextId(contextId, inquirerId).instance,
}));
}
getInquirerId(inquirer) {
return inquirer && inquirer.id;
}
resolveScopedComponentHost(item, contextId, inquirer, parentInquirer) {
return this.isInquirerRequest(item, parentInquirer)
? parentInquirer
: this.resolveComponentHost(item.host, item, contextId, inquirer);
}
isInquirerRequest(item, parentInquirer) {
return item.isTransient && item.name === inquirer_1.INQUIRER && parentInquirer;
}
isInquirer(param, parentInquirer) {
return param === inquirer_1.INQUIRER && parentInquirer;
}
addDependencyMetadata(keyOrIndex, hostWrapper, instanceWrapper) {
shared_utils_1.isString(keyOrIndex)
? hostWrapper.addPropertiesMetadata(keyOrIndex, instanceWrapper)
: hostWrapper.addCtorMetadata(keyOrIndex, instanceWrapper);
}
}
exports.Injector = Injector;