UNPKG

minsky-kit

Version:
208 lines (181 loc) 5.26 kB
/** * Deeply merges source objects into the target object. * * Behavior: * - plain objects are merged recursively * - arrays are concatenated * - primitive values are overwritten by the latest source * * @param {Object} target - object to merge into * @param {...Object} sources - source objects * @returns {Object} merged target object */ export function _merge(target, ...sources) { if (!isObject(target) && !Array.isArray(target)) { target = {}; } for (const source of sources) { if (!isObject(source) && !Array.isArray(source)) continue; for (const key of Object.keys(source)) { const srcValue = source[key]; const tgtValue = target[key]; // merge arrays by concatenation if (Array.isArray(srcValue)) { if (Array.isArray(tgtValue)) { target[key] = [...tgtValue, ...srcValue]; } else { target[key] = [...srcValue]; } continue; } // merge plain objects recursively if (isObject(srcValue)) { if (!isObject(tgtValue)) { target[key] = {}; } target[key] = _merge(target[key], srcValue); continue; } // overwrite primitives target[key] = srcValue; } } return target; } /** * Removes all elements from an array that match any value in the given values array. * Modifies the original array in place. * * @param {Array} array - the array to modify * @param {Array} values - values to remove from the array * @returns {Array} - the modified array */ export function _pullAll(array, values) { if (!Array.isArray(array) || !Array.isArray(values)) { return array; } let index = 0; while (index < array.length) { if (values.includes(array[index])) { array.splice(index, 1); } else { index++; } } return array; } /** * Creates a shallow clone of a value. * - Primitives and functions are returned as-is. * - Arrays are shallow-copied. * - Plain objects are shallow-copied. * * @param {*} value - value to clone * @returns {*} - cloned value */ export function _clone(value) { // Primitives and functions are returned directly if (value === null || typeof value !== 'object') { return value; } // Array clone if (Array.isArray(value)) { return value.slice(); } // Object clone (shallow) const result = {}; Object.keys(value).forEach(key => { result[key] = value[key]; }); return result; } /** * Converts a string to kebab-case. * - Replaces non-alphanumeric characters with spaces. * - Inserts spaces between lowercase-uppercase transitions. * - Converts to lowercase and joins with hyphens. * * @param {string} str - input string * @returns {string} - kebab-case string */ export function _kebabCase(str) { if (typeof str !== 'string') return ''; return str // Replace separators and transitions between lowercase-uppercase .replace(/([a-z0-9])([A-Z])/g, '$1 $2') // Replace non-alphanumeric characters with space .replace(/[^a-zA-Z0-9]+/g, ' ') // Trim spaces .trim() // Split into words .split(/\s+/) // Convert to lowercase .map(word => word.toLowerCase()) // Join with hyphen .join('-'); } /** * Returns an array of an object's own enumerable property names. * Safe for null/undefined objects. * * @param {Object} obj - object to get keys from * @returns {string[]} - array of keys */ export const _keys = obj => obj == null ? [] : Object.keys(Object(obj)); /** * Returns a new object containing only the specified keys from the source object. * Accepts a single key or an array of keys. * Safe for null/undefined objects. * * @param {Object} obj - source object * @param {string[] | string} keys - key(s) to pick * @returns {Object} - object with selected keys */ export function _pick(obj, keys) { if (obj == null) return {}; if (!Array.isArray(keys)) keys = [keys]; const result = {}; for (const key of keys) { if (key in obj) { result[key] = obj[key]; } } return result; } /** * Recursively assigns default values from source objects to the target object. * - Only sets a key if the target's value is undefined. * - Merges nested objects recursively. * * @param {Object} target - target object * @param {...Object} sources - source objects * @returns {Object} - target object with defaults applied */ export function _defaultsDeep(target, ...sources) { if (target == null) target = {}; for (const source of sources) { if (source == null) continue; for (const key of Object.keys(source)) { const srcVal = source[key]; const tgtVal = target[key]; if (tgtVal === undefined) { // copy value if undefined target[key] = srcVal; } else if (isObject(tgtVal) && isObject(srcVal)) { // recurse into nested objects _defaultsDeep(tgtVal, srcVal); } // otherwise, keep existing target value } } return target; } /** * Helper to check if a value is a plain object (non-null, non-array). * * @param {*} val - value to check * @returns {boolean} - true if plain object */ function isObject(val) { return val !== null && typeof val === "object" && !Array.isArray(val); }