@mooncake/container
Version:
DI(dependency injection) container for JavaScript and TypeScript.
265 lines • 7.95 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const async_hooks_map_1 = require("async-hooks-map");
const decorators_1 = require("./decorators");
const registion_1 = require("./registion");
const types_1 = require("./types");
class Container {
constructor() {
this._map = new async_hooks_map_1.AsyncHookMap();
this._lazyBinds = {};
this._autoBinds = new Set();
}
/**
* alias of bindValue
*
* @template T
* @param {ID<T>} id
* @param {T} value
* @memberof ContainerClass
*/
set(id, value, opt = {}) {
return this.bindValue(id, value, opt);
}
/**
* bind with value, it will be singleton
*
* @template T
* @param {ID<T>} id
* @param {T} value
* @memberof ContainerClass
*/
bindValue(id, value, opt = {}) {
const registion = new registion_1.ValueRegistion(value, opt ? opt.scope : undefined);
if (!opt.scope || this._map.hasName(opt.scope)) {
this._map.set(id, registion);
}
else {
const map = this._map.parent(opt.scope);
if (map) {
map.set(id, registion);
}
else {
this._bindLazy(opt.scope, id, registion);
}
}
return this;
}
/**
* bind lazy
*
* @template T
* @param {ID<T>} id
* @param {() => T} func
* @param {BindOption} [opt]
* @memberof ContainerClass
*/
bind(id, creater, opt = {}) {
const registion = new registion_1.FactoryRegistion({ create: creater }, opt.singleton || false, opt.scope);
if (!opt.scope || this._map.hasName(opt.scope)) {
this._map.set(id, registion);
}
else {
const map = this._map.parent(opt.scope);
if (map) {
map.set(id, registion);
}
else {
this._bindLazy(opt.scope, id, registion);
}
}
return this;
}
bindFactory(id, factory, opt = {}) {
if (typeof factory === 'function') {
// factory is constructor
const factoryOpt = {
scope: opt.scope,
singleton: opt.factorySingleton === false ? false : true
};
this.bindClass(factory, factoryOpt);
this.bind(id, () => {
const f = this.get(factory, opt.scope);
return f.create();
}, opt);
}
else {
this.bind(id, factory.create.bind(factory));
}
return this;
}
/**
*
*
* @template T
* @param {ID<T>} id
* @param {Function} cls
* @param {BindOption} [opt={}]
* @memberof Container
*/
bindClassWithId(id, cls, opt = {}) {
const registion = new registion_1.ClassRegistion(cls, opt.singleton || false, opt.scope);
if (!opt.scope || this._map.hasName(opt.scope)) {
this._map.set(id, registion);
}
else {
const map = this._map.parent(opt.scope);
if (map) {
map.set(id, registion);
}
else {
this._bindLazy(opt.scope, id, registion);
}
}
return this;
}
/**
* bind a class, ignore the decoractors
*
* @template T
* @param {Constructor<T>} cls
* @param {BindOption} [opt]
* @memberof ContainerClass
*/
bindClass(cls, opt = {}) {
const registion = new registion_1.ClassRegistion(cls, opt.singleton || false, opt.scope);
if (!opt.scope || this._map.hasName(opt.scope)) {
this._map.set(cls, registion);
}
else {
const map = this._map.parent(opt.scope);
if (map) {
map.set(cls, registion);
}
else {
this._bindLazy(opt.scope, cls, registion);
}
}
return this;
}
bindAlias(id, toId, opt = {}) {
const registion = new registion_1.AliasRegistion(toId);
if (!opt.scope || this._map.hasName(opt.scope)) {
this._map.set(id, registion);
}
else {
const map = this._map.parent(opt.scope);
if (map) {
map.set(id, registion);
}
else {
this._bindLazy(opt.scope, id, registion);
}
}
return this;
}
/**
* auto bind a class with decorectors
*
* @param {Function} target
* @memberof Container
*/
autoBind(target) {
if (this._autoBinds.has(target)) {
return this;
}
this._autoBinds.add(target);
const actions = decorators_1.getBindActions(target) || [];
for (let action of actions) {
action(target, this);
}
return this;
}
/**
* create name or alias a async scope
*
* @param {string} name
* @memberof Container
*/
aliasScope(name) {
if (this._map.parent(name)) {
throw new Error('scope name should not be same with parent');
}
this._map.alias(name);
if (this._lazyBinds[name]) {
this._lazyBinds[name].forEach((reg, id) => {
this._map.set(id, reg);
});
}
return this;
}
/**
* detect a scope
*
* @param {string} name
* @returns
* @memberof Container
*/
hasScope(name) {
return this._map.hasName(name) || !!this._map.parent(name);
}
/**
* fill a instance by it't prop decorectors
*
* @param {*} target
* @param {string} [fromScope]
* @memberof Container
*/
fill(target, fromScope) {
const injectProperties = decorators_1.getInjectMetas(target);
for (let m of injectProperties) {
const prop = m.property;
if (target[prop]) {
continue;
}
let value;
if (m.resolver) {
value = m.resolver(this, m.scope || fromScope);
}
else if (m.id) {
value = this.get(m.id, m.scope || fromScope);
}
else {
const type = Reflect.getMetadata("design:type", target, prop);
value = this.get(type, m.scope || fromScope);
}
if (m.required && typeof value === 'undefined') {
throw new types_1.InjectError('cant resolve depency,bug target prop is required');
}
target[prop] = value;
}
}
get(id, fromScope) {
const map = fromScope ? this._map.closest(fromScope) : this._map;
let reg = map.get(id);
if (reg) {
return reg.getInstance(this, fromScope);
}
if (typeof id === 'function') {
if (!this._autoBinds.has(id)) {
this.autoBind(id);
return this.get(id);
}
const reg = new registion_1.ClassRegistion(id, false);
return reg.getInstance(this, fromScope);
}
}
/**
* get the distance of scope which has the key
*
* @param {ID<any>} id
* @returns
* @memberof Container
*/
distance(id) {
return this._map.distance(id);
}
_bindLazy(scope, id, registion) {
if (!this._lazyBinds[scope]) {
this._lazyBinds[scope] = new Map();
}
this._lazyBinds[scope].set(id, registion);
}
}
exports.Container = Container;
//# sourceMappingURL=container.js.map