@aedart/support
Version:
The Ion support package
312 lines (296 loc) • 9.68 kB
JavaScript
/**
* @aedart/support
*
* BSD-3-Clause, Copyright (c) 2023-present Alin Eugen Deac <aedart@gmail.com>.
*/
;
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