Created
March 23, 2020 11:47
-
-
Save SuperOleg39/91cfb394509d495423411e9147046d1b 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 ServiceObject<List, I extends Values<List>> = { | |
| service: new (...args: any) => I; | |
| dependencies: MapTupleToAllowedNames< | |
| List, | |
| ConstructorParameters<new (...args: any) => I> | |
| >; | |
| }; | |
| type FactoryObject<List, I extends Values<List>> = { | |
| factory: (...args: any) => I; | |
| dependencies: MapTupleToAllowedNames<List, Parameters<(...args: any) => I>>; | |
| }; | |
| class DIContainer< | |
| List extends ParentList, | |
| ParentList extends Record<string, any> = {} | |
| > { | |
| private dependencies: Partial<List> = {}; | |
| private services: Partial< | |
| { [N in Keys<List>]: ServiceObject<List, List[N]> } | |
| > = {}; | |
| private factories: Partial< | |
| { [N in Keys<List>]: FactoryObject<List, List[N]> } | |
| > = {}; | |
| private parent?: DIContainer<ParentList> = null; | |
| constructor(parent?: DIContainer<ParentList>) { | |
| this.parent = parent; | |
| } | |
| register<N extends Keys<List>>(name: N, dependency: List[N]) { | |
| this.dependencies[name] = dependency; | |
| } | |
| service< | |
| N extends Keys<List>, | |
| I extends List[N], | |
| C extends new (...args: any[]) => I, | |
| D extends MapTupleToAllowedNames<List, ConstructorParameters<C>> | |
| >(name: N, service: C, dependencies: D) { | |
| this.services[name] = { service, dependencies }; | |
| } | |
| factory< | |
| N extends Keys<List>, | |
| I extends List[N], | |
| C extends (...args: any[]) => I, | |
| D extends MapTupleToAllowedNames<List, Parameters<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 (name in this.services) { | |
| const { service, dependencies } = this.services[name]; | |
| dependency = this.inject(service, dependencies); | |
| this.register(name, dependency); | |
| } | |
| if (name in this.factories) { | |
| const { factory, dependencies } = this.factories[name]; | |
| dependency = this.call(factory, dependencies); | |
| this.register(name, dependency); | |
| } | |
| if (typeof dependency === "undefined") { | |
| if (this.parent) { | |
| dependency = (this.parent as DIContainer<List>).resolve(name); | |
| } else { | |
| throw new Error(`${name} is not registered`); | |
| } | |
| } | |
| return dependency; | |
| } | |
| inject<C extends new (...args: any[]) => any>( | |
| service: C, | |
| dependencies: MapTupleToAllowedNames<List, ConstructorParameters<C>> | |
| ): C extends new (...args: any[]) => infer I ? I : unknown { | |
| if (Array.isArray(dependencies)) { | |
| return new service( | |
| ...dependencies.map(dependency => this.resolve(dependency)) | |
| ); | |
| } else { | |
| throw new Error(`dependencies is not passed`); | |
| } | |
| } | |
| call<C extends (...args: any[]) => any>( | |
| factory: C, | |
| dependencies: MapTupleToAllowedNames<List, Parameters<C>> | |
| ): ReturnType<C> { | |
| if (Array.isArray(dependencies)) { | |
| return factory( | |
| ...dependencies.map(dependency => this.resolve(dependency)) | |
| ); | |
| } else { | |
| throw new Error(`dependencies is not passed`); | |
| } | |
| } | |
| } | |
| class ServiceZ { | |
| baz: () => string; | |
| bar: number; | |
| ServiceY: ServiceY; | |
| constructor(baz: () => string, bar: number, ServiceY: ServiceY) { | |
| this.baz = baz; | |
| this.bar = bar; | |
| this.ServiceY = ServiceY; | |
| } | |
| hello() { | |
| return `Hello ${this.baz()} Service!`; | |
| } | |
| f() { | |
| return this.ServiceY.hello(); | |
| } | |
| } | |
| class ServiceY { | |
| foo: string; | |
| baz: () => string; | |
| constructor(baz: () => string) { | |
| this.foo = "FOO"; | |
| this.baz = baz; | |
| } | |
| hello() { | |
| return `Hello ${this.foo} Service!`; | |
| } | |
| f() { | |
| return this.baz(); | |
| } | |
| } | |
| const FactoryA = (serviceZ: ServiceZ) => () => | |
| `FactoryA is called! ${serviceZ.f()}`; | |
| const FactoryB = (serviceY: ServiceY) => () => | |
| `FactoryB is called! ${serviceY.f()}`; | |
| type ContainerDependenciesList = { | |
| foo: string; | |
| bar: number; | |
| bar2: number; | |
| baz: () => string; | |
| Service1: ServiceZ; | |
| Service2: ServiceY; | |
| subContainer: DIContainer<SubContainerDependenciesList>; | |
| Factory1: ReturnType<typeof FactoryA>; | |
| }; | |
| type SubContainerDependenciesList = ContainerDependenciesList & { | |
| zeta: number; | |
| }; | |
| const container = new DIContainer<ContainerDependenciesList>(); | |
| const subContainer = new DIContainer< | |
| SubContainerDependenciesList, | |
| ContainerDependenciesList | |
| >(container); | |
| container.register("foo", "foo"); | |
| container.register("bar", 111); | |
| container.register("bar2", 222); | |
| container.register("baz", () => { | |
| return "baz"; | |
| }); | |
| container.service("Service1", ServiceZ, ["baz", "bar", "Service2"]); | |
| container.service("Service2", ServiceY, ["baz"]); | |
| container.register("subContainer", subContainer); | |
| container.factory("Factory1", FactoryA, ["Service1"]); | |
| const foo = container.resolve("foo"); | |
| const bar = container.resolve("bar"); | |
| const baz = container.resolve("baz"); | |
| const s1 = container.resolve("Service1"); | |
| const s2 = container.resolve("Service2"); | |
| const sub = container.resolve("subContainer"); | |
| const f1 = container.resolve("Factory1"); | |
| sub.register("foo", "nested foo"); | |
| subContainer.register("zeta", 7); | |
| const serviceSelfInjected = container.inject(ServiceY, ["baz"]); | |
| const factorySelfInjected = container.call(FactoryB, ["Service2"]); | |
| console.log(foo); | |
| console.log(bar); | |
| console.log(baz()); | |
| console.log(s1.hello()); | |
| console.log(s2.hello()); | |
| console.log(s1.f()); | |
| console.log(s2.f()); | |
| console.log( | |
| "sub container self overwrited foo dependency:", | |
| sub.resolve("foo") | |
| ); | |
| console.log("sub container parent bar dependency:", sub.resolve("bar")); | |
| console.log("sub container self zeta dependency:", sub.resolve("zeta")); | |
| console.log(f1()); | |
| console.log(factorySelfInjected()); | |
| console.error(serviceSelfInjected.hello(), serviceSelfInjected.f()); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment