UNPKG

@thinkeloquent/core-configure

Version:

Entity configuration management with deep merging, validation, and entity definitions

156 lines 5.35 kB
import deepmerge from 'deepmerge'; import { MergeStrategy } from './types.js'; /** * Determines if a value is a plain object */ function isPlainObject(value) { return (typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date) && !(value instanceof RegExp)); } /** * Override merge: source completely replaces target */ function overrideMerge(_target, source) { return { ...source }; } /** * Deep merge with custom merge support for all keys */ function deepMergeWithCustom(target, source, options, customMergeFn) { const result = { ...target }; const sourceRecord = source; for (const key in source) { if (!Object.prototype.hasOwnProperty.call(source, key)) { continue; } const sourceValue = sourceRecord[key]; const targetValue = result[key]; // If there's a custom merge function, try it first if (customMergeFn) { const customResult = customMergeFn(key, targetValue, sourceValue); // If customMerge handled it (returned something other than default), use that if (customResult !== sourceValue || !isPlainObject(sourceValue) || !isPlainObject(targetValue)) { result[key] = customResult; continue; } // Otherwise fall through to handle nested objects } // Handle different value types if (targetValue === undefined) { result[key] = sourceValue; } else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { // Handle arrays based on strategy const arrayMergeStrategy = options.arrayMerge || 'concat'; switch (arrayMergeStrategy) { case 'replace': result[key] = sourceValue; break; case 'unique': result[key] = Array.from(new Set([...targetValue, ...sourceValue])); break; case 'concat': default: result[key] = [...targetValue, ...sourceValue]; } } else if (isPlainObject(targetValue) && isPlainObject(sourceValue)) { // Recursively merge objects result[key] = deepMergeWithCustom(targetValue, sourceValue, options, customMergeFn); } else { // For primitives, use source value result[key] = sourceValue; } } return result; } /** * Deep merge: recursively merge objects and arrays */ function deepMerge(target, source, options) { if (options.customMerge) { return deepMergeWithCustom(target, source, options, options.customMerge); } // No custom merge, use deepmerge library const arrayMergeStrategy = options.arrayMerge || 'concat'; return deepmerge(target, source, { arrayMerge: (targetArray, sourceArray) => { switch (arrayMergeStrategy) { case 'replace': return sourceArray; case 'unique': return Array.from(new Set([...targetArray, ...sourceArray])); case 'concat': default: return [...targetArray, ...sourceArray]; } }, }); } /** * Extend merge: add new keys without replacing existing ones */ function extendMerge(target, source) { const result = { ...target }; for (const [key, value] of Object.entries(source)) { const typedKey = key; if (!(typedKey in result)) { // Key doesn't exist, add it result[key] = value; } else if (isPlainObject(result[typedKey]) && isPlainObject(value)) { // Both are objects, recursively extend result[key] = extendMerge(result[typedKey], value); } else if (Array.isArray(result[typedKey]) && Array.isArray(value)) { // Both are arrays, concatenate unique values const existingArray = result[typedKey]; const newValues = value.filter((v) => !existingArray.includes(v)); result[key] = [...existingArray, ...newValues]; } // Otherwise, keep existing value (don't override) } return result; } /** * Apply merge strategy to combine configurations */ export function mergeConfigs(target, source, options) { switch (options.strategy) { case MergeStrategy.OVERRIDE: return overrideMerge(target, source); case MergeStrategy.EXTEND: return extendMerge(target, source); case MergeStrategy.MERGE: default: return deepMerge(target, source, options); } } /** * Merge multiple configuration layers in priority order */ export function mergeLayers(layers, options) { if (layers.length === 0) { return {}; } if (layers.length === 1) { return layers[0]; } return layers.reduce((accumulated, current) => { return mergeConfigs(accumulated, current, options); }); } /** * Get default merge options */ export function getDefaultMergeOptions(strategy) { return { strategy, arrayMerge: strategy === MergeStrategy.EXTEND ? 'unique' : 'concat', }; } //# sourceMappingURL=merge-strategies.js.map