UNPKG

@sustain/core

Version:

Sustain is a Framework that is barely used despedcies to make stable and sustainable apps

105 lines (91 loc) 3.46 kB
// 2018-2019 Darcy Rayner // 2019 Labidi Aymen (labidi@aymen.co) import { Provider, isClassProvider, ClassProvider, ValueProvider, FactoryProvider, isValueProvider, Token, InjectionToken, } from './provider'; import {Type} from './type'; import {isInjectable} from './injectable'; import 'reflect-metadata'; import {getInjectionToken} from './inject'; type InjectableParam = Type<any>; const REFLECT_PARAMS = 'design:paramtypes'; export class Container { private providers = new Map<Token<any>, Provider<any>>(); addProvider<T>(provider: Provider<T>) { this.assertInjectableIfClassProvider(provider); this.providers.set(provider.provide, provider); } inject<T>(type: Token<T>): T { let provider = this.providers.get(type); if (provider === undefined && !(type instanceof InjectionToken)) { provider = {provide: type, useClass: type}; this.assertInjectableIfClassProvider(provider); } return this.injectWithProvider(type, provider); } get(type: any): any { return this.providers.get(type).provide; } private injectWithProvider<T>(type: Token<T>, provider?: Provider<T>): T { if (provider === undefined) { throw new Error(`No provider for type ${this.getTokenName(type)}`); } if (isClassProvider(provider)) { return this.injectClass(provider); } else if (isValueProvider(provider)) { return this.injectValue(provider); } else { // Factory provider by process of elimination return this.injectFactory(provider); } } private assertInjectableIfClassProvider<T>(provider: Provider<T>) { if (isClassProvider(provider) && !isInjectable(provider.useClass)) { throw new Error( `Cannot provide ${this.getTokenName(provider.provide)} using class ${this.getTokenName( provider.useClass )}, ${this.getTokenName(provider.useClass)} isn't injectable` ); } } private injectClass<T>(classProvider: ClassProvider<T>): T { const target = classProvider.useClass; const params = this.getInjectedParams(target); return Reflect.construct(target, params); } private injectValue<T>(valueProvider: ValueProvider<T>): T { return valueProvider.useValue; } private injectFactory<T>(valueProvider: FactoryProvider<T>): T { return valueProvider.useFactory(); } private getInjectedParams<T>(target: Type<T>) { const argTypes = Reflect.getMetadata(REFLECT_PARAMS, target) as (InjectableParam | undefined)[]; if (argTypes === undefined) { return []; } return argTypes.map((argType, index) => { // The reflect-metadata API fails on circular dependencies, and will return undefined // for the argument instead. if (argType === undefined) { throw new Error( `Injection error. Recursive dependency detected in constructor for type ${target.name} with parameter at index ${index}` ); } const overrideToken = getInjectionToken(target, index); const actualToken = overrideToken === undefined ? argType : overrideToken; let provider = this.providers.get(actualToken); return this.injectWithProvider(actualToken, provider); }); } private getTokenName<T>(token: Token<T>) { return token instanceof InjectionToken ? token.injectionIdentifier : token.name; } }