UNPKG

locustjs-locator

Version:

A locustjs library implementing Service Locator pattern to provide loose coupling through IoC (Inversion of Control).

563 lines (467 loc) 18.9 kB
import { isArray, isFunction, isEmpty, isSubClassOf, isSomeObject, isObject, isNumeric } from 'locustjs-base' import { throwIfInstantiateAbstract, throwNotImplementedException } from 'locustjs-exception'; import Enum from 'locustjs-enum'; const Resolve = Enum.define({ PerRequest: 0, // new instance for each request PerApp: 1, // single instance per app (uses localStorage) PerPage: 2, // single instance per page load PerSession: 3 // single instance per browser session (uses sessionStorage) }, 'Resolve'); class LocatorBase { constructor() { throwIfInstantiateAbstract(LocatorBase, this); } serialize(instance) { const result = JSON.stringify(instance); return result; } deserialize(raw) { const result = JSON.parse(raw); return result; } register(abstraction, concretion, resolveType = Resolve.PerRequest, state = null) { throwNotImplementedException('register'); } registerFactory(abstraction, factory, resolveType = Resolve.PerRequest, state = null) { throwNotImplementedException('registerFactory'); } registerInstance(abstraction, instance, resolveType = Resolve.PerRequest, state = null) { throwNotImplementedException('registerInstance'); } resolveBy(abstraction, state, ...args) { throwNotImplementedException('resolveBy'); } resolve(abstraction, ...args) { throwNotImplementedException('resolve'); } remove(abstraction, state = null) { throwNotImplementedException('remove'); } exists(abstraction, state = null) { throwNotImplementedException('exists'); } indexOf(abstraction, state = null) { throwNotImplementedException('indexOf'); } getLocalStorage() { throwNotImplementedException('getLocalStorage'); } setLocalStorage(storage) { throwNotImplementedException('setLocalStorage'); } getSessionStorage() { throwNotImplementedException('getSessionStorage'); } setSessionStorage(storage) { throwNotImplementedException('setSessionStorage'); } get length() { throwNotImplementedException('length'); } } class DefaultStorage { constructor() { this._data = {}; } getItem(key) { return (this._data[key] || '').toString(); } setItem(key, value) { this._data[key] = (value || '').toString(); } removeItem(key) { delete this._data[key]; } get length() { return Object.keys(this._data).length; } clear() { this._data = {} } } class DefaultLocator extends LocatorBase { constructor(config) { super(); this.config = Object.assign({ throwOnRegisterExistingAbstractions: false, logger: { log: (...args) => console.log(...args) } }, config); this.__entries = []; this.__localStorage = (typeof window !== 'undefined' && window.localStorage) || new DefaultStorage(); this.__sessionStorage = (typeof window !== 'undefined' && window.sessionStorage) || new DefaultStorage(); this.id = this.constructor.name; } getLocalStorage() { return this.__localStorage; } setLocalStorage(storage) { this.__localStorage = storage; } getSessionStorage() { return this.__sessionStorage; } setSessionStorage(storage) { this.__sessionStorage = storage; } get length() { return this.__entries.length; } _danger(...args) { if (isSomeObject(this.config.logger)) { if (isFunction(this.config.logger.danger)) { this.config.logger.danger(...args) } else if (isFunction(this.config.logger.log)) { this.config.logger.log(...args) } } } _debug(...args) { if (isSomeObject(this.config.logger)) { if (isFunction(this.config.logger.debug)) { this.config.logger.debug(...args) } else if (isFunction(this.config.logger.log)) { this.config.logger.log(...args) } } } _registrationExistence(abstraction, concretion, factory, instance, resolveType = Resolve.PerRequest, state = null) { let result = false; let errorMessage; if (!isFunction(factory) && isEmpty(instance)) { result = this.__entries.find(e => e.abstraction == abstraction && e.concretion == concretion && e.resolveType == resolveType && e.state == state); if (result) { errorMessage = `registration entry for abstraction '${abstraction.name}' and state '${state}' already exists.`; } } else if (isFunction(factory)) { result = this.__entries.find(e => e.abstraction == abstraction && e.factory == factory && e.resolveType == resolveType && e.state == state); if (result) { errorMessage = `registration entry for abstraction '${abstraction.name}' based on specified factory and state '${state}' already exists.`; } } else if (!isEmpty(instance)) { result = this.__entries.find(e => e.abstraction == abstraction && e.instance == instance && e.resolveType == resolveType && e.state == state); if (result) { errorMessage = `registration entry for abstraction '${abstraction.name}' based on specified instance and state '${state}' already exists.`; } } if (result) { if (this.config.throwOnRegisterExistingAbstractions) { throw errorMessage } else { this._danger(errorMessage); } } return result; } _log(...args) { if (isSomeObject(this.config.logger)) { if (isFunction(this.config.logger.log)) { this.config.logger.log(...args) } } } _validateAbstraction(abstraction) { if (!isFunction(abstraction)) { throw `Expected class or constructor function for the abstraction.` } if (abstraction.name.length == 0) { throw 'abstraction cannot be anonymous. It must have a name.' } return abstraction; } _validateConcretion(concretion, abstraction) { if (!isFunction(concretion)) { throw `Invalid concretion (class or constructor function expected).` } if (!isSubClassOf(concretion, abstraction)) { throw 'Concretion must be a subclass of abstraction.' } return concretion; } _validateInstance(abstraction, instance) { let result = instance; if (isEmpty(instance)) { throw `no instance specified.` } result = isFunction(instance) ? instance(this) : instance; if (!(result instanceof abstraction)) { throw 'instance must be a subclass of abstraction.' } return result; } _validateFactory(factory) { if (!isFunction(factory)) { throw 'Invalid factory. Factory must be a function' } return factory; } _validateResolveType(resolveType) { return Resolve.getNumber(resolveType, Resolve.PerRequest); } _executeFactory(entry) { let result; try { result = entry.factory(this); } catch (e) { this._danger(e); throw `${entry.abstraction.name}: factory execution failed.`; } if (isEmpty(result)) { throw `${entry.abstraction.name}: factory returned nothing.`; } if (!(result instanceof entry.abstraction)) { throw `factory returned incorrect type. expected ${entry.abstraction.name} object.` } return result; } _getStorageName(abstraction) { return this.id + '.' + abstraction.name } _getStoredInstance(entry, storage, ...args) { let result; try { const key = this._getStorageName(entry.abstraction); const raw = storage.getItem(key); if (raw) { result = this.deserialize(raw); if (result) { const temp = this._createInstance(entry, ...args); Object.setPrototypeOf(result, Object.getPrototypeOf(temp)); } } } catch (e) { this._danger(e); } return result; } _setStoredInstance(entry, storage, instance) { const key = this._getStorageName(entry.abstraction); const raw = this.serialize(instance); storage.setItem(key, raw); } _getDependencyArgs(abstraction, args) { let result = args[0][abstraction]; if (isEmpty(result)) { result = []; } else { if (!isArray(result)) { if (isObject(result) && isArray(result.args)) { result = result.args; } else { result = [result] } } } return result; } _getDependencyState(abstraction, args) { let result = null; let dependencyConfig = args[0][abstraction]; if (isObject(dependencyConfig)) { result = dependencyConfig.state; } return result; } _createInstance(entry, ...args) { let result; const hasDependencyArgs = args.length == 1 && isObject(args[0]) && isArray(args[0].args); if (isArray(entry.abstraction.dependencies)) { const dependencies = []; for (let dependencyAbstract of entry.abstraction.dependencies) { let _dependencyConcrete; let _dependencyArgs = []; let _dependencyState = null; let _dependencyAbstract; do { if (isArray(dependencyAbstract)) { if (dependencyAbstract.length) { _dependencyAbstract = dependencyAbstract[0]; if (hasDependencyArgs) { _dependencyArgs = this._getDependencyArgs(_dependencyAbstract, args); _dependencyState = this._getDependencyState(_dependencyAbstract, args); } if (_dependencyState == null) { _dependencyState = dependencyAbstract.length > 1 ? dependencyAbstract[1] : null; } _dependencyArgs = [...dependencyAbstract.slice(2), ..._dependencyArgs] } break; } if (isObject(dependencyAbstract)) { _dependencyAbstract = dependencyAbstract.dependency; if (hasDependencyArgs) { _dependencyArgs = this._getDependencyArgs(_dependencyAbstract, args); _dependencyState = this._getDependencyState(_dependencyAbstract, args); } if (_dependencyState == null) { _dependencyState = dependencyAbstract.state } if (isArray(dependencyAbstract.args)) { _dependencyArgs = [...dependencyAbstract.args, ..._dependencyArgs] } else { _dependencyArgs = [...[dependencyAbstract.args], ..._dependencyArgs] } break; } _dependencyAbstract = dependencyAbstract; if (hasDependencyArgs) { _dependencyArgs = this._getDependencyArgs(dependencyAbstract, args); _dependencyState = this._getDependencyState(_dependencyAbstract, args); } } while (false); if (isFunction(_dependencyAbstract)) { _dependencyConcrete = this.resolveBy(_dependencyAbstract, _dependencyState, ..._dependencyArgs); dependencies.push(_dependencyConcrete); } } let _args; if (hasDependencyArgs) { _args = [...dependencies, ...args[0].args]; } else { _args = [...dependencies, ...args]; } result = new entry.concretion(..._args); } else { result = new entry.concretion(...args); } return result; } _getInstance(entry, ...args) { let result; if (!isEmpty(entry.instance)) { result = entry.instance; } else if (isFunction(entry.factory)) { result = this._executeFactory(entry); } else { result = this._createInstance(entry, ...args); } return result; } register(abstraction, concretion, resolveType = Resolve.PerRequest, state = null) { abstraction = this._validateAbstraction(abstraction); concretion = this._validateConcretion(concretion, abstraction); resolveType = this._validateResolveType(resolveType); if (!this._registrationExistence(abstraction, concretion, null, null, resolveType, state)) { this.__entries.push({ abstraction, concretion, resolveType, state }); return this.__entries.length - 1; } return -1; } registerFactory(abstraction, factory, resolveType = Resolve.PerRequest, state = null) { abstraction = this._validateAbstraction(abstraction); factory = this._validateFactory(factory); resolveType = this._validateResolveType(resolveType); if (!this._registrationExistence(abstraction, null, factory, null, resolveType, state)) { this.__entries.push({ abstraction, factory, resolveType, state }); return this.__entries.length - 1; } return -1; } registerInstance(abstraction, instance, resolveType = Resolve.PerRequest, state = null) { abstraction = this._validateAbstraction(abstraction); instance = this._validateInstance(abstraction, instance); resolveType = this._validateResolveType(resolveType); if (!this._registrationExistence(abstraction, null, null, instance, resolveType, state)) { this.__entries.push({ abstraction, instance, resolveType, state }); return this.__entries.length - 1; } return -1; } getConfig(abstraction, state = null) { const result = this.__entries.find(e => e.abstraction === abstraction && e.state == state); return { ...result }; } resolveBy(abstraction, state, ...args) { let result, storage; if (arguments.length == 0 || isEmpty(abstraction)) { throw `Please specify abstraction`; } else { const entry = this.getConfig(abstraction, state); if (isEmpty(entry)) { throw `no registration found for ${abstraction.name || 'anonymous'}`; } switch (entry.resolveType) { case Resolve.PerRequest: result = this._getInstance(entry, ...args); break; case Resolve.PerPage: result = this._getInstance(entry, ...args); this.__entries[abstraction].instance = result; break; case Resolve.PerSession: storage = this.getSessionStorage(); result = this._getStoredInstance(entry, storage, ...args); if (isEmpty(result)) { result = this._getInstance(entry, ...args); this._setStoredInstance(entry, storage, result); } break; case Resolve.PerApp: storage = this.getLocalStorage(); result = this._getStoredInstance(entry, storage, ...args); if (isEmpty(result)) { result = this._getInstance(entry, ...args); this._setStoredInstance(entry, storage, result); } break; } } return result; } resolve(abstraction, ...args) { return this.resolveBy(abstraction, null, ...args) } indexOf(abstraction, state = null) { const result = this.__entries.findIndex(e => e.abstraction === abstraction && e.state == state); return result; } remove(abstraction, state = null) { let result = false; if (isNumeric(abstraction)) { const index = parseInt(abstraction); if (index >= 0 && index < this.__entries.length) { this.__entries.splice(index, 1); result = true; } } else { const index = this.indexOf(abstraction, state); if (index >= 0) { this.__entries.splice(index, 1); result = true; } } return result; } exists(abstraction, state = null) { const index = this.indexOf(abstraction, state); return index >= 0; } } let __locator_instance = new DefaultLocator(); class Locator { static get Instance() { return __locator_instance; } static set Instance(value) { if (isEmpty(value)) { throw `no object given to be set as current locator.` } else if (!isFunction(value.constructor)) { throw `locator must have a constructor` } else if (!isSubClassOf(value.constructor, LocatorBase)) { throw `locator must be a subclass of LocatorBase` } __locator_instance = value; } } export default Locator; export { LocatorBase, DefaultLocator, DefaultStorage, Resolve }