Created
March 23, 2020 09:26
-
-
Save SuperOleg39/13a565e5155fc325537bd4dcf359c91b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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