import { head, tail, findIndex, startsWith, dropRight, flow, takeRight, trim, replace } from 'lodash'; import { some, reduce, isEmpty, isArray, join, isString, isInteger, isDate, toNumber, isUndefined, isNaN, isNull, constant, split, includes, filter } from 'lodash/fp'; import { events, eventsList } from './events'; import { apiWrapper } from './apiWrapper'; import { getLock, NO_LOCK, isNolock } from './lock'; import crypto from "./nodeCrypto"; // this is the combinator function export const $$ = (...funcs) => arg => flow(funcs)(arg); // this is the pipe function export const $ = (arg, funcs) => $$(...funcs)(arg); export const keySep = '/'; const trimKeySep = str => trim(str, keySep); const splitByKeySep = str => split(keySep)(str); export const safeKey = key => replace(`${keySep}${trimKeySep(key)}`, `${keySep}${keySep}`, keySep); export const joinKey = (...strs) => { const paramsOrArray = strs.length === 1 & isArray(strs[0]) ? strs[0] : strs; return $(paramsOrArray, [ filter(s => !isUndefined(s) && !isNull(s) && s.toString().length > 0), join(keySep), safeKey ]); }; export const splitKey = $$(trimKeySep, splitByKeySep); export const getDirFomKey = $$(splitKey, dropRight, p => joinKey(...p)); export const getFileFromKey = $$(splitKey, takeRight, head); export const configFolder = `${keySep}.config`; export const fieldDefinitions = joinKey(configFolder, 'fields.json'); export const templateDefinitions = joinKey(configFolder, 'templates.json'); export const appDefinitionFile = joinKey(configFolder, 'appDefinition.json'); export const dirIndex = folderPath => joinKey(configFolder, 'dir', ...splitKey(folderPath), 'dir.idx'); export const getIndexKeyFromFileKey = $$(getDirFomKey, dirIndex); export const ifExists = (val, exists, notExists) => (isUndefined(val) ? isUndefined(notExists) ? (() => { })() : notExists() : exists()); export const getOrDefault = (val, defaultVal) => ifExists(val, () => val, () => defaultVal); export const not = func => val => !func(val); export const isDefined = not(isUndefined); export const isNonNull = not(isNull); export const isNotNaN = not(isNaN); export const allTrue = (...funcArgs) => val => reduce( (result, conditionFunc) => (isNull(result) || result == true) && conditionFunc(val), null)(funcArgs); export const anyTrue = (...funcArgs) => val => reduce( (result, conditionFunc) => result == true || conditionFunc(val), null)(funcArgs); export const insensitiveEquals = (str1, str2) => str1.trim().toLowerCase() === str2.trim().toLowerCase(); export const isSomething = allTrue(isDefined, isNonNull, isNotNaN); export const isNothing = not(isSomething); export const isNothingOrEmpty = v => isNothing(v) || isEmpty(v); export const somethingOrGetDefault = getDefaultFunc => val => (isSomething(val) ? val : getDefaultFunc()); export const somethingOrDefault = (val, defaultVal) => somethingOrGetDefault(constant(defaultVal))(val); export const mapIfSomethingOrDefault = (mapFunc, defaultVal) => val => (isSomething(val) ? mapFunc(val) : defaultVal); export const mapIfSomethingOrBlank = mapFunc => mapIfSomethingOrDefault(mapFunc, ''); export const none = predicate => collection => !some(predicate)(collection); export const all = predicate => collection => none(v => !predicate(v))(collection); export const isNotEmpty = ob => !isEmpty(ob); export const isAsync = fn => fn.constructor.name === 'AsyncFunction'; export const isNonEmptyArray = allTrue(isArray, isNotEmpty); export const isNonEmptyString = allTrue(isString, isNotEmpty); export const tryOr = failFunc => (func, ...args) => { try { return func.apply(null, ...args); } catch (_) { return failFunc(); } }; export const tryAwaitOr = failFunc => async (func, ...args) => { try { return await func.apply(null, ...args); } catch (_) { return await failFunc(); } }; export const defineError = (func, errorPrefix) => { try { return func(); } catch (err) { err.message = `${errorPrefix} : ${err.message}`; throw err; } }; export const tryOrIgnore = tryOr(() => { }); export const tryAwaitOrIgnore = tryAwaitOr(async () => { }); export const causesException = (func) => { try { func(); return false; } catch (e) { return true; } }; export const executesWithoutException = func => !causesException(func); export const handleErrorWith = returnValInError => tryOr(constant(returnValInError)); export const handleErrorWithUndefined = handleErrorWith(undefined); export const switchCase = (...cases) => (value) => { const nextCase = () => head(cases)[0](value); const nextResult = () => head(cases)[1](value); if (isEmpty(cases)) return; // undefined if (nextCase() === true) return nextResult(); return switchCase(...tail(cases))(value); }; export const isValue = val1 => val2 => (val1 === val2); export const isOneOf = (...vals) => val => includes(val)(vals); export const defaultCase = constant(true); export const memberMatches = (member, match) => obj => match(obj[member]); export const StartsWith = searchFor => searchIn => startsWith(searchIn, searchFor); export const contains = val => array => (findIndex(array, v => v === val) > -1); export const getHashCode = (s) => { let hash = 0; let i; let char; let l; if (s.length == 0) return hash; for (i = 0, l = s.length; i < l; i++) { char = s.charCodeAt(i); hash = ((hash << 5) - hash) + char; hash |= 0; // Convert to 32bit integer } // converting to string, but dont want a "-" prefixed if (hash < 0) { return `n${(hash * -1).toString()}`; } return hash.toString(); }; // thanks to https://blog.grossman.io/how-to-write-async-await-without-try-catch-blocks-in-javascript/ export const awEx = async (promise) => { try { const result = await promise; return [undefined, result]; } catch (error) { return [error, undefined]; } }; export const isSafeInteger = n => isInteger(n) && n <= Number.MAX_SAFE_INTEGER && n >= 0 - Number.MAX_SAFE_INTEGER; export const toDateOrNull = s => (isNull(s) ? null : isDate(s) ? s : new Date(s)); export const toBoolOrNull = s => (isNull(s) ? null : s === 'true' || s === true); export const toNumberOrNull = s => (isNull(s) ? null : toNumber(s)); export const isArrayOfString = opts => isArray(opts) && all(isString)(opts); export const pushAll = (target, items) => { for(let i of items) target.push(i); } export const pause = async duration => new Promise(res => setTimeout(res, duration)); export const retry = async (fn, retries, delay, ...args) => { try { return await fn(...args); } catch (err) { if (retries > 1) { return await pause(delay).then(async () => await retry(fn, (retries - 1), delay, ...args)); } throw err; } }; export { events } from './events'; export { apiWrapper, apiWrapperSync } from './apiWrapper'; export { getLock, NO_LOCK, releaseLock, extendLock, isNolock, } from './lock'; export { crypto }; export default { ifExists, getOrDefault, isDefined, isNonNull, isNotNaN, allTrue, isSomething, mapIfSomethingOrDefault, mapIfSomethingOrBlank, configFolder, fieldDefinitions, isNothing, not, switchCase, defaultCase, StartsWith, contains, templateDefinitions, handleErrorWith, handleErrorWithUndefined, tryOr, tryOrIgnore, tryAwaitOr, tryAwaitOrIgnore, dirIndex, keySep, $, $$, getDirFomKey, getFileFromKey, splitKey, somethingOrDefault, getIndexKeyFromFileKey, joinKey, somethingOrGetDefault, appDefinitionFile, isValue, all, isOneOf, memberMatches, defineError, anyTrue, isNonEmptyArray, causesException, executesWithoutException, none, getHashCode, awEx, apiWrapper, events, eventsList, isNothingOrEmpty, isSafeInteger, toNumber, toDate: toDateOrNull, toBool: toBoolOrNull, isArrayOfString, getLock, NO_LOCK, isNolock, insensitiveEquals, pause, retry, pushAll };