@aedart/contracts
Version:
The Ion contracts package. Contains types, interfaces and unique identifiers
480 lines (467 loc) • 15 kB
TypeScript
/**
* @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 };