@elsikora/cladi
Version:
Zero-dependency TypeScript DI toolkit with typed tokens and scoped lifecycles.
130 lines (127 loc) • 4.75 kB
JavaScript
;
/**
* Utility method to safely deep clone objects that may contain functions.
* Unlike structuredClone, this can handle functions by preserving their reference
* but cloning everything else.
* @param {T} source Object to clone
* @returns {T} A deep clone of the object
* @template T Type of object to clone
*/
function safeDeepClone(source) {
return cloneRecursive(source, new WeakMap());
}
/**
* Clones DataView and typed array instances.
* @param {ArrayBufferView} source Typed array or DataView to clone.
* @returns {ArrayBufferView} Cloned buffer view.
*/
function cloneArrayBufferView(source) {
if (source instanceof DataView) {
const clonedDataViewBuffer = source.buffer.slice(source.byteOffset, source.byteOffset + source.byteLength);
return new DataView(clonedDataViewBuffer);
}
const typedArrayConstructor = source.constructor;
return new typedArrayConstructor(source);
}
/**
* Recursively clones values while preserving circular references.
* @param {T} source Source value to clone.
* @param {WeakMap<object, unknown>} references Previously cloned references.
* @returns {T} Cloned value.
*/
function cloneRecursive(source, references) {
if (source == null || typeof source !== "object") {
return source;
}
const sourceObject = source;
const cachedClone = references.get(sourceObject);
if (cachedClone !== undefined) {
return cachedClone;
}
if (source instanceof Date) {
return new Date(source);
}
if (source instanceof RegExp) {
return new RegExp(source.source, source.flags);
}
if (source instanceof ArrayBuffer) {
const clonedArrayBuffer = Uint8Array.from(new Uint8Array(source)).buffer;
references.set(sourceObject, clonedArrayBuffer);
return clonedArrayBuffer;
}
if (isSharedArrayBuffer(source)) {
const clonedSharedArrayBuffer = new SharedArrayBuffer(source.byteLength);
new Uint8Array(clonedSharedArrayBuffer).set(new Uint8Array(source));
references.set(sourceObject, clonedSharedArrayBuffer);
return clonedSharedArrayBuffer;
}
const bufferApi = getBufferApi();
if (bufferApi?.isBuffer(source)) {
const clonedBuffer = bufferApi.from(source);
references.set(sourceObject, clonedBuffer);
return clonedBuffer;
}
if (ArrayBuffer.isView(source)) {
const clonedArrayBufferView = cloneArrayBufferView(source);
references.set(sourceObject, clonedArrayBufferView);
return clonedArrayBufferView;
}
if (Array.isArray(source)) {
const clonedArray = [];
references.set(sourceObject, clonedArray);
for (const item of source) {
clonedArray.push(cloneRecursive(item, references));
}
return clonedArray;
}
if (source instanceof Map) {
const clonedMap = new Map();
references.set(sourceObject, clonedMap);
for (const [key, value] of source.entries()) {
clonedMap.set(cloneRecursive(key, references), cloneRecursive(value, references));
}
return clonedMap;
}
if (source instanceof Set) {
const clonedSet = new Set();
references.set(sourceObject, clonedSet);
for (const value of source.values()) {
clonedSet.add(cloneRecursive(value, references));
}
return clonedSet;
}
const prototype = Object.getPrototypeOf(sourceObject);
const clonedObject = Object.create(prototype);
references.set(sourceObject, clonedObject);
for (const key of Reflect.ownKeys(sourceObject)) {
const descriptor = Object.getOwnPropertyDescriptor(sourceObject, key);
if (!descriptor) {
continue;
}
if ("value" in descriptor) {
const clonedValue = cloneRecursive(descriptor.value, references);
Object.defineProperty(clonedObject, key, { ...descriptor, value: clonedValue });
continue;
}
Object.defineProperty(clonedObject, key, descriptor);
}
return clonedObject;
}
/**
* Returns Buffer API when running in Node.js.
* @returns {TBufferApi | undefined} Node Buffer helpers or undefined.
*/
function getBufferApi() {
const globalWithBuffer = globalThis;
return globalWithBuffer.Buffer;
}
/**
* Checks whether value is a SharedArrayBuffer instance.
* @param {unknown} value Value to inspect.
* @returns {value is SharedArrayBuffer} True when value is SharedArrayBuffer.
*/
function isSharedArrayBuffer(value) {
return typeof SharedArrayBuffer !== "undefined" && value instanceof SharedArrayBuffer;
}
exports.safeDeepClone = safeDeepClone;
//# sourceMappingURL=safe-deep-clone.utility.js.map