UNPKG

@sigi/di

Version:

Dependencies injection library for sigi framework

168 lines (152 loc) 6.17 kB
import { ReflectiveProvider } from './injector-provider' import { InjectionProvider } from './provider' import { Provider, ValueProvider, ClassProvider, FactoryProvider, Token, Type, InjectionToken } from './type' export class Injector { readonly provider = new InjectionProvider() protected readonly resolvedProviders = new Map<ReflectiveProvider<unknown>, unknown>() protected readonly providersCache = new Map<Provider, ReflectiveProvider<unknown>>() constructor(protected readonly parent: Injector | null = null) {} addProvider<T extends Provider<any>>(provider: T): T { return this.provider.addProvider(provider) } addProviders(providers: Provider[]) { for (const provider of providers) { this.provider.addProvider(provider) } return this } getInstance<T>(provider: Provider<T>): T { return this.getInstanceInternal(provider, true) } resolveAndInstantiate<T>(provider: Provider<T>): T { return this.getInstanceInternal(provider, false) } createChild(providers: Provider<unknown>[]): Injector { const childInjector = new Injector(this) childInjector.addProviders(providers) return childInjector } private resolveReflectiveProvider<T>(provider: Provider<T>): ReflectiveProvider<T> | null { let reflectiveProvider: ReflectiveProvider<T> | null = null if (this.provider.findProviderByToken((provider as ValueProvider<T>).provide ?? provider)) { if (this.providersCache.has(provider)) { return this.providersCache.get(provider) as ReflectiveProvider<T> } reflectiveProvider = new ReflectiveProvider(provider) this.providersCache.set(provider, reflectiveProvider) } return reflectiveProvider } private getInstanceInternal<T>(provider: Provider<T>, useCache: boolean): T { let injector: Injector | null = this let instance: T | null = null let reflectiveProvider: ReflectiveProvider<T> | null = null const deps = this.findDeps(provider) while (injector) { reflectiveProvider = injector.resolveReflectiveProvider(provider) if (!reflectiveProvider) { injector = injector.parent continue } if (injector.resolvedProviders.has(reflectiveProvider)) { if (deps) { if (useCache && (injector === this || this.checkDependenciesClean(injector, deps))) { instance = injector.resolvedProviders.get(reflectiveProvider) as T } else { instance = this.resolveByReflectiveProvider(reflectiveProvider, useCache, this) if (useCache) { this.provider.addProvider(provider) this.providersCache.set(provider, reflectiveProvider) this.resolvedProviders.set(reflectiveProvider, instance) } } } else { instance = useCache ? (injector.resolvedProviders.get(reflectiveProvider) as T) : this.resolveByReflectiveProvider(reflectiveProvider, false, this) } break } instance = injector.resolveByReflectiveProvider(reflectiveProvider, useCache, this) if (instance) { if (useCache) { this.provider.addProvider(provider) this.providersCache.set(provider, reflectiveProvider) this.resolvedProviders.set(reflectiveProvider!, instance) } break } injector = injector.parent } if (!instance) { reflectiveProvider = new ReflectiveProvider(provider) throw new TypeError(`No provider for ${reflectiveProvider.name}!`) } return instance } private resolveByReflectiveProvider<T>( reflectiveProvider: ReflectiveProvider<T>, useCache = true, leaf = this, ): T | null { let instance: T | null = null const { provider, name } = reflectiveProvider if (typeof provider === 'function') { const deps: Array<Type<unknown> | InjectionToken<T>> = Reflect.getMetadata('design:paramtypes', provider) ?? [] const depsInstance = deps.map((dep) => leaf.getInstanceInternal(leaf.findExisting(dep), useCache)) instance = new provider(...depsInstance) } else if ('useValue' in provider) { instance = provider.useValue } else if ('useClass' in provider) { instance = leaf.getInstanceInternal(provider.useClass, useCache) } else if ('useFactory' in provider) { let deps: unknown[] = [] if (provider.deps) { deps = provider.deps!.map((dep) => leaf.getInstanceInternal(leaf.findExisting(dep), useCache)) } instance = provider.useFactory(...deps) } else if ('useExisting' in provider) { instance = leaf.getInstanceInternal(this.findExisting(provider.useExisting), useCache) } if (!instance) { throw new TypeError(`Can not resolve ${name}, it's not a valid provider`) } return instance } private findExisting<T>(token: Token<T>): Provider<T> { let provider: Provider<T> | null = null let injector: Injector | null = this while (injector) { provider = injector.provider.findProviderByToken(token) if (provider) { break } injector = injector.parent } if (!provider) { throw new TypeError(`No provider for ${(token as Type<T>).name ?? token.toString()}`) } return provider } private findDeps<T>(provider: Provider<T>): Token<unknown>[] | null | undefined { return typeof provider === 'function' ? Reflect.getMetadata('design:paramtypes', provider) : (provider as ClassProvider<T>).useClass ? Reflect.getMetadata('design:paramtypes', (provider as ClassProvider<T>).useClass) : (provider as FactoryProvider<T>).deps ? (provider as FactoryProvider<T>).deps : null } private checkDependenciesClean(leaf: Injector, deps: Token<unknown>[]): boolean { return deps.every((dep) => { const depInLeaf = leaf.findExisting(dep) const depInRoot = this.findExisting(dep) const isEqual = depInLeaf === depInRoot const deps = this.findDeps(depInLeaf) if (deps) { return this.checkDependenciesClean(leaf, deps) && isEqual } return isEqual }) } }