UNPKG

@aedart/contracts

Version:

The Ion contracts package. Contains types, interfaces and unique identifiers

480 lines (467 loc) 15 kB
/** * @aedart/contracts * * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>. */ import { Throwable } from '@aedart/contracts/support/exceptions'; import { ArrayMergeOptions } from '@aedart/contracts/support/arrays'; /** * Cloneable */ interface Cloneable { /** * Returns a clone (new instance) of this object * * @return {this} * * @throws {Error} */ clone(): this; } /** * Populatable * * Able to be populated (hydrated) with data */ interface Populatable { /** * Populate this component with data * * **Note**: _When no `data` is provided, then nothing is populated_ * * @param {any} [data] E.g. key-value pair (object), array, or any other kind * of data. * * @throws {TypeError} When unable to populate with given data. Or, if type of data is * not supported. */ populate(data?: any): this; } /** * Merge Exception * * To be thrown when two or more objects are unable to be merged. */ interface MergeException extends Throwable { } /** * Merge Source Info * * Contains information about what key-value must be merged from a target source object, * along with other meta information. */ interface MergeSourceInfo { /** * The resulting object (relative to object depth) * * @type {object} */ result: object; /** * The target property key in source object to * * @type {PropertyKey} */ key: PropertyKey; /** * Value of the property in source object * * @type {any} */ value: any; /** * The source object that holds the property key and value * * @type {object} */ source: object; /** * Source object's index (relative to object depth) * * @type {number} */ sourceIndex: number; /** * The current recursion depth */ depth: number; } /** * Callback to perform the merging of nested objects. * Invoking this callback results in the merge callback to * be repeated, for the given source objects. * * @type {function} */ type NextCallback = ( /** * The nested objects to be merged * * @type {object[]} */ sources: object[], /** * The merge options to be applied * * @type {Readonly<MergeOptions>} */ options: Readonly<MergeOptions>, /** * The next recursion depth number * * @type {number} */ nextDepth: number) => any; /** * Merge callback function * * The callback is responsible for resolving the value to be merged into the resulting object. * * **Note**: _[Skipped keys]{@link MergeOptions.skip} are NOT provided to the callback._ * * **Note**: _The callback is responsible for respecting the given [options]{@link MergeOptions}, * ([keys to be skipped]{@link MergeOptions.skip} and [depth]{@link MergeOptions.depth} excluded)_ */ type MergeCallback = ( /** * Source target information * * @type {MergeSourceInfo} */ target: MergeSourceInfo, /** * Callback to invoke for merging nested objects * * @type {function} */ next: NextCallback, /** * The merge options to be applied * * @type {Readonly<MergeOptions>} */ options: Readonly<MergeOptions>) => any; /** * A callback that determines if property key should be skipped * * If the callback returns `true`, then the given key will NOT be merged * from the given source object. * * @see {MergeOptions} */ type SkipKeyCallback = (key: PropertyKey, source: object, result: object) => boolean; /** * Merge Options */ interface MergeOptions { /** * 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?: number; /** * 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?: PropertyKey[] | SkipKeyCallback; /** * 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?: boolean; /** * 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?: boolean; /** * 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().using({ mergeArrays: true }).of(a, b); // { '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?: boolean; /** * Merge Options for arrays * * @type {ArrayMergeOptions} */ 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?: MergeCallback; } /** * Objects Merger * * Able to merge (deep merge) multiple source objects into a single new object. */ interface ObjectsMerger { /** * Use the following merge options or merge callback * * @param {MergeCallback | MergeOptions} [options] Merge callback or merge options. * * @return {this} * * @throws {MergeException} */ using(options?: MergeCallback | MergeOptions): this; /** * 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._ * * @template SourceA extends object * * @param {SourceA} a * * @returns {SourceA} * * @throws {MergeException} */ of<SourceA extends object>(a: SourceA): SourceA; /** * 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._ * * @template SourceA extends object * @template SourceB extends object * * @param {SourceA} a * @param {SourceB} b * * @returns {SourceA & SourceB} * * @throws {MergeException} */ of<SourceA extends object, SourceB extends object>(a: SourceA, b: SourceB): SourceA & SourceB; /** * 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._ * * @template SourceA extends object * @template SourceB extends object * @template SourceC extends object * * @param {SourceA} a * @param {SourceB} b * @param {SourceC} c * * @returns {SourceA & SourceB & SourceC} * * @throws {MergeException} */ of<SourceA extends object, SourceB extends object, SourceC extends object>(a: SourceA, b: SourceB, c: SourceC): SourceA & SourceB & SourceC; /** * 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._ * * @template SourceA extends object * @template SourceB extends object * @template SourceC extends object * @template SourceD extends object * * @param {SourceA} a * @param {SourceB} b * @param {SourceC} c * @param {SourceD} d * * @returns {SourceA & SourceB & SourceC & SourceD} * * @throws {MergeException} */ of<SourceA extends object, SourceB extends object, SourceC extends object, SourceD extends object>(a: SourceA, b: SourceB, c: SourceC, d: SourceD): SourceA & SourceB & SourceC & SourceD; /** * 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._ * * @template SourceA extends object * @template SourceB extends object * @template SourceC extends object * @template SourceD extends object * @template SourceE extends object * * @param {SourceA} a * @param {SourceB} b * @param {SourceC} c * @param {SourceD} d * @param {SourceE} e * * @returns {SourceA & SourceB & SourceC & SourceD & SourceE} * * @throws {MergeException} */ of<SourceA extends object, SourceB extends object, SourceC extends object, SourceD extends object, SourceE extends object>(a: SourceA, b: SourceB, c: SourceC, d: SourceD, e: SourceE): SourceA & SourceB & SourceC & SourceD & SourceE; /** * 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._ * * @template SourceA extends object * @template SourceB extends object * @template SourceC extends object * @template SourceD extends object * @template SourceE extends object * @template SourceF extends object * * @param {SourceA} a * @param {SourceB} b * @param {SourceC} c * @param {SourceD} d * @param {SourceE} e * @param {SourceF} f * * @returns {SourceA & SourceB & SourceC & SourceD & SourceE & SourceF} * * @throws {MergeException} */ of<SourceA extends object, SourceB extends object, SourceC extends object, SourceD extends object, SourceE extends object, SourceF extends object>(a: SourceA, b: SourceB, c: SourceC, d: SourceD, e: SourceE, f: SourceF): SourceA & SourceB & SourceC & SourceD & SourceE & SourceF; /** * 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 * * @returns {object} * * @throws {MergeException} */ of(...sources: object[]): object; } /** * Default maximum merge depth * * @type {number} */ declare const DEFAULT_MAX_MERGE_DEPTH: number; /** * Callback that returns properties to be selected from the source object. * The target is the object intended to have source properties merged or assigned * into. */ type SourceKeysCallback<SourceObj extends object = object, TargetObj extends object = object> = (source: SourceObj, target: TargetObj) => PropertyKey | PropertyKey[]; /** * Support Objects identifier * * @type {Symbol} */ declare const SUPPORT_OBJECTS: unique symbol; /** * Properties that are considered dangerous and should be avoided when merging * objects or assigning properties. * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf#description * @see https://cheatsheetseries.owasp.org/cheatsheets/Prototype_Pollution_Prevention_Cheat_Sheet.html * @see https://medium.com/@king.amit95/prototype-pollution-a-deeper-inspection-82a226796966 * * @type {PropertyKey[]} */ declare const DANGEROUS_PROPERTIES: PropertyKey[]; export { type Cloneable, DANGEROUS_PROPERTIES, DEFAULT_MAX_MERGE_DEPTH, type MergeCallback, type MergeException, type MergeOptions, type MergeSourceInfo, type NextCallback, type ObjectsMerger, type Populatable, SUPPORT_OBJECTS, type SkipKeyCallback, type SourceKeysCallback };