lodash-fp-ex
Version:
lodash/fp extensions
654 lines (631 loc) • 23.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _getOr3 = _interopRequireDefault(require("lodash/fp/getOr"));
var _some2 = _interopRequireDefault(require("lodash/fp/some"));
var _forEach2 = _interopRequireDefault(require("lodash/fp/forEach"));
var _cloneDeep2 = _interopRequireDefault(require("lodash/fp/cloneDeep"));
var _lte2 = _interopRequireDefault(require("lodash/fp/lte"));
var _snakeCase2 = _interopRequireDefault(require("lodash/fp/snakeCase"));
var _size2 = _interopRequireDefault(require("lodash/fp/size"));
var _includes2 = _interopRequireDefault(require("lodash/fp/includes"));
var _isObject2 = _interopRequireDefault(require("lodash/fp/isObject"));
var _set2 = _interopRequireDefault(require("lodash/fp/set"));
var _has2 = _interopRequireDefault(require("lodash/fp/has"));
var _forOwn2 = _interopRequireDefault(require("lodash/fp/forOwn"));
var _find2 = _interopRequireDefault(require("lodash/fp/find"));
var _reduce2 = _interopRequireDefault(require("lodash/fp/reduce"));
var _always2 = _interopRequireDefault(require("lodash/fp/always"));
var _head2 = _interopRequireDefault(require("lodash/fp/head"));
var _filter2 = _interopRequireDefault(require("lodash/fp/filter"));
var _map2 = _interopRequireDefault(require("lodash/fp/map"));
var _toNumber2 = _interopRequireDefault(require("lodash/fp/toNumber"));
var _entries2 = _interopRequireDefault(require("lodash/fp/entries"));
var _flatMapDeep2 = _interopRequireDefault(require("lodash/fp/flatMapDeep"));
var _camelCase2 = _interopRequireDefault(require("lodash/fp/camelCase"));
var _upperFirst2 = _interopRequireDefault(require("lodash/fp/upperFirst"));
var _equals2 = _interopRequireDefault(require("lodash/fp/equals"));
var _isEmpty2 = _interopRequireDefault(require("lodash/fp/isEmpty"));
var _curry2 = _interopRequireDefault(require("lodash/fp/curry"));
var _identity2 = _interopRequireDefault(require("lodash/fp/identity"));
var _isArray2 = _interopRequireDefault(require("lodash/fp/isArray"));
var _isPlainObject2 = _interopRequireDefault(require("lodash/fp/isPlainObject"));
var _T2 = _interopRequireDefault(require("lodash/fp/T"));
var _cond2 = _interopRequireDefault(require("lodash/fp/cond"));
var _isFunction2 = _interopRequireDefault(require("lodash/fp/isFunction"));
var _get2 = _interopRequireDefault(require("lodash/fp/get"));
var _pipe2 = _interopRequireDefault(require("lodash/fp/pipe"));
var _isError2 = _interopRequireDefault(require("lodash/fp/isError"));
var _isNumber2 = _interopRequireDefault(require("lodash/fp/isNumber"));
var _attempt2 = _interopRequireDefault(require("lodash/fp/attempt"));
var _isBoolean2 = _interopRequireDefault(require("lodash/fp/isBoolean"));
var _isString2 = _interopRequireDefault(require("lodash/fp/isString"));
var _isNil2 = _interopRequireDefault(require("lodash/fp/isNil"));
var _concat2 = _interopRequireDefault(require("lodash/fp/concat"));
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
/**
* 대상 문자열이 json형식 문자열인지 여부 조회
*
* @param {string} jsonStr 조회 대상 문자열
* @returns {boolean} json 문자열인지 여부
*/
const isJson = jsonStr => {
const composer = (0, _pipe2.default)(_attempt2.default, _isError2.default);
const result = (0, _isString2.default)(jsonStr) && !composer(() => JSON.parse(jsonStr));
return result;
};
/**
* 원시 타입(primitive) 인지 여부 조회
* null, undefined, Boolean, Number, String
*
* @param {any} arg 조회 대상
* @returns {boolean} 원시 타입(primitive) 인지 여부
*/
const isVal = arg => {
const result = (0, _isNil2.default)(arg) || (0, _isBoolean2.default)(arg) || (0, _isNumber2.default)(arg) || (0, _isString2.default)(arg);
return result;
};
/**
* 참조 타입(reference) 인지 여부 조회
* Array, Object, Function
*
* @param {any} arg 조회 대상
* @returns {boolean} 참조 타입(reference) 인지 여부
*/
const isRef = arg => {
const composer = (0, _pipe2.default)(isVal, not);
const result = composer(arg);
return result;
};
/**
* 대상 인자가 promise(thenable)인지 여부 조회
*
* @param {any} x 조회 대상
* @return {boolean} 대상 인자가 promise(thenable)인지 여부
*/
const isPromise = x => (0, _isFunction2.default)((0, _get2.default)('then', x)) && (0, _isFunction2.default)((0, _get2.default)('catch', x));
/**
* 대상 함수를 promise로 lift
*
* @param {(...args: any[]) => any} fn 대상 합수
* @param {any[]} args 함수 인자 목록
* @returns {Promise<any>} Promise로 lift된 Promise 객체
*/
const fnPromisify = (fn, ...args) => {
return new Promise((resolve, reject) => {
try {
resolve(fn(...args));
} catch (e) {
reject(e);
}
});
};
/**
* 대상 인자를 promise로 wrapping (lift)
*
* @param {any} a 대상 인자
* @param {any[]} args 대상 인자 a가 함수인 경우, 함수의 인자목록
* @return {Promise<any>} Promise로 lift된 Promise 객채
*/
const promisify = (a, ...args) => {
const conditional = (0, _cond2.default)([[_isFunction2.default, () => fnPromisify(a, ...args)], [isPromise, a => (0, _identity2.default)(a)], [_T2.default, a => Promise.resolve(a)]]);
const result = conditional(a);
return result;
};
/**
* promise가 또다른 promise를 resolve하는 경우, promise의 중첩을 제거하기 위한 helper 함수
*
* @param {Promise<any> | any} thenable Promise 객체
* @return {Promise<any> | any} 중첩 제거된 Promise 객체
*/
const flatPromise = thenable => isPromise(thenable) ? thenable.then(x => flatPromise(x)) : thenable;
/**
* lodash 형태의 promise then
*
* @param {(response:any) => any} successHandler 응답값 처리 callback
* @param {Promise<any>} thenable resolve 대상 Promise 객체
* @returns {Promise<any>} fullfilled 상태의 Promise 객체
*/
const andThen = (0, _curry2.default)((successHandler, thenable) => promisify(thenable).then(flatPromise(successHandler)));
/**
* lodash 형태의 promise catch
*
* @param {(error:Error|any) => any} failureHandler error 처리 callback
* @param {Promise<Error|any>} thenable error를 resolve 하는 promise
* @return {Promise<any>} error 상태의 Promise 객체
*/
const otherwise = (0, _curry2.default)((failureHandler, thenable) => promisify(thenable).then(null, flatPromise(failureHandler)));
/**
* lodash 형태의 promise finally
*
* @param {(...args: any[]) => any} callback Promise 상태와 무관하게 실행되는 callback
* @param {Promise<any>} thenable Promise 객체
* @return {Promise<any>} Promise 객체
*/
const _finally = (0, _curry2.default)((callback, thenable) => promisify(thenable).finally(flatPromise(callback)));
/**
* invert boolean
* @param {any} x 대상
* @return {boolean} not 연산된 값
*/
const not = x => !x;
/**
* 대상이 비어있지 않은지 여부
* (주의: 숫자타입은 항상 false를 반환)
*
* @param {any} a 대상
* @returns {boolean} 비어 있는지 여부
*/
const isNotEmpty = a => {
const composer = (0, _pipe2.default)(_isEmpty2.default, not);
const result = composer(a);
return result;
};
/**
* 대상 인자를 boolean 타입으로 변환\
*
* @param {any} a 대상
* @returns {boolean} 변환된 boolean 타입 값
*/
const toBool = arg => !!arg;
/**
* a인자를 인자로, evaluator함수 실행,
* true면 trueHandler에 a인자 대입
* false면 a 반환
*
* @deprecated
* @param {(a) => boolean} evaluator a를 인자로 하는 평가함수
* @param {(a) => any} trueHandler evaluator의 결과가 true인 경우, a를 인자로 실행되는 callback
* @param {any} a 대상 인자
* @returns {any} evaluator가 true를 반환하는 경우, trueHandler의 결과값, false인 경우 a 반환
*/
const ifT = (0, _curry2.default)((evaluator, trueHandler, arg) => {
const isValidEvaluator = (0, _isFunction2.default)(evaluator) || (0, _isBoolean2.default)(evaluator);
if (isValidEvaluator) {
// evaluator가 함수인 경우
if ((0, _isFunction2.default)(evaluator)) {
if ((0, _pipe2.default)(evaluator, (0, _equals2.default)(true))(arg)) {
return (0, _isFunction2.default)(trueHandler) ? trueHandler(arg) : trueHandler;
} else {
return arg;
}
} else {
// evaluator가 boolean인 경우
if (evaluator) {
return (0, _isFunction2.default)(trueHandler) ? trueHandler(arg) : trueHandler;
} else {
return arg;
}
}
} else {
throw new Error('invalid parameter(s)');
}
});
/**
* a인자를 인자로, evaluator함수 실행,
* false면 falseHandler에 a인자 대입
* true면 a 반환
*
* @deprecated
* @param {(a) => boolean} evaluator a를 인자로 하는 평가함수
* @param {(a) => any} falseHandler evaluator의 결과가 false인 경우, a를 인자로 실행되는 callback
* @param {any} a 대상 인자
* @returns {any} evaluator가 false를 반환하는 경우, falseHandler의 결과값, true경우 a 반환
*/
const ifF = (0, _curry2.default)((evaluator, falseHandler, arg) => {
const isValidEvaluator = (0, _isFunction2.default)(evaluator) || (0, _isBoolean2.default)(evaluator);
if (isValidEvaluator) {
// evaluator가 함수인 경우
if ((0, _isFunction2.default)(evaluator)) {
if ((0, _pipe2.default)(evaluator, (0, _equals2.default)(false))(arg)) {
return (0, _isFunction2.default)(falseHandler) ? falseHandler(arg) : falseHandler;
} else {
return arg;
}
} else {
// evaluator가 boolean인 경우
if (evaluator) {
return (0, _isFunction2.default)(falseHandler) ? falseHandler(arg) : falseHandler;
} else {
return arg;
}
}
} else {
throw new Error('invalid parameter(s)');
}
});
/**
* a인자가 t타입인지 여부 조회
* @param {any} t 조회 대상 type
* @param {any} a 조회 대상
* @returns {boolean} a인자가 t타입인지 여부
*/
const instanceOf = (0, _curry2.default)((t, a) => a instanceof t);
/**
* 대상 문자열을 pascalcase 문자열로 변환
* @param {string} str 대상 문자열
* @returns {string} pascal case로 변환된 문자열
*/
const pascalCase = str => {
const composer = (0, _pipe2.default)(_camelCase2.default, _upperFirst2.default);
const result = composer(str);
return result;
};
/**
* (collection) map의 비동기 함수\
* mapper 함수로 비동기 함수를 받아서 처리해준다.
*
* @param {(a) => Promise<any>} asyncMapper 비동기 mapper
* @param {object|any[]} collection 대상 object 또는 array
* @returns {Promise<any[]>} 결과 array를 resolve 하는 promise
*/
const mapAsync = (0, _curry2.default)(async (asyncMapper, collection) => {
const composer = (0, _pipe2.default)(_flatMapDeep2.default.convert({
cap: false
})((0, _pipe2.default)(asyncMapper, promisify)), async a => await Promise.all(a));
const result = await composer(collection);
return result;
});
/**
* 비동기 forEach
* 실행함수로 비동기 함수를 받아서 처리해준다
* 순차실행
*
* @param {(value: any, key: number | string) => Promise<any>} callbackAsync 비동기 iterator
* @param {object|any[]} collection 대상 객체 또는 배열
* @returns {Promise<any[]>} 결과 Promise
*/
const forEachAsync = (0, _curry2.default)(async (callbackAsync, collection) => {
const loopResults = [];
const entryList = (0, _entries2.default)(collection);
for (const entry of entryList) {
loopResults.push(await callbackAsync(entry[1], (0, _isArray2.default)(collection) ? (0, _toNumber2.default)(entry[0]) : entry[0]));
}
return loopResults;
});
/**
* (collection) filter의 비동기 함수\
* 필터함수로 비동기 함수를 받아서 처리해준다.
*
* @param {(a) => Promise<bool>} asyncFilter 비동기 필터
* @param {object|any[]} collection 대상 object 또는 array
* @returns {Promise<any[]>} 결과 array를 resolve하는 promise
*/
const filterAsync = (0, _curry2.default)(async (asyncFilter, collection) => {
const composer = (0, _pipe2.default)(mapAsync(async (item, key) => (await asyncFilter(item, key)) ? item : false), andThen(response => (0, _filter2.default)((0, _pipe2.default)((0, _equals2.default)(false), not))(response)));
const result = await composer(collection);
return result;
});
/**
* (collection) find의 비동기 함수
* @param {(a) => Promise<bool>} asyncFilter 비동기 필터
* @param {object|any[]} collection 대상 object 또는 array
* @returns {Promise<any>} 필터된 단일 결과를 resolve하는 promise
*/
const findAsync = (0, _curry2.default)(async (asyncFilter, collection) => {
const composer = (0, _pipe2.default)(filterAsync(asyncFilter), andThen(response => (0, _isEmpty2.default)(response) ? undefined : (0, _head2.default)(response)), otherwise((0, _always2.default)(undefined)));
const result = await composer(collection);
return result;
});
/**
* asyncFn의 시작은 await accPromise가 되어야 한다.\
* 순차적으로 실행된다.\
* (ex 300ms이 걸리는 5개의 promise가 있다면, 최소 1500ms+alpah의 시간이 소요된다.\
* 상기의 mapAsync의 경우 300+alpah의 시간만 소요된다.(Promise.all과 Promise.resolve의 차이))
*
* @param {(acc:any, v:any) => Promise<any>} asyncFn 비동기 iterator
* @param {Promise<any>|any} initAcc 초기 누적기를 반환하는 promise 또는 누적기
* @param {object|any[]} collection 대상 객체 또는 배열
* @returns {Promise<any>} 결과 Promise
*/
const reduceAsync = (0, _curry2.default)(async (asyncFn, initAcc, collection) => {
const initAccPromise = await (0, _pipe2.default)(promisify, accP => Promise.resolve(accP))(initAcc);
const result = _reduce2.default.convert({
cap: false
})(asyncFn, initAccPromise, collection);
return result;
});
/**
* value로 object key 조회
*
* @param {object} obj 대상 객체
* @param {string} value 조회 대상 값
* @returns {string} 속성명
*/
const key = (0, _curry2.default)((obj, value) => {
const composer = (0, _pipe2.default)(_entries2.default, (0, _find2.default)(([k, val]) => (0, _equals2.default)(value, val)), _head2.default);
const result = composer(obj);
return result;
});
/**
* shallow freeze 보완
* (대상 object의 refence 타입의 properties까지 object.freeze 처리)
* @param {object} obj 대상 객체
* @returns {object} frozen 처리된 객체
*/
const deepFreeze = obj => {
const freezeRecursively = v => isRef(v) && !Object.isFrozen(v) ? deepFreeze(v) : v;
const composer = (0, _pipe2.default)(Object.freeze, (0, _forOwn2.default)(freezeRecursively));
const result = composer(obj);
return result;
};
/**
* 대상 객체의 속성명을 transformFn의 결과값으로 변환
*
* @param {(orignStr) => string} transformFn 변환함수
* @param {object} obj 대상 객체
* @returns {object} 속성명이 변환된 객체
*/
const transformObjectKey = (0, _curry2.default)((transformFn, obj) => {
const convertRecursively = obj => {
const convertTo = o => {
const composer = (0, _pipe2.default)(_reduce2.default.convert({
cap: false
})((acc, v, k) => {
const conditional = arg => {
if ((0, _isPlainObject2.default)(arg)) {
return convertTo(arg);
} else if ((0, _isArray2.default)(arg)) {
return (0, _map2.default)(conditional, arg);
} else {
return (0, _identity2.default)(arg);
}
};
const transformedKey = transformFn(k);
if (!(0, _has2.default)(transformedKey, acc)) {
const result = (0, _set2.default)(transformedKey, conditional(v), acc);
return result;
} else {
throw new Error(`${transformedKey} already exist. duplicated property name is not supported.`);
}
}, {}));
const result = composer(o);
return result;
};
const result = convertTo(obj);
return result;
};
const result = (0, _isObject2.default)(obj) || (0, _isArray2.default)(obj) ? convertRecursively(obj) : obj;
return result;
});
/**
* 대상 object의 property key문자열을 camelcase 문자열로 변환
*
* @param {object} obj 대상 객체
* @returns {object} 속성명이 camel case로 변환된 객체
*/
const toCamelcase = transformObjectKey(_camelCase2.default);
/**
* 대상 object의 property key문자열을 snakecase 문자열로 변환
*
* @param {object} obj 대상 객체
* @returns {object} 속성명이 snake case로 변환된 객체
*/
const toSnakecase = transformObjectKey(_snakeCase2.default);
/**
* 대상 object의 property key문자열을 camelcase 문자열로 변환
*
* @param {object} obj 대상 객체
* @returns {object} 속성명이 camel case로 변환된 객체
*/
const toPascalcase = transformObjectKey(pascalCase);
/**
* date형식 문자열 여부 조회
* @param {string} dateStr date형식 문자열
* @returns {boolean} date형식 문자열 여부
*/
const isDatetimeString = dateStr => (0, _isString2.default)(dateStr) && !isNaN(Date.parse(dateStr));
/**
* applicative functor pattern 구현체
* (주로 pipe함수에서 함수의 인자 순서를 변경하기 위해 사용)
*
* @param {any} arg 대입 인자
* @param {function} curried currying된 함수
* @returns {any} 결과값
*/
const ap = (0, _curry2.default)((a, curried) => curried(a));
/**
* 대상 인자가 undefined 또는 null이 아닌지 여부 조회
*
* @param {any} arg 대상인자
* @returns {boolean} 대상 인자가 undefined 또는 null이 아닌지 여부
*/
const isNotNil = (0, _pipe2.default)(_isNil2.default, not);
/**
* arr인자 배열에 a인자가 포함되지 않았는지 여부 조회
* @param {any} a 대상 인자
* @param {any[] | Record<string, any> | string} arr 대상 배열
* @returns {boolean} arr 배열에 a인자가 포함되지 않았는지 여부
*/
const notIncludes = (0, _curry2.default)((arg, targetArray) => {
const result = !(0, _includes2.default)(arg, targetArray);
return result;
});
/**
* a인자와 b인자가 다른지 여부 (deep equal) 조회
* @param {any} a 비교 인자
* @param {any} b 비교 인자
* @returns {boolean} a인자와 b인자가 다른지 여부 (deep equal)
*/
const notEquals = (0, _curry2.default)((a, b) => {
const composer = (0, _pipe2.default)((0, _equals2.default)(a), not);
const result = composer(b);
return result;
});
/**
* arr인자의 idx인자의 index에 해당하는 요소 제거
* @param {number|string} index numeric 타입 색인값
* @param {any[]} targetArray 대상 배열
* @returns {any[]} index에 해당하는 요소 제거된 배열
*/
const removeByIndex = (0, _curry2.default)((index, targetArray) => {
if ((0, _isArray2.default)(targetArray) && (0, _pipe2.default)(_size2.default, (0, _curry2.default)((index, sz) => {
const num = (0, _toNumber2.default)(index);
const isAccesable = (0, _isNumber2.default)(num) && (0, _lte2.default)(num, sz);
return isAccesable;
})(index))(targetArray)) {
const cloned = (0, _cloneDeep2.default)(targetArray);
cloned.splice((0, _toNumber2.default)(index), 1);
return cloned;
}
return targetArray;
});
/**
* 인자의 마지막 요소 제거 (immutable)
*
* @param {string|any[]} target 문자열 또는 배열의 마지막 요소 제거
* @returns 마지막 요소 제거된 인자
*/
function removeLast(target) {
if ((0, _isArray2.default)(target) || (0, _isString2.default)(target)) {
const result = (0, _cloneDeep2.default)(target);
(0, _isArray2.default)(target) ? result.pop() : result.substring(0, (0, _size2.default)(target) - 1);
return result;
}
return target;
}
/**
* fp.concat alias
*
* @param {any[]} array 병합대상 배열
* @param {any|any[]} a 병합 인자
* @returns {any[]} 병합된 배열
*/
const append = _concat2.default;
/**
* array 인자의 (index상)앞쪽에 value인자를 추가
*
* @param {any[]} targetArray 병합대상 배열
* @param {any|any[]} value 병합 인자
* @returns {any[]} 병합된 배열
*/
const prepend = (0, _curry2.default)((targetArray, value) => (0, _isArray2.default)(value) ? (0, _concat2.default)(value, targetArray) : (0, _concat2.default)([value], targetArray));
/**
* key(index)를 포함한 map
* @param {(v, k) => any} f value, key(또는 index)를 인자로 갖는 callback
* @param {object|any[]} a 대상 collection
* @returns {any[]} 결과 배열
*/
const mapWithKey = (0, _curry2.default)((iteratee, collection) => _map2.default.convert({
cap: false
})(iteratee, collection));
/**
* key(index)를 포함한 forEach
* @param {(v, k) => any} f value, key(또는 index)를 인자로 갖는 callback
* @param {object|any[]} a 대상 collection
* @returns {void} 반환값 없음
*/
const forEachWithKey = (0, _curry2.default)((iteratee, collection) => _forEach2.default.convert({
cap: false
})(iteratee, collection));
/**
* key(index)를 포함한 reduce
*
* @param {(acc, v, k) => any} f accumulator, value, key(또는 index)를 인자로 갖는 callback
* @param {any} acc 누적기
* @param {object|any[]} 대상 collection
* @returns {any} 누적기
*/
const reduceWithKey = (0, _curry2.default)((iteratee, acc, collection) => _reduce2.default.convert({
cap: false
})(iteratee, acc, collection));
/**
* falsy 타입(0, -0, NaN, false, '')인지 여부 조회
* @param {any} arg 조회 대상
* @returns {boolean} falsy 타입(0, -0, NaN, false, '')인지 여부
*/
const isFalsy = arg => {
return (0, _isNil2.default)(arg) || (0, _some2.default)((0, _equals2.default)(arg), [0, -0, NaN, false, '']);
};
/**
* truthy 타입 인지 여부 조회
* (falsy타입(0, -0, NaN, false, '')이 아니면 truthy 타입)
* @param {any} arg 조회 대상
* @returns {boolean} truthy 타입 인지 여부
*/
const isTruthy = arg => !isFalsy(arg);
/**
* getOr override
*
* getOr의 반환값이 null인 경우, 기본값 반환되게 수정한 버전
* circular dependency 때문에 closure로 작성
*/
const getOr = (({
curry,
_getOr
}) => {
const __getOr = curry((defaultValue, path, target) => {
return (0, _isNil2.default)(target) || (0, _isNil2.default)((0, _get2.default)(path, target)) ? defaultValue : (0, _get2.default)(path, target);
});
return __getOr;
})({
curry: _curry2.default,
_getOr: _getOr3.default
});
/**
* ms 시간동안 대기
*
* @param ms 대기시간
* @returns Promise
*/
const delayAsync = async ms => {
const exe = () => new Promise(resolve => setTimeout(() => resolve(), ms));
const result = await exe();
return result;
};
var _default = exports.default = {
mapAsync,
filterAsync,
reduceAsync,
findAsync,
forEachAsync,
promisify,
andThen,
otherwise,
finally: _finally,
isPromise,
isNotEmpty,
isNotNil,
isJson,
notEquals,
isNotEqual: notEquals,
isVal,
isPrimitive: isVal,
isRef,
isReference: isRef,
not,
notIncludes,
toBool,
deepFreeze,
key,
keyByVal: key,
// string
transformObjectKey,
toCamelcase,
toCamelKey: toCamelcase,
toSnakecase,
toSnakeKey: toSnakecase,
toPascalcase,
pascalCase,
isDatetimeString,
ap,
instanceOf,
// array
removeByIndex,
removeLast,
append,
prepend,
mapWithKey,
mapWithIndex: mapWithKey,
forEachWithKey,
forEachWithIndex: forEachWithKey,
reduceWithKey,
reduceWithIndex: reduceWithKey,
isFalsy,
isTruthy,
getOr,
delayAsync,
sleep: delayAsync
};