UNPKG

higlass

Version:

HiGlass Hi-C / genomic / large data viewer

250 lines (230 loc) 7.76 kB
// @ts-nocheck // used by apply() and isApplicationOf() const _appliedMixin = '__mixwith_appliedMixin'; // used by wrap() and unwrap() const _wrappedMixin = '__mixwith_wrappedMixin'; /** * A function that returns a subclass of its argument. * * @example * const M = (superclass) => class extends superclass { * getMessage() { * return "Hello"; * } * } * * @typedef {Function} MixinFunction * @param {Function} superclass * @return {Function} A subclass of `superclass` */ /** * Unwraps the function `wrapper` to return the original function wrapped by * one or more calls to `wrap`. Returns `wrapper` if it's not a wrapped * function. * * @function * @param {MixinFunction} wrapper A wrapped mixin produced by {@link wrap} * @return {MixinFunction} The originally wrapped mixin */ export const unwrap = (wrapper) => wrapper[_wrappedMixin] || wrapper; /** * Applies `mixin` to `superclass`. * * `apply` stores a reference from the mixin application to the unwrapped mixin * to make `isApplicationOf` and `hasMixin` work. * * This function is usefull for mixin wrappers that want to automatically enable * {@link hasMixin} support. * * @example * const Applier = (mixin) => wrap(mixin, (superclass) => apply(superclass, mixin)); * * // M now works with `hasMixin` and `isApplicationOf` * const M = Applier((superclass) => class extends superclass {}); * * class C extends M(Object) {} * let i = new C(); * hasMixin(i, M); // true * * @function * @param {Function} superclass A class or constructor function * @param {MixinFunction} mixin The mixin to apply * @return {Function} A subclass of `superclass` produced by `mixin` */ export const apply = (superclass, mixin) => { const application = mixin(superclass); application.prototype[_appliedMixin] = unwrap(mixin); return application; }; /** * Returns `true` iff `proto` is a prototype created by the application of * `mixin` to a superclass. * * `isApplicationOf` works by checking that `proto` has a reference to `mixin` * as created by `apply`. * * @function * @param {Object} proto A prototype object created by {@link apply}. * @param {MixinFunction} mixin A mixin function used with {@link apply}. * @return {boolean} whether `proto` is a prototype created by the application of * `mixin` to a superclass */ export const isApplicationOf = (proto, mixin) => // biome-ignore lint/suspicious/noPrototypeBuiltins: proto.hasOwnProperty(_appliedMixin) && proto[_appliedMixin] === unwrap(mixin); /** * Returns `true` iff `o` has an application of `mixin` on its prototype * chain. * * @function * @param {Object} o An object * @param {MixinFunction} mixin A mixin applied with {@link apply} * @return {boolean} whether `o` has an application of `mixin` on its prototype * chain */ export const hasMixin = (o, mixin) => { while (o !== null) { if (isApplicationOf(o, mixin)) return true; o = Object.getPrototypeOf(o); } return false; }; /** * Sets up the function `mixin` to be wrapped by the function `wrapper`, while * allowing properties on `mixin` to be available via `wrapper`, and allowing * `wrapper` to be unwrapped to get to the original function. * * `wrap` does two things: * 1. Sets the prototype of `mixin` to `wrapper` so that properties set on * `mixin` inherited by `wrapper`. * 2. Sets a special property on `mixin` that points back to `mixin` so that * it can be retreived from `wrapper` * * @function * @param {MixinFunction} mixin A mixin function * @param {MixinFunction} wrapper A function that wraps {@link mixin} * @return {MixinFunction} `wrapper` */ export const wrap = (mixin, wrapper) => { Object.setPrototypeOf(wrapper, mixin); if (!mixin[_wrappedMixin]) { mixin[_wrappedMixin] = mixin; } return wrapper; }; const _cachedApplications = '__mixwith_cachedApplications'; /** * Decorates `mixin` so that it caches its applications. When applied multiple * times to the same superclass, `mixin` will only create one subclass, memoize * it and return it for each application. * * Note: If `mixin` somehow stores properties its classes constructor (static * properties), or on its classes prototype, it will be shared across all * applications of `mixin` to a super class. It's reccomended that `mixin` only * access instance state. * * @function * @param {MixinFunction} mixin The mixin to wrap with caching behavior * @return {MixinFunction} a new mixin function */ export const Cached = (mixin) => wrap(mixin, (superclass) => { // Get or create a symbol used to look up a previous application of mixin // to the class. This symbol is unique per mixin definition, so a class will have N // applicationRefs if it has had N mixins applied to it. A mixin will have // exactly one _cachedApplicationRef used to store its applications. let cachedApplications = superclass[_cachedApplications]; if (!cachedApplications) { superclass[_cachedApplications] = new Map(); cachedApplications = superclass[_cachedApplications]; } let application = cachedApplications.get(mixin); if (!application) { application = mixin(superclass); cachedApplications.set(mixin, application); } return application; }); /** * Decorates `mixin` so that it only applies if it's not already on the * prototype chain. * * @function * @param {MixinFunction} mixin The mixin to wrap with deduplication behavior * @return {MixinFunction} a new mixin function */ export const DeDupe = (mixin) => wrap(mixin, (superclass) => hasMixin(superclass.prototype, mixin) ? superclass : mixin(superclass), ); /** * Adds [Symbol.hasInstance] (ES2015 custom instanceof support) to `mixin`. * * @function * @param {MixinFunction} mixin The mixin to add [Symbol.hasInstance] to * @return {MixinFunction} the given mixin function */ export const HasInstance = (mixin) => { if (Symbol?.hasInstance && !mixin[Symbol.hasInstance]) { Object.defineProperty(mixin, Symbol.hasInstance, { value(o) { return hasMixin(o, mixin); }, }); } return mixin; }; /** * A basic mixin decorator that applies the mixin with {@link apply} so that it * can be used with {@link isApplicationOf}, {@link hasMixin} and the other * mixin decorator functions. * * @function * @param {MixinFunction} mixin The mixin to wrap * @return {MixinFunction} a new mixin function */ export const BareMixin = (mixin) => wrap(mixin, (s) => apply(s, mixin)); /** * Decorates a mixin function to add deduplication, application caching and * instanceof support. * * @function * @param {MixinFunction} mixin The mixin to wrap * @return {MixinFunction} a new mixin function */ export const Mixin = (mixin) => DeDupe(Cached(BareMixin(mixin))); class MixinBuilder { constructor(superclass) { this.superclass = superclass || class {}; } /** * Applies `mixins` in order to the superclass given to `mix()`. * * @param {Array.<Mixin>} mixins * @return {Function} a subclass of `superclass` with `mixins` applied */ with(...mixins) { return mixins.reduce((c, m) => m(c), this.superclass); } } /** * A fluent interface to apply a list of mixins to a superclass. * * ```javascript * class X extends mix(Object).with(A, B, C) {} * ``` * * The mixins are applied in order to the superclass, so the prototype chain * will be: X->C'->B'->A'->Object. * * This is purely a convenience function. The above example is equivalent to: * * ```javascript * class X extends C(B(A(Object))) {} * ``` * * @function * @param {Function} [superclass=Object] * @return {MixinBuilder} */ export const mix = (superclass) => new MixinBuilder(superclass);