chain-able
Version:
interfaces that describe their intentions.
289 lines (274 loc) • 7.67 kB
JavaScript
const Chainable = require('./Chainable')
const dopemerge = require('./deps/dopemerge')
const reduce = require('./deps/reduce')
const reduceEntries = require('./deps/reduce/entries')
const isFunction = require('./deps/is/function')
const ObjectKeys = require('./deps/util/keys')
const getMeta = require('./deps/meta')
const SHORTHANDS_KEY = require('./deps/meta/shorthands')
/**
* @desc ChainedMapBase composer
* @alias ComposeMap
* @type {Composer}
* @method compose
* @memberOf ChainedMapBase
*
* @param {Class | Object | Composable} [SuperClass=Chainable] class to extend
* @return {Class} ChainedMapBase
*
* @example
*
* const heh = class {}
* const composed = ChainedMapBase.compose(heh)
* const hehchain = new Composed()
* hehchain instanceof heh
* //=> true
*
*/
const CMC = SuperClass => {
/**
* @classdesc this is to avoid circular requires
* because MergeChain & MethodChain extend this
* yet .method & .merge use those chains
*
* @since 4.0.0-alpha.1
* @inheritdoc
* @class ChainedMapBase
* @member ChainedMapBase
* @category Chainable
* @extends {Chainable}
* @type {Chainable}
*
* @types ChainedMapBase
* @tests ChainedMap
*
* @prop {Meta} meta
* @prop {Map} store
*
* {@link https://ponyfoo.com/articles/es6-maps-in-depth pony-map}
* {@link https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map mozilla-map}
* @see {@link pony-map}
* @see {@link mozilla-map}
*
* @see ChainedMap
* @see Chainable
* @see MergeChain
* @see MethodChain
* @see ChainedMap
*
*/
return class ChainedMapBase extends SuperClass {
/**
* @param {ChainedMapBase | Chainable | ParentType | any} parent ParentType
* @constructor
*
* @example
*
* class Eh extends ChainedMapBase {}
* Object.keys(eh)
* //=> ['store', 'meta']
*
*/
constructor(parent) {
super(parent)
this.store = new Map()
this.meta = getMeta(this)
}
/**
* @desc tap a value with a function
* @modifies this.store.get(name)
* @memberOf ChainedMapBase
* @since 0.7.0
* @since 4.0.0-alpha.1 <- moved from transform & shorthands
*
* @param {string | any} name key to `.get`
* @param {Function} fn function to tap with
* @return {Chain} @chainable
*
* {@link https://github.com/sindresorhus/awesome-tap awesome-tap}
* {@link https://github.com/midknight41/map-factory map-factory}
* {@link https://github.com/webpack/tapable tapable}
* @see {@link tapable}
*
* @see ChainedMapBase.set
* @see ChainedMapBase.get
*
* @example
*
* chain
* .set('moose', {eh: true})
* .tap('moose', moose => {moose.eh = false; return moose})
* .get('moose')
*
* // => {eh: false}
*
* @example
*
* const entries = new Chain()
* .set('str', 'emptyish')
* .tap('str', str => str + '+')
* .set('arr', [1])
* .tap('arr', arr => arr.concat([2]))
* .entries()
*
* //=> {str: 'emptyish+', arr: [1, 2]}
*
*/
tap(name, fn) {
return this.set(name, fn(this.get(name), dopemerge))
}
/**
* @desc checks each property of the object
* calls the chains accordingly
*
* @memberOf ChainedMapBase
* @since 0.5.0
*
* @param {Object} obj object with functions to hydrate from
* @return {Chainable} @chainable
*
* @TODO could also add parsing stringified
*
* @example
*
* const from = new Chain().from({eh: true})
* const eh = new Chain().set('eh', true)
* eq(from, eh)
* // => true
*
*/
from(obj) {
const keys = ObjectKeys(obj)
for (let k = 0; k < keys.length; k++) {
const key = keys[k]
const val = obj[key]
const fn = this[key]
if (fn && fn.merge) {
fn.merge(val)
}
else if (isFunction(fn)) {
fn.call(this, val)
}
else {
this.set(key, val)
}
}
return this
}
/**
* @desc shorthand methods, from strings to functions that call .set
* @since 0.4.0
* @memberOf ChainedMapBase
*
* @param {Array<string>} methods decorates/extends an object with new shorthand functions to get/set
* @return {ChainedMapBase} @chainable
*
* @example
*
* const chain1 = new Chain()
* chain1.extend(['eh'])
*
* const chain2 = new Chain()
* chain2.eh = val => this.set('eh', val)
*
* eq(chain2.eh, chain1.eh)
* //=> true
*
*/
extend(methods) {
methods.forEach(method => {
this.meta(SHORTHANDS_KEY, method)
this[method] = value => this.set(method, value)
})
return this
}
/**
* @desc spreads the entries from ChainedMapBase.store (Map)
* return store.entries, plus all chain properties if they exist
* @memberOf ChainedMapBase
*
* @since 4.0.0 <- improved reducing
* @since 0.4.0
*
* @param {boolean} [chains=false] if true, returns all properties that are chains
* @return {Object}
*
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries mozilla-map-entries}
* @see {@link mozilla-map-entries}
*
* @example
*
* map.set('a', 'alpha').set('b', 'beta').entries()
* //=> {a: 'alpha', b: 'beta'}
*
*/
entries(chains = false) {
const reduced = reduce(this.store)
if (chains === false) return reduced
const reducer = reduceEntries(reduced)
reducer(this)
reducer(reduced)
return reduced
}
/**
* @desc get value for key path in the Map store
* ❗ `debug` is a special key and is *not* included into .store
* it goes onto .meta
*
* @memberOf ChainedMapBase
* @since 4.0.0 <- moved debug here
* @since 0.4.0
*
* @param {Primitive} key Primitive data key used as map property to reference the value
* @return {any} value in .store at key
*
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/get mozilla-map-get}
* @see {@link mozilla-map-get}
*
* @example
*
* const chain = new Chain()
* chain.set('eh', true)
* chain.get('eh')
* //=> true
*
* chain.get('nope')
* //=> undefined
*
*/
get(key) {
if (key === 'debug') return this.meta.debug
return this.store.get(key)
}
/**
* @desc sets the value using the key on store
* adds or updates an element with a specified key and value
*
* @memberOf ChainedMapBase
* @since 0.4.0
*
* @param {Primitive} key Primitive to reference the value
* @param {any} value any data to store
* @return {ChainedMapBase} @chainable
*
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/set mozilla-map-set}
* @see {@link mozilla-map-set}
* @see ChainedMapBase.store
*
* @example
*
* const chain = new Chain()
* chain.set('eh', true)
* chain.get('eh')
* //=> true
*
*/
set(key, value) {
this.store.set(key, value)
return this
}
}
}
const cmc = CMC(Chainable)
cmc.compose = CMC
module.exports = cmc