Comparing version 1.2.0 to 1.2.1
@@ -5,2 +5,7 @@ # Change Log | ||
<a name="1.2.1"></a> | ||
## [1.2.1](https://github.com/dmbch/mixinable/compare/v1.2.0...v1.2.1) (2017-11-30) | ||
<a name="1.2.0"></a> | ||
@@ -7,0 +12,0 @@ # [1.2.0](https://github.com/dmbch/mixinable/compare/v1.1.1...v1.2.0) (2017-11-27) |
157
index.js
'use strict'; | ||
module.exports = exports = function define (definition) { | ||
var mixinable = createMixinable(definition); | ||
module.exports = exports = function define (strategies) { | ||
return function mixin () { | ||
var mixins = argsToArray(arguments).map(createMixin); | ||
return function create () { | ||
var args = argsToArray(arguments); | ||
return Object.defineProperties( | ||
new (bindArgs(mixinable, args))(), | ||
{ | ||
__implementations__: { value: getImplementations( | ||
mixinable, | ||
mixins.map(function (mixin) { | ||
return new (bindArgs(mixin, args))(); | ||
}) | ||
)}, | ||
__clone__: { value: bindArgs(create, args) } | ||
} | ||
); | ||
}; | ||
return createMixinable( | ||
strategies || {}, | ||
argsToArray(arguments).map(createMixin) | ||
); | ||
}; | ||
@@ -71,41 +58,45 @@ }; | ||
exports.async = { | ||
override: function () { | ||
return Promise.resolve(exports.override.apply(null, arguments)); | ||
override: function overrideAsync () { | ||
return promisify(exports.override.apply(null, arguments)); | ||
}, | ||
parallel: function () { | ||
return Promise.resolve(exports.parallel.apply(null, arguments)); | ||
parallel: function parallelAsync () { | ||
return promisify(exports.parallel.apply(null, arguments)); | ||
}, | ||
pipe: function () { | ||
return Promise.resolve(exports.pipe.apply(null, arguments)); | ||
pipe: function pipeAsync () { | ||
return promisify(exports.pipe.apply(null, arguments)); | ||
}, | ||
compose: function () { | ||
return Promise.resolve(exports.compose.apply(null, arguments)); | ||
compose: function composeAsync () { | ||
return promisify(exports.compose.apply(null, arguments)); | ||
} | ||
}; | ||
exports.clone = function clone (instance) { | ||
var args = argsToArray(arguments).slice(1); | ||
if (instance && isFunction(instance.__clone__)) { | ||
return instance.__clone__.apply(null, args); | ||
} | ||
exports.replicate = function replicate (handleArgs) { | ||
return function (instance) { | ||
if (instance) { | ||
return new (bindArgs(instance.constructor, handleArgs( | ||
instance.__arguments__ || [], | ||
argsToArray(arguments).slice(1) | ||
)))(); | ||
} | ||
}; | ||
}; | ||
exports.clone = exports.replicate(Array.prototype.concat.bind([])); | ||
// classy helpers | ||
function createMixinable (definition) { | ||
function createMixinable (strategies, mixins) { | ||
var constructor = getConstructor(strategies); | ||
var prototype = getPrototype(strategies); | ||
function Mixinable () { | ||
getConstructor(definition).apply(this, arguments); | ||
var args = argsToArray(arguments); | ||
if (!(this instanceof Mixinable)) { | ||
return new (bindArgs(Mixinable, args))(); | ||
} | ||
enhanceInstance.call(this, prototype, mixins, args); | ||
constructor.apply(this, args); | ||
} | ||
var prototype = getPrototype(definition); | ||
Mixinable.prototype = Object.assign( | ||
Object.create(prototype), | ||
Object.keys(prototype).reduce(function (result, key) { | ||
result[key] = function () { | ||
var args = argsToArray(arguments); | ||
var functions = this.__implementations__[key]; | ||
var strategy = prototype[key]; | ||
return strategy.apply(this, [functions].concat(args)); | ||
}; | ||
return result; | ||
}, {}), | ||
enhancePrototype(prototype), | ||
{ constructor: Mixinable } | ||
@@ -116,2 +107,35 @@ ); | ||
function enhanceInstance (strategies, mixins, args) { | ||
var mixinstances = mixins.map(function (mixin) { | ||
return new (bindArgs(mixin, args))(); | ||
}); | ||
Object.defineProperties(this, { | ||
__implementations__: { | ||
value: Object.keys(strategies).reduce(function (result, key) { | ||
result[key] = mixinstances | ||
.filter(function (mixinstance) { | ||
return isFunction(mixinstance[key]); | ||
}) | ||
.map(function (mixinstance) { | ||
return mixinstance[key].bind(mixinstance); | ||
}); | ||
return result; | ||
}, {}) | ||
}, | ||
__arguments__: { value: args } | ||
}); | ||
} | ||
function enhancePrototype (strategies) { | ||
return Object.keys(strategies).reduce(function (result, key) { | ||
result[key] = function () { | ||
var args = argsToArray(arguments); | ||
var functions = this.__implementations__[key]; | ||
var strategy = strategies[key]; | ||
return strategy.apply(this, [functions].concat(args)); | ||
}; | ||
return result; | ||
}, {}); | ||
} | ||
function createMixin (definition) { | ||
@@ -131,15 +155,19 @@ if (isFunction(definition)) { | ||
function getImplementations (mixinable, mixinstances) { | ||
return Object.keys(mixinable.prototype).reduce(function (result, key) { | ||
result[key] = mixinstances | ||
.filter(function (mixinstance) { | ||
return isFunction(mixinstance[key]); | ||
}) | ||
.map(function (mixinstance) { | ||
return mixinstance[key].bind(mixinstance); | ||
}); | ||
return result; | ||
}, {}); | ||
function getConstructor (obj) { | ||
if (isFunction(obj)) { | ||
return obj; | ||
} | ||
if (obj && obj.hasOwnProperty('constructor')) { | ||
return obj.constructor; | ||
} | ||
return function () {}; | ||
} | ||
function getPrototype (obj) { | ||
if (isFunction(obj)) { | ||
return obj.prototype; | ||
} | ||
return obj || Object.create(null); | ||
} | ||
// utilities | ||
@@ -162,19 +190,2 @@ | ||
function getConstructor (obj) { | ||
if (isFunction(obj)) { | ||
return obj; | ||
} | ||
if (obj && obj.hasOwnProperty('constructor')) { | ||
return obj.constructor; | ||
} | ||
return function () {}; | ||
} | ||
function getPrototype (obj) { | ||
if (isFunction(obj)) { | ||
return obj.prototype; | ||
} | ||
return obj || Object.create(null); | ||
} | ||
function isFunction (obj) { | ||
@@ -187,1 +198,5 @@ return typeof obj === 'function'; | ||
} | ||
function promisify (obj) { | ||
return isPromise(obj) ? obj : Promise.resolve(obj); | ||
} |
{ | ||
"name": "mixinable", | ||
"version": "1.2.0", | ||
"version": "1.2.1", | ||
"description": "Functional JavaScript Mixin Utility", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -38,3 +38,3 @@ # Mixinable | ||
And that `create()` function accepts whatever arguments the `constructor()`s of your mixin container and your mixins accept. | ||
And that `create()` function accepts whatever arguments your `constructor()`s accept. | ||
@@ -101,3 +101,3 @@ ##### Example | ||
Both mixin container and mixin definitions can contain custom constructors. These functions are being passed the `create()` function's arguments upon creation. | ||
Mixin definitions can be (or contain) custom constructors. These functions are being passed the `create()` function's arguments upon creation. | ||
@@ -109,15 +109,17 @@ ##### Example | ||
const mixin = define({ | ||
// mixin container contructor | ||
constructor: function (arg) { | ||
console.log(arg); | ||
} | ||
}); | ||
const mixin = define(); | ||
const create = mixin({ | ||
// mixin contructor | ||
constructor: function (arg) { | ||
console.log(arg); | ||
const create = mixin( | ||
// mixin contructors | ||
{ | ||
constructor: function (arg) { | ||
console.log(arg); | ||
} | ||
}, | ||
class { | ||
constructor (arg) { | ||
console.log(arg); | ||
} | ||
} | ||
}); | ||
); | ||
@@ -151,3 +153,3 @@ create('yee-hah!'); | ||
}, | ||
{ | ||
class { | ||
bar () { | ||
@@ -187,3 +189,3 @@ console.log(2); | ||
}, | ||
{ | ||
class { | ||
bar (val, inc) { | ||
@@ -223,3 +225,3 @@ return val + inc; | ||
}, | ||
{ | ||
class { | ||
bar (val, inc) { | ||
@@ -226,0 +228,0 @@ return (val + inc); |
59
test.js
@@ -10,2 +10,3 @@ 'use strict'; | ||
t.equal(typeof mixinable, 'function', 'main export is a function'); | ||
t.equal(typeof mixinable.replicate, 'function', 'replicate is a function'); | ||
t.equal(typeof mixinable.clone, 'function', 'clone is a function'); | ||
@@ -33,20 +34,2 @@ t.equal(typeof mixinable.override, 'function', 'override is a function'); | ||
test('clone function test', function (t) { | ||
var arg1 = 1; | ||
var arg2 = 2; | ||
var create = mixinable({ | ||
constructor: function (bar, baz) { | ||
this.bar = bar; | ||
this.baz = baz; | ||
} | ||
})(); | ||
var instance = create(arg1); | ||
t.equal(instance.bar, arg1, 'instance has expected 1st property'); | ||
t.equal(instance.baz, undefined, 'instance does not have 2nd property'); | ||
var clone = mixinable.clone(instance, arg2); | ||
t.equal(clone.bar, arg1, 'clone has expected 1st property'); | ||
t.equal(clone.baz, arg2, 'clone has expected 2nd property'); | ||
t.end(); | ||
}); | ||
test('constructor support test', function (t) { | ||
@@ -487,1 +470,41 @@ t.plan(4); | ||
}); | ||
test('replicate function test', function (t) { | ||
t.plan(3); | ||
var arg1 = 1; | ||
var arg2 = 2; | ||
var create = mixinable({ | ||
foo: mixinable.override | ||
})({ | ||
constructor: function (bar) { | ||
this.foo = function () { return bar; }; | ||
} | ||
}); | ||
var instance = create(arg1); | ||
t.equal(instance.foo(), arg1, 'instance returns expected value'); | ||
var replicate = mixinable.replicate(function (initialArgs, newArgs) { | ||
t.pass('handleArgs is being called'); | ||
return [initialArgs[0] + newArgs[0]]; | ||
}); | ||
var replica = replicate(instance, arg2); | ||
t.equal(replica.foo(), arg1 + arg2, 'clone returns expected value'); | ||
}); | ||
test('clone function test', function (t) { | ||
var arg1 = 1; | ||
var arg2 = 2; | ||
var create = mixinable({ | ||
foo: mixinable.override | ||
})({ | ||
constructor: function (bar, baz) { | ||
this.foo = function () { | ||
return bar + (baz || 0); | ||
}; | ||
} | ||
}); | ||
var instance = create(arg1); | ||
t.equal(instance.foo(), arg1, 'instance returns expected value'); | ||
var clone = mixinable.clone(instance, arg2); | ||
t.equal(clone.foo(), arg1 + arg2, 'clone returns expected value'); | ||
t.end(); | ||
}); |
101988
665
293