347 lines
13 KiB
JavaScript
347 lines
13 KiB
JavaScript
(function (global, factory) {
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
|
|
typeof define === 'function' && define.amd ? define(['exports'], factory) :
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["fast-copy"] = {}));
|
|
})(this, (function (exports) { 'use strict';
|
|
|
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
const toStringFunction = Function.prototype.toString;
|
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
const toStringObject = Object.prototype.toString;
|
|
/**
|
|
* Get an empty version of the object with the same prototype it has.
|
|
*/
|
|
function getCleanClone(prototype) {
|
|
if (!prototype) {
|
|
return Object.create(null);
|
|
}
|
|
const Constructor = prototype.constructor;
|
|
if (Constructor === Object) {
|
|
return prototype === Object.prototype ? {} : Object.create(prototype);
|
|
}
|
|
if (Constructor && ~toStringFunction.call(Constructor).indexOf('[native code]')) {
|
|
try {
|
|
return new Constructor();
|
|
}
|
|
catch (_a) {
|
|
// Ignore
|
|
}
|
|
}
|
|
return Object.create(prototype);
|
|
}
|
|
/**
|
|
* Get the tag of the value passed, so that the correct copier can be used.
|
|
*/
|
|
function getTag(value) {
|
|
const stringTag = value[Symbol.toStringTag];
|
|
if (stringTag) {
|
|
return stringTag;
|
|
}
|
|
const type = toStringObject.call(value);
|
|
return type.substring(8, type.length - 1);
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
const { hasOwnProperty, propertyIsEnumerable } = Object.prototype;
|
|
function copyOwnDescriptor(original, clone, property, state) {
|
|
const ownDescriptor = Object.getOwnPropertyDescriptor(original, property) || {
|
|
configurable: true,
|
|
enumerable: true,
|
|
value: original[property],
|
|
writable: true,
|
|
};
|
|
const descriptor = ownDescriptor.get || ownDescriptor.set
|
|
? ownDescriptor
|
|
: {
|
|
configurable: ownDescriptor.configurable,
|
|
enumerable: ownDescriptor.enumerable,
|
|
value: state.copier(ownDescriptor.value, state),
|
|
writable: ownDescriptor.writable,
|
|
};
|
|
try {
|
|
Object.defineProperty(clone, property, descriptor);
|
|
}
|
|
catch (_a) {
|
|
// The above can fail on node in extreme edge cases, so fall back to the loose assignment.
|
|
clone[property] = descriptor.get ? descriptor.get() : descriptor.value;
|
|
}
|
|
}
|
|
/**
|
|
* Striclty copy all properties contained on the object.
|
|
*/
|
|
function copyOwnPropertiesStrict(value, clone, state) {
|
|
const names = Object.getOwnPropertyNames(value);
|
|
for (let index = 0; index < names.length; ++index) {
|
|
copyOwnDescriptor(value, clone, names[index], state);
|
|
}
|
|
const symbols = Object.getOwnPropertySymbols(value);
|
|
for (let index = 0; index < symbols.length; ++index) {
|
|
copyOwnDescriptor(value, clone, symbols[index], state);
|
|
}
|
|
return clone;
|
|
}
|
|
/**
|
|
* Deeply copy the indexed values in the array.
|
|
*/
|
|
function copyArrayLoose(array, state) {
|
|
const clone = new state.Constructor();
|
|
// set in the cache immediately to be able to reuse the object recursively
|
|
state.cache.set(array, clone);
|
|
for (let index = 0; index < array.length; ++index) {
|
|
clone[index] = state.copier(array[index], state);
|
|
}
|
|
return clone;
|
|
}
|
|
/**
|
|
* Deeply copy the indexed values in the array, as well as any custom properties.
|
|
*/
|
|
function copyArrayStrict(array, state) {
|
|
const clone = new state.Constructor();
|
|
// set in the cache immediately to be able to reuse the object recursively
|
|
state.cache.set(array, clone);
|
|
return copyOwnPropertiesStrict(array, clone, state);
|
|
}
|
|
/**
|
|
* Copy the contents of the ArrayBuffer.
|
|
*/
|
|
function copyArrayBuffer(arrayBuffer, _state) {
|
|
return arrayBuffer.slice(0);
|
|
}
|
|
/**
|
|
* Create a new Blob with the contents of the original.
|
|
*/
|
|
function copyBlob(blob, _state) {
|
|
return blob.slice(0, blob.size, blob.type);
|
|
}
|
|
/**
|
|
* Create a new DataView with the contents of the original.
|
|
*/
|
|
function copyDataView(dataView, state) {
|
|
return new state.Constructor(copyArrayBuffer(dataView.buffer));
|
|
}
|
|
/**
|
|
* Create a new Date based on the time of the original.
|
|
*/
|
|
function copyDate(date, state) {
|
|
return new state.Constructor(date.getTime());
|
|
}
|
|
/**
|
|
* Deeply copy the keys and values of the original.
|
|
*/
|
|
function copyMapLoose(map, state) {
|
|
const clone = new state.Constructor();
|
|
// set in the cache immediately to be able to reuse the object recursively
|
|
state.cache.set(map, clone);
|
|
map.forEach((value, key) => {
|
|
clone.set(key, state.copier(value, state));
|
|
});
|
|
return clone;
|
|
}
|
|
/**
|
|
* Deeply copy the keys and values of the original, as well as any custom properties.
|
|
*/
|
|
function copyMapStrict(map, state) {
|
|
return copyOwnPropertiesStrict(map, copyMapLoose(map, state), state);
|
|
}
|
|
/**
|
|
* Deeply copy the properties (keys and symbols) and values of the original.
|
|
*/
|
|
function copyObjectLoose(object, state) {
|
|
const clone = getCleanClone(state.prototype);
|
|
// set in the cache immediately to be able to reuse the object recursively
|
|
state.cache.set(object, clone);
|
|
for (const key in object) {
|
|
if (hasOwnProperty.call(object, key)) {
|
|
clone[key] = state.copier(object[key], state);
|
|
}
|
|
}
|
|
const symbols = Object.getOwnPropertySymbols(object);
|
|
for (let index = 0; index < symbols.length; ++index) {
|
|
const symbol = symbols[index];
|
|
if (propertyIsEnumerable.call(object, symbol)) {
|
|
clone[symbol] = state.copier(object[symbol], state);
|
|
}
|
|
}
|
|
return clone;
|
|
}
|
|
/**
|
|
* Deeply copy the properties (keys and symbols) and values of the original, as well
|
|
* as any hidden or non-enumerable properties.
|
|
*/
|
|
function copyObjectStrict(object, state) {
|
|
const clone = getCleanClone(state.prototype);
|
|
// set in the cache immediately to be able to reuse the object recursively
|
|
state.cache.set(object, clone);
|
|
return copyOwnPropertiesStrict(object, clone, state);
|
|
}
|
|
/**
|
|
* Create a new primitive wrapper from the value of the original.
|
|
*/
|
|
function copyPrimitiveWrapper(primitiveObject, state) {
|
|
return new state.Constructor(primitiveObject.valueOf());
|
|
}
|
|
/**
|
|
* Create a new RegExp based on the value and flags of the original.
|
|
*/
|
|
function copyRegExp(regExp, state) {
|
|
const clone = new state.Constructor(regExp.source, regExp.flags);
|
|
clone.lastIndex = regExp.lastIndex;
|
|
return clone;
|
|
}
|
|
/**
|
|
* Return the original value (an identity function).
|
|
*
|
|
* @note
|
|
* THis is used for objects that cannot be copied, such as WeakMap.
|
|
*/
|
|
function copySelf(value, _state) {
|
|
return value;
|
|
}
|
|
/**
|
|
* Deeply copy the values of the original.
|
|
*/
|
|
function copySetLoose(set, state) {
|
|
const clone = new state.Constructor();
|
|
// set in the cache immediately to be able to reuse the object recursively
|
|
state.cache.set(set, clone);
|
|
set.forEach((value) => {
|
|
clone.add(state.copier(value, state));
|
|
});
|
|
return clone;
|
|
}
|
|
/**
|
|
* Deeply copy the values of the original, as well as any custom properties.
|
|
*/
|
|
function copySetStrict(set, state) {
|
|
return copyOwnPropertiesStrict(set, copySetLoose(set, state), state);
|
|
}
|
|
|
|
function createDefaultCache() {
|
|
return new WeakMap();
|
|
}
|
|
function getOptions({ createCache: createCacheOverride, methods: methodsOverride, strict, }) {
|
|
const defaultMethods = {
|
|
array: strict ? copyArrayStrict : copyArrayLoose,
|
|
arrayBuffer: copyArrayBuffer,
|
|
asyncGenerator: copySelf,
|
|
blob: copyBlob,
|
|
dataView: copyDataView,
|
|
date: copyDate,
|
|
error: copySelf,
|
|
generator: copySelf,
|
|
map: strict ? copyMapStrict : copyMapLoose,
|
|
object: strict ? copyObjectStrict : copyObjectLoose,
|
|
regExp: copyRegExp,
|
|
set: strict ? copySetStrict : copySetLoose,
|
|
};
|
|
const methods = methodsOverride ? Object.assign(defaultMethods, methodsOverride) : defaultMethods;
|
|
const copiers = getTagSpecificCopiers(methods);
|
|
const createCache = createCacheOverride || createDefaultCache;
|
|
// Extra safety check to ensure that object and array copiers are always provided,
|
|
// avoiding runtime errors.
|
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
if (!copiers.Object || !copiers.Array) {
|
|
throw new Error('An object and array copier must be provided.');
|
|
}
|
|
return { createCache, copiers, methods, strict: Boolean(strict) };
|
|
}
|
|
/**
|
|
* Get the copiers used for each specific object tag.
|
|
*/
|
|
function getTagSpecificCopiers(methods) {
|
|
return {
|
|
Arguments: methods.object,
|
|
Array: methods.array,
|
|
ArrayBuffer: methods.arrayBuffer,
|
|
AsyncGenerator: methods.asyncGenerator,
|
|
Blob: methods.blob,
|
|
Boolean: copyPrimitiveWrapper,
|
|
DataView: methods.dataView,
|
|
Date: methods.date,
|
|
Error: methods.error,
|
|
Float32Array: methods.arrayBuffer,
|
|
Float64Array: methods.arrayBuffer,
|
|
Generator: methods.generator,
|
|
Int8Array: methods.arrayBuffer,
|
|
Int16Array: methods.arrayBuffer,
|
|
Int32Array: methods.arrayBuffer,
|
|
Map: methods.map,
|
|
Number: copyPrimitiveWrapper,
|
|
Object: methods.object,
|
|
Promise: copySelf,
|
|
RegExp: methods.regExp,
|
|
Set: methods.set,
|
|
String: copyPrimitiveWrapper,
|
|
WeakMap: copySelf,
|
|
WeakSet: copySelf,
|
|
Uint8Array: methods.arrayBuffer,
|
|
Uint8ClampedArray: methods.arrayBuffer,
|
|
Uint16Array: methods.arrayBuffer,
|
|
Uint32Array: methods.arrayBuffer,
|
|
Uint64Array: methods.arrayBuffer,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create a custom copier based on custom options for any of the following:
|
|
* - `createCache` method to create a cache for copied objects
|
|
* - custom copier `methods` for specific object types
|
|
* - `strict` mode to copy all properties with their descriptors
|
|
*/
|
|
function createCopier(options = {}) {
|
|
const { createCache, copiers } = getOptions(options);
|
|
const { Array: copyArray, Object: copyObject } = copiers;
|
|
function copier(value, state) {
|
|
state.prototype = state.Constructor = undefined;
|
|
if (!value || typeof value !== 'object') {
|
|
return value;
|
|
}
|
|
if (state.cache.has(value)) {
|
|
return state.cache.get(value);
|
|
}
|
|
state.prototype = Object.getPrototypeOf(value);
|
|
// Using logical AND for speed, since optional chaining transforms to
|
|
// a local variable usage.
|
|
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
|
state.Constructor = state.prototype && state.prototype.constructor;
|
|
// plain objects
|
|
if (!state.Constructor || state.Constructor === Object) {
|
|
return copyObject(value, state);
|
|
}
|
|
// arrays
|
|
if (Array.isArray(value)) {
|
|
return copyArray(value, state);
|
|
}
|
|
const tagSpecificCopier = copiers[getTag(value)];
|
|
if (tagSpecificCopier) {
|
|
return tagSpecificCopier(value, state);
|
|
}
|
|
return typeof value.then === 'function' ? value : copyObject(value, state);
|
|
}
|
|
return function copy(value) {
|
|
return copier(value, {
|
|
Constructor: undefined,
|
|
cache: createCache(),
|
|
copier,
|
|
prototype: undefined,
|
|
});
|
|
};
|
|
}
|
|
/**
|
|
* Copy an value deeply as much as possible, where strict recreation of object properties
|
|
* are maintained. All properties (including non-enumerable ones) are copied with their
|
|
* original property descriptors on both objects and arrays.
|
|
*/
|
|
const copyStrict = createCopier({ strict: true });
|
|
/**
|
|
* Copy an value deeply as much as possible.
|
|
*/
|
|
const copy = createCopier();
|
|
|
|
exports.copy = copy;
|
|
exports.copyStrict = copyStrict;
|
|
exports.createCopier = createCopier;
|
|
|
|
}));
|
|
//# sourceMappingURL=index.js.map
|