UNPKG

@mooncake/container

Version:

DI(dependency injection) container for JavaScript and TypeScript.

230 lines (202 loc) 6.24 kB
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' }) }