@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
text/typescript
// 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;
}
}