@hapiness/core
Version:
Project to have a HapiJS (https://hapijs.com/) based framework to create easier NodeJS back-end with some awesome features
255 lines • 8.99 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const rxjs_1 = require("rxjs");
const logger_1 = require("./logger");
const enums_1 = require("./enums");
const module_1 = require("./module");
const hook_1 = require("./hook");
const shutdown_1 = require("./shutdown");
function extensionError(error, name) {
error.message = `[${name}] ${error.message}`;
return error;
}
class Hapiness {
/**
* Entrypoint to bootstrap a module
* will load the extentions and trigger
* module's hooks
*
* @param {Type<any>} module
* @param {Array<Type<any>|ExtensionWithConfig>} extensions?
* @param {BootstrapOptions} options?
* @returns Promise
*/
static bootstrap(module, extensions, options = {}) {
if (options.shutdown !== false) {
this.handleShutdownSignals();
}
return new Promise((resolve, reject) => {
this
.checkArg(module)
.flatMap(_ => module_1.ModuleManager.resolve(_))
.flatMap(_ => this.loadExtensions(extensions, _, options))
.ignoreElements()
.subscribe(null, _ => {
this.logger.debug(`bootstrap error caught [${_.message}]`);
this
.shutdown()
.subscribe(() => reject(_), err => {
this.logger.debug(`bootstrap error caught [${err.message}], shutting down extensions...`);
reject(err);
process.exit(1);
});
}, () => resolve());
});
}
/**
* Force a shutdown
*
* @returns Observable
*/
static shutdown() {
return this
.getShutdownHooks()
.flatMap(_ => this
.shutdownUtils
.shutdown(_));
}
static handleShutdownSignals() {
this
.shutdownUtils
.events$
.flatMap(_ => this.shutdown())
.subscribe(_ => {
this.logger.debug('process shutdown triggered');
process.exit(0);
}, _ => {
errorHandler(_);
process.exit(1);
});
}
/**
* Retrieve all shutdown hooks
*
* @returns ExtensionShutdown[]
*/
static getShutdownHooks() {
return rxjs_1.Observable
.from([].concat(this.extensions).filter(e => !!e))
.filter(_ => !!_ && hook_1.HookManager
.hasLifecycleHook(enums_1.ExtentionHooksEnum.OnShutdown.toString(), _.token))
.flatMap(_ => hook_1.HookManager
.triggerHook(enums_1.ExtentionHooksEnum.OnShutdown.toString(), _.token, _.instance, [module, _.value]))
.toArray();
}
/**
* Load extensions
*
* @param {Array<Type<any>|ExtensionWithConfig>} extensions
* @param {CoreModule} moduleResolved
* @param {BootstrapOptions} options?
* @returns Observable
*/
static loadExtensions(extensions, moduleResolved, options) {
return rxjs_1.Observable
.from([].concat(extensions).filter(_ => !!_))
.map(_ => this.toExtensionWithConfig(_))
.concatMap(_ => this
.loadExtention(_, moduleResolved, options)
.catch(err => rxjs_1.Observable.throw(extensionError(err, _.token.name))))
.toArray()
.flatMap(_ => this.instantiateModule(_, moduleResolved, options));
}
/**
* Instantiate module
*
* @param {Extension[]} extensionsLoaded
* @param {CoreModule} moduleResolved
* @returns Observable
*/
static instantiateModule(extensionsLoaded, moduleResolved, options) {
return rxjs_1.Observable
.from(extensionsLoaded)
.map(_ => ({ provide: _.token, useValue: _.value }))
.toArray()
.flatMap(_ => module_1.ModuleManager.instantiate(moduleResolved, _))
.flatMap(_ => this.callRegister(_))
.flatMap(moduleInstantiated => rxjs_1.Observable
.from(extensionsLoaded)
.flatMap(_ => this
.moduleInstantiated(_, moduleInstantiated)
.timeout(options.extensionTimeout || this.defaultTimeout)
.catch(err => rxjs_1.Observable.throw(extensionError(err, _.token.name))))
.toArray()
.map(_ => moduleInstantiated))
.do(_ => this.module = _)
.flatMap(_ => this.callStart(_));
}
/**
* Call Register Hooks
*
* @param {CoreModule} moduleInstantiated
* @returns Observable
*/
static callRegister(moduleInstantiated) {
return rxjs_1.Observable
.from(module_1.ModuleManager.getModules(moduleInstantiated))
.filter(_ => _.level !== enums_1.ModuleLevel.ROOT)
.filter(_ => hook_1.HookManager
.hasLifecycleHook(enums_1.ModuleEnum.OnRegister.toString(), _.token))
.flatMap(_ => hook_1.HookManager
.triggerHook(enums_1.ModuleEnum.OnRegister.toString(), _.token, _.instance))
.toArray()
.map(_ => moduleInstantiated);
}
/**
* Call Start Hooks
*
* @param {CoreModule} moduleInstantiated
* @returns Observable
*/
static callStart(moduleInstantiated) {
return rxjs_1.Observable
.of(moduleInstantiated)
.flatMap(_ => hook_1.HookManager
.triggerHook(enums_1.ModuleEnum.OnStart.toString(), moduleInstantiated.token, moduleInstantiated.instance, null, false));
}
/**
* Check if the provided module
* is right
*
* @param {Type<any>} module
* @returns Observable
*/
static checkArg(module) {
return rxjs_1.Observable
.of(module)
.do(_ => this.module = null)
.do(_ => this.extensions = null)
.flatMap(_ => !!_ ?
rxjs_1.Observable.of(_) :
rxjs_1.Observable.throw(new Error('Bootstrap failed: no module provided')))
.flatMap(_ => typeof _ === 'function' ?
rxjs_1.Observable.of(_) :
rxjs_1.Observable.throw(new Error('Bootstrap failed: module must be a function/class')));
}
/**
* Convert an extension type to ExtensionWithConfig
*
* @param {} extension
* @returns ExtensionWithConfig
*/
static toExtensionWithConfig(extension) {
if (extension && extension.token) {
return extension;
}
return {
token: extension,
config: {}
};
}
/**
* Call the OnExtensionLoad hook
* of an extension
*
* @param {ExtensionWithConfig} extension
* @returns Observable
*/
static loadExtention(extension, module, options) {
return rxjs_1.Observable
.of(Reflect.construct(extension.token, []))
.do(_ => this.logger.debug(`loading ${extension.token.name}`))
.switchMap(instance => hook_1.HookManager
.triggerHook(enums_1.ExtentionHooksEnum.OnExtensionLoad.toString(), extension.token, instance, [module, extension.config])
.timeout(options.extensionTimeout || this.defaultTimeout)
.catch(_ => {
if (process.env.NODE_ENV !== 'test') {
setTimeout(() => process.exit(1), 1000);
}
return rxjs_1.Observable.throw(_);
}))
.do(_ => this.extensions = []
.concat(this.extensions, _)
.filter(__ => !!__));
}
/**
* Call the OnModuleInstantiated hook
* of an extension
*
* @param {Extension} extension
* @returns Observable
*/
static moduleInstantiated(extension, module) {
return hook_1.HookManager
.triggerHook(enums_1.ExtentionHooksEnum.OnModuleInstantiated.toString(), extension.token, extension.instance, [module, extension.value])
.do(_ => this.logger.debug(`moduleInstantiated ${extension.token.name}`))
.defaultIfEmpty(null);
}
}
Hapiness.logger = new logger_1.InternalLogger('bootstrap');
Hapiness.shutdownUtils = new shutdown_1.ShutdownUtils();
Hapiness.defaultTimeout = 5000;
exports.Hapiness = Hapiness;
/**
* Error handler
* Call onError of Root module
* Or log in console
*
* @param {Error} error
* @param {any} data
* @returns void
*/
function errorHandler(error, data) {
rxjs_1.Observable
.of(Hapiness['module'])
.filter(_ => !!(_ && _.instance))
.flatMap(_ => hook_1.HookManager
.hasLifecycleHook(enums_1.ModuleEnum.OnError.toString(), _.token) ?
hook_1.HookManager
.triggerHook(enums_1.ModuleEnum.OnError.toString(), _.token, _.instance, [error, data], false) :
rxjs_1.Observable
.throw(error))
.subscribe(null, _ => console.error(_));
}
exports.errorHandler = errorHandler;
//# sourceMappingURL=hapiness.js.map