UNPKG

ts-bakery

Version:

Baked dependency injection for Typescript.

195 lines 8.9 kB
"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