xypriss-security
Version:
Advanced High-Performance Security Framework. Military-grade encryption, post-quantum resilience, and fortified data structures.
175 lines • 7.08 kB
JavaScript
;
/***************************************************************************
* 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