Created
May 16, 2025 23:57
-
-
Save guiseek/c4fef702015800b25d671a6c800049a3 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 Alias<T> { | |
name: string | |
constructor(name: string) { | |
this.name = name | |
return this as Alias<T> | |
} | |
} |
This file contains hidden or 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 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'}) | |
} | |
} |
This file contains hidden or 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 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} |
This file contains hidden or 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 * from './decorators' | |
export * from './alias' | |
export * from './types' | |
export * from './di' | |
export * from './is' |
This file contains hidden or 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 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} |
This file contains hidden or 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 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} |
This file contains hidden or 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 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