@specialblend/superclass
Version:
ES6 class mixin (multi class "inheritance") utility - easily extend ES6 classes from multiple classes
121 lines (111 loc) • 4.63 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('assert'), require('@specialblend/is-constructable'), require('ramda'), require('@sindresorhus/is/dist/index')) :
typeof define === 'function' && define.amd ? define(['exports', 'assert', '@specialblend/is-constructable', 'ramda', '@sindresorhus/is/dist/index'], factory) :
(global = global || self, factory(global.index = {}, global.assert, global.isConstructable, global.R, global.is));
}(this, function (exports, assert, isConstructable, R, is) { 'use strict';
assert = assert && assert.hasOwnProperty('default') ? assert['default'] : assert;
isConstructable = isConstructable && isConstructable.hasOwnProperty('default') ? isConstructable['default'] : isConstructable;
is = is && is.hasOwnProperty('default') ? is['default'] : is;
/**
* Assert provided object is equal to or instance of allowed types
* @param obj
* @param message
* @param typePredicates
*/
const assertTypes = (obj, message, typePredicates) => {
if (R.anyPass(typePredicates)(obj)) {
return;
}
assert.fail(message);
};
/**
* Recursively scan the prototype of provided class
* @param {Class} source
* @returns {any}
*/
const scanPrototype = R.memoizeWith(R.identity, source => {
const target = {};
if (typeof source.prototype === 'undefined') {
return target;
}
for (const prop of Reflect.ownKeys(source.prototype)) {
if (typeof prop === 'symbol') {
if (prop !== Symbol.hasInstance) {
target[prop] = source.prototype[prop];
}
}
if (typeof prop === 'string' && !prop.match(/^(?:constructor|prototype|arguments|caller|name|bind|call|apply|toString|length)$/)) {
target[prop] = source.prototype[prop];
}
}
const original = Object.assign({}, target);
return Object.assign({}, scanPrototype(Reflect.getPrototypeOf(source)), original);
});
/**
* Copy source to target without overwriting
* @param target
* @param source
* @returns {*}
*/
const add = (target, source) => {
const original = Object.assign({}, target);
return Object.assign(target, source, original);
};
/**
* create a Superclass from a parent class and provided sister classes
* @param {Class} base: the base class to extend
* @param types
* @returns {Class}: the created defaultExport
*/
const superclass = (base, ...types) => {
assert(isConstructable(base), 'base must be constructable');
const supertypes = [base, ...types];
const subtype = class extends base {
constructor(...props) {
super(...props);
for (const Supertype of supertypes.reverse()) {
add(this, new Supertype(...props));
}
}
};
for (const supertype of supertypes) {
add(subtype.prototype, scanPrototype(supertype));
}
return subtype;
};
/**
* create a subclass of provided base class whose constructor
* will receive arguments transformed by provided transformer function
* @param {Class} base: the base class to extend
* @param {Function} transform: the transformer function
* @returns {Class}: the created subclass
*/
const mixin = (base, transform) => {
assert(isConstructable(base), 'base must be constructable');
assertTypes(transform, 'transformer must be Function, null or undefined', [is.function, is.nullOrUndefined]);
const mixed = class extends base {
constructor(...props) {
if (transform === null) {
super();
return;
}
if (typeof transform === 'undefined') {
super(...props);
return;
}
const transformedProps = transform(...props);
if (is.array(transformedProps)) {
super(...transformedProps);
return;
}
super(transformedProps);
}
};
add(mixed.prototype, scanPrototype(base));
return mixed;
};
exports.default = superclass;
exports.mixin = mixin;
exports.superclass = superclass;
Object.defineProperty(exports, '__esModule', { value: true });
}));