/**
* @file without it, the arguments & caller are uglier when debugging
* @TODO freeze store props
* @TODO callsites are super polymorphic
*/
const ENV_DEBUG = process.env.NODE_ENV === 'debug'
// import ENV_DEBUG from '../env/debug';
import EMPTY_ARRAY from '../native/EMPTY_ARRAY'
import { isSet, hasOwnProperty, isUndefined } from '../is'
import concat from '../array/concat'
import size from '../util/size'
import TRANSFORMERS_KEY from './TRANSFORMERS_KEY'
import OBSERVERS_KEY from './OBSERVERS_KEY'
import SHORTHANDS_KEY from './SHORTHANDS_KEY'
import DECORATED_KEY from './DECORATED_KEY'
const toarr = x => Array.isArray(x)
? x
: typeof x === 'string' && x.includes(',')
? x.split(',')
: x === null || x === undefined
? []
: [x]
// will expand this later
const isInKeyMapAsSet = x => x === OBSERVERS_KEY
// @NOTE: using `[]` deopts o.o
// eslint-disable-next-line
// this.shorthands = new Array()
/**
* @since 4.0.0
* @param {Chain} _this
* @return {Chain}
*/
function getMeta(_this) {
// if we already have it, keep it
if (hasOwnProperty(_this, 'meta')) return _this.meta
// the store
// shorthands: key -> method
const store = {}
// --- uglifiable functions
/** @desc initialize the store maps when we need them */
/* prettier-ignore */
const ensureInitialized = (name, value) => {
if (!isUndefined(store[name])) return
// if (
// name === TRANSFORMERS_KEY ||
// name === SHORTHANDS_KEY ||
// name === DECORATED_KEY
// ) {
// store[name] = new Map()
// }
// else
if (isInKeyMapAsSet(name)) {
store[name] = new Set()
}
else {
store[name] = new Map()
}
}
/**
* @since 4.0.0
* @param {Primitive} key
* @param {Primitive | undefined} [prop=undefined]
* @return {boolean}
*/
const has = (key, prop) => {
if (isUndefined(prop)) return !!size(store[key])
else return store[key].has(prop)
}
/**
* @since 4.0.0
* @param {Primitive} key
* @param {Primitive | undefined} [prop=undefined]
* @return {any}
*/
const get = (key, prop) =>
has(key, prop) ? store[key].get(prop) : EMPTY_ARRAY
/**
* @since 4.0.0
* @param {Primitive} key
* @param {Primitive | undefined} [prop=undefined]
* @param {Primitive | undefined} [value=undefined]
* @return {void}
*/
const set = (key, prop, value) => {
const storage = store[key]
// when it's a set, we have no `prop`, we just have .add
// so `prop = value` && `value = undefined`
if (isSet(storage)) {
storage.add(prop)
} else {
// if (!has(key, prop)) return
const existing = storage.get(prop)
const val = concat(existing, value)
storage.set(prop, val)
}
}
/**
* @since 4.0.0
*
* @desc a single easily minifiable function,
* dynamically setting & getting depending on arguments
* to avoid nested property accessing
* only instantiating when values are **addded**
*
* @param {Primitive} key
* @param {Primitive | undefined} [prop=undefined]
* @param {undefined | any} [value=undefined] (when no value, it's a getter)
* @return {Array | Chain} depending on args
*/
function meta(key, prop, value) {
/* prettier-ignore */
if (isUndefined(value)) {
// when we want to just access the property, return an array
// @example `.meta('transformers')`
if (isUndefined(prop)) {
if (ENV_DEBUG) {
console.log('META_CALL_GETTER', {[key]: store[key]})
}
if (isUndefined(store[key])) return EMPTY_ARRAY
else return size(store[key]) === 0
? EMPTY_ARRAY
: ArrayFrom(store[key])
}
// we have `key, prop`
//
// 1: should `prop` be a value, (isSet?)
else if (isInKeyMapAsSet(key)) {
ensureInitialized(key)
set(key, prop)
if (ENV_DEBUG) {
console.log('META_CALL_SET_SETTER', {key, value: prop, store})
}
}
// 2: prop is a key, we want to return the [..] for that specific property
// @example `.meta('transformers', 'eh')`
else if (isUndefined(store[key])) return EMPTY_ARRAY
else return toarr(get(key, prop))
}
// we have `key, prop, value`
else {
ensureInitialized(key)
// we have a value, let's add it
set(key, prop, value)
if (ENV_DEBUG) {
console.log('META_CALL_MAP_SETTER', {key, prop, value, store})
}
}
return _this
}
// for debugging
meta.store = store
// @NOTE not really needed, can just do `meta.store.[prop].clear`
// meta.clear = prop => meta.store[prop].clear()
// @TODO use `remove` here, so it will delete say, index
//
// @example store.transformers = Map({eh: [transformer, anotherTransformer]})
// store.delete('transformers.eh[0]')
//
// @example store.observers = Map({eh: [transformer, anotherTransformer]})
// store.delete('observers[-1]')
//
// eslint-disable-next-line
// meta['delete'] = (prop, valueOrKey) => meta.store[prop].delete(valueOrKey)
// default value
// meta.debug = false
return meta
}
export default getMeta