foop
Version:
interfaces that describe their intentions.
356 lines (337 loc) • 10.2 kB
JavaScript
/* FROM-TO: src/ChainedMapBase.js */
/* remapped from ./deps/meta/SHORTHANDS_KEY */
const SHORTHANDS_KEY = require('./SHORTHANDS_KEY.js')
/* remapped from ./Chainable */
const Chainable = require('./Chainable.js')
/* remapped from ./deps/util/keys */
const ObjectKeys = require('./keys.js')
/* remapped from ./deps/util/from */
const ArrayFrom = require('./from.js')
/* remapped from ./deps/dopemerge */
const dopemerge = require('./dopemerge.js')
/* remapped from ./deps/reduce */
const reduce = require('./reduce.js')
/* remapped from ./deps/reduce/entries */
const reduceEntries = require('./entries.js')
/* remapped from ./deps/is/function */
const isFunction = require('./function.js')
/* remapped from ./deps/is/undefined */
const isUndefined = require('./undefined.js')
/* remapped from ./deps/meta */
const getMeta = require('./meta.js')
/* remapped from ./deps/flipped/hasOwnPropertyFlipped */
const hasOwnPropertyFlipped = require('./hasOwnPropertyFlipped.js')
/* remapped from ./compose/composer */
const composer = require('./composer.js')
/* remapped from ./deps/construct/map */
const newMap = require('./map.js')
const hasMerge = hasOwnPropertyFlipped('merge')
/**
* this is to avoid circular requires
* because MergeChain & MethodChain extend this
* yet .method & .merge use those chains
* ...also, it serves as a non-references creator for extending new instances
* of Chainable, where it splits into (Map | Set) -> composed prototype decorators
*
*
* @file
* @since 4.0.0-alpha.1
* @inheritdoc
* @class ChainedMapBase
* @member ChainedMapBase
* @category Chainable
* @extends {Chainable}
* @type {Chainable}
*
* @types ChainedMapBase
* @tests ChainedMap
*
* @prop {Meta} meta meta fn
* @prop {Map} store main store
*
* {@link https://tc39.github.io/ecma262/#sec-map-objects emca-map}
* {@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 {@link emca-map}
*
* @see ChainedMap
* @see Chainable
* @see MergeChain
* @see MethodChain
* @see ChainedMap
*
*/
const ComposeChainedMapBase = Target => {
class ChainedMapBase extends Target {
/**
* @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)
}
}
// const constructRef = Target.prototype.construct
// ChainedMapBase.prototype.construct = function() {
// this.store = new Map()
// this.meta = getMeta(this)
// if (constructRef) constructRef.call(this)
// }
/**
* @desc tap a value with a function
* @modifies this.store.get(name)
* Invokes interceptor with the obj, and then returns obj.
* The primary purpose of this method is to "tap into" a method chain, in
* order to perform operations on intermediate results within the chain.
*
* @memberOf ChainedMapBase
* @version 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/jashkenas/underscore/blob/master/underscore.js#L1161 underscore-tap}
* {@link https://github.com/ramda/ramda/blob/master/src/internal/_xtap.js ramda-xtap}
* {@link https://github.com/ramda/ramda/blob/master/src/tap.js ramda-tap}
* {@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 underscore-tap}
* @see {@link tapable}
* @see {@link ramda-tap}
*
* @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]}
*
*/
ChainedMapBase.prototype.tap = function(name, fn) {
// get value, tap it, set it
return this.set(name, fn(this.get(name), dopemerge))
}
/**
* @version 5.0.0 <- moved into ChainedMapBase & ChainedSet for less monomorphic usage
* @since 5.0.0-beta.7
* @return {Array<number | string | *>} keys
*
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set/keys mozilla-set-keys}
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys mozilla-map-keys}
* @see {@link mozilla-map-keys}
* @see {@link mozilla-set-keys}
*
* @example
*
* Chain.set('eh', 1).keys()
* //=> ['eh']
*
*/
ChainedMapBase.prototype.keys = function() {
return ArrayFrom(this.store.keys())
}
/**
* @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
*
*/
ChainedMapBase.prototype.from = function(obj) {
const keys = ObjectKeys(obj)
for (let k = 0; k < keys.length; k++) {
const key = keys[k]
const value = obj[key]
const fn = this[key]
if (hasMerge(fn)) {
fn.merge(value)
}
else if (isFunction(fn)) {
fn.call(this, value)
}
else {
this.set(key, value)
}
}
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
*
*/
ChainedMapBase.prototype.extend = function(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
* @version 4.0.0 <- improved reducing
* @since 0.4.0
*
* @param {boolean} [chains=false] if true, returns all properties that are chains
* @return {Object} reduced object containing all properties from the store, and when `chains` is true, all instance properties, and recursive chains
*
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries mozilla-map-entries}
* @see {@link mozilla-map-entries}
* @see deps/reduce/entries
*
* @example
*
* map.set('a', 'alpha').set('b', 'beta').entries()
* //=> {a: 'alpha', b: 'beta'}
*
*/
ChainedMapBase.prototype.entries = function(chains) {
const reduced = reduce(this.store)
if (isUndefined(chains)) 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
* @version 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
*
*/
ChainedMapBase.prototype.get = function(key) {
// @TODO move this back out...
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
*
*/
ChainedMapBase.prototype.set = function(key, value) {
this.store.set(key, value)
return this
}
return ChainedMapBase
}
/**
* @desc ChainedMapBase composer
* @alias ComposeMap
* @type {Composer}
* @method compose
* @memberOf ChainedMapBase
*
* @param {Class | Object | Composable} [Target=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 composed = composer(ComposeChainedMapBase, Chainable)
module.exports = composed