Cygni_backend/node_modules/@hapi/hoek/lib/clone.js

199 lines
5.0 KiB
JavaScript

'use strict';
const Reach = require('./reach');
const Types = require('./types');
const Utils = require('./utils');
const internals = {
needsProtoHack: new Set([Types.set, Types.map, Types.weakSet, Types.weakMap]),
structuredCloneExists: typeof structuredClone === 'function'
};
module.exports = internals.clone = function (obj, options = {}, _seen = null) {
if (typeof obj !== 'object' ||
obj === null) {
return obj;
}
let clone = internals.clone;
let seen = _seen;
if (options.shallow) {
if (options.shallow !== true) {
return internals.cloneWithShallow(obj, options);
}
clone = (value) => value;
}
else if (seen) {
const lookup = seen.get(obj);
if (lookup) {
return lookup;
}
}
else {
seen = new Map();
}
// Built-in object types
const baseProto = Types.getInternalProto(obj);
switch (baseProto) {
case Types.buffer:
return Buffer?.from(obj);
case Types.date:
return new Date(obj.getTime());
case Types.regex:
case Types.url:
return new baseProto.constructor(obj);
}
// Generic objects
const newObj = internals.base(obj, baseProto, options);
if (newObj === obj) {
return obj;
}
if (seen) {
seen.set(obj, newObj); // Set seen, since obj could recurse
}
if (baseProto === Types.set) {
for (const value of obj) {
newObj.add(clone(value, options, seen));
}
}
else if (baseProto === Types.map) {
for (const [key, value] of obj) {
newObj.set(key, clone(value, options, seen));
}
}
const keys = Utils.keys(obj, options);
for (const key of keys) {
if (key === '__proto__') {
continue;
}
if (baseProto === Types.array &&
key === 'length') {
newObj.length = obj.length;
continue;
}
// Can only be covered in node 21+
/* $lab:coverage:off$ */
if (internals.structuredCloneExists &&
baseProto === Types.error &&
key === 'stack') {
continue; // Already a part of the base object
}
/* $lab:coverage:on$ */
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
if (descriptor) {
if (descriptor.get ||
descriptor.set) {
Object.defineProperty(newObj, key, descriptor);
}
else if (descriptor.enumerable) {
newObj[key] = clone(obj[key], options, seen);
}
else {
Object.defineProperty(newObj, key, { enumerable: false, writable: true, configurable: true, value: clone(obj[key], options, seen) });
}
}
else {
Object.defineProperty(newObj, key, {
enumerable: true,
writable: true,
configurable: true,
value: clone(obj[key], options, seen)
});
}
}
return newObj;
};
internals.cloneWithShallow = function (source, options) {
const keys = options.shallow;
options = Object.assign({}, options);
options.shallow = false;
const seen = new Map();
for (const key of keys) {
const ref = Reach(source, key);
if (typeof ref === 'object' ||
typeof ref === 'function') {
seen.set(ref, ref);
}
}
return internals.clone(source, options, seen);
};
internals.base = function (obj, baseProto, options) {
if (options.prototype === false) { // Defaults to true
if (internals.needsProtoHack.has(baseProto)) {
return new baseProto.constructor();
}
return baseProto === Types.array ? [] : {};
}
const proto = Object.getPrototypeOf(obj);
if (proto &&
proto.isImmutable) {
return obj;
}
if (baseProto === Types.array) {
const newObj = [];
if (proto !== baseProto) {
Object.setPrototypeOf(newObj, proto);
}
return newObj;
}
// Can only be covered in node 21+
/* $lab:coverage:off$ */
else if (baseProto === Types.error && internals.structuredCloneExists &&
(proto === baseProto || Error.isPrototypeOf(proto.constructor))) { // Don't match Util.inherit() subclassed errors
const err = structuredClone(obj); // Needed to copy internal stack state
if (Object.getPrototypeOf(err) !== proto) {
Object.setPrototypeOf(err, proto); // Fix prototype
}
return err;
}
/* $lab:coverage:on$ */
if (internals.needsProtoHack.has(baseProto)) {
const newObj = new proto.constructor();
if (proto !== baseProto) {
Object.setPrototypeOf(newObj, proto);
}
return newObj;
}
return Object.create(proto);
};