UNPKG

@aedart/support

Version:

The Ion support package

312 lines (296 loc) 9.68 kB
/** * @aedart/support * * BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>. */ 'use strict'; var mixins = require('@aedart/contracts/support/mixins'); /** * Mixin Builder * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends#mix-ins * @see https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/ * @see https://justinfagnani.com/2016/01/07/enhancing-mixins-with-decorator-functions/ * * @template T = object */ class Builder { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js /** * The target superclass * * @template T = object * * @type {ConstructorLike<T>} * * @protected */ _superclass; /** * Create a new Mixin Builder instance * * @param {ConstructorLike<T>} [superclass=class {}] */ constructor(superclass = class { }) { this._superclass = superclass; } /** * Apply given mixins to the superclass * * @param {...MixinFunction} mixins * * @return {ConstructorLike<T>} Subclass of given superclass with given mixins applied */ with(...mixins) { return mixins.reduce((superclass, mixin) => { // Return superclass, when mixin isn't a function. if (typeof mixin != 'function') { return superclass; } // Apply the mixin... return mixin(superclass); }, this._superclass); } /** * Returns the superclass * * **Note**: _Method is intended for testing purposes only, or situations when * no mixins are desired applied!_ * * @param {...MixinFunction} [mixins] Ignored * * @return {ConstructorLike<T>} The superclass */ none(...mixins /* eslint-disable-line @typescript-eslint/no-unused-vars */) { return this._superclass; } } /** * Setup given mixin to be wrapped by given `wrapper` and allow * it to be unwrapped at a later point. * * @param {MixinFunction} mixin * @param {MixinFunction} wrapper * * @returns {MixinFunction} */ function wrap(mixin, wrapper) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js Reflect.setPrototypeOf(wrapper, mixin); if (!Reflect.has(mixin, mixins.WRAPPED_MIXIN)) { Reflect.set(mixin, mixins.WRAPPED_MIXIN, mixin); } return wrapper; } /** * Unwrap the given wrapped mixin * * @param {MixinFunction} wrapped A wrapped mixin produced by the {@link import('@aedart/support/mixins').wrap} function * * @returns {MixinFunction} */ function unwrap(wrapped) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js if (Reflect.has(wrapped, mixins.WRAPPED_MIXIN)) { return wrapped[mixins.WRAPPED_MIXIN]; } return wrapped; } /** * Applies mixin to superclass * * @param {object} superclass * @param {MixinFunction} mixin * * @returns {object} */ function apply(superclass, mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js const application = mixin(superclass); Reflect.set(application.prototype, mixins.APPLIED_MIXIN, unwrap(mixin)); return application; } /** * Decorates given mixin such that it can be used by {@link import('@aedart/support/mixins').isApplicationOf}, * {@link import('@aedart/support/mixins').hasMixin} and other mixin utility methods * * @param {MixinFunction} mixin * * @returns {MixinFunction} */ const Bare = function (mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js return wrap(mixin, (superclass) => { return apply(superclass, mixin); }); }; /** * Decorates given mixin such that it caches its applications. * * Method ensures that when mixin is applied multiple times to the same * superclass, the mixin will only create one subclass, memoize it and return * it for each application. * * @see https://justinfagnani.com/2016/01/07/enhancing-mixins-with-decorator-functions/#cachingmixinapplications * * @param {MixinFunction} mixin * * @returns {MixinFunction} */ const Cached = function (mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js return wrap(mixin, (superclass) => { // Attempt to resolve "cached applications" map, or make a new map if none exists in superclass... let cachedApplications = Reflect.has(superclass, mixins.CACHED_APPLICATIONS) ? superclass[mixins.CACHED_APPLICATIONS] : undefined; if (cachedApplications === undefined) { cachedApplications = new WeakMap(); Reflect.set(superclass, mixins.CACHED_APPLICATIONS, cachedApplications); } // Retrieve the cached "application" from cache, or cache it... let application = cachedApplications.get(mixin); if (application === undefined) { application = mixin(superclass); cachedApplications.set(mixin, application); } return application; }); }; /** * Determine if object is a prototype created by the application of * `mixin` to a superclass. * * @param {object} proto * @param {MixinFunction} mixin * * @returns {boolean} */ function isApplicationOf(proto, mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js return Reflect.has(proto, mixins.APPLIED_MIXIN) && proto[mixins.APPLIED_MIXIN] === unwrap(mixin); } /** * Determine if given target has an application of given `mixin` on its prototype * chain. * * @param {object} target * @param {MixinFunction} mixin * * @returns {boolean} */ function hasMixin(target, mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js while (target !== null) { if (isApplicationOf(target, mixin)) { return true; } target = Reflect.getPrototypeOf(target); } return false; } /** * Decorates mixin such that it is only applied if not already on the superclass' * prototype chain. * * @param {MixinFunction} mixin * * @returns {MixinFunction} */ const DeDupe = function (mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js return wrap(mixin, (superclass) => { return hasMixin(superclass.prototype, mixin) ? superclass : mixin(superclass); }); }; /** * Adds {@link Symbol.hasInstance} to mixin, if not already in mixin * * @param {MixinFunction} mixin * * @returns {MixinFunction} */ const HasInstance = function (mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js // Skip if mixin already has Symbol.hasInstance if (Object.hasOwn(mixin, Symbol.hasInstance)) { return mixin; } // Otherwise, define the Symbol.hasInstance return Object.defineProperty(mixin, Symbol.hasInstance, { value: (instance) => { return hasMixin(instance, mixin); }, }); }; /** * Decorates given mixin to add deduplication, application caching, and instance of support * * @param {MixinFunction} mixin * * @returns {MixinFunction} */ const Mixin = function (mixin) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js return DeDupe(Cached(HasInstance(Bare(mixin)))); }; /** * Mix superclass with one or more abstract subclasses ("Mixins") * * **example**: * ```ts * const BoxMixin = <T extends AbstractConstructor>(superclass: T) => class extends superclass { * // ...not shown... * } * * class A extends mix().with( * BoxMixin * ) { * // ...not shown... * } * ``` * * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/extends#mix-ins * @see https://justinfagnani.com/2015/12/21/real-mixins-with-javascript-classes/ * @see https://justinfagnani.com/2016/01/07/enhancing-mixins-with-decorator-functions/ * * @template T = object * * @param {ConstructorLike<T>} [superclass=class {}] * * @return {Builder<T>} New Mixin Builder instance */ function mix(superclass = class { }) { // The following source code is an adaptation of Justin Fagnani's "mixwith.js" (Apache License 2.0) // @see https://github.com/justinfagnani/mixwith.js return new Builder(superclass); } exports.Bare = Bare; exports.Builder = Builder; exports.Cached = Cached; exports.DeDupe = DeDupe; exports.HasInstance = HasInstance; exports.Mixin = Mixin; exports.apply = apply; exports.hasMixin = hasMixin; exports.isApplicationOf = isApplicationOf; exports.mix = mix; exports.unwrap = unwrap; exports.wrap = wrap; module.exports = Object.assign(exports.default, exports); //# sourceMappingURL=mixins.cjs.map