@toktokhan-dev/universal
Version:
A universal library built by TOKTOKHAN.DEV
1,393 lines (1,343 loc) • 42.6 kB
JavaScript
import curry from 'lodash/curry.js';
import curry$1 from 'lodash/fp/curry.js';
import flow from 'lodash/fp/flow.js';
import reduce from 'lodash/fp/reduce.js';
import isFunction from 'lodash/isFunction.js';
import compact from 'lodash/compact.js';
import cloneDeep from 'lodash/cloneDeep.js';
import get$1 from 'lodash/get.js';
import set from 'lodash/set.js';
import setWith from 'lodash/fp/setWith.js';
import map from 'lodash/fp/map.js';
import prop from 'lodash/fp/prop.js';
import split from 'lodash/fp/split.js';
import spread from 'lodash/fp/spread.js';
import toString from 'lodash/fp/toString.js';
import isArray from 'lodash/fp/isArray.js';
/**
* 배열을 Map으로 변환합니다. 각 요소는 지정된 키 선택기 함수를 통해 매핑됩니다.
*
* @curried
* @category Utils/Array
*
* @typeParam T - 배열 요소의 타입
* @typeParam K - Map의 키 타입
*
* @param keySelector - 배열 요소를 Map의 키로 변환하는 함수
* @param arr - 변환할 배열
*
* @returns 배열의 각 요소를 Map으로 매핑한 결과
*
* @example
* ```ts
* const arr = [
* { id: 1, name: 'Alice' },
* { id: 2, name: 'Bob' },
* { id: 3, name: 'Charlie' },
* ];
*
* const map = arrayToMap((item) => item.id , arr);
* // or
* const map = arrayToMap((item) => item.id)(arr);
* // or
* const mapById = arrayToMap((item) => item.id);
* const map = mapById(arr);
*
* console.log(map)
* // Map {
* // 1 => { id: 1, name: 'Alice' },
* // 2 => { id: 2, name: 'Bob' },
* // 3 => { id: 3, name: 'Charlie' },
* // }
*```
*/
const arrayToMap = curry((keySelector, arr) => {
return arr.reduce((prev, cur) => {
const key = keySelector(cur);
return prev.set(key, cur);
}, new Map());
});
/**
* 배열을 Record로 변환합니다. 각 요소는 지정된 키 선택기 함수를 통해 매핑됩니다.
*
* @curried
* @category Utils/Array
*
* @typeParam T - 배열 요소의 타입
* @typeParam K - Record의 키 타입 (string, number, symbol)
*
* @param keySelector - 배열 요소를 Record의 키로 변환하는 함수
* @param arr - 변환할 배열
*
* @returns 배열의 각 요소를 Record으로 매핑한 결과
*
* @example
* ```ts
* const arr = [
* { id: 1, name: 'Alice' },
* { id: 2, name: 'Bob' },
* { id: 3, name: 'Charlie' },
* ];
*
* const record = arrayToRecord((item) => item.id , arr);
* // or
* const record = arrayToRecord((item) => item.id)(arr);
* // or
* const recordById = arrayToRecord((item) => item.id);
* const record = recordById(arr);
*
* console.log(record)
* // {
* // 1: { id: 1, name: 'Alice' },
* // 2: { id: 2, name: 'Bob' },
* // 3: { id: 3, name: 'Charlie' },
* // }
*```
*/
const arrayToRecord = curry((keySelector, arr) => {
return arr.reduce((prev, cur) => {
const key = keySelector(cur);
return Object.assign(Object.assign({}, prev), { [key]: cur }); // 현재 요소를 Record에 추가
}, {});
});
/**
* 주어진 값이 함수인 경우 주어진 인자들을 사용하여 실행하고, 그렇지 않으면 주어진 값을 그대로 반환합니다.
*
* @category Utils/Fn
*
* @typeParam T - 반환할 값의 타입
* @typeParam U - 함수의 매개변수의 타입
* @param valueOrFn - 실행할 함수 또는 반환할 값
* @param args - 함수에 전달할 매개변수
* @returns 주어진 값이 함수인 경우 주어진 인자들을 사용하여 실행한 결과를 반환하고, 그렇지 않으면 주어진 값을 그대로 반환합니다.
*
* @example
* ```ts
* const add = (a: number, b: number) => a + b;
* runIfFn(add, 2, 3); // 5 - add 함수를 실행하여 결과를 반환합니다.
* runIfFn(5, 2, 3); // 5 - 주어진 값이 함수가 아니므로 주어진 값을 그대로 반환합니다.
* ```
*/
function runIfFn$1(valueOrFn, ...args) {
return isFunction(valueOrFn) ? valueOrFn(...args) : valueOrFn;
}
/**
*
* 함수를 실행하고, 인자를 그대로 반환합니다.
* 컴포넌트 합성시(lodash.flow) 함수의 응닶값에 영향을 미치지 않고 특정 함수를 실행시키고 싶을 경우 유용합니다.
*
* @category Utils/Fn
*
* @typeParam T - 함수에 전달할 인자의 타입
* @param fn - 실행할 함수
* @param x - 실행할 함수에 전달할 인자
* @returns 실행할 함수에 전달한 인자
*
* @example
* ```ts
* effect(console.log, 'hello') // 'hello'를 출력하고 'hello'를 반환합니다.
*
* effect(console.log)('hello') // 'hello'를 출력하고 'hello'를 반환합니다.
*
* const log = effect(console.log)
* log('hello') // 'hello'를 출력하고 'hello'를 반환합니다.
*
* const dotToDash = flow(split("."), effect(console.log), join('-'))
* dotToDash('a.b.c') // ['a', 'b', 'c'] 를 출력하고 'a-b-c'를 반환합니다.
* ```
* @curried
*/
const effect = curry((fn, x) => {
fn(x);
return x;
});
/**
* @category Utils/Fn
*
* Promise 를 받아 resolve 된 값으로 함수를 실행합니다.
*
* @example
* ```ts
* const double = (x: number) => x * 2
* const target = 5
* const targetPromise = Promise.resolve(5)
*
* const result = awaited(double, target) // 10
* const resultPromise = awaited(double, targetPromise) // 10
*
* // curried
* flow(() => Promise.resolve(5), awaited(double))
* ```
*/
const awaited = curry((fn, data) => {
if (data instanceof Promise) {
return data.then(fn);
}
return Promise.resolve(fn(data));
});
/**
* 주어진 데이터를 반환하는 함수를 생성합니다.
*
* @category Utils/Fn
*
* @typeParam T - 반환할 데이터의 타입
* @param data - 반환할 데이터
* @returns 주어진 데이터를 반환하는 함수
*
* @example
* ```typescript
* const data = { id: 1, name: 'John' };
* const getData = pass(data);
* const result = getData(); // { id: 1, name: 'John' }
* ```
*/
const pass = (data) => () => data;
/**
* 주어진 값이 null 또는 undefined인 경우 기본값을 반환하고, 그렇지 않으면 주어진 값을 반환합니다.
*
* @category Utils/Fn
*
* @typeParam T - 반환할 값의 타입
* @param def - 기본값
* @param value - 확인할 값
* @returns 주어진 값이 null 또는 undefined인 경우 기본값을 반환하고, 그렇지 않으면 주어진 값을 반환합니다.
*
* @example
* ```ts
* or(0, 5); // 5 - 5는 null 또는 undefined가 아니므로 그대로 반환됩니다.
* or(0, null); // 0 - null이므로 기본값 0이 반환됩니다.
* or(0, undefined); // 0 - undefined이므로 기본값 0이 반환됩니다.
*
* or(0)(5) // 5
* or(0)(null) // 0
* or(0)(undefined) // 0
*
* const isEven = (x: number) => x % 2 === 0;
*
* const isOddOrZero = flow(
* not(isEven), // 짝수가 아닌 값들을 거름
* or(0) // null 또는 undefined인 경우 0으로 대체
* );
*
* // 예시
* console.log(isOddOrZero(5)); // 5 - 홀수는 그대로 반환됩니다.
* console.log(isOddOrZero(10)); // 0 - 짝수는 0으로 대체됩니다.
* console.log(isOddOrZero(null)); // 0 - null은 0으로 대체됩니다.
* ```
*
* @curried
*/
const or = curry((def, value) => value !== null && value !== void 0 ? value : def);
/**
* 여러 함수들이 모두 주어진 인자에 대해 true를 반환하는지 확인합니다.
* 주어진 함수 배열(fns)에 대해 모든 함수가 인자를 받아들여 true를 반환하는지 여부를 검사합니다.
*
* @category Utils/Fn
*
* @typeParam T - 함수에 전달할 인자의 타입
* @param fns - 평가할 함수들의 배열
* @returns 모든 함수가 인자를 받아들여 true를 반환하는 경우 true를 반환하고, 그렇지 않으면 false를 반환합니다.
*
* @example
* ```ts
* const isPositive = (x: number) => x > 0;
* const isEven = (x: number) => x % 2 === 0;
* const isGreaterThanTen = (x: number) => x > 10;
*
* const conditions = [isPositive, isEven, isGreaterThanTen];
*
* isEvery(conditions)(4); // false - 4는 isGreaterThanTen의 조건을 만족하지 않습니다.
* isEvery(conditions)(12); // true - 모든 조건을 만족합니다.
* ```
* @curried
*/
const isEvery = (fns) => (...param) => {
return fns.every((fn) => fn(...param));
};
/**
* 주어진 함수의 부정값을 반환합니다.
* 주어진 함수를 실행하고 그 결과를 부정하여 반환합니다.
*
* @category Utils/Fn
*
* @typeParam T - 함수의 매개변수와 반환값의 타입
* @param fn - 부정할 함수
* @param args - 함수에 전달할 매개변수
* @returns 주어진 함수의 부정값을 반환합니다.
*
* @example
* ```ts
* const isPositive = (x: number) => x > 0;
* const isNegative = not(isPositive);
*
* isNegative(5); // false - isPositive(5)의 부정값이므로 false를 반환합니다.
* isNegative(-5); // true - isPositive(-5)의 부정값이므로 true를 반환합니다.
* ```
* @curried
*/
const not = (fn) => (...args) => !fn(...args);
/**
* @category Utils/Fn
*
* arguments 를 배열로써 반환합니다.
*
* @param args - arguments
*
* @returns arguments 를 배열로써 반환합니다.
*
* @example
* ```ts
* collect(1, 2, 3) // [1, 2, 3]
* ```
*/
const collect$1 = (...args) => args;
const delay = (ms, option) => new Promise((resolve, reject) => setTimeout(() => {
if (option === null || option === void 0 ? void 0 : option.error)
return reject(option === null || option === void 0 ? void 0 : option.error);
resolve((option === null || option === void 0 ? void 0 : option.success) || true);
}, ms));
/******************************************************************************
Copyright (c) Microsoft Corporation.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
***************************************************************************** */
/* global Reflect, Promise, SuppressedError, Symbol */
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
};
/**
* @category Utils/Fn
*
* 주로 refresh token 이 필요한 요청을 관리하는 함수입니다.
* 토큰이 만료됐을 시, refresh token 을 요청하고, 새로운 토큰을 받아서 요청을 재시도합니다.
*
* @returns refresh token 이 필요한 요청을 관리하는 함수입니다.
*
* @example
*
* ```ts
* const retry = retryReqeustManager()
*
* const result = await retry({
* getToken: async () => {
* await delay(200)
* return 'token'
* },
* onRefetch: (token: string) => {
* return token
* },
* onError: (error: any) => {
* return error
* },
* })
*
*
*/
const retryReqeustManager = () => {
let token = null;
return (params) => __awaiter(void 0, void 0, void 0, function* () {
const { onError, onRefetch, getToken } = params;
try {
if (!token) {
token = getToken();
}
const refreshed = yield token;
const refectehd = yield onRefetch(refreshed);
return refectehd;
}
catch (err) {
onError === null || onError === void 0 ? void 0 : onError(err);
throw err;
}
finally {
token = null;
}
});
};
/**
* @category Utils/Fn
*
* 인자로 넘겨준 getNext 함수를 연속적으로 호출하여 데이터를 가져오는 함수입니다.
* 호출된 데이터를 순서대로 배열로 반환합니다.
*
* 주로 pagination 된 데이터의 모든 페이지를 가져오는데 사용됩니다.
*
* @example
*
* ```ts
* const list = range(0, 100)
*
* const getList = async (params: { offset: number; limit: number }) => {
* const { offset, limit } = params
* const next = offset + limit
*
* return {
* total: list.length,
* next: list.length - 1 < next ? null : next,
* data: list.slice(offset, offset + limit),
* }
*
* const result = await relay({
* initialParam: 0,
* getNext: (nextParam: number) => getList({ offset: nextParam, limit: 10 }),
* getNextParams: (last) => {
* return last?.next
* },
*
* console.log(result)
*
* // [
* // { total: 100, next: 10, data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] },
* // { total: 100, next: 20, data: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] },
* // { total: 100, next: 30, data: [20, 21, 22, 23, 24, 25, 26, 27, 28, 29] },
* // ...
* // { total: 100, next: null, data: [90, 91, 92, 93, 94, 95, 96, 97, 98, 99] },
* // ]
*
*
* ```
*/
const relay = (params) => __awaiter(void 0, void 0, void 0, function* () {
const { getNext, getNextParams, initialParam, selector } = params;
const getPages = (pages) => __awaiter(void 0, void 0, void 0, function* () {
const nextParam = pages.length === 0 ? initialParam : getNextParams(pages[pages.length - 1]);
if (nextParam === null) {
return pages;
}
return yield getPages([...pages, yield getNext(nextParam)]);
});
const pages = yield getPages([]);
return selector ? selector(pages) : pages;
});
/**
* @category Utils/Array
*
* 배열을 특정 갯수로 나누어주는 함수입니다.
*
* @param limit - 배열을 나눌 갯수입니다.
* @param arr - 나눌 배열입니다.
*
* @returns 나누어진 배열을 반환합니다.
*
* @example
*
* ```ts
* const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
* const result = paginate(3, arr)
*
* console.log(result)
*
* // [
* // [1, 2, 3],
* // [4, 5, 6],
* // [7, 8, 9],
* // ]
* ```
*/
const paginate = curry$1((limit, arr) => flow(pass(arr), reduce((acc, cur) => {
if (acc.length === 0) {
return acc.concat([[cur]]);
}
if (acc[acc.length - 1].length === limit) {
return acc.concat([[cur]]);
}
return acc.slice(0, -1).concat([acc[acc.length - 1].concat([cur])]);
}, []))());
const _flatObject = (params, obj) => {
const result = {};
const setResult = (object, parentKey = null) => {
Object.entries(object).forEach(([key, value]) => {
const generatedKey = (params === null || params === void 0 ? void 0 : params.formatKey) ?
params === null || params === void 0 ? void 0 : params.formatKey(parentKey, key)
: compact([parentKey, key]).join('.');
const isValue = (params === null || params === void 0 ? void 0 : params.isValueType) ?
params.isValueType(value)
: typeof value !== 'object';
if (isValue) {
const v = (params === null || params === void 0 ? void 0 : params.formatValue) ? params.formatValue({ key, value }) : value;
result[generatedKey] = v;
}
else {
setResult(value, generatedKey);
}
});
};
setResult(obj);
return result;
};
/**
* 재귀적으로 중첩된 객체를 평탄화하는 함수입니다.
*
* @category Utils/Object
*
* @typeParam T - 재귀적으로 중첩된 객체의 타입
* @typeParam V - 중첩된 객체의 값의 타입
*
* @param params - 평탄화 작업에 필요한 매개변수
* @param obj - 평탄화할 객체
* @returns 평탄화된 객체. 키는 문자열이고 값은 V 타입입니다.
*
* @example
* ```typescript
* const nestedObj = { a: { b: { c: 1 } } };
* const flatObj = flatObject({}, nestedObj);
* console.log(flatObj); // Outputs: { 'a.b.c': 1 }
* ```
*
* 또는 커링을 사용하여 함수를 반환할 수 있습니다.
*
* @param params - 평탄화 작업에 필요한 매개변수
* @returns 평탄화 작업을 수행하는 함수. 이 함수는 T 타입의 객체를 받아 평탄화된 객체를 반환합니다.
*
* @example
* ```typescript
* const flatten = flatObject({});
* const nestedObj = { a: { b: { c: 1 } } };
* const flatObj = flatten(nestedObj);
* console.log(flatObj); // Outputs: { 'a.b.c': 1 }
* ```
*/
const flatObject = curry(_flatObject);
/**
* 주어진 객체에서 빈 객체를 제거하는 함수입니다.
*
* @category Utils/Object
* @typeParam T - 어떤 키와 값을 가진 객체의 타입
*
* @param obj - 빈 객체를 제거할 대상 객체
* @returns 빈 객체가 제거된 새로운 객체
*
* @example
* ```typescript
* const obj = { a: { b: {} }, c: 1 };
* const result = removeEmptyObject(obj);
* console.log(result); // Outputs: { c: 1 }
* ```
*/
const removeEmptyObject = (obj) => Object.entries(obj).reduce((prev, [key, value]) => {
const updated = Object.assign({}, prev);
const hasValue = value !== undefined && value !== null;
const isObject = hasValue && typeof value === 'object';
if (!hasValue)
return updated;
if (isObject) {
const cleanedValue = removeEmptyObject(value);
const isEmpty = Object.keys(cleanedValue).length === 0;
if (!isEmpty)
updated[key] = cleanedValue;
return updated;
}
updated[key] = value;
return updated;
}, {});
function runIfFn(valueOrFn, ...args) {
return isFunction(valueOrFn) ? valueOrFn(...args) : valueOrFn;
}
/**
* @deprecated
* use `update` instead
*
* 객체의 지정된 깊은 위치에 값을 설정하거나 업데이트합니다.
*
* @category Utils/Object
*
* @typeParam T - 입력 객체의 타입
* @typeParam K - 설정 또는 업데이트할 속성의 깊은 경로를 나타내는 키
* @param key - 설정 또는 업데이트할 속성의 깊은 경로를 나타내는 키
* @param value - 설정할 값 또는 값을 반환하는 함수
* @param obj - 값을 설정 또는 업데이트할 객체
* @returns 값을 설정 또는 업데이트한 객체
*
* @example
* ```typescript
* const data = { nested: { prop: 42 } };
*
* // 객체의 깊은 경로에 값을 설정
* const updated1 = updateObj('nested.prop', 100, data); // { nested: { prop: 100 } }
*
* // 함수를 사용하여 값을 설정
* const updated2 = updateObj('nested.prop', (prev) => prev + 1, data); // { nested: { prop: 43 } }
*
* // 원본 객체의 다른 값을 참조하여 값을 설정
* const updated3 = updateObj('nested.prop', (prev, obj) => prev + obj.nested.prop, data); // { nested: { prop: 84 } }
*
* // 함수를 부분 적용하여 사용
* const updater = updateObj('nested.prop');
* const updated4 = updater(200)(data); // { nested: { prop: 200 } }
* ```
*
*
* @curried
*/
const updateObj = curry((key, value, obj) => {
const updated = cloneDeep(obj);
return set(updated, `${key}`.split('.'), runIfFn(value, get$1(updated, `${key}`.split('.')), obj));
});
/**
* 객체에서 선택된 속성을 기반으로 새로운 객체를 생성합니다.
* @category Utils/Object
* @typeParam T - 입력 객체의 타입
* @typeParam N - 생성된 객체의 각 속성 값의 타입
* @typeParam M - 선택된 속성과 반환값의 매핑
* @param mapper - 선택된 속성과 각 속성 값의 생성 함수로 이루어진 매핑 객체
* @param prev - 입력 객체
* @returns 선택된 속성을 기반으로 생성된 객체
*
* @example
* ```typescript
* const data = { a: 1, b: 2, c: 3 };
* const selectors = {
* sum: ({ a, b, c }) => a + b + c,
* product: ({ a, b, c }) => a * b * c,
* };
*
* const result = createObjBySelector(selectors, data);
* const result = createObjBySelector(selectors)(data);
*
* console.log(result); // { sum: 6, product: 6 }
* ```
*
* @curried
*/
const createObjBySelector = curry$1((mapper, prev) => {
const create = flow(Object.entries, reduce((acc, [key, selector]) => (Object.assign(Object.assign({}, acc), { [key]: selector(prev) })), {}));
return create(mapper);
});
/**
* @category Utils/Object
*
* 객체의 key 에서 flag 를 찾아서 해당 flag 를 기준으로 중첩 객체를 만들어주는 함수입니다.
*
* @param flag - object 생성 기준이 되는 flag 입니다.
* @param obj - flag 를 기준으로 중첩 객체를 만들 객체입니다.
*
* @returns flag 를 기준으로 중첩 객체를 만들어 반환합니다.
*
* @example
* ```ts
* const obj = {
* a: 1,
* 'b.a': 2,
* 'b.b': 3,
* 'c.a.a': 6,
* }
*
* const result = volumeUpObject('.', obj)
*
* console.log(result)
*
* // {
* // a: 1,
* // b: {
* // a: 3,
* // b: 4,
* // },
* // c: {
* // a: {
* // a: 6,
* // },
* // },
* // }
* ```
*/
const volumeUpObject = curry$1((flag, obj) => flow(pass(obj), Object.entries, reduce((prev, [key, value]) => {
const path = key.replaceAll(flag, '.');
return setWith(Object, path, value, Object.assign({}, prev));
}, {}))());
/**
* 객체의 property 를 key 로 받아서 값을 가져오는 함수입니다.
* 중접 객체의 경우 a.b.c 와 같이, 배열의 경우 a.0.b 와 같이 접근이 가능합니다.
*
*
* @category Utils/Fn
*
* @typeParam T - 객체의 타입
* @typeParam K - 객체의 key
* @param key - 객체의 key
* @param data - 객체 혹은 배열
* @returns 객체의 key 에 해당하는 값
*
* @example
* ```ts
*
* const data = {
* user: {
* name: 'John Doe',
* address: {
* city: 'New York',
* },
* posts: [
* { title: 'Post 1' },
* { title: 'Post 2' },
* ],
* }
*
* // 객체의 key 에 해당하는 값 가져오기
* get('user.name', data) // 'John Doe'
* get('user.address.city', data) // 'New York'
*
* // 배열의 key 에 해당하는 값 가져오기
* get('posts.0.title', data) // 'Post 1'
*
* // curried function 으로 사용하기
* const getUserName = get('user.name')
* getUserName(data) // 'John Doe'
*
* const logname = flow(get('user.name'), console.log)
* logname(data) // 'John Doe'
*
* const name = useStore(get('user.name')) // 'John Doe'
*
* ```
*/
const get = curry$1((key, data) => {
if (typeof key !== 'string')
throw new Error('key must be string');
if (typeof data !== 'object')
throw new Error('data must be object');
let result = data;
key.split('.').forEach((k) => {
result = result === null || result === void 0 ? void 0 : result[k];
});
return result;
});
/**
* 객체의 지정된 깊은 위치에 값을 설정하거나 업데이트합니다.
*
* @category Utils/Object
*
* @typeParam T - 입력 객체의 타입
* @typeParam K - 설정 또는 업데이트할 속성의 깊은 경로를 나타내는 키
* @param key - 설정 또는 업데이트할 속성의 깊은 경로를 나타내는 키
* @param value - 설정할 값 또는 값을 반환하는 함수
* @param obj - 값을 설정 또는 업데이트할 객체
* @returns 값을 설정 또는 업데이트한 객체
*
* @example
* ```typescript
* const data = { nested: { prop: 42 } };
*
* // 객체의 깊은 경로에 값을 설정
* const updated1 = set('nested.prop', 100, data); // { nested: { prop: 100 } }
*
* // 함수를 사용하여 값을 설정
* const updated2 = set('nested.prop', (prev) => prev + 1, data); // { nested: { prop: 43 } }
*
* // 원본 객체의 다른 값을 참조하여 값을 설정
* const updated3 = set('nested.prop', (prev, obj) => prev + obj.nested.prop, data); // { nested: { prop: 84 } }
*
* // 함수를 부분 적용하여 사용
* const updater = set('nested.prop');
* const updated4 = updater(200)(data); // { nested: { prop: 200 } }
* ```
*
*
* @curried
*/
const update = curry$1((key, value, obj) => {
const updated = cloneDeep(obj);
const path = `${key}`.split('.');
const prev = get$1(updated, path);
const result = runIfFn$1(value, prev, get$1, updated);
return set(updated, path, result);
});
/**
* @curried
* @category Functor
*/
class Keep_F {
constructor(kept, value) {
this.kept = kept;
this.value = value;
this.of = (value) => new Keep_F(this.kept, value);
this.map = (fn) => this.of(fn(this.value, this.kept));
this.kept = kept;
this.value = value;
}
}
/**
* @category Functor
*/
const keep = Object.assign((kept) => {
return new Keep_F(kept, kept);
}, {
of: curry((value, keep) => keep.of(value)),
map: curry((fn, keep) => keep.map(fn)),
value: (keep) => keep.value,
});
/**
* @category Functor
*/
class Maybe_F {
get isNothing() {
return this.$value === null || this.$value === undefined;
}
constructor($value) {
this.map = (fn) => {
return this.isNothing ?
Maybe_F.of(this.$value)
: Maybe_F.of(fn(this.$value));
};
this.$value = $value;
}
}
Maybe_F.of = (x) => new Maybe_F(x);
/**
* @category Functor
*/
const maybe = Object.assign((value) => new Maybe_F(value), {
map: (fn) => (maybe) => maybe.map(fn),
value: (maybe) => maybe.$value,
});
/**
* 문자열에서 지정된 문자열을 제거합니다.
*
* @category Utils/String
*
* @param str - 제거할 문자열
* @param s - 대상 문자열
* @returns 지정된 문자열이 제거된 결과 문자열
*
* @example
* ```typescript
* const removedStr1 = removeStr('a', 'banana'); // 'bnn'
* console.log(removedStr1);
*
* const removeA = removeStr('a'); // 부분 적용
* const removedStr2 = removeA('apple'); // 'pple'
* console.log(removedStr2);
*
* const removedStr3 = removeStr(' ', 'hello world'); // 'helloworld'
* console.log(removedStr3);
* ```
*
* @curried
*/
const removeStr = curry((str, s) => s.replaceAll(str, ''));
/**
* 문자열에 접두사를 추가합니다.
*
* @category Utils/String
* @param pre - 접두사로 사용될 문자열
* @param str - 접두사를 추가할 대상 문자열
* @returns 접두사가 추가된 문자열
*
* @example
* ```typescript
* const prefixedStr1 = prefix('pre-', 'string'); // 'pre-string'
* console.log(prefixedStr1);
*
* const prefixWithHello = prefix('Hello, '); // 부분 적용
* const prefixedStr2 = prefixWithHello('world!'); // 'Hello, world!'
* console.log(prefixedStr2);
*
* const prefixedStr3 = prefix('1. ')('First item'); // '1. First item'
* console.log(prefixedStr3);
* ```
*
* @curried
*/
const prefix = curry((pre, str) => {
return pre + str;
});
/**
* 문자열에 접미사를 추가합니다.
*
* @category Utils/String
*
* @param suf - 접미사로 사용될 문자열
* @param str - 접미사를 추가할 대상 문자열
* @returns 접미사가 추가된 문자열
*
* @example
* ```typescript
* const suffixedStr1 = suffix('-post', 'string'); // 'string-post'
* console.log(suffixedStr1);
*
* const suffixWithDot = suffix('.'); // 부분 적용
* const suffixedStr2 = suffixWithDot('extension'); // 'extension.'
* console.log(suffixedStr2);
*
* const suffixedStr3 = suffix('!', 'Hello'); // 'Hello!'
* console.log(suffixedStr3);
* ```
*
* @curried
*/
const suffix = curry((suf, str) => {
return str + suf;
});
/**
* 주어진 값을 로깅하고 반환합니다.
*
* @category Utils/Logger
*
* @typeParam T - 로깅할 값의 타입
* @param title - 로그 제목
* @param value - 로깅할 값
* @returns 주어진 값
*
* @example
* ```typescript
* const result = log('Result:', 42); // Result: 42
* const result = log('Result:')(42); // Result: 42
*
* const ex = flow(
* add(1),
* log('debug:'), // debug
* add(2)
* )
*
* ex(1) debug: 2
*
* ```
*
* @curried
*/
const log = curry((title, value) => {
console.log(title, value);
return value;
});
const collect = (...args) => args;
/**
* @category Utils/Math
*
* 숫자들의 소수점 자리수중 가장 긴 소수점 자리의 길이를 구합니다.
*
* @param numnbers - 소수점 자리수를 구할 숫자들
*
* @returns 소수점 자리수
* @example
* ```ts
* getDecimalPlaces(0.1, 0.2) // 1
* getDecimalPlaces(0.1, 0.02, 0.3) // 2
* getDecimalPlaces(0.1, 0.2, 0.333, 0.4) // 3
*/
const getDecimalPlaces = flow(collect, map(flow(toString, split('.'), prop(1), or(''), prop('length'))), spread(Math.max));
/**
* @category Utils/Math
*
* 두개의 숫자를 더합니다. 부정확 할 수 있는 부동 소수점 연산을 보정합니다.
* @param a - 첫번째 숫자
* @param b - 두번째 숫자
*
* @returns 두 숫자를 더한 결과
*
* @example
* ```ts
* add(0.1, 0.2) // 0.3
* add(0.1)(0.2) // 0.3
* ```
*/
const add = curry$1((a, b) => {
const dem = getDecimalPlaces(a, b);
const multiplier = Math.pow(10, dem) || 1;
return (a * multiplier + b * multiplier) / multiplier;
});
/**
* @category Utils/Math
*
* 두개의 숫자를 뺍니다. 부정확 할 수 있는 부동 소수점 연산을 보정합니다.
*
* @param a - 첫번째 숫자
* @param b - 두번째 숫자
*
* @returns 두 숫자를 뺀 결과
*
* @example
* ```ts
* subtract(0.3, 0.1) // 0.2
* subtract(0.3)(0.1) // 0.2
* ```
*/
const subtract = curry$1((a, b) => {
const dem = getDecimalPlaces(a, b);
const multiplier = Math.pow(10, dem) || 1;
return (a * multiplier - b * multiplier) / multiplier;
});
/**
* @category Utils/Math
*
* 두개의 숫자를 곱합니다. 부정확 할 수 있는 부동 소수점 연산을 보정합니다.
*
* @param a - 첫번째 숫자
* @param b - 두번째 숫자
*
* @returns 두 숫자를 곱한 결과
*
* @example
* ```ts
* multiply(0.1, 0.2) // 0.02
* multiply(0.1)(0.2) // 0.02
* ```
*/
const multiply = curry$1((a, b) => {
const dem = getDecimalPlaces(a, b);
const multiplier = Math.pow(10, dem) || 1;
return (a * multiplier * b * multiplier) / Math.pow(multiplier, 2);
});
/**
* @category Utils/Math
*
* 두개의 숫자를 나눕니다. 부정확 할 수 있는 부동 소수점 연산을 보정합니다.
*
* @param a - 첫번째 숫자
* @param b - 두번째 숫자
*
* @returns 두 숫자를 나눈 결과
*
* @example
* ```ts
* devide(0.3, 0.1) // 3
* devide(0.3)(0.1) // 3
* ```
*/
const devide = curry$1((a, b) => {
const dem = getDecimalPlaces(a, b);
const multiplier = Math.pow(10, dem) || 1;
return (a * multiplier) / (b * multiplier);
});
/**
* @category Utils/File
*
* 특정 바이트 단위를 바이트로 변환합니다.
*
* @param from - 변환할 바이트 단위
* @param value - 변환할 값
*
* @returns 변환된 바이트 값
*
* @example
* ```ts
* byteFrom('KB', 1) // 1024
* byteFrom('KB')(1) // 1024
* ```
*/
const byteFrom = curry$1((from, value) => {
switch (from) {
case 'B':
return value;
case 'KB':
return multiply(value, 1024);
case 'MB':
return multiply(value, Math.pow(1024, 2));
case 'GB':
return multiply(value, Math.pow(1024, 3));
case 'TB':
return multiply(value, Math.pow(1024, 4));
default:
return value;
}
});
/**
* @category Utils/File
*
* 바이트를 특정 바이트 단위로 변환합니다.
*
* @param to - 변환할 바이트 단위
* @param value - 변환할 값
*
* @returns 변환된 바이트 값
*
* @example
* ```ts
* byteTo('KB', 1024) // 1
* byteTo('KB')(1024) // 1
* ```
*/
const byteTo = curry$1((to, value) => {
switch (to) {
case 'B':
return value;
case 'KB':
return devide(value, 1024);
case 'MB':
return devide(value, Math.pow(1024, 2));
case 'GB':
return devide(value, Math.pow(1024, 3));
case 'TB':
return devide(value, Math.pow(1024, 4));
default:
return value;
}
});
/**
* @category Utils/File
*
* 특정 바이트 단위를 다른 바이트 단위로 변환합니다.
*
* @param from - 변환할 바이트 단위
* @param to - 변환될 바이트 단위
* @param value - 변환할 값
*
* @returns 변환된 바이트 값
*
* @example
* ```ts
* const KbToB = byteFromTo('KB', 'B')
* KbToB(1) // 1024
*
* const GBToMb = byteFromTo('gb', 'mb')
* GBToMb(1) // 1024
*
* byteFromTo('KB', 'B', 1) // 1024
* byteFromTo('KB')('B')(1) // 1024
* ```
*/
const byteFromTo = curry$1((from, to, value) => flow(pass(value), byteFrom(from), byteTo(to))());
/**
* @category Utils/File
* 주어진 파일 크기가 최대 크기를 초과하는지 확인하는 함수입니다.
*
* @example
* ```ts
* // 값이 500바이트인 경우
* isOverSize([1000, 'B'], 500); // false
*
* // 커링 사용 예
* const isOver1MB = isOverSize([1, 'MB']);
* isOver1MB(500000); // true
* ```
*
* @param maxSize - 파일의 최대 크기
* @param value - 검사할 값. 바이트 단위의 숫자 또는 크기와 단위를 포함하는 배열
* @returns 값이 최대 크기를 초과하면 true, 그렇지 않으면 false
*/
const isOverSize = curry$1((maxSize, value) => flow(pass(maxSize[0]), byteFrom(maxSize[1]), (maxSize) => [
maxSize,
isArray(value) ? byteFrom(value[1], value[0]) : byteFrom('B', value),
], ([maxSize, target]) => maxSize < target)());
/**
* @category Utils/File
*
* createUploadFlow 함수는 S3 파일 업로드를 위한 플로우를 생성합니다.
*
* @example
*
* ```ts
* const { uploadFile, uploadFiles } = createUploadFlow({
* prepareUpload: async ({name} : {name : string}) => {
* return {
* name: name,
* type: "image",
* }
* },
* uploadFileToS3: async ({ name, type }) => {
* return { name, type, imgUrl: "https://example.com" }
* },
* })
*
* const result = await uploadFile({ name: "example" }) // { name: "example", type: "image", imgUrl: "https://example.com" }
* const results = await uploadFiles([{ name: "example" }, { name: "example2" }]) // { fulfilled: [{ name: "example", type: "image", imgUrl: "https://example.com" } , ...], rejected: [] }
* ```
*
*/
const createS3UploadFlow = (config) => {
const uploadFile = (input) => config
.prepareUpload(input) //
.then(config.uploadFileToS3);
const uploadFiles = (inputs) => __awaiter(void 0, void 0, void 0, function* () {
const files = inputs.map(uploadFile);
const results = yield Promise.allSettled(files);
const fulfilled = results
.filter((res) => res.status === 'fulfilled')
.map(prop('value'));
const rejected = results //
.filter((res) => res.status === 'rejected');
return {
fulfilled,
rejected,
};
});
return {
uploadFile,
uploadFiles,
};
};
const isNullish = (value) => value === null || value === undefined;
const isNotNullish = (value) => value !== null && typeof value !== 'undefined';
const assertItemOf = (array, item, msg) => {
const emptyMsg = `array is empty`;
if (!array.length)
throw new Error(msg || emptyMsg);
const defMsg = `must be one of ${array.join(', ')} but got ${JSON.stringify(item)}`;
if (!array.find((i) => i === item))
throw new Error(msg || defMsg);
};
class InvalidTokenError extends Error {
}
InvalidTokenError.prototype.name = 'InvalidTokenError';
function b64DecodeUnicode(str) {
return decodeURIComponent(atob(str).replace(/(.)/g, (m, p) => {
let code = p.charCodeAt(0).toString(16).toUpperCase();
if (code.length < 2) {
code = '0' + code;
}
return '%' + code;
}));
}
function base64UrlDecode(str) {
let output = str.replace(/-/g, '+').replace(/_/g, '/');
switch (output.length % 4) {
case 0:
break;
case 2:
output += '==';
break;
case 3:
output += '=';
break;
default:
throw new Error('base64 string is not of the correct length');
}
try {
return b64DecodeUnicode(output);
}
catch (err) {
return atob(output);
}
}
function jwtDecode(token, options) {
if (typeof token !== 'string') {
throw new InvalidTokenError('Invalid token specified: must be a string');
}
options || (options = {});
const pos = options.header === true ? 0 : 1;
const part = token.split('.')[pos];
if (typeof part !== 'string') {
throw new InvalidTokenError(`Invalid token specified: missing part #${pos + 1}`);
}
let decoded;
try {
decoded = base64UrlDecode(part);
}
catch (e) {
throw new InvalidTokenError(`Invalid token specified: invalid base64 for part #${pos + 1} (${e.message})`);
}
try {
return JSON.parse(decoded);
}
catch (e) {
throw new InvalidTokenError(`Invalid token specified: invalid json for part #${pos + 1} (${e.message})`);
}
}
/**
* 고차 함수로 fetch를 확장합니다.
*
*/
const applyDefaultOptions = ([input, requestInit], defaultOptions) => {
const headers = new Headers(defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.headers);
new Headers(requestInit === null || requestInit === void 0 ? void 0 : requestInit.headers).forEach((value, key) => {
headers.set(key, value);
});
let inputToReturn = input;
if (defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.baseUrl) {
inputToReturn = new URL(input, defaultOptions.baseUrl);
}
return [
inputToReturn,
Object.assign(Object.assign({}, requestInit), { headers }),
];
};
// Request 객체를 처리하려면 body를 ArrayBuffer로 읽어야 합니다.
const mergeRequestObjectWithRequestInit = (request, requestInit) => __awaiter(void 0, void 0, void 0, function* () {
const mergedRequest = new Request(request, requestInit);
const body = yield new Response(mergedRequest.body).arrayBuffer();
return {
method: mergedRequest.method,
headers: mergedRequest.headers,
body: body,
referrer: mergedRequest.referrer,
referrerPolicy: mergedRequest.referrerPolicy,
mode: mergedRequest.mode,
credentials: mergedRequest.credentials,
cache: mergedRequest.cache,
redirect: mergedRequest.redirect,
integrity: mergedRequest.integrity,
keepalive: mergedRequest.keepalive,
signal: mergedRequest.signal,
window: requestInit === null || requestInit === void 0 ? void 0 : requestInit.window,
};
});
const normalizeArgs = (...args) => __awaiter(void 0, void 0, void 0, function* () {
let input;
let requestInit;
if (args[0] instanceof Request) {
input = args[0].url;
requestInit = yield mergeRequestObjectWithRequestInit(args[0], args[1]);
}
else {
input = args[0];
requestInit = args[1];
}
return [input, requestInit];
});
/**
* @category Utils/Fetch
*
* 고차 함수로 fetch를 확장하거나, interceptor, baseUrl, headers 을 옵션으로 넣어 사용할 수 있습니다.
*
* @param defaultOptions - fetchHelper 함수의 옵션입니다.
*
* @example
*
* ```ts
* export const fetchHelperInterceptors: FetchHelper = (args) =>
* fetchHelper({
* ...args,
* interceptors: {
* request: requestInterceptor,
* response: responseInterceptor,
* },
* })
*
*
* import { fetchHelperInterceptors } from './fetch-interceptors'
*
* export const fetchExtended = fetchHelperInterceptors({
* baseUrl: https://jsonplaceholder.typicode.com
* })
*
* export default fetchExtended
*
* fetchExtended('/todos/1')
*
*
* ```
*/
const fetchHelper = (defaultOptions) => (...args) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b, _c, _d, _e;
const defaultOptionAppliedArgs = applyDefaultOptions(yield normalizeArgs(...args), defaultOptions);
// apply request interceptor
const fetchProvided = (defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.fetch) || fetch;
let requestInterceptorAppliedArgs;
if ((_a = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.interceptors) === null || _a === void 0 ? void 0 : _a.request) {
requestInterceptorAppliedArgs =
yield ((_c = (_b = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.interceptors) === null || _b === void 0 ? void 0 : _b.request) === null || _c === void 0 ? void 0 : _c.call(_b, defaultOptionAppliedArgs, fetchProvided));
}
else {
requestInterceptorAppliedArgs = defaultOptionAppliedArgs;
}
// api call
const response = yield fetchProvided(...requestInterceptorAppliedArgs);
// apply response interceptor
return (((_e = (_d = defaultOptions === null || defaultOptions === void 0 ? void 0 : defaultOptions.interceptors) === null || _d === void 0 ? void 0 : _d.response) === null || _e === void 0 ? void 0 : _e.call(_d, response, requestInterceptorAppliedArgs, fetchProvided)) || response);
});
export { InvalidTokenError, Keep_F, Maybe_F, add, arrayToMap, arrayToRecord, assertItemOf, awaited, byteFrom, byteFromTo, byteTo, collect$1 as collect, createObjBySelector, createS3UploadFlow, delay, devide, effect, fetchHelper, flatObject, get, getDecimalPlaces, isEvery, isNotNullish, isNullish, isOverSize, jwtDecode, keep, log, maybe, multiply, not, or, paginate, pass, prefix, relay, removeEmptyObject, removeStr, retryReqeustManager, runIfFn$1 as runIfFn, subtract, suffix, update, updateObj, volumeUpObject };