Last active
June 10, 2021 14:44
-
-
Save zgover/4b83a8e95360486c4cf1e8a75c5e543b to your computer and use it in GitHub Desktop.
Quick TypeScript Guard and Helper Utility Functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Is literal type 'boolean' | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is boolean} | |
*/ | |
export function _isBool(val: unknown): val is boolean { | |
return typeof val === 'boolean' | |
} | |
/** | |
* Is literal type 'bigint' | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is bigint} | |
*/ | |
export function _isBig(val: unknown): val is bigint { | |
return typeof val === 'bigint' | |
} | |
/** | |
* Is literal type 'number' | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is number} | |
*/ | |
export function _isNumT(val: unknown): val is number { | |
return typeof val === 'number' | |
} | |
/** | |
* Is value a number, allows even if string (e.g. "6" vs 6) | |
* | |
* @export | |
* @param {*} val | |
* @param {boolean} [noStr] | |
* @returns {val is number} | |
*/ | |
export function _isNum(val: unknown, noStr?: boolean): val is number { | |
return noStr && _isStr(val) ? false : !isNaN(Number(val)) | |
} | |
/** | |
* Is literal type symbol | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is symbol} | |
*/ | |
export function _isSym(val: unknown): val is symbol { | |
return typeof val === 'symbol' | |
} | |
/** | |
* Is literal type function | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is Function} | |
*/ | |
export function _isFn(val: unknown): val is Func { | |
return typeof val === 'function' | |
} | |
export interface Func { | |
(...args: unknown[]): unknown | |
} | |
/** | |
* Shortcut for Array.isArray | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is any[]} | |
*/ | |
export function _isArr(val: unknown): val is unknown[] { | |
return Array.isArray(val) | |
} | |
/** | |
* Is an empty array | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is []} | |
*/ | |
export function _isArrEmpty(val: unknown): val is [] { | |
return _isArr(val) && !val.length | |
} | |
/** | |
* Is literal type object, this could be any Object type | |
* such as a function, class, null, {}, array etc | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is object} | |
*/ | |
export function _isObjT(val: unknown): val is Record<string | number, unknown> { | |
return typeof val === 'object' | |
} | |
/** | |
* Is actually a dictionary object (e.g. key:value), but not | |
* an array or null | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is Object} | |
*/ | |
export function _isObj(val: unknown): val is Record<string, unknown> { | |
return !_isNull(val) && _isObjT(val) && !_isArr(val) | |
} | |
/** | |
* Is literal type null... null is actually 'object' type literal | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is null} | |
*/ | |
export function _isNull(val: unknown): val is null { | |
return _isObjT(val) && typeof val === null | |
} | |
/** | |
* Is literal type undefined | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is undefined} | |
*/ | |
export function _isUndef(val: unknown): val is undefined { | |
return typeof val === 'undefined' | |
} | |
/** | |
* Is literal type null or undefined | |
* | |
* @export | |
* @param {*} val | |
* @returns {(val is null | undefined)} | |
*/ | |
export function _isUndOrNull(val: unknown): val is null | undefined { | |
return _isNull(val) || _isUndef(val) | |
} | |
/** | |
* Is literal type string | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is string} | |
*/ | |
export function _isStr(val: unknown): val is string { | |
return typeof val === 'string' | |
} | |
/** | |
* Is type empty string (e.g. "" vs "foo") | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is ''} | |
*/ | |
export function _isStrEmpty(val: unknown): val is '' { | |
return _isStr(val) && !val.length | |
} | |
/** | |
* Is type empty array or empty string | |
* | |
* @export | |
* @param {*} val | |
* @returns {(val is '' | [])} | |
*/ | |
export function _isEmptyStrOrArr(val: unknown): val is '' | [] { | |
return _isStrEmpty(val) || _isArrEmpty(val) | |
} | |
/** | |
* Is type Buffer | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is Buffer} | |
*/ | |
export function _isBuff(val: unknown): val is Buffer { | |
return ( | |
_isObjT(val) && | |
_isObjT(val.constructor) && | |
'isBuffer' in val.constructor && | |
_isFn((val.constructor as any).isBuffer) && | |
(val.constructor as any).isBuffer(val) | |
) | |
} | |
/** | |
* Is literal type of primitive type | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is Primitive} | |
*/ | |
export function _isPrim(val: unknown): val is Primitive { | |
return Boolean( | |
_isSym(val) || | |
_isBig(val) || | |
_isNumT(val) || | |
_isObjT(val) || | |
_isBool(val) || | |
_isStr(val) || | |
_isFn(val) || | |
_isUndef(val) | |
) | |
} | |
export type Primitive = symbol | bigint | boolean | number | string | undefined | |
export type PrimitiveBasic = number | string | boolean | |
/** | |
* Checks if the parameter has length greater than 0 or second parameter | |
* @export | |
* @template T | |
* @param {(Iterable<T> | ArrayLike<T>)} val | |
* @param {number} [of] evaluation number if you want to check it against | |
* @param {('>'|'<'|'=')} [operator] | |
* @return {*} {boolean} | |
*/ | |
export function _ln<T>(val: Iterable<T> | ArrayLike<T> | number, of?: number, operator?: '>' | '<' | '='): boolean { | |
if (val) { | |
const ln = _isNum(val) ? val : val['length'] | |
const base = _isNum(of) ? of : 0 | |
switch (operator) { | |
case '<': | |
return ln < base | |
case '=': | |
return ln === base | |
default: | |
return ln > base | |
} | |
} | |
return false | |
} | |
/** | |
* Verify the value has a length or value greater than 0 by default | |
* | |
* TODO: Document better... And refactor/cleanup.. Maybe just do away with | |
* the combination and separate out all, probably best | |
* | |
* Options: | |
* (equalTo) - val must equal to it otherwise false | |
* (lessThan) - val must be less than then it | |
* (moreThan) - val must be greater than it | |
* (lessThan & moreThan) - val must be between the two | |
* (also) - provide an additional validator | |
* (and) - provide any boolean value that may require true | |
* | |
* @export | |
* @template T | |
* @template U | |
* @param {T} val | |
* @param {HasLenOpt<U>} opts | |
* @returns {boolean} | |
*/ | |
export function hasLn<T extends Iterable<U> | ArrayLike<U> | number, U>(val: T, opts?: HasLenOpt<U>): boolean | |
export function hasLn<T extends Iterable<U> | ArrayLike<U> | number, U>(val: T, equalTo: number): boolean | |
export function hasLn<T extends Iterable<U> | ArrayLike<U> | number, U>(val: T, opts: any): boolean { | |
const _opts = !opts || _isObj(opts) ? { ...opts } : { equalTo: opts } | |
const { equalTo, lessThan, moreThan, and, also } = _opts ?? {} | |
const v = _isNum(val) ? Number(val) : ((val as unknown) as Iterable<T> | ArrayLike<T>) | |
const len: number = _isNum(v) ? v : (v as any).length | |
const e = equalTo | |
const l = lessThan | |
const m = moreThan | |
const et = _isNum(e) | |
const lt = _isNum(l) | |
const mt = _isNum(m) | |
const noOpt = !et && !lt && !mt | |
const chkFunc = (firstCheck: boolean): boolean => { | |
if (_isFn(also)) { | |
return Boolean(and ? firstCheck && also(len, v) : firstCheck || also(len, v)) | |
} | |
// !opts && console.log('has len', val, len, _isNumPos(len)) | |
return firstCheck | |
} | |
const chkOpt = (equal: boolean, less: boolean, more: boolean, chk: boolean): boolean => { | |
return et === equal && lt === less && mt === more && chkFunc(chk) | |
} | |
return Boolean( | |
noOpt | |
? chkFunc(_isNumPos(len)) | |
: chkOpt(true, false, false, len === e) || | |
chkOpt(false, true, false, len < l) || | |
chkOpt(false, false, true, len > m) || | |
chkOpt(false, true, true, and ? len < l && len > m : len < l || len > m) || | |
chkOpt(true, true, false, and ? len === e && len < l : len === e || len < l) || | |
chkOpt(true, false, true, and ? len === e && len > m : len === e || len > m) || | |
chkOpt(true, true, true, and ? len === e && len < l && len > m : len === e || len < l || len > m) | |
) | |
} | |
export type HasLenOpt<U> = { | |
equalTo?: number | undefined | |
lessThan?: number | undefined | |
moreThan?: number | undefined | |
and?: true | undefined | |
also?: ((length: number, value?: U) => boolean) | undefined | |
} | |
/** | |
* Checks if the value is a negative number | |
* | |
* @export | |
* @param {*} val | |
* @returns {(val is number & boolean)} | |
*/ | |
export function _isNumNeg(val: unknown): val is number & boolean { | |
return _isNum(val) && Number(val) < 0 | |
} | |
/** | |
* Checks if the value is a positive number | |
* | |
* @export | |
* @param {*} val | |
* @returns {(val is number & boolean)} | |
*/ | |
export function _isNumPos(val: unknown): val is number & boolean { | |
return _isNum(val) && Number(val) > 0 | |
} | |
/** | |
* Checks if the value is a number equal to zero(0) | |
* | |
* @export | |
* @param {*} val | |
* @returns {val is 0} | |
*/ | |
export function _isNumZero(val: unknown): val is 0 { | |
return _isNum(val) && Number(val) === 0 | |
} | |
/** | |
* Check if the value is same as one of the values within the array | |
* | |
* @export | |
* @template U | |
* @param {*} val | |
* @param {Array<U>} possible | |
* @returns {val is U} | |
*/ | |
export function isOneOf<U>(val: unknown, possible: Array<U>): val is U { | |
return possible.some((i) => val === i) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { _isArr, _isFn, _isNum, _isObj, _isStr, _isUndef, hasLn } from './guards' | |
export function tools(): string { | |
return 'tools' | |
} | |
/** | |
* Shortcut for retrieving the length property, | |
* defaults to zero (0) if the property does not exist | |
* | |
* @export | |
* @template T | |
* @param {(Iterable<T> | ArrayLike<T>)} val | |
* @returns {number} | |
*/ | |
export function ln<T>(val: Iterable<T> | ArrayLike<T>): number { | |
if (val) { | |
return val['length'] ?? 0 | |
} | |
return 0 | |
} | |
/** | |
* Shortcut for String(...) | |
* | |
* @export | |
* @param {*} val | |
* @returns {string} | |
*/ | |
export function s(...val: Parameters<typeof String>): string { | |
return String(val) | |
} | |
/** | |
* Shortcut for Boolean(...) | |
* | |
* @export | |
* @param {*} val | |
* @returns {boolean} | |
*/ | |
export function b(...val: Parameters<typeof Boolean>): boolean { | |
return Boolean(...val) | |
} | |
/** | |
* Shortcut for JSON.stringify(...) | |
* | |
* @export | |
* @param args | |
* @return {string} | |
*/ | |
export function JSONs(...args: Parameters<typeof JSON.stringify>): ReturnType<typeof JSON.stringify> { | |
return JSON.stringify(...args) | |
} | |
/** | |
* Shortcut for JSON.parse(...) | |
* | |
* @export | |
* @param args | |
* @return {any} | |
*/ | |
export function JSONp(...args: Parameters<typeof JSON.parse>): ReturnType<typeof JSON.parse> { | |
return JSON.parse(...args) | |
} | |
/** | |
* Sort iterable by property or index returned from callbackFn | |
* @export` | |
* @param {T[]} target | |
* @param {(item: T) => (string | number)} callbackFn | |
* @param thisArg | |
* @returns {T[]} | |
*/ | |
export function sortBy<T>(target: T[], callbackFn: ((item: T, target: T[]) => string | number), thisArg?: any) { | |
return target.slice().sort((a, b) => { | |
const propA = callbackFn.call(thisArg, a, target) // callbackFn(a) | |
const propB = callbackFn.call(thisArg, b, target) // prop(b) | |
if (propA < propB) return -1 | |
if (propA > propB) return 1 | |
return 0 | |
}) | |
} | |
/** | |
* Safe array, will always return an array | |
* | |
* @export | |
* @template T | |
* @param {T} val | |
* @param {any[]} [or] | |
* @returns {any[]} | |
*/ | |
export function safeArray<T>(val: T, or?: any[]): any[] { | |
return _isArr(val) ? val : _isArr(or) ? or : [] | |
} | |
/** | |
* Safe object/{} will always return an object | |
* | |
* @export | |
* @template T | |
* @param {T} val | |
* @param {Record<string, unknown>} [or] | |
* @returns {Record<string, unknown>} | |
*/ | |
export function safeObj<T>(val: T, or?: Record<string, unknown>): Record<string, unknown> { | |
return _isObj(val) ? val : _isObj(or) ? or : {} | |
} | |
/** | |
* Get safe property | |
* | |
* @see getDeepProperty | |
* @see setDeepProperty | |
* | |
* @export | |
* @template T | |
* @template K | |
* @param {T} obj | |
* @param {K} key | |
* @returns {T[K]} | |
*/ | |
export function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] | undefined { | |
if (!_isObj(obj)) { | |
return undefined | |
} | |
return obj[key] | |
} | |
/** | |
* Get a nested property within an object literal | |
* | |
* EXAMPLE: | |
* const obj = {'a': {'prop': {'that': 'exists'}}}; | |
* getNestedProperty(obj, 'a.very.deep.prop', 'value') | |
* | |
* @see getProperty | |
* @see setDeepProperty | |
* | |
* @export | |
* @template T | |
* @param {T} obj | |
* @param {string} path | |
* @param {string} [separator='.'] | |
* @returns {*} | |
*/ | |
export function getDeepProperty<T>(obj: T, path: string, separator = '.'): any { | |
const keys = path.split(separator) | |
const lastKey = keys.pop() | |
const lastObj = keys.reduce((v, k) => (v[k] = v[k] ?? {}), obj) | |
return lastObj[lastKey] | |
} | |
/** | |
* Set a nested property within an object literal | |
* | |
* EXAMPLE: | |
* const obj = {'a': {'prop': {'that': 'exists'}}}; | |
* setNestedProperty(obj, 'a.very.deep.prop', 'value') | |
* | |
* @see getProperty | |
* @see getDeepProperty | |
* | |
* @export | |
* @template T | |
* @param {T} obj | |
* @param {string} path | |
* @param {*} val | |
* @param {string} [separator='.'] | |
*/ | |
export function setDeepProperty<T>(obj: T, path: string, val: any, separator = '.'): T { | |
const keys = path.split(separator) | |
const lastKey = keys.pop() | |
const lastObj = keys.reduce((v, k) => (v[k] = v[k] ?? {}), obj) | |
lastObj[lastKey] = val | |
return obj | |
} | |
/** | |
* Sort an array of object by a specified property path, optional second sorter | |
* | |
* @export | |
* @template T | |
* @param {T[]} items | |
* @param {string} firstPath | |
* @param {string} [secondPath] | |
* @returns {T[]} | |
* | |
* @deprecated {@link sortBy} | |
* @see {@link sortBy} | |
* TODO: REMOVE AMBIGUOUS METHOD TO REPLACE WITH `sortBy` | |
*/ | |
export function sortByProperty<T>(items: T[], firstPath: string, secondPath?: string): T[] { | |
const lgr = (a, b, p) => getDeepProperty(a, p) > getDeepProperty(b, p) | |
const eq = (a, b, p) => getDeepProperty(a, p) === getDeepProperty(b, p) | |
return items.sort((a, b) => | |
lgr(a, b, firstPath) ? 1 : secondPath && eq(a, b, firstPath) ? (lgr(a, b, secondPath) ? 1 : -1) : -1, | |
) | |
} | |
/** | |
* Copy an object literal | |
* | |
* @export | |
* @param {Record<string, unknown>} obj | |
* @returns | |
*/ | |
export function copyObj(obj: Record<string, unknown>) { | |
return copy(obj) | |
} | |
/** | |
* Copy a json object by stringify and then parsing | |
* | |
* @export | |
* @template T | |
* @param {T} json | |
* @returns {T} | |
*/ | |
export function copyJson<T>(json: T): T { | |
return JSON.parse(JSON.stringify(json)) | |
} | |
/** | |
* | |
* | |
* @export | |
* @param {[any, any][]} arr | |
* @returns | |
*/ | |
export function arrayToObjectLiteral(arr: [any, any][]) { | |
return Object.assign({}, ...arr.map(([key, val]) => ({ [key]: val }))) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @template K | |
* @param {T} obj | |
* @returns {K[]} | |
*/ | |
export function getAllObjectKeys<T extends Record<string, unknown>, K extends keyof T>(obj: T): K[] { | |
return (_isFn(Object.getOwnPropertySymbols) | |
? Object.keys(obj).concat(Object.getOwnPropertySymbols(obj) as any) | |
: Object.keys(obj)) as K[] | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @template K | |
* @param {T} target | |
* @param {(val: T[K], key?: K, original?: T) => T[K]} reducerCallback | |
* @returns {T} | |
*/ | |
export function reduceObject<T extends Record<string, unknown>, K extends keyof T>( | |
target: T, | |
reducerCallback: (val: T[K], key?: K, original?: T) => T[K], | |
): T { | |
if (_isUndef(reducerCallback)) { | |
return target | |
} | |
const _target: T = copyObj(target) | |
const original: T = copyObj(target) | |
return getAllObjectKeys(_target).reduce((result, key) => { | |
result[key as string] = reducerCallback(_target[key] as T[K], key as K, original) | |
return result | |
}, {}) as T | |
} | |
/** | |
* Map an object keys and values | |
* @export | |
* @template K | |
* @template V | |
* @template U | |
* @param target | |
* @param callbackFn | |
* @param thisArg | |
* @returns {{[key in K]: U}} | |
*/ | |
export function map<K extends string, V, U>( | |
target: { [key in K]: V }, | |
callbackFn: (value: V, key: K, obj: { [key in K]: V }) => U, | |
thisArg?: unknown, | |
): { [key in K]: U } { | |
const res: Partial<{ [key in K]: U }> = {} | |
for (const key in target) { | |
if (Object.prototype.hasOwnProperty.call(target, key)) { | |
res[key] = callbackFn.call(thisArg, target[key], key, target) | |
} | |
} | |
return res as { [key in K]: U } | |
} | |
/** | |
* | |
* Map object literal for quick editing or build a keyed object literal from | |
* an array of objects | |
* | |
* @example ` | |
* let foobar = mapObject( | |
* [{id:'foo',foobar:'bar'}], | |
* ((value, key, index, array) => [value.id, value]), | |
* { advanced: true, copy: true, filter: true } | |
* ) // => ({'foo':{id:'foo',foobar:'bar'}}) | |
* ` | |
* @example ` | |
* let foobar = mapObject( | |
* {foo:1,bar:2}, | |
* ((value, key, index, array) => value * 2), | |
* { advanced: false, copy: true, filter: true } | |
* ) // => ({foo:2,bar:4}) | |
* ` | |
* | |
* @export | |
* @template T | |
* @param {T|Array<object>|object} target | |
* @param {<((val,key,index,arr) => (val|[key,val]))>} callbackfn | |
* @param {<{copy: boolean, filter: boolean, advanced: boolean}>} [opt] ( | |
* if !!filter - removes anything undefined, | |
* if !!advanced - the clbk should return a tuple of the new key and value [k,v] | |
* ) | |
* @returns {object} | |
* @deprecated Move to {@link map} | |
* @see {@link map} | |
*/ | |
export function mapObject(target, callbackfn: MapObjectClbkFn, opt?: MapObjectOptions) { | |
const { copy: cp = false, filter = false, advanced = false, forEach = false } = opt ?? {} | |
const data = cp ? copy(target) : target | |
const entries = Object.entries(data) ?? [] | |
const handler = ([key, value], index, array) => | |
// If advanced is true allow the user to set the | |
// full tuple (i.e. including the object key) | |
!advanced ? [key, callbackfn(value, key, index, array)] : callbackfn(value, key, index, array) | |
const result: any = forEach ? entries.forEach(handler) : entries.map(handler) | |
if (!forEach) { | |
const filtered = !filter ? result : result.filter(([, v]) => !_isUndef(v)) | |
return Object.fromEntries(filtered) | |
} | |
return undefined | |
} | |
export type MapObjectClbkFn = ( | |
value: any, | |
key?: string | number, | |
index?: number, | |
array?: Array<any>, | |
) => [key: string | number, value: any] | any | void | |
export type MapObjectOptions = { | |
copy?: boolean | |
filter?: boolean | |
advanced?: boolean | |
forEach?: boolean | |
} | |
/** | |
* Filter indexes inside an object literal. Similar to | |
* Array.filter(...) | |
* @param target | |
* @param {FilterObjPredicate<T>} predicate | |
* @returns {{[p: string]: any}} | |
*/ | |
export function filterObject<T>(target, predicate: FilterObjPredicate<T>) { | |
return mapObject( | |
target, | |
(v, k, i, a): [any, any] | null => { | |
if (predicate(v, k, i, a)) { | |
return [k, v] | |
} | |
return null | |
}, | |
{ advanced: true, filter: true }, | |
) | |
} | |
type FilterObjPredicate<T> = { | |
(value: T, key: keyof any, index: number, array: T[]): unknown | |
} | |
/** | |
* Shortcut for Object.assign({}, target, ...otherTargets) | |
* | |
* @export | |
* @template T | |
* @template U | |
* @param {T} target | |
* @param {...U[]} source | |
* @returns {(T & U)} | |
*/ | |
export function updateObj<T, U>(target: T, ...source: U[]): T & U { | |
// Encapsulate the idea of passing a new object as the first parameter | |
// to Object.assign to ensure we correctly copy data instead of mutating | |
return Object.assign({}, target, ...source) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @template K | |
* @param {Readonly<T>} obj | |
* @param {K} key | |
* @param {{ copy: boolean }} [options] | |
* @returns {T} | |
*/ | |
export function deleteProperty<T, K extends keyof T>(obj: Readonly<T>, key: K, options?: { copy: boolean }): T { | |
const opts = { copy: false, ...options } | |
const newObj = opts.copy ? copyObj(obj) : obj | |
delete newObj[key] | |
return newObj | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @template U | |
* @template F | |
* @param {(Iterable<T> | ArrayLike<T>)} iterable | |
* @param {(v: T, k: number) => U} [mapfn] | |
* @param {*} [thisArg] | |
* @returns {F extends undefined ? Array<T> : Array<U>} | |
*/ | |
export function toArray<T, U, F extends (v: T, k: number) => U = undefined>( | |
iterable: Iterable<T> | ArrayLike<T>, | |
mapfn?: (v: T, k: number) => U, | |
thisArg?: any, | |
): F extends undefined ? Array<T> : Array<U> { | |
return Array.from(iterable, mapfn, thisArg) as F extends undefined ? Array<T> : Array<U> | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @template U | |
* @template F | |
* @param {(Iterable<T> | ArrayLike<T>)} iterable | |
* @param {F} [mapfn] | |
* @param {*} [thisArg] | |
* @returns {F extends undefined ? Array<T> : Array<U>} | |
*/ | |
export function copyArray<T, U, F extends (v: T, k: number) => U = undefined>( | |
iterable: Iterable<T> | ArrayLike<T>, | |
mapfn?: F, | |
thisArg?: any, | |
): F extends undefined ? Array<T> : Array<U> { | |
return toArray(iterable, mapfn, thisArg) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @param {Array<T>} oldArray | |
* @param {(Array<T> | object)} newArray | |
* @returns {Array<T>} | |
*/ | |
export function updateArray<T>(oldArray: Array<T>, newArray: Array<T> | Record<string, unknown>): Array<T> { | |
return Object.assign([], oldArray, newArray) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @param {(number | any)} index | |
* @param {Array<T>} array | |
* @param {(T | Array<T>)} [items] | |
* @param {{ replace?: boolean, copy?: boolean }} [options] | |
* @returns {MutatedArrayResponse<T>} | |
*/ | |
export function mutateArray<T>( | |
index: number | any, | |
array: Array<T>, | |
items?: T | Array<T>, | |
options?: { replace?: boolean; copy?: boolean }, | |
): MutatedArrayResponse<T> { | |
const { replace, copy } = { replace: true, copy: false, ...options } | |
const _array = copy ? copyArray(array) : array | |
const _items = safeArray(items, items ? [items] : []) | |
const deleteCount = replace ? ln(_items) : 0 | |
const deleted = _array.splice(index, deleteCount, ..._items) | |
return { items: _array, deleted, added: _items } | |
} | |
type MutatedArrayResponse<T> = Record<'items' | 'deleted' | 'added', Array<T>> | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @param {number} index | |
* @param {Array<T>} array | |
* @param {(T | Array<T>)} items | |
* @returns {MutatedArrayResponse<T>} | |
*/ | |
export function addAtIndex<T>(index: number, array: Array<T>, items: T | Array<T>): MutatedArrayResponse<T> { | |
return mutateArray(index, array, items, { replace: false }) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @param {(number | any)} index | |
* @param {Array<T>} array | |
* @param {T} item | |
* @returns {MutatedArrayResponse<T>} | |
*/ | |
export function updateAtIndex<T>(index: number | any, array: Array<T>, item: T): MutatedArrayResponse<T> { | |
return mutateArray(index, array, item) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @param {number} index | |
* @param {Array<T>} array | |
* @returns {MutatedArrayResponse<T>} | |
*/ | |
export function removeAtIndex<T>(index: number, array: Array<T>): MutatedArrayResponse<T> { | |
return mutateArray(index, array) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template T | |
* @param {T} item | |
* @param {Array<T>} array | |
* @returns {Array<T>} | |
*/ | |
export function removeFromArray<T>(item: T, array: Array<T>): Array<T> { | |
return safeArray(array).filter((i) => i !== item) | |
} | |
/** | |
* | |
* | |
* @export | |
* @template K | |
* @template T | |
* @template U | |
* @param {T} array | |
* @param {(K | any)} currentIndex | |
* @param {(K | any)} newIndex | |
* @returns {T} | |
*/ | |
export function reorderArray<K extends number & keyof T, T extends Array<U>, U>( | |
array: T, | |
currentIndex: K | any, | |
newIndex: K | any, | |
): T { | |
const arr = mutateArray(currentIndex, array) | |
return addAtIndex(newIndex, arr.items, arr.deleted).items as T | |
} | |
/** | |
* Shortcut for String(val).trim() | |
* | |
* @export | |
* @template T | |
* @param {T} val | |
* @returns {string} | |
*/ | |
export function trim<T>(val: T): string { | |
return s(val).trim() | |
} | |
/** | |
* Capitalize the first word in the string | |
* | |
* @export | |
* @template T | |
* @param {T} val | |
* @returns {T} | |
*/ | |
export function capitalize<T extends string>(val: T): T { | |
if (!_isStr(val) || !hasLn(val)) { | |
return val | |
} | |
return (s(val).charAt(0).toUpperCase() + s(val).slice(1)) as T | |
} | |
/** | |
* Capitalize every word separated by a space | |
* | |
* @export | |
* @template T | |
* @param {T} val | |
* @returns {T} | |
*/ | |
export function capitalizeTitle<T extends string>(val: T, separator = ' '): T { | |
return s(val) | |
.split(separator) | |
.map((i) => capitalize(i)) | |
.join(separator) as T | |
} | |
/** | |
* Create a numeronym string from the provided string of characters | |
* | |
* @export | |
* @param {string} str | |
* @param {NumeronymOpts} [opt] | |
* @returns | |
*/ | |
export function numeronym(str: string, opt?: NumeronymOpts) { | |
const { kind, short } = opt ?? {} | |
const builder = { | |
[NumeronymKind.n19s]: () => { | |
const v = String(str) | |
const ln = v.length | |
if (ln < 2) { | |
return v | |
} | |
if (ln < 3 || short) { | |
return `${v[0]}${ln - 1}` | |
} | |
return `${v[0]}${ln - 2}${v[ln - 1]}` | |
}, | |
} | |
return builder[NumeronymKind[kind] ?? NumeronymKind.n19s]() | |
} | |
export enum NumeronymKind { | |
n19s = 'NumericalContractions' /* first letter + len between(+ last letter) */, | |
A9bs = 'AlphanumericAbbreviationS' /* AlphaN. = A9, Abbr. = bs */, | |
A2S = 'AlphanumericAcronymS' /* (2) A's and (1) S (e.g. W3 or W3C) */, | |
} | |
export type NumeronymOpts = { | |
kind?: NumeronymKind | |
short?: boolean | |
} | |
/** | |
* Ensure and parse any value to a number | |
* | |
* @export | |
* @param {*} val the value to parse into a number | |
* @param {*} [elseT] returns this if parsing fails | |
* @param {number} [radix] only necessary if value is of type string | |
* @returns {(number | typeof elseT)} | |
*/ | |
export function toNum(val: any, elseT?: any, radix?: number): number | typeof elseT { | |
const num = _isNum(radix) && _isStr(val) ? parseInt(val, radix) : Number(val) | |
return isNaN(num) ? elseT ?? 0 : num | |
} | |
/** | |
* Convert decimal/number into its Base16/Hexadecimal equivalent | |
* | |
* @export | |
* @param {number} val | |
* @returns {string} | |
*/ | |
export function numberToHexadecimal(val: number): string { | |
return Number(val).toString(16) | |
} | |
/** | |
* Convert Base16/Hexadecimal string to its decimal/number equivalent | |
* | |
* @export | |
* @param {string} val | |
* @returns {number} | |
*/ | |
export function hexadecimalToNumber(val: string): number { | |
return toNum(val, 0, 16) | |
} | |
/** | |
* Get the display name of a function, react component | |
* @export | |
* @param {*} fn | |
* @param {string} fallback | |
* @returns {string} | |
*/ | |
export function getDisplayName(fn, fallback = 'Component'): string { | |
return fn?.displayName ?? fn?.name ?? fallback | |
} | |
/** | |
* No operation function with no return | |
* @param args | |
* @returns {any} | |
*/ | |
export function noop(...args: any[]): any { | |
// Do nothing. | |
} | |
/** | |
* Convince closure compiler that the wrapped function has no side-effects. | |
* | |
* Closure compiler always assumes that `toString` has no side-effects. We use this quirk to | |
* allow us to execute a function but have closure compiler mark the call as no-side-effects. | |
* It is important that the return value for the `noSideEffects` function be assigned | |
* to something which is retained otherwise the call to `noSideEffects` will be removed by closure | |
* compiler. | |
* | |
* @see https://github.com/angular/angular/blob/master/packages/core/src/util/closure.ts | |
*/ | |
export function noSideEffects<T>(fn: () => T): T { | |
return { toString: fn }.toString() as unknown as T | |
} | |
namespace Copier { | |
const getType = <T>(obj: T) => (toString.call(obj) as string).slice(8, -1) | |
const defaultAssign = <T, S>(target: T, source: S) => { | |
getAllKeys(source).forEach((key) => { | |
if (Object.prototype.hasOwnProperty.call(source, key)) { | |
target[key] = source[key] | |
} | |
}) | |
return target as T & S | |
} | |
const objectAssign = Object.assign ?? defaultAssign | |
const getAllKeys = typeof Object.getOwnPropertySymbols === 'function' | |
? (obj) => Object.keys(obj).concat(Object.getOwnPropertySymbols(obj) as any) | |
: (obj) => Object.keys(obj) | |
type CopyParams<T, U, K, V, X> = T extends ReadonlyArray<U> | |
? ReadonlyArray<U> | |
: T extends Map<K, V> | |
? Map<K, V> | |
: T extends Set<X> | |
? Set<X> | |
: T extends Record<string, unknown> | |
? T | |
: any | |
/** | |
* Immutability Copy Cherry Pick | |
* @see {@link:https://github.com/kolodny/immutability-helper/blob/master/index.ts} | |
*/ | |
export function copy<T, U, K, V, X>(value: CopyParams<T, U, K, V, X>) { | |
return Array.isArray(value) | |
? objectAssign(value.constructor(value.length), value) | |
: getType(value) === 'Map' | |
? new Map(value as Map<K, V>) | |
: getType(value) === 'Set' | |
? new Set(value as Set<X>) | |
: value && typeof value === 'object' | |
? objectAssign(Object.create(Object.getPrototypeOf(value)), value) as T | |
: value as T | |
} | |
} | |
export const copy = Copier.copy |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment