Skip to content

Instantly share code, notes, and snippets.

@guiseek
Created April 2, 2025 19:57
Show Gist options
  • Save guiseek/efdcac98e9ced04d16f1e58cab784fe1 to your computer and use it in GitHub Desktop.
Save guiseek/efdcac98e9ced04d16f1e58cab784fe1 to your computer and use it in GitHub Desktop.
DI Skills
type Concrete<T> = new (...params: any[]) => T;
type Abstract<T> = abstract new (...params: any[]) => T;
type Factory<T> = (...params: any[]) => T;
type AsyncFactory<T> = (...params: any[]) => Promise<T>;
type Ref<T> = Concrete<T> | Abstract<T>;
type Use<T> = Concrete<T>;
interface Config<T> {
ref: Ref<T>;
use?: Use<T>;
dep?: Ref<any>[];
}
const typeIs = {
factory<T>(value: unknown): value is Factory<T> {
return typeof value === "function";
},
asyncFactory<T>(value: unknown): value is AsyncFactory<T> {
return this.factory(value) && value.constructor.name === "AsyncFunction";
},
concrete<T>(value: unknown): value is Concrete<T> {
return this.factory(value) && typeof value.prototype === "object";
},
};
const container = new Map();
const relations = new Map();
export function inject<T>(ref: Ref<T>): T {
const instance = container.get(ref);
if (!instance) {
throw `Provider ${ref.name} not found`;
}
return instance;
}
const determine = async <T>({ use, ref }: Config<T>) => {
const concrete = use ?? ref;
if (typeIs.factory(concrete)) {
const deps = relations.get(ref) ?? [];
if (typeIs.concrete(concrete)) {
return new concrete(...deps);
}
if (typeIs.asyncFactory(concrete)) {
return await concrete(...deps);
}
return concrete(...deps);
}
return concrete;
};
const register = async <T>(config: Config<T>) => {
if (config.dep && config.dep.length) {
relations.set(config.ref, config.dep.map(inject));
}
container.set(config.ref, await determine(config));
};
interface Skill {
id: string
name: string;
}
/**
* Camada data-access
* depende da camada domain
* implementa o contrato
*/
class SkillRepositoryMockImpl implements SkillRepository {
async findAll() {
return new Array({ name: "Renan", id: crypto.randomUUID() });
}
}
class SkillRepositoryHTTPImpl implements SkillRepository {
async findAll() {
return fetch("https://api.devparana.mx/api/skills").then((res) =>
res.json()
);
}
}
/**
* Podem estar na camada domain
*/
/**
* Contrato
*/
abstract class SkillRepository {
abstract findAll(): Promise<Skill[]>;
}
/**
* Caso de uso
* depende de um contrato
*/
class FindAllSkillsUseCase {
constructor(public repository: SkillRepository) {}
execute() {
return this.repository.findAll();
}
}
await register({
ref: SkillRepository,
use: SkillRepositoryHTTPImpl,
});
await register({
ref: FindAllSkillsUseCase,
dep: [SkillRepository],
});
function Injectable() {
return <T>(use: Use<T>) => {
const ref = Object.getPrototypeOf(use)
register({ ref, use }).then()
};
}
abstract class SkillService {
abstract findOne(): Promise<Skill>;
}
@Injectable()
class SkillServiceImpl extends SkillService {
async findOne() {
return { name: "Marques", id: crypto.randomUUID() };
}
}
const repository = inject(SkillRepository);
console.log(repository);
const useCase = inject(FindAllSkillsUseCase);
console.log(useCase);
console.log(await useCase.execute());
const service = inject(SkillService);
console.log(service);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment