nhb-toolbox
Version:
A versatile collection of smart, efficient, and reusable utility functions and classes for everyday development needs.
285 lines (284 loc) • 10.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseJSON = exports.isDeepEqual = void 0;
exports.convertArrayToString = convertArrayToString;
exports.debounceAction = debounceAction;
exports.throttleAction = throttleAction;
exports.getInstanceMethodNames = getInstanceMethodNames;
exports.getStaticMethodNames = getStaticMethodNames;
exports.countInstanceMethods = countInstanceMethods;
exports.countStaticMethods = countStaticMethods;
exports.getInstanceGetterNames = getInstanceGetterNames;
exports.getStaticGetterNames = getStaticGetterNames;
exports.getClassDetails = getClassDetails;
exports.deepParsePrimitives = deepParsePrimitives;
const helpers_1 = require("../array/helpers");
const sort_1 = require("../array/sort");
const non_primitives_1 = require("../guards/non-primitives");
const primitives_1 = require("../guards/primitives");
const specials_1 = require("../guards/specials");
/**
* * Deeply compare two values (arrays, objects, or primitive values).
*
* @param a First value to compare.
* @param b Second value to compare.
* @returns Whether the values are deeply equal.
*/
const isDeepEqual = (a, b) => {
// If both values are strictly equal (handles primitive types and same references)
if (a === b)
return true;
// If the types of the two values are different
if (typeof a !== typeof b)
return false;
// If either is null or undefined, they must both be null or undefined
if (a === null || b === null)
return a === b;
// Check for array equality
if (Array.isArray(a) && Array.isArray(b)) {
if (a?.length !== b?.length)
return false;
return a?.every((element, index) => (0, exports.isDeepEqual)(element, b[index]));
}
// Check for object equality
if (typeof a === 'object' && typeof b === 'object') {
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
if (aKeys?.length !== bKeys?.length)
return false;
return aKeys?.every((key) => (0, exports.isDeepEqual)(a[key], b[key]));
}
return false;
};
exports.isDeepEqual = isDeepEqual;
/**
* * Converts an array of primitive values or objects to a string using a separator or target key.
*
* @param array Array to convert.
* @param options Options for separator or key extraction from objects.
* @returns String representation of array values.
*/
function convertArrayToString(array, options) {
if (!(0, non_primitives_1.isValidArray)(array))
return '';
const { separator = ', ' } = options ?? {};
if ((0, non_primitives_1.isArrayOfType)(array, primitives_1.isPrimitive)) {
return array?.join(separator);
}
else if ((0, non_primitives_1.isArrayOfType)(array, non_primitives_1.isNotEmptyObject)) {
if (options && 'target' in options) {
return array
?.map((el) => (0, helpers_1._resolveNestedKey)(el, options?.target))
?.join(separator);
}
else {
return '';
}
}
return '';
}
/**
* * A generic debounce function that delays the execution of a callback.
*
* @param callback - The function to debounce.
* @param delay - The delay in milliseconds. Default is `300ms`.
* @returns A debounced version of the callback function.
*
* @example
* const debouncedSearch = debounceAction((query: string) => {
* console.log(`Searching for: ${query}`);
* }, 300);
*
* debouncedSearch('laptop'); // Executes after 300ms of inactivity.
*/
function debounceAction(callback, delay = 300) {
let timeoutId = undefined;
return (...args) => {
// Clear the previous timeout
clearTimeout(timeoutId);
// Set a new timeout
timeoutId = setTimeout(() => {
callback(...args);
}, delay);
};
}
/**
* * A generic throttle function that ensures a callback is executed at most once per specified interval.
*
* @param callback - The function to throttle.
* @param delay - The delay in milliseconds. Default is `150ms`.
* @returns A throttled version of the callback function.
*
* @example
* const throttledResize = throttleAction(() => {
* console.log('Resized');
* }, 300);
*
* window.addEventListener('resize', throttledResize);
*/
function throttleAction(callback, delay = 150) {
let lastCall = 0;
return (...args) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
callback(...args);
}
};
}
/**
* * Retrieves the names of all instance methods defined directly on a class prototype.
*
* @param cls - The class constructor (not an instance).
* @returns A sorted array of instance method names.
*/
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 (0, non_primitives_1.isMethodDescriptor)(descriptor);
});
return (0, sort_1.sortAnArray)(methods);
}
/**
* * Retrieves the names of all static methods defined directly on a class constructor.
*
* @param cls - The class constructor (not an instance).
* @returns A sorted array of static method names.
*/
function getStaticMethodNames(cls) {
const methods = Object.getOwnPropertyNames(cls).filter((method) => {
return (method !== 'prototype' && method !== 'name' && method !== 'length');
});
return (0, sort_1.sortAnArray)(methods);
}
/**
* * Counts the number of instance methods defined directly on a class prototype.
*
* @param cls - The class constructor (not an instance).
* @returns The number of instance methods defined on the class prototype.
*/
function countInstanceMethods(cls) {
return getInstanceMethodNames(cls)?.length;
}
/**
* * Counts the number of static methods defined directly on a class constructor.
*
* @param cls - The class constructor (not an instance).
* @returns The number of static methods defined on the class constructor.
*/
function countStaticMethods(cls) {
return getStaticMethodNames(cls)?.length;
}
/**
* * Retrieves the names of all instance getters defined directly on a class prototype.
*
* @param cls - The class constructor (not an instance).
* @returns A sorted array of instance getter names.
*/
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 (0, sort_1.sortAnArray)(result);
}
/**
* * Retrieves the names of all static getters defined directly on a class constructor.
*
* @param cls - The class constructor (not an instance).
* @returns A sorted array of static getter names.
*/
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 (0, sort_1.sortAnArray)(result);
}
/**
* * Gathers detailed information about the instance and static methods of a class.
*
* @param cls - The class constructor (not an instance).
* @returns An object containing names and counts of instance and static methods.
*/
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,
};
}
/**
* * Parses any valid JSON string, optionally converting stringified primitives inside (nested) arrays or objects.
*
* @template T - Expected return type (default is unknown).
* @param value - The JSON string to parse.
* @param parsePrimitives - Whether to convert stringified primitives (default: `true`).
* @returns The parsed JSON value typed as `T`, or the original parsed value with optional primitive conversion.
* - Returns `{}` if parsing fails, such as when the input is malformed or invalid JSON or passing single quoted string.
*
* - *Unlike `parseJsonToObject`, which ensures the root value is an object,
* this function returns any valid JSON structure such as arrays, strings, numbers, or objects.*
*
* This is useful when you're not sure of the root structure of the JSON, or when you expect something other than an object.
*
* @see `parseJsonToObject` for strict object-only parsing.
*/
const parseJSON = (value, parsePrimitives = true) => {
try {
const parsed = JSON.parse(value);
return (parsePrimitives ? deepParsePrimitives(parsed) : parsed);
}
catch {
return {};
}
};
exports.parseJSON = parseJSON;
/**
* * Recursively parses primitive values inside objects and arrays.
*
* @template T - Expected return type after parsing (default is unknown).
* @param input - Any input value to parse recursively.
* @returns Input with primitives (strings like "true", "123") converted, typed as `T`.
*/
function deepParsePrimitives(input) {
if (Array.isArray(input)) {
return input?.map(deepParsePrimitives);
}
if ((0, non_primitives_1.isObject)(input)) {
const result = {};
for (const [key, value] of Object.entries(input)) {
result[key] = deepParsePrimitives(value);
}
return result;
}
if ((0, primitives_1.isString)(input)) {
if (/^(true|false)$/i.test(input)) {
return (input?.toLowerCase() === 'true');
}
if ((0, specials_1.isNumericString)(input)) {
return Number(input);
}
if (input === 'null') {
return null;
}
if (input === 'undefined') {
return undefined;
}
return input;
}
return input;
}