UNPKG

nhb-toolbox

Version:

A versatile collection of smart, efficient, and reusable utility functions, classes and types for everyday development needs.

194 lines (193 loc) 6.65 kB
import { _resolveNestedKey } from '../array/helpers.js'; import { sortAnArray } from '../array/sort.js'; import { isDateLike } from '../date/guards.js'; import { isArray, isArrayOfType, isMethodDescriptor, isNotEmptyObject, isObject, isValidArray, } from '../guards/non-primitives.js'; import { isNonEmptyString, isPrimitive, isString } from '../guards/primitives.js'; import { isNumericString } from '../guards/specials.js'; export const isDeepEqual = (a, b) => { if (a === b) return true; if (typeof a !== typeof b) return false; if (a === null || b === null) return a === b; if (isArray(a) && isArray(b)) { if (a?.length !== b?.length) return false; return a?.every((element, index) => isDeepEqual(element, b?.[index])); } if (isObject(a) && isObject(b)) { const aKeys = Object.keys(a); const bKeys = Object.keys(b); if (aKeys?.length !== bKeys?.length) return false; return aKeys?.every((key) => isDeepEqual(a?.[key], b?.[key])); } return false; }; export function convertArrayToString(array, options) { if (!isValidArray(array)) return ''; const { separator = ', ' } = options ?? {}; if (isArrayOfType(array, isPrimitive)) { return array?.join(separator); } else if (isArrayOfType(array, isNotEmptyObject)) { if (options && 'target' in options) { return array?.map((el) => _resolveNestedKey(el, options?.target))?.join(separator); } else { return ''; } } return ''; } export function debounceAction(callback, delay = 300) { let timeoutId; return (...args) => { clearTimeout(timeoutId); timeoutId = setTimeout(() => { callback(...args); }, delay); }; } export function throttleAction(callback, delay = 150) { let lastCall = 0; return (...args) => { const now = Date.now(); if (now - lastCall >= delay) { lastCall = now; callback(...args); } }; } export function getInstanceMethodNames(cls) { const prototype = cls.prototype; const methods = Object.getOwnPropertyNames(prototype).filter((method) => { if (method === 'constructor') { return false; } const descriptor = Object.getOwnPropertyDescriptor(prototype, method); return isMethodDescriptor(descriptor); }); return sortAnArray(methods); } export function getStaticMethodNames(cls) { const methods = Object.getOwnPropertyNames(cls).filter((method) => { return method !== 'prototype' && method !== 'name' && method !== 'length'; }); return sortAnArray(methods); } export function countInstanceMethods(cls) { return getInstanceMethodNames(cls)?.length; } export function countStaticMethods(cls) { return getStaticMethodNames(cls)?.length; } export function getInstanceGetterNames(cls) { const descriptors = Object.getOwnPropertyDescriptors(cls.prototype); const result = Object.entries(descriptors) .filter(([key, desc]) => typeof desc.get === 'function' && key !== 'constructor') .map(([key]) => key); return sortAnArray(result); } export function getStaticGetterNames(cls) { const descriptors = Object.getOwnPropertyDescriptors(cls); const result = Object.entries(descriptors) .filter(([key, desc]) => typeof desc.get === 'function' && key !== 'prototype') .map(([key]) => key); return sortAnArray(result); } export function getClassDetails(cls) { const instanceNames = getInstanceMethodNames(cls); const staticNames = getStaticMethodNames(cls); const instanceGetters = getInstanceGetterNames(cls); const staticGetters = getStaticGetterNames(cls); return { instanceMethods: instanceNames, staticMethods: staticNames, instanceGetters, staticGetters, instanceCount: instanceNames?.length, staticCount: staticNames?.length, totalGetters: instanceGetters?.length + staticGetters?.length, totalMethods: instanceNames?.length + staticNames?.length, }; } export function stableStringify(obj) { const _replacer = (_, v) => (v === undefined ? null : v); if (isNotEmptyObject(obj)) { const keys = Object.keys(obj).sort(); return ('{' + keys .map((k) => JSON.stringify(k, _replacer) + ':' + (isDateLike(obj[k]) ? JSON.stringify(obj[k]) : stableStringify(obj[k]))) .join(',') + '}'); } if (isValidArray(obj)) { return '[' + obj.map((v) => stableStringify(v)).join(',') + ']'; } return JSON.stringify(obj, _replacer); } export function stripJsonEdgeGarbage(str) { if (!isNonEmptyString(str)) return ''; const lastIdx = Math.max(str.lastIndexOf('}'), str.lastIndexOf(']')); const _idxOf = (sym) => (str.indexOf(sym) !== -1 ? str.indexOf(sym) : Infinity); const firstIdx = Math.min(_idxOf('{'), _idxOf('[')); if (lastIdx === -1 || firstIdx === Infinity) return str; return str.slice(firstIdx, lastIdx + 1); } export const parseJSON = (value, parsePrimitives = true) => { try { const parsed = JSON.parse(value); return (parsePrimitives ? deepParsePrimitives(parsed) : parsed); } catch { return {}; } }; export function deepParsePrimitives(input) { if (Array.isArray(input)) { return input?.map(deepParsePrimitives); } if (isObject(input)) { const result = {}; for (const [key, value] of Object.entries(input)) { result[key] = deepParsePrimitives(value); } return result; } if (isString(input)) { if (/^(true|false)$/i.test(input)) { return (input?.toLowerCase() === 'true'); } if (isNumericString(input)) { return Number(input); } if (input === 'null') { return null; } if (input === 'undefined') { return undefined; } return input; } return input; } export function definePrototypeMethod(proto, name, impl, options) { const alreadyExists = Object.hasOwn(proto, name); if (alreadyExists && !options?.overwrite) return; Object.defineProperty(proto, name, { value: function (...args) { return impl.apply(this, args); }, enumerable: options?.enumerable ?? false, configurable: options?.configurable ?? false, writable: options?.writable ?? true, }); }