UNPKG

@specialblend/superclass

Version:

ES6 class mixin (multi class "inheritance") utility - easily extend ES6 classes from multiple classes

121 lines (111 loc) 4.63 kB
(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 }); }));