smart-mixin
Advanced tools
| cd "$(dirname $0)" | ||
| browserify -t 6to5ify index.js > bundle.js | ||
| open index.html |
| (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
| "use strict"; | ||
| var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; | ||
| var mixins = require("../.."); | ||
| // define a mixin behavior | ||
| var mixIntoGameObject = mixins({ | ||
| // this can only be defined once, will throw otherwise | ||
| render: mixins.ONCE, | ||
| // can be defined in the source and mixins | ||
| // only the source return value will be returned | ||
| // the mixin function is called first (as in all of the following except REDUCE_LEFT) | ||
| onClick: mixins.MANY, | ||
| // like MANY but expects objects to be returned | ||
| // which will be merged with the other objects | ||
| // will throw when duplicate keys are found | ||
| getState: mixins.MANY_MERGED, | ||
| // TODO: this isn't currently implemented, PR welcome (or I'll get around to it) | ||
| // like MANY_MERGED but also handles arrays, and non-function properties | ||
| // the behavior expressed in pseudo pattern matching syntax: | ||
| // undefined, y:any => y | ||
| // x:any, undefined => x | ||
| // x:Array, y:Array => x.concat(y) | ||
| // x:Object, y:Object => merge(x, y) // key conflicts cause error | ||
| // _, _ => THROWS | ||
| getSomething: mixins.MANY_MERGED_LOOSE, | ||
| // this calls the next function with the return value of | ||
| // the previous | ||
| // if not present the default value is the identity function | ||
| // in REDUCE_LEFT this looks like mixinFn(sourceFn(...args)); | ||
| // in REDUCE_RIGHT this looks like sourceFn(mixinFn(...args)); | ||
| // of course the `this` value is still preserved | ||
| countChickens: mixins.REDUCE_LEFT, | ||
| countDucks: mixins.REDUCE_RIGHT, | ||
| // define your own handler for it | ||
| // the two operands are the value of onKeyPress on each object | ||
| // these could be functions, undefined, or in strange cases something else | ||
| // don't forget to call them with `this` set correctly | ||
| // here we allow event.stopImmediatePropagation() to prevent the next mixin from | ||
| // being called | ||
| // key is 'onKeyPress' here, this allows reuse of these functions | ||
| // args is the arguments we were called with; treat it like an arraylike object | ||
| // thrower is a special function which attempts to improve the error stack by including | ||
| // the location where it was actually mixed in | ||
| onKeyPress: function (left, right, key) { | ||
| left = left || function () {}; | ||
| right = right || function () {}; | ||
| return function (args, thrower) { | ||
| var event = args[0]; | ||
| if (!event) thrower(TypeError(key + " called without an event object")); | ||
| var ret = left.apply(this, args); | ||
| if (event && !event.immediatePropagationIsStopped) { | ||
| var ret2 = right.apply(this, args); | ||
| } | ||
| return ret || ret2; | ||
| }; | ||
| } | ||
| }, { | ||
| // optional extra arguments and their defaults | ||
| // what should we do when the function is unknown? | ||
| // most likely ONCE or NEVER | ||
| unknownFunction: mixins.ONCE, | ||
| // what should we do when there's a non-function property? | ||
| // this function isn't exposed but the signature is (left, right, key) => any, with this pattern: | ||
| // undefined, y => y | ||
| // x, undefined => x | ||
| // _, _ => THROWS | ||
| // note: it doesn't need to return a function | ||
| nonFunctionProperty: "INTERNAL" | ||
| }); | ||
| // simple usage example | ||
| var mixin = { | ||
| getState: function getState(foo) { | ||
| return { bar: foo + 1 }; | ||
| } | ||
| }; | ||
| var Duck = (function () { | ||
| function Duck() {} | ||
| _prototypeProperties(Duck, null, { | ||
| render: { | ||
| value: function render() { | ||
| console.log(this.getState(5)); // {baz: 4, bar: 6} | ||
| }, | ||
| writable: true, | ||
| configurable: true | ||
| }, | ||
| getState: { | ||
| value: function getState(foo) { | ||
| return { baz: foo - 1 }; | ||
| }, | ||
| writable: true, | ||
| configurable: true | ||
| } | ||
| }); | ||
| return Duck; | ||
| })(); | ||
| // apply the mixin | ||
| mixIntoGameObject(Duck.prototype, mixin); | ||
| new Duck().render(); | ||
| },{"../..":2}],2:[function(require,module,exports){ | ||
| "use strict"; | ||
| var objToStr = function (x) { | ||
| return Object.prototype.toString.call(x); | ||
| }; | ||
| var mixins = module.exports = function makeMixinFunction(rules, _opts) { | ||
| var opts = _opts || {}; | ||
| if (!opts.unknownFunction) { | ||
| opts.unknownFunction = mixins.ONCE; | ||
| } | ||
| if (!opts.nonFunctionProperty) { | ||
| opts.nonFunctionProperty = function (left, right, key) { | ||
| if (left !== undefined && right !== undefined) { | ||
| var getTypeName = function (obj) { | ||
| if (obj && obj.constructor && obj.constructor.name) { | ||
| return obj.constructor.name; | ||
| } else { | ||
| return objToStr(obj).slice(8, -1); | ||
| } | ||
| }; | ||
| throw new TypeError("Cannot mixin key " + key + " because it is provided by multiple sources, " + "and the types are " + getTypeName(left) + " and " + getTypeName(right)); | ||
| } | ||
| }; | ||
| } | ||
| // TODO: improve | ||
| var thrower = function (error) { | ||
| throw error; | ||
| }; | ||
| return function applyMixin(source, mixin) { | ||
| Object.keys(mixin).forEach(function (key) { | ||
| var left = source[key], | ||
| right = mixin[key], | ||
| rule = rules[key]; | ||
| // this is just a weird case where the key was defined, but there's no value | ||
| // behave like the key wasn't defined | ||
| if (left === undefined && right === undefined) return; | ||
| var wrapIfFunction = function (thing) { | ||
| return typeof thing !== "function" ? thing : function () { | ||
| return thing.call(this, arguments, thrower); | ||
| }; | ||
| }; | ||
| // do we have a rule for this key? | ||
| if (rule) { | ||
| // may throw here | ||
| var fn = rule(left, right, key); | ||
| source[key] = wrapIfFunction(fn); | ||
| return; | ||
| } | ||
| var leftIsFn = typeof left === "function"; | ||
| var rightIsFn = typeof right === "function"; | ||
| // check to see if they're some combination of functions or undefined | ||
| // we already know there's no rule, so use the unknown function behavior | ||
| if (leftIsFn && right === undefined || rightIsFn && left === undefined || leftIsFn && rightIsFn) { | ||
| // may throw, the default is ONCE so if both are functions | ||
| // the default is to throw | ||
| source[key] = wrapIfFunction(opts.unknownFunction(left, right, key)); | ||
| return; | ||
| } | ||
| // we have no rule for them, one may be a function but one or both aren't | ||
| // our default is MANY_MERGED_LOOSE which will merge objects, concat arrays | ||
| // and throw if there's a type mismatch or both are primitives (how do you merge 3, and "foo"?) | ||
| source[key] = opts.nonFunctionProperty(left, right, key); | ||
| }); | ||
| }; | ||
| }; | ||
| // define our built-in mixin types | ||
| mixins.ONCE = function (left, right, key) { | ||
| if (left && right) { | ||
| throw new TypeError("Cannot mixin " + key + " because it has a unique constraint."); | ||
| } | ||
| var fn = left || right; | ||
| return function (args) { | ||
| return fn.apply(this, args); | ||
| }; | ||
| }; | ||
| mixins.MANY = function (left, right, key) { | ||
| return function (args) { | ||
| if (right) right.apply(this, args); | ||
| return left ? left.apply(this, args) : undefined; | ||
| }; | ||
| }; | ||
| mixins.MANY_MERGED = function (left, right, key) { | ||
| return function (args, thrower) { | ||
| var res1 = right && right.apply(this, args); | ||
| var res2 = left && left.apply(this, args); | ||
| if (res1 && res2) { | ||
| var assertObject = function (obj, obj2) { | ||
| var type = objToStr(obj); | ||
| if (type !== "[object Object]") { | ||
| var displayType = obj.constructor ? obj.constructor.name : "Unknown"; | ||
| var displayType2 = obj2.constructor ? obj2.constructor.name : "Unknown"; | ||
| thrower("cannot merge returned value of type " + displayType + " with an " + displayType2); | ||
| } | ||
| }; | ||
| assertObject(res1, res2); | ||
| assertObject(res2, res1); | ||
| var result = {}; | ||
| Object.keys(res1).forEach(function (k) { | ||
| if (Object.prototype.hasOwnProperty.call(res2, k)) { | ||
| thrower("cannot merge returns because both have the " + JSON.stringify(k) + " key"); | ||
| } | ||
| result[k] = res1[k]; | ||
| }); | ||
| Object.keys(res2).forEach(function (k) { | ||
| // we can skip the conflict check because all conflicts would already be found | ||
| result[k] = res2[k]; | ||
| }); | ||
| return result; | ||
| } | ||
| return res2 || res1; | ||
| }; | ||
| }; | ||
| mixins.REDUCE_LEFT = function (_left, _right, key) { | ||
| var left = _left || function () { | ||
| return x; | ||
| }; | ||
| var right = _right || function (x) { | ||
| return x; | ||
| }; | ||
| return function (args) { | ||
| return right.call(this, left.apply(this, args)); | ||
| }; | ||
| }; | ||
| mixins.REDUCE_RIGHT = function (_left, _right, key) { | ||
| var left = _left || function () { | ||
| return x; | ||
| }; | ||
| var right = _right || function (x) { | ||
| return x; | ||
| }; | ||
| return function (args) { | ||
| return left.call(this, right.apply(this, args)); | ||
| }; | ||
| }; | ||
| },{}]},{},[1]) |
| <html> | ||
| <body> | ||
| <h4>Check console</h4> | ||
| <script src="bundle.js"></script> | ||
| </body> | ||
| </html> |
| var mixins = require('../..'); | ||
| // define a mixin behavior | ||
| var mixIntoGameObject = mixins({ | ||
| // this can only be defined once, will throw otherwise | ||
| render: mixins.ONCE, | ||
| // can be defined in the source and mixins | ||
| // only the source return value will be returned | ||
| // the mixin function is called first (as in all of the following except REDUCE_LEFT) | ||
| onClick: mixins.MANY, | ||
| // like MANY but expects objects to be returned | ||
| // which will be merged with the other objects | ||
| // will throw when duplicate keys are found | ||
| getState: mixins.MANY_MERGED, | ||
| // TODO: this isn't currently implemented, PR welcome (or I'll get around to it) | ||
| // like MANY_MERGED but also handles arrays, and non-function properties | ||
| // the behavior expressed in pseudo pattern matching syntax: | ||
| // undefined, y:any => y | ||
| // x:any, undefined => x | ||
| // x:Array, y:Array => x.concat(y) | ||
| // x:Object, y:Object => merge(x, y) // key conflicts cause error | ||
| // _, _ => THROWS | ||
| getSomething: mixins.MANY_MERGED_LOOSE, | ||
| // this calls the next function with the return value of | ||
| // the previous | ||
| // if not present the default value is the identity function | ||
| // in REDUCE_LEFT this looks like mixinFn(sourceFn(...args)); | ||
| // in REDUCE_RIGHT this looks like sourceFn(mixinFn(...args)); | ||
| // of course the `this` value is still preserved | ||
| countChickens: mixins.REDUCE_LEFT, | ||
| countDucks: mixins.REDUCE_RIGHT, | ||
| // define your own handler for it | ||
| // the two operands are the value of onKeyPress on each object | ||
| // these could be functions, undefined, or in strange cases something else | ||
| // don't forget to call them with `this` set correctly | ||
| // here we allow event.stopImmediatePropagation() to prevent the next mixin from | ||
| // being called | ||
| // key is 'onKeyPress' here, this allows reuse of these functions | ||
| // args is the arguments we were called with; treat it like an arraylike object | ||
| // thrower is a special function which attempts to improve the error stack by including | ||
| // the location where it was actually mixed in | ||
| onKeyPress: function(left, right, key) { | ||
| left = left || function(){}; | ||
| right = right || function(){}; | ||
| return function(args, thrower){ | ||
| var event = args[0]; | ||
| if (!event) thrower(TypeError(key + ' called without an event object')); | ||
| var ret = left.apply(this, args); | ||
| if (event && !event.immediatePropagationIsStopped) { | ||
| var ret2 = right.apply(this, args); | ||
| } | ||
| return ret || ret2; | ||
| } | ||
| } | ||
| }, { | ||
| // optional extra arguments and their defaults | ||
| // what should we do when the function is unknown? | ||
| // most likely ONCE or NEVER | ||
| unknownFunction: mixins.ONCE, | ||
| // what should we do when there's a non-function property? | ||
| // this function isn't exposed but the signature is (left, right, key) => any, with this pattern: | ||
| // undefined, y => y | ||
| // x, undefined => x | ||
| // _, _ => THROWS | ||
| // note: it doesn't need to return a function | ||
| nonFunctionProperty: "INTERNAL" | ||
| }); | ||
| // simple usage example | ||
| var mixin = { | ||
| getState(foo){ | ||
| return {bar: foo+1} | ||
| } | ||
| }; | ||
| class Duck { | ||
| render(){ | ||
| console.log(this.getState(5)); // {baz: 4, bar: 6} | ||
| } | ||
| getState(foo){ | ||
| return {baz: foo - 1} | ||
| } | ||
| } | ||
| // apply the mixin | ||
| mixIntoGameObject(Duck.prototype, mixin); | ||
| new Duck().render(); | ||
+1
-1
@@ -42,3 +42,3 @@ var objToStr = function(x){ return Object.prototype.toString.call(x); }; | ||
| : function(){ | ||
| thing.call(this, arguments, thrower); | ||
| return thing.call(this, arguments, thrower); | ||
| }; | ||
@@ -45,0 +45,0 @@ }; |
+2
-1
| { | ||
| "name": "smart-mixin", | ||
| "version": "1.0.0", | ||
| "version": "1.0.2", | ||
| "description": "", | ||
@@ -18,2 +18,3 @@ "main": "index.js", | ||
| "devDependencies": { | ||
| "6to5ify": "^4.0.0", | ||
| "expect.js": "^0.3.1", | ||
@@ -20,0 +21,0 @@ "mocha": "^2.1.0", |
+8
-5
@@ -1,2 +0,2 @@ | ||
| # smart-mixin | ||
|  | ||
@@ -21,3 +21,3 @@ Mixins with smart merging strategies and errors over silent failure. | ||
| // define a mixin behavior | ||
| var mixIntoGameObject = ({ | ||
| var mixIntoGameObject = mixins({ | ||
| // this can only be defined once, will throw otherwise | ||
@@ -53,3 +53,3 @@ render: mixins.ONCE, | ||
| countChickens: mixins.REDUCE_LEFT, | ||
| countDucks: mixins.REDUCE_RIGHT | ||
| countDucks: mixins.REDUCE_RIGHT, | ||
@@ -105,3 +105,3 @@ // define your own handler for it | ||
| class Duck(){ | ||
| class Duck { | ||
| render(){ | ||
@@ -118,2 +118,5 @@ console.log(this.getState(5)); // {baz: 4, bar: 6} | ||
| mixIntoGameObject(Duck.prototype, mixin); | ||
| // use it | ||
| new Duck().render(); | ||
| ``` | ||
@@ -124,3 +127,3 @@ | ||
| Nothing too crazy, this was mostly built for use in react-class-mixins, but hopefully | ||
| is useful to other people. It's fully tested and ready for production use in node and the browser. | ||
| is useful to other people. I'll be adding more test coverage (the mixin.FN apis are fully tested, but not the actual mixin function). Any bug reports will be fixed ASAP. | ||
@@ -127,0 +130,0 @@ # License |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
23531
142.06%9
80%441
252.8%128
2.4%4
33.33%1
Infinity%