UNPKG

xypriss-security

Version:

Advanced High-Performance Security Framework. Military-grade encryption, post-quantum resilience, and fortified data structures.

175 lines 7.08 kB
"use strict"; /*************************************************************************** * XyPrissJS - Fast And Secure * * @author Nehonix * @license Nehonix OSL (NOSL) * * Copyright (c) 2025 Nehonix. All rights reserved. * * This License governs the use, modification, and distribution of software * provided by NEHONIX under its open source projects. * NEHONIX is committed to fostering collaborative innovation while strictly * protecting its intellectual property rights. * Violation of any term of this License will result in immediate termination of all granted rights * and may subject the violator to legal action. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, * INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, * AND NON-INFRINGEMENT. * IN NO EVENT SHALL NEHONIX BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES ARISING FROM THE USE OR INABILITY TO USE THE SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * ***************************************************************************** */ Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeWithDefaults = mergeWithDefaults; // --------------------------------------------------------------------------- // Implementation // --------------------------------------------------------------------------- /** Keys that must never be written to avoid prototype pollution. */ const BLOCKED_KEYS = new Set(["__proto__", "constructor", "prototype"]); /** * mergeWithDefaults — Smart config merge utility * * Unlike `{ ...defaults, ...userOptions }` (shallow spread), this utility: * * 1. **User-provided keys always win** — a key present in `userOptions` (even * if its value is `false`, `null`, `0`, or `""`) is NEVER overridden by a * default. Only truly absent keys fall back to the default. * * 2. **Deep merge for plain objects** — nested objects are merged recursively * at any depth, so that `userOptions.rateLimit.max` doesn't wipe out * `defaults.rateLimit.windowMs`. * * 3. **No mutation** — returns a new object; inputs are untouched. * * 4. **Circular reference protection** — cycles are detected and broken * (the circular reference is replaced by `undefined`). * * 5. **Prototype pollution protection** — dangerous keys (`__proto__`, * `constructor`, `prototype`) are silently ignored. * * 6. **Array strategy** — configurable via `options.arrayStrategy`: * - `"replace"` (default) — user array fully replaces the default array. * - `"concat"` — arrays are concatenated (defaults first). * - `"merge"` — elements are merged index-by-index (plain * objects recurse, primitives use user value). * * 7. **Symbol keys** — included in the merge alongside string keys. * * ### Example * ```ts * const defaults = { * origin: true, * credentials: false, * maxAge: 86400, * rateLimit: { max: 100, windowMs: 60_000 }, * }; * const user = { * credentials: true, * origin: ["http://localhost:5173"], * rateLimit: { max: 50 }, // windowMs falls back to default * }; * * mergeWithDefaults(defaults, user); * // → { * // origin: ["http://localhost:5173"], * // credentials: true, * // maxAge: 86400, * // rateLimit: { max: 50, windowMs: 60_000 }, * // } * ``` */ function mergeWithDefaults(defaults, userOptions, options = {}, /** @internal — tracks visited objects to break circular refs */ _seen = new WeakSet()) { if (!userOptions) return shallowClone(defaults); // Guard against circular structures if (_seen.has(userOptions)) return shallowClone(defaults); _seen.add(userOptions); const result = shallowClone(defaults); const arrayStrategy = options.arrayStrategy ?? "replace"; // Merge both string and symbol keys from userOptions const keys = [ ...Object.keys(userOptions), ...Object.getOwnPropertySymbols(userOptions), ]; for (const key of keys) { // Prototype pollution guard (only relevant for string keys) if (typeof key === "string" && BLOCKED_KEYS.has(key)) continue; const userVal = userOptions[key]; const defaultVal = defaults[key]; // Honour `undefined` as "not provided" — let default win if (userVal === undefined) continue; if (isPlainObject(userVal) && isPlainObject(defaultVal)) { // Both sides are plain objects → recurse result[key] = mergeWithDefaults(defaultVal, userVal, options, _seen); } else if (Array.isArray(userVal) && Array.isArray(defaultVal) && arrayStrategy !== "replace") { result[key] = mergeArrays(defaultVal, userVal, arrayStrategy, options, _seen); } else { // Primitive, array (replace strategy), class instance, null, etc. result[key] = userVal; } } return result; } // --------------------------------------------------------------------------- // Helpers // --------------------------------------------------------------------------- /** Merges two arrays according to the chosen strategy. */ function mergeArrays(defaultArr, userArr, strategy, options, seen) { if (strategy === "concat") { return [...defaultArr, ...userArr]; } // "merge" — element-wise const length = Math.max(defaultArr.length, userArr.length); const merged = []; for (let i = 0; i < length; i++) { const dItem = defaultArr[i]; const uItem = userArr[i]; if (i >= userArr.length) { // User array is shorter — keep default element merged.push(dItem); } else if (i >= defaultArr.length) { // Default array is shorter — take user element as-is merged.push(uItem); } else if (isPlainObject(uItem) && isPlainObject(dItem)) { merged.push(mergeWithDefaults(dItem, uItem, options, seen)); } else { // Primitive or non-plain-object — user wins merged.push(uItem !== undefined ? uItem : dItem); } } return merged; } /** Shallow-clones an object preserving both string and symbol own keys. */ function shallowClone(obj) { const clone = Object.create(Object.getPrototypeOf(obj)); for (const key of Object.getOwnPropertyNames(obj)) { clone[key] = obj[key]; } for (const sym of Object.getOwnPropertySymbols(obj)) { clone[sym] = obj[sym]; } return clone; } /** Returns `true` for plain `{}` objects (not arrays, not class instances). */ function isPlainObject(value) { if (typeof value !== "object" || value === null) return false; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; } //# sourceMappingURL=mergeWithDefaults.js.map