Skip to content

Instantly share code, notes, and snippets.

@SuperOleg39
Created March 23, 2020 09:26
Show Gist options
  • Select an option

  • Save SuperOleg39/13a565e5155fc325537bd4dcf359c91b to your computer and use it in GitHub Desktop.

Select an option

Save SuperOleg39/13a565e5155fc325537bd4dcf359c91b to your computer and use it in GitHub Desktop.
type Keys<T extends {}> = keyof T;
type Values<T extends {}> = T[keyof T];
type FilterFlags<Base, Condition> = {
[Key in keyof Base]: Base[Key] extends Condition ? Key : never;
};
type AllowedNames<Base, Condition> = FilterFlags<Base, Condition>[keyof Base];
type MapTupleToAllowedNames<Base, Tuple> = {
[K in keyof Tuple]: AllowedNames<Base, Tuple[K]>;
};
type FactoryObject<List, I extends Values<List>> = {
factory: new (...args: any) => I,
dependencies: MapTupleToAllowedNames<List, ConstructorParameters<new (...args: any) => I>>
}
class DIContainer<List extends Record<string, any>> {
private dependencies: Partial<List> = {};
private factories: Partial<{
[N in Keys<List>]: FactoryObject<List, List[N]>
}> = {};
register<N extends Keys<List>>(name: N, dependency: List[N]) {
this.dependencies[name] = dependency;
}
factory<
N extends Keys<List>,
I extends List[N],
C extends new (...args: any[]) => I,
D extends MapTupleToAllowedNames<List, ConstructorParameters<C>>
>(
name: N,
factory: C,
dependencies: D
) {
this.factories[name] = {factory, dependencies};
}
resolve<N extends Keys<List>>(name: N): List[N] {
let dependency: List[N] = this.dependencies[name];
if (typeof dependency === 'undefined') {
const factoryObject = this.factories[name];
if (typeof factoryObject === 'undefined') {
throw new Error(`${name} is not registered`);
}
const {factory, dependencies} = factoryObject;
dependency = this.inject(factory, dependencies);
this.register(name, dependency);
}
return dependency;
}
inject<C extends new (...args: any[]) => any>(
factory: C,
dependencies: MapTupleToAllowedNames<List, ConstructorParameters<C>>
): C extends new (...args: any[]) => infer I ? I : unknown {
if (Array.isArray(dependencies)) {
return new factory(...dependencies.map((dependency) => this.resolve(dependency)));
} else {
throw new Error(`dependencies is not passed`);
}
}
}
class FactoryZ {
baz: () => string;
bar: number;
FactoryY: FactoryY;
constructor(baz: () => string, bar: number, FactoryY: FactoryY) {
this.baz = baz;
this.bar = bar;
this.FactoryY = FactoryY;
}
hello() {
return `Hello ${this.baz()} Factory!`;
}
f() {
return this.FactoryY.hello();
}
}
class FactoryY {
foo: string;
baz: () => string;
constructor(baz: () => string) {
this.foo = 'FOO';
this.baz = baz;
}
hello() {
return `Hello ${this.foo} Factory!`;
}
f() {
return this.baz();
}
}
type ConcreteDependenciesList = {
'foo': string;
'bar': number;
'bar2': number;
'baz': () => string;
'Factory1': FactoryZ;
'Factory2': FactoryY;
'subContainer': DIContainer<{'foo': number;}>;
}
const container = new DIContainer<ConcreteDependenciesList>();
container.register('foo', 'foo');
container.register('bar', 111);
container.register('bar2', 222);
container.register('baz', () => {
return 'baz';
});
container.factory('Factory1', FactoryZ, ['baz', 'bar', 'Factory2']);
container.factory('Factory2', FactoryY, ['baz']);
container.factory('subContainer', DIContainer, []);
const foo = container.resolve('foo');
const bar = container.resolve('bar');
const baz = container.resolve('baz');
const f1 = container.resolve('Factory1');
const f2 = container.resolve('Factory2');
const sub = container.resolve('subContainer');
sub.register('foo', 0);
const factorySelfInjected = container.inject(FactoryY, ['baz']);
console.log(foo);
console.log(bar);
console.log(baz());
console.log(f1.hello());
console.log(f2.hello());
console.log(f1.f());
console.log(f2.f());
console.log(sub.resolve('foo'));
console.error(factorySelfInjected.hello(), factorySelfInjected.f());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment