@laurence79/ts-ioc
Version:
An opinionated Inversion of Control container for Typescript
167 lines (154 loc) • 4.88 kB
JavaScript
import 'reflect-metadata';
class ResolutionContext {
constructor(resolver, cache = new Map(), resolutionChain = []) {
this.resolver = resolver;
this.cache = cache;
this.resolutionChain = resolutionChain;
}
withReplaceHead(token) {
return new ResolutionContext(this.resolver, this.cache, [
token,
...this.resolutionChain.slice(1)
]);
}
withAppend(token) {
return new ResolutionContext(this.resolver, this.cache, [
token,
...this.resolutionChain
]);
}
}
class ConstructProvider {
constructor(container, ctor) {
this.container = container;
this.ctor = ctor;
this.params = Reflect.getMetadata('design:paramtypes', this.ctor) || [];
}
provide(context) {
if (!this.params || this.params.length === 0) {
if (this.ctor.length === 0) {
return new this.ctor();
}
else {
throw new Error(`Unable to construct "${this.ctor.name}".`);
}
}
const deps = this.params.map(param => this.container.resolveWithContext(param, context.withAppend(param)));
return new this.ctor(...deps);
}
}
class FactoryProvider {
constructor(fn) {
this.fn = fn;
}
provide(context) {
return this.fn(context.resolver, context.resolutionChain);
}
}
class InstanceProvider {
constructor(instance) {
this.instance = instance;
}
provide() {
return this.instance;
}
}
class AliasProvider {
constructor(of) {
this.of = of;
}
provide(context) {
return context.resolver.resolveWithContext(this.of, context.withReplaceHead(this.of));
}
}
class SingletonProvider {
constructor(innerProvider) {
this.innerProvider = innerProvider;
}
provide(context) {
return (this.instance ?? (this.instance = this.innerProvider.provide(context)));
}
}
var Scope;
(function (Scope) {
Scope[Scope["transient"] = 0] = "transient";
Scope[Scope["resolution"] = 1] = "resolution";
})(Scope || (Scope = {}));
const isConstructor = (obj) => {
return typeof obj === 'function' && obj.prototype !== undefined;
};
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
class InternalContainer {
constructor(parent) {
this.parent = parent;
this.providers = new Map();
this.options = new Map();
this.registerSingleton(InternalContainer, this);
}
createChildContainer() {
return new InternalContainer(this);
}
register(ctor, options) {
this.providers.set(ctor, new ConstructProvider(this, ctor));
this.options.set(ctor, options);
return this;
}
registerAlias(from, to) {
this.providers.set(from, new AliasProvider(to));
return this;
}
registerFactory(token, factory, maybeOptions) {
this.providers.set(token, new FactoryProvider(factory));
if (maybeOptions) {
this.options.set(token, maybeOptions);
}
return this;
}
registerSingleton(ctor, maybeInstance) {
if (maybeInstance) {
this.providers.set(ctor, new InstanceProvider(maybeInstance));
return this;
}
this.providers.set(ctor, new SingletonProvider(new ConstructProvider(this, ctor)));
return this;
}
resolve(token) {
const context = new ResolutionContext(this, undefined, [token]);
return this.resolveWithContext(token, context);
}
resolveWithContext(token, context) {
const shouldCache = this.options.get(token)?.scope === Scope.resolution;
if (shouldCache && context.cache.has(token)) {
return context.cache.get(token);
}
const registeredProvider = this.providers.get(token);
if (!registeredProvider && this.parent) {
return this.parent.resolve(token);
}
const provider = registeredProvider ??
(isConstructor(token) ? new ConstructProvider(this, token) : null);
if (!provider) {
throw new Error(`Can not resolve ${String(token)}`);
}
const instance = provider.provide(context);
if (shouldCache) {
context.cache.set(token, instance);
}
return instance;
}
}
class Container {
static create() {
return new InternalContainer();
}
}
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-explicit-any */
function injectable() {
return function (target) {
Reflect.defineMetadata('ioc:injectable', true, target);
};
}
export { Container, Scope, injectable };