UNPKG

@qntm-code/utils

Version:

A collection of useful utility functions with associated TypeScript types. All functions have been unit tested.

145 lines (144 loc) 4.8 kB
import { isPlainObject } from '../type-predicates/isPlainObject.js'; import { typeOf, ValueType } from '../type-predicates/typeOf.js'; /** * Recursively (deep) clones native types, like Object, Array, RegExp, Date, Map, Set, Symbol, Error as well as primitives. */ export function clone(value, instanceClone = false) { switch (typeOf(value)) { case ValueType.object: { return cloneObjectDeep(value, instanceClone); } case ValueType.map: { return cloneMapDeep(value, instanceClone); } case ValueType.set: { return cloneSetDeep(value, instanceClone); } case ValueType.array: case ValueType.int8array: case ValueType.uint8array: case ValueType.uint8clampedarray: case ValueType.int16array: case ValueType.uint16array: case ValueType.int32array: case ValueType.uint32array: case ValueType.float32array: case ValueType.float64array: case ValueType.bigint64array: case ValueType.biguint64array: { return cloneArrayDeep(value, instanceClone); } default: { return cloneShallow(value); } } } function cloneShallow(value) { switch (typeOf(value)) { case ValueType.buffer: { return cloneBuffer(value); } case ValueType.symbol: { return cloneSymbol(value); } case ValueType.error: { return Object.create(value); } case ValueType.date: { return new Date(value); } case ValueType.regexp: { return cloneRegExp(value); } case ValueType.moment: { return value.clone(); } } return value; } function cloneObjectDeep(value, instanceClone) { if (typeof instanceClone === 'function') { return instanceClone(value); } if (instanceClone || isPlainObject(value)) { const source = value; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const ctor = value.constructor; const cloned = (ctor === undefined ? Object.create(null) : new ctor()); // Only clone own enumerable properties (matches `isEqual` semantics) for (const key of Object.keys(source)) { cloned[key] = clone(source[key], instanceClone); } return cloned; } return value; } function cloneArrayDeep(value, instanceClone) { const length = value.length; // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access const cloned = new value.constructor(length); for (let i = 0; i < length; i++) { cloned[i] = clone(value[i], instanceClone); } return cloned; } function cloneMapDeep(value, instanceClone) { const cloned = new Map(); const nestedInstanceClone = instanceClone; for (const [key, item] of value.entries()) { const clonedKey = clone(key, nestedInstanceClone); const clonedValue = clone(item, nestedInstanceClone); cloned.set(clonedKey, clonedValue); } return cloned; } function cloneSetDeep(value, instanceClone) { const cloned = new Set(); const nestedInstanceClone = instanceClone; for (const item of value.values()) { cloned.add(clone(item, nestedInstanceClone)); } return cloned; } function cloneRegExp(value) { // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access const cloned = new value.constructor(value.source, value.flags); cloned.lastIndex = value.lastIndex; return cloned; } function cloneBuffer(value) { const length = value.length; // eslint-disable-next-line @typescript-eslint/no-unsafe-argument const buffer = Buffer.allocUnsafe ? Buffer.allocUnsafe(length) : Buffer.from(length); value.copy(buffer); return buffer; } function cloneSymbol(value) { // Symbols are primitives and cannot be truly cloned; create a new symbol with the same description. const description = value.description; // Preserve well-known symbols (they cannot be recreated) const wellKnownSymbolKeys = [ 'asyncIterator', 'hasInstance', 'isConcatSpreadable', 'iterator', 'match', 'matchAll', 'replace', 'search', 'species', 'split', 'toPrimitive', 'toStringTag', 'unscopables', 'dispose', 'asyncDispose', ]; for (const key of wellKnownSymbolKeys) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (Symbol[key] === value) { return value; } } return Symbol(description); }