UNPKG

@launchtray/tsyringe-async

Version:

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

239 lines (238 loc) 9.63 kB
import { __awaiter } from "tslib"; import { isClassProvider, isFactoryProvider, isNormalToken, isTokenProvider, isValueProvider } from "./providers"; import { isProvider } from "./providers/provider"; import { isTokenDescriptor } from "./providers/injection-token"; import Registry from "./registry"; import Lifecycle from "./types/lifecycle"; import ResolutionContext from "./resolution-context"; import { formatErrorCtor } from "./error-helpers"; import { callInitializers } from "./decorators/initializer"; export const typeInfo = new Map(); class InternalDependencyContainer { constructor(parent) { this.parent = parent; this._registry = new Registry(); } register(token, providerOrConstructor, options = { lifecycle: Lifecycle.Transient }) { let provider; if (!isProvider(providerOrConstructor)) { provider = { useClass: providerOrConstructor }; } else { provider = providerOrConstructor; } if (options.lifecycle === Lifecycle.Singleton || options.lifecycle == Lifecycle.ContainerScoped || options.lifecycle == Lifecycle.ResolutionScoped) { if (isValueProvider(provider) || isFactoryProvider(provider)) { throw new Error(`Cannot use lifecycle "${Lifecycle[options.lifecycle]}" with ValueProviders or FactoryProviders`); } } this._registry.set(token, { provider, options }); return this; } registerType(from, to) { if (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 (isNormalToken(from)) { if (isNormalToken(to)) { return this.register(from, { useToken: to }, { lifecycle: Lifecycle.Singleton }); } else if (to) { return this.register(from, { useClass: to }, { lifecycle: Lifecycle.Singleton }); } throw new Error('Cannot register a type name as a singleton without a "to" token'); } let useClass = from; if (to && !isNormalToken(to)) { useClass = to; } return this.register(from, { useClass }, { lifecycle: Lifecycle.Singleton }); } resolve(token, context = new ResolutionContext()) { return __awaiter(this, void 0, void 0, function* () { const registration = this.getRegistration(token); if (!registration && 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 callInitializers(this, resolved); return resolved; }); } resolveRegistration(registration, context) { if (registration.options.lifecycle === Lifecycle.ResolutionScoped && context.scopedResolutions.has(registration)) { return context.scopedResolutions.get(registration); } const resolutionPromise = this.resolveRegistrationHelper(registration, context); if (registration.options.lifecycle === Lifecycle.ResolutionScoped) { context.scopedResolutions.set(registration, resolutionPromise); } return resolutionPromise; } resolveRegistrationHelper(registration, context) { return __awaiter(this, void 0, void 0, function* () { const isSingleton = registration.options.lifecycle === Lifecycle.Singleton; const isContainerScoped = registration.options.lifecycle === Lifecycle.ContainerScoped; const returnInstance = isSingleton || isContainerScoped; let resolved; if (isValueProvider(registration.provider)) { resolved = registration.provider.useValue; } else if (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 (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 (isFactoryProvider(registration.provider)) { resolved = yield registration.provider.useFactory(this); } else { resolved = yield this.construct(registration.provider, context); } yield callInitializers(this, resolved); return resolved; }); } resolveAll(token, context = new ResolutionContext()) { return __awaiter(this, void 0, void 0, function* () { let registrations = this.getAllRegistrations(token); if (!registrations && 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 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 => !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.ContainerScoped)) { childContainer._registry.setAll(token, registrations.map(registration => { if (registration.options.lifecycle === Lifecycle.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 __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 = 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) => __awaiter(this, void 0, void 0, function* () { try { if (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(formatErrorCtor(ctor, idx, e)); } }); } } export const instance = new InternalDependencyContainer(); export default instance;