Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

function-composer

Package Overview
Dependencies
Maintainers
1
Versions
5
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

function-composer - npm Package Compare versions

Comparing version 0.2.0 to 0.3.0

examples/any_type.js

2

examples/custom_type.js

@@ -10,3 +10,3 @@ var compose = require('../function-composer');

// register a test for this new type
compose.tests['Person'] = function (x) {
compose.types['Person'] = function (x) {
return x instanceof Person;

@@ -13,0 +13,0 @@ };

@@ -22,5 +22,12 @@ /**

// order types
// object will be ordered last as other types may be an object too.
// anytype (*) will be ordered last, and then object, as other types may be
// an object too.
function compareTypes(a, b) {
return a === 'Object' ? 1 : b === 'Object' ? -1 : 0
if (a === '*') return 1;
if (b === '*') return -1;
if (a === 'Object') return 1;
if (b === 'Object') return -1;
return 0;
}

@@ -34,45 +41,138 @@

/**
* Compose a function from sub-functions each handling a single type signature.
* @param {string} [name] An optional name for the function
* @param {Object.<string, function>} signatures
* A map with the type signature as key and the sub-function as value
* @return {function} Returns the composed function
* Collection with function definitions (local shortcuts to functions)
* @constructor
*/
function compose(name, signatures) {
if (!signatures) {
signatures = name;
name = null;
}
function Defs() {}
var normalized = {}; // normalized function signatures
var defs = { // function definitions (local shortcuts to functions)
signature: [],
test: [],
convert: []
};
function addDef(fn, type) {
var index = defs[type].indexOf(fn);
if (index == -1) {
index = defs[type].length;
defs[type].push(fn);
}
return type + index;
/**
* Add a function definition.
* @param {function} fn
* @param {string} [type='fn']
* @returns {string} Returns the function name, for example 'fn0' or 'signature2'
*/
Defs.prototype.add = function (fn, type) {
type = type || 'fn';
if (!this[type]) this[type] = [];
var index = this[type].indexOf(fn);
if (index == -1) {
index = this[type].length;
this[type].push(fn);
}
return type + index;
};
// analise all signatures
var argumentCount = 0;
var parameters = {};
Object.keys(signatures).forEach(function (signature) {
var fn = signatures[signature];
var params = (signature !== '') ? signature.split(',').map(function (param) {
/**
* Create code lines for all definitions
* @param [name='defs']
* @returns {Array} Returns the code lines containing all function definitions
*/
Defs.prototype.code = function (name) {
var me = this;
var code = [];
name = name || 'defs';
Object.keys(this).forEach(function (type) {
var def = me[type];
def.forEach(function (def, index) {
code.push('var ' + type + index + ' = ' + name + '[\'' + type + '\'][' + index + '];');
});
});
return code;
};
/**
* A function signature
* @param {string | Array.<string>} params Array with the type(s) of each parameter,
* or a comma separated string with types
* @param {function} fn The actual function
* @constructor
*/
function Signature(params, fn) {
if (typeof params === 'string') {
this.params = (params !== '') ? params.split(',').map(function (param) {
return param.trim();
}) : [];
var normSignature = params.join(',');
normalized[normSignature] = fn;
argumentCount = Math.max(argumentCount, params.length);
}
else {
this.params = params;
}
this.fn = fn;
}
// get the entry for this number of arguments
var obj = parameters[params.length];
// TODO: implement function Signature.split
// TODO: implement function Signature.merge
// TODO: implement function Signature.toString
/**
* split all raw signatures into an array with splitted params
* @param {Object.<string, function>} rawSignatures
* @return {Array.<{params: Array.<string>, fn: function}>} Returns splitted signatures
*/
function splitSignatures(rawSignatures) {
return Object.keys(rawSignatures).map(function (params) {
var fn = rawSignatures[params];
return new Signature(params, fn);
});
// TODO: split params containing an '|' into multiple entries
}
/**
* create a map with normalized signatures as key and the function as value
* @param {Array.<{params: Array.<string>, fn: function}>} signatures An array with splitted signatures
* @return {{}} Returns a map with normalized signatures
*/
function normalizeSignatures(signatures) {
var normalized = {};
signatures.map(function (entry) {
var signature = entry.params.join(',');
if (signature in normalized) {
throw new Error('Error: signature "' + signature + '" defined twice');
}
normalized[signature] = entry.fn;
});
return normalized
}
/**
* create an array with for every parameter an array with possible types
* @param {Array.<{params: Array.<string>, fn: function}>} signatures An array with splitted signatures
* @return {Array.<Array.<string>>} Returns an array with allowed types per parameter
*/
function splitTypes(signatures) {
var types = [];
signatures.forEach(function (entry) {
entry.params.forEach(function (param, i) {
if (!types[i]) {
types[i] = [];
}
if (types[i].indexOf(param) == -1) {
types[i].push(param);
}
});
});
return types;
}
/**
* create a recursive tree for traversing the number and type of parameters
* @param {Array.<{params: Array.<string>, fn: function}>} signatures An array with splitted signatures
* @returns {{}}
*/
function createParamsTree(signatures) {
var tree = {};
signatures.forEach(function (entry) {
var params = entry.params.concat([]);
// get the tree entry for the current number of arguments
var obj = tree[params.length];
if (!obj) {
obj = parameters[params.length] = {
obj = tree[params.length] = {
signature: [],

@@ -97,5 +197,26 @@ fn: null,

obj.fn = fn;
// add the function as leaf
obj.fn = entry.fn;
});
return tree;
}
/**
* Compose a function from sub-functions each handling a single type signature.
* @param {string} [name] An optional name for the function
* @param {Object.<string, function>} signatures
* A map with the type signature as key and the sub-function as value
* @return {function} Returns the composed function
*/
function compose(name, signatures) {
// handle arguments
if (!signatures) {
signatures = name;
name = null;
}
var defs = new Defs();
var structure = splitSignatures(signatures);
function switchTypes(signature, args, prefix) {

@@ -105,3 +226,3 @@ var code = [];

if (signature.fn !== null) {
var def = addDef(signature.fn, 'signature');
var def = defs.add(signature.fn, 'signature');
code.push(prefix + 'return ' + def + '(' + args.join(', ') +'); // signature: ' + signature.signature);

@@ -113,9 +234,22 @@ }

.sort(compareTypes)
.forEach(function (type) {
.forEach(function (type, index) {
var arg = 'arg' + args.length;
var def = addDef(getTest(type), 'test') + '(' + arg + ')';
code.push(prefix + 'if (' + def + ') { // type: ' + type);
code = code.concat(switchTypes(signature.types[type], args.concat(arg), prefix + ' '));
code.push(prefix + '}');
var before;
var after;
var nextPrefix = prefix + ' ';
if (type == '*') {
before = (index > 0 ? 'else {' : '');
after = (index > 0 ? '}' : '');
if (index == 0) {nextPrefix = prefix;}
}
else {
var def = defs.add(getTest(type), 'test') + '(' + arg + ')';
before = 'if (' + def + ') { // type: ' + type;
after = '}';
}
if (before) code.push(prefix + before);
code = code.concat(switchTypes(signature.types[type], args.concat(arg), nextPrefix));
if (after) code.push(prefix + after);
});

@@ -135,4 +269,4 @@

var arg = 'arg' + args.length;
var test = addDef(getTest(conversion.from), 'test') + '(' + arg + ')';
var convert = addDef(conversion.convert, 'convert') + '(' + arg + ')';
var test = defs.add(getTest(conversion.from), 'test') + '(' + arg + ')';
var convert = defs.add(conversion.convert, 'convert') + '(' + arg + ')';

@@ -144,3 +278,2 @@ code.push(prefix + 'if (' + test + ') { // type: ' + conversion.from + ', convert to ' + conversion.to);

});
}

@@ -151,14 +284,16 @@

var args = [];
for (var i = 0; i < argumentCount; i++) {
args.push('arg' + i);
var types = splitTypes(structure);
var params = [];
for (var i = 0; i < types.length; i++) { // we can't use .map here, some entries may be undefined
params.push('arg' + i);
}
var code = [];
var counts = Object.keys(parameters);
code.push('return function ' + (name || '') + '(' + args.join(', ') + ') {');
counts
var tree = createParamsTree(structure);
var paramCounts = Object.keys(tree);
code.push('return function ' + (name || '') + '(' + params.join(', ') + ') {');
paramCounts
.sort(compareNumbers)
.forEach(function (count, index) {
var signature = parameters[count];
var signature = tree[count];
var args = [];

@@ -170,3 +305,3 @@ var statement = (index == 0) ? 'if' : 'else if';

code.push(' }');
if (index == counts.length - 1) {
if (index == paramCounts.length - 1) {
code.push(' else {');

@@ -182,15 +317,10 @@ code.push(' throw new TypeError(\'Wrong number of arguments\');'); // TODO: output the allowed numbers

factory.push('(function (defs) {');
Object.keys(defs).forEach(function (type) {
defs[type].forEach(function
(def, index) {
factory.push('var ' + type + index + ' = defs[\'' + type + '\'][' + index + '];');
});
});
factory = factory.concat(defs.code('defs'));
factory = factory.concat(code);
factory.push( '})');
factory.push('})');
var fn = eval(factory.join('\n'))(defs);
// attach the original functions
fn.signatures = normalized;
// attach the signatures with sub-functions to the constructed function
fn.signatures = normalizeSignatures(structure); // normalized signatures

@@ -201,3 +331,3 @@ return fn;

// data type tests
compose.tests = {
compose.types = {
'null': function (x) {return x === null},

@@ -215,5 +345,5 @@ 'boolean': function (x) {return typeof x === 'boolean'},

function getTest(type) {
var test = compose.tests[type];
var test = compose.types[type];
if (!test) {
var matches = Object.keys(compose.tests)
var matches = Object.keys(compose.types)
.filter(function (t) {

@@ -220,0 +350,0 @@ return t.toLowerCase() == type.toLowerCase();

@@ -1,1 +0,1 @@

(function(root,factory){if(typeof define==="function"&&define.amd){define([],factory)}else if(typeof exports==="object"){module.exports=factory()}else{root.compose=factory()}})(this,function(){"use strict";function compareTypes(a,b){return a==="Object"?1:b==="Object"?-1:0}function compareNumbers(a,b){return a>b}function compose(name,signatures){if(!signatures){signatures=name;name=null}var normalized={};var defs={signature:[],test:[],convert:[]};function addDef(fn,type){var index=defs[type].indexOf(fn);if(index==-1){index=defs[type].length;defs[type].push(fn)}return type+index}var argumentCount=0;var parameters={};Object.keys(signatures).forEach(function(signature){var fn=signatures[signature];var params=signature!==""?signature.split(",").map(function(param){return param.trim()}):[];var normSignature=params.join(",");normalized[normSignature]=fn;argumentCount=Math.max(argumentCount,params.length);var obj=parameters[params.length];if(!obj){obj=parameters[params.length]={signature:[],fn:null,types:{}}}while(params.length>0){var param=params.shift();if(!obj.types[param]){obj.types[param]={signature:obj.signature.concat(param),fn:null,types:{}}}obj=obj.types[param]}obj.fn=fn});function switchTypes(signature,args,prefix){var code=[];if(signature.fn!==null){var def=addDef(signature.fn,"signature");code.push(prefix+"return "+def+"("+args.join(", ")+"); // signature: "+signature.signature)}else{Object.keys(signature.types).sort(compareTypes).forEach(function(type){var arg="arg"+args.length;var def=addDef(getTest(type),"test")+"("+arg+")";code.push(prefix+"if ("+def+") { // type: "+type);code=code.concat(switchTypes(signature.types[type],args.concat(arg),prefix+" "));code.push(prefix+"}")});var added={};compose.conversions.filter(function(conversion){return signature.types[conversion.to]&&!signature.types[conversion.from]}).forEach(function(conversion){if(!added[conversion.from]){added[conversion.from]=true;var arg="arg"+args.length;var test=addDef(getTest(conversion.from),"test")+"("+arg+")";var convert=addDef(conversion.convert,"convert")+"("+arg+")";code.push(prefix+"if ("+test+") { // type: "+conversion.from+", convert to "+conversion.to);code=code.concat(switchTypes(signature.types[conversion.to],args.concat(convert),prefix+" "));code.push(prefix+"}")}})}return code}var args=[];for(var i=0;i<argumentCount;i++){args.push("arg"+i)}var code=[];var counts=Object.keys(parameters);code.push("return function "+(name||"")+"("+args.join(", ")+") {");counts.sort(compareNumbers).forEach(function(count,index){var signature=parameters[count];var args=[];var statement=index==0?"if":"else if";code.push(" "+statement+" (arguments.length == "+count+") {");code=code.concat(switchTypes(signature,args," "));code.push(" }");if(index==counts.length-1){code.push(" else {");code.push(" throw new TypeError('Wrong number of arguments');");code.push(" }")}});code.push(" throw new TypeError('Wrong function signature');");code.push("}");var factory=[];factory.push("(function (defs) {");Object.keys(defs).forEach(function(type){defs[type].forEach(function(def,index){factory.push("var "+type+index+" = defs['"+type+"']["+index+"];")})});factory=factory.concat(code);factory.push("})");var fn=eval(factory.join("\n"))(defs);fn.signatures=normalized;return fn}compose.tests={"null":function(x){return x===null},"boolean":function(x){return typeof x==="boolean"},number:function(x){return typeof x==="number"},string:function(x){return typeof x==="string"},"function":function(x){return typeof x==="function"},Array:function(x){return Array.isArray(x)},Date:function(x){return x instanceof Date},RegExp:function(x){return x instanceof RegExp},Object:function(x){return typeof x==="object"}};function getTest(type){var test=compose.tests[type];if(!test){var matches=Object.keys(compose.tests).filter(function(t){return t.toLowerCase()==type.toLowerCase()}).map(function(t){return'"'+t+'"'});throw new Error('Unknown type "'+type+'"'+(matches.length?". Did you mean "+matches.join(", or ")+"?":""))}return test}compose.conversions=[];return compose});
!function(e,t){"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?module.exports=t():e.compose=t()}(this,function(){"use strict";function compareTypes(e,t){return"*"===e?1:"*"===t?-1:"Object"===e?1:"Object"===t?-1:0}function compareNumbers(e,t){return e>t}function Defs(){}function Signature(e,t){this.params="string"==typeof e?""!==e?e.split(",").map(function(e){return e.trim()}):[]:e,this.fn=t}function splitSignatures(e){return Object.keys(e).map(function(t){var n=e[t];return new Signature(t,n)})}function normalizeSignatures(e){var t={};return e.map(function(e){var n=e.params.join(",");if(n in t)throw new Error('Error: signature "'+n+'" defined twice');t[n]=e.fn}),t}function splitTypes(e){var t=[];return e.forEach(function(e){e.params.forEach(function(e,n){t[n]||(t[n]=[]),-1==t[n].indexOf(e)&&t[n].push(e)})}),t}function createParamsTree(e){var t={};return e.forEach(function(e){var n=e.params.concat([]),r=t[n.length];for(r||(r=t[n.length]={signature:[],fn:null,types:{}});n.length>0;){var o=n.shift();r.types[o]||(r.types[o]={signature:r.signature.concat(o),fn:null,types:{}}),r=r.types[o]}r.fn=e.fn}),t}function compose(name,signatures){function switchTypes(e,t,n){var r=[];if(null!==e.fn){var o=defs.add(e.fn,"signature");r.push(n+"return "+o+"("+t.join(", ")+"); // signature: "+e.signature)}else{Object.keys(e.types).sort(compareTypes).forEach(function(o,s){var u,c,a="arg"+t.length,i=n+" ";if("*"==o)u=s>0?"else {":"",c=s>0?"}":"",0==s&&(i=n);else{var f=defs.add(getTest(o),"test")+"("+a+")";u="if ("+f+") { // type: "+o,c="}"}u&&r.push(n+u),r=r.concat(switchTypes(e.types[o],t.concat(a),i)),c&&r.push(n+c)});var s={};compose.conversions.filter(function(t){return e.types[t.to]&&!e.types[t.from]}).forEach(function(o){if(!s[o.from]){s[o.from]=!0;var u="arg"+t.length,c=defs.add(getTest(o.from),"test")+"("+u+")",a=defs.add(o.convert,"convert")+"("+u+")";r.push(n+"if ("+c+") { // type: "+o.from+", convert to "+o.to),r=r.concat(switchTypes(e.types[o.to],t.concat(a),n+" ")),r.push(n+"}")}})}return r}signatures||(signatures=name,name=null);for(var defs=new Defs,structure=splitSignatures(signatures),types=splitTypes(structure),params=[],i=0;i<types.length;i++)params.push("arg"+i);var code=[],tree=createParamsTree(structure),paramCounts=Object.keys(tree);code.push("return function "+(name||"")+"("+params.join(", ")+") {"),paramCounts.sort(compareNumbers).forEach(function(e,t){var n=tree[e],r=[],o=0==t?"if":"else if";code.push(" "+o+" (arguments.length == "+e+") {"),code=code.concat(switchTypes(n,r," ")),code.push(" }"),t==paramCounts.length-1&&(code.push(" else {"),code.push(" throw new TypeError('Wrong number of arguments');"),code.push(" }"))}),code.push(" throw new TypeError('Wrong function signature');"),code.push("}");var factory=[];factory.push("(function (defs) {"),factory=factory.concat(defs.code("defs")),factory=factory.concat(code),factory.push("})");var fn=eval(factory.join("\n"))(defs);return fn.signatures=normalizeSignatures(structure),fn}function getTest(e){var t=compose.types[e];if(!t){var n=Object.keys(compose.types).filter(function(t){return t.toLowerCase()==e.toLowerCase()}).map(function(e){return'"'+e+'"'});throw new Error('Unknown type "'+e+'"'+(n.length?". Did you mean "+n.join(", or ")+"?":""))}return t}return Defs.prototype.add=function(e,t){t=t||"fn",this[t]||(this[t]=[]);var n=this[t].indexOf(e);return-1==n&&(n=this[t].length,this[t].push(e)),t+n},Defs.prototype.code=function(e){var t=this,n=[];return e=e||"defs",Object.keys(this).forEach(function(r){var o=t[r];o.forEach(function(t,o){n.push("var "+r+o+" = "+e+"['"+r+"']["+o+"];")})}),n},compose.types={"null":function(e){return null===e},"boolean":function(e){return"boolean"==typeof e},number:function(e){return"number"==typeof e},string:function(e){return"string"==typeof e},"function":function(e){return"function"==typeof e},Array:function(e){return Array.isArray(e)},Date:function(e){return e instanceof Date},RegExp:function(e){return e instanceof RegExp},Object:function(e){return"object"==typeof e}},compose.conversions=[],compose});
# History
## 2014-11-05, version 0.3.0
- Implemented support for anytype arguments (denoted with `*`).
## 2014-10-23, version 0.2.0

@@ -5,0 +10,0 @@

{
"name": "function-composer",
"version": "0.2.0",
"version": "0.3.0",
"description": "Compose functions with multiple type signatures",

@@ -26,3 +26,3 @@ "author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)",

"scripts": {
"minify": "uglifyjs function-composer.js -o function-composer.min.js",
"minify": "uglifyjs function-composer.js -o function-composer.min.js -c -m",
"test": "mocha test --recursive",

@@ -29,0 +29,0 @@ "coverage": "istanbul cover _mocha -- test --recursive; echo \"\nCoverage report is available at ./coverage/lcov-report/index.html\""

@@ -14,3 +14,3 @@ function-composer

# Load
## Load

@@ -22,3 +22,3 @@ Install via npm:

# Usage
## Usage

@@ -57,3 +57,3 @@ Example usage:

# Performance
## Performance

@@ -67,6 +67,8 @@ Type checking input arguments adds some overhead to a function. For very small

# API
## API
## Construction
### Construction
A function is constructed as:
```js

@@ -77,6 +79,7 @@ compose(signatures: Object.<string, function>) : function

## Properties
### Properties
- `compose.tests: Object`
A map with type checking tests. Add custom types like:
- `compose.types: Object`
A map with the object types as key and a type checking test as value.
Custom types can be added like:

@@ -88,3 +91,3 @@ ```js

compose.tests['Person'] = function (x) {
compose.types['Person'] = function (x) {
return x instanceof Person;

@@ -107,7 +110,32 @@ };

### Types
# Roadmap
function-composer has the following built-in types:
## Version 1
- `null`
- `boolean`
- `number`
- `string`
- `function`
- `Array`
- `Date`
- `RegExp`
- `Object`
- `*` (anytype)
### Output
The functions generated with `compose({...})` have:
- A `toString()` function which returns well readable code, giving insight in
what the function exactly does.
- A property `signatures`, which holds a map with the (normalized)
signatures as key and the original sub-functions as value.
## Roadmap
### Version 1
- Extend function signatures:

@@ -118,5 +146,8 @@ - Any type arguments like `'*, boolean'`

- Multiple types per argument like `number | string, number'`
- Detailed error messages.
- Create a good benchmark, to get insight in the overhead.
- Allow conversions not to be able to convert any input (for example string to
number is not always possible).
## Version 2
### Version 2

@@ -129,7 +160,13 @@ - Extend function signatures:

## Test
# Minify
To test the library, run:
npm test
## Minify
To generate the minified version of the library, run:
npm run minify
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc