UNPKG

x-utils-es

Version:

helper tools for javascript validation

1,670 lines (1,510 loc) 98.7 kB
/// <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