Skip to content

Instantly share code, notes, and snippets.

@guiseek
Created March 23, 2025 06:21
Show Gist options
  • Save guiseek/cc80748ca009d04e08e4ca269a2e7941 to your computer and use it in GitHub Desktop.
Save guiseek/cc80748ca009d04e08e4ca269a2e7941 to your computer and use it in GitHub Desktop.
Tiny Dependency Injection
import { Abstract, AsyncFactory, Constructor, Factory, Params } from './types';
import { validate } from './validate';
import { Token } from './token';
type Ref<T> = Token<T> | Abstract<T> | Constructor<T>;
type Use<T> = Constructor<T> | Factory<T> | AsyncFactory<T> | T;
interface Config<T> {
ref: Ref<T>;
use?: Use<T>;
dep?: Ref<unknown>[];
}
export type Provider<T> = Config<T> | Constructor<T>;
const container = new Map();
const relations = new Map<Ref<unknown>, unknown>();
export function inject<T>(ref: Token<T>): T;
export function inject<T>(ref: Abstract<T>): T;
export function inject<T>(ref: Constructor<T>): T;
export function inject<T>(ref: Ref<T>): T {
const type = container.get(ref);
if (!type) throw `Provider ${ref.name} not found`;
return type;
}
const determine = async <T>({ ref, use }: Config<T>) => {
const concrete = use ?? ref;
if (validate.factory<T>(concrete)) {
const deps = (relations.get(ref) ?? []) as Params<Ref<T>>;
if (validate.constructor(concrete)) {
return new concrete(...deps);
}
if (validate.asyncFactory(concrete)) {
return await concrete(...deps);
}
return concrete(...deps);
}
return concrete as T;
};
export const provide = async <T>(config: Provider<T>) => {
if (validate.constructor<T>(config)) {
container.set(config, await determine({ ref: config }));
return inject<T>(config);
} else {
if (config.dep && config.dep.length > 0) {
relations.set(config.ref, config.dep.map(inject));
}
container.set(config.ref, await determine(config));
return inject<T>(config.ref);
}
};
export const init = async <T>(generator: AsyncGenerator<T>) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _ of generator) {
// console.log(_)
}
};
export async function* setup<T>(
...providers: Provider<T | unknown>[]
): AsyncGenerator<T, Awaited<void>, unknown> {
for (const p of providers) {
yield await provide(p as Provider<T>);
}
}
export class Token<T> {
constructor(public readonly name: string, public value?: T) {}
}
export const token = <T>(name: string, value?: T) => {
return new Token<T>(name, value);
};
export type Abstract<T, P extends unknown[] = never[]> = abstract new (
...args: P
) => T;
export interface AsyncFactory<T, P extends unknown[] = never[]> {
(...args: P): Promise<T>;
}
export interface Constructor<T, P extends unknown[] = never[]>
extends NewableFunction {
new (...args: P): T;
}
export interface Factory<T, P extends unknown[] = never[]> {
(...args: P): T;
}
export type Params<T> = T extends Constructor<unknown>
? ConstructorParameters<T>
: T extends Factory<unknown>
? Parameters<T>
: never[];
import { AsyncFactory, Constructor, Factory } from './types';
export const validate = {
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';
},
constructor<T>(value: unknown): value is Constructor<T> {
return this.factory(value) && typeof value.prototype === 'object';
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment