@specialblend/superclass
Version:
ES6 class mixin (multi class "inheritance") utility - easily extend ES6 classes from multiple classes
118 lines (108 loc) • 3.69 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var assert = _interopDefault(require('assert'));
var isConstructable = _interopDefault(require('@specialblend/is-constructable'));
var R = require('ramda');
var is = _interopDefault(require('@sindresorhus/is/dist/index'));
/**
* 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;