@nodeswork/kiws
Version:
Koa-based Injectable Web Service
207 lines (205 loc) • 7.34 kB
JavaScript
;
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