UNPKG

@aedart/contracts

Version:

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

775 lines (752 loc) 24.1 kB
/** * @aedart/contracts * * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>. */ import { ConstructorLike } from '@aedart/contracts'; import { Throwable } from '@aedart/contracts/support/exceptions'; import { InjectionException as InjectionException$1 } from '@aedart/contracts/support/concerns'; /** * Concern * * A "concern" is component that can be injected into a target class (concern owner). * The concern itself is NOT responsible for performing the actual injection logic. * * @interface */ interface Concern { /** * The owner class instance this concern is injected into, * or `this` concern instance if no owner was set. * * @readonly * * @type {object} */ get concernOwner(): object; } /** * Concern Constructor * * @template T extends Concern * * @see Concern */ interface ConcernConstructor<T extends Concern = Concern> { /** * Creates a new concern instance * * @param {object} [owner] The owner class instance this concern is injected into. * Defaults to `this` concern instance if none given. * * @throws {Error} When concern is unable to preform initialisation, e.g. caused * by the owner or other circumstances. */ new (owner?: object): T; /** * Returns list of property keys that this concern class offers. * * **Note**: _Only properties and methods returned by this method can be aliased * into a target class._ * * @static * * @return {PropertyKey[]} */ [PROVIDES](): PropertyKey[]; } /** * An alias for a property or method in a {@link Concern} class */ type Alias = PropertyKey; /** * Key-value pair where the key corresponds to a property or method inside the {@link Concern} instance, * and value is an "alias" for that property or method. * * In this context an "alias" means a property or method that is added onto a target * class' prototype and acts as a proxy to the original property or method inside the * concern class instance. */ type Aliases<T extends Concern = Concern> = { [key in keyof T]: Alias; } | { [key: PropertyKey]: Alias; }; /** * Shorthand Concern Injection Configuration * * @see [Configuration]{@link import('@aedart/contracts/support/concerns').Configuration} */ type ShorthandConfiguration<T extends Concern = Concern> = [ ConcernConstructor<T> ] | [ConcernConstructor<T>, Aliases<T>] | [ConcernConstructor<T>, boolean]; /** * Concern Injection Configuration * * Defines the Concern class that must be injected into a target class, * along with other aspects of the injection, such as what aliases to use * and what properties or methods should not be aliased. * * @template T extends Concern = Concern * * @see {Concern} * @see {Aliases} */ interface Configuration<T extends Concern = Concern> { /** * The Concern Class that must be injected into a target class * * @template T extends Concern = Concern * * @type {ConcernConstructor<T>} */ concern: ConcernConstructor<T>; /** * Aliases for Concern's properties or methods. * * **Note**: _Defaults to same property and method names as "aliases", as those defined by * a concern class' [PROVIDES]{@link import('@aedart/contracts/support/concerns').PROVIDES}, * if this property is empty or `undefined`._ * * @template T extends Concern = Concern * * @type {Aliases<T>|undefined} */ aliases?: Aliases<T>; /** * Flag that indicates whether an "injector" is allowed to create * "aliases" (proxy) properties and methods into a target class's prototype. * * **Note**: _Defaults to `true` if this property is `undefined`._ * * **Note**: _If set to `false`, then {@link aliases} are ignored._ * * @type {boolean} */ allowAliases?: boolean; } /** * Concerns Owner * * An owner is an object, e.g. instance of a class, that offers a concerns {@link Container}. */ interface Owner { /** * The concerns container for this class * * @readonly * * @type {Container} */ readonly [CONCERNS]: Container; } /** * Concerns Container */ interface Container { /** * The amount of concerns in this container * * @readonly * * @type {number} */ readonly size: number; /** * Get the concerns container owner * * @type {Owner} */ get owner(): Owner; /** * Determine if concern class is registered in this container * * @param {ConcernConstructor} concern * * @return {boolean} */ has(concern: ConcernConstructor): boolean; /** * Retrieve concern instance for given concern class * * **Note**: _If concern class is registered in this container, but not yet * booted, then this method will boot it via the {@link boot} method, and return * the resulting instance._ * * @template T extends {@link Concern} * * @param {ConcernConstructor<T>} concern * * @return {T} The booted instance of the concern class. If concern class was * previously booted, then that instance is returned. * * @throws {ConcernException} */ get<T extends Concern>(concern: ConcernConstructor<T>): T; /** * Determine if concern class has been booted * * @param {ConcernConstructor} concern * * @return {boolean} */ hasBooted(concern: ConcernConstructor): boolean; /** * Boot concern class * * @template T extends {@link Concern} * * @param {ConcernConstructor<T>} concern * * @return {T} New concern instance * * @throws {NotRegisteredException} If concern class is not registered in this container * @throws {BootException} If concern is unable to be booted, e.g. if already booted */ boot<T extends Concern>(concern: ConcernConstructor<T>): T; /** * Boots all registered concern classes * * @throws {ConcernException} */ bootAll(): void; /** * Determine if this container is empty * * @return {boolean} */ isEmpty(): boolean; /** * Opposite of {@link isEmpty} * * @return {boolean} */ isNotEmpty(): boolean; /** * Returns all concern classes * * @return {IterableIterator<ConcernConstructor>} */ all(): IterableIterator<ConcernConstructor>; /** * Invoke a method with given arguments in concern instance * * @param {ConcernConstructor} concern * @param {PropertyKey} method * @param {...any} [args] * * @return {any} * * @throws {ConcernException} * @throws {Error} */ call(concern: ConcernConstructor, method: PropertyKey, ...args: any[]): any; /** * Set the value of given property in concern instance * * @param {ConcernConstructor} concern * @param {PropertyKey} property * @param {any} value * * @throws {ConcernException} * @throws {Error} */ setProperty(concern: ConcernConstructor, property: PropertyKey, value: any): void; /** * Get value of given property in concern instance * * @param {ConcernConstructor} concern * @param {PropertyKey} property * * @return {any} * * @throws {ConcernException} * @throws {Error} */ getProperty(concern: ConcernConstructor, property: PropertyKey): any; } /** * Uses Concerns * * A target class that uses one or more concern classes. * * @template T = object */ interface UsesConcerns<T = object> { /** * Constructor * * @template T = object * * @param {...any} [args] * * @returns {ConstructorLike<T> & Owner} */ new (...args: any[]): ConstructorLike<T & Owner>; /** * A list of the concern classes to be used by this target class. * * **Note**: _If this class' parent class also uses concerns, then those * concern classes are included recursively in this list, in the order * that they have been registered._ * * **Note**: _This property is usually automatically defined and populated * by an {@link Injector}, and used to prevent duplicate concern injections._ * * @static * * @type {ConcernConstructor[]} */ [CONCERN_CLASSES]: ConcernConstructor[]; /** * List of aliases applied in this target class. * * **Note**: _This property is usually automatically defined and populated * by an {@link Injector}, and used to prevent alias conflicts._ * * @static * * @type {Alias[]} */ [ALIASES]: Alias[]; } /** * Descriptors Repository * * Utility for obtaining property descriptors for a target class or concern. */ interface DescriptorsRepository { /** * Returns property descriptors for given target class (recursively) * * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target The target class, or concern class * @param {boolean} [force=false] If `true` then method will not return evt. cached descriptors. * @param {boolean} [cache=false] Caches the descriptors if `true`. * * @returns {Record<PropertyKey, PropertyDescriptor>} */ get(target: ConstructorLike | UsesConcerns | ConcernConstructor, force?: boolean, cache?: boolean): Record<PropertyKey, PropertyDescriptor>; /** * Caches property descriptors for target during the execution of callback. * * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target The target class, or concern class * @param {() => any} callback Callback to invoke * @param {boolean} [forgetAfter=true] It `true`, cached descriptors are deleted after callback is invoked * * @return {any} */ rememberDuring(target: ConstructorLike | UsesConcerns | ConcernConstructor, callback: () => any, /* eslint-disable-line @typescript-eslint/no-explicit-any */ forgetAfter?: boolean): any; /** * Retrieves the property descriptors for given target and caches them * * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target The target class, or concern class * @param {boolean} [force=false] If `true` then evt. previous cached result is not used. * * @returns {Record<PropertyKey, PropertyDescriptor>} */ remember(target: ConstructorLike | UsesConcerns | ConcernConstructor, force?: boolean): Record<PropertyKey, PropertyDescriptor>; /** * Deletes cached descriptors for target * * @param {ConstructorLike | UsesConcerns | ConcernConstructor} target * * @return {boolean} */ forget(target: ConstructorLike | UsesConcerns | ConcernConstructor): boolean; /** * Clears all cached descriptors * * @return {this} */ clear(): this; } /** * Concern Configuration Factory * * Able to make new {@link Configuration} from a given concern "entry". */ interface Factory { /** * Returns a new normalised concern configuration for given concern "entry" * * **Note**: _"normalised" in this context means:_ * * _**A**: If a concern class is given, then a new concern configuration made._ * * _**B**: If configuration is given, then a new concern configuration and given * configuration is merged into the new configuration._ * * _**C**: Configuration's `aliases` are automatically populated. When a concern * configuration is provided, its evt. aliases merged with the default ones, * unless `allowAliases` is set to `false`, in which case all aliases are removed._ * * @param {object} target * @param {ConcernConstructor | Configuration | ShorthandConfiguration} entry * * @returns {Configuration} * * @throws {InjectionException} If entry is unsupported or invalid */ make(target: object, entry: ConcernConstructor | Configuration | ShorthandConfiguration): Configuration; } /** * Concerns Injector * * Able to inject concerns into a target class and create alias (proxy) properties * and methods to the provided concerns' properties and methods, in the target class. * * @template T = object The target class that concern classes must be injected into * * @see {Concern} */ interface Injector<T = object> { /** * The target class * * @returns {T} */ get target(): T; /** * Injects concern classes into the target class and return the modified target. * * **Note**: _Method performs injection in the following way:_ * * _**A**: Defines the concern classes in target class, via {@link defineConcerns}._ * * _**B**: Defines a concerns container in target class' prototype, via {@link defineContainer}._ * * _**C**: Defines "aliases" (proxy properties and methods) in target class' prototype, via {@link defineAliases}._ * * @template T = object The target class that concern classes must be injected into * * @param {...ConcernConstructor | Configuration | ShorthandConfiguration} concerns List of concern classes / injection configurations * * @returns {UsesConcerns<T>} The modified target class * * @throws {InjectionException} */ inject(...concerns: (ConcernConstructor | Configuration | ShorthandConfiguration)[]): UsesConcerns<T>; /** * Defines the concern classes that must be used by the target class. * * **Note**: _Method changes the target class, such that it implements and respects the * {@link UsesConcerns} interface._ * * @template T = object * * @param {T} target The target class that must define the concern classes to be used * @param {Constructor<Concern>[]} concerns List of concern classes * * @returns {UsesConcerns<T>} The modified target class * * @throws {AlreadyRegisteredException} * @throws {InjectionException} */ defineConcerns<T = object>(target: T, concerns: ConcernConstructor[]): UsesConcerns<T>; /** * Defines a concerns {@link Container} in target class' prototype. * * **Note**: _Method changes the target class, such that it implements and respects the * [Owner]{@link import('@aedart/contracts/support/concerns').Owner} interface!_ * * @template T = object * * @param {UsesConcerns<T>} target The target in which a concerns container must be defined * * @returns {UsesConcerns<T>} The modified target class * * @throws {InjectionException} If unable to define concerns container in target class */ defineContainer<T = object>(target: UsesConcerns<T>): UsesConcerns<T>; /** * Defines "aliases" (proxy properties and methods) in target class' prototype, such that they * point to the properties and methods available in the concern classes. * * **Note**: _Method defines each alias using the {@link defineAlias} method!_ * * @template T = object * * @param {UsesConcerns<T>} target The target in which "aliases" must be defined in * @param {Configuration[]} configurations List of concern injection configurations * * @returns {UsesConcerns<T>} The modified target class * * @throws {AliasConflictException} If case of alias naming conflicts. * @throws {InjectionException} If unable to define aliases in target class. */ defineAliases<T = object>(target: UsesConcerns<T>, configurations: Configuration[]): UsesConcerns<T>; /** * Defines an "alias" (proxy property or method) in target class' prototype, which points to a property or method * in the given concern. * * **Note**: _Method will do nothing, if a property or method already exists in the target class' prototype * chain, with the same name as given "alias"._ * * @template T = object * * @param {UsesConcerns<T>} target The target in which "alias" must be defined in * @param {PropertyKey} alias Name of the "alias" in the target class (name of the proxy property or method) * @param {PropertyKey} key Name of the property or method that the "alias" points to, in the concern class (`source`) * @param {Constructor} source The source concern class that contains the property or methods that is pointed to (`key`) * * @returns {boolean} `true` if "alias" was in target class. `false` if a property or method already exists in the * target, with the same name as the "alias". * * @throws {UnsafeAliasException} If an alias points to an "unsafe" property or method in the source concern class. * @throws {InjectionException} If unable to define "alias" in target class. */ defineAlias<T = object>(target: UsesConcerns<T>, alias: PropertyKey, key: PropertyKey, source: ConcernConstructor): boolean; } /** * Registration Aware * * Concern class is aware of when it is being registered by a target class * and is able to performs pre-/post-registration logic. */ interface RegistrationAware { /** * Perform pre-registration logic. * * **Note**: _This hook method is intended to be invoked by an * [Injector]{@link import('@aedart/contracts/support/concerns').Injector}, before * the concern container and aliases are defined in the target class._ * * @static * * @param {UsesConcerns} target Target class constructor * * @return {void} * * @throws {Error} */ [BEFORE](target: UsesConcerns): void; /** * Perform post-registration logic. * * **Note**: _This hook method is intended to be invoked by an * [Injector]{@link import('@aedart/contracts/support/concerns').Injector}, after * this concern class has been registered in a target class and aliases have been * defined in target's prototype._ * * @static * * @param {UsesConcerns} target Target class constructor, after concerns and aliases defined * * @return {void} * * @throws {Error} */ [AFTER](target: UsesConcerns): void; } /** * Alias Descriptor Factory */ interface AliasDescriptorFactory { /** * Makes a property descriptor to be used for an "alias" (proxy) property or method * * @param {PropertyKey} key The property key in `source` concern * @param {ConcernConstructor} source The concern that holds the property key * @param {PropertyDescriptor} keyDescriptor Descriptor of `key` in `source` concern * * @returns {PropertyDescriptor} Descriptor to be used for defining alias in a target class */ make(key: PropertyKey, source: ConcernConstructor, keyDescriptor: PropertyDescriptor): PropertyDescriptor; } /** * Concern Exception * * To be thrown when a concern class is the cause of an error. */ interface ConcernException extends Throwable { /** * The Concern class that caused this error or exception * * @readonly * * @type {ConcernConstructor | null} */ readonly concern: ConcernConstructor | null; } /** * Concern Injection Exception * * To be thrown when a concern class cannot be injected into a target class. */ interface InjectionException extends ConcernException { /** * The target class * * @readonly * * @type {ConstructorLike | UsesConcerns} */ readonly target: ConstructorLike | UsesConcerns; } /** * Alias Conflict Exception * * To be thrown if an alias conflicts with another alias. */ interface AliasConflictException extends InjectionException { /** * The requested alias that conflicts with another alias * of the same name. * * @readonly * * @type {Alias} */ readonly alias: Alias; /** * the property key that the conflicting alias points to * * @readonly * * @type {Alias} */ readonly key: PropertyKey; /** * The source class (e.g. parent class) that defines that originally defined the alias * * @readonly * * @type {ConstructorLike | UsesConcerns} */ readonly source: ConstructorLike | UsesConcerns; } /** * Already Registered Exception * * To be thrown when a concern class is attempted registered in a target, but * was previously registered in the target or target's parent. */ interface AlreadyRegisteredException extends InjectionException$1 { /** * The source, e.g. a parent class, in which a concern class * was already registered. * * @readonly * * @type {ConstructorLike | UsesConcerns} */ readonly source: ConstructorLike | UsesConcerns; } /** * Concern Boot Exception * * To be thrown when a concern class is unable to boot. * * @see ConcernException */ interface BootException extends ConcernException { } /** * Concern Not Registered Exception * * To be thrown when a given concern is expected registered in a [Container]{@link import('@aedart/contracts/support/concerns').Container}, * but isn't. * * @see ConcernException */ interface NotRegisteredException extends ConcernException { } /** * Unsafe Alias Exception * * To be thrown when an alias points to an "unsafe" property or method inside a concern. */ interface UnsafeAliasException extends InjectionException { /** * The alias that points to an "unsafe" property or method * * @readonly * * @type {PropertyKey} */ readonly alias: PropertyKey; /** * The "unsafe" property or method that an alias points to * * @readonly * * @type {PropertyKey} */ readonly key: PropertyKey; } /** * Support Concerns identifier * * @type {Symbol} */ declare const SUPPORT_CONCERNS: unique symbol; /** * Symbol used by a [concern class]{@link ConcernConstructor} to indicate what properties * and methods can be aliased into a target class. * * **Note**: _Symbol MUST be used to as name for a "static" method in the desired Concern class._ * * **Example**: * ```ts * class MyConcern implements Concern * { * static [PROVIDES](): PropertyKey[] * { * // ...not shown... * } * * // ...remaining not shown... * } * ``` * * @see ConcernConstructor * * @type {symbol} */ declare const PROVIDES: unique symbol; /** * Symbol that can be used by a [concern class]{@link ConcernConstructor} to perform * pre-registration logic * * @see RegistrationAware * @see ConcernConstructor * * @type {symbol} */ declare const BEFORE: unique symbol; /** * Symbol that can be used by a [concern class]{@link ConcernConstructor} to perform * post-registration logic. * * @see RegistrationAware * @see ConcernConstructor * * @type {symbol} */ declare const AFTER: unique symbol; /** * Symbol used to define a list of the concern classes to be used by a target class. * * @see {UsesConcerns} * * @type {Symbol} */ declare const CONCERN_CLASSES: unique symbol; /** * Symbol used to list the aliases applied in a target class * * @see {UsesConcerns} * * @type {Symbol} */ declare const ALIASES: unique symbol; /** * Symbol used to define a "concerns container" property inside a target class' prototype * * @see {Owner} * @see {Container} * * @type {Symbol} */ declare const CONCERNS: unique symbol; export { AFTER, ALIASES, type Alias, type AliasConflictException, type AliasDescriptorFactory, type Aliases, type AlreadyRegisteredException, BEFORE, type BootException, CONCERNS, CONCERN_CLASSES, type Concern, type ConcernConstructor, type ConcernException, type Configuration, type Container, type DescriptorsRepository, type Factory, type InjectionException, type Injector, type NotRegisteredException, type Owner, PROVIDES, type RegistrationAware, SUPPORT_CONCERNS, type ShorthandConfiguration, type UnsafeAliasException, type UsesConcerns };