Created
March 23, 2025 06:21
-
-
Save guiseek/cc80748ca009d04e08e4ca269a2e7941 to your computer and use it in GitHub Desktop.
Tiny Dependency Injection
This file contains 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
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>); | |
} | |
} |
This file contains 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
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); | |
}; |
This file contains 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
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[]; |
This file contains 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
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