UNPKG

@aedart/support

Version:

The Ion support package

296 lines (281 loc) 8.61 kB
import { isArrayLike as isArrayLike$1 } from 'lodash-es'; import { TYPED_ARRAY_PROTOTYPE } from '@aedart/contracts/support/reflections'; import { populate } from '@aedart/support/objects'; import { getErrorMessage, configureCustomError } from '@aedart/support/exceptions'; import { ArrayMergeError as ArrayMergeError$1 } from '@aedart/support/arrays'; /** * @aedart/support * * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>. */ /** * Determine if array includes all given values * * @param {any[]} arr * @param {any[]} values * * @return {boolean} */ function includesAll(arr, /* eslint-disable-line @typescript-eslint/no-explicit-any */ values /* eslint-disable-line @typescript-eslint/no-explicit-any */) { return values.every((value) => arr.includes(value)); } /** * Determine if array includes any (_some_) of the given values * * @param {any[]} arr * @param {any[]} values * * @return {boolean} */ function includesAny(arr, /* eslint-disable-line @typescript-eslint/no-explicit-any */ values /* eslint-disable-line @typescript-eslint/no-explicit-any */) { return values.some((value) => arr.includes(value)); } /** * Determine if value is "array-like". * (Alias for Lodash's [`isArrayLike`]{@link import('lodash').isArrayLike}) method. * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array#array-like_objects * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Indexed_collections#working_with_array-like_objects * * @param {any} value * * @return {boolean} */ const isArrayLike = isArrayLike$1; /** * Determine if target object contains the well-known symbol {@link Symbol.isConcatSpreadable} * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable * * @param {object} target * * @return {boolean} */ function isConcatSpreadable(target) { return typeof target == 'object' && target !== null && Reflect.has(target, Symbol.isConcatSpreadable); } /** * Determine if given target is an instance of a `TypedArray` * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray * * @param {object} target * * @return {boolean} */ function isTypedArray(target) { return target instanceof TYPED_ARRAY_PROTOTYPE; } /** * Determine if value is "safe" array-like object * * **Note**: _In this context "safe" means that given object passes {@link isArrayLike}, * but value is:_ * - not a string. * - not instance of a {@link String} object. * - not a [Typed Array]{@link isTypedArray} object. * * @param {object} value * * @return {boolean} */ function isSafeArrayLike(value /* eslint-disable-line @typescript-eslint/no-explicit-any */) { return typeof value != 'string' && !(value instanceof String) && !isTypedArray(value) && isArrayLike(value); } /** * Default Array Merge callback * * @param {any} element * @param {number} index * @param {any[]} array * @param {Readonly<ArrayMergeOptions>} options * * @return {any} */ const defaultArrayMergeCallback = function (element, /* eslint-disable-line @typescript-eslint/no-explicit-any */ index, array, /* eslint-disable-line @typescript-eslint/no-explicit-any */ options) { // Transfer function, if requested by options. if (options.transferFunctions) { return element; } // Otherwise, create a structured clone of the element return structuredClone(element); }; /** * Default Array Merge Options */ class DefaultArrayMergeOptions { /** * Transfer functions * * **When `true`**: _functions are transferred into resulting array._ * * **When `false` (_default behaviour_)**: _The merge operation will fail when a function * is encountered (functions are not cloneable by default)._ * * @type {boolean} */ transferFunctions = false; /** * Merge callback to be applied * * **Note**: _When no callback is provided, then the merge function's default * callback is used._ */ callback; /** * Create new default merge options from given options * * @param {ArrayMergeCallback | ArrayMergeOptions} [options] */ constructor(options) { // Merge provided options, if any given if (options && typeof options == 'object') { populate(this, options); } // Resolve merge callback this.callback = (options && typeof options == 'function') ? options : defaultArrayMergeCallback; } /** * Create new default merge options from given options * * @param {ArrayMergeOptions} [options] * * @return {Readonly<DefaultArrayMergeOptions|ArrayMergeOptions>} */ static from(options) { const resolved = new this(options); return Object.freeze(resolved); } } /** * Array Merger */ class Merger { /** * Merge options to be applied * * @type {Readonly<DefaultArrayMergeOptions | ArrayMergeOptions>} * * @protected */ _options; /** * Create new Array Merger instance * * @param {ArrayMergeCallback | ArrayMergeOptions} [options] */ constructor(options) { // @ts-expect-error Need to init options, however they are resolved via "using". this._options = null; this.using(options); } /** * Use the following merge options * * @param {ArrayMergeCallback | ArrayMergeOptions} [options] * * @return {this} * * @throws {ArrayMergeException} */ using(options) { this._options = this.resolveOptions(options); return this; } /** * Merge options to be applied * * @type {Readonly<DefaultArrayMergeOptions | ArrayMergeOptions>} */ get options() { return this._options; } /** * Returns a merger of given source arrays * * @param {...any[]} sources * * @return {any[]} * * @throws {ArrayMergeException} */ of(...sources) { try { const options = this.options; const callback = options.callback.bind(this); // Array.concat only performs shallow copies of the array values, which might // fine in some situations. However, this version must ensure to perform a // deep copy of the values... return [].concat(...sources).map((element, index, array) => { return callback(element, index, array, options); }); } catch (e) { const reason = getErrorMessage(e); throw new ArrayMergeError$1('Unable to merge arrays: ' + reason, { cause: { previous: e, sources: sources } }); } } /** * Resolves options * * @param {ArrayMergeCallback | ArrayMergeOptions} options * * @return {Readonly<ArrayMergeOptions>} * * @protected */ resolveOptions(options) { return DefaultArrayMergeOptions.from(options); } } /** * Merge two or more arrays * * **Note**: _Method attempts to deep copy array values, via [structuredClone]{@link https://developer.mozilla.org/en-US/docs/Web/API/structuredClone}_ * * @see https://developer.mozilla.org/en-US/docs/Web/API/structuredClone * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable * * @param {...any[]} sources * * @return {ArrayMerger|any[]} * * @throws {ArrayMergeError} If unable to merge arrays, e.g. if a value cannot be cloned via `structuredClone()` */ function merge(...sources /* eslint-disable-line @typescript-eslint/no-explicit-any */) { const merger = new Merger(); if (sources.length == 0) { return merger; } return merger.of(...sources); } /** * Array Merge Error * * To be thrown when two or more arrays are unable to be merged. */ class ArrayMergeError extends Error { /** * Create a new Array Merge Error instance * * @param {string} [message] * @param {ErrorOptions} [options] */ constructor(message, options) { super(message, options); configureCustomError(this); } } export { ArrayMergeError, DefaultArrayMergeOptions, Merger, defaultArrayMergeCallback, includesAll, includesAny, isArrayLike, isConcatSpreadable, isSafeArrayLike, isTypedArray, merge };