Skip to content

Instantly share code, notes, and snippets.

@guiseek
Created May 16, 2025 23:57
Show Gist options
  • Save guiseek/c4fef702015800b25d671a6c800049a3 to your computer and use it in GitHub Desktop.
Save guiseek/c4fef702015800b25d671a6c800049a3 to your computer and use it in GitHub Desktop.
export class Alias<T> {
name: string
constructor(name: string) {
this.name = name
return this as Alias<T>
}
}
import type {Ref, Use} from './types'
import {provide} from './di'
export function singleton<T>(dep: Ref<unknown>[] = [], ref?: Ref<T>) {
return (use: Use<T>) => {
ref ??= use as Ref<T>
dep ??= []
return provide<T>({ref, use, dep, scope: 'singleton'})
}
}
export function transient<T>(dep: Ref<unknown>[] = [], ref?: Ref<T>) {
return (use: Use<T>) => {
ref ??= use as Ref<T>
dep ??= []
return provide<T>({ref, use, dep, scope: 'transient'})
}
}
import type {Params, Provider, Ref, Use} from './types'
import {container, registry} from './internal'
import {is} from './is'
const use = <T>(ref: Ref<T>) => {
const provider = registry.get(ref)
if (provider && provider.scope && provider.scope === 'transient') {
return sync(ref)
}
const value = container.get(ref)
if (!value) {
throw `${ref.name} not registered`
}
return value
}
const sync = <T>(ref: Ref<T>) => {
const provider = registry.get(ref)
const concrete = (provider.use ?? provider.ref) as Use<T>
if (is.asyncFactory(concrete)) {
throw `Provider with 'transient' scope cannot use async factories`
}
const deps = (provider.dep ?? []).map(use) as Params<Use<T>>
if (is.constructor(concrete)) {
return new concrete(...deps)
}
if (is.factory(concrete)) {
return concrete(...deps)
}
if (is.object(concrete)) {
return {...concrete}
}
return concrete
}
const async = async <T>(ref: Ref<T>) => {
const provider = registry.get(ref)
const concrete = (provider.use ?? provider.ref) as Use<T>
const deps = (provider.dep ?? []).map(use) as Params<Use<T>>
if (is.constructor(concrete)) {
return new concrete(...deps)
}
if (is.asyncFactory(concrete)) {
return await concrete(...deps)
}
if (is.factory(concrete)) {
return concrete(...deps)
}
return concrete
}
const load = async () => {
for (const [ref, provider] of registry.entries()) {
const scope = provider.scope ?? 'singleton'
if (scope === 'transient') {
continue
}
const deps = provider.dep ?? []
if (container.has(ref)) {
continue
}
for (const dep of deps) {
if (container.has(dep)) {
continue
}
if (!registry.has(dep)) {
throw `${dep.name} not registered`
}
container.set(dep, await async(dep))
}
container.set(ref, await async(ref))
}
}
const provide = <T>(provider: Provider<T>) => {
registry.set(provider.ref, provider)
}
const provides = <T>(...providers: Provider<T | unknown>[]) => {
providers.forEach(provide)
}
const boot = (fn: VoidFunction) => {
return load().then(fn)
}
export {use, load, boot, provide, provides}
export * from './decorators'
export * from './alias'
export * from './types'
export * from './di'
export * from './is'
import type {Provider, Ref} from './types'
const _container = new Map()
const container = {
has<T>(ref: Ref<T>) {
return _container.has(ref)
},
get<T>(ref: Ref<T>): T {
return _container.get(ref)
},
set<T>(ref: Ref<T>, dependency: T) {
_container.set(ref, dependency)
},
entries() {
return _container.entries()
},
}
const _registry = new Map()
const registry = {
has<T>(ref: Ref<T>) {
return _registry.has(ref)
},
get<T>(ref: Ref<T>): Provider<T> {
return _registry.get(ref)
},
set<T>(ref: Ref<T>, provider: Provider<T>) {
_registry.set(ref, provider)
},
entries() {
return _registry.entries()
},
}
export {container, registry}
import type {AsyncFactory, Constructor, Factory} from './types'
const is = {
object<T, K extends keyof T>(value: unknown): value is Record<K, T[K]> {
return typeof value === 'object'
},
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'
},
}
export {is}
import type {Alias} from './alias'
export type Abstract<T, P extends unknown[] = never[]> = abstract new (
...args: P
) => T
export type Constructor<T, P extends unknown[] = never[]> = new (
...args: P
) => T
export interface Factory<T, P extends unknown[] = never[]> {
(...args: P): T
}
export interface AsyncFactory<T, P extends unknown[] = never[]> {
(...args: P): Promise<T>
}
export type Params<T> = T extends Constructor<unknown>
? ConstructorParameters<T>
: T extends Factory<unknown>
? Parameters<T>
: never[]
export type Ref<T, P extends unknown[] = never[]> =
| Abstract<T, P>
| Constructor<T, P>
| Alias<T>
export type Use<T, P extends unknown[] = never[]> =
| Constructor<T, P>
| Factory<T, P>
| AsyncFactory<T, P>
| T
export type Scope = `singleton` | `transient`
export interface Provider<T> {
ref: Ref<T>
use?: Use<T>
dep?: Ref<unknown>[]
scope?: Scope
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment