UNPKG

@launchtray/tsyringe-async

Version:

Lightweight dependency injection container for JavaScript/TypeScript, with asynchronous resolution

241 lines (240 loc) 10.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const providers_1 = require("./providers"); const provider_1 = require("./providers/provider"); const injection_token_1 = require("./providers/injection-token"); const registry_1 = require("./registry"); const lifecycle_1 = require("./types/lifecycle"); const resolution_context_1 = require("./resolution-context"); const error_helpers_1 = require("./error-helpers"); const initializer_1 = require("./decorators/initializer"); exports.typeInfo = new Map(); class InternalDependencyContainer { constructor(parent) { this.parent = parent; this._registry = new registry_1.default(); } register(token, providerOrConstructor, options = { lifecycle: lifecycle_1.default.Transient }) { let provider; if (!provider_1.isProvider(providerOrConstructor)) { provider = { useClass: providerOrConstructor }; } else { provider = providerOrConstructor; } if (options.lifecycle === lifecycle_1.default.Singleton || options.lifecycle == lifecycle_1.default.ContainerScoped || options.lifecycle == lifecycle_1.default.ResolutionScoped) { if (providers_1.isValueProvider(provider) || providers_1.isFactoryProvider(provider)) { throw new Error(`Cannot use lifecycle "${lifecycle_1.default[options.lifecycle]}" with ValueProviders or FactoryProviders`); } } this._registry.set(token, { provider, options }); return this; } registerType(from, to) { if (providers_1.isNormalToken(to)) { return this.register(from, { useToken: to }); } return this.register(from, { useClass: to }); } registerInstance(token, instance) { return this.register(token, { useValue: instance }); } registerSingleton(from, to) { if (providers_1.isNormalToken(from)) { if (providers_1.isNormalToken(to)) { return this.register(from, { useToken: to }, { lifecycle: lifecycle_1.default.Singleton }); } else if (to) { return this.register(from, { useClass: to }, { lifecycle: lifecycle_1.default.Singleton }); } throw new Error('Cannot register a type name as a singleton without a "to" token'); } let useClass = from; if (to && !providers_1.isNormalToken(to)) { useClass = to; } return this.register(from, { useClass }, { lifecycle: lifecycle_1.default.Singleton }); } resolve(token, context = new resolution_context_1.default()) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const registration = this.getRegistration(token); if (!registration && providers_1.isNormalToken(token)) { throw new Error(`Attempted to resolve unregistered dependency token: "${token.toString()}"`); } if (registration) { return yield this.resolveRegistration(registration, context); } const resolved = yield this.construct(token, context); yield initializer_1.callInitializers(this, resolved); return resolved; }); } resolveRegistration(registration, context) { if (registration.options.lifecycle === lifecycle_1.default.ResolutionScoped && context.scopedResolutions.has(registration)) { return context.scopedResolutions.get(registration); } const resolutionPromise = this.resolveRegistrationHelper(registration, context); if (registration.options.lifecycle === lifecycle_1.default.ResolutionScoped) { context.scopedResolutions.set(registration, resolutionPromise); } return resolutionPromise; } resolveRegistrationHelper(registration, context) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const isSingleton = registration.options.lifecycle === lifecycle_1.default.Singleton; const isContainerScoped = registration.options.lifecycle === lifecycle_1.default.ContainerScoped; const returnInstance = isSingleton || isContainerScoped; let resolved; if (providers_1.isValueProvider(registration.provider)) { resolved = registration.provider.useValue; } else if (providers_1.isTokenProvider(registration.provider)) { resolved = returnInstance ? registration.instance || (registration.instance = yield this.resolve(registration.provider.useToken, context)) : yield this.resolve(registration.provider.useToken, context); } else if (providers_1.isClassProvider(registration.provider)) { resolved = returnInstance ? registration.instance || (registration.instance = yield this.construct(registration.provider.useClass, context)) : yield this.construct(registration.provider.useClass, context); } else if (providers_1.isFactoryProvider(registration.provider)) { resolved = yield registration.provider.useFactory(this); } else { resolved = yield this.construct(registration.provider, context); } yield initializer_1.callInitializers(this, resolved); return resolved; }); } resolveAll(token, context = new resolution_context_1.default()) { return tslib_1.__awaiter(this, void 0, void 0, function* () { let registrations = this.getAllRegistrations(token); if (!registrations && providers_1.isNormalToken(token)) { registrations = []; } if (registrations) { const instances = []; for (const item of registrations) { instances.push(yield this.resolveRegistration(item, context)); } return instances; } const resolved = yield this.construct(token, context); yield initializer_1.callInitializers(this, resolved); return [resolved]; }); } isRegistered(token, recursive = false) { return (this._registry.has(token) || (recursive && (this.parent || false) && this.parent.isRegistered(token, true))); } reset() { this._registry.clear(); } clearInstances() { for (const [token, registrations] of this._registry.entries()) { this._registry.setAll(token, registrations .filter(registration => !providers_1.isValueProvider(registration.provider)) .map(registration => { registration.instance = undefined; return registration; })); } } createChildContainer() { const childContainer = new InternalDependencyContainer(this); for (const [token, registrations] of this._registry.entries()) { if (registrations.some(({ options }) => options.lifecycle === lifecycle_1.default.ContainerScoped)) { childContainer._registry.setAll(token, registrations.map(registration => { if (registration.options.lifecycle === lifecycle_1.default.ContainerScoped) { return { provider: registration.provider, options: registration.options }; } return registration; })); } } return childContainer; } getRegistration(token) { if (this.isRegistered(token)) { return this._registry.get(token); } if (this.parent) { return this.parent.getRegistration(token); } return null; } getAllRegistrations(token) { if (this.isRegistered(token)) { return this._registry.getAll(token); } if (this.parent) { return this.parent.getAllRegistrations(token); } return null; } construct(ctor, context) { return tslib_1.__awaiter(this, void 0, void 0, function* () { if (typeof ctor === "undefined") { throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem."); } if (ctor.length === 0) { return new ctor(); } const paramInfo = exports.typeInfo.get(ctor); if (!paramInfo || paramInfo.length === 0) { throw new Error(`TypeInfo not known for "${ctor.name}"`); } let idx = 0; const params = []; for (const param of paramInfo) { const resolver = this.resolveParams(context, ctor); params.push(yield resolver(param, idx)); idx++; } return new ctor(...params); }); } resolveParams(context, ctor) { return (param, idx) => tslib_1.__awaiter(this, void 0, void 0, function* () { try { if (injection_token_1.isTokenDescriptor(param)) { return param.multiple ? yield this.resolveAll(param.token) : yield this.resolve(param.token, context); } return yield this.resolve(param, context); } catch (e) { throw new Error(error_helpers_1.formatErrorCtor(ctor, idx, e)); } }); } } exports.instance = new InternalDependencyContainer(); exports.default = exports.instance;