@launchtray/tsyringe-async
Version:
Lightweight dependency injection container for JavaScript/TypeScript, with asynchronous resolution
239 lines (238 loc) • 9.63 kB
JavaScript
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;