ts-bakery
Version:
Baked dependency injection for Typescript.
178 lines • 7.61 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const DependencyConfigurationProvider_1 = require("../Configuration/DependencyConfigurationProvider");
const Paths_1 = require("../Util/Paths");
const DecoratorRegistry_1 = require("./DecoratorRegistry");
const ManualInjector_1 = require("./ManualInjector");
const PostResolvable_1 = require("./PostResolvable");
class DependencyResolver {
constructor() {
this.modules = [];
this.moduleNames = new Set();
this.instances = new Map();
this.activeInstantiations = new Set();
this.registrations = new Map();
this.configurationProvider = new DependencyConfigurationProvider_1.default();
this.postResolvables = [];
ManualInjector_1.default.setResolver(this);
}
build() {
this.validatePaths();
this.registerModules();
this.buildRegistrations();
this.instantiateFromRegistrations();
this.callPostResolvables();
}
manualInject(instance) {
const constructor = instance['constructor'];
const registration = this.registrations.get(constructor.name);
if (registration == null) {
throw new Error(`Unable to find dependency registration for type ${constructor.name}.`);
}
const injectMethod = DecoratorRegistry_1.default.getInjectMethod(registration.constructorReference);
if (injectMethod == null) {
throw new Error(`${constructor.name} is not marked for method injection.`);
}
const params = this.resolveParams(registration);
injectMethod.call(instance, ...params);
const postInjectMethod = DecoratorRegistry_1.default.getPostInjectMethod(registration.constructorReference);
if (postInjectMethod != null) {
postInjectMethod.call(instance);
}
}
registerModule(constructor) {
if (this.moduleNames.has(constructor.name)) {
throw new Error(`Module with name ${constructor.name} has already been registered.`);
}
this.moduleNames.add(constructor.name);
const module = new constructor();
this.modules.push(module);
}
validatePaths() {
Paths_1.default.validate();
}
buildRegistrations() {
const dependencyMapping = this.configurationProvider.getConfiguration();
const bindings = dependencyMapping.bindings;
let configIndex = 0;
for (const module of this.modules) {
const regs = module.buildRegistrations();
for (let config of regs) {
if (this.registrations.has(config.name)) {
throw new Error(`Cannot set configuration for type of name ${config.name} twice.`);
}
config.setBinding(bindings[configIndex]);
this.registrations.set(config.name, config);
configIndex++;
}
}
}
instantiateFromRegistrations() {
const configs = this.registrations.values();
for (const config of configs) {
this.resolveRegistration(config);
}
}
resolveRegistration(registration) {
if (registration.isLazy) {
return;
}
const instance = this.getOrCreateInstance(registration);
const postResolve = DecoratorRegistry_1.default.getPostResolveMethod(registration.constructorReference);
if (postResolve != null) {
this.postResolvables.push(new PostResolvable_1.default(instance, postResolve));
}
}
getInstance(requested, requester) {
if (!requested.canBeRequestedBy(requester)) {
throw new Error(`Trying to inject ${requested.name} into ${requester.name} but access has been restricted.`);
}
return this.getOrCreateInstance(requested);
}
getOrCreateInstance(registration) {
if (registration.isSingle && this.instances.has(registration)) {
return this.instances.get(registration)[0];
}
return this.createInstance(registration);
}
createInstance(registration) {
if (registration.isSingle && this.instances.has(registration)) {
return this.instances.get(registration)[0];
}
this.activeInstantiations.add(registration.name);
const params = this.resolveParams(registration);
const instance = this.createInstanceWithParams(registration, params);
this.activeInstantiations.delete(registration.name);
if (!this.instances.has(registration)) {
this.instances.set(registration, []);
}
this.instances.get(registration).push(instance);
const postInject = DecoratorRegistry_1.default.getPostInjectMethod(registration.constructorReference);
if (postInject != null) {
postInject.call(instance);
}
return instance;
}
createInstanceWithParams(registration, params) {
const injectMethod = DecoratorRegistry_1.default.getInjectMethod(registration.constructorReference);
if (injectMethod == null) {
return new registration.constructorReference(...params);
}
else {
const instance = new registration.constructorReference();
injectMethod.call(instance, ...params);
return instance;
}
}
resolveParams(registration) {
const dependencyConfiguration = this.configurationProvider.getConfiguration();
const dependencyMapping = dependencyConfiguration.mappings.find(i => i.implementation == registration.name);
const params = [];
if (dependencyMapping == null) {
return params;
}
for (const dependencyName of dependencyMapping.dependencies) {
const dependencyConfig = this.resolveDependencyName(dependencyName, registration);
if (this.activeInstantiations.has(dependencyConfig.name)) {
throw new Error(`Cyclic dependency detected while instantiating ${dependencyConfig.name} at ${registration.name}.`);
}
const dependencyInstance = this.getInstance(dependencyConfig, registration);
params.push(dependencyInstance);
}
return params;
}
resolveDependencyName(dependencyName, requester) {
const matchingRegistrations = this.getMatchingRegistrations(dependencyName);
matchingRegistrations.sort((a, b) => {
const ari = a.restrictions.includes(requester.name);
const bri = b.restrictions.includes(requester.name);
if (ari != bri) {
return Number(ari) - Number(bri);
}
return a.restrictions.length - b.restrictions.length;
});
for (const registration of matchingRegistrations) {
if (registration.canBeRequestedBy(requester)) {
return registration;
}
}
throw new Error('Unable to resolve dependency ' + dependencyName + ' for ' + requester.name);
}
getMatchingRegistrations(dependencyName) {
const matching = [];
const registrations = this.registrations.values();
for (const config of registrations) {
if (config.matchesBinding(dependencyName)) {
matching.push(config);
}
}
return matching;
}
callPostResolvables() {
for (const init of this.postResolvables) {
init.initFunction.call(init.instance);
}
}
}
exports.default = DependencyResolver;
//# sourceMappingURL=DependencyResolver.js.map