UNPKG

@aedart/support

Version:

The Ion support package

887 lines (862 loc) 32.1 kB
import { unset, get as get$1, hasIn, set as set$1 } from 'lodash-es'; import { hasMethod, isWeakKind, isKeyUnsafe, isKeySafe } from '@aedart/support/reflections'; import { isset as isset$1, descTag } from '@aedart/support/misc'; import { DEFAULT_MAX_MERGE_DEPTH } from '@aedart/contracts/support/objects'; import { configureCustomError, getErrorMessage } from '@aedart/support/exceptions'; import { isConcatSpreadable, isSafeArrayLike, merge as merge$1 } from '@aedart/support/arrays'; import { MergeError as MergeError$1, canCloneUsingStructuredClone as canCloneUsingStructuredClone$1, populate as populate$1, isCloneable as isCloneable$1 } from '@aedart/support/objects'; import { TYPED_ARRAY_PROTOTYPE } from '@aedart/contracts/support/reflections'; /** * @aedart/support * * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>. */ /** * Remove value in object at given path * (Alias for Lodash' [unset]{@link import('lodash').unset}) method * * @type {(object: any, path: import('@aedart/contracts/support').Key) => boolean} */ const forget = unset; /** * Remove all values in object that match given paths * * @template T * * @param {T} object Target object * @param {...Key} paths Property path(s) */ function forgetAll(object, ...paths) { for (const path of paths) { forget(object, path); } } /** * @typedef {import('lodash').PropertyPath} PropertyPath * @typedef {import('lodash').NumericDictionary} NumericDictionary * @typedef {import('lodash').GetFieldType} GetFieldType */ /** * Get value from object that matches given path * (Alias for Lodash' [get]{@link import('lodash').get}) method * * @type {{<TObject extends object, TKey extends keyof TObject>(object: TObject, path: ([TKey] | TKey)): TObject[TKey], <TObject extends object, TKey extends keyof TObject>(object: (TObject | null | undefined), path: ([TKey] | TKey)): (TObject[TKey] | undefined), <TObject extends object, TKey extends keyof TObject, TDefault>(object: (TObject | null | undefined), path: ([TKey] | TKey), defaultValue: TDefault): (Exclude<TObject[TKey], undefined> | TDefault), <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1]>(object: TObject, path: [TKey1, TKey2]): TObject[TKey1][TKey2], <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1]>(object: (TObject | null | undefined), path: [TKey1, TKey2]): (TObject[TKey1][TKey2] | undefined), <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TDefault>(object: (TObject | null | undefined), path: [TKey1, TKey2], defaultValue: TDefault): (Exclude<TObject[TKey1][TKey2], undefined> | TDefault), <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2]>(object: TObject, path: [TKey1, TKey2, TKey3]): TObject[TKey1][TKey2][TKey3], <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2]>(object: (TObject | null | undefined), path: [TKey1, TKey2, TKey3]): (TObject[TKey1][TKey2][TKey3] | undefined), <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2], TDefault>(object: (TObject | null | undefined), path: [TKey1, TKey2, TKey3], defaultValue: TDefault): (Exclude<TObject[TKey1][TKey2][TKey3], undefined> | TDefault), <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2], TKey4 extends keyof TObject[TKey1][TKey2][TKey3]>(object: TObject, path: [TKey1, TKey2, TKey3, TKey4]): TObject[TKey1][TKey2][TKey3][TKey4], <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2], TKey4 extends keyof TObject[TKey1][TKey2][TKey3]>(object: (TObject | null | undefined), path: [TKey1, TKey2, TKey3, TKey4]): (TObject[TKey1][TKey2][TKey3][TKey4] | undefined), <TObject extends object, TKey1 extends keyof TObject, TKey2 extends keyof TObject[TKey1], TKey3 extends keyof TObject[TKey1][TKey2], TKey4 extends keyof TObject[TKey1][TKey2][TKey3], TDefault>(object: (TObject | null | undefined), path: [TKey1, TKey2, TKey3, TKey4], defaultValue: TDefault): (Exclude<TObject[TKey1][TKey2][TKey3][TKey4], undefined> | TDefault), <T>(object: NumericDictionary<T>, path: number): T, <T>(object: (NumericDictionary<T> | null | undefined), path: number): (T | undefined), <T, TDefault>(object: (NumericDictionary<T> | null | undefined), path: number, defaultValue: TDefault): (T | TDefault), <TDefault>(object: (null | undefined), path: PropertyPath, defaultValue: TDefault): TDefault, (object: (null | undefined), path: PropertyPath): undefined, <TObject, TPath extends string>(data: TObject, path: TPath): string extends TPath ? any : GetFieldType<TObject, TPath>, <TObject, TPath extends string, TDefault=GetFieldType<TObject, TPath>>(data: TObject, path: TPath, defaultValue: TDefault): (Exclude<GetFieldType<TObject, TPath>, null | undefined> | TDefault), (object: any, path: PropertyPath, defaultValue?: any): any}} */ const get = get$1; /** * Determine if path is a property of given object * * (Alias for Lodash' [hasIn]{@link import('lodash').hasIn}) method * * @type {<T>(object: T, path: import('@aedart/contracts/support').Key) => boolean} */ const has = hasIn; /** * Determine if all paths are properties of given object * @template T * * @param {T} object Target object * @param {...Key} paths Property path(s) * * @returns {boolean} */ function hasAll(object, ...paths) { if (object === undefined || paths.length === 0) { return false; } for (const path of paths) { if (!has(object, path)) { return false; } } return true; } /** * Determine if any paths are properties of given object * @template T * * @param {T} object Target object * @param {...Key} paths Property path(s) * * @returns {boolean} */ function hasAny(object, ...paths) { for (const path of paths) { if (has(object, path)) { return true; } } return false; } /** * Object ID * * Utility that is able to return a numeric ID for objects. * * Source is heavily inspired by Nicolas Gehlert's blog post: * "Get object reference IDs in JavaScript/TypeScript" (September 28, 2022) * @see https://developapa.com/object-ids/ * @see https://github.com/ngehlert/developapa/blob/master/content/blog/object-ids/index.md */ class ObjectId { /** * Internal counter * * @type {number} * * @protected * @static */ static _count = 0; /** * Weak Map of objects and their associated id * * @type {WeakMap<object, number>} * * @protected * @readonly */ static _map = new WeakMap(); /** * Returns a unique ID for target object. * * If no ID exists for the given object, then a new ID is * generated and returned. Subsequent calls to this method using * the same object will return the same ID. * * @param {object} target * @returns {number} */ static get(target) { const id = ObjectId._map.get(target); if (id !== undefined) { return id; } ObjectId._count += 1; ObjectId._map.set(target, ObjectId._count); return ObjectId._count; } /** * Determine if a unique ID exists for target object * * @param {object} target * * @returns {boolean} */ static has(target) { return ObjectId._map.has(target); } } /** * Alias for {@link ObjectId.has} */ const hasUniqueId = ObjectId.has; /** * Determine if target object is cloneable. * * **Note**: _Method assumes that target is cloneable if it implements the * [Cloneable]{@link import('@aedart/constracts/support/objects').Cloneable} interface._ * * @param {object} target * * @return {boolean} */ function isCloneable(target) { return hasMethod(target, 'clone'); } /** * Determine if target is populatable * * **Note**: _Method assumes that target is populatable if it implements the * [Populatable]{@link import('@aedart/constracts/support/objects').Populatable} interface._ * * @param {object} target * * @return {boolean} */ function isPopulatable(target) { return hasMethod(target, 'populate'); } /** * Determine if properties at given paths are declared, and their values are not undefined or null * * @template T * * @param {T} object * @param {...Key} paths * * @returns {boolean} */ function isset(object, ...paths) { if (object === undefined || paths.length === 0) { return false; } for (const path of paths) { if (!isset$1(get(object, path))) { return false; } } return true; } /** * Merge Error * * @see MergeException */ class MergeError extends Error { /** * Create a new Merge Error instance * * @param {string} [message] * @param {ErrorOptions} [options] */ constructor(message, options) { super(message, options); configureCustomError(this); } } /** * The default merge callback * * @type {MergeCallback} */ const defaultMergeCallback = function (target, next, options) { const { result, key, value, source, sourceIndex, depth } = target; // Determine if result contains key const hasExisting = Reflect.has(result, key); // The existing value, if any // @ts-expect-error Existing value can be of any type here... const existingValue /* eslint-disable-line @typescript-eslint/no-explicit-any */ = result[key]; // Determine the type and resolve value based on it... const type = typeof value; switch (type) { // -------------------------------------------------------------------------------------------------------- // // Primitives // @see https://developer.mozilla.org/en-US/docs/Glossary/Primitive case 'undefined': // Do not overwrite existing value with `undefined`, if options do not allow it... if (value === undefined && options.overwriteWithUndefined === false && hasExisting && existingValue !== undefined) { return existingValue; } return value; case 'string': case 'number': case 'bigint': case 'boolean': case 'symbol': return value; // -------------------------------------------------------------------------------------------------------- // // Functions case 'function': return value; // -------------------------------------------------------------------------------------------------------- // // Null, Arrays and Objects case 'object': // Null (primitive) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if (value === null) { return value; } // Arrays - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const isArray = Array.isArray(value); /* eslint-disable-line no-case-declarations */ if (isArray || isConcatSpreadable(value) || isSafeArrayLike(value)) { // If required to merge with existing value, if one exists... if (options.mergeArrays === true && hasExisting && (isArray || Array.isArray(existingValue))) { // If either existing or new value is of the type array, merge values into // a new array. return merge$1() .using(options.arrayMergeOptions) .of(existingValue, value); } else if (isArray) { // When not requested merged, just overwrite existing value with a new array, // if new value is an array. return merge$1() .using(options.arrayMergeOptions) .of(value); } // For concat spreadable objects or array-like objects, the "basic object" merge logic // will deal with them. } // Objects (of "native" kind) - - - - - - - - - - - - - - - - - - - - - - - - // Clone the object of a "native" kind value, if supported. if (canCloneUsingStructuredClone$1(value)) { return structuredClone(value); } // Objects (WeakRef, WeakMap and WeakSet) - - - - - - - - - - - - - - - - - - // "Weak Reference" kind of objects cannot, nor should they, be cloned. if (isWeakKind(value)) { return value; } // Objects (basic)- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Merge with existing, if existing value is not null... if (hasExisting && typeof existingValue == 'object' && existingValue !== null && !(Array.isArray(existingValue))) { return next([existingValue, value], options, depth + 1); } // Otherwise, create a new object and merge it. return next([Object.create(null), value], options, depth + 1); // -------------------------------------------------------------------------------------------------------- // // If for some reason this point is reached, it means that we are unable to merge "something". default: throw new MergeError$1(`Unable to merge value of type ${type} (${descTag(value)}) at source index ${sourceIndex}`, { cause: { key: key, value: value, source: source, sourceIndex: sourceIndex, depth: depth, options: options } }); } }; /** * Returns a new skip callback for given property keys * * @param {PropertyKey[]} keys * * @return {SkipKeyCallback} */ function makeSkipCallback(keys) { return (key, source, result /* eslint-disable-line @typescript-eslint/no-unused-vars */) => { return keys.includes(key) && Reflect.has(source, key); }; } /** * Default Merge Options * * @see MergeOptions */ class DefaultMergeOptions { /** * The maximum merge depth * * **Note**: _Value must be greater than or equal zero._ * * **Note**: _Defaults to [DEFAULT_MAX_MERGE_DEPTH]{@link import('@aedart/contracts/support/objects').DEFAULT_MAX_MERGE_DEPTH} * when not specified._ * * @type {number} */ depth = DEFAULT_MAX_MERGE_DEPTH; /** * Property Keys that must not be merged. * * **Note**: [DANGEROUS_PROPERTIES]{@link import('@aedart/contracts/support/objects').DANGEROUS_PROPERTIES} * are always skipped, regardless of specified keys._ * * **Callback**: _A callback can be specified to determine if a given key, * in a source object should be skipped._ * * **Example:** * ```js * const a = { 'foo': true }; * const b = { 'bar': true, 'zar': true }; * * merge().using({ skip: [ 'zar' ] }).of(a, b); // { 'foo': true, 'bar': true } * * merge().using({ skip: (key, source) => { * return key === 'bar' && Reflect.has(source, key); * } }).of(a, b); // { 'foo': true, 'zar': true } * ``` * * @type {PropertyKey[] | SkipKeyCallback} */ skip = []; /** * Flag, overwrite property values with `undefined`. * * **When `true` (_default behaviour_)**: _If an existing property value is not `undefined`, it will be overwritten * with new value, even if the new value is `undefined`._ * * **When `false`**: _If an existing property value is not `undefined`, it will NOT be overwritten * with new value, if the new value is `undefined`._ * * **Example:** * ```js * const a = { 'foo': true }; * const b = { 'foo': undefined }; * * merge(a, b); // { 'foo': undefined } * * merge().using({ overwriteWithUndefined: false }).of(a, b) // { 'foo': true } * ``` * * @type {boolean} */ overwriteWithUndefined = true; /** * Flag, if source object is [`Cloneable`]{@link import('@aedart/contracts/support/objects').Cloneable}, then the * resulting object from the `clone()` method is used. * * **When `true` (_default behaviour_)**: _If source object is cloneable then the resulting object from `clone()` * method is used. Its properties are then iterated by the merge function._ * * **When `false`**: _Cloneable objects are treated like any other objects, the `clone()` method is ignored._ * * **Example:** * ```js * const a = { 'foo': { 'name': 'John Doe' } }; * const b = { 'foo': { * 'name': 'Jane Doe', * clone() { * return { * 'name': 'Rick Doe', * 'age': 26 * } * } * } }; * * merge(a, b); // { 'foo': { 'name': 'Rick Doe', 'age': 26 } } * * merge().using({ useCloneable: false }).of(a, b); // { 'foo': { 'name': 'Jane Doe', clone() {...} } } * ``` * * @see [`Cloneable`]{@link import('@aedart/contracts/support/objects').Cloneable} * * @type {boolean} */ useCloneable = true; /** * Flag, whether to merge array, array-like, and [concat spreadable]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable} * properties or not. * * **When `true`**: _existing property is merged with new property value._ * * **When `false` (_default behaviour_)**: _existing property is overwritten with new property value_ * * **Example:** * ```js * const a = { 'foo': [ 1, 2, 3 ] }; * const b = { 'foo': [ 4, 5, 6 ] }; * * merge([ a, b ]); // { 'foo': [ 4, 5, 6 ] } * merge([ a, b ], { mergeArrays: true }); // { 'foo': [ 1, 2, 3, 4, 5, 6 ] } * ``` * * **Note**: _`String()` (object) and [Typed Arrays]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray} * are not merged, even though they are considered to be "array-like" (they offer a `length` property). * You need to manually handle these, via a custom [callback]{@link MergeCallback}, if such value types must be merged._ * * @see [merge (array)]{@link import('@aedart/support/arrays').merge} * * @type {boolean} */ mergeArrays = false; /** * Merge Options for arrays * * @type {ArrayMergeOptions} */ arrayMergeOptions = {}; /** * The merge callback that must be applied * * **Note**: _When no callback is provided, then the merge function's default * callback is used._ * * @type {MergeCallback} */ callback; /** * Creates a new Merge Options instance * * @param {MergeCallback | MergeOptions} [options] */ constructor(options) { // Merge provided options, if any given if (options && typeof options == 'object') { populate$1(this, options); } // Abort in case of invalid maximum depth - other options can also be asserted, but they are less important. // The Browser / Node.js Engine will throw an error in case that they maximum recursion level is reached! if (this.depth < 0) { throw new MergeError('Invalid maximum "depth" merge option value', { cause: { options: this } }); } // Resolve merge callback this.callback = (options && typeof options == 'function') ? options : defaultMergeCallback; // Resolve skip callback if (typeof this.skip != 'function') { this.skip = makeSkipCallback(this.skip); } } /** * Create new default merge options from given options * * @param {MergeCallback | MergeOptions} [options] * * @return {DefaultMergeOptions} * * @throws {MergeError} */ static from(options) { const resoled = new this(options); return Object.freeze(resoled); } } /** * Objects Merger * * @see ObjectsMerger */ class Merger { /** * The merge options to be applied * * @type {Readonly<DefaultMergeOptions | MergeOptions>} * * @protected */ _options; /** * Callback to perform the merging of nested objects. * * @type {NextCallback} * * @protected * @readonly */ _next; /** * Create a new objects merger instance * * @param {MergeCallback | MergeOptions} [options] * * @throws {MergeError} */ constructor(options) { // @ts-expect-error Need to init options, however they are resolved via "using". this._options = null; this._next = this.merge; this.using(options); } /** * Returns the merge options that are to be applied * * @return {Readonly<DefaultMergeOptions | MergeOptions>} */ get options() { return this._options; } /** * Returns the "next" callback that performs merging of nested objects. */ get nextCallback() { return this._next; } /** * Use the following merge options or merge callback * * @param {MergeCallback | MergeOptions} [options] * * @return {this} * * @throws {MergeError} */ using(options) { this._options = this.resolveOptions(options); return this; } of(...sources) { try { return this.nextCallback(sources, this.options, 0); } catch (error) { if (error instanceof MergeError) { // @ts-expect-error Error SHOULD have a cause object set - support by all browsers now! error.cause.sources = sources; // @ts-expect-error Error SHOULD have a cause object set - support by all browsers now! error.cause.options = this.options; throw error; } const reason = getErrorMessage(error); throw new MergeError(`Unable to merge objects: ${reason}`, { cause: { previous: error, sources: sources, options: this.options } }); } } /** * Merge given source objects into a single object * * @param {object[]} sources * @param {Readonly<MergeOptions>} options * @param {number} [depth] Current recursion depth * * @return {object} * * @throws {MergeError} */ merge(sources, options, depth = 0) { // Abort if maximum depth has been reached this.assertMaxDepthNotExceeded(depth, sources, options); // Resolve callbacks to apply const nextCallback = this.nextCallback.bind(this); const skipCallback = options.skip.bind(this); const mergeCallback = options.callback.bind(this); // Loop through the sources and merge them into a single object return sources.reduce((result, source, index) => { // Abort if source is invalid... this.assertSourceObject(source, index, depth); // If allowed and source implements "Cloneable" interface, favour "clone()" method's resulting object. const resolved = this.resolveSourceObject(source, options); // Loop through all the source's properties, including symbols const keys = Reflect.ownKeys(resolved); for (const key of keys) { // Skip key if needed ... if (isKeyUnsafe(key) || skipCallback(key, resolved, result)) { continue; } // Resolve the value via callback and set it in resulting object. // @ts-expect-error Safe to set the value in result object! result[key] = mergeCallback({ result, key, // @ts-expect-error Value can be of any type value: resolved[key], source: resolved, sourceIndex: index, depth: depth, }, nextCallback, options); } return result; }, Object.create(null)); } /** * Resolves the source object * * @param {object} source * @param {MergeOptions} options * * @protected * * @return {object} */ resolveSourceObject(source, options) { let output = source; if (options.useCloneable && isCloneable$1(source)) { output = this.cloneSource(source); } return output; } /** * Invokes the "clone()" method on given cloneable object * * @param {Cloneable} source * * @protected * * @return {object} * * @return {MergeError} If unable to */ cloneSource(source) { const clone = source.clone(); // Abort if resulting value from "clone()" is not a valid value... if (!clone || typeof clone != 'object' || Array.isArray(clone)) { throw new MergeError(`Expected clone() method to return object for source, ${descTag(clone)} was returned`, { cause: { source: source, clone: clone, } }); } return clone; } /** * Resolves provided merge options * * @param {MergeCallback | MergeOptions} [options] * * @protected * * @return {Readonly<DefaultMergeOptions | MergeOptions>} * * @throws {MergeError} */ resolveOptions(options) { return DefaultMergeOptions.from(options); } /** * Assert that current recursion depth has now exceeded the maximum depth * * @param {number} currentDepth * @param {object[]} sources * @param {MergeOptions} [options] Defaults to this Merger's options when none given * * @protected * * @throws {MergeError} */ assertMaxDepthNotExceeded(currentDepth, sources, options) { const max = options?.depth || this.options.depth; if (max && currentDepth > max) { throw new MergeError(`Maximum merge depth (${max}) has been exceeded`, { cause: { source: sources, depth: currentDepth } }); } } /** * Assert given source is a valid object * * @param {unknown} source * @param {number} index * @param {number} currentDepth * * @protected * * @throws {MergeError} */ assertSourceObject(source, index, currentDepth) { if (!source || typeof source != 'object' || Array.isArray(source)) { throw new MergeError(`Unable to merge source of invalid type "${descTag(source)}" (source index: ${index})`, { cause: { source: source, index: index, depth: currentDepth } }); } } } /** * Returns a merger of given source objects * * **Note**: _This method is responsible for returning [deep copy]{@link https://developer.mozilla.org/en-US/docs/Glossary/Deep_copy} * of all given sources._ * * @param {....object} [sources] * * @return {ObjectsMerger|object} * * @throws {MergeError} */ function merge(...sources) { const merger = new Merger(); if (sources.length == 0) { return merger; } return merger.of(...sources); } /** * Populate target object with the properties from source object * * **Warning**: _This method performs a shallow copy of properties in source object!_ * * **Warning**: _`target` object is mutated!_ * * **Note**: _Properties that are [unsafe]{@link import('@aedart/support/reflections').isKeyUnsafe} are always disregarded!_ * * @template TargetObj extends object = object * @template SourceObj extends object = object * * @param {object} target * @param {object} source * @param {PropertyKey | PropertyKey[] | SourceKeysCallback} [keys='*'] Keys to select and copy from `source` object. * If wildcard (`*`) given, then all properties from the `source` * are selected. If a callback is given, then that callback must return * key or keys to select from `source`. * @param {boolean} [safe=true] When `true`, properties must exist in target (_must be defined in target_), * before they are shallow copied. * * @returns {object} The populated target * * @throws {TypeError} If a key does not exist in `target` (_when `safe = true`_). * Or, if key does not exist in `source` (_regardless of `safe` flag_). */ function populate(target, source, keys = '*', safe = true) { if (keys === '*') { keys = Reflect.ownKeys(source); } else if (typeof keys == 'function') { keys = keys(source, target); } if (!Array.isArray(keys)) { keys = [keys]; } // Always remove dangerous keys, regardless of "safe" flag. keys = keys.filter((key) => isKeySafe(key)); // Populate... for (const key of keys) { // If "safe" is enabled, then only keys that are already defined in target are allowed. if (safe && !Reflect.has(target, key)) { throw new TypeError(`Key "${key.toString()}" does not exist in target object`); } // However, fail if property does not exist in source, regardless of "safe" flag. if (!Reflect.has(source, key)) { throw new TypeError(`Key "${key.toString()}" does not exist in source object`); } // @ts-expect-error At this point, all should be safe... target[key] = source[key]; } return target; } /** * @typedef {import('@aedart/contracts/support').Key} Key */ /** * Set value in object at given path * (Alias for Lodash' [set]{@link import('lodash').set}) method * * @type {{<T extends object>(object: T, path: Key, value: any): T, <TResult>(object: object, path: Key, value: any): TResult}} */ const set = set$1; /** * Alias for {@link ObjectId.get} */ const uniqueId = ObjectId.get; /** * Determine if an object value can be cloned via `structuredClone()` * * @see https://developer.mozilla.org/en-US/docs/Web/API/structuredClone * * @internal * * @param {object} value * * @return {boolean} */ function canCloneUsingStructuredClone(value) { const supported = [ // Array, // Handled by array, with evt. array value merges ArrayBuffer, Boolean, DataView, Date, Error, Map, Number, // Object, // Handled by "basic" objects merging... // (Primitive Types), // Also handled elsewhere... RegExp, Set, String, TYPED_ARRAY_PROTOTYPE ]; for (const constructor of supported) { if (value instanceof constructor) { return true; } } return false; } export { DefaultMergeOptions, MergeError, Merger, ObjectId, canCloneUsingStructuredClone, defaultMergeCallback, forget, forgetAll, get, has, hasAll, hasAny, hasUniqueId, isCloneable, isPopulatable, isset, makeSkipCallback, merge, populate, set, uniqueId };