"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@nestjs/common");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const iterare_1 = require("iterare");
const constants_1 = require("./constants");
const unknown_element_exception_1 = require("./errors/exceptions/unknown-element.exception");
const unknown_module_exception_1 = require("./errors/exceptions/unknown-module.exception");
const helpers_1 = require("./helpers");
const hooks_1 = require("./hooks");
const container_scanner_1 = require("./injector/container-scanner");
const injector_1 = require("./injector/injector");
/**
* @publicApi
*/
class NestApplicationContext {
constructor(container, scope = new Array(), contextModule = null) {
this.container = container;
this.scope = scope;
this.contextModule = contextModule;
this.isInitialized = false;
this.injector = new injector_1.Injector();
this.activeShutdownSignals = new Array();
this.containerScanner = new container_scanner_1.ContainerScanner(container);
}
selectContextModule() {
const modules = this.container.getModules().values();
this.contextModule = modules.next().value;
}
select(moduleType) {
const modules = this.container.getModules();
const moduleMetatype = this.contextModule.metatype;
const scope = this.scope.concat(moduleMetatype);
const moduleTokenFactory = this.container.getModuleTokenFactory();
const token = moduleTokenFactory.create(moduleType);
const selectedModule = modules.get(token);
if (!selectedModule) {
throw new unknown_module_exception_1.UnknownModuleException();
}
return new NestApplicationContext(this.container, scope, selectedModule);
}
get(typeOrToken, options = { strict: false }) {
if (!(options && options.strict)) {
return this.find(typeOrToken);
}
return this.findInstanceByToken(typeOrToken, this.contextModule);
}
resolve(typeOrToken, contextId = helpers_1.createContextId(), options = { strict: false }) {
return this.resolvePerContext(typeOrToken, this.contextModule, contextId, options);
}
/**
* Initalizes the Nest application.
* Calls the Nest lifecycle events.
*
* @returns {Promise<this>} The NestApplicationContext instance as Promise
*/
async init() {
if (this.isInitialized) {
return this;
}
await this.callInitHook();
await this.callBootstrapHook();
this.isInitialized = true;
return this;
}
async close() {
await this.callDestroyHook();
await this.callBeforeShutdownHook();
await this.dispose();
await this.callShutdownHook();
await this.unsubscribeFromProcessSignals();
}
useLogger(logger) {
common_1.Logger.overrideLogger(logger);
}
/**
* Enables the usage of shutdown hooks. Will call the
* `onApplicationShutdown` function of a provider if the
* process receives a shutdown signal.
*
* @param {ShutdownSignal[]} [signals=[]] The system signals it should listen to
*
* @returns {this} The Nest application context instance
*/
enableShutdownHooks(signals = []) {
if (shared_utils_1.isEmpty(signals)) {
signals = Object.keys(common_1.ShutdownSignal).map((key) => common_1.ShutdownSignal[key]);
}
else {
// given signals array should be unique because
// process shouldn't listen to the same signal more than once.
signals = Array.from(new Set(signals));
}
signals = iterare_1.iterate(signals)
.map((signal) => signal.toString().toUpperCase().trim())
// filter out the signals which is already listening to
.filter(signal => !this.activeShutdownSignals.includes(signal))
.toArray();
this.listenToShutdownSignals(signals);
return this;
}
async dispose() {
// Nest application context has no server
// to dispose, therefore just call a noop
return Promise.resolve();
}
/**
* Listens to shutdown signals by listening to
* process events
*
* @param {string[]} signals The system signals it should listen to
*/
listenToShutdownSignals(signals) {
const cleanup = async (signal) => {
try {
signals.forEach(sig => process.removeListener(sig, cleanup));
await this.callDestroyHook();
await this.callBeforeShutdownHook(signal);
await this.dispose();
await this.callShutdownHook(signal);
process.kill(process.pid, signal);
}
catch (err) {
common_1.Logger.error(constants_1.MESSAGES.ERROR_DURING_SHUTDOWN, err.stack, NestApplicationContext.name);
process.exit(1);
}
};
this.shutdownCleanupRef = cleanup;
signals.forEach((signal) => {
this.activeShutdownSignals.push(signal);
process.on(signal, cleanup);
});
}
/**
* Unsubscribes from shutdown signals (process events)
*/
unsubscribeFromProcessSignals() {
if (!this.shutdownCleanupRef) {
return;
}
this.activeShutdownSignals.forEach(signal => {
process.removeListener(signal, this.shutdownCleanupRef);
});
}
/**
* Calls the `onModuleInit` function on the registered
* modules and its children.
*/
async callInitHook() {
const modulesContainer = this.container.getModules();
for (const module of [...modulesContainer.values()].reverse()) {
await hooks_1.callModuleInitHook(module);
}
}
/**
* Calls the `onModuleDestroy` function on the registered
* modules and its children.
*/
async callDestroyHook() {
const modulesContainer = this.container.getModules();
for (const module of modulesContainer.values()) {
await hooks_1.callModuleDestroyHook(module);
}
}
/**
* Calls the `onApplicationBootstrap` function on the registered
* modules and its children.
*/
async callBootstrapHook() {
const modulesContainer = this.container.getModules();
for (const module of [...modulesContainer.values()].reverse()) {
await hooks_1.callModuleBootstrapHook(module);
}
}
/**
* Calls the `onApplicationShutdown` function on the registered
* modules and children.
*/
async callShutdownHook(signal) {
const modulesContainer = this.container.getModules();
for (const module of [...modulesContainer.values()].reverse()) {
await hooks_1.callAppShutdownHook(module, signal);
}
}
/**
* Calls the `beforeApplicationShutdown` function on the registered
* modules and children.
*/
async callBeforeShutdownHook(signal) {
const modulesContainer = this.container.getModules();
for (const module of [...modulesContainer.values()].reverse()) {
await hooks_1.callBeforeAppShutdownHook(module, signal);
}
}
find(typeOrToken) {
return this.containerScanner.find(typeOrToken);
}
findInstanceByToken(metatypeOrToken, contextModule) {
return this.containerScanner.findInstanceByToken(metatypeOrToken, contextModule);
}
async resolvePerContext(typeOrToken, contextModule, contextId, options) {
let wrapper, collection;
const isStrictModeEnabled = options && options.strict;
if (!isStrictModeEnabled) {
[wrapper, collection] = this.containerScanner.getWrapperCollectionPair(typeOrToken);
}
else {
[
wrapper,
collection,
] = this.containerScanner.getWrapperCollectionPairByHost(typeOrToken, contextModule);
}
const ctorHost = wrapper.instance
? wrapper.instance
: { constructor: typeOrToken };
const instance = await this.injector.loadPerContext(ctorHost, wrapper.host, collection, contextId);
if (!instance) {
throw new unknown_element_exception_1.UnknownElementException();
}
return instance;
}
}
exports.NestApplicationContext = NestApplicationContext;