@sigi/di
Version:
Dependencies injection library for sigi framework
168 lines (152 loc) • 6.17 kB
text/typescript
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
})
}
}