UNPKG

inceptum

Version:

hipages take on the foundational library for enterprise-grade apps written in NodeJS

434 lines 18.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const LogManager_1 = require("../../log/LogManager"); const PromiseUtil_1 = require("../../util/PromiseUtil"); const IoCException_1 = require("../IoCException"); const Lifecycle_1 = require("../Lifecycle"); const SingletonDefinition_1 = require("./SingletonDefinition"); class BaseSingletonState extends Lifecycle_1.LifecycleState { } BaseSingletonState.INSTANTIATING = new BaseSingletonState('INSTANTIATING', 100); BaseSingletonState.INSTANTIATED = new BaseSingletonState('INSTANTIATED', 200); BaseSingletonState.PROPERTIES_SET = new BaseSingletonState('PROPERTIES_SET', 400); exports.BaseSingletonState = BaseSingletonState; var ParamType; (function (ParamType) { ParamType[ParamType["Value"] = 0] = "Value"; ParamType[ParamType["Reference"] = 1] = "Reference"; ParamType[ParamType["Type"] = 2] = "Type"; ParamType[ParamType["TypeArray"] = 3] = "TypeArray"; ParamType[ParamType["Config"] = 4] = "Config"; ParamType[ParamType["Group"] = 5] = "Group"; ParamType[ParamType["DefinitionGroup"] = 6] = "DefinitionGroup"; })(ParamType = exports.ParamType || (exports.ParamType = {})); class ParamDefinition { static withValue(val) { const resp = new ParamDefinition(ParamType.Value); resp.val = val; return resp; } static withConfigKey(key, defaultValue) { const resp = new ParamDefinition(ParamType.Config); resp.key = key; resp.defaultValue = defaultValue; return resp; } static withGroupName(groupName) { const resp = new ParamDefinition(ParamType.Group); resp.group = groupName; return resp; } static withDefinitionGroupName(groupName) { const resp = new ParamDefinition(ParamType.DefinitionGroup); resp.group = groupName; return resp; } static withRefName(refName) { const resp = new ParamDefinition(ParamType.Reference); resp.refName = refName; return resp; } static withClassName(className) { const resp = new ParamDefinition(ParamType.Type); resp.className = className; return resp; } static withClassNameArr(className) { const resp = new ParamDefinition(ParamType.TypeArray); resp.className = className; return resp; } constructor(type) { this.type = type; } } exports.ParamDefinition = ParamDefinition; class CallDefinition { constructor(paramName, paramDefinition) { this.args = []; this.paramName = paramName; this.args.push(paramDefinition); } } exports.CallDefinition = CallDefinition; class ConfigurableSingletonDefinition extends SingletonDefinition_1.SingletonDefinition { constructor(clazz, name, logger) { super(clazz, name, logger); this.constructorArgDefinitions = []; this.propertiesToSetDefinitions = []; this.startFunctionName = null; this.shutdownFunctionName = null; } getConstructorArgDefinitions() { return this.constructorArgDefinitions; } getPropertiesToSetDefinitions() { return this.propertiesToSetDefinitions; } getStartFunctionName() { return this.startFunctionName; } getShutdownFunctionName() { return this.shutdownFunctionName; } constructorParamByValue(value) { this.assertState(BaseSingletonState.NOT_STARTED); this.constructorArgDefinitions.push(ParamDefinition.withValue(value)); return this; } constructorParamByConfig(key, defaultValue) { this.assertState(BaseSingletonState.NOT_STARTED); this.constructorArgDefinitions.push(ParamDefinition.withConfigKey(key, defaultValue)); return this; } constructorParamByRef(name) { this.assertState(BaseSingletonState.NOT_STARTED); this.constructorArgDefinitions.push(ParamDefinition.withRefName(name)); return this; } constructorParamByType(clazz) { this.assertState(BaseSingletonState.NOT_STARTED); this.constructorArgDefinitions.push(ParamDefinition.withClassName(clazz)); return this; } constructorParamByTypeArray(clazz) { this.assertState(BaseSingletonState.NOT_STARTED); this.constructorArgDefinitions.push(ParamDefinition.withClassNameArr(clazz)); return this; } setPropertyByValue(paramName, value) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withValue(value))); return this; } setPropertyByConfig(paramName, key, defaultValue) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withConfigKey(key, defaultValue))); return this; } setPropertyByGroup(paramName, groupName) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withGroupName(groupName))); return this; } setPropertyByDefinitionGroup(paramName, groupName) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withDefinitionGroupName(groupName))); return this; } setPropertyByRef(paramName, name) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withRefName(name))); return this; } setPropertyByType(paramName, className) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withClassName((typeof className === 'function' && className.name) ? className.name : className))); return this; } setPropertyByTypeArray(paramName, className) { this.assertState(BaseSingletonState.NOT_STARTED); this.propertiesToSetDefinitions.push(new CallDefinition(paramName, ParamDefinition.withClassNameArr((typeof className === 'function' && className.name) ? className.name : className))); return this; } startFunction(startFunctionName) { this.assertState(BaseSingletonState.NOT_STARTED); if (Object.prototype.hasOwnProperty.call(this.clazz.prototype, startFunctionName)) { this.startFunctionName = startFunctionName; } else { throw new IoCException_1.IoCException(`Can't find function named ${startFunctionName} on class ${this.clazz.name}`); } return this; } stopFunction(shutdownFunctionName) { this.assertState(BaseSingletonState.NOT_STARTED); if (Object.prototype.hasOwnProperty.call(this.clazz.prototype, shutdownFunctionName)) { this.shutdownFunctionName = shutdownFunctionName; } else { throw new IoCException_1.IoCException(`Can't find function named ${shutdownFunctionName} on class ${this.clazz.name}`); } return this; } copyInternalProperties(copyTo) { super.copyInternalProperties(copyTo); copyTo.constructorArgDefinitions = this.constructorArgDefinitions.slice(0); copyTo.propertiesToSetDefinitions = this.propertiesToSetDefinitions.slice(0); copyTo.startFunctionName = this.startFunctionName; copyTo.shutdownFunctionName = this.shutdownFunctionName; } } exports.ConfigurableSingletonDefinition = ConfigurableSingletonDefinition; class BaseSingletonDefinition extends ConfigurableSingletonDefinition { // private static getMaxStateInstance(def, trace, postLoad: Set<ObjectDefinition<any>>) { // // console.log(`In ${this.getName()} getting max instance of ${def.getName()} - ${def.status} - ${BaseSingletonState.STARTED} / trace: [${trace}]`); // if ((trace.indexOf(def.getName()) >= 0) || (def.status < BaseSingletonState.STARTED)) { // // Circular reference, let's try to go for one that is just instantiated and finalise init afterwards // postLoad.add(def); // // console.log(`Getting ${def.getName()} as Instantiated`); // return def.getInstanceAtState(BaseSingletonState.INSTANTIATED, trace.concat([def.getName()]), postLoad); // } // // console.log(`Getting ${def.getName()} as ready`); // return def.getInstanceWithTrace(trace.concat([def.getName()]), postLoad); // } constructor(clazz, name, logger) { super(clazz, name, logger || LogManager_1.LogManager.getLogger(__filename)); } // ************************************ // Get instance methods // ************************************ getInstance() { const self = this; if (this.instancePromise) { return this.instancePromise; } try { this.checkConstructorCircularDependency(); // No circular dependencies in the constructor this.instancePromise = this.instantiate(); // At this point our promise will at least instantiate the object. const wiredInstancePromise = this.instancePromise .then(() => self.setAllProperties()) .then(() => self.lcStart()) .then(() => { // Once we've set the properties self.instancePromise = wiredInstancePromise; return self.instance; }); // We should check now if there are circular dependencies on properties if (!this.hasPropertiesCircularDependency()) { // As we have no Circular Dependencies in setting the properties we can make our dependants wait for our complete initialisation this.instancePromise = wiredInstancePromise; } return this.instancePromise; } catch (e) { this.instancePromise = Promise.reject(e); return this.instancePromise; } } // ************************************ // Lifecycle methods // ************************************ hasPropertiesCircularDependency(trace = []) { if (trace.indexOf(this.name) >= 0) { trace.push(this.name); this.logger.debug(`Circular dependency detected on property injection: ${trace.join(' -> ')}`); return true; } const newTrace = trace.concat([this.name]); const toCheck = [] .concat(this.getAllPropertiesObjectDefinitions()) .concat(this.getAllConstructorObjectDefinitions()); return toCheck.some((element) => { if (element instanceof BaseSingletonDefinition) { if (element.hasPropertiesCircularDependency(newTrace)) { return true; } } return false; }); } checkConstructorCircularDependency(trace = []) { if (trace.indexOf(this.name) >= 0) { trace.push(this.name); throw new Error(`Circular dependency detected: ${trace.join(' -> ')}`); } const newTrace = trace.concat([this.name]); this.getAllConstructorObjectDefinitions().forEach((element) => { if (element instanceof BaseSingletonDefinition) { element.checkConstructorCircularDependency(newTrace); } }); } getAllConstructorObjectDefinitions() { return this.getConstructorArgDefinitions().reduce((prev, current) => { const defs = this.resolveParamObjectDefinition(current); if (defs && defs.length > 0) { return prev.concat(defs); } return prev; }, []); } getAllPropertiesObjectDefinitions() { return this.getPropertiesToSetDefinitions().reduce((prev, current) => { const allDefs = current.args.reduce((prev2, currentParamDef) => { const defs = this.resolveParamObjectDefinition(currentParamDef); if (defs && defs.length > 0) { return prev2.concat(defs); } return prev2; }, []); if (allDefs && allDefs.length > 0) { return prev.concat(allDefs); } return prev; }, []); } getAllPropertyObjectDefinitions() { return this.getPropertiesToSetDefinitions().reduce((prev, current) => { const argu = current.args; if (!argu || argu.length <= 0) { return prev; } const defs = []; argu.forEach((c) => { const od = this.resolveParamObjectDefinition(c); if (od) { defs.push(od); } }); if (defs && defs.length > 0) { return prev.concat(defs); } return prev; }, []); } resolveParamObjectDefinition(paramDefinition) { if (paramDefinition.objectDefinitions) { return paramDefinition.objectDefinitions; // It's already resolved. Move on } switch (paramDefinition.type) { case ParamType.Value: case ParamType.Config: paramDefinition.objectDefinitions = []; break; case ParamType.Reference: paramDefinition.objectDefinitions = [this.context.getDefinitionByName(paramDefinition.refName)]; break; case ParamType.Type: paramDefinition.objectDefinitions = [this.context.getDefinitionByType(paramDefinition.className)]; break; case ParamType.TypeArray: paramDefinition.objectDefinitions = this.context.getDefinitionsByType(paramDefinition.className); break; case ParamType.Group: case ParamType.DefinitionGroup: paramDefinition.objectDefinitions = this.context.getGroupObjectNames(paramDefinition.group).map((refName) => this.context.getDefinitionByName(refName)); break; default: throw new IoCException_1.IoCException(`Unknown argument type ${paramDefinition.type} on bean ${this.name}`); } return paramDefinition.objectDefinitions; } instantiate() { if (!this.context) { return Promise.reject(new IoCException_1.IoCException(`ObjectDefinition ${this.getName()} hasn't been added to a context, Can't instantiate`)); } this.setStatus(BaseSingletonState.INSTANTIATING); return PromiseUtil_1.PromiseUtil.map(this.getConstructorArgDefinitions(), (argDefinition) => this.getParamDefinitionValue(argDefinition)) .then((constructorArgs) => { this.instance = Reflect.construct(this.clazz, constructorArgs); this.setStatus(BaseSingletonState.INSTANTIATED); return this.instance; }); } setAllProperties() { return PromiseUtil_1.PromiseUtil.map(this.getPropertiesToSetDefinitions(), (propertyToSet) => this.getSetPropertyPromise(propertyToSet)); } getSetPropertyPromise(propertyToSet) { return PromiseUtil_1.PromiseUtil.map(propertyToSet.args, (argDefinition) => this.getParamDefinitionValue(argDefinition)) .then((propertyParams) => { this.instance[propertyToSet.paramName] = propertyParams[0]; // TODO: Make use of setters if available. }); } getParamDefinitionValue(paramDefinition) { switch (paramDefinition.type) { case ParamType.Value: return Promise.resolve(paramDefinition.val); case ParamType.Config: const configValue = this.context.getConfig(paramDefinition.key); return Promise.resolve(configValue !== undefined ? configValue : paramDefinition.defaultValue); case ParamType.DefinitionGroup: return Promise.resolve(paramDefinition.objectDefinitions); default: const prom = PromiseUtil_1.PromiseUtil.map(paramDefinition.objectDefinitions, (od) => od.getInstance()); switch (paramDefinition.type) { case ParamType.Reference: case ParamType.Type: return prom.then((constArgs) => constArgs[0]); } return prom; } } doStart() { if (this.getStartFunctionName()) { const resp = this.clazz.prototype[this.getStartFunctionName()].call(this.instance); if (resp && resp.then) { return resp; } return Promise.resolve(); } return Promise.resolve(); } doStop() { if (this.getShutdownFunctionName()) { const resp = this.clazz.prototype[this.getShutdownFunctionName()].call(this.instance); if (resp && resp.then) { return resp; } return Promise.resolve(); } return Promise.resolve(); } // protected resolveArgs(constructorArgs): Promise<any[]> { // return PromiseUtil.map(constructorArgs, (arg) => { // switch (arg.type) { // case ParamType.Value: // return arg.val; // case ParamType.Config: // return this.context.getConfig(arg.key); // case ParamType.Reference: // return BaseSingletonDefinition.getMaxStateInstance( // this.context.getDefinitionByName(arg.refName), // trace, // postLoad); // case ParamType.Type: // return BaseSingletonDefinition.getMaxStateInstance( // this.context.getDefinitionByType(arg.className), // trace, // postLoad); // case ParamType.TypeArray: // return Promise.all( // this.context.getDefinitionsByType(arg.className).map( // (d) => BaseSingletonDefinition.getMaxStateInstance(d, trace, postLoad))); // default: // return Promise.reject(new IoCException(`Unknown argument type ${arg.type} on bean ${this.name}`)); // } // }); // } getCopyInstance() { return new BaseSingletonDefinition(this.clazz, this.name, this.logger); } } exports.BaseSingletonDefinition = BaseSingletonDefinition; class BaseSingletonDefinitionTestUtil extends BaseSingletonDefinition { exposeGetAllConstructorObjectDefinitions() { return this.getAllConstructorObjectDefinitions(); } } exports.BaseSingletonDefinitionTestUtil = BaseSingletonDefinitionTestUtil; //# sourceMappingURL=BaseSingletonDefinition.js.map