UNPKG

@laurence79/ts-ioc

Version:

An opinionated Inversion of Control container for Typescript

167 lines (154 loc) 4.88 kB
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 };