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
JavaScript
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,
});
}