ts-bakery
Version:
Baked dependency injection for Typescript.
195 lines • 8.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const DependencyBinding_1 = require("../Configuration/DependencyBinding");
const DependencyConfigurationLoader_1 = require("../Json/DependencyConfigurationLoader");
const DecoratorRegistry_1 = require("./DecoratorRegistry");
const PostResolvable_1 = require("./PostResolvable");
class DependencyContainer {
constructor() {
this.instances = new Map();
this.activeInstantiations = new Set();
this.registrations = new Map();
this.configurationProvider = new DependencyConfigurationLoader_1.default();
this.postResolvables = [];
}
registerDependencies(registrations) {
const dependencyConfiguration = this.configurationProvider.getConfiguration();
const bindings = this.createBindingsFromIndexedFormat(dependencyConfiguration);
for (let i = 0; i < registrations.length; i++) {
const registration = registrations[i];
if (this.registrations.has(registration.name)) {
throw new Error(`Cannot set configuration for type of name ${registration.name} twice.`);
}
registration.setBinding(bindings[i]);
this.registrations.set(registration.name, registration);
}
}
instantiateFromRegistrations() {
const configs = this.registrations.values();
for (const config of configs) {
this.resolveRegistration(config);
}
}
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);
}
}
callPostResolvables() {
for (const postResolvable of this.postResolvables) {
postResolvable.initFunction.call(postResolvable.instance);
}
}
createBindingsFromIndexedFormat(configuration) {
const bindings = [];
const allImplementationIndices = new Set();
// Add all implementations from requestedDependencies
for (const [implementationIndex] of configuration.requestedDependencies) {
allImplementationIndices.add(implementationIndex);
}
// Add all implementations from interfaceBindings
for (const [, implementationIndex] of configuration.interfaceBindings) {
allImplementationIndices.add(implementationIndex);
}
// Create bindings for all implementations
for (const implementationIndex of allImplementationIndices) {
const binding = new DependencyBinding_1.default();
binding.implementationIndex = implementationIndex;
binding.implementation = this.getClassName(configuration, implementationIndex);
// Find if this implementation has an interface binding
const interfaceBinding = configuration.interfaceBindings.find(([, implIndex]) => implIndex === implementationIndex);
if (interfaceBinding) {
binding.abstractionIndex = interfaceBinding[0];
binding.abstraction = this.getClassName(configuration, interfaceBinding[0]);
}
else {
binding.abstractionIndex = -1;
binding.abstraction = null;
}
bindings.push(binding);
}
return bindings;
}
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];
}
try {
return this.createInstance(registration);
}
catch (error) {
console.error(error);
return null;
}
}
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();
// Find the requested dependencies for this implementation
const classNameIndex = dependencyConfiguration.dependencies.indexOf(registration.name);
const requestedDependency = dependencyConfiguration.requestedDependencies.find(([implIndex]) => implIndex === classNameIndex);
const params = [];
if (requestedDependency == null) {
return params;
}
const [, dependencyIndices] = requestedDependency;
for (const dependencyIndex of dependencyIndices) {
const dependencyName = this.getClassName(dependencyConfiguration, dependencyIndex);
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;
}
getClassName(configuration, index) {
return configuration.dependencies[index];
}
}
exports.default = DependencyContainer;
//# sourceMappingURL=DependencyContainer.js.map