UNPKG

@lykmapipo/common

Version:

Helper utilities for day to day development

1,606 lines (1,506 loc) 40.8 kB
import { resolve } from 'path'; import { readFileSync } from 'fs'; import { arch, cpus, endianness, freemem, homedir, hostname, loadavg, networkInterfaces, platform, release, tmpdir, totalmem, type, uptime } from 'os'; import { isNaN, isBoolean, isNumber, isError, isFunction, isString, isEmpty, trim, isDate, first, filter, cloneDeep, flattenDeep, map, reduce, isArray, compact as compact$1, isPlainObject, omitBy, uniq as uniq$1, orderBy, assign as assign$1, merge, pick, forEach, toLower, startCase, words, get, camelCase, includes, every, some, toUpper, omit, join as join$1, size, find, noop, snakeCase, toString } from 'lodash'; export { getExtension as mimeExtensionOf, getType as mimeTypeOf } from 'mime'; import { flatten, unflatten } from 'flat'; import { message } from 'statuses'; export { message as STATUS_CODES } from 'statuses'; import inflection from 'inflection'; import generateColor from 'randomcolor'; import moment from 'moment'; import hashObject from 'object-hash'; import renderTemplate from 'string-template'; import stripTags from 'striptags'; import parseValue from 'auto-parse'; export { v1 as uuidv1, v3 as uuidv3, v4 as uuidv4, v5 as uuidv5 } from 'uuid'; export { isBrowser, isNode, isWebWorker } from 'browser-or-node'; /** * @name RESOURCE_ACTIONS * @description Default resource actions * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.1.0 * @static * @private */ const RESOURCE_ACTIONS = [ // PERMISSION_SEED_ACTIONS // RESOURCE_ACTIONS 'list', 'create', 'view', 'edit', 'delete', 'share', 'print', 'import', 'export', 'download', ]; /** * @function isNotValue * @name isNotValue * @description Check if variable has no associated state or has empty state * @param {*} value variable to check * @returns {boolean} whether variable contain state * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.10.0 * @version 0.3.0 * @static * @public * @example * * const notValue = isNotValue('a'); * // => false * * const notValue = isNotValue(null); * // => true */ const isNotValue = (value) => { // handle NaN if (isNaN(value)) { return true; } // handle boolean, number, error and function if ( isBoolean(value) || isNumber(value) || isError(value) || isFunction(value) ) { return false; } // handle string if (isString(value)) { return !value || isEmpty(trim(value)); } // handle date if (isDate(value)) { return !value || !value.getTime(); } // handle other types return !value || isEmpty(value); }; /** * @function isValue * @name isValue * @description Check if variable has associated state or has no empty state * @param {*} value variable to check * @returns {boolean} whether variable contain state * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.40.0 * @version 0.2.0 * @static * @public * @example * * const notValue = isValue('a'); * // => true * * const notValue = isValue(null); * // => false */ const isValue = (value) => { return !isNotValue(value); }; /** * @function firstValue * @name firstValue * @description Obtain first valid value * @param {*} values list of values * @returns {*} first valid value * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.36.0 * @version 0.1.0 * @static * @public * @example * * firstValue('a', 'b'); * // => 'a' * * firstValue(undefined, 'b'); * // => 'b' */ const firstValue = (...values) => { return first(filter([...values], (value) => !isNotValue(value))); }; /** * @function copyOf * @name copyOf * @description Recursively clone a value * @param {*} value valid value to clone * @returns {*} cloned value * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.25.0 * @version 0.1.0 * @static * @public * @example * * const copy = copyOf('a'); * // => 'a' * * const copy = copyOf({ 'a': 1 }); * // => { 'a': 1 } */ const copyOf = (value) => cloneDeep(value); /** * @function mapToUpper * @name mapToUpper * @description Convert list of values to upper values * @param {...string} values list to convert to upper * @returns {string[]} list of upper values * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.12.0 * @version 0.1.0 * @static * @public * @example * * const mapToUpper = mapToUpper('a'); * // => ['A'] * * const mapToUpper = mapToUpper(['a', 'b'], 'c'); * // => ['A', 'B', 'C'] */ const mapToUpper = (...values) => { // convert lower to upper const convertToUpper = (value) => toUpper(value); // collect values const lowerValues = flattenDeep([...values]); // convert to upper const upperValues = map(lowerValues, convertToUpper); // return upper values return upperValues; }; /** * @function mapToLower * @name mapToLower * @description Convert list of values to lower values * @param {...string} values list to convert to lower * @returns {string[]} list of lower values * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.12.0 * @version 0.1.0 * @static * @public * @example * * const mapToLower = mapToLower('A'); * // => ['a'] * * const mapToLower = mapToLower(['A', 'B'], 'C'); * // => ['a', 'b', 'c'] */ const mapToLower = (...values) => { // convert upper to lower const convertToLower = (value) => toLower(value); // collect values const upperValues = flattenDeep([...values]); // convert to lower const lowerValues = map(upperValues, convertToLower); // return lower values return lowerValues; }; /** * @function areNotEmpty * @name areNotEmpty * @description Check if provided values are not empty * @param {...string} values set of values to check for emptiness * @returns {boolean} whether values are not empty * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.2.0 * @static * @public * @example * * const notEmpty = areNotEmpty('a', 'b', 'c'); * // => true * * const notEmpty = areNotEmpty('a', 'b', null); * // => false */ const areNotEmpty = (...values) => { // copy values const copyOfValues = [...values]; // check for empty values so far const checkForEmpties = (arePreviousEmpty, nextValue) => { return arePreviousEmpty && !isEmpty(toString(nextValue)); }; // assert for emptiness const notEmpty = reduce(copyOfValues, checkForEmpties, true); // return emptiness state return notEmpty; }; /** * @function compact * @name compact * @description Creates new array(or object) with all falsey values removed. * The values false, null, 0, "", undefined, and NaN are falsey. * @param {Array|object} value The array(or object) to compact. * @returns {object|Array} new array(or object) of filtered values. * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.1.0 * @static * @public * @example * * const b = compact([null, 1, "", undefined]); * // => [ 1 ] * * const y = compact({a: 1, b: "", c: undefined}); * // => { a: 1 } */ const compact = (value) => { // copy value const copyOfValue = copyOf(value); // compact array if (isArray(copyOfValue)) { return compact$1(copyOfValue); } // compact object if (isPlainObject(copyOfValue)) { return omitBy(copyOfValue, isNotValue); } // return value return copyOfValue; }; /** * @function uniq * @name uniq * @description Creates new duplicate-free version of array(or object). * @param {Array|object} value The array(or object) to inspect. * @returns {object|Array} new duplicate free array(or object). * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.1.0 * @static * @public * @example * * const b = uniq([null, 1, 1, "", undefined, 2]); * // => [ 1, 2 ] * * const y = uniq({a: 1, b: "", c: undefined}); * // => { a: 1 } */ const uniq = (value) => { // uniq if (value) { let copyOfValue = compact(value); copyOfValue = isArray(value) ? uniq$1(copyOfValue) : copyOfValue; return copyOfValue; } // return value return value; }; /** * @function sortedUniq * @name sortedUniq * @description Creates new duplicate-free version of sorted array(or object). * @param {Array|object} value The array(or object) to inspect. * @returns {object|Array} new duplicate free sorted array(or object). * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.1.0 * @static * @public * @example * * const b = sortedUniq([null, 1, 2, "", undefined, 1]); * // => [ 1, 2 ] * * const y = sortedUniq({a: 1, b: "", c: undefined}); * // => { a: 1 } */ const sortedUniq = (value) => { // sortedUniq if (value) { let copyOfValue = uniq(value); copyOfValue = isArray(copyOfValue) ? orderBy(copyOfValue) : copyOfValue; return copyOfValue; } // return value return value; }; /** * @function assign * @name assign * @description Assign a list of objects into a single object * * Note:** This method mutates `object`. * @param {object} [object={}] destination object * @param {...object} objects list of objects * @returns {object} a merged object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.26.0 * @version 0.1.0 * @static * @public * @example * * const obj = { a: 1 }; * assign(obj, { b: 1 }, { c: 2}); * // => { a: 1, b: 1, c: 2 } */ const assign = (object = {}, ...objects) => { // ensure source objects let sources = compact$1([...objects]); sources = map(sources, compact); // assign objects assign$1(object, ...sources); // return assigned object return object; }; /** * @function mergeObjects * @name mergeObjects * @description Merge a list of objects into a single object * @param {...object} objects list of objects * @returns {object} a merged object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.10.0 * @version 0.1.0 * @static * @public * @example * * const obj = mergeObjects({ a: 1 }, { b: 1 }, { c: 2}, { c: 2}, {b: null}) * // => { a: 1, b: 1, c: 2 } */ const mergeObjects = (...objects) => { // ensure source objects let sources = compact$1([...objects]); sources = map(sources, compact); // merged objects const merged = merge({}, ...sources); // return merged object return merged; }; /** * @function safeMergeObjects * @name safeMergeObjects * @description Merge a list of objects into a single object without * cloning sources * @param {...object} objects list of objects * @returns {object} a merged object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.31.0 * @version 0.1.0 * @static * @public * @example * * const obj = safeMergeObjects({ a: 1 }, { b: 1 }, { c: 2}, { c: 2}, {b: null}) * // => { a: 1, b: 1, c: 2 } */ const safeMergeObjects = (...objects) => { // ensure source objects const sources = compact$1([...objects]); // merged objects const merged = merge({}, ...sources); // return merged object return merged; }; /** * @function pkg * @name pkg * @description Read package information * @param {string} [path] valid path to package.json file * @param {...string} field fields to pick from package * @returns {object} current process package information * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.1.0 * @version 0.3.0 * @static * @public * @example * * const { name, version } = pkg(); * // => { name: ..., version: ...} * * const { name, version } = pkg(__dirname); * // => { name: ..., version: ...} */ const pkg = (path, ...field) => { // try read from path or process cwd const read = () => { try { const filePath = resolve(path, 'package.json'); const json = JSON.parse(readFileSync(filePath, 'utf8')); return json; } catch (e) { const filePath = resolve(process.cwd(), 'package.json'); const json = JSON.parse(readFileSync(filePath, 'utf8')); return json; } }; // try read package data try { const packageInfo = mergeObjects(read()); const fields = uniq([...field, path]); if (!isEmpty(fields)) { const info = { ...pick(packageInfo, ...fields) }; return isEmpty(info) ? { ...packageInfo } : info; } return packageInfo; } catch (e) { // no package data found return {}; } }; /** * @function scopesFor * @name scopesFor * @description Generate resource scopes * @param {...string} resources valid resources * @returns {string[]} resources scopes * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * const scopes = scopesFor('user') * // => ['user:create', 'user:view'] */ const scopesFor = (...resources) => { // initialize resources scopes let scopes; // map resource to actions const toActions = (resource) => { // map action to wildcard scopes const toWildcard = (action) => { // map action to scope(permission) const scope = toLower([resource, action].join(':')); return scope; }; // create scopes(permissions) per action return map(RESOURCE_ACTIONS, toWildcard); }; // generate resources scopes if (resources) { // copy unique resources const copyOfResources = uniq([...resources]); // create scopes(permissions) per resource scopes = map(copyOfResources, toActions); scopes = sortedUniq(flattenDeep(scopes)); } // return resources scopes return scopes; }; /** * @function permissionsFor * @name permissionsFor * @description Generate resource permissions * @param {...string} resources valid resources * @returns {object[]} resources permissions * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.28.0 * @version 0.1.0 * @static * @public * @example * * const permissions = permissionsFor('User') * // => [{resource: 'User', wildcard: 'user:create', action: ...}, ....]; */ const permissionsFor = (...resources) => { // initialize resources permissions let permissions = []; // generate resources permissions if (resources) { // copy unique resources const copyOfResources = uniq([...resources]); // create permissions(permissions) per resource forEach(copyOfResources, (resource) => { // prepare resource permissions const resourcePermissions = map(RESOURCE_ACTIONS, (action) => { return { resource, action: toLower(action), description: startCase(`${action} ${resource}`), wildcard: toLower([resource, action].join(':')), }; }); // collect resource permissions permissions = [...permissions, ...resourcePermissions]; }); } // return resources permissions return permissions; }; /** * @function abbreviate * @name abbreviate * @description Generate shortened form of word(s) or phrase. * @param {...string} words set of words to derive abbreaviation * @returns {string} abbreviation * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.6.0 * @version 0.1.0 * @static * @public * @example * * const abbreaviation = abbreviate('Ministry of Finance') * // => MoF */ const abbreviate = (...words$1) => { // ensure words let phrases = flattenDeep([...words$1]); phrases = words(phrases.join(' ')); // generate abbreviation const pickFirstLetters = (abbr, phrase) => { return toUpper(abbr + first(phrase)); }; const abbreviation = reduce(phrases, pickFirstLetters, ''); // return abbreviation return abbreviation; }; /** * @function idOf * @name idOf * @description Obtain an id or a given object * @param {object} data object to pick id from * @returns {*} id of a given object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.10.0 * @version 0.1.0 * @static * @public * @example * * const id = idOf({ id: 1 }) * // => 1 * * const id = idOf({ _id: 1 }) * // => 1 */ const idOf = (data) => get(data, '_id') || get(data, 'id'); /** * @function variableNameFor * @name variableNameFor * @description Produce camelize variable name based on passed strings * @param {...string} names list of strings to produce variable name * @returns {string} camelized variable name * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.10.0 * @version 0.1.0 * @static * @public * @example * * const name = variableNameFor('get', 'name'); * // => getName * * const name = variableNameFor('pick', 'a', 'name'); * // => pickAName */ const variableNameFor = (...names) => camelCase([...names].join(' ')); /** * @function has * @name has * @description Check if value is in a collection * @param {Array} collection The collection to inspect. * @param {*} value The value to search for. * @returns {boolean} whether value is in collection * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.11.0 * @version 0.1.0 * @static * @public * @example * * const hasValue = has([ 1, 2 ], 1); * // => true * * const hasValue = has([ 'a', 'b' ], 'c'); * // => false */ const has = (collection, value) => includes(collection, value); /** * @function hasAll * @name hasAll * @description Check if all value are in a collection * @param {Array} collection The collection to inspect. * @param {*} values The values to search for. * @returns {boolean} whether values are in collection * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.11.0 * @version 0.1.0 * @static * @public * @example * * const hasValues = hasAll([ 1, 2 ], 1, 2); * // => true * * const hasValues = hasAll([ 1, 2 ], [ 1, 2 ]); * // => true * * const hasValues = hasAll([ 'a', 'b' ], 'c', 'd'); * // => false */ const hasAll = (collection, ...values) => { // check if value is in collection const checkIfIsInCollection = (value) => has(collection, value); // check if collection has all values const flatValues = flattenDeep([...values]); const areAllInCollection = every(flatValues, checkIfIsInCollection); // return whether collection has all value return areAllInCollection; }; /** * @function hasAny * @name hasAny * @description Check if any value is in a collection * @param {Array} collection The collection to inspect. * @param {*} values The values to search for. * @returns {boolean} whether any value is in collection * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.11.0 * @version 0.1.0 * @static * @public * @example * * const hasValues = hasAny([ 1, 2 ], 1, 2); * // => true * * const hasValues = hasAny([ 1, 2 ], [ 1, 2 ]); * // => true * * const hasValues = hasAny([ 'a', 'b' ], 'b', 'd'); * // => true * * const hasValues = hasAny([ 'a', 'b' ], 'c', 'd'); * // => false */ const hasAny = (collection, ...values) => { // check if value is in collection const checkIfIsInCollection = (value) => has(collection, value); // check if collection has all values const flatValues = flattenDeep([...values]); const isAnyInCollection = some(flatValues, checkIfIsInCollection); // return whether collection has any value return isAnyInCollection; }; /** * @function normalizeError * @name normalizeError * @description Normalize error instance with name, code, status and message. * * Note:** This method mutates `object`. * @param {Error} error valid error instance * @param {object} [options] additional convert options * @param {string} [options.name=Error] default error name * @param {string} [options.code=500] default error code * @param {string} [options.status=500] default error status * @param {string} [options.message=500] default error message * @see {@link https://jsonapi.org/format/#errors} * @returns {Error} normalized error object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.26.0 * @version 0.1.0 * @static * @public * @example * * const body = normalizeError(new Error('Missing API Key')); * // => error.status = 500; */ const normalizeError = (error, options = {}) => { // ensure options let { name = 'Error', code = 500, status, message: message$1 } = mergeObjects(options); // prepare error properties code = error.code || error.statusCode || code; status = error.status || error.statusCode || status || code; name = error.name || name; message$1 = error.message || message$1 || message[code]; // assign values assign(error, { code, status, name, message: message$1 }); // return normalized error return error; }; /** * @function bagify * @name bagify * @description Normalize errors bag to light weight object * @param {object} errors valid errors bag * @returns {object} formatted errors bag * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.14.0 * @version 0.2.0 * @static * @public * @example * * const body = bagify({name : new Error('Validation Error') }); * // => { name: { name: 'Error', message: 'Name Required'}, ... } */ const bagify = (errors = {}) => { // initialize normalize errors bag const bag = {}; // iterate errors ba forEach(errors, (error = {}, key) => { // simplify error bag const { message, name, type, kind, path, value, index, properties = {}, } = error; const normalized = mergeObjects( { message, name, type, kind, path, value, index }, properties ); // reset key with normalized error const props = ['message', 'name', 'type', 'kind', 'path', 'value', 'index']; bag[key] = pick(normalized, ...props); }); // return errors bag return bag; }; /** * @function mapErrorToObject * @name mapErrorToObject * @description Convert error instance to light weight object * @param {Error} error valid error instance * @param {object} [options] additional convert options * @param {string} [options.name=Error] default error name * @param {string} [options.code=500] default error code * @param {string} [options.stack=false] whether to include error stack * @param {string} [options.status=500] default error status * @see {@link https://jsonapi.org/format/#errors} * @returns {object} formatted error object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.13.0 * @version 0.1.0 * @static * @public * @example * * const body = mapErrorToObject(new Error('Missing API Key')); * // => { name:'Error', message: 'Missing API Key', ... } */ const mapErrorToObject = (error, options = {}) => { // ensure options const { name = 'Error', code = 500, stack = false, status, message: message$1, description, } = mergeObjects(options); // prepare error payload const body = {}; body.code = error.code || error.statusCode || code; body.status = error.status || error.statusCode || status || code; body.name = error.name || name; body.message = error.message || message$1 || message[code]; body.description = error.description || description || body.message; body.errors = error.errors ? bagify(error.errors) : undefined; body.stack = stack ? error.stack : undefined; // support OAuth v2 error style // https://tools.ietf.org/html/rfc6749#page-71 body.uri = get(error, 'error_uri', error.uri); body.error = error.error || body.name; body.error_description = body.description; body.error_uri = body.uri; // return formatted error response return mergeObjects(body); }; /** * @function osInfo * @name osInfo * @description Obtain operating system information * @returns {object} os information object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.14.0 * @version 0.1.0 * @static * @public * @example * * const info = osInfo(); * // => { arch:'x64', ... } */ const osInfo = () => { // collect os information const info = { arch: arch(), cpus: cpus(), endianness: endianness(), freemem: freemem(), homedir: homedir(), hostname: hostname(), loadavg: loadavg(), networkInterfaces: networkInterfaces(), platform: platform(), release: release(), tmpdir: tmpdir(), totalmem: totalmem(), type: type(), uptime: uptime(), }; // return collected os information return info; }; /** * @function processInfo * @name processInfo * @description Obtain current process information * @returns {object} current process information * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.15.0 * @version 0.1.0 * @static * @public * @example * * const info = processInfo(); * // => { pid: 8989, ... } */ const processInfo = () => { // collect process information const info = { arch: process.arch, cpuUsage: process.cpuUsage(), cwd: process.cwd(), features: process.features, egid: process.getegid(), euid: process.geteuid(), gid: process.getgid(), groups: process.getgroups(), uid: process.getuid(), hrtime: process.hrtime(), memoryUsage: process.memoryUsage(), pid: process.pid, platform: process.platform, ppid: process.ppid, title: process.title, uptime: process.uptime(), version: process.version, versions: process.versions, }; // return collected process information return info; }; /** * @function randomColor * @name randomColor * @description Generating attractive random colors * @param {object} [optns] valid generator options * @param {string} [optns.luminosity=light] controls the luminosity of the * generated color. you can specify a string containing `bright`, `light` or * `dark`. * @returns {string} random color * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.18.0 * @version 0.1.0 * @static * @public * @example * * const color = randomColor(); * // => #C349D8 */ const randomColor = (optns = { luminosity: 'light' }) => { const options = mergeObjects(optns); const color = toUpper(generateColor(options)); return color; }; /** * @function formatDate * @name formatDate * @description Format a date using specified format * @param {Date} [date=new Date()] valid date instance * @param {string} [format='YYYY-MM-DD'] valid date format * @returns {string} formatted date string * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.19.0 * @version 0.1.0 * @static * @public * @example * * const date = formatDate(new Date(), 'YYYY-MM-DD'); * // => 2019-05-30 */ const formatDate = (date = new Date(), format = 'YYYY-MM-DD') => { const formatted = moment.utc(date).format(format); return formatted; }; /** * @function parseDate * @name parseDate * @description Parse a date in UTC from specified format * @param {string} date valid date string * @param {string} [format='YYYY-MM-DD'] valid date format * @returns {string} parsed date object in UTC * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.41.0 * @version 0.1.0 * @static * @public * @example * * const date = parseDate('2019-05-30', 'YYYY-MM-DD'); * // => Thu May 30 2019 ... */ const parseDate = (date, format = 'YYYY-MM-DD') => { const parsed = moment.utc(date, format).toDate(); return parsed; }; /** * @function hashOf * @name hashOf * @description Generate hash of provided object * @param {object} object valid object to hash * @param {...string} [ignore] properties to ignore * @returns {string} valid object hash * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.21.0 * @version 0.1.0 * @static * @public * @example * * const hash = hashOf({ foo: 'bar' }) * // => '67b69634f9880a282c14a0f0cb7ba20cf5d677e9' */ const hashOf = (object, ...ignore) => { // ensure object let copyOfObject = mergeObjects(object); copyOfObject = omit(copyOfObject, ...ignore); // compute hash const hash = hashObject(copyOfObject); // return computed hash return hash; }; /** * @function parseTemplate * @name parseTemplate * @description Parse, format and render string based template * @param {string} template valid template * @param {object} data object valid object apply on template * @returns {string} formatted string * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.21.0 * @version 0.1.0 * @static * @public * @example * * const template = 'Hello {name}, you have {count} unread messages'; * const formatted = parseTemplate(template, { name: 'John', count: 12 }); * // => 'Hello John, you have 12 unread messages' */ const parseTemplate = (template, data) => { // ensure copy const copyOfTemplate = copyOf(template); const copyOfData = mergeObjects(data); // render string template const formatted = renderTemplate(copyOfTemplate, copyOfData); // return formatted string return formatted; }; /** * @function stripHtmlTags * @name stripHtmlTags * @description Strip HTML tags from a string * @param {string} html valid html string * @returns {string} string with no html tags * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.21.0 * @version 0.1.0 * @static * @public * @example * * const html = 'lorem ipsum <strong>dolor</strong> <em>sit</em> amet'; * const formatted = stripHtmlTags(html); * // => 'lorem ipsum dolor sit amet' */ const stripHtmlTags = (html) => { const copyOfHtml = copyOf(html); const formatted = stripTags(copyOfHtml); return formatted; }; /** * @function stringify * @name stringify * @description Safely converts a given value to a JSON string * @param {*} value valid value * @returns {string} JSON string of a value * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.22.0 * @version 0.1.0 * @static * @public * @example * * const value = { x: 5, y: 6 }; * const string = stringify(value); * // => '{"x":5,"y":6}' */ const stringify = (value) => { try { return JSON.stringify(value); } catch (e) { return value; } }; /** * @function parse * @name parse * @description Safely parses a JSON string to a value * @param {string} value JSON string of a value * @returns {*} valid value * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.22.0 * @version 0.1.0 * @static * @public * @example * * const string = '{"x":5,"y":6}'; * const value = parse(value); * // => { x: 5, y: 6 } */ const parse = (value) => { try { return JSON.parse(value); } catch (e) { return value; } }; /** * @function pluralize * @name pluralize * @description Convert a given string value to its plural form * @param {string} value subject value * @returns {string} plural form of provided string * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.24.0 * @version 0.1.0 * @static * @public * @example * * pluralize('person'); * // => people * * pluralize('Hat'); * // => Hats */ const pluralize = (value) => { let plural = copyOf(value); plural = inflection.pluralize(plural); return plural; }; /** * @function singularize * @name singularize * @description Convert a given string value to its singular form * @param {string} value subject value * @returns {string} singular form of provided string * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.24.0 * @version 0.1.0 * @static * @public * @example * * singularize('people'); * // => person * * singularize('Hats'); * // => Hat */ const singularize = (value) => { let singular = copyOf(value); singular = inflection.singularize(singular); return singular; }; /** * @function autoParse * @name autoParse * @description Safely auto parse a given value to js object * @param {*} value subject to parse * @param {...string} [fields] subject fields to apply auto parse * @returns {*} valid js object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.24.0 * @version 0.1.0 * @static * @public * @example * * autoParse('5'); * // => 5 * * autoParse('{"x":5,"y":6}'); * // => { x: 5, y: 6 } * * autoParse({ a: '5', b: '6' }, 'a')) * // => { a: 5, b: '6' } */ const autoParse = (value, ...fields) => { const copyOfValue = copyOf(value); // handle plain object if (isPlainObject(copyOfValue)) { let parsed = pick(copyOfValue, ...fields); parsed = isEmpty(parsed) ? copyOfValue : parsed; parsed = parseValue(parsed); return merge(copyOfValue, parsed); } // handle others return parseValue(copyOfValue); }; /** * @function flat * @name flat * @description Flatten a nested object * @param {object} value valid object to flatten * @returns {object} flatten object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.27.0 * @version 0.1.0 * @static * @public * @example * * const value = { a: { b: { c: 2 } } }; * flat(value); * // => { 'a.b.c': 2 } */ const flat = (value) => { let flattened = copyOf(value); flattened = flatten(flattened); return flattened; }; /** * @function unflat * @name unflat * @description Unflatten object to nested object * @param {object} value valid object to un flatten * @returns {object} nested object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.27.0 * @version 0.1.0 * @static * @public * @example * * const value = { 'a.b.c': 2 }; * unflat(value); * // => { a: { b: { c: 2 } } }; */ const unflat = (value) => { let unflatted = copyOf(value); unflatted = unflatten(unflatted); return unflatted; }; /** * @function join * @name join * @description Converts array values into a string separated by separator * @param {string[]} values list to convert to string * @param {string} [separator=', '] valid separator * @param {string} [property] property to pick when value is object * @returns {string} joined values * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.29.0 * @version 0.1.0 * @static * @public * @example * * const join = join('a'); * // => 'a' * * const join = join(['a', 'b']); * // => 'a, b, c' * * const join = join([{ a: 'c' }, 'b'], ', ', 'c'); * // => 'c, b' */ const join = (values = [], separator = ', ', property = '') => { // TODO: prefix(support number)?, suffix(support new line)? // copy values const copies = flattenDeep([].concat(values)); // collect parts const parts = map(copies, (copy) => { if (isPlainObject(copy)) { return get(copy, property); } return copy; }); // joined parts const joined = join$1(parts, separator); // return joined values return joined; }; /** * @function transform * @name transform * @description Preprocess given values according to provided transformers * @param {*} vals value to be convert * @param {Function} [transformers] iteratee function which receive result * `value` to be transformed * @returns {*} resulted value * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.43.0 * @version 0.1.0 * @static * @public * @example * * const transform = transform(['a']); * // => ['a'] * * const transform = transform([1, '2'], _.toNumber); * // => [1, 2] */ const transform = (vals, ...transformers) => { // ensure compact values const values = compact([].concat(vals)); // prepare transformers const defaultTransformer = (value) => value; const preprocessors = map( compact([defaultTransformer].concat(...transformers)), (transformer) => { return isFunction(transformer) ? transformer : defaultTransformer; } ); // transform values let transformed = map(values, (value) => { let data; forEach(preprocessors, (transformer) => { data = transformer(value); }); return data; }); // return transformed data transformed = compact(transformed); transformed = size(transformed) === 1 ? first(transformed) : transformed; return transformed; }; /** * @function arrayToObject * @name arrayToObject * @description Converts array values into an object * @param {string[]} array array to convert to object * @param {Function} [transformer] iteratee function which receive result * `object` and current `key` to be transformed * @returns {object} resulted object or empty * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.33.0 * @version 0.1.0 * @static * @public * @example * * const arrayToObject = arrayToObject(['a']); * // => { a: 'a' } * * const arrayToObject = arrayToObject(['a', 'b']); * // => { a: 'a', b: 'b' } */ const arrayToObject = (array, transformer) => { // ensure compact keys const keys = compact([].concat(array)); // prepare transformer const defaultTransformer = (object, value) => value; const valueFor = isFunction(transformer) ? transformer : defaultTransformer; // transform array to object const object = {}; forEach(keys, (key) => { object[key] = valueFor(object, key); }); // return object return object; }; /** * @function parseMs * @name parseMs * @description Safely parse a given millisecond absolute value into js object * @param {number} ms valid millisecond value * @returns {object} valid js object * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.34.0 * @version 0.1.0 * @static * @public * @example * * parseMs(1337000001); * // => { * days: 15, * hours: 11, * minutes: 23, * seconds: 20, * milliseconds: 1, * microseconds: 0, * nanoseconds: 0, * } */ const parseMs = (ms) => { // ensure absolute value const value = Math.abs(ms); // parse milliseconds // credits: https://github.com/sindresorhus/parse-ms/blob/main/index.js#L6 const parsed = { days: Math.trunc(value / 86400000), hours: Math.trunc(value / 3600000) % 24, minutes: Math.trunc(value / 60000) % 60, seconds: Math.trunc(value / 1000) % 60, milliseconds: Math.trunc(value) % 1000, microseconds: Math.trunc(value * 1000) % 1000, nanoseconds: Math.trunc(value * 1e6) % 1000, }; // return parsed return parsed; }; /** * @function wrapCallback * @name wrapCallback * @description Wrap callback with default args * @param {Function} cb valid function to wrap * @param {...object} [defaultArgs] default arguments to wrapped function * @returns {Function} wrapped function. * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.35.0 * @version 0.1.0 * @static * @public * @example * * wrapCallback(cb, defaults); * // => fn */ const wrapCallback = (cb, ...defaultArgs) => (...replyArgs) => { // prepare replies const args = compact([...replyArgs, ...defaultArgs]); const error = find(args, (arg) => isError(arg)); const replies = filter(args, (arg) => !isError(arg)); // reply if (isFunction(cb)) { return cb(error, ...replies); } // noop return noop(error, ...replies); }; // TODO: promiseOrCallback /** * @function classify * @name classify * @description Convert a given string value to its class name form * @param {string} value subject value * @returns {string} plural form of provided string * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.37.0 * @version 0.1.0 * @static * @public * @example * * classify('Health Center'); * // => HealthCenter */ const classify = (value) => { let className = snakeCase(copyOf(value)); className = inflection.classify(className); return className; }; /** * @function tryCatch * @name tryCatch * @description Attempts to invoke `func`, returning either the result or * `defaultValue` on error * @param {Function} func The function to attempt. * @param {*} defaultValue value to return on error * @returns {*} `func` result or `defaultValue`. * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.38.0 * @version 0.1.0 * @static * @public * @example * * tryCatch(() => 1, 0); * //=> 1 * * tryCatch(() => { throw new Error('Failed'); }, {}); * // => {} */ const tryCatch = (func, defaultValue) => { try { return func(); } catch (e) { return defaultValue; } }; export { RESOURCE_ACTIONS, abbreviate, areNotEmpty, arrayToObject, assign, autoParse, bagify, classify, compact, copyOf, firstValue, flat, formatDate, has, hasAll, hasAny, hashOf, idOf, isNotValue, isValue, join, mapErrorToObject, mapToLower, mapToUpper, mergeObjects, normalizeError, osInfo, parse, parseDate, parseMs, parseTemplate, permissionsFor, pkg, pluralize, processInfo, randomColor, safeMergeObjects, scopesFor, singularize, sortedUniq, stringify, stripHtmlTags, transform, tryCatch, unflat, uniq, variableNameFor, wrapCallback };