x-utils-es
Version:
helper tools for javascript validation
1,670 lines (1,510 loc) • 98.7 kB
JavaScript
/// <reference path="./x-utils.es.d.ts" />
/**
* @namespace xutils
* @module x-utils
* @license MIT
* {@link https://eaglex.net Eaglex}
* @description Simple javascript, lodash alternative library, for support contact me at eaglex.net
* @author Developed by Anon
* @version ^2.x.x
*/
/* eslint-disable no-proto */
// eslint-disable-next-line semi
/* istanbul ignore next */
const isWindow = () => {
try {
if ((process.env || {}).NODE_ENV === 'test') return false
/* istanbul ignore next */
if (window) return true
} catch (err) {
return false
}
}
/**
* @typedef {'log' | 'warn' | 'onerror' | 'error' | 'alert'| 'attention' | 'debug' | 'stack' | 'errorTrace'} logType
*/
/**
*
* If you used logging in your application from the moment this method was called all logging will be disabled
* - it affects: log, warn,error, onerror, errorTrace, stack, attention, alert, debug
* @returns {true|false}
*/
const disableLogging = () => {
try {
/* istanbul ignore next */
if (isWindow()) {
// on browser
// @ts-ignore
if (window.xUtilsConfig) {
// @ts-ignore
window.xUtilsConfig.logging = 'off'
} else {
// @ts-ignore
window.xUtilsConfig = {
logging: 'off'
}
}
return true
}
} catch (err) {
//
}
try {
// in node
// @ts-ignore
if (global.xUtilsConfig) {
// @ts-ignore
global.xUtilsConfig.logging = 'off'
} else {
// @ts-ignore
global.xUtilsConfig = {
logging: 'off'
}
}
return true
} catch (err) {
/* istanbul ignore next */
return false
}
}
/**
* Change state of xutils loggers when calling at top of hoist level.
* - affects: log, warn,error, onerror, errorTrace, stack, attention, alert, debug
* @returns {true|false}
*
* @example
* resetLogging() // will reset any previously set loggerSetting(...) options
*/
const resetLogging = () => {
try {
/* istanbul ignore next */
if (isWindow()) {
// @ts-ignore
if (window.xUtilsConfig) {
// @ts-ignore
window.xUtilsConfig.logging = 'on'
} else {
// @ts-ignore
window.xUtilsConfig = {
logging: 'on'
}
}
return true
}
} catch (err) {
//
}
try {
// @ts-ignore
if (global.xUtilsConfig) {
// @ts-ignore
global.xUtilsConfig.logging = 'on'
} else {
// @ts-ignore
global.xUtilsConfig = {
logging: 'on'
}
}
return true
} catch (err) {
/* istanbul ignore next */
return false
}
}
/**
* Allow enabling and disabling of loggers: `log/warn/error/onerror/attention/debug/alert`
* @param {logType} logType logger name
* @param {string} logMode off/on
* @returns {true|false}
*
* @example
* loggerSetting('log','off') // future calls to log() will be disabled
* // this applies to all logger methods:
*/
const loggerSetting = (logType = 'log', logMode = 'off') => {
let availTypes = ['log', 'warn', 'onerror', 'error', 'alert', 'attention', 'debug', 'stack', 'errorTrace']
let availModes = ['on', 'off']
if (!availTypes.includes(logType) || !logType) return false
if (!availModes.includes(logMode) || !logMode) return false
if (logType === 'onerror') logType = 'error'
try {
/* istanbul ignore next */
if (isWindow()) {
// on browser
// @ts-ignore
if (window.xUtilsConfig) {
// @ts-ignore
window.xUtilsConfig[logType] = logMode
} else {
// @ts-ignore
window.xUtilsConfig = {
[logType]: logMode
}
}
return true
}
} catch (err) {
//
}
try {
// in node
// @ts-ignore
if (global.xUtilsConfig) {
// @ts-ignore
global.xUtilsConfig[logType] = logMode
} else {
// @ts-ignore
global.xUtilsConfig = {
[logType]: logMode
}
}
return true
} catch (err) {
/* istanbul ignore next */
return false
}
}
/**
*
* Internal method
* check if any log,warn,error,onerror are currently disabled
* @param {logType} logType
* @returns {'on'|'off'}
*/
const checkLoggerSetting = (logType = undefined) => {
try {
/* istanbul ignore next */
if (isWindow()) {
// @ts-ignore
if (!window.xUtilsConfig) window.xUtilsConfig = {}
// on browser
// @ts-ignore
if (window.xUtilsConfig) {
// @ts-ignore
if (window.xUtilsConfig.logging === 'off') return 'off'
// @ts-ignore
else return (window.xUtilsConfig[logType] ? window.xUtilsConfig[logType] : 'on').toString()
} else {
return 'on'
}
}
} catch (err) {
//
}
try {
// @ts-ignore
if (!global.xUtilsConfig) global.xUtilsConfig = {}
// in node
// @ts-ignore
if (global.xUtilsConfig) {
// @ts-ignore
if (global.xUtilsConfig.logging === 'off') return 'off'
// @ts-ignore
else return (global.xUtilsConfig[logType] ? global.xUtilsConfig[logType] : 'on').toString()
} else {
return 'on'
}
} catch (err) {
//
}
return 'on'
}
/**
* When xUtilsConfig wasn't set, then we are on, else if ..xUtilsConfig==='off', do not print logs
* @effects `log, warn,error, onerror, errorTrace, stack, debug, alert, attention`
* @returns {true|false}
*/
const loggingON = () => {
try {
/* istanbul ignore next */
// @ts-ignore
if (isWindow()) return (window.xUtilsConfig || {}).logging === 'on' || (window.xUtilsConfig || {}).logging === undefined
} catch (err) {
//
}
try {
// @ts-ignore
return (global.xUtilsConfig || {}).logging === 'on' || (global.xUtilsConfig || {}).logging === undefined
} catch (err) {
//
}
return true
}
/**
* @ignore
*/
const callFN = (cb = undefined) => {
if (typeof cb !== 'function') return false
try {
let d = cb()
return d === true || d > 0
} catch (err) {
return false
}
}
/**
* @ignore
*/
/* istanbul ignore next */
const logConstract = function (type = '', args) {
if (!args.length) args[0] = ''
let allData = args.filter(n => typeof n === 'string' || n === undefined).length === 0
let format = allData ? '\%o' : ''
if (type === 'log') args = [].concat(`\x1b[90m[log]\x1b[0m\x1b[2m${format} `, args, '\x1b[0m')
if (type === 'debug') args = [].concat(`\x1b[90m[debug]\x1b[0m\x1b[32m${format} `, args, '\x1b[0m')
if (type === 'warn') args = [].concat(`\x1b[90m[warning]\x1b[0m\x1b[1m${format} `, args, '\x1b[0m')
if (type === 'alert') args = [].concat(`\x1b[90m[alert]\x1b[0m\x1b[33m${format} `, args, '\x1b[0m')
if (type === 'attention') args = [].concat(`\x1b[90m[attention]\x1b[0m\x1b[36m${format} `, args, '\x1b[0m')
console.log.apply(null, args)
}
/**
* Extends console.log with [log] prefix
* @borrows console.log
* @param {...any} args
*/
const log = function (...args) {
if (!loggingON()) return
if (checkLoggerSetting('log') === 'off') return
return logConstract('log', args)
}
/**
*
* Extends console.log with [debug] prefix
* - produces green color output
* @borrows console.log
* @param {...any} args
*/
const debug = function (...args) {
if (!loggingON()) return
if (checkLoggerSetting('debug') === 'off') return
return logConstract('debug', args)
}
/**
* Extends console.log with [warn] prefix
* - produces bright color output
* @param {...any} args
*/
const warn = function (...args) {
if (!loggingON()) return
if (checkLoggerSetting('warn') === 'off') return
return logConstract('warn', args)
}
/**
* Extends console.log with [alert] prefix
* - produces yellow color output
* - this method does not work on window object _( for obvious reasons! )_
* @param {...any} args
*/
const alert = function (...args) {
if (isWindow()) return
if (!loggingON()) return
if (checkLoggerSetting('alert') === 'off') return
return logConstract('alert', args)
}
/**
* Extends console.log with [attention] prefix
* - produces blue color output
* @param {...any} args
*/
const attention = function (...args) {
if (!loggingON()) return
if (checkLoggerSetting('attention') === 'off') return
return logConstract('attention', args)
}
/**
* Extends console.error with [error] prefix
* - produces red color output
* @param {...any} args
*/
const onerror = function (...args) {
if (!loggingON()) return
if (checkLoggerSetting('error') === 'off' || checkLoggerSetting('onerror') === 'off') return
if (!args.length) args[0] = ''
let allData = args.filter(n => typeof n === 'string' || n === undefined).length === 0
let format = allData ? '\%o' : ''
try {
/* istanbul ignore next */
if (isWindow()) {
args = [].concat(`\x1b[31m[error]\x1b[0m\x1b[31m${format} `, args, '\x1b[0m')
console.error.apply(null, args)
return
}
} catch (err) {
// using node
}
args = [].concat(`\x1b[41m[error]\x1b[0m\x1b[31m${format} `, args, '\x1b[0m')
console.error.apply(null, args)
}
/**
* For stack tracing
* - produces/prefixed [STACK TRACE]: ...
* @param {any} data
* @param {boolean} asArray if set, will output stack trace as array, otherwise a string
*/
const stack = (data, asArray = false) => {
if (!loggingON()) return
if (checkLoggerSetting('stack') === 'off') return
let stackList = new Error(JSON.stringify(data)).stack.split('(')
stackList.splice(1, 1)
let stackHead = stackList[0].split(/\n/)[0].replace('Error', '[STACK TRACE]')
stackList.splice(0, 1)
stackList.unshift(stackHead)
if (asArray) console.log(stackList)
else console.log.apply(null, stackList)
return undefined
}
/**
* Extended console.error, stack trace
* - produces/prefixed [ERROR]: ...
* @param {any} data optional
* @param {boolean} asArray if set, will output stack trace as array, otherwise a string
*
* @example
* errorTrace('error data', true) // returns [[ERROR],... including full stack trace
*/
const errorTrace = (data, asArray = false) => {
if (!loggingON()) return
if (checkLoggerSetting('errorTrace') === 'off') return
let stackList = new Error(JSON.stringify(data)).stack.split('(')
stackList.splice(1, 1)
let errHead = stackList[0].split(/\n/)[0].replace('Error', '[ERROR]')
stackList.splice(0, 1)
stackList.unshift(errHead)
if (asArray) console.error(stackList)
else console.error.apply(null, stackList)
return undefined
}
/**
* @alias onerror
*
*/
const error = onerror
/**
* Check if item is a function
* @param {any} el
* @returns {true|false}
*
* @example
* isFunction(()=>{}) // true
* isFunction(Function) // true
*/
const isFunction = (el = undefined) => typeof el === 'function'
/**
* Test provided item is BigInt
* @param {any} n
* @returns {true|false}
*
* @example
* isBigInt( BigInt(Number.MAX_SAFE_INTEGER) ) // true
* isBigInt( 1n ) // true
* isBigInt( (2n ** 54n) ) // true
*
*/
const isBigInt = (n) => typeof (n) === 'bigint'
/**
* loopCB
* @ignore
* @callback loopCB
* @param {string} param string
*/
/**
* @description Looping each item inside of callback
* - Returned cb is pushed to array
* - break loop when returning `{break:true}` inside callback
* @param {number} size
* @param {loopCB} cb callback issued at end of each loop que
* @returns {array} whatever was returned inside the loop
*
* @example
* loop(5,inx=>10+inx) // [10, 11, 12, 13, 14]
* loop(3,inx=>{
* if(inx===3) return {break:true}
* return {[inx]:inx+1}
* }) // [ { '0': 1 }, { '1': 2 }, { '2': 3 } ]
*/
// @ts-ignore
const loop = function (size = 0, cb = (index = 0) => {}) {
let isFN = typeof cb === 'function'
let isNum = typeof size === 'number'
if (!isFN || !isNum) return []
let d = []
for (let inx = 0; inx < Array(size).length; inx++) {
let r = cb.apply(this, [inx])
// add support for break from the loop inside callback function
try {
if (r && Object.entries(r).length) {
if (r.break) break
}
} catch (err) {
//
}
d.push(r) // always grub any data
}
return d
}
/**
* Evaluate if data is an actual `Date`
* @param {Date} dt
* @param {function|undefined} cbEval (optional) callback operator, continue checking when callback returns !!true
* @returns {true|false}
*
* @example
* validDate(new Date('')) // false
* validDate(new Date()) // true
* validDate( new Date(), ()=>false ) // false callback !!false
*/
const validDate = (dt, cbEval = undefined) => {
if (isFunction(cbEval) && !callFN(cbEval)) return false
try {
// @ts-ignore
if (dt.__proto__ === Date.prototype && (dt).toString() !== 'Invalid Date') return true
else return false
} catch (err) {
return false
}
}
/**
* Check item is an array
* @param {any} arr
* @param {function | undefined} cbEval (optional) callback operator, continue checking when callback returns !!true
* @returns {true|false}
*
* @example
* isArray([]) // true
* isArray({}) // false
* isArray(new Array()) // true
* isArray(new Array(), ()=>[1,2].length===1) // false, because callback return !!false
* isArray({}, ()=>true) // false // not array
*/
const isArray = (arr = undefined, cbEval = undefined) => {
if (isFunction(cbEval) && !callFN(cbEval)) return false
if (isBigInt(arr)) return false
else return !arr ? false : Array.prototype === (arr).__proto__
}
/**
* Test item is an array, and check the size
* @param {array} arr
* @returns {number}
*
* @example
* arraySize([1,2,3]) // 3
* arraySize({a:1}) // 0
*/
const arraySize = (arr = undefined) => {
let inx = 0
if (!isArray(arr)) return inx
inx = arr.length
return inx
}
/**
* Examines element for its `type`, provided `value`, and `primitive value`
* @param {any} el
* @param {boolean} standard `standard==true` > return javascript standard types, `standard==false` > return user friendly definition types:`[date,NaN,promise,array,...typeof]`
* @returns {{type:any,value:number,primitiveValue:object}} `{ "type":date,NaN,promise,instance,prototype,array,...typeof, value: number, primitiveValue }`
*
* @example
* typeCheck({}) // {type:'object', value:0, primitiveValue: Object() }
* typeCheck({a:1,b:2}) // {type:'object', value:2, primitiveValue: Object() }
* typeCheck([2,3],false) // {type:'array', value:2, primitiveValue: Array() }
* typeCheck(Date,false) // {type:'date', value:1, primitiveValue: Date() }
* typeCheck(2) // {type:'number', value:2, primitiveValue: Number() }
* typeCheck(false) // {type:'boolean', value:0, primitiveValue: Boolean() }
* typeCheck(true) // {type:'boolean', value:1, primitiveValue: Boolean() }
* typeCheck(null,false) // {type:'null', value:0, primitiveValue: Object() }
* typeCheck(null) // {type:'object', value:0, primitiveValue: Object() }
* typeCheck(undefined) // {type:'undefined', value:0, primitiveValue: undefined }
* typeCheck(function () { }) // {type:'function', value:1, primitiveValue: Function }
* typeCheck(Promise.resolve(),false) // {type:'promise', value:1, primitiveValue: Function }
* typeCheck(Promise.resolve()) // {type:'object', value:1, primitiveValue: Function }
* typeCheck(BigInt(1)) // { type: 'bigint', value: 1, primitiveValue: 0n }
* typeCheck( new Error()) // { type: 'object', value: 0, primitiveValue: Error() }
*/
const typeCheck = (el, standard = true) => {
const ofType = (type) => {
if (standard) return typeof el
else return type || typeof el
}
const asPrototype = (Type) => {
return Type.prototype === el.prototype
}
try {
/* istanbul ignore next */
if (typeof el === 'symbol') return { "type": ofType(), value: 0, primitiveValue: Symbol('') }
if (el === undefined) return { "type": ofType(), value: 0, primitiveValue: undefined }
if (typeof el === 'boolean') return { "type": ofType(), value: +(el), primitiveValue: Boolean() }
/* istanbul ignore next */
if (typeof el === 'bigint' && typeof Object(el) === 'object') return { "type": ofType(), value: 1, primitiveValue: BigInt('') } // eslint-disable-line no-undef
if (el === null) return { "type": ofType('null'), value: 0, primitiveValue: Object() }
/* istanbul ignore next */
if (el.__proto__ === Date.prototype || asPrototype(Date)) return { "type": ofType('date'), value: 1, primitiveValue: new Date() }
if (String.prototype === (el).__proto__) return { 'type': ofType(), value: el.length, primitiveValue: String() }
if (Array.prototype === (el).__proto__ || asPrototype(Array)) return { "type": ofType('array'), value: (el || []).length, primitiveValue: Array() } // eslint-disable-line no-array-constructor
if (Promise.prototype === (el || '').__proto__ || asPrototype(Promise)) return { type: ofType('promise'), value: 1, primitiveValue: Function }
if (Function.prototype === (el).__proto__ || asPrototype(Function)) return { type: ofType(), value: 1, primitiveValue: Function }
if ((Object.prototype === (el).__proto__) || asPrototype(Object)) return { "type": ofType(), value: Object.keys(el).length, primitiveValue: Object() }
if ((Error.prototype === (el).__proto__) || asPrototype(Error)) return { "type": ofType('error'), value: Object.keys(el).length, primitiveValue: Error() }
/* istanbul ignore next */
if ((el).__proto__ === Number.prototype || asPrototype(Number)) {
if (isNaN(el)) return { "type": ofType('NaN'), value: 0, primitiveValue: Number() }
else return { "type": ofType(), value: el, primitiveValue: Number() }
// Unary plus operator
/* istanbul ignore next */
} else if ((+(el) >= 0) === false) return { 'type': typeof el, value: +(el), primitiveValue: undefined }
else return { 'type': typeof el, value: 0, primitiveValue: undefined }
} catch (err) {
/* istanbul ignore next */
error(err)
// @ts-ignore
return {}
}
}
/**
* Check if item is `lt < 1`, `false`, `null` or `undefined`
* @param {any} el number/boolean
* @returns {true|false}
*
* @example
* isFalse(undefined) // false
* isFalse(5) // false
* isFalse(0) // true
* isFalse(-1) // true
* isFalse(true) // false
* isFalse(false) // true
* isFalse({}) // false
* isFalse( new Boolean(false) ) // true
*
*/
const isFalse = (el) => {
if (el === null) return true
if (typeof el === 'undefined') return true
if (typeof el === 'number' && el < 1) return true
if (typeof el === 'boolean' && el === false) return true
try {
if (el instanceof Boolean) {
return el.valueOf() === false
}
} catch (err) {
//
}
return false
}
/**
* Check if item is `gth > 0`, `true`, basically opposite of `isFalse()`
* @param {any} el number/boolean
* @returns {true|false}
*
* @example
* isTrue(undefined) // false
* isTrue(5) // true
* isTrue(0) // false
* isTrue(-1) // false
* isTrue(true) // true
* isTrue(false) // false
* isTrue([]) // false
* isTrue( new Boolean(true) ) // true
*
*/
const isTrue = (el) => {
if (el === null) return false
if (typeof el === 'undefined') return false
if (typeof el === 'number' && el > 0) return true
if (typeof el === 'boolean' && el === true) return true
try {
if (el instanceof Boolean) {
return el.valueOf() === true
}
} catch (err) {
//
}
return false
}
/**
* Check if item is a boolean
* @param {any} el
* @returns {true|false}
*
* @example
* isBoolean(null) // false
* isBoolean(undefined) // false
* isBoolean(false) // true
* isBoolean(new Boolean(false)) // true
*
*/
const isBoolean = (el) => {
if (el === undefined) return false
if (el === null) return false
if (el === true || el === false) return true
try {
if (el instanceof Boolean) {
return true
}
} catch (err) {
//
}
return false
}
/**
* Check if item is `===null`
* @param {any} el
* @returns {true|false}
*
* @example
* isNull(null) // true
* isNull(undefined) // false
*/
const isNull = (el) => {
if (el === null) return true
else return false
}
/**
* Check if item is `===undefined`
* @param {any} el
* @returns {true|false}
*
* @example
* isUndefined(undefined) // true
* isUndefined(null) // false
*
*/
const isUndefined = (el) => {
if (typeof el === 'undefined') return true
else return false
}
/**
* Check item has some value, set of props, or length
* @param {any} value any
* @borrows typeCheck
* @returns {boolean}
*
* @example
* isEmpty({}) // true
* isEmpty({a:1}) // false
* isEmpty([]) // true
* isEmpty([0]) // false
* isEmpty(1) // false
* isEmpty(false) // true
*/
const isEmpty = (value) => {
if (isError(value)) return false
return !typeCheck(value).value
}
/**
* Get first item from array
* - Allow 1 level [[1,2]]
* @param {array} arr
* @returns {any} first array[] item[0]
*
* @example
* head([[{ value: 1 }, { value: 2 }]]) // { value: 1 }
* head([[ [1], {value:1} ]]) // [1]
* head([1,2]) // 1
*
*/
const head = (arr = []) => {
// @ts-ignore
if (Array.prototype !== (arr || null).__proto__) return undefined
// @ts-ignore
return arr.flat().shift()
}
/**
* Gets last item from array
* @param {array} arr
* @returns {any}
*
* @example
* last([{},{},[1], { value: 1 }]) // { value: 1 }
*/
const last = (arr = []) => {
// @ts-ignore
return (arr && Array.prototype === (arr).__proto__) ? arr[arr.length - 1] : undefined
}
/**
* @ignore
* @callback timerCB
*/
/**
* Timer callback executes on timeout
* @param {timerCB} cb
* @param {number} time
*
* @example
* timer(() => log('timer called'), 2000) // executed after time expired
*
*/
const timer = (cb = () => {}, time = 0) => {
const isFN = typeof cb === 'function'
if (!isFN) return undefined
time = (typeof time === 'number' && time >= 0) ? time : 0 // must provide number
const s = setTimeout(() => {
cb()
clearTimeout(s)
}, time)
}
/**
* @ignore
* @callback intervalCB
*/
/**
* Execute callback every interval, then exit on endTime
* @param {intervalCB} cb
* @param {number} every
* @param {number} endTime
*
* @example
* interval(() => log('interval called'), 100, 300)
*
**/
const interval = (cb = () => {}, every = 0, endTime = 0) => {
const isFN = typeof cb === 'function'
if (!isFN) return null
every = (typeof every === 'number' && every >= 0) ? every : 0 // must provide number
endTime = (typeof endTime === 'number' && endTime >= 0) ? endTime : 0 // must provide number
let counter = 0
const c = setInterval(() => {
cb()
if (endTime <= counter) return clearInterval(c)
counter = counter + every
}, every)
}
/**
* SimpleQ / instanceof Promise & SimpleQ
* - Deferred simplified promise
* - Available methods: `resolve() / reject() / (get) promise / progress( (value:string,time:number)=>self,every?:number,timeout?:number ):self`, _progress() calls with values: `resolved | rejected | in_progress | timeout`, its discarted when fulfilled or timedout_
*
* @borrows Promise
* @returns `{SimpleQ}`
*
* @example
* let defer = sq()
*
* let every = 100 // how often to check
* let timeout = 5000 // exists if not already resolved or rejected
*
* defer.progress((val,time)=>{
* // val //> "resolved" | "rejected" | "in_progress" | "timeout"
* log('[progress]',val,time)
* }, every, timeout)
*
* .then(n=>{
* log('[sq][resolve]',n)
* }).catch(err=>{
* onerror('[sq][reject]',err)
* })
*
* defer.resolve('hello world')
* // defer.reject('kill it')
* // or
* defer.resolve('hello world')
* .then(log)
*
* // or
* defer.then(log)
* .resolve('hello world')
*
* // or
* defer.reject('ups')
* .catch(onerror)
*
* // or either
* await defer // resolves // rejects?
* await defer.promise // resolves // rejects?
*
*
**/
// @ts-ignore
const sq = () => {
/**
* @interface
* @ignore
*/
class SimpleQ extends Promise {
/**
* deferrerCallback
* @ignore
* @callback SimpleQdeferrerCallback
* @param {function} resolve
* @param {function} reject
*
*/
/* istanbul ignore next */
/**
*
* @param {SimpleQdeferrerCallback} deferrerCallback
*/
/** @typedef {"resolved"|"rejected"|"in_progress"|"timeout"} val */
/** @typedef {number} time */
/**
* @typedef {function(val, time):void} callback
*/
// @ts-ignore
constructor(deferrerCallback = (resolve = (data) => { }, reject = (data) => { }) => { }) {
super(deferrerCallback)
/**
* @ignore
* @typedef { {every:number,timeout:number,cb:callback,done:boolean,index:number} } progress_cb
*/
/**
* @ignore
* @type {progress_cb}
*/
this._progress_cb = {
every: 100,
timeout: 1000,
cb: undefined,
done: undefined,
index: 0
}
}
/**
* @name progress
* @method progress
* Returns callback when promise if resolved or rejected
* - On resolved cb() value is `resolved`
* - On rejected cb() value is `rejected`
* - If neither, cb initiated on {every} until {timeout}, or before {done}, with value `in_progress`
* - If promise is never resolved and {timeout} is reached, final callback value is `timeout`
* @memberof SimpleQ
* @param {callback} cb
* @param {number} every
* @param {number} timeout when to stop checking progress (will exit setInterval)
*/
progress(cb, every = 100, timeout = 1000) {
// make sure every is never bigger then timeout
if (every > timeout) {
every = 0
}
let index = 0
this._progress_cb.cb = cb
this._progress_cb.every = every
this._progress_cb.timeout = timeout
this._progress_cb.index = index
// execute either { timeout | in_progress } based on selection
let t = setInterval(() => {
this._progress_cb.index = index
if (this._progress_cb.done) return clearInterval(t)
if (index >= timeout) {
this._progress_cb.cb('timeout', index)
this._progress_cb.done = true
return clearInterval(t)
} else this._progress_cb.cb('in_progress', index)
index = index + every
}, every)
return this
}
/**
*
* Resolve your promise outside of callback
* @param {*} data
* @memberof SimpleQ
*/
resolve(data) {
// @ts-ignore
let res = SimpleQ._resolve
if (res instanceof Function) {
res(data)
if (isFunction(this._progress_cb.cb) && !this._progress_cb.done) {
this._progress_cb.cb('resolved', this._progress_cb.index)
this._progress_cb.done = true
}
// eslint-disable-next-line brace-style
}
/* istanbul ignore next */
else onerror('[SimpleQ][resolve]', 'not callable')
return this
}
/**
* Reject your promise outside of callback
* @memberof SimpleQ
* @param {*} data
*/
reject(data) {
// @ts-ignore
let rej = SimpleQ._reject
if (rej instanceof Function) {
rej(data)
if (isFunction(this._progress_cb.cb) && !this._progress_cb.done) {
this._progress_cb.cb('rejected', this._progress_cb.index)
this._progress_cb.done = true
}
} else {
/* istanbul ignore next */
onerror('[SimpleQ][reject]', 'not callable')
}
return this
}
/**
* - Returns promise, and instanceof Promise
* @memberof Promise
* @readonly
* @memberof SimpleQ
*/
get promise() {
// @ts-ignore
let promise = SimpleQ._promise
return promise instanceof Promise ? promise : undefined
}
}
const deferred = SimpleQ._promise = new SimpleQ((resolve, reject) => {
// @ts-ignore
SimpleQ._resolve = resolve
// @ts-ignore
SimpleQ._reject = reject
})
// for recongnition
// @ts-ignore
deferred.__proto__.entity = 'SimpleQ'
if (deferred instanceof Promise &&
deferred instanceof SimpleQ) {
return deferred
} else {
/* istanbul ignore next */
throw ('sq() not a valid Promise ?')
}
}
/**
* @ignore
* @param {Object} o
* @param {string} o.error
* @param {Promise<any>} o.defer
* @param {string} o.id
*/
// @ts-ignore
// eslint-disable-next-line no-unused-vars
const cancelPromiseCB = ({ error, defer, id }) => {}
/**
* Cancelable synchronous process, determines how long to wait before we exit
* - If the promise never resolves or takes too long, we can cancel when maxWait expires
*
* @param {object} config `{defer,checkEvery,maxWait,cbErr,message,logging,id}`
* @param {Promise<any>} config.defer resolved when process complete or called from callback on timeout
* @param {number} config.checkEvery how frequently to check if promise is resolved
* @param {number} config.maxWait how long to wait before exciting with cbErr
* @param {cancelPromiseCB} config.cbErr called on timeout cbErr(({error,defer,id})) here you can either resolve or reject the pending promise
* @param {string} config.message (optional) defaults: **taken too long to respond**, or provide your own
* @param {boolean} config.logging (optional) will prompt waiting process
* @param {string|number} config.id (optional) added to error callback, and to logging
* @returns {Promise} the promise provided in config.defer, dont need to use it
*
* @example
* let dfr = sq()
* cancelPromise({ defer:dfr, // can use standard Promise, sq(), or node.js q.defer
* checkEvery: 200, // << log process on every
* maxWait: 3000, // expire promise
* message: 'waited too long', // << use this error message
* logging: true, // display process
* id: new Date().getTime(), // custom id to display or on error
* cbErr: function({ error, defer, id }) {
* // update our reject message
* defer.reject(error)
* }
* }) // returns promise
*
* // will catch the timeout rejection
* df2.promise.catch(onerror)
*
* // or would exist waiting process and resolve
* // dfr.resolve('success')
* // dfr.promise.then(log)
*
**/
// @ts-ignore
const cancelPromise = ({ defer = undefined, checkEvery = 500, maxWait = 9500, cbErr = ({ error, defer, id }) => {}, message = 'taken too long to respond', logging = false, id = undefined }) => {
let isFN = (el) => typeof el === 'function'
let validPromise = isPromise(defer) || isQPromise(defer)
if (!validPromise || !isFN(cbErr) || !maxWait) {
onerror('[cancelPromise]', '{defer,maxWait,cbErr} must be provided')
return Promise.reject('{defer,maxWait,cbErr} must be provided')
}
let exit_interval
let every = checkEvery || 500
maxWait = maxWait || 1
let inx = 0
const t = setInterval(() => {
if (exit_interval) {
// if (logging) log('[cancelPromise]', 'cleared')
/* istanbul ignore next */
return clearInterval(t)
}
if (inx > maxWait) {
let args = { error: `${message}, time: ${inx}`, defer, id }
try {
cbErr.apply(args, [args])
// @ts-ignore
defer.reject(`${message}, time: ${inx}`)
} catch (err) {
// ups
/* istanbul ignore next */
onerror('[cancelPromise]', err)
}
return clearInterval(t)
} else {
if (logging) {
if (id) log('-- processing: ', id)
/* istanbul ignore next */
else alert('-- processing ')
}
}
inx = every + inx
}, every)
const deffer = (def) => {
return def.then(n => {
// will exit the interval
exit_interval = true
return n
}, err => {
exit_interval = true
return Promise.reject(err)
})
}
if (isSQ(defer) || (isPromise(defer) && !isQPromise(defer))) return deffer(defer)
// @ts-ignore
if (isQPromise(defer)) return deffer(defer.promise)
/* istanbul ignore next */
else return Promise.reject('[cancelPromise], Supplied {defer} is not a promise')
}
/**
* Convert to string, remove spaces, toLowerCase
* @param {string|number} id
* @returns {string}
*
* @example
* validID('sdfkj 45 AMKD') // sdfkj45amkd
**/
const validID = (id = '') => !(id || '') ? '' : (id || '').toString().toLowerCase().replace(/\s/g, '')
/**
* Check item is a number
* @param {any} n
* @returns {true|false}
*
* @example
* isNumber(-1) // true
* isNumber( new Number(-1) ) // true
* isNumber(NaN) // true
* isNumber(true) // false
* isNumber([]) // false
**/
const isNumber = (n) => {
if (isBigInt(n)) return false
try {
if (n instanceof Number) return true
} catch (err) {
//
}
return n !== undefined && n !== null && n !== '' ? (n).__proto__ === Number.prototype : false
}
/**
* @deprecated in favour of validDate()
* Check item is a date, example: new Date()
* @param {any} d
* @returns {true|false}
**/
/* istanbul ignore next */
const isDate = (d) => {
try {
return (d) instanceof Date
} catch (err) {
return false
}
}
/**
* Test the length of string
* @param {string} str
* @returns {number} length of string
*
* @example
* stringSize('abc') // 3
* stringSize(-1) // 0
* stringSize('-1') // 2
* stringSize(undefined) // 0
* stringSize([123]) // 0
*/
// @ts-ignore
const stringSize = (str = '') => str !== undefined && str !== null ? (str).__proto__ === String.prototype ? str.length : 0 : 0
/**
* There are 2 types of promises available javascript standard Promise and the node.js `q.defer()` promise
* - this method tests for the q.defer node.js promise version
* @param {any} defer q.defer() promise to check against
* @returns {true|false}
*
* @example
* isQPromise(Promise.resolve()) }) // false
* isQPromise( sq() ) // false
* isQPromise( q.defer() ) // true (referring to node.js q )
*
**/
const isQPromise = (defer = undefined) => {
try {
if (
(defer.promise !== undefined &&
typeof defer.resolve === 'function' &&
typeof defer.reject === 'function' &&
typeof defer.fulfill === 'function' &&
typeof defer.notify === 'function'
) === true
) {
return true
}
} catch (err) {
//
}
return false
}
/**
* Test if item is our SimpleQ promise
* @param {any} defer
* @returns {true|false}
*
* @example
* isSQ( sq() ) // true
* isSQ( Promise.resolve() ) // false
* isSQ( q.defer() ) // false
*/
const isSQ = (defer) => {
try {
return defer.entity === 'SimpleQ'
} catch (err) {
return false
}
}
/**
* Check for Promise / q.defer / and xutils promise ( sq() ),
* - test if its a resolvable promise
* @param {any} defer
* @returns {true|false}
*
* @example
* isPromise( function () { } ) // false
* isPromise( Promise.resolve()) ) // true
* isPromise( sq() ) // true
* isPromise( q.defer() ) // true
*
*/
const isPromise = (defer) => {
if (isQPromise(defer)) return true
else {
try {
if (defer instanceof Promise) return true
// if (isSQ(defer)) return true
} catch (err) {
// onerror('err', err)
}
return false
}
}
/**
* Test item is a true object, and not array
* - Should not be a function/primitive, or class (except for instance)
* @param {any} obj
* @param {function|undefined} cbEval (optional) callback operator, continue checking when callback returns !!true
* @returns {true|false}
*
* @example
* isObject({}) // true
* isObject([]) // false
* isObject( (new function(){}) ) // true
* isObject((function () { })) }) // false
* isObject((new class { })) // true
* isObject( (class{}) ) // false
* isObject(new Error()) // true
* isObject(null) // false
* isObject( {}, ()=>false ) // false, due to callback !!false
* isObject( [], ()=>Object.keys({1:1}).length ) // false, not an object
*
*/
const isObject = (obj = undefined, cbEval = undefined) => {
if (isFunction(cbEval) && !callFN(cbEval)) return false
if (isBigInt(obj)) return false
if (isNaN(obj) && typeof obj === 'number') return false
if (typeof obj === 'function') return false
if (!isNaN((+obj)) || obj === undefined) return false
// @ts-ignore
if ((obj).__proto__ === ([]).__proto__) return false // is array
// testing standard Object and Error
const a = (Object.prototype === (obj).__proto__ || Error.prototype === (obj).__proto__)
const ab = a && (obj instanceof Object)
if (ab) return true
if (obj.__proto__ !== undefined) {
try {
return obj instanceof Object
} catch (err) {
/* istanbul ignore next */
return false
}
}
/* istanbul ignore next */
if (obj.prototype) return true
return false
}
/**
* Returns new array of unique values
* @param {array} arr
* @returns {array}
*
* @example
* uniq([1, 1, 3, 'a', 'b', 'a', null, null, true, true])
* // [1,3,'a','b',null,true]
*/
const uniq = (arr = []) => {
let o = []
o = arr.filter((el, i, all) => all.indexOf(el) === i)
return o instanceof Array ? o : []
}
/**
* Randomise items in array
* @param {array} arr
* @returns {array}
*
*/
const shuffle = (arr = []) => {
if (!isArray(arr)) return []
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * i)
const k = arr[i]
arr[i] = arr[j]
arr[j] = k
}
return arr
}
/**
* Select data from array of objects by reference, and go down recursively in order of selectBy `['a.b']` ref
* @param {Array<string>} selectBy list of uniq references, example ['a.b.c.d.e','e.f.g'], each selectBy/item targets nested object props
* @param {Array<any>} data list of objects to target by select ref
* @returns {array} by selected order in same pair index
*
* @example
* // select b from both arrays, return same index order
* selectiveArray(['a.b'], [ { a: { b:'hello' }, b:{c:'hello'} },{ a: { b:'world' },b:{c:'world'} } ])
* //=> [ [ 'hello'], [ 'world'] ]
*
* //select b, and select c from both arrays, return same index order
* selectiveArray(['a.b','b.c'], [ { a: { b:'hello' }, b:{c:'hello'} },{ a: { b:'world' },b:{c:'world'} } ])
* //=> [ ['hello','hello'], ['world','world'] ]
*
* // destructuring example :
* let [b,c]=Array.from( flatten(selectiveArray(['a.b','b.c'], [ { a: { b:'hello' }, b:{c:'world'} }]) ) ).values()
* // b==="hello", c ==="world"
*/
const selectiveArray = (selectBy = [], data = []) => {
if (!isArray(data)) return []
if (!data.length) return []
// NOTE if selectBy is empty or invalid will return same data
if (!isArray(selectBy)) return data
if (!selectBy.length) return data
selectBy = uniq(selectBy)
let nData = []
// go down recursively
let findNest = (s, item, inx = 0) => {
let lastItem = null
let found
if (!s) return undefined
if (!isArray(s)) return undefined
if (!s.length) return undefined
try {
if (item[s[inx]] !== undefined) {
lastItem = item[s[inx]]
found = lastItem
inx = inx + 1
if (s[inx]) return findNest(s, found, inx)
else return found
}
} catch (err) {
/* istanbul ignore next */
console.log(err.toString())
}
return found
}
for (let i = 0; i < data.length; i++) {
let item = data[i]
if (!isObject(item)) {
// each item in an array must be an object to be able to selectBy nested prop
nData.push([item])
continue
}
let found
let collective = [] // insert collective
for (let o = 0; o < selectBy.length; o++) {
let sArr = (selectBy[o] || "").split('.')
try {
found = findNest(sArr, item, 0)
collective.push(found)
} catch (err) {
//
}
}
// if all items are undef and selectBy/size matches collective/size
// if all collective are undef filter them out
// this helps with positioning of uneven results, and 1 side has match and the other does not,
// valid example:[ [ 'abc', undefined ], 'efg', undefined ] << pairs should be consistent, when selectBy has more then 1
if (selectBy.length === collective.length) {
let allUndef = collective.filter(n => n === undefined)
if (allUndef.length === selectBy.length) collective = collective.filter(n => !!n)
}
if (collective.length) {
nData.push([].concat(collective))
} else if (found !== undefined) nData.push(found)
}
return nData
}
/**
* Test item is a class{} constractor, that can be initiated
* @param {any} obj
* @param {*} cbEval (optional) callback operator, continue checking when callback returns !!true
* @returns {true|false}
*
* @example
* isClass(Array) // true
* isClass(Object) //true
* isClass((class {}) ) //true
* // instance of a class
* isClass( (new function() {}()) ) // false
* isClass( new Object() ) // false
*/
const isClass = (obj, cbEval = undefined) => {
if (isFunction(cbEval) && !callFN(cbEval)) return false
if (!obj) return false
if ((obj).prototype !== undefined) return true
return false
}
/**
* Same as isClass() method
* @method hasPrototype(obj)
* @alias isClass
*
*/
const hasPrototype = isClass
/**
* Check if item has access to __proto__
* @param {any} el
* @param {function|undefined} cbEval optional callback, continue checking when callback returns !!true
* @returns {boolean}
*
* @example
* hasProto({}) // true
* hasProto('') // true
* hasProto(-1) // true
* hasProto(false) // true
* hasProto(undefined) // false
* hasProto(null) // false
* hasProto(NaN) // true
* hasProto({}, ()=> Object.keys({}).length ) // false because object has no keys
*/
const hasProto = (el, cbEval = undefined) => {
if (isFunction(cbEval) && !callFN(cbEval)) return false
try {
return el.__proto__ !== undefined
} catch (err) {
return false
}
}
/**
* Check pattern is an expression of RegExp
* @param {RegExp} expression
* @returns {boolean}
*
* @example
* isRegExp('abc') // false
* isRegExp(/abc/) // true
*/
const isRegExp = (expression = (/\\/)) => {
try {
return expression instanceof RegExp
} catch (err) {
/* istanbul ignore next */
return false
}
}
/**
* Testing if item{} is a `new Item{}`, instance of a class
* @param {any} obj
* @param {function|undefined} cbEval (optional) continue checking when callback returns !!true
* @returns {boolean}
*
* @example
* isInstance({}) // false
* isInstance(new function(){}) // true
* isInstance(new class(){} ) // true
* isInstance(function () { }) // false
* isInstance([]) // false
*/
const isInstance = (obj = {}, cbEval = undefined) => {
if (isFunction(cbEval) && !callFN(cbEval)) return false
if (!obj) return false
if (isArray(obj)) return false
if (obj.__proto__ && !isClass(obj)) {
try {
return obj.__proto__ instanceof Object
} catch (err) {
/* istanbul ignore next */
return false
}
}
return false
}
/**
* Check size object, we want to know how many keys are set
* @param {object} obj
* @returns {number} number of keys on the object
*
* @example
* objectSize({ a: 1, b: 2 }) }) // 2
* objectSize([1,2]) // 0
* objectSize( (new function(){this.a=1}()) ) // 1
* objectSize( (new function(){}()) ) // 0
*/
const objectSize = (obj = {}) => {
if (!obj || !isNaN(+(obj))) return 0
if (isInstance(obj)) return Object.keys(obj).length
return ((Object.prototype === (obj).__proto__) || Error.prototype === (obj).__proto__) ? Object.keys(obj).length : 0
}
/**
* Check if any item type is falsy, object, array, class/instance, having no props set
* @param {any} el
* @returns {boolean}
*
* @example
* isFalsy({}) // true
* isFalsy({a:1}) // false
* isFalsy([]) // true
* isFalsy([1]) // false
* isFalsy(true) // false
* isFalsy(false) // true
* isFalsy(0) // true
* isFalsy( (new function(){}()) ) // true
* isFalsy( (new function(){this.a=false}()) ) // false
*/
const isFalsy = (el = undefined) => {
if (el === undefined ||
el === null) return true
if (el === false && typeof el === 'boolean') return true
if (el === true && typeof el === 'boolean') return false
if (typeof el === 'number' && el > 0) return false
if (String.prototype === (el).__proto__) return el.length < 1
if (Array.prototype === (el).__proto__) return (el || []).length === 0
if (Promise.prototype === (el || {}).__proto__) return false
if (typeof el === 'function') return false
if ((Object.prototype === (el).__proto__) || isInstance(el)) return Object.keys(el).length === 0
if ((Error.prototype === (el).__proto__)) return false
if (el !== undefined && (el).__proto__ === Number.prototype) {
if (isNaN(el)) return true
else return el <= 0
}
/* istanbul ignore next */
if ((+(el) > 0) === false) return true
if (el) return false
else return false
}
/**
* Test item is a string type
* @param {any} str
* @param {function|undefined} cbEval (optional) operator, continue checking when callback returns !!true
* @returns {boolean}
*
* @example
* isString('') // true
* isString(new String()) // true
* isString(NaN) // false
* isString(new Date()) // false
* isString('123', ()=>'123'.length>5) // false, callback return !!false
* isString('123', ()=>'123'.length>2) // true
*/
const isString = (str = undefined, cbEva