UNPKG

@mooncake/container

Version:

DI(dependency injection) container for JavaScript and TypeScript.

265 lines 7.95 kB
"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