Skip to content

Instantly share code, notes, and snippets.

@SuperOleg39
Created March 23, 2020 11:47
Show Gist options
  • Select an option

  • Save SuperOleg39/91cfb394509d495423411e9147046d1b to your computer and use it in GitHub Desktop.

Select an option

Save SuperOleg39/91cfb394509d495423411e9147046d1b 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 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