UNPKG

flipchain

Version:

core chaining library, heavily based on [webpack-chain](https://github.com/mozilla-rpweb/webpack-chain)

337 lines (302 loc) 7.69 kB
const deepmerge = require('deepmerge') const Chainable = require('./Chainable') class ChainedMap extends Chainable { /** * @param {any} parent */ constructor(parent) { super(parent) this.shorthands = [] this.chainableMethods = [] this.store = new Map() if (!this.name) this.name = this.constructor.name this.className = this.constructor.name } // @TODO: depreciate these 3 new(parent) { return new this(parent || this) } and() { if (this.parent.parent) return this.parent.parent return this.parent } use(obj) { return this.merge(obj).parent } /** * checks each property of the object * calls the chains accordingly * * @param {Object} obj * @return {Chainable} */ from(obj) { Object.keys(obj).forEach((key) => { const fn = this[key] const value = obj[key] if (this[key] && this[key] instanceof Chainable) { return this[key].merge(value) } else if (typeof this[key] === 'function') { // const fnStr = typeof fn === 'function' ? fn.toString() : '' // if (fnStr.includes('return this') || fnStr.includes('=> this')) { return this[key](value) } else { this.set(key, value) } }) return this } /** * @description shorthand methods, from strings to functions that call .set * @param {Array<string>} methods * @return {ChainedMap} */ extend(methods) { this.shorthands = methods methods.forEach((method) => { this[method] = (value) => this.set(method, value) }) return this } /** * @description * clears the map, * goes through this properties, * calls .clear if they are instanceof Chainable or Map * * @return {ChainedMap} */ clear() { this.store.clear() Object.keys(this).forEach((key) => { if (this[key] instanceof Chainable) this[key].clear() if (this[key] instanceof Map) this[key].clear() }) return this } /** * @description spreads the entries from ChainedMap.store (Map) * @return {Object} */ entries() { const entries = [...this.store] if (!entries.length) { return null } return entries.reduce((acc, [key, value]) => { acc[key] = value return acc }, {}) } /** * @description spreads the entries from ChainedMap.store.values * @return {Array<any>} */ values() { return [...this.store.values()] } /** * @param {any} key * @return {any} */ get(key) { return this.store.get(key) } /** * @description sets the value using the key on store * @see ChainedMap.store * @param {any} key * @param {any} value * @return {ChainedMap} */ set(key, value) { this.store.set(key, value) return this } /** * @description concats an array `value` in the store with the `key` * @see ChainedMap.store * @param {any} key * @param {Array<any>} value * @return {ChainedMap} */ concat(key, value) { if (!Array.isArray(value)) value = [value] this.store.set(key, this.store.get(value).concat(value)) return this } /** * @description appends the string value to the current value at the `key` * @see ChainedMap.concat * @param {any} key * @param {string | Array} value * @return {ChainedMap} */ append(key, value) { let existing = this.store.get(value) if (Array.isArray(existing)) { existing.push(value) } else { existing += value } this.store.set(key, existing) return this } /** * @description same as .merge, but will set instead of merge * @see ChainedMap.merge * @param {Object} obj * @return {ChainedMap} */ override(obj) { Object .keys(obj) .forEach((key) => { const value = obj[key] if (this[key] && this[key] instanceof Chainable) { return this[key].override(value) } if (this.shorthands.includes(key)) { return this[key](value) } return this.set(key, value) }) return this } /** * @description same as .merge, but only not `null` & `undefined` values * @see ChainedMap.merge * @param {Object} obj * @return {ChainedMap} */ mergeReal(obj) { Object .keys(obj) .filter(key => obj[key]) .forEach((key) => { const value = obj[key] if (!value) return this if (this[key] && this[key] instanceof Chainable) { return this[key].merge(value) } if (this.shorthands.includes(key)) { const existing = this.get(key) if (existing) { if (existing === value) return this if (typeof existing === 'string') return this[key]([existing, value]) const merged = deepmerge(existing, value) return this[key](merged) } return this[key](value) } // if (this[key]) return this.set(key, value) }) return this } /** * @description merges an object with the current store * @see deepmerge * @param {Object} obj * @return {ChainedMap} */ merge(obj) { if (obj.toConfig !== undefined && obj.toConfig !== null && typeof obj.toConfig === 'function') { const msg = 'when merging two chains, first call .toConfig' const validation = new Error(msg) let log try { log = require('fliplog') .verbose(2) .data(validation) .preset('error') } catch (e) { log = console } log.log(validation) throw validation return this } Object .keys(obj) .forEach((key) => { const value = obj[key] if (this[key] && this[key] instanceof Chainable) return this[key].merge(value) if (this.shorthands.includes(key)) { const existing = this.get(key) if (existing) { const merged = deepmerge(existing, value) return this[key](merged) } return this[key](value) } // if (this[key]) return this.set(key, value) }) return this } /** * @description * goes through the maps, * and the map values, * reduces them to array * then to an object using the reduced values * * @param {Object} obj * @return {Object} */ clean(obj) { return Object.keys(obj).reduce((acc, key) => { const value = obj[key] if (value === undefined) return acc if (Array.isArray(value) && !value.length) return acc if (Object.prototype.toString.call(value) === '[object Object]' && Object.keys(value).length === 0) { return acc } acc[key] = value return acc }, {}) } /** * @param {any} key * @param {Function} [trueBrancher=Function.prototype] * @param {Function} [falseBrancher=Function.prototype] * @return {ChainedMap} */ whenHas(key, trueBrancher = Function.prototype, falseBrancher = Function.prototype) { if (this.has(key) === true) { trueBrancher(this.get(key), this) } else { falseBrancher(false, this) } return this } /** * @description * when the condition is true, * trueBrancher is called, * else, falseBrancher is called * * @param {boolean} condition * @param {Function} [trueBrancher=Function.prototype] * @param {Function} [falseBrancher=Function.prototype] * @return {ChainedMap} */ when(condition, trueBrancher = Function.prototype, falseBrancher = Function.prototype) { if (condition) { trueBrancher(this) } else { falseBrancher(this) } return this } } module.exports = ChainedMap