Comparing version 1.0.0 to 1.1.0
@@ -5,2 +5,14 @@ # Change Log | ||
<a name="1.1.0"></a> | ||
# [1.1.0](https://github.com/dmbch/mixinable/compare/v0.4.0...v1.1.0) (2017-11-23) | ||
### Features | ||
* accept constructor functions as definitions ([05fa890](https://github.com/dmbch/mixinable/commit/05fa890)) | ||
* add compose strategy ([81d1e14](https://github.com/dmbch/mixinable/commit/81d1e14)) | ||
* add override strategy, refactor and simplify ([a49b613](https://github.com/dmbch/mixinable/commit/a49b613)) | ||
<a name="1.0.0"></a> | ||
@@ -7,0 +19,0 @@ # [1.0.0](https://github.com/dmbch/mixinable/compare/3799c885...v1.0.0) (2017-11-21) |
91
index.js
'use strict'; | ||
module.exports = exports = function define (definition) { | ||
var methodNames = getMethodNames(definition); | ||
var mixinable = createMixinable(definition, methodNames); | ||
var mixinable = createMixinable(definition); | ||
return function mixin () { | ||
var mixins = argsToArray(arguments).map(createClass); | ||
var mixins = argsToArray(arguments).map(createMixin); | ||
return function create () { | ||
var args = argsToArray(arguments); | ||
var mixinstances = mixins.map(function (mixin) { | ||
return new (mixin.bind.apply(mixin, [mixin].concat(args)))(); | ||
return new (bindArgs(mixin, args))(); | ||
}); | ||
return Object.defineProperties( | ||
new (mixinable.bind.apply(mixinable, [mixinable].concat(args)))(), | ||
new (bindArgs(mixinable, args))(), | ||
{ | ||
__implementations__: { | ||
value: getImplementations(mixinstances, methodNames) | ||
value: getImplementations(mixinable, mixinstances) | ||
}, | ||
__clone__: { | ||
value: create.bind.apply(create, [create].concat(args)) | ||
value: bindArgs(create, args) | ||
} | ||
@@ -28,2 +27,4 @@ } | ||
// strategy exports | ||
exports.override = function override (functions) { | ||
@@ -64,2 +65,10 @@ var args = argsToArray(arguments).slice(1); | ||
exports.compose = function compose (_functions) { | ||
var args = argsToArray(arguments).slice(1); | ||
var functions = [].concat(_functions).reverse(); | ||
return exports.pipe.apply(null, [functions].concat(args)); | ||
}; | ||
// utility exports | ||
exports.clone = function clone (instance) { | ||
@@ -72,31 +81,38 @@ var args = argsToArray(arguments).slice(1); | ||
function createMixinable (definition, methodNames) { | ||
return createClass( | ||
methodNames.reduce(function (result, methodName) { | ||
if (methodName === 'constructor') { | ||
result[methodName] = definition[methodName]; | ||
} else { | ||
result[methodName] = function () { | ||
// classy helpers | ||
function createMixinable (definition) { | ||
var prototype = getPrototype(definition); | ||
return Object.assign(function Mixinable () { | ||
getConstructor(definition).apply(this, arguments); | ||
}, { | ||
prototype: Object.assign( | ||
Object.create(prototype), | ||
Object.keys(prototype).reduce(function (result, key) { | ||
result[key] = function () { | ||
var args = argsToArray(arguments); | ||
var functions = this.__implementations__[methodName]; | ||
return definition[methodName].apply(null, [functions].concat(args)); | ||
var functions = this.__implementations__[key]; | ||
var strategy = prototype[key]; | ||
return strategy.apply(this, [functions].concat(args)); | ||
}; | ||
} | ||
return result; | ||
}, {}) | ||
); | ||
return result; | ||
}, {}) | ||
) | ||
}); | ||
} | ||
function createClass (prototype) { | ||
var constructor = function () {}; | ||
if (prototype.hasOwnProperty('constructor')) { | ||
constructor = prototype.constructor; | ||
function createMixin (definition) { | ||
if (isFunction(definition)) { | ||
return definition; | ||
} else { | ||
prototype.constructor = constructor; | ||
return Object.assign(function Mixin () { | ||
getConstructor(definition).apply(this, arguments); | ||
}, { | ||
prototype: getPrototype(definition) | ||
}); | ||
} | ||
constructor.prototype = prototype; | ||
return constructor; | ||
} | ||
function getImplementations (mixinstances, methodNames) { | ||
function getImplementations (mixinable, mixinstances) { | ||
var methodNames = Object.keys(mixinable.prototype); | ||
return methodNames.reduce(function (result, methodName) { | ||
@@ -114,8 +130,3 @@ result[methodName] = mixinstances | ||
function getMethodNames (definition) { | ||
return Object.keys(definition || {}) | ||
.filter(function (methodName) { | ||
return isFunction(definition[methodName]); | ||
}); | ||
} | ||
// utilities | ||
@@ -133,2 +144,14 @@ var argsToArray = Function.prototype.apply.bind( | ||
function bindArgs (fn, args) { | ||
return Function.prototype.bind.apply(fn, [fn].concat(args)); | ||
} | ||
function getConstructor (def) { | ||
return (def && ((isFunction(def)) ? def : def.constructor)) || function () {}; | ||
} | ||
function getPrototype (def) { | ||
return (def && ((isFunction(def)) ? def.prototype : def)) || {}; | ||
} | ||
function isFunction (obj) { | ||
@@ -135,0 +158,0 @@ return typeof obj === 'function'; |
{ | ||
"name": "mixinable", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Functional JavaScript Mixin Utility", | ||
@@ -9,3 +9,3 @@ "main": "index.js", | ||
"release": "standard-version", | ||
"postrelease": "npm publish --registry https://registry.npmjs.org/" | ||
"postrelease": "git push --follow-tags; npm publish --registry https://registry.npmjs.org/" | ||
}, | ||
@@ -12,0 +12,0 @@ "repository": { |
@@ -231,3 +231,20 @@ # Mixinable | ||
#### ```define.compose``` | ||
`compose` works essentially identically as `pipe`, but in reverse order: the very last implementation receives the initial value and the first implementation returns the final output. | ||
##### Example | ||
```javascript | ||
import define, { compose } from 'mixinable'; | ||
const mixin = define({ | ||
// mixin strategy function | ||
bar: compose | ||
}); | ||
// ... | ||
``` | ||
#### Custom Strategies | ||
@@ -234,0 +251,0 @@ |
149
test.js
@@ -71,2 +71,49 @@ 'use strict'; | ||
test('inheritance test', function (t) { | ||
t.plan(15); | ||
var arg = 1; | ||
function Strategy (_arg) { | ||
t.pass('strategy constructor is being called'); | ||
t.equal(_arg, arg, 'strategy constructor receives correct arg'); | ||
t.ok(this instanceof Strategy, 'strategy inherits correctly'); | ||
t.ok( | ||
Strategy.prototype.isPrototypeOf(this), | ||
'strategy prototype chain is set up' | ||
); | ||
} | ||
Strategy.prototype = { | ||
foo: function (functions, _arg) { | ||
t.equal(_arg, arg, 'strategy definition receives correct arg'); | ||
t.ok(this instanceof Strategy, 'strategy inherits correctly'); | ||
t.ok( | ||
Strategy.prototype.isPrototypeOf(this), | ||
'strategy prototype chain is set up' | ||
); | ||
functions.forEach(function (fn) { fn(_arg); }); | ||
} | ||
}; | ||
function Implementation (_arg) { | ||
t.pass('implementation constructor is being called'); | ||
t.equal(_arg, arg, 'implementation constructor receives correct arg'); | ||
t.ok(this instanceof Implementation, 'implementation inherits correctly'); | ||
t.ok( | ||
Implementation.prototype.isPrototypeOf(this), | ||
'implementation prototype chain is set up' | ||
); | ||
} | ||
Implementation.prototype = { | ||
foo: function (_arg) { | ||
t.pass('implementation is being called'); | ||
t.equal(_arg, arg, 'implementation receives correct arg'); | ||
t.ok(this instanceof Implementation, 'implementation inherits correctly'); | ||
t.ok( | ||
Implementation.prototype.isPrototypeOf(this), | ||
'implementation prototype chain is set up' | ||
); | ||
} | ||
}; | ||
var instance = mixinable(Strategy)(Implementation)(arg); | ||
instance.foo(arg); | ||
}); | ||
test('override helper test', function (t) { | ||
@@ -311,1 +358,103 @@ t.plan(2); | ||
}); | ||
test('sync compose helper test', function (t) { | ||
t.plan(10); | ||
var arg = 1; | ||
var instance = mixinable( | ||
{ | ||
foo: mixinable.compose | ||
} | ||
)( | ||
{ | ||
foo: function (ctr, _arg) { | ||
t.equal(ctr, 2, '1st implementation receives 2nd\'s result'); | ||
t.equal(_arg, arg, '1st implementation receives correct arg'); | ||
return this.increment(ctr); | ||
}, | ||
increment: function (ctr) { | ||
t.pass('1st private method is being called'); | ||
return ++ctr; | ||
} | ||
}, | ||
{ | ||
foo: function (ctr, _arg) { | ||
t.equal(ctr, 1, '2nd implementation receives 1st\'s result'); | ||
t.equal(_arg, arg, '2nd implementation receives correct arg'); | ||
return this.increment(ctr); | ||
}, | ||
increment: function (ctr) { | ||
t.pass('2nd private method is being called'); | ||
return ++ctr; | ||
} | ||
}, | ||
{ | ||
foo: function (ctr, _arg) { | ||
t.equal(ctr, 0, '3rd implementation receives inital value'); | ||
t.equal(_arg, arg, '3rd implementation receives correct arg'); | ||
return this.increment(ctr); | ||
}, | ||
increment: function (ctr) { | ||
t.pass('3rd private method is being called'); | ||
return ++ctr; | ||
} | ||
} | ||
)(); | ||
t.equal(instance.foo(0, arg), 3, 'correct result received'); | ||
}); | ||
test('async compose helper test', function (t) { | ||
t.plan(11); | ||
var arg = 1; | ||
var instance = mixinable( | ||
{ | ||
foo: mixinable.compose | ||
} | ||
)( | ||
{ | ||
foo: function (ctr, _arg) { | ||
t.equal(ctr, 2, '1st implementation receives 2nd\'s result'); | ||
t.equal(_arg, arg, '1st implementation receives correct arg'); | ||
return this.increment(ctr); | ||
}, | ||
increment: function (ctr) { | ||
t.pass('1st private method is being called'); | ||
return ++ctr; | ||
} | ||
}, | ||
{ | ||
foo: function (ctr, _arg) { | ||
t.equal(ctr, 1, '2nd implementation receives 1st\'s result'); | ||
t.equal(_arg, arg, '2nd implementation receives correct arg'); | ||
return new Promise(function (resolve) { | ||
setTimeout(function () { | ||
resolve(this.increment(ctr)); | ||
}.bind(this), 5); | ||
}.bind(this)); | ||
}, | ||
increment: function (ctr) { | ||
t.pass('2nd private method is being called'); | ||
return ++ctr; | ||
} | ||
}, | ||
{ | ||
foo: function (ctr, _arg) { | ||
t.equal(ctr, 0, '3rd implementation receives inital value'); | ||
t.equal(_arg, arg, '3rd implementation receives correct arg'); | ||
return new Promise(function (resolve) { | ||
setTimeout(function () { | ||
resolve(this.increment(ctr)); | ||
}.bind(this), 10); | ||
}.bind(this)); | ||
}, | ||
increment: function (ctr) { | ||
t.pass('3rd private method is being called'); | ||
return ++ctr; | ||
} | ||
} | ||
)(); | ||
var result = instance.foo(0, arg); | ||
t.ok(result instanceof Promise, 'received result is a promise'); | ||
result.then(function (result) { | ||
t.equal(result, 3, 'promise resolves to correct value'); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
99182
584
291