UNPKG

@nodeswork/kiws

Version:

Koa-based Injectable Web Service

207 lines (205 loc) 7.34 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); require("reflect-metadata"); const _ = require("underscore"); exports.INJECTION_METADATA_KEY = Symbol('kiws:injection'); const INJECTION_TOKEN_KEY = Symbol('kiws:token'); function isConstructor(arg) { return typeof arg === 'function'; } /** * Bean provider to hold injection instances and create beans. */ class BeanProvider { constructor() { this.beans = {}; this.singletons = {}; } /** * Register a new constructor. */ register(val) { let override; if (isConstructor(val)) { override = { provide: val.name, useClass: val, multi: false, }; } else { override = val; } const key = override.provide; const exists = this.beans[key]; if (!override.multi && _.isArray(override.useClass)) { throw new Error(`Bean {${key}} sets multiple classes but not in multi mode`); } if (!override.multi && exists != null && exists.length && exists[0] !== val) { throw new Error(`Bean {${key}} already registered`); } if (this.beans[key] == null) { this.beans[key] = []; } this.beans[key] = _.union(this.beans[key], _.flatten([override.useClass])); } getBean(key, inputs = []) { const instances = this.getBeans(key, inputs, { limit: 1 }); return instances[0]; } getBeans(key, inputs = [], options = {}) { const constructors = this.beans[key]; if (constructors == null) { throw new Error(`bean {${key}} is not registed`); } const firstConstructors = _.first(constructors, options.limit || constructors.length); const result = _.map(firstConstructors, (constructor) => { let injectionMetadata = (Reflect.getMetadata(exports.INJECTION_METADATA_KEY, constructor.prototype) || {}); const args = _.map(injectionMetadata.injects, (inject) => { return inject.isArray ? this.getSingletonBeans(inject.ref) : this.getSingletonBean(inject.ref); }); const instance = new (constructor.bind.apply(constructor, [null].concat(args))); _.each(injectionMetadata.inputs, (input) => { const candidates = _.filter(inputs, (i) => i.constructor.name === input.ref); if (input.isArray) { instance[input.propertyName] = candidates; } else if (candidates.length > 0) { instance[input.propertyName] = candidates[0]; } else if (input.fallback) { instance[input.propertyName] = this.getBean(input.ref, inputs); } }); _.each(injectionMetadata.posts, (post) => { instance[post](); }); return instance; }); return result; } getSingletonBean(key) { const vals = this.getSingletonBeans(key); return vals[0]; } getSingletonBeans(key) { if (!(key in this.singletons)) { this.singletons[key] = this.getBeans(key); } return this.singletons[key]; } getRegisteredBeans() { return _.union(_.flatten(Object.values(this.beans))); } getRegisteredBeanClazz() { return this.beans; } clear() { this.singletons = {}; this.beans = {}; } } exports.BeanProvider = BeanProvider; exports.beanProvider = new BeanProvider(); /** * Property decorator which will be injected from inputs or registerd * constructors. */ function Inject(options = {}) { const inputOptions = _.extend({}, options, { fallback: true }); return Input(inputOptions); } exports.Inject = Inject; /** * Property decorator which will be injected from inputs only. */ function Input(options = {}) { return (target, propertyName) => { const t = Reflect.getMetadata('design:type', target, propertyName); const isArray = t.name === 'Array'; const ref = options.type || t.name; let injectionMetadata = (Reflect.getMetadata(exports.INJECTION_METADATA_KEY, target) || {}); if (injectionMetadata.inputs == null) { injectionMetadata.inputs = []; } if (t.name == null) { throw new Error(`missing type of property ${propertyName}`); } injectionMetadata.inputs.push({ propertyName, ref, isArray, fallback: options.fallback, }); Reflect.defineMetadata(exports.INJECTION_METADATA_KEY, injectionMetadata, target); }; } exports.Input = Input; /** * Decorator for methods which will be executed after construtor. */ function PostConstruct() { return (target, propertyName, descriptor) => { let injectionMetadata = (Reflect.getMetadata(exports.INJECTION_METADATA_KEY, target) || {}); if (injectionMetadata.posts == null) { injectionMetadata.posts = []; } injectionMetadata.posts.push(propertyName); Reflect.defineMetadata(exports.INJECTION_METADATA_KEY, injectionMetadata, target); }; } exports.PostConstruct = PostConstruct; function Token(name) { return (target, propertyKey, parameterIndex) => { const names = Reflect.getOwnMetadata(INJECTION_TOKEN_KEY, target) || []; names[parameterIndex] = name; Reflect.defineMetadata(INJECTION_TOKEN_KEY, names, target); }; } exports.Token = Token; /** * Class decorator to specify tags and metas associated with the constructor. */ function Injectable(options = {}) { return (constructor) => { const injectionMetadata = Reflect.getOwnMetadata(exports.INJECTION_METADATA_KEY, constructor.prototype) || {}; const ct = Reflect.getMetadata('design:paramtypes', constructor); const tokens = Reflect.getOwnMetadata(INJECTION_TOKEN_KEY, constructor) || []; injectionMetadata.name = constructor.name; if (!options.inputs) { injectionMetadata.inputs = []; } injectionMetadata.injects = _.map(ct, (a, idx) => { return { ref: tokens[idx] || a.name, isArray: a.name === 'Array', }; }); if (options.tags) { injectionMetadata.tags = options.tags; } if (options.meta) { injectionMetadata.meta = options.meta; } Reflect.defineMetadata(exports.INJECTION_METADATA_KEY, injectionMetadata, constructor.prototype); }; } exports.Injectable = Injectable; function getInjectionMetadata(constructor) { const metadata = Reflect.getMetadata(exports.INJECTION_METADATA_KEY, constructor.prototype) || { tags: [], meta: {}, posts: [], injects: [], inputs: [], }; return metadata; } exports.getInjectionMetadata = getInjectionMetadata; function defineInjectionMetadata(constructor, metadata) { Reflect.defineMetadata(exports.INJECTION_METADATA_KEY, metadata, constructor.prototype); } exports.defineInjectionMetadata = defineInjectionMetadata; //# sourceMappingURL=injection.js.map