inversify
Version:
A powerful and lightweight inversion of control container for JavaScript and Node.js apps powered by TypeScript.
216 lines (215 loc) • 10.8 kB
JavaScript
;
var plan_1 = require("./plan");
var context_1 = require("./context");
var request_1 = require("./request");
var target_1 = require("./target");
var METADATA_KEY = require("../constants/metadata_keys");
var ERROR_MSGS = require("../constants/error_msgs");
var binding_type_1 = require("../bindings/binding_type");
var utils_1 = require("../utils/utils");
var target_type_1 = require("./target_type");
var Planner = (function () {
function Planner() {
}
Planner.prototype.createContext = function (kernel) {
return new context_1.default(kernel);
};
Planner.prototype.createPlan = function (context, binding, target) {
var _this = this;
var rootRequest = new request_1.default(binding.serviceIdentifier, context, null, binding, target);
var plan = new plan_1.default(context, rootRequest);
context.addPlan(plan);
if (binding.type === binding_type_1.default.Instance) {
var dependencies = this._getDependencies(binding.implementationType);
dependencies.forEach(function (dependency) { _this._createSubRequest(rootRequest, dependency); });
}
return plan;
};
Planner.prototype.getBindings = function (kernel, serviceIdentifier) {
var bindings = [];
var _kernel = kernel;
var _bindingDictionary = _kernel._bindingDictionary;
if (_bindingDictionary.hasKey(serviceIdentifier)) {
bindings = _bindingDictionary.get(serviceIdentifier);
}
else if (_kernel._parentKernel !== undefined) {
bindings = this.getBindings(_kernel._parentKernel, serviceIdentifier);
}
return bindings;
};
Planner.prototype.getActiveBindings = function (parentRequest, target) {
var bindings = this.getBindings(parentRequest.parentContext.kernel, target.serviceIdentifier);
var activeBindings = [];
if (bindings.length > 1 && target.isArray() === false) {
activeBindings = bindings.filter(function (binding) {
var request = new request_1.default(binding.serviceIdentifier, parentRequest.parentContext, parentRequest, binding, target);
return binding.constraint(request);
});
}
else {
activeBindings = bindings;
}
return activeBindings;
};
Planner.prototype._createSubRequest = function (parentRequest, target) {
try {
var activeBindings = this.getActiveBindings(parentRequest, target);
if (activeBindings.length === 0) {
var serviceIdentifier = parentRequest.parentContext.kernel.getServiceIdentifierAsString(target.serviceIdentifier);
throw new Error(ERROR_MSGS.NOT_REGISTERED + " " + serviceIdentifier);
}
else if (activeBindings.length > 1 && target.isArray() === false) {
var serviceIdentifier = parentRequest.parentContext.kernel.getServiceIdentifierAsString(target.serviceIdentifier);
throw new Error(ERROR_MSGS.AMBIGUOUS_MATCH + " " + serviceIdentifier);
}
else {
this._createChildRequest(parentRequest, target, activeBindings);
}
}
catch (error) {
if (error instanceof RangeError) {
this._throwWhenCircularDependenciesFound(parentRequest.parentContext.plan.rootRequest);
}
else {
throw new Error(error.message);
}
}
};
Planner.prototype._createChildRequest = function (parentRequest, target, bindings) {
var _this = this;
var childRequest = parentRequest.addChildRequest(target.serviceIdentifier, bindings, target);
var subChildRequest = childRequest;
bindings.forEach(function (binding) {
if (target.isArray()) {
subChildRequest = childRequest.addChildRequest(binding.serviceIdentifier, binding, target);
}
if (binding.type === binding_type_1.default.Instance) {
var subDependencies = _this._getDependencies(binding.implementationType);
subDependencies.forEach(function (d, index) {
_this._createSubRequest(subChildRequest, d);
});
}
});
};
Planner.prototype._throwWhenCircularDependenciesFound = function (request, previousServiceIdentifiers) {
var _this = this;
if (previousServiceIdentifiers === void 0) { previousServiceIdentifiers = []; }
var parentServiceIdentifier = request.parentContext.kernel.getServiceIdentifierAsString(request.serviceIdentifier);
previousServiceIdentifiers.push(parentServiceIdentifier);
request.childRequests.forEach(function (childRequest) {
var childServiceIdentifier = request.parentContext.kernel.getServiceIdentifierAsString(childRequest.serviceIdentifier);
if (previousServiceIdentifiers.indexOf(childServiceIdentifier) === -1) {
if (childRequest.childRequests.length > 0) {
_this._throwWhenCircularDependenciesFound(childRequest, previousServiceIdentifiers);
}
else {
previousServiceIdentifiers.push(childServiceIdentifier);
}
}
else {
previousServiceIdentifiers.push(childServiceIdentifier);
var services = previousServiceIdentifiers.reduce(function (prev, curr) {
return (prev !== "") ? prev + " -> " + curr : "" + curr;
}, "");
throw new Error(ERROR_MSGS.CIRCULAR_DEPENDENCY + " " + services);
}
});
};
Planner.prototype._formatTargetMetadata = function (targetMetadata) {
var targetMetadataMap = {};
targetMetadata.forEach(function (m) {
targetMetadataMap[m.key.toString()] = m.value;
});
return {
inject: targetMetadataMap[METADATA_KEY.INJECT_TAG],
multiInject: targetMetadataMap[METADATA_KEY.MULTI_INJECT_TAG],
targetName: targetMetadataMap[METADATA_KEY.NAME_TAG],
unmanaged: targetMetadataMap[METADATA_KEY.UNMANAGED_TAG]
};
};
Planner.prototype._getTargets = function (func, isBaseClass) {
var constructorName = utils_1.getFunctionName(func);
var serviceIdentifiers = Reflect.getMetadata(METADATA_KEY.PARAM_TYPES, func);
if (serviceIdentifiers === undefined) {
var msg = ERROR_MSGS.MISSING_INJECTABLE_ANNOTATION + " " + constructorName + ".";
throw new Error(msg);
}
var constructorArgsMetadata = Reflect.getMetadata(METADATA_KEY.TAGGED, func) || [];
var targets = (this._constructorArgsTargets(isBaseClass, constructorName, serviceIdentifiers, constructorArgsMetadata, func.length)).concat((this._getClassPropsTargets(func)));
return targets;
};
Planner.prototype._constructorArgsTargets = function (isBaseClass, constructorName, serviceIdentifiers, constructorArgsMetadata, constructorLength) {
var targets = [];
for (var i = 0; i < constructorLength; i++) {
var targetMetadata = constructorArgsMetadata[i.toString()] || [];
var metadata = this._formatTargetMetadata(targetMetadata);
var serviceIndentifier = serviceIdentifiers[i];
serviceIndentifier = (metadata.inject || metadata.multiInject) ? (metadata.inject || metadata.multiInject) : serviceIndentifier;
var isUnknownType = (serviceIndentifier === Object || serviceIndentifier === Function || serviceIndentifier === undefined);
if (isBaseClass === false && isUnknownType) {
var msg = ERROR_MSGS.MISSING_INJECT_ANNOTATION + " argument " + i + " in class " + constructorName + ".";
throw new Error(msg);
}
var target = new target_1.default(target_type_1.default.ConstructorArgument, metadata.targetName, serviceIndentifier);
target.metadata = targetMetadata;
targets.push(target);
}
return targets;
};
Planner.prototype._getClassPropsTargets = function (func) {
var classPropsMetadata = Reflect.getMetadata(METADATA_KEY.TAGGED_PROP, func) || [];
var targets = [];
var keys = Object.keys(classPropsMetadata);
for (var i = 0; i < keys.length; i++) {
var key = keys[i];
var targetMetadata = classPropsMetadata[key];
var metadata = this._formatTargetMetadata(classPropsMetadata[key]);
var targetName = metadata.targetName || key;
var serviceIndentifier = (metadata.inject || metadata.multiInject);
var target = new target_1.default(target_type_1.default.ClassProperty, targetName, serviceIndentifier);
target.metadata = targetMetadata;
targets.push(target);
}
var baseConstructor = Object.getPrototypeOf(func.prototype).constructor;
if (baseConstructor !== Object) {
var baseTargets = this._getClassPropsTargets(baseConstructor);
targets = targets.concat(baseTargets);
}
return targets;
};
Planner.prototype._getDependencies = function (func) {
var constructorName = utils_1.getFunctionName(func);
var targets = this._getTargets(func, false);
var baseClassDepencencyCount = this._baseClassDepencencyCount(func);
if (targets.length < baseClassDepencencyCount) {
var error = ERROR_MSGS.ARGUMENTS_LENGTH_MISMATCH_1 + constructorName + ERROR_MSGS.ARGUMENTS_LENGTH_MISMATCH_2;
throw new Error(error);
}
return targets;
};
Planner.prototype._baseClassDepencencyCount = function (func) {
var baseConstructor = Object.getPrototypeOf(func.prototype).constructor;
if (baseConstructor !== Object) {
var targets = this._getTargets(baseConstructor, true);
var metadata = targets.map(function (t) {
return t.metadata.filter(function (m) {
return m.key === METADATA_KEY.UNMANAGED_TAG;
});
});
var unmanagedCount = [].concat.apply([], metadata).length;
var dependencyCount = targets.length - unmanagedCount;
if (dependencyCount > 0) {
return dependencyCount;
}
else {
return this._baseClassDepencencyCount(baseConstructor);
}
}
else {
return 0;
}
};
return Planner;
}());
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = Planner;