@mooncake/container
Version:
DI(dependency injection) container for JavaScript and TypeScript.
230 lines (202 loc) • 6.24 kB
text/typescript
import {
BindOption,
Constructor,
ContainerBinder,
ContainerResolver,
Factory as TypeFactory,
ID,
InjectOption,
} from './types';
export const SYMBOL_INJECT_META = Symbol.for('@mooncake/container:inject')
export const SYMBOL_BIND_META = Symbol.for('@mooncake/container:bind')
export type ResolveMethod = (container: ContainerResolver, scope?: string) => any
export interface InjectMeta {
required: boolean
index?: number
property?: string
scope?: string
resolver?: Function
id?: any
}
export function getInjectMetas (target: Function): InjectMeta[] {
return (Reflect as any).getMetadata(SYMBOL_INJECT_META, target) as InjectMeta[] || []
}
function addInjectMeta (target: any, m: InjectMeta) {
const arr = (Reflect as any).getMetadata(SYMBOL_INJECT_META, target) as InjectMeta[]
if (arr) {
arr.push(m)
} else {
(Reflect as any).defineMetadata(SYMBOL_INJECT_META, [m], target)
}
}
export type BindMethod<T=any> = (clz: Constructor<T>, container: ContainerBinder) => void
export function getBindActions (target: Function) {
return (Reflect as any).getMetadata(SYMBOL_BIND_META, target) as BindMethod[] || []
}
function addBindAction (target: Function, action: BindMethod<any>) {
let arr = (Reflect as any).getMetadata(SYMBOL_BIND_META, target) as BindMethod[]
if (!arr) {
arr = [];
(Reflect as any).defineMetadata(SYMBOL_BIND_META, arr, target)
}
arr.push(action)
}
export type OptionalInjectOption = Pick<InjectOption, Exclude<keyof InjectOption, "required">>
/**
* Inject optional, undefined will be inject when can not resolve dependency
*
* @export
* @param {ID<any>} [id]
* @returns
*/
export function InjectOptional (option?: ID<any> | (OptionalInjectOption & { id?: ID<any> })) {
if (!option) {
return Inject()
}
if (typeof option !== 'object') {
return Inject({ id: option, required: false })
} else {
return Inject(Object.assign(option, { required: false }))
}
}
/**
* Inject with container
*
* @export
* @param {ID<any>} [id]
* @returns
*/
export function Inject (option?: ID<any> | (InjectOption & { id?: ID<any> })) {
let id: symbol | string | Function | undefined
const opt: InjectOption = { required: true }
if (typeof option === 'object') {
id = option.id
Object.assign(opt, option)
} else if (option) {
id = option
}
return function (target: any, prop?: any, index?: any) {
addInjectMeta(target, {
required: opt.required,
scope: opt.scope,
id: id,
property: prop,
index: index
})
}
}
/**
* Inject with custom rules
*
* @export
* @param {(container: ContainerResolver) => any} resolve
* @returns
*/
export function InjectRaw (resolveMethod: ResolveMethod, opt: InjectOption = { required: true }) {
return function (target: any, prop?: string, index?: any) {
addInjectMeta(target, {
required: opt.required,
scope: opt.scope,
resolver: resolveMethod,
property: prop,
index: index
})
}
}
/**
* customize bind Action
*
* @export
* @template T
* @param {BindMethod<T>} action
* @returns
*/
export function BindAction<T extends Function> (action: BindMethod<T>) {
return function (target: T) {
addBindAction(target, action)
}
}
export function Alias (id: symbol | string | Function, fromId?: symbol | string | Function, opt?: BindOption) {
opt = Object.assign({ singleton: true, scope: 'root' }, opt)
return function (target: any) {
const action: BindMethod<any> = (clz, c) => c.bindAlias(id, fromId || target, opt)
addBindAction(target, action)
}
}
export function Factory<T> (factory: Constructor<TypeFactory<T>> | TypeFactory<T>, opt?: BindOption) {
opt = Object.assign({ singleton: true, scope: 'root' }, opt)
return function (target: Constructor<T>) {
const action: BindMethod<T> = (clz, c) => {
c.bindFactory(target, factory)
}
addBindAction(target, action)
}
}
/**
* bind a implement class for this class
*
* @export
* @template T
* @param {() => Constructor<T>} implementClassGetter
* @param {BindOption} [opt]
* @returns
*/
export function Implement<T> (implementClassGetter: () => Constructor<T>, opt?: BindOption) {
opt = Object.assign({ singleton: true, scope: 'root' }, opt)
return function (target: Constructor<T>) {
const action: BindMethod<T> = (clz, c) => {
c.bindClassWithId(target, implementClassGetter())
}
addBindAction(target, action)
}
}
/**
* mark class as a Service
* default opt: {singleton: true, scope: 'root'}
*
* @export
* @param {BindOption} [opt]
* @returns {(cls: Function) => void}
*/
export function Service (opt?: BindOption): (cls: Function) => void;
/**
* mark class as a Service with a id
* default opt: {singleton: true, scope: 'root'}
*
* @export
* @param {(symbol | string | Function)} id
* @param {BindOption} [opt]
* @returns {(cls: Function) => void}
*/
export function Service (id: symbol | string | Function, opt?: BindOption): (cls: Function) => void;
export function Service (id?: symbol | string | Function | BindOption, opt?: BindOption) {
return function (target: Function) {
const defaultOpt = {
singleton: true, scope: 'root'
}
let _id: ID<any> = target
if (!id) {
opt = defaultOpt
} else if (typeof id === 'object') {
opt = Object.assign(defaultOpt, id)
} else {
_id = id
opt = Object.assign(defaultOpt, opt)
}
const action: BindMethod<any> = (clz, c) => c.bindClassWithId(_id, clz, opt)
addBindAction(target, action)
}
}
/**
* alias of Service. singleton alwas be true
*
* @export
* @param {Pick<BindOption, 'scope'>} [opt={ scope: 'root' }]
* @returns
*/
export function Singleton (opt: Pick<BindOption, 'scope'> = { scope: 'root' }) {
return Service({
singleton: true,
scope: opt.scope || 'root'
})
}