Skip to content

Instantly share code, notes, and snippets.

@guiseek
Last active March 26, 2025 00:40
Show Gist options
  • Save guiseek/dc8214db02a929593208be1d0a42c9d3 to your computer and use it in GitHub Desktop.
Save guiseek/dc8214db02a929593208be1d0a42c9d3 to your computer and use it in GitHub Desktop.
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>;
}
import {Ref} from './ref'
import {Use} from './use'
export interface Config<T> {
ref: Ref<T>
use?: Use<T>
dep?: Ref<unknown>[]
}
export interface Constructor<T, P extends unknown[] = never[]>
extends NewableFunction {
new (...args: P): T;
}
import {Token} from './token'
export const createToken = <T>(name: string, value?: T) => {
return new Token<T>(name, value)
}
import {Config, Params, Ref} from './types'
import {typeIs} from './type-is'
export const determine = <T>(relations: Map<Ref<T>, T>) => {
return async ({ref, use}: Config<T>) => {
const concrete = use ?? ref
if (typeIs.factory<T>(concrete)) {
const deps = (relations.get(ref) ?? []) as Params<Ref<T>>
if (typeIs.constructor(concrete)) {
return new concrete(...deps)
}
if (typeIs.asyncFactory(concrete)) {
return await concrete(...deps)
}
return concrete(...deps)
}
return concrete as T
}
}
import {Abstract, Config, Constructor, Params, Provider, Ref} from './types'
import {typeIs} from './utils'
import {Token} from './token'
class DI {
#container = new Map()
#relations = new Map<Ref<unknown>, unknown[]>()
inject<T>(ref: Token<T>): T
inject<T>(ref: Abstract<T>): T
inject<T>(ref: Constructor<T>): T
inject<T>(ref: Ref<T>): T {
const type = this.#container.get(ref)
if (!type) throw `Provider ${ref.name} not found`
return type
}
async #determine<T>({ref, use}: Config<T>) {
const concrete = use ?? ref
if (typeIs.factory<T>(concrete)) {
const deps = this.#getDeps(ref)
if (typeIs.constructor(concrete)) {
return new concrete(...deps)
}
if (typeIs.asyncFactory(concrete)) {
return await concrete(...deps)
}
return concrete(...deps)
}
return concrete as T
}
#getDeps<T>(ref: Ref<T>) {
const deps = this.#relations.get(ref)
return deps as Params<Ref<T>>
}
async #provide<T>(config: Provider<T>) {
if (typeIs.constructor<T>(config)) {
const dependency = this.#determine({ref: config})
this.#container.set(config, await dependency)
return this.inject<T>(config)
} else {
if (config.dep && config.dep.length > 0) {
const deps = config.dep.map((dep) => this.inject(dep))
this.#relations.set(config.ref, deps)
}
const dependency = this.#determine(config)
this.#container.set(config.ref, await dependency)
return this.inject<T>(config.ref)
}
}
async *#setup<T>(
...providers: Provider<T | unknown>[]
): AsyncGenerator<T, Awaited<void>, unknown> {
for (const p of providers) {
yield await this.#provide(p as Provider<T>)
}
}
async config<T>(...providers: Provider<T | unknown>[]) {
const init = async <T>(generator: AsyncGenerator<T>) => {
for await (const _ of generator) {
}
}
return await init(this.#setup(...providers))
}
}
export const di = new DI()
export interface Factory<T, P extends unknown[] = never[]> {
(...args: P): T;
}
import { Constructor } from './constructor';
import { Factory } from './factory';
export type Params<T> = T extends Constructor<unknown>
? ConstructorParameters<T>
: T extends Factory<unknown>
? Parameters<T>
: never[];
import {Constructor} from './constructor'
import {Config} from './config'
export type Provider<T> = Config<T> | Constructor<T>
import {Constructor} from './constructor'
import {Abstract} from './abstract'
import {Token} from './token'
export type Ref<T> = Token<T> | Abstract<T> | Constructor<T>
export class Token<T> {
constructor(public readonly name: string, public value?: T) {}
}
import {AsyncFactory, Constructor, Factory} from './types'
export 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'
},
constructor<T>(value: unknown): value is Constructor<T> {
return this.factory(value) && typeof value.prototype === 'object'
},
}
import {AsyncFactory} from './async-factory'
import {Constructor} from './constructor'
import {Factory} from './factory'
export type Use<T> = Constructor<T> | Factory<T> | AsyncFactory<T> | T
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment