UNPKG

@lykmapipo/common

Version:

Helper utilities for day to day development

1,702 lines (1,598 loc) 41.6 kB
import { resolve as resolvePath } from 'path'; import { readFileSync } from 'fs'; import { arch, cpus, endianness, freemem, homedir, hostname, loadavg, networkInterfaces, platform, release, tmpdir, totalmem, type as osType, uptime, } from 'os'; import { assign as assignify, camelCase, cloneDeep, compact as compactify, every, flattenDeep, get, find, filter, first, forEach, isArray, isBoolean, isDate, isFunction, isNumber, isNaN as isNotANumber, isEmpty, isError, isString, includes, isPlainObject, join as joinify, map, merge as mergify, noop, pick, omit, omitBy, orderBy, reduce, size, some, snakeCase, startCase, toLower, toString, toUpper, trim, uniq as uniqify, words as wordify, } from 'lodash'; import { getType as mimeTypeOf, getExtension as mimeExtensionOf } from 'mime'; import { flatten, unflatten } from 'flat'; import { 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 { STATUS_CODES }; export { v1 as uuidv1, v3 as uuidv3, v4 as uuidv4, v5 as uuidv5 } from 'uuid'; export { isNode, isBrowser, 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 */ export 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 */ export const isNotValue = (value) => { // handle NaN if (isNotANumber(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 */ export 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' */ export 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 } */ export 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'] */ export 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'] */ export 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 */ export 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 } */ export const compact = (value) => { // copy value const copyOfValue = copyOf(value); // compact array if (isArray(copyOfValue)) { return compactify(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 } */ export const uniq = (value) => { // uniq if (value) { let copyOfValue = compact(value); copyOfValue = isArray(value) ? uniqify(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 } */ export 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 } */ export const assign = (object = {}, ...objects) => { // ensure source objects let sources = compactify([...objects]); sources = map(sources, compact); // assign objects assignify(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 } */ export const mergeObjects = (...objects) => { // ensure source objects let sources = compactify([...objects]); sources = map(sources, compact); // merged objects const merged = mergify({}, ...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 } */ export const safeMergeObjects = (...objects) => { // ensure source objects const sources = compactify([...objects]); // merged objects const merged = mergify({}, ...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: ...} */ export const pkg = (path, ...field) => { // try read from path or process cwd const read = () => { try { const filePath = resolvePath(path, 'package.json'); const json = JSON.parse(readFileSync(filePath, 'utf8')); return json; } catch (e) { const filePath = resolvePath(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'] */ export 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: ...}, ....]; */ export 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 */ export const abbreviate = (...words) => { // ensure words let phrases = flattenDeep([...words]); phrases = wordify(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 */ export 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 */ export 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 */ export 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 */ export 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 */ export 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; */ export const normalizeError = (error, options = {}) => { // ensure options let { name = 'Error', code = 500, status, message } = mergeObjects(options); // prepare error properties code = error.code || error.statusCode || code; status = error.status || error.statusCode || status || code; name = error.name || name; message = error.message || message || STATUS_CODES[code]; // assign values assign(error, { code, status, name, message }); // 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'}, ... } */ export 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', ... } */ export const mapErrorToObject = (error, options = {}) => { // ensure options const { name = 'Error', code = 500, stack = false, status, message, 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 || STATUS_CODES[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', ... } */ export 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: osType(), 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, ... } */ export 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 */ export 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 */ export 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 ... */ export const parseDate = (date, format = 'YYYY-MM-DD') => { const parsed = moment.utc(date, format).toDate(); return parsed; }; /** * @function mimeTypeOf * @name mimeTypeOf * @description Lookup a mime type based on file extension * @param {string} extension valid file extension or name * @returns {string} valid mime type * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.20.0 * @version 0.1.0 * @static * @public * @example * * const mime = mimeTypeOf('txt'); * // => 'text/plain' */ export { mimeTypeOf }; /** * @function mimeExtensionOf * @name mimeExtensionOf * @description Obtain file extension associated with a mime type * @param {string} mimeType valid mime type * @returns {string} valid file extension * @author lally elias <lallyelias87@gmail.com> * @license MIT * @since 0.20.0 * @version 0.1.0 * @static * @public * @example * * const extension = mimeExtensionOf('text/plain'); * // => txt */ export { mimeExtensionOf }; /** * @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' */ export 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' */ export 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' */ export 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}' */ export 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 } */ export 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 */ export 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 */ export 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' } */ export 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 mergify(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 } */ export 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 } } }; */ export 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' */ export 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 = joinify(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] */ export 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' } */ export 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, * } */ export 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 */ export 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 */ export 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'); }, {}); * // => {} */ export const tryCatch = (func, defaultValue) => { try { return func(); } catch (e) { return defaultValue; } };