From a14de0cd9b08f1a8b19d16b6a027ba8019ab1362 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 21 Jun 2016 15:07:26 -0400 Subject: [PATCH 01/77] Version bump, rename test script --- bower.json | 2 +- package.json | 4 ++-- test => run_tests | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename test => run_tests (100%) diff --git a/bower.json b/bower.json index 535a913..1be269b 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "0.0.1", + "version": "2.0.1", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index fd89520..d075954 100644 --- a/package.json +++ b/package.json @@ -12,14 +12,14 @@ "homepage": "http://myw.github.io" } ], - "version": "0.0.1", + "version": "2.0.1", "license": "MIT", "ignore": [], "main": [ "./curious.js" ], "scripts": { - "test": "mocha --recursive tests" + "test": "./run_tests" }, "dependencies": { }, diff --git a/test b/run_tests similarity index 100% rename from test rename to run_tests From cc4a12c478b948949ed7b4e881f0a3e5a27a2ef0 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 23 Jun 2016 16:18:21 -0400 Subject: [PATCH 02/77] Refactor how wrappers work --- curious.js | 138 ++++++++++++++++++++++++++++++++++++---- tests/curious_client.js | 75 ++++++++++++++-------- 2 files changed, 172 insertions(+), 41 deletions(-) diff --git a/curious.js b/curious.js index 874316e..f219824 100644 --- a/curious.js +++ b/curious.js @@ -5,6 +5,19 @@ */ (function () { 'use strict'; + + var ex; // Alias for exporting to window or CommonJS exports + + // Export either to browser window or CommonJS module + if (typeof window !== 'undefined' && window) { + ex = window; + } else if (typeof exports !== 'undefined' && exports) { + ex = exports; + } else if (typeof self !== 'undefined' && self) { + ex = self; + } + + // QUERY TERMS /** @@ -1032,7 +1045,9 @@ * *

Any function that meets the signature, makes a POST request and * returns a thenable that resolves to the parsed JSON of the curious - * server's response will work.

+ * server's response will work. Note that axios.post and $http.post wrap the + * response in an object, and so require wrapper functions to be used. + * See {@link module:curious.CuriousClient.wrappers} for the wrappers.

* @param {Object} clientDefaultArgs * Default parameters to send to the serever with every query performed by * this client; see {@link module:curious.CuriousClient#performQuery} for an @@ -1118,25 +1133,120 @@ }; }; - CuriousClient.axios_post = function(axios) { - // axios returns the server's response nested within an object + /** + * Common code of convenience functions that make it easier to interact + * with a variety of http clients. + * + * @example + * var axiosWrapper = _dataUnwrapper.bind(this, 'axios'); + * + * @private + * @memberof module:curious.CuriousClient.wrappers + * + * @param {string} defaultModuleName + * The default module object name to use if one is not provided. Should + * be bound to a string when actually used as a wrapper. + * + * @param {Object?} moduleObjectOrFunction + * Either the module to use, like axios/$http, or the posting function + * itself, like axios.post. + * + * @return {function(string, Object): Promise} + * An ideal function for making requests in curious client: + * takes the url and arguments, makes an axios post request, and returns + * a promise that resolves directly to the returned query data (unwrapped). + */ + function _unwrapResponseData(defaultModuleName, moduleObjectOrFunction) { + var mod; + var postRequestFunction; + + // Prevent code injection + if (/[^$.\w'"\[\]]/.test(defaultModuleName)) { + throw new Error('Invalid module name: likely code injection attempt'); + } + + // Default to the provided module name, but if one is not provided, + // look in the global namespace, then in the this context, and finally, + // just evaluate the variable with that name and see if it resovles + // to something. + // XXX uses `eval`, only as a last resort. there is no way around this. + /* eslint-disable no-eval */ + mod = moduleObjectOrFunction + || (typeof module !== 'undefined' && module[defaultModuleName]) + || (typeof exports !== 'undefined' && exports[defaultModuleName]) + || (typeof global !== 'undefined' && global[defaultModuleName]) + || (typeof window !== 'undefined' && window[defaultModuleName]) + || eval(defaultModuleName); + /* eslint-enable no-eval */ + + // If the module provided has a `post` method, use that. Otherwise, + // just call the "module" itself: this allows passing in either + // $http or $http.post, for example + postRequestFunction = mod.post || mod; + + // axios/angular return the server's response nested within an object // (response.data); here we return a tiny filter function to pull that // server response out return function (url, args) { - return axios.post(url, args).then(function (response) { return response.data; }); + return postRequestFunction(url, args) + .then(function (response) { + return response.data; + }); }; - }; - - // Export either to browser window or CommonJS module - var ex; - if (typeof window !== 'undefined' && window) { - ex = window; - } else if (typeof exports !== 'undefined' && exports) { - ex = exports; - } else if (typeof self !== 'undefined' && self) { - ex = self; } + /** + * Convenience functions for interfacing with a variety of common HTTP/ajax + * client libraries. + * + * Note that jQuery.post does not need a wrapper. + * + * @namespace module:curious.CuriousClient.wrappers + */ + CuriousClient.wrappers = {}; + + /** + * Convenience function to make it easier to interact with axios responses: + * axios is not required by this module at all. + * + * @example + * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios() ...) + * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios(axios) ...) + * + * @memberof module:curious.CuriousClient.wrappers + * + * @param {Object?} axiosModuleOrFunction + * The axios module, or axios.post. Defaults to using whatever + * axios resolves to. + * + * @return {function(string, Object): Promise} + * An ideal function for making requests in curious client: + * takes the url and arguments, makes an axios post request, and returns + * a promise that resolves directly to the returned query data (unwrapped). + */ + CuriousClient.wrappers.axios = _unwrapResponseData.bind(ex, 'axios'); + + /** + * Convenience function to make it easier to interact with angular responses: + * angular is not required by this module at all. + * + * @example + * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.angular() ...) + * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.angular($http) ...) + * + * @memberof module:curious.CuriousClient.wrappers + * + * @param {Object?} angularHttpServiceOrPostFunction + * The angular $http service object, or $http.post. Defaults to using + * whatever $http resolves to. + * + * @return {function(string, Object): Promise} + * An ideal function for making requests in curious client: + * takes the url and arguments, makes an axios post request, and returns + * a promise that resolves directly to the returned query data (unwrapped). + */ + CuriousClient.wrappers.angular = _unwrapResponseData.bind(ex, '$http'); + ex.CuriousObjects = CuriousObjects; ex.CuriousClient = CuriousClient; ex.CuriousQuery = CuriousQuery; diff --git a/tests/curious_client.js b/tests/curious_client.js index adb1bf8..f60f4e1 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -29,36 +29,57 @@ describe('CuriousClient', function () { describe('#performQuery', function () { it('should work with axios', function (done) { - var client = new curious.CuriousClient(CURIOUS_URL, function (url, args) { - // axios returns the server's response nested within an object - // (response.data); we add a tiny filter function to pull that server - // response out - return axios.post(url, args) - .then(function (response) { return response.data; }); - }, null, true); + var requestFunctions; + // Set the global axios variable to test defaults + if (typeof global !== 'undefined' && !global.axios) { + global.axios = axios; + } - client.performQuery( - 'query does not matter', - ['experiments', 'reactions'] - ) - .then(function (response) { - try { - var expectedObjects = examples.expectedObjects(); - expect(response).to.deep.equal({ - trees: [null, null], - objects: { - experiments: curious.CuriousObjects.values(expectedObjects.experiments), - reactions: curious.CuriousObjects.values(expectedObjects.reactions), - }, + requestFunctions = [ + curious.CuriousClient.wrappers.axios(axios), + curious.CuriousClient.wrappers.axios(axios.post), + curious.CuriousClient.wrappers.axios() + ]; + + try { + requestFunctions.forEach(function (requestFunction) { + var client = new curious.CuriousClient( + CURIOUS_URL, + requestFunction, + null, + true + ); + + client.performQuery( + 'query does not matter', + ['experiments', 'reactions'] + ) + .then(function (response) { + var expectedObjects = examples.expectedObjects(); + expect(response).to.deep.equal({ + trees: [null, null], + objects: { + experiments: curious.CuriousObjects.values(expectedObjects.experiments), + reactions: curious.CuriousObjects.values(expectedObjects.reactions), + }, + }); + }, function (error) { + throw error; }); - done(); - } - catch (error) { - done(error); + + }); + + // Clean up the global axios variable + if (typeof global !== 'undefined' && global.axios) { + delete global.axios; } - }, function (error) { - throw error; - }); + + done(); + } + catch (error) { + done(error); + } + }); }); }); From ca49042f062689b4e9920bdfa5a33aa1c4c20144 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 23 Jun 2016 16:19:19 -0400 Subject: [PATCH 03/77] Bump version to 2.1.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 1be269b..ed3191b 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.0.1", + "version": "2.1.0", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index d075954..8296952 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.0.1", + "version": "2.1.0", "license": "MIT", "ignore": [], "main": [ From 79c650e9effb3bed589b842539e6240c240975d3 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 29 Jun 2016 11:42:39 -0400 Subject: [PATCH 04/77] Add wrapper for Polymer's `iron-request` object and options - wrappers.ironReuquest takes in a Polymer `iron-request` element object and returns an appropriate `POST` request-making function - all of the wrapper functions now take an optional `options` argument to merge in object sent to the request client, for providing additional, client-specific options. --- curious.js | 55 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/curious.js b/curious.js index f219824..9a77083 100644 --- a/curious.js +++ b/curious.js @@ -1146,17 +1146,18 @@ * @param {string} defaultModuleName * The default module object name to use if one is not provided. Should * be bound to a string when actually used as a wrapper. - * * @param {Object?} moduleObjectOrFunction * Either the module to use, like axios/$http, or the posting function * itself, like axios.post. + * @param {Object?} options + * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} * An ideal function for making requests in curious client: * takes the url and arguments, makes an axios post request, and returns * a promise that resolves directly to the returned query data (unwrapped). */ - function _unwrapResponseData(defaultModuleName, moduleObjectOrFunction) { + function _unwrapResponseData(defaultModuleName, moduleObjectOrFunction, options) { var mod; var postRequestFunction; @@ -1188,7 +1189,7 @@ // (response.data); here we return a tiny filter function to pull that // server response out return function (url, args) { - return postRequestFunction(url, args) + return postRequestFunction(url, args, options || {}) .then(function (response) { return response.data; }); @@ -1213,11 +1214,14 @@ * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios() ...) * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios(axios) ...) * + * @function * @memberof module:curious.CuriousClient.wrappers * * @param {Object?} axiosModuleOrFunction - * The axios module, or axios.post. Defaults to using whatever - * axios resolves to. + * Either the axios module itself, or axios.post. + * Defaults to using whatever axios resolves to. + * @param {Object?} options + * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} * An ideal function for making requests in curious client: @@ -1234,19 +1238,54 @@ * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.angular() ...) * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.angular($http) ...) * + * @function * @memberof module:curious.CuriousClient.wrappers * * @param {Object?} angularHttpServiceOrPostFunction - * The angular $http service object, or $http.post. Defaults to using - * whatever $http resolves to. + * Either the Angular $http service object, or $http.post. + * Defaults to using whatever $http resolves to. + * @param {Object?} options + * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} * An ideal function for making requests in curious client: - * takes the url and arguments, makes an axios post request, and returns + * takes the url and arguments, makes a POST request and returns * a promise that resolves directly to the returned query data (unwrapped). */ CuriousClient.wrappers.angular = _unwrapResponseData.bind(ex, '$http'); + /** + * Convenience function to make it easier to interact with Polymer's + * <iron-request> element. + * + * @example + * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.ironRequest(this.$.xhr) ...) + * + * @memberof module:curious.CuriousClient.wrappers + * + * @param {PolymerElement} ironRequestElement + * The iron-request element being used to make the request. + * @param {Object?} options + * Additional options to send to the requesting function. + * + * @return {function(string, Object): Promise} + * An ideal function for making requests in curious client: + * takes the url and arguments, makes a POST request with + * ironRequestElement, and returns a promise that resolves + * directly to the returned query data (unwrapped). + */ + CuriousClient.wrappers.ironRequest = function (ironRequestElement, options) { + return function (url, args) { + var requestParameters = Object.create(Object.prototype, options || {}); + + requestParameters.method = 'POST'; + requestParameters.url = url; + requestParameters.params = args; + + return ironRequestElement.send(requestParameters); + }; + }; + ex.CuriousObjects = CuriousObjects; ex.CuriousClient = CuriousClient; ex.CuriousQuery = CuriousQuery; From d21ad43b67204cec874d7b71263a4087a6c95b15 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 29 Jun 2016 11:45:58 -0400 Subject: [PATCH 05/77] Bump version to 2.1.1 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index ed3191b..56868b7 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.0", + "version": "2.1.1", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index 8296952..3ef101c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.0", + "version": "2.1.1", "license": "MIT", "ignore": [], "main": [ From d5f5d8a700e93045cbc0cd552f89e73813a9f163 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 29 Jun 2016 12:27:16 -0400 Subject: [PATCH 06/77] Add detailed README documentation --- README.md | 35 ++++++++++++++++++++++++++++++++++- curious.js | 2 +- make_doc | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d55bb3c..c164067 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -Javascript consumer for Curious API outputs. +# curious-js + +JavaScript consumer code for Curious APIs. + +## Usage + +There are two main parts to using the Curious from Javascript: `CuriousClient`, +and `CuriousQuery`. + +First, create an instance of `CuriousClient` that points to your curious +server. You are responsible for picking and using a request method that works +for you. `CuriousClient` provides some convenience wrappers, but you can make +any asynchronous transport layer work. + +```javascript +var curiousClient = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios(axios), ...); +``` + +Then, construct a `CuriousQuery` and perform it on the server using the client. +Attach any callbacks to the Promise object returned by the `perform` method. The +results of the query will be passed to the callback: + +```javascript +var q = new curious.CuriousQuery('Experiment(id=302)', 'experiment') + .follow('Experiment.reaction_set', 'reactions') + .follow('Reaction.dataset_set', 'dataset', Dataset) + .follow('Dataset.attachment_set'); + +q.perform(curiousClient).then(function (queriedData) { + // Do stuff with queriedData +}); +``` + +The detailed API is explained in the documentation. diff --git a/curious.js b/curious.js index 9a77083..6010e17 100644 --- a/curious.js +++ b/curious.js @@ -101,7 +101,7 @@ return this; }; QueryTermHaving.prototype = new QueryTerm(); - + QueryTermHaving.prototype.conditional = function () { return true; }; QueryTermHaving.prototype.toString = function () { diff --git a/make_doc b/make_doc index 2774b98..0483bf9 100755 --- a/make_doc +++ b/make_doc @@ -1,3 +1,3 @@ #!/bin/bash mkdir -p doc -jsdoc --destination doc curious.js "$@" +jsdoc --destination doc curious.js README.md "$@" From b5fd7a1018aa54a414a9cf375c5bdc712787192b Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 29 Jun 2016 13:08:09 -0400 Subject: [PATCH 07/77] Add documentation release script --- release_doc | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100755 release_doc diff --git a/release_doc b/release_doc new file mode 100755 index 0000000..ac65b28 --- /dev/null +++ b/release_doc @@ -0,0 +1,28 @@ +#!/bin/bash -v + +cd `dirname $0`; + +archivepath=`mktemp` +current_branch=`git symbolic-ref --short -q HEAD` +current_head=`git rev-parse HEAD` + +./make_doc + +# Arcive documentation somewhere safe +tar -cv -f $archivepath -C doc . + +# Switch to the gh-pages branch & clean everything +git checkout gh-pages && rm -rf ./* + +# Extract the archived documentation +tar xvf $archivepath + +git add . +git commit -m "Update documentation to $current_head" +git push + +rm -vf $archivepath + +git checkout $current_branch + +cd - From 6fb341bd4178260b2fc8655847e00c56dc26a233 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 30 Jun 2016 17:09:37 -0400 Subject: [PATCH 08/77] Fix improperly named property for ironRequest wrapper --- curious.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curious.js b/curious.js index 6010e17..88368ca 100644 --- a/curious.js +++ b/curious.js @@ -1280,7 +1280,7 @@ requestParameters.method = 'POST'; requestParameters.url = url; - requestParameters.params = args; + requestParameters.body = args; return ironRequestElement.send(requestParameters); }; From d92bbe7359183fd393e11a5e8542cc1e56d51ad7 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 30 Jun 2016 17:10:16 -0400 Subject: [PATCH 09/77] Bump version to 2.1.2 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 56868b7..d0b332e 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.1", + "version": "2.1.2", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index 3ef101c..55ed967 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.1", + "version": "2.1.2", "license": "MIT", "ignore": [], "main": [ From 41ff953a80a536b924a6b6f8c58f4e87a48bf565 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 30 Jun 2016 17:55:04 -0400 Subject: [PATCH 10/77] Work around a bug with ironRequest --- curious.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curious.js b/curious.js index 88368ca..862b2ee 100644 --- a/curious.js +++ b/curious.js @@ -1280,7 +1280,7 @@ requestParameters.method = 'POST'; requestParameters.url = url; - requestParameters.body = args; + requestParameters.body = JSON.stringify(args); return ironRequestElement.send(requestParameters); }; From 7aa3c4404e737380fa5590de49ab625240e3565f Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 30 Jun 2016 18:02:26 -0400 Subject: [PATCH 11/77] Bump version to 2.1.3 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index d0b332e..2b64ee1 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.2", + "version": "2.1.3", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index 55ed967..da54f9f 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.2", + "version": "2.1.3", "license": "MIT", "ignore": [], "main": [ From b502494ea20eae88c0ee88fe6c6e14512d6763a7 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 30 Jun 2016 21:51:11 -0400 Subject: [PATCH 12/77] Use automatic detection of content type from the headers --- curious.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/curious.js b/curious.js index 862b2ee..8f41669 100644 --- a/curious.js +++ b/curious.js @@ -1280,7 +1280,9 @@ requestParameters.method = 'POST'; requestParameters.url = url; - requestParameters.body = JSON.stringify(args); + requestParameters.headers = requestParameters.headers || {}; + requestParameters.headers['content-type'] = 'application/json'; + requestParameters.body = args; return ironRequestElement.send(requestParameters); }; From a53f7614546dc6cf42d668d47dd0a07b9a3947af Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 30 Jun 2016 23:30:33 -0400 Subject: [PATCH 13/77] Rewrite polymer wrapper to use `iron-ajax` instead of `iron-request` --- curious.js | 62 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/curious.js b/curious.js index 8f41669..709fae6 100644 --- a/curious.js +++ b/curious.js @@ -141,7 +141,7 @@ return this; }; QueryTermWith.prototype = new QueryTerm(); - + QueryTermWith.prototype.leftJoin = function () { return true; }; QueryTermWith.prototype.toString = function () { @@ -1153,9 +1153,9 @@ * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} - * An ideal function for making requests in curious client: - * takes the url and arguments, makes an axios post request, and returns - * a promise that resolves directly to the returned query data (unwrapped). + * A function that meets the requirements to make requests in the curious client: + * takes the url and arguments, makes a POST request, and returns + * a promise that resolves directly to the returned query response (unwrapped). */ function _unwrapResponseData(defaultModuleName, moduleObjectOrFunction, options) { var mod; @@ -1224,9 +1224,9 @@ * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} - * An ideal function for making requests in curious client: + * A function that meets the requirements to make requests in the curious client: * takes the url and arguments, makes an axios post request, and returns - * a promise that resolves directly to the returned query data (unwrapped). + * a promise that resolves directly to the returned query response (unwrapped). */ CuriousClient.wrappers.axios = _unwrapResponseData.bind(ex, 'axios'); @@ -1248,43 +1248,59 @@ * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} - * An ideal function for making requests in curious client: + * A function that meets the requirements to make requests in the curious client: * takes the url and arguments, makes a POST request and returns - * a promise that resolves directly to the returned query data (unwrapped). + * a promise that resolves directly to the returned query response (unwrapped). */ CuriousClient.wrappers.angular = _unwrapResponseData.bind(ex, '$http'); /** * Convenience function to make it easier to interact with Polymer's - * <iron-request> element. + * <iron-ajax> element. * * @example - * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.ironRequest(this.$.xhr) ...) + * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.ironAjax(this.$.xhr) ...) * * @memberof module:curious.CuriousClient.wrappers * - * @param {PolymerElement} ironRequestElement - * The iron-request element being used to make the request. + * @param {PolymerElement} ironAjaxElement + * The iron-ajax element being used to make the request. * @param {Object?} options * Additional options to send to the requesting function. * * @return {function(string, Object): Promise} - * An ideal function for making requests in curious client: + * A function that meets the requirements to make requests in the curious client: * takes the url and arguments, makes a POST request with - * ironRequestElement, and returns a promise that resolves - * directly to the returned query data (unwrapped). + * ironAjaxElement, and returns a promise that resolves + * directly to the returned query response (unwrapped). */ - CuriousClient.wrappers.ironRequest = function (ironRequestElement, options) { + CuriousClient.wrappers.ironAjax = function (ironAjaxElement, options) { return function (url, args) { - var requestParameters = Object.create(Object.prototype, options || {}); + var oldAutoValue; + var request; + + // Don't make requests while we're setting the properties + oldAutoValue = ironAjaxElement.get('auto'); + ironAjaxElement.set('auto', false); + + if (options) { + Object.keys(options).forEach(function (option) { + ironAjaxElement.set(option, options[option]); + }); + } + + ironAjaxElement.set('method', 'POST'); + ironAjaxElement.set('url', url); + ironAjaxElement.set('contentType', 'application/json'); + ironAjaxElement.set('body', args); + + request = ironAjaxElement.generateRequest(); - requestParameters.method = 'POST'; - requestParameters.url = url; - requestParameters.headers = requestParameters.headers || {}; - requestParameters.headers['content-type'] = 'application/json'; - requestParameters.body = args; + // Return auto to its old state + ironAjaxElement.set('auto', oldAutoValue); - return ironRequestElement.send(requestParameters); + // Return the promise that gets fired when the XHR completes. + return request.completes; }; }; From 719acd6e1bcf800b3f6b3505e345a4d3c64883bf Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 1 Jul 2016 01:18:26 -0400 Subject: [PATCH 14/77] Parse out the response from the resolved ironRequest promise --- curious.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/curious.js b/curious.js index 709fae6..016545a 100644 --- a/curious.js +++ b/curious.js @@ -1299,8 +1299,12 @@ // Return auto to its old state ironAjaxElement.set('auto', oldAutoValue); - // Return the promise that gets fired when the XHR completes. - return request.completes; + // Return the promise that gets fired when the XHR completes, but parse + // out the actual response data, since the original promise resolves to + // the iron-request object + return request.completes.then(function (ironRequestObject) { + return ironRequestObject.response; + }); }; }; From 33f2104640dcc4bb254bb27a6404d0ecebf34d4c Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 1 Jul 2016 01:36:37 -0400 Subject: [PATCH 15/77] Bump version to 2.1.4 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 2b64ee1..7d855bd 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.3", + "version": "2.1.4", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index da54f9f..c5bfc98 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.3", + "version": "2.1.4", "license": "MIT", "ignore": [], "main": [ From ccb7e702433fc8c631699ced7e01a8259e07a331 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 1 Jul 2016 18:50:41 -0400 Subject: [PATCH 16/77] Fix minor eslint tweaks --- .eslintrc.yaml | 15 ++ curious.js | 72 +++--- tests/curious_objects.js | 481 +++++++++++++++++++-------------------- 3 files changed, 299 insertions(+), 269 deletions(-) create mode 100644 .eslintrc.yaml diff --git a/.eslintrc.yaml b/.eslintrc.yaml new file mode 100644 index 0000000..23ec848 --- /dev/null +++ b/.eslintrc.yaml @@ -0,0 +1,15 @@ +--- + # vim: ft=yaml sw=2 ts=2 sts=2 + extends: "eslint-config-airbnb-es5" + rules: + comma-dangle: + - "warn" + - "always-multiline" + func-names: "off" + no-spaced-func: "warn" + space-before-function-paren: + - "warn" + - anonymous: "always" + named: "never" + no-underscore-dangle: "off" +... diff --git a/curious.js b/curious.js index 016545a..cafbc09 100644 --- a/curious.js +++ b/curious.js @@ -1,4 +1,4 @@ -/*** +/** * Module for curious client-side query construction and JSON parsing. * * @module curious @@ -31,11 +31,14 @@ * @param {string} term The internal term text to use. */ var QueryTerm = function (term) { - /** The term contents - * @readonly - * @public - */ + /** + * The term contents + * @readonly + * @public + * @return {string} The term contents + */ this.term = function () { return term; }; + return this; }; @@ -79,6 +82,8 @@ * @class * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermFollow + * + * @param {string} term The term contents. */ var QueryTermFollow = function (term) { QueryTerm.call(this, term); @@ -94,6 +99,8 @@ * @class * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermHaving + * + * @param {string} term The term contents. */ var QueryTermHaving = function (term) { QueryTerm.call(this, term); @@ -111,8 +118,12 @@ /** * Make a term that performs a negative filter. * + * @private * @class * @extends {module:curious~QueryTerm} + * @alias module:curious~QueryTermHaving + * + * @param {string} term The term contents. */ var QueryTermNotHaving = function (term) { QueryTerm.call(this, term); @@ -134,6 +145,8 @@ * @class * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermWith + * + * @param {string} term The term contents. */ var QueryTermWith = function (term) { QueryTerm.call(this, term); @@ -247,9 +260,9 @@ // commas are inserted to ensure that the objects that correspond to // those terms are returned if (termIndex > 0) { - if (term.conditional()) + if (term.conditional()) { query += ' '; - else if ( + } else if ( !term.conditional() && !terms[termIndex - 1].leftJoin() && !term.leftJoin() @@ -337,7 +350,7 @@ this.terms.push(termObject); this.relationships.push(relationship); } else { - throw ( + throw new Error( 'Must specify a term and a relationship to append to: (' + this.query() + ')' @@ -387,7 +400,7 @@ // reference copy this.terms[this.terms.length - 1] = lastTerm; } else { - throw('Must add terms before appending "' + termObject + '" to them.'); + throw new Error('Must add terms before appending "' + termObject + '" to them.'); } return this; @@ -418,6 +431,8 @@ * The contents of the starting term. * @param {string} relationship * The name of this term in inter-term relationships + * @param {Function} customConstructor + * A custom constructor function for the resulting objects. * * @return {CuriousQuery} The query object, with the term appended */ @@ -496,7 +511,7 @@ if (this.objectFactories.length) { this.objectFactories[this.objectFactories.length - 1] = factoryFunction; } else { - throw('Cannot specify custom object constructor before starting a query'); + throw new Error('Cannot specify custom object constructor before starting a query'); } return this; @@ -602,7 +617,6 @@ * @alias module:curious.CuriousObjects */ var CuriousObjects = (function () { - /** * Base (default) class for an object returned from a Curious query * @@ -662,7 +676,6 @@ var objects = []; if (queryData.objects instanceof Array) { - queryData.objects.forEach(function (objectDataArray, objectIndex) { var url = queryData.urls[objectIndex]; var objectData = {}; @@ -685,7 +698,6 @@ // override existing fields in obj obj[fieldName] = objectData[fieldName]; }); - } else { // The CuriousObject constructor does this automatically obj = new CuriousObject(objectData); @@ -713,6 +725,8 @@ * * @param {string[]} relationships * The names of the relationships objects will have to one another. + * @param {Function[]} customConstructors + * The custom constructors for curious object classes. * @param {Object} queryJSONResponse * An object of fields holding the query response, as returned and parsed * directly from JSON without any post-processing. @@ -737,7 +751,7 @@ * The existing objects. Each object in the array is a mapping of an id * to its corresponding object. * - * @return {{objects: Object[], trees: Object[]} + * @return {{objects: Object[], trees: Object[]}} * The parsed objects. trees holds any hierarchical * relationships, for recursive queries. */ @@ -748,7 +762,6 @@ var trees = []; if (queryJSONResponse.data instanceof Array) { - queryJSONResponse.data.forEach(function (queryData, queryIndex) { var queryObjects; // the objects parsed from this query var objectsByID = {}; @@ -762,7 +775,7 @@ // Only pass in custom constructors if we need to (customConstructors instanceof Array) ? customConstructors[queryIndex] - : undefined + : null ); queryObjects.forEach(function (object) { @@ -787,7 +800,6 @@ // For each subquery, add a relationship to the results of the next // subquery and then a reverse relationship queryJSONResponse.results.forEach(function (queryResult, queryIndex) { - // An array of pairs: [objectID, srcObjectID], where // the srcObjectID points to the ID of the object that this // object is joined from (the 'source' of the join) @@ -820,7 +832,8 @@ joinIDPairs.forEach(function (joinIDPair) { var id = joinIDPair[0]; var srcID = joinIDPair[1]; // the ID of the parent - var obj, srcObj; // the corresponding objects + var obj; + var srcObj; // the corresponding objects if (srcID) { @@ -929,8 +942,8 @@ * with duplicates removed, in the same order as the input * object list. */ - function idString(objects) { - var ids = idList(objects); + function idString(arrayOfObjects) { + var ids = idList(arrayOfObjects); return ids.join(','); } @@ -941,7 +954,6 @@ idList: idList, idString: idString, }; - }()); // QUERY CLIENT @@ -953,7 +965,7 @@ * @param {string[]} relationships The relationship names * @param {Array>} objects The objects from each relationship * - * @return {Object} + * @return {Object} The rearranged results */ function _convertResultsToOutput(relationships, objects) { var output = {}; @@ -1022,11 +1034,13 @@ */ function _groupArraysOfObjectsByID(arrayOfArraysOfObjects) { return arrayOfArraysOfObjects.map(function (arrayOfObjects) { + var group = null; + if (arrayOfObjects) { - return CuriousObjects.groupObjectsByID(arrayOfObjects); - } else { - return null; + group = CuriousObjects.groupObjectsByID(arrayOfObjects); } + + return group; }); } @@ -1059,7 +1073,6 @@ * A client object with a single performQuery method. */ var CuriousClient = function (curiousURL, request, clientDefaultArgs, quiet) { - return { /** * Perform a Curious query and return back parsed objects. @@ -1106,13 +1119,16 @@ */ performQuery: function (q, relationships, constructors, params, existingObjects) { var args; + var groupedExistingObjects; if (!quiet) { + /* eslint-disable no-console */ console.info(q); + /* eslint-enable no-console */ } if (existingObjects) { - existingObjects = _groupArraysOfObjectsByID(existingObjects); + groupedExistingObjects = _groupArraysOfObjectsByID(existingObjects); } args = _getArgs(params, clientDefaultArgs); @@ -1121,7 +1137,7 @@ return request(curiousURL, args) .then(function (response) { var parsedResult = CuriousObjects.parse( - relationships, constructors, response.result, existingObjects + relationships, constructors, response.result, groupedExistingObjects ); return { diff --git a/tests/curious_objects.js b/tests/curious_objects.js index 2146d5a..788eaaa 100644 --- a/tests/curious_objects.js +++ b/tests/curious_objects.js @@ -1,300 +1,299 @@ +/* global describe it beforeEach */ + // mocha.js tests for functions dealing with Curious objects (function () { -'use strict'; - -var expect = require('chai').expect; -var curious = require('../curious.js'); -var examples = require('./examples.js'); - -// TESTS - -describe('CuriousObjects', function () { - describe('#values', function () { - var obj; - - beforeEach(function () { - obj = { - a: 1, - b: 'two', - c: ['3'], - d: {'4': true}, - e: function () { return 5; }, - }; - }); + 'use strict'; + + var expect = require('chai').expect; + var curious = require('../curious.js'); + var examples = require('./examples.js'); + + // TESTS + + describe('CuriousObjects', function () { + describe('#values', function () { + var obj; + + beforeEach(function () { + obj = { + a: 1, + b: 'two', + c: ['3'], + d: {'4': true}, + e: function () { return 5; }, + }; + }); - it('should return all of the values of an object', function () { - expect(curious.CuriousObjects.values(obj)).to.have.deep.members( - [obj.a, obj.b, obj.c, obj.d, obj.e] - ); - }); + it('should return all of the values of an object', function () { + expect(curious.CuriousObjects.values(obj)).to.have.deep.members( + [obj.a, obj.b, obj.c, obj.d, obj.e] + ); + }); - it('should return values in the same order as "for-in"', function () { - var key; - var valuesInOrder = []; + it('should return values in the same order as "for-in"', function () { + var key; + var valuesInOrder = []; - for (key in obj) { - if (obj.hasOwnProperty(key)) { - valuesInOrder.push(obj[key]); + for (key in obj) { + if (obj.hasOwnProperty(key)) { + valuesInOrder.push(obj[key]); + } } - } - expect(curious.CuriousObjects.values(obj)).to.deep.equal(valuesInOrder); - }); + expect(curious.CuriousObjects.values(obj)).to.deep.equal(valuesInOrder); + }); - it('should maintain only complex references', function () { - var vals = curious.CuriousObjects.values(obj); + it('should maintain only complex references', function () { + var vals = curious.CuriousObjects.values(obj); - vals.forEach(function (value, ix) { - // Simple reference - if (value === 1) { - // Use vals[ix] instead of value here to make sure we mutate vals - // and not a shallow copy - vals[ix] = 'moop'; - } + vals.forEach(function (value, ix) { + // Simple reference + if (value === 1) { + // Use vals[ix] instead of value here to make sure we mutate vals + // and not a shallow copy + vals[ix] = 'moop'; + } - // Complex reference - if (JSON.stringify(value) === JSON.stringify({'4': true})) { - vals[ix][4] = 'something'; - } - }); + // Complex reference + if (JSON.stringify(value) === JSON.stringify({'4': true})) { + vals[ix][4] = 'something'; + } + }); - // Simple references do not change - expect(obj.a).to.equal(1); - // Complex references do change - expect(obj.d['4']).to.equal('something'); + // Simple references do not change + expect(obj.a).to.equal(1); + // Complex references do change + expect(obj.d['4']).to.equal('something'); + }); }); - }); - describe('#groupObjectsByID', function () { - var objs; + describe('#groupObjectsByID', function () { + var objs; - beforeEach(function () { - objs = [ - {name: 'a', id: 1}, - {name: 'b', id: 2}, - {name: 'c', id: 3}, - ]; - }); + beforeEach(function () { + objs = [ + {name: 'a', id: 1}, + {name: 'b', id: 2}, + {name: 'c', id: 3}, + ]; + }); - it('should return an object', function () { - expect(curious.CuriousObjects.groupObjectsByID(objs)).to.be.an('object'); - }); + it('should return an object', function () { + expect(curious.CuriousObjects.groupObjectsByID(objs)).to.be.an('object'); + }); - it('should contain all of the IDs', function () { - expect(curious.CuriousObjects.groupObjectsByID(objs)).to.have.all.keys( - {'1': objs[0], '2': objs[1], '3': objs[2]} - ); - }); + it('should contain all of the IDs', function () { + expect(curious.CuriousObjects.groupObjectsByID(objs)).to.have.all.keys( + {'1': objs[0], '2': objs[1], '3': objs[2]} + ); + }); - it('should maintain only complex references', function () { - var objsByID = curious.CuriousObjects.groupObjectsByID(objs); + it('should maintain only complex references', function () { + var objsByID = curious.CuriousObjects.groupObjectsByID(objs); - // Simple reference - objsByID[1] = 'moop'; // objs[0] - // Complex reference - objsByID[2].name = 'broop'; // objs[1] + // Simple reference + objsByID[1] = 'moop'; // objs[0] + // Complex reference + objsByID[2].name = 'broop'; // objs[1] - // Simple references do not change - expect(objs[0]).to.have.all.keys({name: 'a', id: 1}); - // Complex references do change - expect(objs[1]).to.have.all.keys({name: 'broop', id: 2}); - }); + // Simple references do not change + expect(objs[0]).to.have.all.keys({name: 'a', id: 1}); + // Complex references do change + expect(objs[1]).to.have.all.keys({name: 'broop', id: 2}); + }); - it('should take the last object in the arary in case of duplicate IDs', function () { - var objsByID; + it('should take the last object in the arary in case of duplicate IDs', function () { + var objsByID; - objs.push({name: 'b2', id: 2}); - objsByID = curious.CuriousObjects.groupObjectsByID(objs); + objs.push({name: 'b2', id: 2}); + objsByID = curious.CuriousObjects.groupObjectsByID(objs); - expect(objsByID[2]).to.have.property('name', 'b2'); - }); + expect(objsByID[2]).to.have.property('name', 'b2'); + }); - it('should ignore objects without IDs', function () { - var objsByID; - var objsByIDWithNoID; + it('should ignore objects without IDs', function () { + var objsByID; + var objsByIDWithNoID; - objsByID = curious.CuriousObjects.groupObjectsByID(objs); + objsByID = curious.CuriousObjects.groupObjectsByID(objs); - objs.push({name: 'noID'}); + objs.push({name: 'noID'}); - objsByIDWithNoID = curious.CuriousObjects.groupObjectsByID(objs); + objsByIDWithNoID = curious.CuriousObjects.groupObjectsByID(objs); - expect(objsByID).to.deep.equal(objsByIDWithNoID); - }); + expect(objsByID).to.deep.equal(objsByIDWithNoID); + }); - }); + }); - describe('#idList', function () { - var objs; + describe('#idList', function () { + var objs; - beforeEach(function () { - objs = [ - {name: 'a', id: 1}, - {name: 'b', id: 2}, - {name: 'c', id: 3}, - ]; - }); + beforeEach(function () { + objs = [ + {name: 'a', id: 1}, + {name: 'b', id: 2}, + {name: 'c', id: 3}, + ]; + }); - it('should contain all of the IDs', function () { - expect(curious.CuriousObjects.idList(objs)).to.have.members([1, 2, 3]); - }); + it('should contain all of the IDs', function () { + expect(curious.CuriousObjects.idList(objs)).to.have.members([1, 2, 3]); + }); - it('should not contain duplicates', function () { - objs.push({name: 'b2', id: 2}); + it('should not contain duplicates', function () { + objs.push({name: 'b2', id: 2}); - expect(curious.CuriousObjects.idList(objs)).to.have.members([1, 2, 3]); - }); + expect(curious.CuriousObjects.idList(objs)).to.have.members([1, 2, 3]); + }); - it('should maintain the order of IDs and keep the first unique one', function () { + it('should maintain the order of IDs and keep the first unique one', function () { - // This object with id = 2 will be ignored, but the first one will be kept. - objs.push({name: 'b2', id: 2}); - objs.push({name: 'd', id: 4}); - objs.push({name: 'e', id: 0}); - // This object with id = 2 will also be ignored - objs.push({name: 'b3', id: 2}); - objs.push({name: 'f', id: 23}); + // This object with id = 2 will be ignored, but the first one will be kept. + objs.push({name: 'b2', id: 2}); + objs.push({name: 'd', id: 4}); + objs.push({name: 'e', id: 0}); + // This object with id = 2 will also be ignored + objs.push({name: 'b3', id: 2}); + objs.push({name: 'f', id: 23}); - expect(curious.CuriousObjects.idList(objs)).to.deep.equal([1, 2, 3, 4, 0, 23]); + expect(curious.CuriousObjects.idList(objs)).to.deep.equal([1, 2, 3, 4, 0, 23]); + }); }); - }); - describe('#idString', function () { - var objs; + describe('#idString', function () { + var objs; - function _parseToNumbers(idString) { - return idString.split(',').map(function (id) {return Number(id);}); - } + function _parseToNumbers(idString) { + return idString.split(',').map(function (id) {return Number(id);}); + } - beforeEach(function () { - objs = [ - {name: 'a', id: 1}, - {name: 'b', id: 2}, - {name: 'c', id: 3}, - ]; - }); + beforeEach(function () { + objs = [ + {name: 'a', id: 1}, + {name: 'b', id: 2}, + {name: 'c', id: 3}, + ]; + }); - it('should be equivalent to idList', function () { - expect(_parseToNumbers(curious.CuriousObjects.idString(objs))).to.deep.equal( - curious.CuriousObjects.idList(objs) - ); - - // This object with id = 2 will be ignored, but the first one will be kept. - objs.push({name: 'b2', id: 2}); - objs.push({name: 'd', id: 4}); - objs.push({name: 'e', id: 0}); - // This object with id = 2 will also be ignored - objs.push({name: 'b3', id: 2}); - objs.push({name: 'f', id: 23}); - - expect(_parseToNumbers(curious.CuriousObjects.idString(objs))).to.deep.equal( - curious.CuriousObjects.idList(objs) - ); - }); + it('should be equivalent to idList', function () { + expect(_parseToNumbers(curious.CuriousObjects.idString(objs))).to.deep.equal( + curious.CuriousObjects.idList(objs) + ); + + // This object with id = 2 will be ignored, but the first one will be kept. + objs.push({name: 'b2', id: 2}); + objs.push({name: 'd', id: 4}); + objs.push({name: 'e', id: 0}); + // This object with id = 2 will also be ignored + objs.push({name: 'b3', id: 2}); + objs.push({name: 'f', id: 23}); + + expect(_parseToNumbers(curious.CuriousObjects.idString(objs))).to.deep.equal( + curious.CuriousObjects.idList(objs) + ); + }); - it('should not escape commas', function () { - objs.push({name: 'commas', id: '4,5,6'}); + it('should not escape commas', function () { + objs.push({name: 'commas', id: '4,5,6'}); - expect( - // 1, 2, 3, 4, 5, 6, converted to numbers - _parseToNumbers(curious.CuriousObjects.idString(objs)) - ).to.deep.equal( - // Remove the element we added ('4,5,6') and add 4, 5, 6 to the end - curious.CuriousObjects.idList(objs).slice(0, -1).concat([4, 5, 6]) - ); + expect( + // 1, 2, 3, 4, 5, 6, converted to numbers + _parseToNumbers(curious.CuriousObjects.idString(objs)) + ).to.deep.equal( + // Remove the element we added ('4,5,6') and add 4, 5, 6 to the end + curious.CuriousObjects.idList(objs).slice(0, -1).concat([4, 5, 6]) + ); + }); }); - }); - describe('#parse', function () { - var queryJSONResponse; + describe('#parse', function () { + var queryJSONResponse; - beforeEach(function () { - queryJSONResponse = examples.response().result; - }); + beforeEach(function () { + queryJSONResponse = examples.response().result; + }); - it('should correctly parse the output', function () { - var expectedObjects = examples.expectedObjects(); - - expect(curious.CuriousObjects.parse( - ['experiments', 'reactions'], - null, - queryJSONResponse - )).to.deep.equal( - { - trees: [null, null], - objects: [ - expectedObjects.experiments, - expectedObjects.reactions, - ], - } - ); - }); + it('should correctly parse the output', function () { + var expectedObjects = examples.expectedObjects(); + + expect(curious.CuriousObjects.parse( + ['experiments', 'reactions'], + null, + queryJSONResponse + )).to.deep.equal( + { + trees: [null, null], + objects: [ + expectedObjects.experiments, + expectedObjects.reactions, + ], + } + ); + }); - it('should construct objects correctly with custom constructors', function () { - var constructors; - var parsedData; + it('should construct objects correctly with custom constructors', function () { + var constructors; + var parsedData; - // Fake constructors - function Experiment() { - // Do nothing - } + // Fake constructors + function Experiment() { + // Do nothing + } - function Reaction() { - // Do nothing - } + function Reaction() { + // Do nothing + } - constructors = [Experiment, Reaction]; + constructors = [Experiment, Reaction]; - parsedData = curious.CuriousObjects.parse( - ['experiments', 'reactions'], - constructors, - queryJSONResponse - ); + parsedData = curious.CuriousObjects.parse( + ['experiments', 'reactions'], + constructors, + queryJSONResponse + ); - parsedData.objects.forEach(function (objectsByID, ix) { - var objects = curious.CuriousObjects.values(objectsByID); + parsedData.objects.forEach(function (objectsByID, ix) { + var objects = curious.CuriousObjects.values(objectsByID); - objects.forEach(function (object) { - expect(object).to.be.an.instanceof(constructors[ix]); + objects.forEach(function (object) { + expect(object).to.be.an.instanceof(constructors[ix]); + }); }); }); + it('should combine new objects with existing ones', function () { + var parsedData; + // jscs:disable requireCamelCaseOrUpperCaseIdentifiers + var existingObjects = [ + null, + { + 23063: { + id: 23063, + created_by_id: 12345, + created_on: 'another date', + updated_by_id: 67890, + updated_on: 'another time', + experiment_id: 403, + is_blank: false, + is_control: false, + notes: '', + sample_id: 124816, + __url: 'http://example.com/experiment/403/', + __model: 'Reaction', + }, + }, + ]; + // jscs:enable + + parsedData = curious.CuriousObjects.parse( + ['experiments', 'reactions'], + null, + queryJSONResponse, + existingObjects + ); + + expect(parsedData.objects[1][23063]).to.contain.keys(existingObjects[1][23063]); + }); }); - - it('should combine new objects with existing ones', function () { - var parsedData; - // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - var existingObjects = [ - null, - { - 23063: { - id: 23063, - created_by_id: 12345, - created_on: 'another date', - updated_by_id: 67890, - updated_on: 'another time', - experiment_id: 403, - is_blank: false, - is_control: false, - notes: '', - sample_id: 124816, - __url: 'http://example.com/experiment/403/', - __model: 'Reaction', - } - }, - ]; - // jscs:enable - - parsedData = curious.CuriousObjects.parse( - ['experiments', 'reactions'], - null, - queryJSONResponse, - existingObjects - ); - - expect(parsedData.objects[1][23063]).to.contain.keys(existingObjects[1][23063]); - }); - }); -}); - }()); From f216634474a20e9ba0a861f370b54affc3d97a24 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 1 Jul 2016 20:23:34 -0400 Subject: [PATCH 17/77] Add a camelCase function and tests for it --- curious.js | 84 ++++++++++++++++++++++++++++++++++++++++ tests/curious_objects.js | 71 +++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/curious.js b/curious.js index cafbc09..c789579 100644 --- a/curious.js +++ b/curious.js @@ -947,12 +947,96 @@ return ids.join(','); } + /** + * Camel case a string with - or _ separators: + * + * @example + * CuriousObjects.camelCase('this_is-someTHING') === 'thisIsSomething' + * CuriousObjects.camelCase('_alreadyDone') === '_alreadyDone' + * + * @memberof module:curious.CuriousObjects + * + * @param {string} input The string to camel-case. + * + * @return {string} The input, camel-cased. + */ + function camelCase(input) { + var components; + var casedComponents; + var separators; + var output; + + // For leading/trailing separators + separators = { + leading: { + re: /^[-_]+/g, + match: null, + text: '', + }, + trailing: { + re: /[-_]+$/g, + match: null, + text: '', + }, + }; + + // Match the leading/trailing separators and store the text + Object.keys(separators).forEach(function (key) { + var separatorType = separators[key]; + separatorType.match = separatorType.re.exec(input); + if (separatorType.match) { + separatorType.text = separatorType.match[0]; + } + }); + + if (separators.leading.text.length === input.length) { + // Special case: string consists entirely of separators: just return it + output = input; + } else { + // Only split the parts of the string that are not leading/trailing + // separators + components = input.substring( + separators.leading.text.length, + input.length - separators.trailing.text.length + ).split(/[_-]/); + + // If we don't have anything to camel-case, just leave the body alone + if (components.length > 1) { + casedComponents = components.map(function (component, ix) { + // Normalize by lowercasing everything + var casedComponent = component.toLowerCase(); + + // Capitalize every word but the first + if (ix > 0) { + casedComponent = ( + casedComponent.charAt(0).toUpperCase() + + casedComponent.slice(1) + ); + } + + return casedComponent; + }); + } else { + casedComponents = components; + } + + output = ( + separators.leading.text + + casedComponents.join('') + + separators.trailing.text + ); + } + + return output; + } + return { parse: parse, values: values, groupObjectsByID: groupObjectsByID, idList: idList, idString: idString, + camelCase: camelCase, }; }()); diff --git a/tests/curious_objects.js b/tests/curious_objects.js index 788eaaa..39281f3 100644 --- a/tests/curious_objects.js +++ b/tests/curious_objects.js @@ -206,6 +206,77 @@ }); }); + describe('#camelCase', function () { + var camelCase = curious.CuriousObjects.camelCase; + + it('should replace all _ with a camel-casing', function () { + expect(camelCase('a_single')).to.equal('aSingle'); + expect(camelCase('a_double_one')).to.equal('aDoubleOne'); + expect(camelCase('a_triple_one_even')).to.equal('aTripleOneEven'); + expect(camelCase('something_else_entirely')).to.equal('somethingElseEntirely'); + }); + + it('should replace all - with a camel-casing', function () { + expect(camelCase('a-single')).to.equal('aSingle'); + expect(camelCase('a-double-one')).to.equal('aDoubleOne'); + expect(camelCase('a-triple-one-even')).to.equal('aTripleOneEven'); + }); + + it('should correctly handle mixed - and _', function () { + expect(camelCase('a-first-mix')).to.equal('aFirstMix'); + expect(camelCase('a_second-one')).to.equal('aSecondOne'); + expect(camelCase('a-triple_one-even')).to.equal('aTripleOneEven'); + }); + + it('should correctly handle multiple separators', function () { + expect(camelCase('a--single')).to.equal('aSingle'); + expect(camelCase('a__single')).to.equal('aSingle'); + expect(camelCase('a__double--one')).to.equal('aDoubleOne'); + expect(camelCase('a_______many')).to.equal('aMany'); + expect(camelCase('a__-_-__many')).to.equal('aMany'); + }); + + it('should leave leading underscores or dashes', function () { + expect(camelCase('_a_single')).to.equal('_aSingle'); + expect(camelCase('-a_single')).to.equal('-aSingle'); + expect(camelCase('__a_single')).to.equal('__aSingle'); + expect(camelCase('__a-single')).to.equal('__aSingle'); + expect(camelCase('_-_a_single')).to.equal('_-_aSingle'); + }); + + it('should leave trailing underscores or dashes', function () { + expect(camelCase('a_single_')).to.equal('aSingle_'); + expect(camelCase('a_single-')).to.equal('aSingle-'); + expect(camelCase('a_single__')).to.equal('aSingle__'); + expect(camelCase('a-single__')).to.equal('aSingle__'); + expect(camelCase('__a-single__')).to.equal('__aSingle__'); + expect(camelCase('_-_a_single-_-')).to.equal('_-_aSingle-_-'); + }); + + it('should leave periods alone', function () { + expect(camelCase('some_thing.prop')).to.equal('someThing.prop'); + }); + + it('should overwrite existing casing', function () { + expect(camelCase('some_HTML_thing')).to.equal('someHtmlThing'); + expect(camelCase('soMany_WORDS_might_Be_capitalized')) + .to.equal('somanyWordsMightBeCapitalized'); + }); + + it('should correctly handle special-character variables like $, $$, _, __, etc.', function () { + expect(camelCase('_')).to.equal('_'); + expect(camelCase('__')).to.equal('__'); + expect(camelCase('$')).to.equal('$'); + expect(camelCase('$$')).to.equal('$$'); + }); + + it('should leave valid expressions alone:', function () { + expect(camelCase('word')).to.equal('word'); + expect(camelCase('aCamelCasedExpression')).to.equal('aCamelCasedExpression'); + expect(camelCase('_aPrefixedExpression')).to.equal('_aPrefixedExpression'); + }); + }); + describe('#parse', function () { var queryJSONResponse; From 0becfb8a8e810a56bea4d86cb9e2b19e042f4bfd Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 2 Jul 2016 00:03:06 -0400 Subject: [PATCH 18/77] Fix indentation/eslint issues in tests/examples --- tests/examples.js | 405 +++++++++++++++++++++++----------------------- 1 file changed, 200 insertions(+), 205 deletions(-) diff --git a/tests/examples.js b/tests/examples.js index e9b8fb4..31d45bf 100644 --- a/tests/examples.js +++ b/tests/examples.js @@ -3,219 +3,214 @@ * @module examples */ (function () { -'use strict'; + 'use strict'; + var curious = require('../curious.js'); -/** - * Generate sample data as would be returned by a Curious server in JSON. - * - * @return {Object} The response data - */ -exports.response = function () { - - // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - return { - result: { - computed_on: '2015-05-21 14:36:07.478769', - last_model: 'Reaction', - results: [ - { - model: 'Experiment', - join_index: -1, - tree: null, - objects: [ - [403, null], - ], - }, { - model: 'Reaction', - join_index: 0, - tree: null, - objects: [ - [23063, 403], - [23064, 403], - [23057, 403], - ], - }, - ], - data: [ - { // Experiments - fields: [ - 'id', - 'created_by_id', - 'created_on', - 'updated_by_id', - 'updated_on', - 'assay_id', - 'name', - 'description', - 'completed', - 'ignore', - 'temperature', - ], - objects: [ - [ - 403, - 22, - '2015-01-16 14:54:13+00:00', - 22, - '2015-05-14 16:03:13+00:00', - 1, - 'MS2 for x401 and x402', - '', - true, - false, - null, + /** + * Generate sample data as would be returned by a Curious server in JSON. + * + * @return {Object} The response data + */ + exports.response = function () { + return { + result: { + computed_on: '2015-05-21 14:36:07.478769', + last_model: 'Reaction', + results: [ + { + model: 'Experiment', + join_index: -1, + tree: null, + objects: [ + [403, null], + ], + }, { + model: 'Reaction', + join_index: 0, + tree: null, + objects: [ + [23063, 403], + [23064, 403], + [23057, 403], + ], + }, + ], + data: [ + { // Experiments + fields: [ + 'id', + 'created_by_id', + 'created_on', + 'updated_by_id', + 'updated_on', + 'assay_id', + 'name', + 'description', + 'completed', + 'ignore', + 'temperature', + ], + objects: [ + [ + 403, + 22, + '2015-01-16 14:54:13+00:00', + 22, + '2015-05-14 16:03:13+00:00', + 1, + 'MS2 for x401 and x402', + '', + true, + false, + null, + ], + ], + urls: [ + 'http://example.com/experiment/403/', + ], + }, { + fields: [ + 'id', + 'created_by_id', + 'created_on', + 'updated_by_id', + 'updated_on', + 'experiment_id', + 'is_blank', + 'is_control', + 'notes', + 'sample_id', + ], + objects: [ + [ + 23057, + null, + '2015-01-16 14:54:27+00:00', + null, + '2015-01-16 14:54:27+00:00', + 403, + false, + false, + '', + 454565, + ], [ + 23063, + null, + '2015-01-16 14:54:27+00:00', + null, + '2015-01-16 14:54:27+00:00', + 403, + true, + false, + '', + null, + ], [ + 23064, + null, + '2015-01-16 14:54:27+00:00', + null, + '2015-01-16 14:54:27+00:00', + 403, + true, + false, + '', + null, + ], ], - ], - urls: [ - 'http://example.com/experiment/403/' - ], - }, { - fields: [ - 'id', - 'created_by_id', - 'created_on', - 'updated_by_id', - 'updated_on', - 'experiment_id', - 'is_blank', - 'is_control', - 'notes', - 'sample_id', - ], - objects: [ - [ - 23057, - null, - '2015-01-16 14:54:27+00:00', - null, - '2015-01-16 14:54:27+00:00', - 403, - false, - false, - '', - 454565, - ], [ - 23063, - null, - '2015-01-16 14:54:27+00:00', - null, - '2015-01-16 14:54:27+00:00', - 403, - true, - false, - '', - null, - ], [ - 23064, - null, - '2015-01-16 14:54:27+00:00', - null, - '2015-01-16 14:54:27+00:00', - 403, - true, - false, - '', - null, + urls: [ + 'http://example.com/experiment/403/', + 'http://example.com/experiment/403/', + 'http://example.com/experiment/403/', ], - ], - urls: [ - 'http://example.com/experiment/403/', - 'http://example.com/experiment/403/', - 'http://example.com/experiment/403/', - ], - }, - ], - } + }, + ], + }, + }; }; - // jscs:enable -}; -/** - * Generate sample data as would be returned by a Curious server in JSON. - * - * @return {{experiments: Object, reactions: Object}} - * The objects expected to be constructed from - * @{link module:examples.response} - */ -exports.expectedObjects = function () { - var exp; - var rxns; - - // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - exp = { - id: 403, - created_by_id: 22, - created_on: '2015-01-16 14:54:13+00:00', - updated_by_id: 22, - updated_on: '2015-05-14 16:03:13+00:00', - assay_id: 1, - name: 'MS2 for x401 and x402', - description: '', - completed: true, - ignore: false, - temperature: null, - __url: 'http://example.com/experiment/403/', - __model: 'Experiment', - }; + /** + * Generate sample data as would be returned by a Curious server in JSON. + * + * @return {{experiments: Object, reactions: Object}} + * The objects expected to be constructed from @{link examples.response|the response} + */ + exports.expectedObjects = function () { + var exp; + var rxns; - rxns = [ - { - id: 23063, - created_by_id: null, - created_on: '2015-01-16 14:54:27+00:00', - updated_by_id: null, - updated_on: '2015-01-16 14:54:27+00:00', - experiment_id: 403, - is_blank: true, - is_control: false, - notes: '', - sample_id: null, + // jscs:disable requireCamelCaseOrUpperCaseIdentifiers + exp = { + id: 403, + created_by_id: 22, + created_on: '2015-01-16 14:54:13+00:00', + updated_by_id: 22, + updated_on: '2015-05-14 16:03:13+00:00', + assay_id: 1, + name: 'MS2 for x401 and x402', + description: '', + completed: true, + ignore: false, + temperature: null, __url: 'http://example.com/experiment/403/', - __model: 'Reaction', - }, { - id: 23064, - created_by_id: null, - created_on: '2015-01-16 14:54:27+00:00', - updated_by_id: null, - updated_on: '2015-01-16 14:54:27+00:00', - experiment_id: 403, - is_blank: true, - is_control: false, - notes: '', - sample_id: null, - __url: 'http://example.com/experiment/403/', - __model: 'Reaction', - }, { - id: 23057, - created_by_id: null, - created_on: '2015-01-16 14:54:27+00:00', - updated_by_id: null, - updated_on: '2015-01-16 14:54:27+00:00', - experiment_id: 403, - is_blank: false, - is_control: false, - notes: '', - sample_id: 454565, - __url: 'http://example.com/experiment/403/', - __model: 'Reaction', - }, - ]; + __model: 'Experiment', + }; - // Link foregin keys - exp.reactions = rxns; - rxns.forEach(function (rxn) { rxn.experiments = [exp]; }); + rxns = [ + { + id: 23063, + created_by_id: null, + created_on: '2015-01-16 14:54:27+00:00', + updated_by_id: null, + updated_on: '2015-01-16 14:54:27+00:00', + experiment_id: 403, + is_blank: true, + is_control: false, + notes: '', + sample_id: null, + __url: 'http://example.com/experiment/403/', + __model: 'Reaction', + }, { + id: 23064, + created_by_id: null, + created_on: '2015-01-16 14:54:27+00:00', + updated_by_id: null, + updated_on: '2015-01-16 14:54:27+00:00', + experiment_id: 403, + is_blank: true, + is_control: false, + notes: '', + sample_id: null, + __url: 'http://example.com/experiment/403/', + __model: 'Reaction', + }, { + id: 23057, + created_by_id: null, + created_on: '2015-01-16 14:54:27+00:00', + updated_by_id: null, + updated_on: '2015-01-16 14:54:27+00:00', + experiment_id: 403, + is_blank: false, + is_control: false, + notes: '', + sample_id: 454565, + __url: 'http://example.com/experiment/403/', + __model: 'Reaction', + }, + ]; - return { - experiments: { - 403: exp - }, - reactions: { - 23063: rxns[0], - 23064: rxns[1], - 23057: rxns[2], - }, - }; - // jscs:enable -}; + // Link foregin keys + exp.reactions = rxns; + rxns.forEach(function (rxn) { rxn.experiments = [exp]; }); + return { + experiments: { + 403: exp, + }, + reactions: { + 23063: rxns[0], + 23064: rxns[1], + 23057: rxns[2], + }, + }; + }; }()); From cd4fe7bf5c42cb6152d53513a9b969cd5226e8c5 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 2 Jul 2016 00:08:02 -0400 Subject: [PATCH 19/77] Fix indentation/eslint issues in tests/curious_client --- tests/curious_client.js | 136 ++++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 69 deletions(-) diff --git a/tests/curious_client.js b/tests/curious_client.js index f60f4e1..efdcc47 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -1,86 +1,84 @@ +/* global describe it before */ + // mocha.js tests for functions dealing with Curious objects (function () { -'use strict'; + 'use strict'; -var http = require('http'); -var axios = require('axios'); -var expect = require('chai').expect; -var curious = require('../curious.js'); -var examples = require('./examples.js'); + var http = require('http'); + var axios = require('axios'); + var expect = require('chai').expect; + var curious = require('../curious.js'); + var examples = require('./examples.js'); -// TESTS -var PORT = 8080; -var CURIOUS_URL = 'http://localhost:' + PORT; + // TESTS + var PORT = 8080; + var CURIOUS_URL = 'http://localhost:' + PORT; -describe('CuriousClient', function () { - before(function () { - return ( - http - .createServer(function (request, response) { - response.writeHead(200, { - 'Content-Type': 'application/json', - }); - response.end(JSON.stringify(examples.response())); - }) - .listen(PORT) - ); - }); - - describe('#performQuery', function () { + describe('CuriousClient', function () { + before(function () { + return ( + http + .createServer(function (request, response) { + response.writeHead(200, { + 'Content-Type': 'application/json', + }); + response.end(JSON.stringify(examples.response())); + }) + .listen(PORT) + ); + }); - it('should work with axios', function (done) { - var requestFunctions; - // Set the global axios variable to test defaults - if (typeof global !== 'undefined' && !global.axios) { - global.axios = axios; - } + describe('#performQuery', function () { + it('should work with axios', function (done) { + var requestFunctions; + // Set the global axios variable to test defaults + if (typeof global !== 'undefined' && !global.axios) { + global.axios = axios; + } - requestFunctions = [ - curious.CuriousClient.wrappers.axios(axios), - curious.CuriousClient.wrappers.axios(axios.post), - curious.CuriousClient.wrappers.axios() - ]; + requestFunctions = [ + curious.CuriousClient.wrappers.axios(axios), + curious.CuriousClient.wrappers.axios(axios.post), + curious.CuriousClient.wrappers.axios(), + ]; - try { - requestFunctions.forEach(function (requestFunction) { - var client = new curious.CuriousClient( - CURIOUS_URL, - requestFunction, - null, - true - ); + try { + requestFunctions.forEach(function (requestFunction) { + var client = new curious.CuriousClient( + CURIOUS_URL, + requestFunction, + null, + true + ); - client.performQuery( - 'query does not matter', - ['experiments', 'reactions'] - ) - .then(function (response) { - var expectedObjects = examples.expectedObjects(); - expect(response).to.deep.equal({ - trees: [null, null], - objects: { - experiments: curious.CuriousObjects.values(expectedObjects.experiments), - reactions: curious.CuriousObjects.values(expectedObjects.reactions), - }, + client.performQuery( + 'query does not matter', + ['experiments', 'reactions'] + ) + .then(function (response) { + var expectedObjects = examples.expectedObjects(); + expect(response).to.deep.equal({ + trees: [null, null], + objects: { + experiments: curious.CuriousObjects.values(expectedObjects.experiments), + reactions: curious.CuriousObjects.values(expectedObjects.reactions), + }, + }); + }, function (error) { + throw error; }); - }, function (error) { - throw error; }); - }); + // Clean up the global axios variable + if (typeof global !== 'undefined' && global.axios) { + delete global.axios; + } - // Clean up the global axios variable - if (typeof global !== 'undefined' && global.axios) { - delete global.axios; + done(); + } catch (error) { + done(error); } - - done(); - } - catch (error) { - done(error); - } - + }); }); }); -}); }()); From 98bd359920acb7da3ae1911a409170bc4d465c5e Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 2 Jul 2016 00:10:32 -0400 Subject: [PATCH 20/77] Fix indentation/eslint issues in tests/curious_query --- tests/curious_query.js | 806 ++++++++++++++++++++--------------------- 1 file changed, 401 insertions(+), 405 deletions(-) diff --git a/tests/curious_query.js b/tests/curious_query.js index 12f4605..7664853 100644 --- a/tests/curious_query.js +++ b/tests/curious_query.js @@ -1,524 +1,520 @@ +/* global describe it before beforeEach */ + // mocha.js tests for the functions dealing with Curious queries (function () { -'use strict'; + 'use strict'; -var expect = require('chai').expect; -var curious = require('../curious.js'); + var expect = require('chai').expect; + var curious = require('../curious.js'); -describe('CuriousQuery', function () { - describe('#query', function () { - var expectedQuery = ( - 'Experiment(id=302), Experiment.reaction_set, ' - + 'Reaction.dataset_set, Dataset.attachment_set' - ); + describe('CuriousQuery', function () { + describe('#query', function () { + var expectedQuery = ( + 'Experiment(id=302), Experiment.reaction_set, ' + + 'Reaction.dataset_set, Dataset.attachment_set' + ); - // Fake constructor - function Dataset() {} + // Fake constructor + function Dataset() {} - it('should return the empty string with no terms', function () { - expect((new curious.CuriousQuery()).query()).to.equal(''); - }); + it('should return the empty string with no terms', function () { + expect((new curious.CuriousQuery()).query()).to.equal(''); + }); - it('should return the correct query string', function () { - var q = (new curious.CuriousQuery()) - .start('Experiment(id=302)', 'experiments') - .follow('Experiment.reaction_set', 'reactions') - .follow('Reaction.dataset_set', 'datasets').wrapWith(Dataset) - .follow('Dataset.attachment_set', 'attachments'); + it('should return the correct query string', function () { + var q = (new curious.CuriousQuery()) + .start('Experiment(id=302)', 'experiments') + .follow('Experiment.reaction_set', 'reactions') + .follow('Reaction.dataset_set', 'datasets').wrapWith(Dataset) + .follow('Dataset.attachment_set', 'attachments'); - expect(q.query()).to.equal(expectedQuery); - }); + expect(q.query()).to.equal(expectedQuery); + }); - it('should allow shortcuts', function () { - // Terser version of the same query - var q = new curious.CuriousQuery('Experiment(id=302)', 'experiment') - .follow('Experiment.reaction_set', 'reactions') - .follow('Reaction.dataset_set', 'dataset', Dataset) - .follow('Dataset.attachment_set', 'attachments'); + it('should allow shortcuts', function () { + // Terser version of the same query + var q = new curious.CuriousQuery('Experiment(id=302)', 'experiment') + .follow('Experiment.reaction_set', 'reactions') + .follow('Reaction.dataset_set', 'dataset', Dataset) + .follow('Dataset.attachment_set', 'attachments'); - expect(q.query()).to.equal(expectedQuery); + expect(q.query()).to.equal(expectedQuery); + }); }); - }); - describe('#start', function () { - var startingTerm = 'Experiment(id=302)'; + describe('#start', function () { + var startingTerm = 'Experiment(id=302)'; - // Fake constructor - function Experiment() { return this; } - function experimentFactory() { return Object.create(new Experiment()); } + // Fake constructor + function Experiment() { return this; } + function experimentFactory() { return Object.create(new Experiment()); } - it('should start the query', function () { - var q = (new curious.CuriousQuery()).start(startingTerm, 'experiments'); + it('should start the query', function () { + var q = (new curious.CuriousQuery()).start(startingTerm, 'experiments'); - expect(q.query()).to.equal(startingTerm); - }); + expect(q.query()).to.equal(startingTerm); + }); - it('should require both a term and a relationship', function () { - expect(function () { - (new curious.CuriousQuery()).start(); - }).to.throw(/term/); + it('should require both a term and a relationship', function () { + expect(function () { + (new curious.CuriousQuery()).start(); + }).to.throw(/term/); - expect(function () { - (new curious.CuriousQuery()).start(startingTerm); - }).to.throw(/relationship/); - }); + expect(function () { + (new curious.CuriousQuery()).start(startingTerm); + }).to.throw(/relationship/); + }); - it('should allow custom constructors', function () { - var q = (new curious.CuriousQuery()) - .start(startingTerm, 'experiments') - .wrapWith(Experiment); + it('should allow custom constructors', function () { + var q = (new curious.CuriousQuery()) + .start(startingTerm, 'experiments') + .wrapWith(Experiment); - expect(q.query()).to.equal(startingTerm); + expect(q.query()).to.equal(startingTerm); - expect(q.objectFactories).to.have.length(1); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - }); + expect(q.objectFactories).to.have.length(1); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + }); - it('should allow function factories', function () { - var q = (new curious.CuriousQuery()) - .start(startingTerm, 'experiments') - .wrapDynamically(experimentFactory); + it('should allow function factories', function () { + var q = (new curious.CuriousQuery()) + .start(startingTerm, 'experiments') + .wrapDynamically(experimentFactory); - expect(q.query()).to.equal(startingTerm); + expect(q.query()).to.equal(startingTerm); - expect(q.objectFactories).to.have.length(1); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - }); + expect(q.objectFactories).to.have.length(1); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + }); - it('should allow shortcuts', function () { - var q = new curious.CuriousQuery(startingTerm, 'experiments', Experiment); - var q2 = new curious.CuriousQuery(startingTerm, 'experiments', experimentFactory); + it('should allow shortcuts', function () { + var q = new curious.CuriousQuery(startingTerm, 'experiments', Experiment); + var q2 = new curious.CuriousQuery(startingTerm, 'experiments', experimentFactory); - expect(q.query()).to.equal(startingTerm); + expect(q.query()).to.equal(startingTerm); - expect(q.objectFactories).to.have.length(1); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories).to.have.length(1); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q2.objectFactories).to.have.length(1); - expect(q2.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q2.objectFactories).to.have.length(1); + expect(q2.objectFactories[0]()).to.be.an.instanceof(Experiment); + }); }); - }); - - describe('#follow', function () { - var expectedQuery = 'Experiment(id=302), Experiment.reaction_set'; - var startingQuery; - - - // Fake constructor - function Experiment() { return this; } - function Reaction() { return this; } - function reactionFactory() { return Object.create(new Reaction()); } - - beforeEach(function () { - startingQuery = new curious.CuriousQuery( - 'Experiment(id=302)', 'experiments', Experiment - ); - }); - it('should append to the query with a comma', function () { - var q = startingQuery - .follow('Experiment.reaction_set', 'reactions'); - expect(q.query()).to.equal(expectedQuery); - }); + describe('#follow', function () { + var expectedQuery = 'Experiment(id=302), Experiment.reaction_set'; + var startingQuery; - it('should successfully allow terms without commas', function () { - var q = startingQuery - .follow('Experiment.reaction_set Reaction.dataset_set', 'datasets'); - expect(q.query()).to.equal(expectedQuery + ' Reaction.dataset_set'); - }); + // Fake constructor + function Experiment() { return this; } + function Reaction() { return this; } + function reactionFactory() { return Object.create(new Reaction()); } - it('should require both a term and a relationship', function () { - expect(function () { - startingQuery.follow(); - }).to.throw(/term/); + beforeEach(function () { + startingQuery = new curious.CuriousQuery( + 'Experiment(id=302)', 'experiments', Experiment + ); + }); - expect(function () { - startingQuery.follow('Experiment.reaction_set'); - }).to.throw(/relationship/); - }); + it('should append to the query with a comma', function () { + var q = startingQuery + .follow('Experiment.reaction_set', 'reactions'); + expect(q.query()).to.equal(expectedQuery); + }); - it('should allow custom constructors', function () { - var q = startingQuery - .follow('Experiment.reaction_set', 'reactions') - .wrapWith(Reaction); + it('should successfully allow terms without commas', function () { + var q = startingQuery + .follow('Experiment.reaction_set Reaction.dataset_set', 'datasets'); - expect(q.query()).to.equal(expectedQuery); + expect(q.query()).to.equal(expectedQuery + ' Reaction.dataset_set'); + }); - expect(q.objectFactories).to.have.length(2); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q.objectFactories[1]()).to.be.an.instanceof(Reaction); - }); + it('should require both a term and a relationship', function () { + expect(function () { + startingQuery.follow(); + }).to.throw(/term/); - it('should allow function factories', function () { - var q = startingQuery - .follow('Experiment.reaction_set', 'reactions') - .wrapDynamically(reactionFactory); + expect(function () { + startingQuery.follow('Experiment.reaction_set'); + }).to.throw(/relationship/); + }); - expect(q.query()).to.equal(expectedQuery); + it('should allow custom constructors', function () { + var q = startingQuery + .follow('Experiment.reaction_set', 'reactions') + .wrapWith(Reaction); - expect(q.objectFactories).to.have.length(2); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q.objectFactories[1]()).to.be.an.instanceof(Reaction); - }); + expect(q.query()).to.equal(expectedQuery); - it('should allow shortcuts', function () { - var q = startingQuery.clone() - .follow('Experiment.reaction_set', 'reactions', Reaction); - var q2 = startingQuery.clone() - .follow('Experiment.reaction_set', 'reactions', reactionFactory); + expect(q.objectFactories).to.have.length(2); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories[1]()).to.be.an.instanceof(Reaction); + }); - expect(q.query()).to.equal(expectedQuery); + it('should allow function factories', function () { + var q = startingQuery + .follow('Experiment.reaction_set', 'reactions') + .wrapDynamically(reactionFactory); - expect(q.objectFactories).to.have.length(2); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q.objectFactories[1]()).to.be.an.instanceof(Reaction); + expect(q.query()).to.equal(expectedQuery); - expect(q2.query()).to.equal(expectedQuery); + expect(q.objectFactories).to.have.length(2); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories[1]()).to.be.an.instanceof(Reaction); + }); - expect(q2.objectFactories).to.have.length(2); - expect(q2.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q2.objectFactories[1]()).to.be.an.instanceof(Reaction); - }); + it('should allow shortcuts', function () { + var q = startingQuery.clone() + .follow('Experiment.reaction_set', 'reactions', Reaction); + var q2 = startingQuery.clone() + .follow('Experiment.reaction_set', 'reactions', reactionFactory); - }); + expect(q.query()).to.equal(expectedQuery); - describe('#with', function () { - var expectedQuery = ( - 'Experiment(id=302)' - + ' ?(Experiment.compounds_of_interest)' - + ' Experiment.reaction_set' - ); - var startingQuery; - - // Fake constructor - function Experiment() { return this; } - function Reaction() { return this; } - function Compound() { return this; } - function compoundFactory() { return Object.create(new Compound()); } - - beforeEach(function () { - startingQuery = new curious.CuriousQuery( - 'Experiment(id=302)', 'experiments', Experiment - ); - }); + expect(q.objectFactories).to.have.length(2); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories[1]()).to.be.an.instanceof(Reaction); - it('should append to the query with a groupng', function () { - var q = startingQuery - .with('Experiment.compounds_of_interest', 'compounds') - .follow('Experiment.reaction_set', 'reactions'); + expect(q2.query()).to.equal(expectedQuery); - expect(q.query()).to.equal(expectedQuery); + expect(q2.objectFactories).to.have.length(2); + expect(q2.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q2.objectFactories[1]()).to.be.an.instanceof(Reaction); + }); }); - it('should successfully allow terms without commas inside the group', function () { - var q = startingQuery - .with('Experiment.compounds_of_interest Compound.standard_set', 'standards') - .follow('Experiment.reaction_set', 'reactions'); - - expect(q.query()).to.equal( + describe('#with', function () { + var expectedQuery = ( 'Experiment(id=302)' - + ' ?(Experiment.compounds_of_interest Compound.standard_set)' + + ' ?(Experiment.compounds_of_interest)' + ' Experiment.reaction_set' ); - }); - - it('should require both a term and a relationship', function () { - expect(function () { - startingQuery.with(); - }).to.throw(/term/); - - expect(function () { - startingQuery.with('Experiment.reaction_set'); - }).to.throw(/relationship/); - }); + var startingQuery; - it('should allow custom constructors', function () { - var q = startingQuery - .with('Experiment.compounds_of_interest', 'compounds').wrapWith(Compound) - .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); + // Fake constructor + function Experiment() { return this; } + function Reaction() { return this; } + function Compound() { return this; } + function compoundFactory() { return Object.create(new Compound()); } - expect(q.query()).to.equal(expectedQuery); + beforeEach(function () { + startingQuery = new curious.CuriousQuery( + 'Experiment(id=302)', 'experiments', Experiment + ); + }); - expect(q.objectFactories).to.have.length(3); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q.objectFactories[1]()).to.be.an.instanceof(Compound); - expect(q.objectFactories[2]()).to.be.an.instanceof(Reaction); - }); + it('should append to the query with a groupng', function () { + var q = startingQuery + .with('Experiment.compounds_of_interest', 'compounds') + .follow('Experiment.reaction_set', 'reactions'); - it('should allow function factories', function () { - var q = startingQuery - .with('Experiment.compounds_of_interest', 'compounds') - .wrapDynamically(compoundFactory) - .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); + expect(q.query()).to.equal(expectedQuery); + }); - expect(q.query()).to.equal(expectedQuery); + it('should successfully allow terms without commas inside the group', function () { + var q = startingQuery + .with('Experiment.compounds_of_interest Compound.standard_set', 'standards') + .follow('Experiment.reaction_set', 'reactions'); - expect(q.objectFactories).to.have.length(3); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q.objectFactories[1]()).to.be.an.instanceof(Compound); - expect(q.objectFactories[2]()).to.be.an.instanceof(Reaction); - }); + expect(q.query()).to.equal( + 'Experiment(id=302)' + + ' ?(Experiment.compounds_of_interest Compound.standard_set)' + + ' Experiment.reaction_set' + ); + }); - it('should allow shortcuts', function () { - var q = startingQuery.clone() - .with('Experiment.compounds_of_interest', 'compounds', Compound) - .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); - var q2 = startingQuery.clone() - .with('Experiment.compounds_of_interest', 'compounds', compoundFactory) - .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); + it('should require both a term and a relationship', function () { + expect(function () { + startingQuery.with(); + }).to.throw(/term/); - expect(q.query()).to.equal(expectedQuery); + expect(function () { + startingQuery.with('Experiment.reaction_set'); + }).to.throw(/relationship/); + }); - expect(q.objectFactories).to.have.length(3); - expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q.objectFactories[1]()).to.be.an.instanceof(Compound); - expect(q.objectFactories[2]()).to.be.an.instanceof(Reaction); + it('should allow custom constructors', function () { + var q = startingQuery + .with('Experiment.compounds_of_interest', 'compounds').wrapWith(Compound) + .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); - expect(q2.query()).to.equal(expectedQuery); + expect(q.query()).to.equal(expectedQuery); - expect(q2.objectFactories).to.have.length(3); - expect(q2.objectFactories[0]()).to.be.an.instanceof(Experiment); - expect(q2.objectFactories[1]()).to.be.an.instanceof(Compound); - expect(q2.objectFactories[2]()).to.be.an.instanceof(Reaction); - }); - }); + expect(q.objectFactories).to.have.length(3); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories[1]()).to.be.an.instanceof(Compound); + expect(q.objectFactories[2]()).to.be.an.instanceof(Reaction); + }); - // having/notHaving should work basically the same way - [ - {word: 'having', symbol: '+'}, - {word: 'notHaving', symbol: '-'}, - ].forEach(function (having) { + it('should allow function factories', function () { + var q = startingQuery + .with('Experiment.compounds_of_interest', 'compounds') + .wrapDynamically(compoundFactory) + .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); - describe('#' + having.word, function () { - var havingClause = 'Experiment.compounds_of_interest(id=123)'; - var expectedQuery = 'Experiment ' + having.symbol + '(' + havingClause + ')'; - var startingQuery; + expect(q.query()).to.equal(expectedQuery); - beforeEach(function () { - startingQuery = new curious.CuriousQuery( - 'Experiment', 'experiments' - ); + expect(q.objectFactories).to.have.length(3); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories[1]()).to.be.an.instanceof(Compound); + expect(q.objectFactories[2]()).to.be.an.instanceof(Reaction); }); - it('should append to the query with a "' + having.symbol + '"', function () { - var q = startingQuery[having.word](havingClause); + it('should allow shortcuts', function () { + var q = startingQuery.clone() + .with('Experiment.compounds_of_interest', 'compounds', Compound) + .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); + var q2 = startingQuery.clone() + .with('Experiment.compounds_of_interest', 'compounds', compoundFactory) + .follow('Experiment.reaction_set', 'reactions').wrapWith(Reaction); + expect(q.query()).to.equal(expectedQuery); - }); - it('should correctly insert into a query', function () { - var q = startingQuery[having.word](havingClause) - .follow('Experiment.reaction_set', 'reactions'); - expect(q.query()).to.equal(expectedQuery + ', Experiment.reaction_set'); - }); - }); - }); + expect(q.objectFactories).to.have.length(3); + expect(q.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q.objectFactories[1]()).to.be.an.instanceof(Compound); + expect(q.objectFactories[2]()).to.be.an.instanceof(Reaction); - describe('#setExistingObjects', function () { - var query; - var someObjects = [{a: 'b'}, {c: 'd', e: 'f'}, {g: 23}]; + expect(q2.query()).to.equal(expectedQuery); - beforeEach(function () { - query = new curious.CuriousQuery(); + expect(q2.objectFactories).to.have.length(3); + expect(q2.objectFactories[0]()).to.be.an.instanceof(Experiment); + expect(q2.objectFactories[1]()).to.be.an.instanceof(Compound); + expect(q2.objectFactories[2]()).to.be.an.instanceof(Reaction); + }); }); - it('should set the existingObjects property', function () { - query.setExistingObjects(someObjects); - expect(query.existingObjects).to.deep.equal(someObjects); + // having/notHaving should work basically the same way + [ + {word: 'having', symbol: '+'}, + {word: 'notHaving', symbol: '-'}, + ].forEach(function (having) { + describe('#' + having.word, function () { + var havingClause = 'Experiment.compounds_of_interest(id=123)'; + var expectedQuery = 'Experiment ' + having.symbol + '(' + havingClause + ')'; + var startingQuery; + + beforeEach(function () { + startingQuery = new curious.CuriousQuery( + 'Experiment', 'experiments' + ); + }); + + it('should append to the query with a "' + having.symbol + '"', function () { + var q = startingQuery[having.word](havingClause); + expect(q.query()).to.equal(expectedQuery); + }); + + it('should correctly insert into a query', function () { + var q = startingQuery[having.word](havingClause) + .follow('Experiment.reaction_set', 'reactions'); + expect(q.query()).to.equal(expectedQuery + ', Experiment.reaction_set'); + }); + }); }); - it('should make a one-level shallow copy', function () { - query.setExistingObjects(someObjects); + describe('#setExistingObjects', function () { + var query; + var someObjects = [{a: 'b'}, {c: 'd', e: 'f'}, {g: 23}]; - // one-level-deep assignments do not transfer - someObjects[0] = 'moop'; - expect(query.existingObjects[0]).to.deep.equal({a: 'b'}); + beforeEach(function () { + query = new curious.CuriousQuery(); + }); - // nested assigments do transfer, however - someObjects[1].c = 'moop'; - expect(query.existingObjects).to.have.deep.property('[1].c', 'moop'); - }); + it('should set the existingObjects property', function () { + query.setExistingObjects(someObjects); + expect(query.existingObjects).to.deep.equal(someObjects); + }); - it('should do nothing if passed in nothing', function () { - expect(query.existingObjects).to.be.null; - query.setExistingObjects(); - expect(query.existingObjects).to.be.null; - }); - }); + it('should make a one-level shallow copy', function () { + query.setExistingObjects(someObjects); - describe('#setParams', function () { - var query; - var someParams = {a: 'b', c: 'd', e: 'f', g: 23, h: {i: 'j'}}; + // one-level-deep assignments do not transfer + someObjects[0] = 'moop'; + expect(query.existingObjects[0]).to.deep.equal({a: 'b'}); - beforeEach(function () { - query = new curious.CuriousQuery(); - }); + // nested assigments do transfer, however + someObjects[1].c = 'moop'; + expect(query.existingObjects).to.have.deep.property('[1].c', 'moop'); + }); - it('should set the params property', function () { - query.setParams(someParams); - expect(query.params).to.deep.equal(someParams); + it('should do nothing if passed in nothing', function () { + expect(query.existingObjects).to.be.null; + query.setExistingObjects(); + expect(query.existingObjects).to.be.null; + }); }); - it('should make a one-level shallow copy', function () { - query.setParams(someParams); + describe('#setParams', function () { + var query; + var someParams = {a: 'b', c: 'd', e: 'f', g: 23, h: {i: 'j'}}; - // one-level-deep assignments do not transfer - someParams.a = 'moop'; - expect(query.params).to.have.property('a', 'b'); + beforeEach(function () { + query = new curious.CuriousQuery(); + }); - // nested assigments do transfer, however - someParams.h.i = 'moop'; - expect(query.params).to.have.deep.property('h.i', 'moop'); - }); + it('should set the params property', function () { + query.setParams(someParams); + expect(query.params).to.deep.equal(someParams); + }); - it('should do nothing if passed in nothing', function () { - expect(query.params).to.be.null; - query.setParams(); - expect(query.params).to.be.null; - }); - }); + it('should make a one-level shallow copy', function () { + query.setParams(someParams); - describe('#clone', function () { - var excludedProperties; - var originalQuery; + // one-level-deep assignments do not transfer + someParams.a = 'moop'; + expect(query.params).to.have.property('a', 'b'); - // Fake constructor - function Dataset() {} + // nested assigments do transfer, however + someParams.h.i = 'moop'; + expect(query.params).to.have.deep.property('h.i', 'moop'); + }); - before(function () { - excludedProperties = { - objectFactories: 'object factory functions do not compare directly', - }; + it('should do nothing if passed in nothing', function () { + expect(query.params).to.be.null; + query.setParams(); + expect(query.params).to.be.null; + }); }); - beforeEach(function () { - originalQuery = new curious.CuriousQuery('Experiment(id=302)', 'experiments') - .with('Experiment.compounds_of_interest', 'compounds') - .follow('Experiment.reaction_set', 'reactions') - .having('Reaction.subjects(name="s481466")') - .follow('Reaction.dataset_set', 'datasets', Dataset) - .follow('Dataset.attachment_set', 'attachments'); - }); + describe('#clone', function () { + var excludedProperties; + var originalQuery; - it('should create the same query string', function () { - expect(originalQuery.clone().query()).to.equal(originalQuery.query()); - }); + // Fake constructor + function Dataset() {} - it('should clone the properties corretly', function () { - Object.keys(originalQuery).forEach(function (property) { - if (!excludedProperties.hasOwnProperty(property)) { - expect(originalQuery.clone()[property]) - .to - .deep - .equal(originalQuery[property], 'Comparing: query.' + property); - } + before(function () { + excludedProperties = { + objectFactories: 'object factory functions do not compare directly', + }; }); - }); - it('should clone the object factories correctly', function () { - var clonedObjectFactories = originalQuery.clone().objectFactories; + beforeEach(function () { + originalQuery = new curious.CuriousQuery('Experiment(id=302)', 'experiments') + .with('Experiment.compounds_of_interest', 'compounds') + .follow('Experiment.reaction_set', 'reactions') + .having('Reaction.subjects(name="s481466")') + .follow('Reaction.dataset_set', 'datasets', Dataset) + .follow('Dataset.attachment_set', 'attachments'); + }); - originalQuery.objectFactories.forEach(function (factory, factoryIndex) { - if (factory) { - expect(Object.getPrototypeOf(clonedObjectFactories[factoryIndex]())) - .to - .deep - .equal(Object.getPrototypeOf(factory())); - } + it('should create the same query string', function () { + expect(originalQuery.clone().query()).to.equal(originalQuery.query()); + }); + + it('should clone the properties corretly', function () { + Object.keys(originalQuery).forEach(function (property) { + if (!excludedProperties.hasOwnProperty(property)) { + expect(originalQuery.clone()[property]) + .to + .deep + .equal(originalQuery[property], 'Comparing: query.' + property); + } + }); }); - }); - }); - describe('#extend', function () { - var excludedProperties; - var combinedQueryString = ( - 'Experiment(id=302) ?(Experiment.compounds_of_interest)' - + ' Experiment.reaction_set +(Reaction.subjects(name="s481466")),' - + ' Reaction.dataset_set, Dataset.attachment_set' - ); - var query1; - var query2; - - // Fake constructor - function Dataset() {} - - before(function () { - excludedProperties = { - objectFactories: 'functions do not compare well', - }; + it('should clone the object factories correctly', function () { + var clonedObjectFactories = originalQuery.clone().objectFactories; + + originalQuery.objectFactories.forEach(function (factory, factoryIndex) { + if (factory) { + expect(Object.getPrototypeOf(clonedObjectFactories[factoryIndex]())) + .to + .deep + .equal(Object.getPrototypeOf(factory())); + } + }); + }); }); - beforeEach(function () { - query1 = new curious.CuriousQuery('Experiment(id=302)', 'experiments') - .with('Experiment.compounds_of_interest', 'compounds') - .follow('Experiment.reaction_set', 'reactions') - .having('Reaction.subjects(name="s481466")'); + describe('#extend', function () { + var excludedProperties; + var combinedQueryString = ( + 'Experiment(id=302) ?(Experiment.compounds_of_interest)' + + ' Experiment.reaction_set +(Reaction.subjects(name="s481466")),' + + ' Reaction.dataset_set, Dataset.attachment_set' + ); + var query1; + var query2; - query2 = new curious.CuriousQuery('Reaction.dataset_set', 'datasets', Dataset) - .follow('Dataset.attachment_set', 'attachments'); - }); + // Fake constructor + function Dataset() {} - it('should create the correct query string', function () { - expect(query1.extend(query2).query()).to.equal(combinedQueryString); - }); + before(function () { + excludedProperties = { + objectFactories: 'functions do not compare well', + }; + }); + + beforeEach(function () { + query1 = new curious.CuriousQuery('Experiment(id=302)', 'experiments') + .with('Experiment.compounds_of_interest', 'compounds') + .follow('Experiment.reaction_set', 'reactions') + .having('Reaction.subjects(name="s481466")'); - it('should extend properties corretly', function () { - var extendedQuery = query1.clone().extend(query2); - - Object.keys(query1).forEach(function (property) { - if ( - !excludedProperties.hasOwnProperty(property) - && (extendedQuery[property] instanceof Array) - ) { - expect(extendedQuery[property]) - .to - .deep - .equal( - query1[property].concat(query2[property]), - 'Examining: extendedQuery.' + property - ); - } + query2 = new curious.CuriousQuery('Reaction.dataset_set', 'datasets', Dataset) + .follow('Dataset.attachment_set', 'attachments'); }); - }); - it('should extend the object factories correctly', function () { - var extendedQuery = query1.clone().extend(query2); - var extendedObjectFactories = query1.objectFactories.concat(query2.objectFactories); + it('should create the correct query string', function () { + expect(query1.extend(query2).query()).to.equal(combinedQueryString); + }); - extendedQuery.objectFactories.forEach(function (factory, factoryIndex) { - if (factory) { - expect(Object.getPrototypeOf(extendedObjectFactories[factoryIndex]())) - .to - .deep - .equal(Object.getPrototypeOf(factory())); - } + it('should extend properties corretly', function () { + var extendedQuery = query1.clone().extend(query2); + + Object.keys(query1).forEach(function (property) { + if ( + !excludedProperties.hasOwnProperty(property) + && (extendedQuery[property] instanceof Array) + ) { + expect(extendedQuery[property]) + .to + .deep + .equal( + query1[property].concat(query2[property]), + 'Examining: extendedQuery.' + property + ); + } + }); }); - }); - }); - describe('#perform', function () { - var curiousClient; + it('should extend the object factories correctly', function () { + var extendedQuery = query1.clone().extend(query2); + var extendedObjectFactories = query1.objectFactories.concat(query2.objectFactories); + + extendedQuery.objectFactories.forEach(function (factory, factoryIndex) { + if (factory) { + expect(Object.getPrototypeOf(extendedObjectFactories[factoryIndex]())) + .to + .deep + .equal(Object.getPrototypeOf(factory())); + } + }); + }); + }); - before(function () { + describe('#perform', function () { + var curiousClient; - // Make a mock CuriousClient class - function MockCuriousClient() { } + before(function () { + // Make a mock CuriousClient class + function MockCuriousClient() { } - MockCuriousClient.prototype.performQuery = function () { - return 'some arbitrary value'; - }; + MockCuriousClient.prototype.performQuery = function () { + return 'some arbitrary value'; + }; - curiousClient = new MockCuriousClient(); - }); + curiousClient = new MockCuriousClient(); + }); - it('should return whatever CuriousClient#performQuery does', function () { - var q = new curious.CuriousQuery(); - expect(q.perform(curiousClient)).to.equal(curiousClient.performQuery()); + it('should return whatever CuriousClient#performQuery does', function () { + var q = new curious.CuriousQuery(); + expect(q.perform(curiousClient)).to.equal(curiousClient.performQuery()); + }); }); }); - - -}); - }()); From bd6b7ba423d556408c925e2895fd86daa0a6c2bd Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 2 Jul 2016 00:18:07 -0400 Subject: [PATCH 21/77] Add a camelCase option and incorporate it into object construction Also add tests to verify objects are constructed with camelCased keys --- curious.js | 197 +++++++++++++++++++++++---------------- tests/curious_client.js | 48 +++++----- tests/curious_objects.js | 31 +++++- tests/examples.js | 20 +++- 4 files changed, 191 insertions(+), 105 deletions(-) diff --git a/curious.js b/curious.js index c789579..d68a8b3 100644 --- a/curious.js +++ b/curious.js @@ -431,7 +431,7 @@ * The contents of the starting term. * @param {string} relationship * The name of this term in inter-term relationships - * @param {Function} customConstructor + * @param {function} customConstructor * A custom constructor function for the resulting objects. * * @return {CuriousQuery} The query object, with the term appended @@ -627,9 +627,10 @@ * @param {Object} objectData * A plain JavaScript object representing the query data, as parsed from * the returned JSON. - * + * @param {boolean?} camelCase + * If true, construct camel-cased versions the fields in objectData. */ - function CuriousObject(objectData) { + function CuriousObject(objectData, camelCase) { var newObject = this; // Special properties that aren't data-bearing, but are often convenient @@ -638,7 +639,13 @@ // Copy over the object data to be properties of the new CuriousObject Object.keys(objectData).forEach(function (key) { - newObject[key] = objectData[key]; + var newKey = key; + + if (camelCase) { + newKey = CuriousObjects.makeCamelCase(key); + } + + newObject[newKey] = objectData[key]; }); return newObject; @@ -666,13 +673,16 @@ * The url of every object, if they have one * @param {string} model * The name of the Django model these objects come from - * @param {function} customConstructor + * @param {function?} customConstructor * A constructor to use instead of the default CuriousObject constructor. + * @param {boolean?} camelCase + * If true, construct camel-cased versions of the JSON objects returned + * by the Curious server. * * @return {Array} * An array of objects, which contain the data described in queryData. */ - function _parseObjects(queryData, model, customConstructor) { + function _parseObjects(queryData, model, customConstructor, camelCase) { var objects = []; if (queryData.objects instanceof Array) { @@ -694,13 +704,19 @@ // got all the fields assigned, so we should do it ourselves just // in case for any fields the constructor might have missed. queryData.fields.forEach(function (fieldName) { + var newFieldName = fieldName; + + if (camelCase) { + newFieldName = CuriousObjects.makeCamelCase(fieldName); + } + // NOTE: don't check for obj.hasOwnProperty - we actually want to // override existing fields in obj - obj[fieldName] = objectData[fieldName]; + obj[newFieldName] = objectData[fieldName]; }); } else { // The CuriousObject constructor does this automatically - obj = new CuriousObject(objectData); + obj = new CuriousObject(objectData, camelCase); } // Set the magic fields @@ -725,7 +741,7 @@ * * @param {string[]} relationships * The names of the relationships objects will have to one another. - * @param {Function[]} customConstructors + * @param {function[]} customConstructors * The custom constructors for curious object classes. * @param {Object} queryJSONResponse * An object of fields holding the query response, as returned and parsed @@ -750,13 +766,17 @@ * @param {Array>} existingObjects * The existing objects. Each object in the array is a mapping of an id * to its corresponding object. + * @param {boolean?} camelCase + * If true, construct camel-cased versions of the JSON objects returned + * by the Curious server. * * @return {{objects: Object[], trees: Object[]}} * The parsed objects. trees holds any hierarchical * relationships, for recursive queries. */ function parse( - relationships, customConstructors, queryJSONResponse, existingObjects + relationships, customConstructors, queryJSONResponse, existingObjects, + camelCase ) { var combinedObjects = []; var trees = []; @@ -775,7 +795,9 @@ // Only pass in custom constructors if we need to (customConstructors instanceof Array) ? customConstructors[queryIndex] - : null + : null, + + camelCase ); queryObjects.forEach(function (object) { @@ -814,6 +836,11 @@ var forwardRelationshipName = relationships[queryIndex]; var reverseRelationshipName = relationships[joinIndex]; + if (camelCase) { + forwardRelationshipName = CuriousObjects.makeCamelCase(forwardRelationshipName); + reverseRelationshipName = CuriousObjects.makeCamelCase(reverseRelationshipName); + } + var joinSourceObjects = combinedObjects[joinIndex]; var joinDestinationObjects = combinedObjects[queryIndex]; @@ -951,8 +978,8 @@ * Camel case a string with - or _ separators: * * @example - * CuriousObjects.camelCase('this_is-someTHING') === 'thisIsSomething' - * CuriousObjects.camelCase('_alreadyDone') === '_alreadyDone' + * CuriousObjects.makeCamelCase('this_is-someTHING') === 'thisIsSomething' + * CuriousObjects.makeCamelCase('_alreadyDone') === '_alreadyDone' * * @memberof module:curious.CuriousObjects * @@ -960,71 +987,73 @@ * * @return {string} The input, camel-cased. */ - function camelCase(input) { + function makeCamelCase(input) { var components; var casedComponents; var separators; - var output; - - // For leading/trailing separators - separators = { - leading: { - re: /^[-_]+/g, - match: null, - text: '', - }, - trailing: { - re: /[-_]+$/g, - match: null, - text: '', - }, - }; - - // Match the leading/trailing separators and store the text - Object.keys(separators).forEach(function (key) { - var separatorType = separators[key]; - separatorType.match = separatorType.re.exec(input); - if (separatorType.match) { - separatorType.text = separatorType.match[0]; - } - }); - - if (separators.leading.text.length === input.length) { - // Special case: string consists entirely of separators: just return it - output = input; - } else { - // Only split the parts of the string that are not leading/trailing - // separators - components = input.substring( - separators.leading.text.length, - input.length - separators.trailing.text.length - ).split(/[_-]/); - - // If we don't have anything to camel-case, just leave the body alone - if (components.length > 1) { - casedComponents = components.map(function (component, ix) { - // Normalize by lowercasing everything - var casedComponent = component.toLowerCase(); - - // Capitalize every word but the first - if (ix > 0) { - casedComponent = ( - casedComponent.charAt(0).toUpperCase() - + casedComponent.slice(1) - ); - } + var output = input; + + if (input) { + // For leading/trailing separators + separators = { + leading: { + re: /^[-_]+/g, + match: null, + text: '', + }, + trailing: { + re: /[-_]+$/g, + match: null, + text: '', + }, + }; + + // Match the leading/trailing separators and store the text + Object.keys(separators).forEach(function (key) { + var separatorType = separators[key]; + separatorType.match = separatorType.re.exec(input); + if (separatorType.match) { + separatorType.text = separatorType.match[0]; + } + }); - return casedComponent; - }); + if (separators.leading.text.length === input.length) { + // Special case: string consists entirely of separators: just return it + output = input; } else { - casedComponents = components; - } + // Only split the parts of the string that are not leading/trailing + // separators + components = input.substring( + separators.leading.text.length, + input.length - separators.trailing.text.length + ).split(/[_-]/); + + // If we don't have anything to camel-case, just leave the body alone + if (components.length > 1) { + casedComponents = components.map(function (component, ix) { + // Normalize by lowercasing everything + var casedComponent = component.toLowerCase(); + + // Capitalize every word but the first + if (ix > 0) { + casedComponent = ( + casedComponent.charAt(0).toUpperCase() + + casedComponent.slice(1) + ); + } + + return casedComponent; + }); + } else { + casedComponents = components; + } - output = ( - separators.leading.text - + casedComponents.join('') - + separators.trailing.text - ); + output = ( + separators.leading.text + + casedComponents.join('') + + separators.trailing.text + ); + } } return output; @@ -1036,7 +1065,7 @@ groupObjectsByID: groupObjectsByID, idList: idList, idString: idString, - camelCase: camelCase, + makeCamelCase: makeCamelCase, }; }()); @@ -1048,16 +1077,21 @@ * * @param {string[]} relationships The relationship names * @param {Array>} objects The objects from each relationship + * @param {boolean?} camelCase Whether or not to camel-case the relationship names * * @return {Object} The rearranged results */ - function _convertResultsToOutput(relationships, objects) { + function _convertResultsToOutput(relationships, objects, camelCase) { var output = {}; objects.forEach(function (object, objectIndex) { var relationship = relationships[objectIndex]; var uniqueIndex = 2; + if (camelCase) { + relationship = CuriousObjects.makeCamelCase(relationship); + } + // If there is already a key in the object with the existing relationship // name, add a number after it to make it unique. while (output.hasOwnProperty(relationship)) { @@ -1150,13 +1184,16 @@ * Default parameters to send to the serever with every query performed by * this client; see {@link module:curious.CuriousClient#performQuery} for an * explanation of what each parameter means. - * @param {boolean} quiet + * @param {boolean?} quiet * Unless true, log every query to the console. + * @param {boolean?} camelCase + * If true, construct camel-cased versions of the JSON objects returned + * by the Curious server. * * @return {{performQuery: function}} * A client object with a single performQuery method. */ - var CuriousClient = function (curiousURL, request, clientDefaultArgs, quiet) { + var CuriousClient = function (curiousURL, request, clientDefaultArgs, quiet, camelCase) { return { /** * Perform a Curious query and return back parsed objects. @@ -1221,11 +1258,15 @@ return request(curiousURL, args) .then(function (response) { var parsedResult = CuriousObjects.parse( - relationships, constructors, response.result, groupedExistingObjects + relationships, + constructors, + response.result, + groupedExistingObjects, + camelCase ); return { - objects: _convertResultsToOutput(relationships, parsedResult.objects), + objects: _convertResultsToOutput(relationships, parsedResult.objects, camelCase), trees: parsedResult.trees, }; }); diff --git a/tests/curious_client.js b/tests/curious_client.js index efdcc47..69d6353 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -31,7 +31,7 @@ describe('#performQuery', function () { it('should work with axios', function (done) { var requestFunctions; - // Set the global axios variable to test defaults + // Set the global axios variable to test axios wrapper defaults if (typeof global !== 'undefined' && !global.axios) { global.axios = axios; } @@ -43,29 +43,33 @@ ]; try { - requestFunctions.forEach(function (requestFunction) { - var client = new curious.CuriousClient( - CURIOUS_URL, - requestFunction, - null, - true - ); + // Try with and without camelCase + [true, false].forEach(function (camelCase) { + requestFunctions.forEach(function (requestFunction) { + var client = new curious.CuriousClient( + CURIOUS_URL, + requestFunction, + null, + true, + camelCase + ); - client.performQuery( - 'query does not matter', - ['experiments', 'reactions'] - ) - .then(function (response) { - var expectedObjects = examples.expectedObjects(); - expect(response).to.deep.equal({ - trees: [null, null], - objects: { - experiments: curious.CuriousObjects.values(expectedObjects.experiments), - reactions: curious.CuriousObjects.values(expectedObjects.reactions), - }, + client.performQuery( + 'query does not matter', + ['experiments', 'reactions'] + ) + .then(function (response) { + var expectedObjects = examples.expectedObjects(camelCase); + expect(response).to.deep.equal({ + trees: [null, null], + objects: { + experiments: curious.CuriousObjects.values(expectedObjects.experiments), + reactions: curious.CuriousObjects.values(expectedObjects.reactions), + }, + }); + }, function (error) { + throw error; }); - }, function (error) { - throw error; }); }); diff --git a/tests/curious_objects.js b/tests/curious_objects.js index 39281f3..a332dc1 100644 --- a/tests/curious_objects.js +++ b/tests/curious_objects.js @@ -122,7 +122,6 @@ expect(objsByID).to.deep.equal(objsByIDWithNoID); }); - }); describe('#idList', function () { @@ -147,7 +146,6 @@ }); it('should maintain the order of IDs and keep the first unique one', function () { - // This object with id = 2 will be ignored, but the first one will be kept. objs.push({name: 'b2', id: 2}); objs.push({name: 'd', id: 4}); @@ -207,7 +205,7 @@ }); describe('#camelCase', function () { - var camelCase = curious.CuriousObjects.camelCase; + var camelCase = curious.CuriousObjects.makeCamelCase; it('should replace all _ with a camel-casing', function () { expect(camelCase('a_single')).to.equal('aSingle'); @@ -270,11 +268,16 @@ expect(camelCase('$$')).to.equal('$$'); }); - it('should leave valid expressions alone:', function () { + it('should leave valid expressions alone', function () { expect(camelCase('word')).to.equal('word'); expect(camelCase('aCamelCasedExpression')).to.equal('aCamelCasedExpression'); expect(camelCase('_aPrefixedExpression')).to.equal('_aPrefixedExpression'); }); + + it('should leave null and undefined values unchanged', function () { + expect(camelCase(null)).to.equal(null); + expect(camelCase()).to.be.an('undefined'); + }); }); describe('#parse', function () { @@ -302,6 +305,26 @@ ); }); + it('should correctly camel-case the output', function () { + var expectedObjects = examples.expectedObjects(true); + + expect(curious.CuriousObjects.parse( + ['experiments', 'reactions'], + null, + queryJSONResponse, + null, + true + )).to.deep.equal( + { + trees: [null, null], + objects: [ + expectedObjects.experiments, + expectedObjects.reactions, + ], + } + ); + }); + it('should construct objects correctly with custom constructors', function () { var constructors; var parsedData; diff --git a/tests/examples.js b/tests/examples.js index 31d45bf..50d7f4d 100644 --- a/tests/examples.js +++ b/tests/examples.js @@ -131,10 +131,13 @@ /** * Generate sample data as would be returned by a Curious server in JSON. * + * @param {boolean?} camelCase + * If true, construct camel-cased versions of the objects. + * * @return {{experiments: Object, reactions: Object}} * The objects expected to be constructed from @{link examples.response|the response} */ - exports.expectedObjects = function () { + exports.expectedObjects = function (camelCase) { var exp; var rxns; @@ -198,6 +201,21 @@ }, ]; + if (camelCase) { + [exp].concat(rxns).forEach(function (object) { + Object.keys(object).forEach(function (key) { + var newKey = curious.CuriousObjects.makeCamelCase(key); + var value; + + if (newKey !== key) { + value = object[key]; + delete object[key]; + object[newKey] = value; + } + }); + }); + } + // Link foregin keys exp.reactions = rxns; rxns.forEach(function (rxn) { rxn.experiments = [exp]; }); From cf0ec55eac0fb1147bfd7829c3d92d1709a74b77 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 2 Jul 2016 00:25:13 -0400 Subject: [PATCH 22/77] Bump version to 2.2.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 7d855bd..27ef012 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.4", + "version": "2.2.0", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index c5bfc98..f485d10 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.1.4", + "version": "2.2.0", "license": "MIT", "ignore": [], "main": [ From 4f8ef424b75c38ca3aabbad059873cef8d9f3aa1 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 2 Jul 2016 02:17:24 -0400 Subject: [PATCH 23/77] Remove line breaks from the query string It messes up the server, apparently --- curious.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curious.js b/curious.js index d68a8b3..1677355 100644 --- a/curious.js +++ b/curious.js @@ -1253,7 +1253,7 @@ } args = _getArgs(params, clientDefaultArgs); - args.q = q; + args.q = q.replace('\n', ' '); return request(curiousURL, args) .then(function (response) { From 1657c6247ca3f020314eb3046378826b5f87acc0 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Mon, 19 Sep 2016 19:42:00 -0400 Subject: [PATCH 24/77] Add JSON serialization functionality - Expose CuriousObject as CuriousObjects.defaultType - Add CurousObject.prototype.toJSON so that JSON.stringify(curiousObject) works - Add CuriousObject.fromJSON for easy parsing from JSON strings --- curious.js | 46 ++++++++++++++++++ run_tests | 2 +- tests/curious_objects.js | 100 ++++++++++++++++++++++++++++++++++++++- tests/examples.js | 22 ++------- 4 files changed, 149 insertions(+), 21 deletions(-) diff --git a/curious.js b/curious.js index 1677355..b028692 100644 --- a/curious.js +++ b/curious.js @@ -651,6 +651,51 @@ return newObject; } + /** + * Serialize CuriousObject instances to JSON effectively if they are passed to JSON.stringify + * + * @memberof module:curious.CuriousObjects.defaultType + * + * @return {Object} A plain JavaScript object containing the CuriousObject's data + */ + CuriousObject.prototype.toJSON = function () { + var curiousObject = this; + var serializableObject = {}; + + // Copy over the object data to be properties of the new CuriousObject + Object.keys(curiousObject).forEach(function (key) { + serializableObject[key] = curiousObject[key]; + }); + + return serializableObject; + }; + + /** + * When parsing a JSON string into objects, instantiate any objects that look like + * CuriousObject instances as such, instead of plain JavaScript objects. + * + * @memberof module:curious.CuriousObjects.defaultType + * @static + * + * @param {string} jsonString A string of JSON-encoded data + * + * @return {*} The instantiated JSON-encoded data, with CuriousObjects placed where + * appropriate + */ + CuriousObject.fromJSON = function (jsonString) { + return JSON.parse(jsonString, function (key, value) { + var parsedValue = value; + + // If a plain object has '__url' and '__model' fields, it's probably a CuriousObject + if (value && value.hasOwnProperty('__url') && value.hasOwnProperty('__model')) { + parsedValue = new CuriousObject(value); + } + + return parsedValue; + }); + }; + + /** * When a Curious query is performed, the returned data comes in a set of 3 * arrays to save space: objects, fields, urls. Assemble that data into a @@ -1066,6 +1111,7 @@ idList: idList, idString: idString, makeCamelCase: makeCamelCase, + defaultType: CuriousObject, }; }()); diff --git a/run_tests b/run_tests index 6f2d7dc..a844400 100755 --- a/run_tests +++ b/run_tests @@ -1 +1 @@ -NODE_PATH=lib:. node_modules/mocha/bin/mocha tests +NODE_PATH=lib:. node_modules/.bin/mocha tests diff --git a/tests/curious_objects.js b/tests/curious_objects.js index a332dc1..6beb18c 100644 --- a/tests/curious_objects.js +++ b/tests/curious_objects.js @@ -296,11 +296,11 @@ queryJSONResponse )).to.deep.equal( { - trees: [null, null], objects: [ expectedObjects.experiments, expectedObjects.reactions, ], + trees: [null, null], } ); }); @@ -316,11 +316,11 @@ true )).to.deep.equal( { - trees: [null, null], objects: [ expectedObjects.experiments, expectedObjects.reactions, ], + trees: [null, null], } ); }); @@ -389,5 +389,101 @@ expect(parsedData.objects[1][23063]).to.contain.keys(existingObjects[1][23063]); }); }); + + describe('#defaultType', function () { + var CuriousObject = curious.CuriousObjects.defaultType; + + describe('#toJSON', function () { + var exampleObjectsWithoutKeys; + var exampleObjectsWithKeys; + + beforeEach(function () { + exampleObjectsWithoutKeys = [ + true, + false, + 3, + 0, + 'something', + '', + null, + {}, + [], + [1], + [1, 2, {3: 'q'}], + ]; + + exampleObjectsWithKeys = [ + { a: 1 }, + { a: 1, b: 2, 34: 'sam', d: { n: 'nested' }, e: [5, 6]}, + ]; + }); + + it("should be equivalent to serializing the object's internal data keys", function () { + exampleObjectsWithKeys.forEach(function (obj) { + var curiousObj = new CuriousObject(obj); + var internalObj = obj; + + ['__model', '__url'].forEach(function (field) { + internalObj[field] = curiousObj[field]; + }); + + // Use JSON.parse and .to.deep.equal to allow for varying order of serialization + expect(curiousObj.toJSON()).to.deep.equal(obj); + }); + }); + + it('should create empty objects for serializing objects with no internal data', function () { + exampleObjectsWithoutKeys.forEach(function (obj) { + var curiousObj = new CuriousObject(obj); + var internalObj = { + __model: null, + __url: null, + }; + + // Use JSON.parse and .to.deep.equal to allow for varying order of serialization + expect(JSON.parse(JSON.stringify(curiousObj.toJSON()))) + .to.deep.equal(internalObj); + }); + }); + + it('should register the class with JSON.stringify', function () { + exampleObjectsWithKeys.forEach(function (obj) { + var curiousObj = new CuriousObject(obj); + var internalObj = obj; + + ['__model', '__url'].forEach(function (field) { + internalObj[field] = curiousObj[field]; + }); + + // Use JSON.parse and .to.deep.equal to allow for varying order of serialization + expect(JSON.parse(JSON.stringify(curiousObj))) + .to.deep.equal(internalObj); + }); + }); + }); + + describe('#fromJSON', function () { + it('should create CuriousObject instances when required', function () { + expect(CuriousObject.fromJSON(JSON.stringify({ + a: 1, + __model: null, + __url: null, + }))).to.deep.equal(new CuriousObject({ a: 1 })); + }); + + it('should not create CuriousObject instances unnecessarily', function () { + expect(CuriousObject.fromJSON(JSON.stringify({ + a: 1, + }))).to.deep.equal({ a: 1 }); + }); + + it('should be idempotent with JSON.stringify', function () { + var exampleObject = examples.expectedObjects().experiments[403]; + + exampleObject.reactions = []; // Prevent it from being circular to allow serialization + expect(CuriousObject.fromJSON(JSON.stringify(exampleObject))).to.deep.equal(exampleObject); + }); + }); + }); }); }()); diff --git a/tests/examples.js b/tests/examples.js index 50d7f4d..1a39976 100644 --- a/tests/examples.js +++ b/tests/examples.js @@ -140,9 +140,10 @@ exports.expectedObjects = function (camelCase) { var exp; var rxns; + var CuriousObject = curious.CuriousObjects.defaultType; // jscs:disable requireCamelCaseOrUpperCaseIdentifiers - exp = { + exp = new CuriousObject({ id: 403, created_by_id: 22, created_on: '2015-01-16 14:54:13+00:00', @@ -156,7 +157,7 @@ temperature: null, __url: 'http://example.com/experiment/403/', __model: 'Experiment', - }; + }, camelCase); rxns = [ { @@ -199,22 +200,7 @@ __url: 'http://example.com/experiment/403/', __model: 'Reaction', }, - ]; - - if (camelCase) { - [exp].concat(rxns).forEach(function (object) { - Object.keys(object).forEach(function (key) { - var newKey = curious.CuriousObjects.makeCamelCase(key); - var value; - - if (newKey !== key) { - value = object[key]; - delete object[key]; - object[newKey] = value; - } - }); - }); - } + ].map(function (objectData) { return new CuriousObject(objectData, camelCase); }); // Link foregin keys exp.reactions = rxns; From 77c4151ef55d9f003760013a14ff98600dd873c9 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Mon, 19 Sep 2016 19:44:42 -0400 Subject: [PATCH 25/77] Only create object data for actual Objects passed in Not strings or Arrays --- curious.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/curious.js b/curious.js index b028692..c900f1c 100644 --- a/curious.js +++ b/curious.js @@ -638,15 +638,17 @@ newObject.__model = null; // Copy over the object data to be properties of the new CuriousObject - Object.keys(objectData).forEach(function (key) { - var newKey = key; + if (objectData instanceof Object && !(objectData instanceof Array)) { + Object.keys(objectData).forEach(function (key) { + var newKey = key; - if (camelCase) { - newKey = CuriousObjects.makeCamelCase(key); - } + if (camelCase) { + newKey = CuriousObjects.makeCamelCase(key); + } - newObject[newKey] = objectData[key]; - }); + newObject[newKey] = objectData[key]; + }); + } return newObject; } From a16758f1b4a770c9f20e391768879c9586eaffff Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Mon, 19 Sep 2016 19:45:10 -0400 Subject: [PATCH 26/77] Fix minor consistency issues in JSDoc --- curious.js | 192 +++++++++++++++++++++++++++-------------------------- 1 file changed, 98 insertions(+), 94 deletions(-) diff --git a/curious.js b/curious.js index c900f1c..bc44d03 100644 --- a/curious.js +++ b/curious.js @@ -1,5 +1,5 @@ /** - * Module for curious client-side query construction and JSON parsing. + * Module for curious client-side query construction and JSON parsing * * @module curious */ @@ -21,14 +21,14 @@ // QUERY TERMS /** - * Abstract base class for query terms. + * Abstract base class for query terms * * @private * @abstract * @class * @alias module:curious~QueryTerm * - * @param {string} term The internal term text to use. + * @param {string} term The internal term text to use */ var QueryTerm = function (term) { /** @@ -44,11 +44,11 @@ /** * Return the term contents as they belong in a query, wrapped with parens and - * other operators + * other operators. * * @public * - * @return {string} The term contents, formatted. + * @return {string} The term contents, formatted */ QueryTerm.prototype.toString = function () { return this.term(); }; @@ -60,7 +60,7 @@ * * @return {boolean} * True if the term implicitly joins with its following term. True by - * default. + * default */ QueryTerm.prototype.leftJoin = function () { return false; }; @@ -71,19 +71,19 @@ * @public * * @return {boolean} - * True if term is a conditional. + * True if term is a conditional */ QueryTerm.prototype.conditional = function () { return false; }; /** - * Make a term that follows the query chain. + * Make a term that follows the query chain * * @private * @class * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermFollow * - * @param {string} term The term contents. + * @param {string} term The term contents */ var QueryTermFollow = function (term) { QueryTerm.call(this, term); @@ -100,7 +100,7 @@ * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermHaving * - * @param {string} term The term contents. + * @param {string} term The term contents */ var QueryTermHaving = function (term) { QueryTerm.call(this, term); @@ -116,14 +116,14 @@ }; /** - * Make a term that performs a negative filter. + * Make a term that performs a negative (exclusive) filter. * * @private * @class * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermHaving * - * @param {string} term The term contents. + * @param {string} term The term contents */ var QueryTermNotHaving = function (term) { QueryTerm.call(this, term); @@ -146,7 +146,7 @@ * @extends {module:curious~QueryTerm} * @alias module:curious~QueryTermWith * - * @param {string} term The term contents. + * @param {string} term The term contents */ var QueryTermWith = function (term) { QueryTerm.call(this, term); @@ -202,7 +202,7 @@ * @param {function} [initialObjectClass] * A custom object class constructor for the starting term * - * @return {CuriousQuery} The newly constructed object. + * @return {CuriousQuery} The newly constructed object * * @example * // Explicitly set start, wrapWith classes @@ -242,13 +242,13 @@ /** * Generate the constructed query string represented by this object. * - * @return {string} The fully constructed query. + * @return {string} The fully constructed query */ CuriousQuery.prototype.query = function () { var query = ''; var terms = []; - // Flatten all terms and arrays of terms into a single array. + // Flatten all terms and arrays of terms into a single array this.terms.forEach(function (term) { terms = terms.concat(term); }); @@ -280,7 +280,7 @@ }; /** - * Extend this query object with another query object: Return a new query + * Extend this query object with another query object: return a new query * chain with the current query chain's terms followed * by the other query chain's terms. * @@ -376,7 +376,7 @@ * * @param {QueryTerm|Array} termObject * A {@link module:curious~QueryTerm} object (or an array of them), to - * append to the previous term. + * append to the previous term * * @return {CuriousQuery} * The query object, with the term object's string representation appended @@ -411,7 +411,7 @@ * directly to the constructor. * * @param {string} termString - * The contents of the starting term. + * The contents of the starting term * @param {string} relationship * The name of this term in inter-term relationships * @param {function} [customConstructor] @@ -428,11 +428,11 @@ * Add an inner-join term to this query. * * @param {string} termString - * The contents of the starting term. + * The contents of the starting term * @param {string} relationship * The name of this term in inter-term relationships * @param {function} customConstructor - * A custom constructor function for the resulting objects. + * A custom constructor function for the resulting objects * * @return {CuriousQuery} The query object, with the term appended */ @@ -444,7 +444,7 @@ * Add a filter term to this query. * * @param {string} termString - * The subquery to filter by. + * The subquery to filter by * * @return {CuriousQuery} The query object, with the term appended */ @@ -456,7 +456,7 @@ * Add an exclude filter term to this query. * * @param {string} termString - * The subquery to filter by. + * The subquery to filter by * * @return {CuriousQuery} The query object, with the term appended */ @@ -468,7 +468,7 @@ * Add an outer-join term to this query. * * @param {string} termString - * The contents of the starting term. + * The contents of the starting term * @param {string} relationship * The name of this term in inter-term relationships * @param {function} [customConstructor] @@ -502,7 +502,7 @@ * with factory functions that explicitly return an object. * * @param {function (Object): Object} factoryFunction - * A factory function that returns an object of the desired wrapping class. + * A factory function that returns an object of the desired wrapping class * * @return {CuriousQuery} * The query object, with the new constructor data stored internally @@ -522,12 +522,12 @@ * perform is called. * * @param {Object} params - * An object of parameters to set. See + * An object of parameters to set--see * {@link module:curious.CuriousClient#performQuery} for a full description of the * parameters. * * @return {CuriousQuery} - * The query object with its curious client parameters updated. + * The query object with its curious client parameters updated */ CuriousQuery.prototype.setParams = function (params) { var queryObject = this; @@ -551,7 +551,7 @@ * @param {Object[]} objs The existing objects to set * * @return {CuriousQuery} The query object with its existing object set - * updated. + * updated */ CuriousQuery.prototype.setExistingObjects = function (objs) { var queryObject = this; @@ -573,7 +573,7 @@ * methods. * * @param {CuriousClient} curiousClient - * A CuriousClient object that will handle performing the actual query. + * A CuriousClient object that will handle performing the actual query * * @return {Promise} * A promise, as returned by {@link module:curious.CuriousClient#performQuery} @@ -596,10 +596,10 @@ * * @param {function} customConstructor * The constructor that will be called with the new keyword to construct - * the object. + * the object * * @return {function} A factory function that will return a new object - * whenever called. + * whenever called */ function _makeObjectFactory(customConstructor) { var CustomConstructorClass = customConstructor; @@ -620,15 +620,14 @@ /** * Base (default) class for an object returned from a Curious query * - * @private - * @class + * @class CuriousObjects.defaultType * @memberof module:curious.CuriousObjects * * @param {Object} objectData * A plain JavaScript object representing the query data, as parsed from - * the returned JSON. + * the returned JSON * @param {boolean?} camelCase - * If true, construct camel-cased versions the fields in objectData. + * If true, construct camel-cased versions the fields in objectData */ function CuriousObject(objectData, camelCase) { var newObject = this; @@ -709,10 +708,10 @@ * * @param {Object} queryData * A plain JavaScript object representing the query data, as parsed from - * the returned JSON. This format is not meant to be easy to use, but + * the returned JSON--this format is not meant to be easy to use, but * takes less space. * @param {string[]} queryData.fields - * The fields every object has. + * The fields every object has * @param {Array[]} queryData.objects * An array of arrays, where each array is the values of a single object's * properties, in the order specified by queryData.fields @@ -721,13 +720,13 @@ * @param {string} model * The name of the Django model these objects come from * @param {function?} customConstructor - * A constructor to use instead of the default CuriousObject constructor. + * A constructor to use instead of the default CuriousObject constructor * @param {boolean?} camelCase * If true, construct camel-cased versions of the JSON objects returned * by the Curious server. * * @return {Array} - * An array of objects, which contain the data described in queryData. + * An array of objects that contain the data described in queryData */ function _parseObjects(queryData, model, customConstructor, camelCase) { var objects = []; @@ -787,38 +786,38 @@ * @memberof module:curious.CuriousObjects * * @param {string[]} relationships - * The names of the relationships objects will have to one another. + * The names of the relationships objects will have to one another * @param {function[]} customConstructors - * The custom constructors for curious object classes. + * The custom constructors for curious object classes * @param {Object} queryJSONResponse * An object of fields holding the query response, as returned and parsed - * directly from JSON without any post-processing. + * directly from JSON without any post-processing * @param {string} queryJSONResponse.computed_on - * The query timestamp. + * The query timestamp * @param {string} queryJSONResponse.last_model - * The model name of the last set of objects returned. + * The model name of the last set of objects returned * @param {Object[]} queryJSONResponse.results * An array of objects containing Django object ids and other - * meta-information about the query; one element per model. + * meta-information about the query; one element per model * @param {string} queryJSONResponse.results[].model - * The model name for this part of the query. + * The model name for this part of the query * @param {number} queryJSONResponse.results[].join_index - * The index of the model this model joins to. + * The index of the model this model joins to * @param {Array[]} queryJSONResponse.results[].objects * The IDs of the objects returned by the query * @param {Object[]} queryJSONResponse.data - * An array of objects containing the other fields of the Django objects, - * more than just the IDs. See _parseObjects for a description of this - * data in the queryData parameter. + * An array of objects containing the other fields of the Django objects, more than just the + * IDs--see {@link module:curious.CuriousObjects~_parseObjects} for a description of this data + * in the queryData parameter. * @param {Array>} existingObjects - * The existing objects. Each object in the array is a mapping of an id + * The existing objects--each object in the array is a mapping of an id * to its corresponding object. * @param {boolean?} camelCase * If true, construct camel-cased versions of the JSON objects returned * by the Curious server. * * @return {{objects: Object[], trees: Object[]}} - * The parsed objects. trees holds any hierarchical + * The parsed objects--trees holds any hierarchical * relationships, for recursive queries. */ function parse( @@ -934,13 +933,13 @@ /** * Utility for pulling out all the values contained in an object, in - * the same order as its keys would come out. + * the same order as its keys would come out * * @memberof module:curious.CuriousObjects * - * @param {Object} obj The object to look at. + * @param {Object} obj The object to look at * - * @return {Array} The values of the object, whatever it holds. + * @return {Array} The values of the object, whatever it holds */ function values(obj) { return Object.keys(obj).map(function (key) { return obj[key]; }); @@ -954,7 +953,7 @@ * * @param {Object[]} arrayOfObjects The array to turn into an object * - * @return {Array} The values of the object, whatever it holds. + * @return {Array} The values of the object, whatever it holds */ function groupObjectsByID(arrayOfObjects) { var objectsByID = {}; @@ -1014,7 +1013,7 @@ * * @return {string} A comma-separated string containing the objects' IDs, * with duplicates removed, in the same order as the input - * object list. + * object list */ function idString(arrayOfObjects) { var ids = idList(arrayOfObjects); @@ -1030,9 +1029,9 @@ * * @memberof module:curious.CuriousObjects * - * @param {string} input The string to camel-case. + * @param {string} input The string to camel-case * - * @return {string} The input, camel-cased. + * @return {string} The input, camel-cased */ function makeCamelCase(input) { var components; @@ -1211,7 +1210,7 @@ } /** - * Tool for making a curious query and returning parsed objects. + * Tool for making a curious query and returning parsed objects * * @class * @alias module:curious.CuriousClient @@ -1219,8 +1218,8 @@ * @param {string} curiousURL * The URL of the Curious server * @param {function (string, Object): Promise} request - *

A function that makes a POST request and returns a promise - * (a thenable). Examples are jQuery.post, + *

A function that makes a POST request and returns a Promise + * (a thenable)--examples are jQuery.post, * axios.post, and Angular's $http.post.

* *

Any function that meets the signature, makes a POST request and @@ -1230,7 +1229,7 @@ * See {@link module:curious.CuriousClient.wrappers} for the wrappers.

* @param {Object} clientDefaultArgs * Default parameters to send to the serever with every query performed by - * this client; see {@link module:curious.CuriousClient#performQuery} for an + * this client--see {@link module:curious.CuriousClient#performQuery} for an * explanation of what each parameter means. * @param {boolean?} quiet * Unless true, log every query to the console. @@ -1239,7 +1238,7 @@ * by the Curious server. * * @return {{performQuery: function}} - * A client object with a single performQuery method. + * A client object with a single performQuery method */ var CuriousClient = function (curiousURL, request, clientDefaultArgs, quiet, camelCase) { return { @@ -1249,12 +1248,12 @@ * @memberof module:curious.CuriousClient * * @param {string} q - * The query string. + * The query string * @param {string[]} relationships - * The names of relationships between each joined set of objects. + * The names of relationships between each joined set of objects * @param {Array} constructors * An array of constructors for any custom classes, or null for the - * default. + * default * @param {Object} params * Query-specific parameters for the request * @param {boolean} [params.x] @@ -1279,12 +1278,12 @@ * If provided, the name of the app, to use for cache key construction. * @param {Array>} existingObjects * Objects that already exist to be linked into the results returned by - * this query. + * this query * * @return {Promise<{objects: Array, trees: Array}>} * A promise that resolves to an object containing the parsed objects - * from the query, and a tree structure that relates IDs for recursive - * queries. + * from the query and a tree structure that relates IDs for recursive + * queries */ performQuery: function (q, relationships, constructors, params, existingObjects) { var args; @@ -1324,7 +1323,7 @@ /** * Common code of convenience functions that make it easier to interact - * with a variety of http clients. + * with a variety of http clients * * @example * var axiosWrapper = _dataUnwrapper.bind(this, 'axios'); @@ -1333,18 +1332,18 @@ * @memberof module:curious.CuriousClient.wrappers * * @param {string} defaultModuleName - * The default module object name to use if one is not provided. Should + * The default module object name to use if one is not provided--should * be bound to a string when actually used as a wrapper. * @param {Object?} moduleObjectOrFunction - * Either the module to use, like axios/$http, or the posting function - * itself, like axios.post. + * Either the module to use, like axios/$http, or the posting function + * itself, like axios.post. * @param {Object?} options - * Additional options to send to the requesting function. + * Additional options to send to the requesting function * * @return {function(string, Object): Promise} * A function that meets the requirements to make requests in the curious client: - * takes the url and arguments, makes a POST request, and returns - * a promise that resolves directly to the returned query response (unwrapped). + * takes the url and arguments, makes a POST request, and returns + * a promise that resolves directly to the returned query response (unwrapped) */ function _unwrapResponseData(defaultModuleName, moduleObjectOrFunction, options) { var mod; @@ -1389,15 +1388,20 @@ * Convenience functions for interfacing with a variety of common HTTP/ajax * client libraries. * - * Note that jQuery.post does not need a wrapper. + * NOTE: None of these client libraries are required by this module—the wrappers are here simply + * to help CuriousCient interact with code that uses those libraries. + * + * NOTE: jQuery.post does not need a wrapper--it, and any other functions that + * work like it and resolve to the same kind of data structure can be passed directly as the + * request parameter. * * @namespace module:curious.CuriousClient.wrappers */ CuriousClient.wrappers = {}; /** - * Convenience function to make it easier to interact with axios responses: - * axios is not required by this module at all. + * Convenience function to make it easier to interact with axios responses + * (axios is not required by this module at all.) * * @example * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios() ...) @@ -1407,10 +1411,10 @@ * @memberof module:curious.CuriousClient.wrappers * * @param {Object?} axiosModuleOrFunction - * Either the axios module itself, or axios.post. - * Defaults to using whatever axios resolves to. + * Either the axios module itself, or axios.post--defaults to using + * whatever axios resolves to * @param {Object?} options - * Additional options to send to the requesting function. + * Additional options to send to the requesting function * * @return {function(string, Object): Promise} * A function that meets the requirements to make requests in the curious client: @@ -1420,8 +1424,8 @@ CuriousClient.wrappers.axios = _unwrapResponseData.bind(ex, 'axios'); /** - * Convenience function to make it easier to interact with angular responses: - * angular is not required by this module at all. + * Convenience function to make it easier to interact with AngularJS $http.post + * responses (AngularJS is not required by this module at all.) * * @example * var client = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.angular() ...) @@ -1431,15 +1435,15 @@ * @memberof module:curious.CuriousClient.wrappers * * @param {Object?} angularHttpServiceOrPostFunction - * Either the Angular $http service object, or $http.post. - * Defaults to using whatever $http resolves to. + * Either the Angular $http service object, or $http.post--defaults + * to using whatever $http resolves to. * @param {Object?} options - * Additional options to send to the requesting function. + * Additional options to send to the requesting function * * @return {function(string, Object): Promise} * A function that meets the requirements to make requests in the curious client: * takes the url and arguments, makes a POST request and returns - * a promise that resolves directly to the returned query response (unwrapped). + * a promise that resolves directly to the returned query response (unwrapped) */ CuriousClient.wrappers.angular = _unwrapResponseData.bind(ex, '$http'); @@ -1453,22 +1457,22 @@ * @memberof module:curious.CuriousClient.wrappers * * @param {PolymerElement} ironAjaxElement - * The iron-ajax element being used to make the request. + * The iron-ajax element being used to make the request * @param {Object?} options - * Additional options to send to the requesting function. + * Additional options to send to the requesting function * * @return {function(string, Object): Promise} * A function that meets the requirements to make requests in the curious client: * takes the url and arguments, makes a POST request with * ironAjaxElement, and returns a promise that resolves - * directly to the returned query response (unwrapped). + * directly to the returned query response (unwrapped) */ CuriousClient.wrappers.ironAjax = function (ironAjaxElement, options) { return function (url, args) { var oldAutoValue; var request; - // Don't make requests while we're setting the properties + // Don't make requests while we're setting the properties. oldAutoValue = ironAjaxElement.get('auto'); ironAjaxElement.set('auto', false); @@ -1490,7 +1494,7 @@ // Return the promise that gets fired when the XHR completes, but parse // out the actual response data, since the original promise resolves to - // the iron-request object + // the iron-request object. return request.completes.then(function (ironRequestObject) { return ironRequestObject.response; }); From 124fcef99bce516777fd4e7dc82c2a9cec5f8189 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Mon, 19 Sep 2016 19:47:58 -0400 Subject: [PATCH 27/77] Bump version to 2.2.1 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 27ef012..c3c0e7d 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.2.0", + "version": "2.2.1", "license": "MIT", "ignore": [], "main": [ diff --git a/package.json b/package.json index f485d10..bb0fe74 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.2.0", + "version": "2.2.1", "license": "MIT", "ignore": [], "main": [ From 6a5e86d0bb66a6cb5721d701775d15121c7f7488 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 13:17:23 -0400 Subject: [PATCH 28/77] Make minor improvements to the release_doc script --- release_doc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/release_doc b/release_doc index ac65b28..169bf07 100755 --- a/release_doc +++ b/release_doc @@ -1,4 +1,4 @@ -#!/bin/bash -v +#!/bin/bash -e -x cd `dirname $0`; @@ -19,7 +19,7 @@ tar xvf $archivepath git add . git commit -m "Update documentation to $current_head" -git push +git push origin gh-pages rm -vf $archivepath From 37f03b90a8fbfd0e33e26e227892c3f16eae89a7 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 13:48:31 -0400 Subject: [PATCH 29/77] Move scripts to scripts/ folder --- make_doc | 3 --- package.json | 6 +++++- run_tests | 1 - scripts/make_doc | 10 ++++++++++ release_doc => scripts/release_doc | 9 ++++++--- repl => scripts/repl | 4 +++- scripts/run_tests | 9 +++++++++ 7 files changed, 33 insertions(+), 9 deletions(-) delete mode 100755 make_doc delete mode 100755 run_tests create mode 100755 scripts/make_doc rename release_doc => scripts/release_doc (75%) rename repl => scripts/repl (73%) create mode 100755 scripts/run_tests diff --git a/make_doc b/make_doc deleted file mode 100755 index 0483bf9..0000000 --- a/make_doc +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -mkdir -p doc -jsdoc --destination doc curious.js README.md "$@" diff --git a/package.json b/package.json index bb0fe74..1881b16 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,11 @@ "./curious.js" ], "scripts": { - "test": "./run_tests" + "make_doc": "./scripts/make_doc", + "prepublish": "./scripts/make_doc", + "release_doc": "./scripts/release_doc", + "repl": "./scripts/repl", + "test": "./scripts/run_tests" }, "dependencies": { }, diff --git a/run_tests b/run_tests deleted file mode 100755 index a844400..0000000 --- a/run_tests +++ /dev/null @@ -1 +0,0 @@ -NODE_PATH=lib:. node_modules/.bin/mocha tests diff --git a/scripts/make_doc b/scripts/make_doc new file mode 100755 index 0000000..95418a6 --- /dev/null +++ b/scripts/make_doc @@ -0,0 +1,10 @@ +#!/bin/bash -e -x + +# make_doc - generate JSDoc for the project + +cd "`dirname $0`/.." + +mkdir -p doc +./node_modules/.bin/jsdoc --destination doc curious.js README.md "$@" + +cd - diff --git a/release_doc b/scripts/release_doc similarity index 75% rename from release_doc rename to scripts/release_doc index 169bf07..1d2b6d7 100755 --- a/release_doc +++ b/scripts/release_doc @@ -1,18 +1,21 @@ #!/bin/bash -e -x -cd `dirname $0`; +# release_doc - generate JSDoc for the project and push it to GitHub Pages + +cd "`dirname $0`/.." archivepath=`mktemp` current_branch=`git symbolic-ref --short -q HEAD` current_head=`git rev-parse HEAD` -./make_doc +./scripts/make_doc # Arcive documentation somewhere safe tar -cv -f $archivepath -C doc . # Switch to the gh-pages branch & clean everything -git checkout gh-pages && rm -rf ./* +git checkout gh-pages +rm -rf ./* # Extract the archived documentation tar xvf $archivepath diff --git a/repl b/scripts/repl similarity index 73% rename from repl rename to scripts/repl index 539ff7e..f1af5e5 100755 --- a/repl +++ b/scripts/repl @@ -1,7 +1,9 @@ #!/usr/bin/env node +// repl - run a REPL with curious.js already loaded + var repl = require('repl'); -var curious = require('./curious.js'); +var curious = require('../curious'); var replServer = repl.start({ prompt: 'curious-js > ', diff --git a/scripts/run_tests b/scripts/run_tests new file mode 100755 index 0000000..c1e74f5 --- /dev/null +++ b/scripts/run_tests @@ -0,0 +1,9 @@ +#!/bin/bash -e -x + +# run_tests - run the entire project's test suite + +cd "`dirname $0`/.." + +NODE_PATH=lib:. ./node_modules/.bin/mocha tests + +cd - From 17ea2ae50f95fdc1e0f44bcce620b75dc4445aa4 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 14:19:20 -0400 Subject: [PATCH 30/77] Ignore unnecessary files in each package --- bower.json | 8 +++++++- package.json | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index c3c0e7d..7873123 100644 --- a/bower.json +++ b/bower.json @@ -14,7 +14,13 @@ ], "version": "2.2.1", "license": "MIT", - "ignore": [], + "ignore": [ + "doc", + "scripts", + "tests", + "package.json", + "**/.*" + ], "main": [ "./curious.js" ], diff --git a/package.json b/package.json index 1881b16..f46dbdb 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ ], "version": "2.2.1", "license": "MIT", - "ignore": [], + "ignore": [ + "bower.json", + "**/.*" + ], "main": [ "./curious.js" ], From 5e513a8a11d236c90c6e320ab82021f73f423576 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 15:43:08 -0400 Subject: [PATCH 31/77] Add cleaning step to doc script --- scripts/make_doc | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/make_doc b/scripts/make_doc index 95418a6..13a07e5 100755 --- a/scripts/make_doc +++ b/scripts/make_doc @@ -5,6 +5,7 @@ cd "`dirname $0`/.." mkdir -p doc +rm -rf doc/* ./node_modules/.bin/jsdoc --destination doc curious.js README.md "$@" cd - From 00020afa8a12d5b5a2e27bb949ce0d3644566eac Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 15:43:23 -0400 Subject: [PATCH 32/77] Clean up types and labels for documentation --- curious.js | 95 ++++++++++++++++++++++++++---------------------------- 1 file changed, 46 insertions(+), 49 deletions(-) diff --git a/curious.js b/curious.js index bc44d03..8349bc9 100644 --- a/curious.js +++ b/curious.js @@ -334,7 +334,7 @@ * array of them * @param {string} relationship * The name of this term in inter-term relationships - * @param {function} [customConstructor] + * @param {?function(Object)=} customConstructor * A custom constructor for the resulting objects, if this part of the * query returns new objects * @@ -414,7 +414,7 @@ * The contents of the starting term * @param {string} relationship * The name of this term in inter-term relationships - * @param {function} [customConstructor] + * @param {?function(Object)=} customConstructor * A custom constructor for the resulting objects, if this part of the * query returns new objects * @@ -431,7 +431,7 @@ * The contents of the starting term * @param {string} relationship * The name of this term in inter-term relationships - * @param {function} customConstructor + * @param {?function(Object)=} customConstructor * A custom constructor function for the resulting objects * * @return {CuriousQuery} The query object, with the term appended @@ -521,7 +521,7 @@ * Set the parameters that this query will pass to its curious client when * perform is called. * - * @param {Object} params + * @param {!Object} params * An object of parameters to set--see * {@link module:curious.CuriousClient#performQuery} for a full description of the * parameters. @@ -548,7 +548,7 @@ * Set the existing objects that this query will use to link the returned * objects into. * - * @param {Object[]} objs The existing objects to set + * @param {Array} objs The existing objects to set * * @return {CuriousQuery} The query object with its existing object set * updated @@ -572,7 +572,7 @@ * or any other client that has an asynchronous POST request * methods. * - * @param {CuriousClient} curiousClient + * @param {!CuriousClient} curiousClient * A CuriousClient object that will handle performing the actual query * * @return {Promise} @@ -620,13 +620,14 @@ /** * Base (default) class for an object returned from a Curious query * - * @class CuriousObjects.defaultType - * @memberof module:curious.CuriousObjects + * @class + * @static + * @alias module:curious.CuriousObjects.defaultType * * @param {Object} objectData * A plain JavaScript object representing the query data, as parsed from * the returned JSON - * @param {boolean?} camelCase + * @param {boolean=} camelCase * If true, construct camel-cased versions the fields in objectData */ function CuriousObject(objectData, camelCase) { @@ -655,8 +656,6 @@ /** * Serialize CuriousObject instances to JSON effectively if they are passed to JSON.stringify * - * @memberof module:curious.CuriousObjects.defaultType - * * @return {Object} A plain JavaScript object containing the CuriousObject's data */ CuriousObject.prototype.toJSON = function () { @@ -675,9 +674,7 @@ * When parsing a JSON string into objects, instantiate any objects that look like * CuriousObject instances as such, instead of plain JavaScript objects. * - * @memberof module:curious.CuriousObjects.defaultType * @static - * * @param {string} jsonString A string of JSON-encoded data * * @return {*} The instantiated JSON-encoded data, with CuriousObjects placed where @@ -706,22 +703,22 @@ * @private * @memberof module:curious.CuriousObjects * - * @param {Object} queryData + * @param {Object} queryData * A plain JavaScript object representing the query data, as parsed from * the returned JSON--this format is not meant to be easy to use, but * takes less space. - * @param {string[]} queryData.fields + * @param {Array} queryData.fields * The fields every object has - * @param {Array[]} queryData.objects + * @param {Array} queryData.objects * An array of arrays, where each array is the values of a single object's * properties, in the order specified by queryData.fields - * @param {string[]} queryData.urls + * @param {Array} queryData.urls * The url of every object, if they have one * @param {string} model * The name of the Django model these objects come from - * @param {function?} customConstructor + * @param {?function(Object)} customConstructor * A constructor to use instead of the default CuriousObject constructor - * @param {boolean?} camelCase + * @param {boolean=} camelCase * If true, construct camel-cased versions of the JSON objects returned * by the Curious server. * @@ -785,9 +782,9 @@ * * @memberof module:curious.CuriousObjects * - * @param {string[]} relationships + * @param {Array} relationships * The names of the relationships objects will have to one another - * @param {function[]} customConstructors + * @param {Array} customConstructors * The custom constructors for curious object classes * @param {Object} queryJSONResponse * An object of fields holding the query response, as returned and parsed @@ -796,27 +793,27 @@ * The query timestamp * @param {string} queryJSONResponse.last_model * The model name of the last set of objects returned - * @param {Object[]} queryJSONResponse.results + * @param {Array} queryJSONResponse.results * An array of objects containing Django object ids and other * meta-information about the query; one element per model * @param {string} queryJSONResponse.results[].model * The model name for this part of the query * @param {number} queryJSONResponse.results[].join_index * The index of the model this model joins to - * @param {Array[]} queryJSONResponse.results[].objects + * @param {Array} queryJSONResponse.results[].objects * The IDs of the objects returned by the query - * @param {Object[]} queryJSONResponse.data + * @param {Array} queryJSONResponse.data * An array of objects containing the other fields of the Django objects, more than just the * IDs--see {@link module:curious.CuriousObjects~_parseObjects} for a description of this data * in the queryData parameter. - * @param {Array>} existingObjects + * @param {Array>} existingObjects * The existing objects--each object in the array is a mapping of an id * to its corresponding object. - * @param {boolean?} camelCase + * @param {boolean=} camelCase * If true, construct camel-cased versions of the JSON objects returned * by the Curious server. * - * @return {{objects: Object[], trees: Object[]}} + * @return {{objects: Array, trees: Array}} * The parsed objects--trees holds any hierarchical * relationships, for recursive queries. */ @@ -951,7 +948,7 @@ * * @memberof module:curious.CuriousObjects * - * @param {Object[]} arrayOfObjects The array to turn into an object + * @param {Array} arrayOfObjects The array to turn into an object * * @return {Array} The values of the object, whatever it holds */ @@ -975,10 +972,10 @@ * * @memberof module:curious.CuriousObjects * - * @param {Object[]} objects + * @param {Array} objects * An array of objects with the id property * - * @return {number[]} + * @return {Array} * The set of IDs, with duplicates removed, in the same order as the * input object list */ @@ -1009,7 +1006,7 @@ * * @memberof module:curious.CuriousObjects * - * @param {Object[]} arrayOfObjects The array to turn into an object + * @param {Array} arrayOfObjects The array to turn into an object * * @return {string} A comma-separated string containing the objects' IDs, * with duplicates removed, in the same order as the input @@ -1122,11 +1119,11 @@ * Rearrange the results from an array of arrays of objects to an object, * where each array of objects is named by its appropriate relationship name. * - * @param {string[]} relationships The relationship names + * @param {Array} relationships The relationship names * @param {Array>} objects The objects from each relationship - * @param {boolean?} camelCase Whether or not to camel-case the relationship names + * @param {boolean=} camelCase Whether or not to camel-case the relationship names * - * @return {Object} The rearranged results + * @return {Object} The rearranged results */ function _convertResultsToOutput(relationships, objects, camelCase) { var output = {}; @@ -1156,8 +1153,8 @@ * Get the final args to send to the Curious server, after filtering down * through all of the defaults. * - * @param {Object} queryArgs Query-specific args - * @param {Object} clientDefaultArgs Client-specific args + * @param {?Object} queryArgs Query-specific args + * @param {?Object} clientDefaultArgs Client-specific args * * @return {Object} The args, with all defaults filled in hierarchially */ @@ -1215,7 +1212,7 @@ * @class * @alias module:curious.CuriousClient * - * @param {string} curiousURL + * @param {!string} curiousURL * The URL of the Curious server * @param {function (string, Object): Promise} request *

A function that makes a POST request and returns a Promise @@ -1227,13 +1224,13 @@ * server's response will work. Note that axios.post and $http.post wrap the * response in an object, and so require wrapper functions to be used. * See {@link module:curious.CuriousClient.wrappers} for the wrappers.

- * @param {Object} clientDefaultArgs + * @param {Object=} clientDefaultArgs * Default parameters to send to the serever with every query performed by * this client--see {@link module:curious.CuriousClient#performQuery} for an * explanation of what each parameter means. - * @param {boolean?} quiet + * @param {boolean=} quiet * Unless true, log every query to the console. - * @param {boolean?} camelCase + * @param {boolean=} camelCase * If true, construct camel-cased versions of the JSON objects returned * by the Curious server. * @@ -1249,9 +1246,9 @@ * * @param {string} q * The query string - * @param {string[]} relationships + * @param {Array} relationships * The names of relationships between each joined set of objects - * @param {Array} constructors + * @param {Array} constructors * An array of constructors for any custom classes, or null for the * default * @param {Object} params @@ -1334,10 +1331,10 @@ * @param {string} defaultModuleName * The default module object name to use if one is not provided--should * be bound to a string when actually used as a wrapper. - * @param {Object?} moduleObjectOrFunction + * @param {?Object} moduleObjectOrFunction * Either the module to use, like axios/$http, or the posting function * itself, like axios.post. - * @param {Object?} options + * @param {?Object} options * Additional options to send to the requesting function * * @return {function(string, Object): Promise} @@ -1410,10 +1407,10 @@ * @function * @memberof module:curious.CuriousClient.wrappers * - * @param {Object?} axiosModuleOrFunction + * @param {?Object} axiosModuleOrFunction * Either the axios module itself, or axios.post--defaults to using * whatever axios resolves to - * @param {Object?} options + * @param {?Object} options * Additional options to send to the requesting function * * @return {function(string, Object): Promise} @@ -1434,10 +1431,10 @@ * @function * @memberof module:curious.CuriousClient.wrappers * - * @param {Object?} angularHttpServiceOrPostFunction + * @param {?Object} angularHttpServiceOrPostFunction * Either the Angular $http service object, or $http.post--defaults * to using whatever $http resolves to. - * @param {Object?} options + * @param {?Object} options * Additional options to send to the requesting function * * @return {function(string, Object): Promise} @@ -1458,7 +1455,7 @@ * * @param {PolymerElement} ironAjaxElement * The iron-ajax element being used to make the request - * @param {Object?} options + * @param {?Object} options * Additional options to send to the requesting function * * @return {function(string, Object): Promise} From 317028d670b2478925396fbf792fd25c97847b71 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 15:44:52 -0400 Subject: [PATCH 33/77] Bump version to 2.2.2 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 7873123..8e4bf52 100644 --- a/bower.json +++ b/bower.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.2.1", + "version": "2.2.2", "license": "MIT", "ignore": [ "doc", diff --git a/package.json b/package.json index f46dbdb..749d24c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "homepage": "http://myw.github.io" } ], - "version": "2.2.1", + "version": "2.2.2", "license": "MIT", "ignore": [ "bower.json", From ded22c7f264fd711101b409b7cba389b85373b00 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 18:37:15 -0400 Subject: [PATCH 34/77] Fix bower.json and package.json files Also add some recommended fields --- bower.json | 14 ++++++++++---- package.json | 18 ++++++++++++++---- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/bower.json b/bower.json index 8e4bf52..f5cfc21 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,13 @@ { "name": "curious-js", - "repository": "https://github.com/benjiec/curious-js", + "repository": "https://github.com/ginkgobioworks/curious-js", + "description": "JavaScript consumer code for Curious APIs.", + "keywords": [ + "curious", + "graph-db", + "query", + "client" + ], "authors": [ { "name": "Benjie Chen", @@ -12,6 +19,7 @@ "homepage": "http://myw.github.io" } ], + "homepage": "http://ginkgobioworks.github.io/curious-js", "version": "2.2.2", "license": "MIT", "ignore": [ @@ -21,9 +29,7 @@ "package.json", "**/.*" ], - "main": [ - "./curious.js" - ], + "main": "./curious.js", "dependencies": { }, "devDependencies": { diff --git a/package.json b/package.json index 749d24c..dde23a5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,13 @@ { "name": "curious-js", - "repository": "https://github.com/benjiec/curious-js", + "repository": "https://github.com/ginkgobioworks/curious-js", + "description": "JavaScript consumer code for Curious APIs.", + "keywords": [ + "curious", + "graph-db", + "query", + "client" + ], "author": { "name": "Benjie Chen", "email": "benjie@ginkgobioworks.com" @@ -12,15 +19,15 @@ "homepage": "http://myw.github.io" } ], + "homepage": "http://ginkgobioworks.github.io/curious-js", + "bugs": "https://github.com/ginkgobioworks/curious-js/issues", "version": "2.2.2", "license": "MIT", "ignore": [ "bower.json", "**/.*" ], - "main": [ - "./curious.js" - ], + "main": "./curious.js", "scripts": { "make_doc": "./scripts/make_doc", "prepublish": "./scripts/make_doc", @@ -35,5 +42,8 @@ "chai": "~2.3.0", "jsdoc": "~3.3.0", "mocha": "~2.2.5" + }, + "engines": { + "node": ">=0.10.0" } } From 34448ea2525c1712be3ce0fbc1c67bfab5f08d42 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 20 Sep 2016 18:38:07 -0400 Subject: [PATCH 35/77] Bump version to 2.2.3 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index f5cfc21..9161786 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,7 @@ } ], "homepage": "http://ginkgobioworks.github.io/curious-js", - "version": "2.2.2", + "version": "2.2.3", "license": "MIT", "ignore": [ "doc", diff --git a/package.json b/package.json index dde23a5..d669f09 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.2.2", + "version": "2.2.3", "license": "MIT", "ignore": [ "bower.json", From a5e7f1f77d491c07ff50c3f132eb692c62f04ed2 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 21 Sep 2016 19:11:14 -0400 Subject: [PATCH 36/77] Add toString method for CuriousQuery objects So that displaying and serializing them into strings can work easily --- curious.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/curious.js b/curious.js index 8349bc9..a2d4beb 100644 --- a/curious.js +++ b/curious.js @@ -279,6 +279,13 @@ return query; }; + /** + * Generate the constructed query string represented by this object. + * + * @return {string} The fully constructed query + */ + CuriousQuery.prototype.toString = CuriousQuery.prototype.query; + /** * Extend this query object with another query object: return a new query * chain with the current query chain's terms followed From 6c09585fcb2278cdcb235892e77c2aee9a0b816c Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 21 Sep 2016 19:11:51 -0400 Subject: [PATCH 37/77] Update minor documentation improvements --- curious.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/curious.js b/curious.js index a2d4beb..d8ad6ea 100644 --- a/curious.js +++ b/curious.js @@ -195,11 +195,11 @@ * @class * @alias module:curious.CuriousQuery * - * @param {string} [initialTermString] + * @param {string=} initialTermString * The string for the starting term - * @param {string} [initialRelationship] + * @param {string=} initialRelationship * The starting term's relationship - * @param {function} [initialObjectClass] + * @param {function(Object)=} initialObjectClass * A custom object class constructor for the starting term * * @return {CuriousQuery} The newly constructed object @@ -575,9 +575,7 @@ }; /** - * Perform the query using a passed-in Curious client, such as jQuery's ajax, - * or any other client that has an asynchronous POST request - * methods. + * Perform the query using a passed-in CuriousClient object. * * @param {!CuriousClient} curiousClient * A CuriousClient object that will handle performing the actual query From 7dcaf83977bd70a98e650edc756f812d610dbb1a Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 22 Sep 2016 13:51:31 -0400 Subject: [PATCH 38/77] Add valueOf and toJSON methods to CuriousQuery --- curious.js | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/curious.js b/curious.js index d8ad6ea..5039012 100644 --- a/curious.js +++ b/curious.js @@ -280,12 +280,48 @@ }; /** - * Generate the constructed query string represented by this object. + * Convert this object to a string, returning the complete query string * * @return {string} The fully constructed query */ CuriousQuery.prototype.toString = CuriousQuery.prototype.query; + /** + * Convert this probject to its native value equivalent, returning the complete query string + * + * @return {string} The fully constructed query + */ + CuriousQuery.prototype.valueOf = CuriousQuery.prototype.query; + + /** + * Convert this probject to a plain JavaScript object to allow it to be serialized. + * + * @return {string} The fully constructed query + */ + CuriousQuery.prototype.toJSON = function () { + return { + terms: this.terms, + relationships: this.relationships, + params: this.params, + + // Object Factories can't be serialized directly: they're functions. + objectFactories: this.objectFactories.map(function (factory) { + return factory ? factory.toString() : null; + }), + + // Existing objects are turned into plain objects if possible, or left alone otherwise. + existingObjects: this.existingObjects.map(function (objectArray) { + return objectArray.map(function (existingObject) { + return ( + existingObject.toJSON + ? existingObject.toJSON() + : existingObject + ); + }); + }), + }; + }; + /** * Extend this query object with another query object: return a new query * chain with the current query chain's terms followed From 99a7da513cce787eb64dcb70c31fc9a41acfb08e Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 22 Sep 2016 17:28:46 -0400 Subject: [PATCH 39/77] Provide detailed documentation on the nature of data returned by the client --- README.md | 185 +++++++++++++++++++++++++++++++++++++++++++++++++---- curious.js | 89 ++++++++++++++++++++------ 2 files changed, 245 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index c164067..a13f30f 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,185 @@ for you. `CuriousClient` provides some convenience wrappers, but you can make any asynchronous transport layer work. ```javascript -var curiousClient = new CuriousClient(CURIOUS_URL, CuriousClient.wrappers.axios(axios), ...); +var curiousClient = new curious.CuriousClient(CURIOUS_URL, curious.CuriousClient.wrappers.axios(axios), ...); ``` Then, construct a `CuriousQuery` and perform it on the server using the client. Attach any callbacks to the Promise object returned by the `perform` method. The -results of the query will be passed to the callback: +results of the query will be passed to the callback. +Here's a trivial example. The results, of course, depend on the schema on the curious server. + +```javascript +// A simple node.js use case; works similarly on the front end, minus module loading + +const util = require('util'); +const axios = require('axios'); +const curious = require('curious'); + +// Define a client to connect to a curious server +const client = new curious.CuriousClient( + 'http://your-curious.com', + curious.CuriousClient.wrappers.axios(axios), + null, // serverArgs + true, // quiet + true // camelCase +); + +// The schema here is a simple 1:many relationship + +// Make a query +const q = new curious.CuriousQuery('Document(id=12345)', 'documents') + .follow('Document.section_set', 'sections'); + +// Perform that query with the client +q.perform(client).then((data) => { + + // In the callback, examine the resulting data + try { + console.log('data:'); + console.log(data); + + console.log('data.objects:'); + console.log(data.objects); + + console.log('data.objects.documents:'); + console.log(data.objects.documents); + + console.log('data.objects.documents[0].sections:'); + console.log(data.objects.documents[0].sections); + + console.log('data.objects.documents[0].sections[0].documents[0]:'); + console.log(data.objects.documents[0].sections[0].documents[0]); + + console.log('data.objects.sections:'); + console.log(data.objects.sections); + + console.log('data.objects.sections[0].documents:'); + console.log(data.objects.sections[0].documents); + + console.log('data.objects.sections[0].documents[0].sections[0]:'); + console.log(data.objects.sections[0].documents[0].sections[0]); + } catch (e) { + console.error(e); + } +}, console.error); +``` + +The output from this example would look something like this, depending on the schema: ```javascript -var q = new curious.CuriousQuery('Experiment(id=302)', 'experiment') - .follow('Experiment.reaction_set', 'reactions') - .follow('Reaction.dataset_set', 'dataset', Dataset) - .follow('Dataset.attachment_set'); - -q.perform(curiousClient).then(function (queriedData) { - // Do stuff with queriedData -}); +data: +{ objects: { documents: [ [Object] ], sections: [ [Object], [Object] ] }, + trees: [ null, null ] } + +data.objects: +{ documents: + [ CuriousObject { + __url: 'http://your-curious.com/document/12345', + __model: 'Document', + id: 12345, + ... + sections: [Object] } ], + sections: + [ CuriousObject { + __url: null, + __model: 'Section', + id: 12205, + documentId: 12345, + ... + documents: [Object] }, + CuriousObject { + __url: null, + __model: 'Section', + id: 112403, + documentId: 12345, + ... + documents: [Object] } ] } + +data.objects.documents: +[ CuriousObject { + __url: 'http://your-curious.com/document/12345', + __model: 'Document', + id: 12345, + ... + sections: [ [Object], [Object] ] } ] + +data.objects.documents[0].sections: +[ CuriousObject { + __url: null, + __model: 'Section', + id: 12205, + documentId: 12345, + ... + documents: [ [Object] ] }, + CuriousObject { + __url: null, + __model: 'Section', + id: 112403, + documentId: 12345, + ... + documents: [ [Object] ] } ] + +data.objects.documents[0].sections[0].documents[0]: +CuriousObject { + __url: 'http://your-curious.com/document/12345', + __model: 'Document', + id: 12345, + ... + sections: + [ CuriousObject { + __url: null, + __model: 'Section', + id: 12205, + documentId: 12345, + ... + documents: [Object] }, + CuriousObject { + __url: null, + __model: 'Section', + id: 112403, + documentId: 12345, + ... + documents: [Object] } ] } + +data.objects.sections: +[ CuriousObject { + __url: null, + __model: 'Section', + id: 12205, + documentId: 12345, + ... + documents: [ [Object] ] }, + CuriousObject { + __url: null, + __model: 'Section', + id: 112403, + documentId: 12345, + ... + documents: [ [Object] ] } ] + +data.objects.sections[0].documents: +[ CuriousObject { + __url: 'http://your-curious.com/document/12345', + __model: 'Document', + id: 12345, + ... + sections: [ [Object], [Object] ] } ] + +data.objects.sections[0].documents[0].sections[0]: +CuriousObject { + __url: null, + __model: 'Section', + id: 12205, + documentId: 12345, + ... + documents: + [ CuriousObject { + __url: 'http://your-curious.com/document/12345', + __model: 'Document', + id: 12345, + ... + sections: [Object] } ] } ``` -The detailed API is explained in the documentation. +The API is explained in detail in the documentation. diff --git a/curious.js b/curious.js index 5039012..501311b 100644 --- a/curious.js +++ b/curious.js @@ -205,23 +205,23 @@ * @return {CuriousQuery} The newly constructed object * * @example - * // Explicitly set start, wrapWith classes - * var q = (new curious.CuriousQuery()) - * .start('Experiment(id=302)', 'experiment') - * .follow('Experiment.reaction_set', 'reactions') - * .follow('Reaction.dataset_set', 'dataset').wrapWith(Dataset) - * .follow('Dataset.attachment_set'); + * // Explicitly set start, wrapWith classes + * var q = (new curious.CuriousQuery()) + * .start('Experiment(id=302)', 'experiment') + * .follow('Experiment.reaction_set', 'reactions') + * .follow('Reaction.dataset_set', 'dataset').wrapWith(Dataset) + * .follow('Dataset.attachment_set'); * - * q.query() == - * 'Experiment(id=302), Experiment.reaction_set, ' - * + 'Reaction.dataset_set, Dataset.attachment_set' + * q.query() == + * 'Experiment(id=302), Experiment.reaction_set, ' + * + 'Reaction.dataset_set, Dataset.attachment_set' * * @example - * // Terser version of the same query above - * var q = new curious.CuriousQuery('Experiment(id=302)', 'experiment') - * .follow('Experiment.reaction_set', 'reactions') - * .follow('Reaction.dataset_set', 'dataset', Dataset) - * .follow('Dataset.attachment_set'); + * // Terser version of the same query above + * var q = new curious.CuriousQuery('Experiment(id=302)', 'experiment') + * .follow('Experiment.reaction_set', 'reactions') + * .follow('Reaction.dataset_set', 'dataset', Dataset) + * .follow('Dataset.attachment_set'); */ var CuriousQuery = function ( initialTermString, initialRelationship, initialObjectClass @@ -734,7 +734,6 @@ }); }; - /** * When a Curious query is performed, the returned data comes in a set of 3 * arrays to save space: objects, fields, urls. Assemble that data into a @@ -1283,6 +1282,60 @@ /** * Perform a Curious query and return back parsed objects. * + * @example + * // Here's a many-to-many example + * client.performQuery( + * 'Document(id__in=[1,2]) ?(Document.entities)', + * ['documents', 'entities'], + * ).then(function (results) { + * + * console.log(results.objects.documents); + * // Will show an array of documents, as CuriousObject instances: + * // [ + * // CuriousObject({ + * // __model: 'Document', + * // __url: 'http://somewhere/document/1', + * // id: 1, + * // entities: [ + * // // entities associated with document 1 + * // ] + * // ... other fields of Document objects ... + * // }), + * // CuriousObject({ + * // __model: 'Document', + * // __url: 'http://somewhere/document/2', + * // id: 2, + * // entities: [ + * // // entities associated with document 2 + * // ] + * // ... + * // }), + * // ] + * + * console.log(results.objects.entities); + * // Will show an array of entities, as CuriousObject instances: + * // [ + * // CuriousObject({ + * // __model: 'Entity', + * // __url: 'http://somewhere/entity/1', + * // id: 2348, + * // documents: [ + * // // documents associated with entity 1 + * // ] + * // ... other fields of Entity objects ... + * // }), + * // CuriousObject({ + * // __model: 'Entity', + * // __url: 'http://somewhere/entity/2', + * // id: 2725, + * // documents: [ + * // // documents associated with entity 2 + * // ] + * // ... + * // }), + * // ] + * }); + * * @memberof module:curious.CuriousClient * * @param {string} q @@ -1319,9 +1372,9 @@ * this query * * @return {Promise<{objects: Array, trees: Array}>} - * A promise that resolves to an object containing the parsed objects - * from the query and a tree structure that relates IDs for recursive - * queries + * A promise that resolves to an object containing the objects requested by the query + * and a tree structure that relates IDs for recursive queries + * */ performQuery: function (q, relationships, constructors, params, existingObjects) { var args; From ed32c51caceee0861bd297d1ff2f7582c6f6bcae Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 22 Sep 2016 19:51:40 -0400 Subject: [PATCH 40/77] Add .then and .catch methods to CuriousQuery objects --- curious.js | 65 +++++++++++++++++++++++++++++- tests/curious_client.js | 27 ++++++------- tests/curious_query.js | 87 ++++++++++++++++++++++++++++++++++++++++- tests/server.js | 36 +++++++++++++++++ 4 files changed, 197 insertions(+), 18 deletions(-) create mode 100644 tests/server.js diff --git a/curious.js b/curious.js index 501311b..1c48885 100644 --- a/curious.js +++ b/curious.js @@ -232,6 +232,9 @@ this.params = null; this.existingObjects = null; // array of object arrays + // then-style callback pairs to attach to the end of the promise when the query is performed + this.thens = []; + if (initialTermString && initialRelationship) { this.start(initialTermString, initialRelationship, initialObjectClass); } @@ -621,11 +624,71 @@ * */ CuriousQuery.prototype.perform = function (curiousClient) { + var promise; var q = this.query(); - return curiousClient.performQuery( + promise = curiousClient.performQuery( q, this.relationships, this.objectFactories, this.params, this.existingObjects ); + + // Attach any thenable resolve/reject promise callback pairs to the promise that + // results from query execution + this.thens.forEach(function (thenPair) { + // thenPair looks like [resolved, rejected] + if (thenPair[0]) { + // Just like promise = promise.then(resolved, rejected); + promise = promise.then.apply(promise, thenPair); + } else if (thenPair.length > 1 && thenPair[1]) { + // If the first callback is null but the second one isn't, we're looking at a catch + // situation. We use the same data structure to store both situations, so that they're + // attached to the proise in the same order they were attached to the Query object + promise = promise.catch(thenPair[1]); + } + }); + + return promise; + }; + + /** + * Add a (pair of) callback(s) to be called when the promise to perform the query resolves. + * + * This can be useful for constructing a query object with known post-processing before + * actually executing it. + * + * @param {function} fulfilled + * A function to call when the promise is fulfilled (just like you would pass to + * Promise.prototype.then) + * + * @param {function=} rejected + * A function to call when the promise is rejected (just like you would pass as the + * second argument to Promise.prototype.then) + * + * @return {CuriousQuery} + * The query itself, to allow chaining thens, or any other methods + */ + CuriousQuery.prototype.then = function (fulfilled, rejected) { + this.thens.push([fulfilled, rejected]); + + return this; + }; + + /** + * Add a callback to be called if the promise to perform the query is rejected. + * + * This can be useful for constructing a query object with known error-handling before + * actually executing it. + * + * @param {function} rejected + * A function to call when the promise is rejected (just like you would pass to + * Promise.prototype.catch) + * + * @return {CuriousQuery} + * The query itself, to allow chaining thens catches, or any other methods + */ + CuriousQuery.prototype.catch = function (rejected) { + this.thens.push([null, rejected]); + + return this; }; /** diff --git a/tests/curious_client.js b/tests/curious_client.js index 69d6353..bb7e546 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -1,31 +1,26 @@ -/* global describe it before */ +/* global describe it before after */ // mocha.js tests for functions dealing with Curious objects (function () { 'use strict'; - var http = require('http'); var axios = require('axios'); var expect = require('chai').expect; var curious = require('../curious.js'); var examples = require('./examples.js'); + var server = require('./server.js'); // TESTS - var PORT = 8080; - var CURIOUS_URL = 'http://localhost:' + PORT; describe('CuriousClient', function () { - before(function () { - return ( - http - .createServer(function (request, response) { - response.writeHead(200, { - 'Content-Type': 'application/json', - }); - response.end(JSON.stringify(examples.response())); - }) - .listen(PORT) - ); + var srv; + + before(function (done) { + srv = server.start(done); + }); + + after(function (done) { + srv.close(done); }); describe('#performQuery', function () { @@ -47,7 +42,7 @@ [true, false].forEach(function (camelCase) { requestFunctions.forEach(function (requestFunction) { var client = new curious.CuriousClient( - CURIOUS_URL, + server.url, requestFunction, null, true, diff --git a/tests/curious_query.js b/tests/curious_query.js index 7664853..de789af 100644 --- a/tests/curious_query.js +++ b/tests/curious_query.js @@ -1,4 +1,4 @@ -/* global describe it before beforeEach */ +/* global describe it before beforeEach after */ // mocha.js tests for the functions dealing with Curious queries (function () { @@ -6,6 +6,8 @@ var expect = require('chai').expect; var curious = require('../curious.js'); + var axios = require('axios'); + var server = require('./server.js'); describe('CuriousQuery', function () { describe('#query', function () { @@ -516,5 +518,88 @@ expect(q.perform(curiousClient)).to.equal(curiousClient.performQuery()); }); }); + + describe('#then', function () { + var validClient; + var invalidClient; + var srv; + + before(function (done) { + validClient = new curious.CuriousClient( + server.url, + curious.CuriousClient.wrappers.axios(axios), + null, + true, + true + ); + + invalidClient = new curious.CuriousClient( + 'INVALID URL', + curious.CuriousClient.wrappers.axios(axios), + null, + true, + true + ); + + srv = server.start(done); + return srv; + }); + + after(function (done) { + srv.close(done); + }); + + it('should attach callbacks on fulfilled promises', function (done) { + try { + var q = new curious.CuriousQuery().then(function (response) { + expect(response).to.be.ok; + done(); + }, done); + q.perform(validClient); + } catch (error) { + done(error); + } + }); + + it('should attach callbacks on rejected promises', function (done) { + try { + var q = new curious.CuriousQuery().then(function () { + throw new Error('Incorrectly called fulfilled handler'); + }, function (error) { + expect(error).to.be.ok; + done(); + }); + q.perform(invalidClient); + } catch (error) { + done(error); + } + }); + }); + + describe('#catch', function () { + var invalidClient; + + before(function () { + invalidClient = new curious.CuriousClient( + 'INVALID URL', + curious.CuriousClient.wrappers.axios(axios), + null, + true, + true + ); + }); + + it('should attach callbacks on rejected promises', function (done) { + try { + var q = new curious.CuriousQuery().catch(function (error) { + expect(error).to.be.ok; + done(); + }); + q.perform(invalidClient); + } catch (error) { + done(error); + } + }); + }); }); }()); diff --git a/tests/server.js b/tests/server.js new file mode 100644 index 0000000..084d1c0 --- /dev/null +++ b/tests/server.js @@ -0,0 +1,36 @@ +/** + * A trivial curious server + * @module examples + */ +(function () { + 'use strict'; + + var http = require('http'); + var examples = require('./examples.js'); + + var PORT = 8080; + var CURIOUS_URL = 'http://localhost:' + PORT; + + function start(cb) { + var server; + + server = http.createServer(function (request, response) { + response.writeHead(200, { + 'Content-Type': 'application/json', + }); + response.end(JSON.stringify(examples.response())); + }); + + server.listen(PORT, function () { return cb(); }); + server.on('error', cb); + + return server; + } + + module.exports = { + url: CURIOUS_URL, + start: start, + }; + + return module.exports; +}()); From b3f46274a6a2a6357490175319c2ab2fd548b5f2 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Thu, 22 Sep 2016 19:54:19 -0400 Subject: [PATCH 41/77] Bump version to 2.3.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 9161786..78d8909 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,7 @@ } ], "homepage": "http://ginkgobioworks.github.io/curious-js", - "version": "2.2.3", + "version": "2.3.0", "license": "MIT", "ignore": [ "doc", diff --git a/package.json b/package.json index d669f09..8153251 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.2.3", + "version": "2.3.0", "license": "MIT", "ignore": [ "bower.json", From 63d065b1a14af30b93f001e0e82346661a8252b7 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 30 Sep 2016 16:10:37 -0400 Subject: [PATCH 42/77] Name anonymous functions and fix types in documentation --- curious.js | 255 ++++++++++++++++++++++++++++------------------------- 1 file changed, 133 insertions(+), 122 deletions(-) diff --git a/curious.js b/curious.js index 1c48885..ffaed0c 100644 --- a/curious.js +++ b/curious.js @@ -1,22 +1,31 @@ +/* global define */ /** * Module for curious client-side query construction and JSON parsing * * @module curious */ -(function () { - 'use strict'; - - var ex; // Alias for exporting to window or CommonJS exports +(function (global, factory) { + // UMD Format for exports. Works with all module systems: AMD/RequireJS, CommonJS, and global + var mod; + + // AMD + if (typeof define === 'function' && define.amd) { + define(['exports'], factory); + } else if (typeof exports !== 'undefined') { + factory(exports); + } else { + mod = { + exports: {}, + }; - // Export either to browser window or CommonJS module - if (typeof window !== 'undefined' && window) { - ex = window; - } else if (typeof exports !== 'undefined' && exports) { - ex = exports; - } else if (typeof self !== 'undefined' && self) { - ex = self; + factory(mod.exports); + global.curious = mod.exports; } +})(this, function _curiousUmdFactory(exports) { + 'use strict'; + // For node.js and CommonJS + Object.defineProperty(exports, '__esModule', { value: true }); // QUERY TERMS @@ -30,17 +39,20 @@ * * @param {string} term The internal term text to use */ - var QueryTerm = function (term) { + function QueryTerm(term) { /** * The term contents - * @readonly - * @public - * @return {string} The term contents - */ - this.term = function () { return term; }; + * + * @method term + * @readonly + * @public + * + * @return {string} The term contents + */ + this.term = function _term() { return term; }; return this; - }; + } /** * Return the term contents as they belong in a query, wrapped with parens and @@ -50,7 +62,7 @@ * * @return {string} The term contents, formatted */ - QueryTerm.prototype.toString = function () { return this.term(); }; + QueryTerm.prototype.toString = function toString() { return this.term(); }; /** * Determine whether or not the terms sticks implicit joins into adjacent @@ -62,7 +74,7 @@ * True if the term implicitly joins with its following term. True by * default */ - QueryTerm.prototype.leftJoin = function () { return false; }; + QueryTerm.prototype.leftJoin = function leftJoin() { return false; }; /** * Determine whether or not the term is a conditional and does not affect @@ -73,7 +85,7 @@ * @return {boolean} * True if term is a conditional */ - QueryTerm.prototype.conditional = function () { return false; }; + QueryTerm.prototype.conditional = function conditional() { return false; }; /** * Make a term that follows the query chain @@ -85,11 +97,11 @@ * * @param {string} term The term contents */ - var QueryTermFollow = function (term) { + function QueryTermFollow(term) { QueryTerm.call(this, term); return this; - }; + } QueryTermFollow.prototype = new QueryTerm(); /** @@ -102,18 +114,16 @@ * * @param {string} term The term contents */ - var QueryTermHaving = function (term) { + function QueryTermHaving(term) { QueryTerm.call(this, term); return this; - }; + } QueryTermHaving.prototype = new QueryTerm(); - QueryTermHaving.prototype.conditional = function () { return true; }; + QueryTermHaving.prototype.conditional = function conditional() { return true; }; - QueryTermHaving.prototype.toString = function () { - return '+(' + this.term() + ')'; - }; + QueryTermHaving.prototype.toString = function toString() { return '+(' + this.term() + ')'; }; /** * Make a term that performs a negative (exclusive) filter. @@ -125,18 +135,16 @@ * * @param {string} term The term contents */ - var QueryTermNotHaving = function (term) { + function QueryTermNotHaving(term) { QueryTerm.call(this, term); return this; - }; + } QueryTermNotHaving.prototype = new QueryTerm(); - QueryTermNotHaving.prototype.conditional = function () { return true; }; + QueryTermNotHaving.prototype.conditional = function conditional() { return true; }; - QueryTermNotHaving.prototype.toString = function () { - return '-(' + this.term() + ')'; - }; + QueryTermNotHaving.prototype.toString = function toString() { return '-(' + this.term() + ')'; }; /** * Make a term that performs an outer join. @@ -148,18 +156,16 @@ * * @param {string} term The term contents */ - var QueryTermWith = function (term) { + function QueryTermWith(term) { QueryTerm.call(this, term); return this; - }; + } QueryTermWith.prototype = new QueryTerm(); - QueryTermWith.prototype.leftJoin = function () { return true; }; + QueryTermWith.prototype.leftJoin = function leftJoin() { return true; }; - QueryTermWith.prototype.toString = function () { - return '?(' + this.term() + ')'; - }; + QueryTermWith.prototype.toString = function toString() { return '?(' + this.term() + ')'; }; // QUERY OBJECT @@ -223,7 +229,7 @@ * .follow('Reaction.dataset_set', 'dataset', Dataset) * .follow('Dataset.attachment_set'); */ - var CuriousQuery = function ( + function CuriousQuery( initialTermString, initialRelationship, initialObjectClass ) { this.terms = []; @@ -240,15 +246,15 @@ } return this; - }; + } /** * Generate the constructed query string represented by this object. * * @return {string} The fully constructed query */ - CuriousQuery.prototype.query = function () { - var query = ''; + CuriousQuery.prototype.query = function query() { + var queryString = ''; var terms = []; // Flatten all terms and arrays of terms into a single array @@ -264,22 +270,22 @@ // those terms are returned if (termIndex > 0) { if (term.conditional()) { - query += ' '; + queryString += ' '; } else if ( !term.conditional() && !terms[termIndex - 1].leftJoin() && !term.leftJoin() ) { - query += ', '; + queryString += ', '; } else { - query += ' '; + queryString += ' '; } } - query += term; + queryString += term; }); - return query; + return queryString; }; /** @@ -287,21 +293,21 @@ * * @return {string} The fully constructed query */ - CuriousQuery.prototype.toString = CuriousQuery.prototype.query; + CuriousQuery.prototype.toString = function toString() { return this.query(); }; /** * Convert this probject to its native value equivalent, returning the complete query string * * @return {string} The fully constructed query */ - CuriousQuery.prototype.valueOf = CuriousQuery.prototype.query; + CuriousQuery.prototype.valueOf = function valueOf() { return this.query(); }; /** * Convert this probject to a plain JavaScript object to allow it to be serialized. * * @return {string} The fully constructed query */ - CuriousQuery.prototype.toJSON = function () { + CuriousQuery.prototype.toJSON = function toJSON() { return { terms: this.terms, relationships: this.relationships, @@ -333,7 +339,7 @@ * @param {CuriousQuery} extensionQueryObject The query object being added * @return {CuriousQuery} The combined query */ - CuriousQuery.prototype.extend = function (extensionQueryObject) { + CuriousQuery.prototype.extend = function extend(extensionQueryObject) { var queryObject = this; extensionQueryObject.terms.forEach(function (term, termIndex) { @@ -354,7 +360,7 @@ * A new CuriousQuery object constaining the same terms, relationships, * constructors */ - CuriousQuery.prototype.clone = function () { + CuriousQuery.prototype.clone = function clone() { var clonedObject; clonedObject = new CuriousQuery(); @@ -375,10 +381,10 @@ * * @private * - * @param {QueryTerm|Array} termObject + * @param {!QueryTerm|Array} termObject * A {@link module:curious~QueryTerm} object to append to the term, or an * array of them - * @param {string} relationship + * @param {!string} relationship * The name of this term in inter-term relationships * @param {?function(Object)=} customConstructor * A custom constructor for the resulting objects, if this part of the @@ -386,7 +392,7 @@ * * @return {CuriousQuery} The query object, with the new term added */ - CuriousQuery.prototype._addTerm = function ( + CuriousQuery.prototype._addTerm = function _addTerm( termObject, relationship, customConstructor ) { // Ensure that objectFactories, relationships, and terms always have the @@ -420,7 +426,7 @@ * * @private * - * @param {QueryTerm|Array} termObject + * @param {!QueryTerm|Array} termObject * A {@link module:curious~QueryTerm} object (or an array of them), to * append to the previous term * @@ -428,7 +434,7 @@ * The query object, with the term object's string representation appended * to the previous term */ - CuriousQuery.prototype._appendToPreviousTerm = function (termObject) { + CuriousQuery.prototype._appendToPreviousTerm = function _appendToPreviousTerm(termObject) { var lastTerm; if (this.terms.length) { @@ -456,9 +462,9 @@ * Add a starting term to this query. Equivalent to passing parameters * directly to the constructor. * - * @param {string} termString + * @param {!string} termString * The contents of the starting term - * @param {string} relationship + * @param {!string} relationship * The name of this term in inter-term relationships * @param {?function(Object)=} customConstructor * A custom constructor for the resulting objects, if this part of the @@ -466,78 +472,80 @@ * * @return {CuriousQuery} The query object, with the term appended */ - CuriousQuery.prototype.start = function (termString, relationship, customConstructor) { + CuriousQuery.prototype.start = function start(termString, relationship, customConstructor) { return this._addTerm(new QueryTermFollow(termString), relationship, customConstructor); }; /** * Add an inner-join term to this query. * - * @param {string} termString + * @param {!string} termString * The contents of the starting term - * @param {string} relationship + * @param {!string} relationship * The name of this term in inter-term relationships * @param {?function(Object)=} customConstructor * A custom constructor function for the resulting objects * * @return {CuriousQuery} The query object, with the term appended */ - CuriousQuery.prototype.follow = function (termString, relationship, customConstructor) { + CuriousQuery.prototype.follow = function follow(termString, relationship, customConstructor) { return this._addTerm(new QueryTermFollow(termString), relationship, customConstructor); }; /** * Add a filter term to this query. * - * @param {string} termString + * @param {!string} termString * The subquery to filter by * * @return {CuriousQuery} The query object, with the term appended */ - CuriousQuery.prototype.having = function (termString) { + CuriousQuery.prototype.having = function having(termString) { return this._appendToPreviousTerm(new QueryTermHaving(termString)); }; /** * Add an exclude filter term to this query. * - * @param {string} termString + * @param {!string} termString * The subquery to filter by * * @return {CuriousQuery} The query object, with the term appended */ - CuriousQuery.prototype.notHaving = function (termString) { + CuriousQuery.prototype.notHaving = function notHaving(termString) { return this._appendToPreviousTerm(new QueryTermNotHaving(termString)); }; /** * Add an outer-join term to this query. * - * @param {string} termString + * @method with + * + * @param {!string} termString * The contents of the starting term - * @param {string} relationship + * @param {!string} relationship * The name of this term in inter-term relationships - * @param {function} [customConstructor] + * @param {?function(Object)=} customConstructor * A custom constructor for the resulting objects, if this part of the * query returns new objects * * @return {CuriousQuery} The query object, with the term appended */ - CuriousQuery.prototype.with = function (termString, relationship, customConstructor) { + CuriousQuery.prototype.with = function _with(termString, relationship, customConstructor) { return this._addTerm(new QueryTermWith(termString), relationship, customConstructor); }; /** * Specify the object constructor to use for the preceding term in the query. * - * @param {function} customConstructor + * @param {?function(Object)=} customConstructor * A constructor to use when instantiating objects from the previous part of * the query * * @return {CuriousQuery} * The query object, with the new constructor data stored internally */ - CuriousQuery.prototype.wrapWith = function (customConstructor) { + CuriousQuery.prototype.wrapWith = function wrapWith(customConstructor) { return this.wrapDynamically(_makeObjectFactory(customConstructor)); }; @@ -547,13 +555,13 @@ * constructors that do not return a value by default, this will only work * with factory functions that explicitly return an object. * - * @param {function (Object): Object} factoryFunction + * @param {function(Object)} factoryFunction * A factory function that returns an object of the desired wrapping class * * @return {CuriousQuery} * The query object, with the new constructor data stored internally */ - CuriousQuery.prototype.wrapDynamically = function (factoryFunction) { + CuriousQuery.prototype.wrapDynamically = function wrapDynamically(factoryFunction) { if (this.objectFactories.length) { this.objectFactories[this.objectFactories.length - 1] = factoryFunction; } else { @@ -575,7 +583,7 @@ * @return {CuriousQuery} * The query object with its curious client parameters updated */ - CuriousQuery.prototype.setParams = function (params) { + CuriousQuery.prototype.setParams = function setParams(params) { var queryObject = this; if (params instanceof Object) { @@ -594,12 +602,12 @@ * Set the existing objects that this query will use to link the returned * objects into. * - * @param {Array} objs The existing objects to set + * @param {!Array} objs The existing objects to set * * @return {CuriousQuery} The query object with its existing object set * updated */ - CuriousQuery.prototype.setExistingObjects = function (objs) { + CuriousQuery.prototype.setExistingObjects = function setExistingObjects(objs) { var queryObject = this; if (objs && objs.forEach) { @@ -623,7 +631,7 @@ * A promise, as returned by {@link module:curious.CuriousClient#performQuery} * */ - CuriousQuery.prototype.perform = function (curiousClient) { + CuriousQuery.prototype.perform = function perform(curiousClient) { var promise; var q = this.query(); @@ -666,7 +674,7 @@ * @return {CuriousQuery} * The query itself, to allow chaining thens, or any other methods */ - CuriousQuery.prototype.then = function (fulfilled, rejected) { + CuriousQuery.prototype.then = function then(fulfilled, rejected) { this.thens.push([fulfilled, rejected]); return this; @@ -678,6 +686,8 @@ * This can be useful for constructing a query object with known error-handling before * actually executing it. * + * @method catch + * * @param {function} rejected * A function to call when the promise is rejected (just like you would pass to * Promise.prototype.catch) @@ -685,7 +695,7 @@ * @return {CuriousQuery} * The query itself, to allow chaining thens catches, or any other methods */ - CuriousQuery.prototype.catch = function (rejected) { + CuriousQuery.prototype.catch = function _catch(rejected) { this.thens.push([null, rejected]); return this; @@ -707,7 +717,7 @@ */ function _makeObjectFactory(customConstructor) { var CustomConstructorClass = customConstructor; - return function () { + return function CustomConstructorClassFactory() { return new CustomConstructorClass(); }; } @@ -720,7 +730,7 @@ * @namespace * @alias module:curious.CuriousObjects */ - var CuriousObjects = (function () { + var CuriousObjects = (function _curiousObjectsModule() { /** * Base (default) class for an object returned from a Curious query * @@ -762,7 +772,7 @@ * * @return {Object} A plain JavaScript object containing the CuriousObject's data */ - CuriousObject.prototype.toJSON = function () { + CuriousObject.prototype.toJSON = function toJSON() { var curiousObject = this; var serializableObject = {}; @@ -784,7 +794,7 @@ * @return {*} The instantiated JSON-encoded data, with CuriousObjects placed where * appropriate */ - CuriousObject.fromJSON = function (jsonString) { + CuriousObject.fromJSON = function fromJSON(jsonString) { return JSON.parse(jsonString, function (key, value) { var parsedValue = value; @@ -1256,8 +1266,8 @@ * Get the final args to send to the Curious server, after filtering down * through all of the defaults. * - * @param {?Object} queryArgs Query-specific args - * @param {?Object} clientDefaultArgs Client-specific args + * @param {?Object=} queryArgs Query-specific args + * @param {?Object=} clientDefaultArgs Client-specific args * * @return {Object} The args, with all defaults filled in hierarchially */ @@ -1337,10 +1347,10 @@ * If true, construct camel-cased versions of the JSON objects returned * by the Curious server. * - * @return {{performQuery: function}} + * @return {CuriousClient} * A client object with a single performQuery method */ - var CuriousClient = function (curiousURL, request, clientDefaultArgs, quiet, camelCase) { + function CuriousClient(curiousURL, request, clientDefaultArgs, quiet, camelCase) { return { /** * Perform a Curious query and return back parsed objects. @@ -1401,45 +1411,45 @@ * * @memberof module:curious.CuriousClient * - * @param {string} q + * @param {!string} q * The query string - * @param {Array} relationships + * @param {!Array} relationships * The names of relationships between each joined set of objects - * @param {Array} constructors + * @param {?Array} constructors * An array of constructors for any custom classes, or null for the * default - * @param {Object} params + * @param {?Object=} params * Query-specific parameters for the request - * @param {boolean} [params.x] + * @param {boolean=} params.x * Whether or not to ignore excludes; defaults to false - * @param {boolean} [params.c] + * @param {boolean=} params.c * Whether or not to just do a check of the query syntax; defaults to * false - * @param {boolean} [params.d] + * @param {boolean=} params.d * Whether or not return the object data, or just return ids; always * forced to be true for the JavaScript client - * @param {boolean} [params.fk] + * @param {boolean=} params.fk * Whether or not follow foreign keys: if false, foregin keys will be * IDs, as expecte. If true, foreign keys will be 4-member arrays * that include the ID, name, and URL of the object being pointed to. * Defaults to false. - * @param {boolean} [params.r] + * @param {boolean=} params.r * If true, force a refresh of the data not from cache; defaults to * false. - * @param {boolean} [params.fc] + * @param {boolean=} params.fc * If true, force using the cached data; defaults to false. - * @param {string} [params.app] + * @param {string=} params.app * If provided, the name of the app, to use for cache key construction. - * @param {Array>} existingObjects + * @param {Array>=} existingObjects * Objects that already exist to be linked into the results returned by * this query * - * @return {Promise<{objects: Array, trees: Array}>} + * @return {Promise<{objects: Array, trees: Array}>} * A promise that resolves to an object containing the objects requested by the query * and a tree structure that relates IDs for recursive queries * */ - performQuery: function (q, relationships, constructors, params, existingObjects) { + performQuery: function performQuery(q, relationships, constructors, params, existingObjects) { var args; var groupedExistingObjects; @@ -1473,14 +1483,15 @@ }); }, }; - }; + } /** * Common code of convenience functions that make it easier to interact - * with a variety of http clients + * with a variety of http clients: used to set a default module name if one + * is not provided for the wrapper. * * @example - * var axiosWrapper = _dataUnwrapper.bind(this, 'axios'); + * var axiosWrapper = _unwrapResponseData.bind(this, 'axios'); * * @private * @memberof module:curious.CuriousClient.wrappers @@ -1488,10 +1499,10 @@ * @param {string} defaultModuleName * The default module object name to use if one is not provided--should * be bound to a string when actually used as a wrapper. - * @param {?Object} moduleObjectOrFunction + * @param {?Object=} moduleObjectOrFunction * Either the module to use, like axios/$http, or the posting function * itself, like axios.post. - * @param {?Object} options + * @param {?Object=} options * Additional options to send to the requesting function * * @return {function(string, Object): Promise} @@ -1530,7 +1541,7 @@ // axios/angular return the server's response nested within an object // (response.data); here we return a tiny filter function to pull that // server response out - return function (url, args) { + return function _postRequestWrapper(url, args) { return postRequestFunction(url, args, options || {}) .then(function (response) { return response.data; @@ -1575,7 +1586,7 @@ * takes the url and arguments, makes an axios post request, and returns * a promise that resolves directly to the returned query response (unwrapped). */ - CuriousClient.wrappers.axios = _unwrapResponseData.bind(ex, 'axios'); + CuriousClient.wrappers.axios = _unwrapResponseData.bind(this, 'axios'); /** * Convenience function to make it easier to interact with AngularJS $http.post @@ -1599,7 +1610,7 @@ * takes the url and arguments, makes a POST request and returns * a promise that resolves directly to the returned query response (unwrapped) */ - CuriousClient.wrappers.angular = _unwrapResponseData.bind(ex, '$http'); + CuriousClient.wrappers.angular = _unwrapResponseData.bind(this, '$http'); /** * Convenience function to make it easier to interact with Polymer's @@ -1610,18 +1621,18 @@ * * @memberof module:curious.CuriousClient.wrappers * - * @param {PolymerElement} ironAjaxElement + * @param {!PolymerElement} ironAjaxElement * The iron-ajax element being used to make the request * @param {?Object} options * Additional options to send to the requesting function * - * @return {function(string, Object): Promise} + * @return {function(string, Object=): Promise} * A function that meets the requirements to make requests in the curious client: * takes the url and arguments, makes a POST request with * ironAjaxElement, and returns a promise that resolves * directly to the returned query response (unwrapped) */ - CuriousClient.wrappers.ironAjax = function (ironAjaxElement, options) { + CuriousClient.wrappers.ironAjax = function ironAjax(ironAjaxElement, options) { return function (url, args) { var oldAutoValue; var request; @@ -1655,10 +1666,10 @@ }; }; - ex.CuriousObjects = CuriousObjects; - ex.CuriousClient = CuriousClient; - ex.CuriousQuery = CuriousQuery; + exports.CuriousObjects = CuriousObjects; + exports.CuriousClient = CuriousClient; + exports.CuriousQuery = CuriousQuery; - return ex; -})(); + return exports; +}); // vim: sw=2 ts=2 sts=2 et From fc41cd117a0794430a5802f7e9a6877f2dd69e5e Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 30 Sep 2016 17:07:20 -0400 Subject: [PATCH 43/77] Add Docker setup --- .dockerignore | 6 ++++++ Dockerfile | 20 ++++++++++++++++++++ bower.json | 4 +++- docker-compose.yml | 10 ++++++++++ package.json | 4 +++- scripts/run_tests | 7 +++++-- 6 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..b6ec12b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +*/.git* +**/*.zip +.tmp +doc +node_modules +bower_components diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ae0ac21 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM node:latest + +MAINTAINER Misha Wolfson + +# Last update, to invalidate cache if needed +ENV LAST_UPDATE 2016-09-30 + +ENV CURIOUS_JS_DIR /usr/src/curious-js +WORKDIR $CURIOUS_JS_DIR + +# Set Chrome script to run in non-sandbox mode +COPY package.json . +RUN npm install + +COPY curious.js . +COPY scripts ./scripts +COPY tests ./tests + +ENTRYPOINT ["npm", "run"] +CMD ["test"] diff --git a/bower.json b/bower.json index 78d8909..536cc71 100644 --- a/bower.json +++ b/bower.json @@ -27,7 +27,9 @@ "scripts", "tests", "package.json", - "**/.*" + "**/.*", + "Dockerfile", + "docker-compose.yml" ], "main": "./curious.js", "dependencies": { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..5d53ab7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +version: '2' +services: + curious-js: + build: + context: . + dockerfile: Dockerfile + volumes: + - ./curious.js:/usr/src/curious-js/curious.js + - ./scripts:/usr/src/curious-js/scripts + - ./tests:/usr/src/curious-js/tests diff --git a/package.json b/package.json index 8153251..b4c79f5 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,9 @@ "license": "MIT", "ignore": [ "bower.json", - "**/.*" + "**/.*", + "Dockerfile", + "docker-compose.yml" ], "main": "./curious.js", "scripts": { diff --git a/scripts/run_tests b/scripts/run_tests index c1e74f5..b64faaf 100755 --- a/scripts/run_tests +++ b/scripts/run_tests @@ -1,6 +1,9 @@ -#!/bin/bash -e -x +#!/bin/bash -# run_tests - run the entire project's test suite +set -e +set -x + +# run_tests: run the entire project's test suite cd "`dirname $0`/.." From 7ca9ded2ae41c487fbf8defb18c73ef567e87fa7 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 30 Sep 2016 17:07:30 -0400 Subject: [PATCH 44/77] Update README --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index a13f30f..404b353 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,56 @@ JavaScript consumer code for Curious APIs. ## Usage -There are two main parts to using the Curious from Javascript: `CuriousClient`, +### Installation + +`curious-js` is available both from [bower](https://bower.io/search/?q=curious-js) and from [npm](https://www.npmjs.com/package/curious-js). + +### Importing + +`curious-js` has been written using the [UMD](https://github.com/umdjs/umd) module pattern. This +pattern should be compatible with AMD/require.js, CommonJS/node.js, direct loading in the browser, +and Babel module variants. Requiring or loading the `curious.js` file will create a module (with the +default name `curious`) in whatever system you are using. + +### Using + +There are two main parts to using Curious from Javascript: `CuriousClient`, and `CuriousQuery`. -First, create an instance of `CuriousClient` that points to your curious -server. You are responsible for picking and using a request method that works -for you. `CuriousClient` provides some convenience wrappers, but you can make -any asynchronous transport layer work. +First, create an instance of `CuriousClient` that points to your Curious server. You are responsible +for picking and using a request method that works for you. `CuriousClient` provides some convenience +wrappers, but you can make any asynchronous transport layer work by passing a custom function as the +second parameter to the client constructor. Note that if you're using jQuery, `jQuery.post` does not +require any wrapping and can be passed as the second parameter directly. ```javascript +// example using Axios var curiousClient = new curious.CuriousClient(CURIOUS_URL, curious.CuriousClient.wrappers.axios(axios), ...); + +// example using a custom request method +var curiousClient = new curious.CuriousClient(CURIOUS_URL, function (url, data) { + return new Promise(function (resolve, reject) { + var result; + var error; + + // perform some asynchronous access + + if (error) { + reject(error); + } else { + resolve(result); + } + }); +}, ...); ``` -Then, construct a `CuriousQuery` and perform it on the server using the client. -Attach any callbacks to the Promise object returned by the `perform` method. The -results of the query will be passed to the callback. +Then, construct a `CuriousQuery` and perform it on the server using the client. Attach any callbacks +to the Promise object returned by the `perform` method, or directly to the query object. They will +be executed in the order they were attached, before any callbacks attached later. The results of the +query will be passed to the callback. -Here's a trivial example. The results, of course, depend on the schema on the curious server. +Here's a trivial example. The results, of course, depend on the schema on the Curious server; the +example uses a made up schema consiting of Document and Section entities in a 1:many relationship. ```javascript // A simple node.js use case; works similarly on the front end, minus module loading @@ -75,10 +108,12 @@ q.perform(client).then((data) => { } catch (e) { console.error(e); } + + return data; }, console.error); ``` -The output from this example would look something like this, depending on the schema: +The output from the example code above would look something like this, depending on the data: ```javascript data: { objects: { documents: [ [Object] ], sections: [ [Object], [Object] ] }, @@ -195,3 +230,13 @@ CuriousObject { ``` The API is explained in detail in the documentation. + +## Development + +This project provides a basic Dockerfile that builds a Docker container capable of running unit +tests. To run the tests, bring up the container with `docker-compose up`. + +### Test framework + +The tests are written in [mocha](https://mochajs.org/), with [chai](http://chaijs.com/) expect-style +assertions. From e1fe95fb8dd2c6b0a70bea3e4ed179c3c5fa2527 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 30 Sep 2016 17:14:51 -0400 Subject: [PATCH 45/77] Bump version to 2.3.1 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 536cc71..6e033f7 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,7 @@ } ], "homepage": "http://ginkgobioworks.github.io/curious-js", - "version": "2.3.0", + "version": "2.3.1", "license": "MIT", "ignore": [ "doc", diff --git a/package.json b/package.json index b4c79f5..9e91e51 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.3.0", + "version": "2.3.1", "license": "MIT", "ignore": [ "bower.json", From 2b8cb5a6e2f3c4c3bfc01a394758e195936a0e00 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 29 Nov 2016 18:59:16 -0500 Subject: [PATCH 46/77] Rename CURIOUS_URL to URL, since this is just an arbitrary server --- tests/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/server.js b/tests/server.js index 084d1c0..2a18543 100644 --- a/tests/server.js +++ b/tests/server.js @@ -9,7 +9,7 @@ var examples = require('./examples.js'); var PORT = 8080; - var CURIOUS_URL = 'http://localhost:' + PORT; + var URL = 'http://localhost:' + PORT; function start(cb) { var server; @@ -28,7 +28,7 @@ } module.exports = { - url: CURIOUS_URL, + url: URL, start: start, }; From b0dd6b49857a1838437d9ac64a67ce45ba3d7c80 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 29 Nov 2016 18:59:44 -0500 Subject: [PATCH 47/77] Fix typo --- curious.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curious.js b/curious.js index ffaed0c..57c63da 100644 --- a/curious.js +++ b/curious.js @@ -649,7 +649,7 @@ } else if (thenPair.length > 1 && thenPair[1]) { // If the first callback is null but the second one isn't, we're looking at a catch // situation. We use the same data structure to store both situations, so that they're - // attached to the proise in the same order they were attached to the Query object + // attached to the promise in the same order they were attached to the Query object promise = promise.catch(thenPair[1]); } }); From 490063cd8dcc10f0d16f8a6105a115fe17909e9f Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 29 Nov 2016 19:01:17 -0500 Subject: [PATCH 48/77] Add eslint to tests --- package.json | 9 ++++++--- tests/eslint.js | 12 ++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) create mode 100644 tests/eslint.js diff --git a/package.json b/package.json index 9e91e51..94a1bdf 100644 --- a/package.json +++ b/package.json @@ -37,13 +37,16 @@ "repl": "./scripts/repl", "test": "./scripts/run_tests" }, - "dependencies": { - }, + "dependencies": {}, "devDependencies": { "axios": "~0.5.4", + "babel-eslint": "^7.1.1", "chai": "~2.3.0", + "eslint-config-airbnb-es5": "^1.1.0", + "eslint-plugin-react": "^6.7.1", "jsdoc": "~3.3.0", - "mocha": "~2.2.5" + "mocha": "~2.2.5", + "mocha-eslint": "^3.0.1" }, "engines": { "node": ">=0.10.0" diff --git a/tests/eslint.js b/tests/eslint.js new file mode 100644 index 0000000..10c2386 --- /dev/null +++ b/tests/eslint.js @@ -0,0 +1,12 @@ +/* global describe it before beforeEach after */ + +// mocha.js tests for the functions dealing with Curious queries +(function () { + 'use strict'; + var lint = require('mocha-eslint'); + + lint([ + 'curious.js', + 'tests', + ]); +}()); From 5bab89a5c391f6d2c0b9471f60a18a8ba459e44f Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 17:24:01 -0500 Subject: [PATCH 49/77] Change make_doc script to be more compatible --- scripts/make_doc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/make_doc b/scripts/make_doc index 13a07e5..4ef032b 100755 --- a/scripts/make_doc +++ b/scripts/make_doc @@ -1,4 +1,6 @@ -#!/bin/bash -e -x +#!/bin/bash +set -e +set -x # make_doc - generate JSDoc for the project From c6652a0f04c1c48d63c94051e0c11a438577df13 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 17:24:41 -0500 Subject: [PATCH 50/77] Add lint script --- package.json | 1 + scripts/lint | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100755 scripts/lint diff --git a/package.json b/package.json index 94a1bdf..9e7a535 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ ], "main": "./curious.js", "scripts": { + "lint": "./scripts/lint", "make_doc": "./scripts/make_doc", "prepublish": "./scripts/make_doc", "release_doc": "./scripts/release_doc", diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..1802c7e --- /dev/null +++ b/scripts/lint @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +# lint - run ESLint on the code + +cd "`dirname $0`/.." + +./node_modules/.bin/eslint "$@" curious.js tests && echo OK + +cd - 1>/dev/null From d98e047238246b2e590f4756b6fd4f79ebd5e272 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 17:42:08 -0500 Subject: [PATCH 51/77] Add query URL resolution function _getQueryUrl() now gets the query url ('foo/q') from the base URL, which is now the one users are expected to pass in --- curious.js | 44 ++++++++++++++++++++++++++++++----- tests/curious_client.js | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/curious.js b/curious.js index 57c63da..e003b9e 100644 --- a/curious.js +++ b/curious.js @@ -4,13 +4,13 @@ * * @module curious */ -(function (global, factory) { +(function _umd(global, factory) { // UMD Format for exports. Works with all module systems: AMD/RequireJS, CommonJS, and global var mod; // AMD if (typeof define === 'function' && define.amd) { - define(['exports'], factory); + define('curious', ['exports'], factory); } else if (typeof exports !== 'undefined') { factory(exports); } else { @@ -1319,14 +1319,43 @@ }); } + /** + * Determine the query endpoint URL from the base URL: add in the query endpoint '/q/' if the URL + * does not already include it, and make sure the URL ends in a '/'. + * + * @param {string} url The base URL, maybe also including the query endpoint + * + * @return {string} The fully formed query URL, ending in a '/'. + */ + function _getQueryUrl(url) { + var queryUrl = url; + + // Ensure that we end with a '/' + if (!queryUrl.endsWith('/')) { + queryUrl += '/'; + } + + // Ensure that if the last component was not '/q/', it is now; + if (!queryUrl.endsWith('/q/')) { + queryUrl += 'q/'; + } + + return queryUrl; + } + + /** * Tool for making a curious query and returning parsed objects * * @class * @alias module:curious.CuriousClient * - * @param {!string} curiousURL - * The URL of the Curious server + * @param {!string} curiousUrl + *

The URL of the Curious server. The query is sent to curiousUrl + '/q/'.

+ * + *

XXX: For compatability with legacy clients, the /q/ is not added if + * curiousUrl has a 'q' as its last path element. However, new code should not + * rely on this behavior.

* @param {function (string, Object): Promise} request *

A function that makes a POST request and returns a Promise * (a thenable)--examples are jQuery.post, @@ -1350,8 +1379,11 @@ * @return {CuriousClient} * A client object with a single performQuery method */ - function CuriousClient(curiousURL, request, clientDefaultArgs, quiet, camelCase) { + function CuriousClient(curiousUrl, request, clientDefaultArgs, quiet, camelCase) { return { + /** The URL to query */ + queryUrl: _getQueryUrl(curiousUrl), + /** * Perform a Curious query and return back parsed objects. * @@ -1466,7 +1498,7 @@ args = _getArgs(params, clientDefaultArgs); args.q = q.replace('\n', ' '); - return request(curiousURL, args) + return request(this.queryUrl, args) .then(function (response) { var parsedResult = CuriousObjects.parse( relationships, diff --git a/tests/curious_client.js b/tests/curious_client.js index bb7e546..2b16c88 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -23,6 +23,57 @@ srv.close(done); }); + describe('#queryUrl', function () { + function _queryUrl(baseUrl) { return new curious.CuriousClient(baseUrl).queryUrl; } + + it('should add "/q/" to url paths', function () { + expect(_queryUrl('foobar.com/curious')).to.equal('foobar.com/curious/q/'); + expect(_queryUrl('foobar.com/curious/')).to.equal('foobar.com/curious/q/'); + }); + + it('should add "/q/" to server urls', function () { + expect(_queryUrl('curious.foobar.com')).to.equal('curious.foobar.com/q/'); + expect(_queryUrl('curious.foobar.com/')).to.equal('curious.foobar.com/q/'); + }); + + it('should add "/q/" to local urls', function () { + expect(_queryUrl('curious/')).to.equal('curious/q/'); + expect(_queryUrl('curious')).to.equal('curious/q/'); + expect(_queryUrl('/curious/')).to.equal('/curious/q/'); + expect(_queryUrl('/curious')).to.equal('/curious/q/'); + }); + + it('should add "/q/" to empty urls', function () { + expect(_queryUrl('')).to.equal('/q/'); + expect(_queryUrl('/')).to.equal('/q/'); + expect(_queryUrl('//')).to.equal('//q/'); + }); + + it('should not add extra "/q/"s to url paths', function () { + expect(_queryUrl('foobar.com/curious/q')).to.equal('foobar.com/curious/q/'); + expect(_queryUrl('foobar.com/curious/q/')).to.equal('foobar.com/curious/q/'); + }); + + it('should not add extra "/q/"s to server urls', function () { + expect(_queryUrl('curious.foobar.com/q')).to.equal('curious.foobar.com/q/'); + expect(_queryUrl('curious.foobar.com/q/')).to.equal('curious.foobar.com/q/'); + }); + + it('should not add extra "/q/"s to local urls', function () { + expect(_queryUrl('curious/')).to.equal('curious/q/'); + expect(_queryUrl('curious')).to.equal('curious/q/'); + expect(_queryUrl('/curious/')).to.equal('/curious/q/'); + expect(_queryUrl('/curious')).to.equal('/curious/q/'); + }); + + it('should not add extra "/q/" to "q" urls', function () { + expect(_queryUrl('/q')).to.equal('/q/'); + expect(_queryUrl('/q/')).to.equal('/q/'); + expect(_queryUrl('//q')).to.equal('//q/'); + expect(_queryUrl('//q/')).to.equal('//q/'); + }); + }); + describe('#performQuery', function () { it('should work with axios', function (done) { var requestFunctions; From 775736aecac22b7b3c1c44274271f0b03ff48690 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 17:44:01 -0500 Subject: [PATCH 52/77] Include .eslintrc.yaml in the Docker image So that ESLint tests pass --- Dockerfile | 4 +++- docker-compose.yml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index ae0ac21..f3cd5e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,7 +8,6 @@ ENV LAST_UPDATE 2016-09-30 ENV CURIOUS_JS_DIR /usr/src/curious-js WORKDIR $CURIOUS_JS_DIR -# Set Chrome script to run in non-sandbox mode COPY package.json . RUN npm install @@ -16,5 +15,8 @@ COPY curious.js . COPY scripts ./scripts COPY tests ./tests +# Development files +COPY .eslintrc.yaml . + ENTRYPOINT ["npm", "run"] CMD ["test"] diff --git a/docker-compose.yml b/docker-compose.yml index 5d53ab7..f491732 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,7 @@ services: context: . dockerfile: Dockerfile volumes: + - ./.eslintrc.yaml:/usr/src/curious-js/.eslintrc.yaml - ./curious.js:/usr/src/curious-js/curious.js - ./scripts:/usr/src/curious-js/scripts - ./tests:/usr/src/curious-js/tests From d73bc6888d032a74ca52f20194c04ce4be7e8562 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 17:57:43 -0500 Subject: [PATCH 53/77] Update README to reflect changes --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 404b353..5a09bb5 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,33 @@ default name `curious`) in whatever system you are using. There are two main parts to using Curious from Javascript: `CuriousClient`, and `CuriousQuery`. -First, create an instance of `CuriousClient` that points to your Curious server. You are responsible -for picking and using a request method that works for you. `CuriousClient` provides some convenience -wrappers, but you can make any asynchronous transport layer work by passing a custom function as the -second parameter to the client constructor. Note that if you're using jQuery, `jQuery.post` does not -require any wrapping and can be passed as the second parameter directly. +First, create an instance of `CuriousClient` that points to your Curious server, providing a +server URL and a request function. + +_The server URL provided to the client should point to the Curious root endpoint, **not** to the +query endpoint (`/q/`)on the server_. The code will not break if you make this mistake, but the +behavior is deprecated. + +You must also provide a request method. You are responsible for picking and using a request +method/transport layer that works for you. `CuriousClient` provides convenience wrappers for +common request methods, but you can make any asynchronous transport layer work by passing a custom +function as the second parameter to the client constructor. The function must take the URL as its +first parameter and an object payload as its second parameter, make a `POST` request to the curious +server, and return a Promise (or any thenable) that resolves to the JSON data returned by the +server. Note that if you're using jQuery, `jQuery.post` does not require any wrapping and can be +passed as the second parameter directly. ```javascript -// example using Axios +// Example using Axios var curiousClient = new curious.CuriousClient(CURIOUS_URL, curious.CuriousClient.wrappers.axios(axios), ...); -// example using a custom request method +// Example using a custom request method var curiousClient = new curious.CuriousClient(CURIOUS_URL, function (url, data) { return new Promise(function (resolve, reject) { var result; var error; - // perform some asynchronous access + // Perform some asynchronous access if (error) { reject(error); @@ -50,7 +60,7 @@ var curiousClient = new curious.CuriousClient(CURIOUS_URL, function (url, data) Then, construct a `CuriousQuery` and perform it on the server using the client. Attach any callbacks to the Promise object returned by the `perform` method, or directly to the query object. They will be executed in the order they were attached, before any callbacks attached later. The results of the -query will be passed to the callback. +query will be passed to the first callback. Here's a trivial example. The results, of course, depend on the schema on the Curious server; the example uses a made up schema consiting of Document and Section entities in a 1:many relationship. @@ -229,14 +239,37 @@ CuriousObject { sections: [Object] } ] } ``` -The API is explained in detail in the documentation. +The API is explained in detail in the documentation. ## Development -This project provides a basic Dockerfile that builds a Docker container capable of running unit -tests. To run the tests, bring up the container with `docker-compose up`. +Development is carried out through an included Docker environment. + +### Docker + +The project provides a Dockerfile that builds a container capable of running the unit tests and +scripts. To run the tests, bring up the container with `docker-compose up`. Any of the scripts shown +to be run below from a shell with `npm run` can be executed in an instance of the container with +`docker-compose run --rm [script name]`. + +### REPL + +A script that opens up a node.js REPL and loads curious.js as `curious` is provided with +`npm run repl`. ### Test framework The tests are written in [mocha](https://mochajs.org/), with [chai](http://chaijs.com/) expect-style -assertions. +assertions. Tests can be run with `npm test`. + +Coding conventions and linting are enforced at the unit test level, but can also be run +independently with `npm run lint`. + +### Documentation + +Any new code added to `curious.js` must be documented in a manner consistent with existing +documentation. + +JSDoc documentation can be generated into the `doc/` subdirectory from the source code and README +with `npm run make_doc`. It can be updated on the project website automatically with `npm run +release_doc`. From e1edef83b3b2698e397b8b38be77590a03314612 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 23:03:00 -0500 Subject: [PATCH 54/77] Add Travis CI support --- .travis.yml | 7 +++++++ README.md | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..4edd519 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,7 @@ +language: node_js +node_js: + - node + +cache: + directories: + - node_modules diff --git a/README.md b/README.md index 5a09bb5..eb96104 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ JavaScript consumer code for Curious APIs. +[![Build Status](https://travis-ci.org/ginkgobioworks/curious-js.svg?branch=master)](https://travis-ci.org/ginkgobioworks/curious-js) + ## Usage ### Installation From cbcb0b09f3334f1ab80f9075bfd008c0ba8634de Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 23:28:39 -0500 Subject: [PATCH 55/77] Update build badge in README --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index eb96104..1915e18 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ # curious-js -JavaScript consumer code for Curious APIs. - -[![Build Status](https://travis-ci.org/ginkgobioworks/curious-js.svg?branch=master)](https://travis-ci.org/ginkgobioworks/curious-js) +JavaScript consumer code for Curious APIs. [![Build Status](https://travis-ci.org/ginkgobioworks/curious-js.svg?branch=v2)](https://travis-ci.org/ginkgobioworks/curious-js) ## Usage From df158caf92709e5ad2e2b34a71267fc971933981 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 30 Nov 2016 23:37:21 -0500 Subject: [PATCH 56/77] Bump version to 2.4.0 --- bower.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bower.json b/bower.json index 6e033f7..8161301 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,7 @@ } ], "homepage": "http://ginkgobioworks.github.io/curious-js", - "version": "2.3.1", + "version": "2.4.0", "license": "MIT", "ignore": [ "doc", diff --git a/package.json b/package.json index 9e7a535..c6937d8 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.3.1", + "version": "2.4.0", "license": "MIT", "ignore": [ "bower.json", From 20c413ffbc4ba5a549cab4982544d904511ed55d Mon Sep 17 00:00:00 2001 From: Korei Klein Date: Thu, 8 Dec 2016 15:52:34 -0500 Subject: [PATCH 57/77] Bind the post method of axios. --- curious.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curious.js b/curious.js index e003b9e..d8dd596 100644 --- a/curious.js +++ b/curious.js @@ -1568,7 +1568,7 @@ // If the module provided has a `post` method, use that. Otherwise, // just call the "module" itself: this allows passing in either // $http or $http.post, for example - postRequestFunction = mod.post || mod; + postRequestFunction = mod.post.bind(mod) || mod; // axios/angular return the server's response nested within an object // (response.data); here we return a tiny filter function to pull that From 3eae6d82a25169ef6653efc9b47bb9cd221a6973 Mon Sep 17 00:00:00 2001 From: Korei Klein Date: Thu, 8 Dec 2016 16:43:27 -0500 Subject: [PATCH 58/77] Check whether to call bind more carefully. --- curious.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curious.js b/curious.js index d8dd596..d6caeb9 100644 --- a/curious.js +++ b/curious.js @@ -1568,7 +1568,7 @@ // If the module provided has a `post` method, use that. Otherwise, // just call the "module" itself: this allows passing in either // $http or $http.post, for example - postRequestFunction = mod.post.bind(mod) || mod; + postRequestFunction = mod.post ? mod.post.bind(mod) : mod; // axios/angular return the server's response nested within an object // (response.data); here we return a tiny filter function to pull that From a14141fb9dd566384d18091a51915b59266ebb42 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 9 Dec 2016 19:21:32 -0500 Subject: [PATCH 59/77] Add NPM deployment to TravisCI setup --- .travis.yml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 4edd519..e00230d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,15 @@ language: node_js node_js: - - node - +- node cache: - directories: - - node_modules + directories: + - node_modules +deploy: + provider: npm + email: misha@ginkgobioworks.com + api_key: + secure: RaHu9nAQUkPyp6LZ2mEvD5zbdXeHIZrP20iTT4N5kl3kB8BmXQBVphJ4rkcmdRYT13JZIlXzIvqWE8QdK+PmapOIWaGJ4t5XQ0Hjrqm5iUutDzvkypKBl6R44SlhIAp4KwWbEHeDkVZnhHZFirhgJMADbROBvBxTYAORab2zMmWWP1QbtLNjZ0RA1GPfM4KA+XVQsNwKTvlVZDQN8pptVsxfLI+MpDkGiZbq2zs0m5MUzMRVit2yN+HbSKCZRNU46cVFkA0SaDKTShHbydFvFLCgIQrOei+GzLu0PQXyjFLyz+lgkEdRgEp6bjC+ZrpU3vDKMnzo0pucpa8LTfUFxua/OLV6MVBcjB6/BeifMyd9R9yDfZcxlePSwyiiwWdR79bWnQckrN+jf5RaUk03GVmn6oo2zXK8kFZKIQw9rYfBKyisN5I9apqEyOmGQU7Uk6oqcUU8rYAsSp+8FQ9pcPXd2PXj9HkjzakGPWutHlb+5EKRy2pjX/G2EuJUhVzs4f3TPuAEfNnG/mz5TaMU7T1XpaZ0NQ5yTgr0NRNt2lgLV5Zej6sqkE3evMEGwtC1GSkbWO3kaigBVDi3Ys09fOUzRBZebaO63exzwqVKA15D4gAsaNoReh9eelhOix4PYvlnb4huxgy1Jf5SuNSjHblAm8I2UoTAl4MRle940fQ= + on: + tags: true + repo: ginkgobioworks/curious-js + branch: v2 From 744ccf9253f450cb36156eabf05e481ce86bcfdd Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 9 Dec 2016 20:03:11 -0500 Subject: [PATCH 60/77] Remove version field from bower.json, since it is now deprecated Bower only uses git tags for versioning, as described in the [spec](https://github.com/bower/spec/blob/master/json.md#version) --- bower.json | 1 - 1 file changed, 1 deletion(-) diff --git a/bower.json b/bower.json index 8161301..8dde939 100644 --- a/bower.json +++ b/bower.json @@ -20,7 +20,6 @@ } ], "homepage": "http://ginkgobioworks.github.io/curious-js", - "version": "2.4.0", "license": "MIT", "ignore": [ "doc", From f59fd9c02807f33121a3aa1b9f1d4bb02866b365 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 9 Dec 2016 20:04:31 -0500 Subject: [PATCH 61/77] Bump version to 2.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c6937d8..bba666d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.4.0", + "version": "2.4.1", "license": "MIT", "ignore": [ "bower.json", From 97ab94af41715d0dcd01494af0ebee04d395005a Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 9 Dec 2016 20:56:22 -0500 Subject: [PATCH 62/77] Update and fix bower/npm ignore globs --- .npmignore | 8 ++++++++ bower.json | 10 +++++----- package.json | 6 ------ 3 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 .npmignore diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..915d119 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +.* +Dockerfile +bower.json +bower_components +docker-compose.yml +node_modules +scripts +tests diff --git a/bower.json b/bower.json index 8dde939..2bee2be 100644 --- a/bower.json +++ b/bower.json @@ -22,13 +22,13 @@ "homepage": "http://ginkgobioworks.github.io/curious-js", "license": "MIT", "ignore": [ + ".*", + "Dockerfile", "doc", - "scripts", - "tests", + "docker-compose.yml", "package.json", - "**/.*", - "Dockerfile", - "docker-compose.yml" + "scripts", + "tests" ], "main": "./curious.js", "dependencies": { diff --git a/package.json b/package.json index bba666d..ef84021 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,6 @@ "bugs": "https://github.com/ginkgobioworks/curious-js/issues", "version": "2.4.1", "license": "MIT", - "ignore": [ - "bower.json", - "**/.*", - "Dockerfile", - "docker-compose.yml" - ], "main": "./curious.js", "scripts": { "lint": "./scripts/lint", From 1f8ff007d99071f8029b20bead7845ce0f2a01d2 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Fri, 9 Dec 2016 20:57:50 -0500 Subject: [PATCH 63/77] Bump version to 2.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ef84021..0a040c3 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.4.1", + "version": "2.4.2", "license": "MIT", "main": "./curious.js", "scripts": { From 90d06a8c198d30f17bf2952d679ed08a5ea354ea Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 10 Dec 2016 04:58:55 -0500 Subject: [PATCH 64/77] Clean up JSDoc --- curious.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/curious.js b/curious.js index d6caeb9..ebddf3e 100644 --- a/curious.js +++ b/curious.js @@ -1,8 +1,9 @@ /* global define */ /** - * Module for curious client-side query construction and JSON parsing + * curious.js - JavaScript consumer code for Curious APIs. * - * @module curious + * Copyright (c) 2015 Ginkgo BIoworks, Inc. + * @license MIT */ (function _umd(global, factory) { // UMD Format for exports. Works with all module systems: AMD/RequireJS, CommonJS, and global @@ -22,6 +23,12 @@ global.curious = mod.exports; } })(this, function _curiousUmdFactory(exports) { + /** + * Curious JavaScript client and query construction + * + * @module curious + */ + 'use strict'; // For node.js and CommonJS From dacf213346fb7c14f0099a3a356e05ae65d4b682 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 10 Dec 2016 05:00:37 -0500 Subject: [PATCH 65/77] Document CI setup --- README.md | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 1915e18..3a23647 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,15 @@ # curious-js -JavaScript consumer code for Curious APIs. [![Build Status](https://travis-ci.org/ginkgobioworks/curious-js.svg?branch=v2)](https://travis-ci.org/ginkgobioworks/curious-js) +JavaScript consumer code for Curious APIs. + +[![Build Status](https://travis-ci.org/ginkgobioworks/curious-js.svg?branch=v2)](https://travis-ci.org/ginkgobioworks/curious-js) ## Usage ### Installation -`curious-js` is available both from [bower](https://bower.io/search/?q=curious-js) and from [npm](https://www.npmjs.com/package/curious-js). +`curious-js` is available both from [bower](https://bower.io/search/?q=curious-js) and from +[npm](https://www.npmjs.com/package/curious-js). ### Importing @@ -17,14 +20,13 @@ default name `curious`) in whatever system you are using. ### Using -There are two main parts to using Curious from Javascript: `CuriousClient`, -and `CuriousQuery`. +There are two main parts to using Curious from JavaScript: `CuriousClient` and `CuriousQuery`. First, create an instance of `CuriousClient` that points to your Curious server, providing a server URL and a request function. _The server URL provided to the client should point to the Curious root endpoint, **not** to the -query endpoint (`/q/`)on the server_. The code will not break if you make this mistake, but the +query endpoint (`/q/`) on the server_. The code will not break if you make this mistake, but the behavior is deprecated. You must also provide a request method. You are responsible for picking and using a request @@ -243,7 +245,13 @@ The API is explained in detail in the documentation. ## Development -Development is carried out through an included Docker environment. +Development is carried out through an included Docker environment and Travis CI. + +### CI + +Continuous integration is performed with [Travis CI](https://travis-ci.org/ginkgobioworks/curious-js). +Any tagged commits on the main branch (v2), which update bower by default, are also automatically +deployed to NPM. ### Docker @@ -254,7 +262,7 @@ to be run below from a shell with `npm run` can be executed in an instance of th ### REPL -A script that opens up a node.js REPL and loads curious.js as `curious` is provided with +A script that opens up a node.js REPL and loads curious.js as `curious` is available via `npm run repl`. ### Test framework @@ -262,8 +270,8 @@ A script that opens up a node.js REPL and loads curious.js as `curious` is provi The tests are written in [mocha](https://mochajs.org/), with [chai](http://chaijs.com/) expect-style assertions. Tests can be run with `npm test`. -Coding conventions and linting are enforced at the unit test level, but can also be run -independently with `npm run lint`. +Coding conventions and linting are enforced at the unit test level but can also be run independently +with `npm run lint`. ### Documentation From d800f2851e797e2fa78c790be190179cce955098 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 10 Dec 2016 05:06:46 -0500 Subject: [PATCH 66/77] Clean up scripts --- package.json | 10 ++++++---- scripts/lint | 10 ---------- scripts/make_doc | 4 ++-- scripts/release_doc | 5 ++++- scripts/run_tests | 12 ------------ 5 files changed, 12 insertions(+), 29 deletions(-) delete mode 100755 scripts/lint delete mode 100755 scripts/run_tests diff --git a/package.json b/package.json index 0a040c3..303bcec 100644 --- a/package.json +++ b/package.json @@ -25,12 +25,14 @@ "license": "MIT", "main": "./curious.js", "scripts": { - "lint": "./scripts/lint", + "test": "mocha tests", + "prepublish": "npm run make_doc", + "postpublish": "npm run release_doc", + + "lint": "eslint curious.js tests && echo OK", "make_doc": "./scripts/make_doc", - "prepublish": "./scripts/make_doc", "release_doc": "./scripts/release_doc", - "repl": "./scripts/repl", - "test": "./scripts/run_tests" + "repl": "./scripts/repl" }, "dependencies": {}, "devDependencies": { diff --git a/scripts/lint b/scripts/lint deleted file mode 100755 index 1802c7e..0000000 --- a/scripts/lint +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -e - -# lint - run ESLint on the code - -cd "`dirname $0`/.." - -./node_modules/.bin/eslint "$@" curious.js tests && echo OK - -cd - 1>/dev/null diff --git a/scripts/make_doc b/scripts/make_doc index 4ef032b..40c73a6 100755 --- a/scripts/make_doc +++ b/scripts/make_doc @@ -8,6 +8,6 @@ cd "`dirname $0`/.." mkdir -p doc rm -rf doc/* -./node_modules/.bin/jsdoc --destination doc curious.js README.md "$@" +jsdoc --destination doc curious.js README.md "$@" -cd - +cd - 1>/dev/null diff --git a/scripts/release_doc b/scripts/release_doc index 1d2b6d7..fdae6eb 100755 --- a/scripts/release_doc +++ b/scripts/release_doc @@ -1,7 +1,10 @@ -#!/bin/bash -e -x +#!/bin/bash # release_doc - generate JSDoc for the project and push it to GitHub Pages +set -e +set -x + cd "`dirname $0`/.." archivepath=`mktemp` diff --git a/scripts/run_tests b/scripts/run_tests deleted file mode 100755 index b64faaf..0000000 --- a/scripts/run_tests +++ /dev/null @@ -1,12 +0,0 @@ -#!/bin/bash - -set -e -set -x - -# run_tests: run the entire project's test suite - -cd "`dirname $0`/.." - -NODE_PATH=lib:. ./node_modules/.bin/mocha tests - -cd - From c09258e6000f61188712dfb1a34ecd2a5729feb3 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 10 Dec 2016 05:14:21 -0500 Subject: [PATCH 67/77] Improve README --- README.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3a23647..37aa976 100644 --- a/README.md +++ b/README.md @@ -247,15 +247,28 @@ The API is explained in detail in the documentation. Development is carried out through an included Docker environment and Travis CI. -### CI + +### Gotchas + +For historic reasons, since some repos still need to use Curious v 1.0, the `master` branch still +points there, and the current version, the _default_ branch, points to `v2`. Eventually this will be +resolved. + +### CI build and deployment Continuous integration is performed with [Travis CI](https://travis-ci.org/ginkgobioworks/curious-js). -Any tagged commits on the main branch (v2), which update bower by default, are also automatically -deployed to NPM. +Any tagged commits on the main branch (v2), update bower by default and are automatically deployed to +NPM through the CI `deploy` task. + +To deploy the code: + +1. Get on the default branch `git checkout v2` [*not master*] +2. Bump the version: `npm version [type]` +3. Push to the origin with tags: `git push && git push --tags` ### Docker -The project provides a Dockerfile that builds a container capable of running the unit tests and +The project also provides a Dockerfile that builds a container capable of running the unit tests and scripts. To run the tests, bring up the container with `docker-compose up`. Any of the scripts shown to be run below from a shell with `npm run` can be executed in an instance of the container with `docker-compose run --rm [script name]`. From ea814761facedb5f0f3ac25431cc457dc56916fc Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 10 Dec 2016 05:16:50 -0500 Subject: [PATCH 68/77] Add default messge for version bumps --- .npmrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..ff972fe --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +message=Bump version to %s From 8cc3c87d6feb0e3905c35a2e3a2e951bf33dcb9b Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Sat, 10 Dec 2016 05:17:35 -0500 Subject: [PATCH 69/77] Bump version to 2.4.3 --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 303bcec..23587ba 100644 --- a/package.json +++ b/package.json @@ -21,14 +21,13 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.4.2", + "version": "2.4.3", "license": "MIT", "main": "./curious.js", "scripts": { "test": "mocha tests", "prepublish": "npm run make_doc", "postpublish": "npm run release_doc", - "lint": "eslint curious.js tests && echo OK", "make_doc": "./scripts/make_doc", "release_doc": "./scripts/release_doc", From d92b7a5d6a69af520f3f6bb97a7c3bc22b53569d Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 7 Feb 2017 04:09:49 -0500 Subject: [PATCH 70/77] Fix query cleanup Line breaks were not properly being removed: global replace must use a regexp, not a plain string --- curious.js | 2 +- package.json | 1 + tests/curious_client.js | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/curious.js b/curious.js index ebddf3e..d1cae1b 100644 --- a/curious.js +++ b/curious.js @@ -1503,7 +1503,7 @@ } args = _getArgs(params, clientDefaultArgs); - args.q = q.replace('\n', ' '); + args.q = q.replace(/\n+/g, ' '); return request(this.queryUrl, args) .then(function (response) { diff --git a/package.json b/package.json index 23587ba..2699b73 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,7 @@ "axios": "~0.5.4", "babel-eslint": "^7.1.1", "chai": "~2.3.0", + "es6-promise": "^4.0.5", "eslint-config-airbnb-es5": "^1.1.0", "eslint-plugin-react": "^6.7.1", "jsdoc": "~3.3.0", diff --git a/tests/curious_client.js b/tests/curious_client.js index 2b16c88..71abe38 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -9,6 +9,7 @@ var curious = require('../curious.js'); var examples = require('./examples.js'); var server = require('./server.js'); + var Promise = require('es6-promise'); // TESTS @@ -75,6 +76,41 @@ }); describe('#performQuery', function () { + it('should remove all line breaks', function () { + // Encapsulate logic for testing query processing + function testQueryString(original, expected) { + // Fake request function that tests whether or not the arguments are what we expect + function fakeRequest(url, args) { + expect(args.q).to.equal(expected); + + // Must resolve to an object with a 'result' property to prevent errors in parsing + return Promise.resolve({ result: args }); + } + + return new curious.CuriousClient(server.url, fakeRequest, null, true).performQuery(original); + } + + return Promise.all([ + testQueryString('no breaks', 'no breaks'), + testQueryString( + 'break\nin the middle', + 'break in the middle' + ), + testQueryString( + 'multiple\nbreaks\nin the middle', + 'multiple breaks in the middle' + ), + testQueryString( + '\nmultiple\nbreaks\nin the middle and edges\n', + ' multiple breaks in the middle and edges ' + ), + testQueryString( + 'multiple\nbreaks\n\nin a row', + 'multiple breaks in a row' + ), + ]); + }); + it('should work with axios', function (done) { var requestFunctions; // Set the global axios variable to test axios wrapper defaults From c0cd8b016a5e00dc174f652a1fdd32798ccf9afe Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 7 Feb 2017 04:19:56 -0500 Subject: [PATCH 71/77] Bump version to 2.4.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2699b73..89167cf 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.4.3", + "version": "2.4.4", "license": "MIT", "main": "./curious.js", "scripts": { From 90c4209b340e4751f24aa4d9718af128f80b9716 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 7 Feb 2017 05:53:02 -0500 Subject: [PATCH 72/77] Fix query trimming Untrimmed whitespace at the ends is also bad apparently --- curious.js | 2 +- tests/curious_client.js | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/curious.js b/curious.js index d1cae1b..3bc7637 100644 --- a/curious.js +++ b/curious.js @@ -1503,7 +1503,7 @@ } args = _getArgs(params, clientDefaultArgs); - args.q = q.replace(/\n+/g, ' '); + args.q = q.replace(/\n+/g, ' ').trim(); return request(this.queryUrl, args) .then(function (response) { diff --git a/tests/curious_client.js b/tests/curious_client.js index 71abe38..7e584e2 100644 --- a/tests/curious_client.js +++ b/tests/curious_client.js @@ -76,7 +76,7 @@ }); describe('#performQuery', function () { - it('should remove all line breaks', function () { + it('should remove all line breaks and extra whitespace', function () { // Encapsulate logic for testing query processing function testQueryString(original, expected) { // Fake request function that tests whether or not the arguments are what we expect @@ -102,12 +102,24 @@ ), testQueryString( '\nmultiple\nbreaks\nin the middle and edges\n', - ' multiple breaks in the middle and edges ' + 'multiple breaks in the middle and edges' ), testQueryString( 'multiple\nbreaks\n\nin a row', 'multiple breaks in a row' ), + testQueryString( + ' extra space at the start', + 'extra space at the start' + ), + testQueryString( + 'extra space at the end ', + 'extra space at the end' + ), + testQueryString( + ' extra space at the start and end ', + 'extra space at the start and end' + ), ]); }); From da20d22d479b8e0ca655f832dfe28dc2060cfbe8 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Tue, 7 Feb 2017 05:54:12 -0500 Subject: [PATCH 73/77] Bump version to 2.4.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 89167cf..e6f401c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.4.4", + "version": "2.4.5", "license": "MIT", "main": "./curious.js", "scripts": { From bc5256a5478aa81ed2329151667e4bcb5a7c1611 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 5 Apr 2017 23:49:29 -0400 Subject: [PATCH 74/77] Remove AMD label from 'define' --- curious.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/curious.js b/curious.js index 3bc7637..7ab6cc1 100644 --- a/curious.js +++ b/curious.js @@ -2,7 +2,7 @@ /** * curious.js - JavaScript consumer code for Curious APIs. * - * Copyright (c) 2015 Ginkgo BIoworks, Inc. + * Copyright (c) 2015 Ginkgo Bioworks, Inc. * @license MIT */ (function _umd(global, factory) { @@ -11,7 +11,7 @@ // AMD if (typeof define === 'function' && define.amd) { - define('curious', ['exports'], factory); + define(['exports'], factory); } else if (typeof exports !== 'undefined') { factory(exports); } else { From e8203b4702b36ec06273e2b255d5712eda8dc1be Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 5 Apr 2017 23:58:33 -0400 Subject: [PATCH 75/77] Bump version to 2.4.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e6f401c..6fac2dc 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ ], "homepage": "http://ginkgobioworks.github.io/curious-js", "bugs": "https://github.com/ginkgobioworks/curious-js/issues", - "version": "2.4.5", + "version": "2.4.6", "license": "MIT", "main": "./curious.js", "scripts": { From 25bb27e759eacc569af73d0090a95d49df3da8ea Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 25 Oct 2017 00:54:04 -0400 Subject: [PATCH 76/77] Remove 'method' JSDoc tags for with, catch --- curious.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/curious.js b/curious.js index 7ab6cc1..334d345 100644 --- a/curious.js +++ b/curious.js @@ -526,8 +526,6 @@ /** * Add an outer-join term to this query. * - * @method with - * * @param {!string} termString * The contents of the starting term * @param {!string} relationship @@ -693,8 +691,6 @@ * This can be useful for constructing a query object with known error-handling before * actually executing it. * - * @method catch - * * @param {function} rejected * A function to call when the promise is rejected (just like you would pass to * Promise.prototype.catch) From 28da06ebe863a286b7111163d1b8826b829ee0b9 Mon Sep 17 00:00:00 2001 From: Misha Wolfson Date: Wed, 25 Oct 2017 01:09:16 -0400 Subject: [PATCH 77/77] Upgrade dev deps and add package-lock.json --- package-lock.json | 1766 +++++++++++++++++++++++++++++++++++++++++++++ package.json | 16 +- 2 files changed, 1774 insertions(+), 8 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..cde812c --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1766 @@ +{ + "name": "curious-js", + "version": "2.4.6", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-4.0.4.tgz", + "integrity": "sha1-F6jWp6bE71OLgU7Jq6wneSk78wo=", + "dev": true + }, + "acorn-jsx": { + "version": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" + }, + "dependencies": { + "acorn": { + "version": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "https://registry.npmjs.org/ajv/-/ajv-4.11.2.tgz", + "integrity": "sha1-8WbDwRy8bLncwQKlvP5bcslSh+Y=", + "dev": true, + "requires": { + "co": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz" + } + }, + "ajv-keywords": { + "version": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "ansi-escapes": { + "version": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", + "dev": true, + "requires": { + "sprintf-js": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + } + }, + "array-union": { + "version": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "dev": true, + "requires": { + "array-uniq": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz" + } + }, + "array-uniq": { + "version": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "dev": true + }, + "array.prototype.find": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.0.4.tgz", + "integrity": "sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "es-abstract": "1.9.0" + } + }, + "arrify": { + "version": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "assertion-error": { + "version": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "dev": true + }, + "axios": { + "version": "https://registry.npmjs.org/axios/-/axios-0.5.4.tgz", + "integrity": "sha1-4G+FIleDgTPmkJTZJcy0Gd6U/fs=", + "dev": true, + "requires": { + "es6-promise": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz" + }, + "dependencies": { + "es6-promise": { + "version": "https://registry.npmjs.org/es6-promise/-/es6-promise-2.3.0.tgz", + "integrity": "sha1-lu258v2wGZWCKyY92KratnSBgbw=", + "dev": true + } + } + }, + "babel-code-frame": { + "version": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "integrity": "sha1-AnYgvuVnqIwyVhV05/0IAdMxGOQ=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz" + } + }, + "babel-eslint": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-7.2.3.tgz", + "integrity": "sha1-sv4tgBJkcPXBlELcdXJTqJdxCCc=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "babel-traverse": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0" + } + }, + "babel-messages": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", + "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "2.5.1", + "regenerator-runtime": "0.11.0" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "babel-messages": "6.23.0", + "babel-runtime": "6.26.0", + "babel-types": "6.26.0", + "babylon": "6.18.0", + "debug": "2.6.9", + "globals": "9.18.0", + "invariant": "2.2.2", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "js-tokens": "3.0.2" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "6.26.0", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "to-fast-properties": "1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "balanced-match": { + "version": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "dev": true + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==", + "dev": true + }, + "brace-expansion": { + "version": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz", + "integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=", + "dev": true, + "requires": { + "balanced-match": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", + "concat-map": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + }, + "buffer-shims": { + "version": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", + "dev": true + }, + "caller-path": { + "version": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz" + } + }, + "callsites": { + "version": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "catharsis": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.8.9.tgz", + "integrity": "sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=", + "dev": true, + "requires": { + "underscore-contrib": "0.3.0" + } + }, + "chai": { + "version": "https://registry.npmjs.org/chai/-/chai-2.3.0.tgz", + "integrity": "sha1-ii9qNHSNqAEJD9cyh7Kqc5pOkJo=", + "dev": true, + "requires": { + "assertion-error": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", + "deep-eql": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz" + } + }, + "chalk": { + "version": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "has-ansi": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + } + }, + "circular-json": { + "version": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", + "integrity": "sha1-vos2rvzN6LPKeqLWr8B6NyQsDS0=", + "dev": true + }, + "cli-cursor": { + "version": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz" + } + }, + "cli-width": { + "version": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", + "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", + "dev": true + }, + "co": { + "version": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "commander": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz", + "integrity": "sha1-/UMOiJgy7DU7ms0d4hfBHLPu+HM=", + "dev": true + }, + "concat-map": { + "version": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "readable-stream": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", + "typedarray": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + } + }, + "core-js": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.1.tgz", + "integrity": "sha1-rmh03GaTd4m4B1T/VCjfZoGcpQs=", + "dev": true + }, + "core-util-is": { + "version": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "d": { + "version": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=", + "dev": true, + "requires": { + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz" + } + }, + "debug": { + "version": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", + "integrity": "sha1-vFlryr52F/Edn6FTYe3tVgi4SZs=", + "dev": true, + "requires": { + "ms": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz" + } + }, + "deep-eql": { + "version": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz" + } + }, + "deep-is": { + "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", + "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", + "dev": true, + "requires": { + "foreach": "2.0.5", + "object-keys": "1.0.11" + } + }, + "del": { + "version": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", + "dev": true, + "requires": { + "globby": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "is-path-cwd": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "is-path-in-cwd": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "rimraf": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz" + } + }, + "diff": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz", + "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", + "dev": true + }, + "doctrine": { + "version": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" + } + }, + "es-abstract": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.9.0.tgz", + "integrity": "sha512-kk3IJoKo7A3pWJc0OV8yZ/VEX2oSUytfekrJiqoxBlKJMFAJVJVpGdHClCCTdv+Fn2zHfpDHHIelMFhZVfef3Q==", + "dev": true, + "requires": { + "es-to-primitive": "1.1.1", + "function-bind": "1.1.1", + "has": "1.0.1", + "is-callable": "1.1.3", + "is-regex": "1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", + "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", + "dev": true, + "requires": { + "is-callable": "1.1.3", + "is-date-object": "1.0.1", + "is-symbol": "1.0.1" + } + }, + "es5-ext": { + "version": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz", + "integrity": "sha1-qoRkHU23a2Krul5F/YBey6sUAEc=", + "dev": true, + "requires": { + "es6-iterator": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz", + "es6-symbol": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz" + } + }, + "es6-iterator": { + "version": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz", + "integrity": "sha1-vZaFZ9YWNeM8C4BydhPJy0sJa6w=", + "dev": true, + "requires": { + "d": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz", + "es6-symbol": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz" + } + }, + "es6-map": { + "version": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz", + "integrity": "sha1-o0sUe+IkdzpNfagHJ5TO+jYyuJc=", + "dev": true, + "requires": { + "d": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz", + "es6-iterator": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz", + "es6-set": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz", + "es6-symbol": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz", + "event-emitter": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz" + } + }, + "es6-promise": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.1.1.tgz", + "integrity": "sha512-OaU1hHjgJf+b0NzsxCg7NdIYERD6Hy/PEmFLTjw+b65scuisG3Kt4QoTvJ66BBkPZ581gr0kpoVzKnxniM8nng==", + "dev": true + }, + "es6-set": { + "version": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz", + "integrity": "sha1-lRa2dhwpZLkv9HlFYjOiR9xwfOg=", + "dev": true, + "requires": { + "d": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz", + "es6-iterator": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz", + "es6-symbol": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz", + "event-emitter": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz" + } + }, + "es6-symbol": { + "version": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz", + "integrity": "sha1-lEgcZV56fK2C66gy2X1UM0ltf/o=", + "dev": true, + "requires": { + "d": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz" + } + }, + "es6-weak-map": { + "version": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz", + "integrity": "sha1-DSu9iCfrX7S6j5f7/qUNQ9sh6oE=", + "dev": true, + "requires": { + "d": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz", + "es6-iterator": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz", + "es6-symbol": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz" + } + }, + "escape-string-regexp": { + "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz", + "es6-weak-map": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz", + "esrecurse": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz", + "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz" + } + }, + "eslint": { + "version": "https://registry.npmjs.org/eslint/-/eslint-3.15.0.tgz", + "integrity": "sha1-vcxqbF/+CBYOe5PAZmlTYqkeMPI=", + "dev": true, + "requires": { + "babel-code-frame": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.22.0.tgz", + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "concat-stream": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "debug": "https://registry.npmjs.org/debug/-/debug-2.6.0.tgz", + "doctrine": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "escope": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "espree": "https://registry.npmjs.org/espree/-/espree-3.4.0.tgz", + "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "esutils": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "file-entry-cache": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "globals": "https://registry.npmjs.org/globals/-/globals-9.14.0.tgz", + "ignore": "https://registry.npmjs.org/ignore/-/ignore-3.2.2.tgz", + "imurmurhash": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "inquirer": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "is-my-json-valid": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", + "is-resolvable": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "js-yaml": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.1.tgz", + "json-stable-stringify": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "levn": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "natural-compare": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "optionator": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "path-is-inside": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "pluralize": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "progress": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "require-uncached": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "shelljs": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "strip-bom": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "strip-json-comments": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "table": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "text-table": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "user-home": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + } + }, + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, + "eslint-config-airbnb-es5": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-es5/-/eslint-config-airbnb-es5-1.2.0.tgz", + "integrity": "sha512-MaOKwNpqNZIRy+3augFj5vGHJ4F1sskPjJ/Od7K3N8Vv+8pD6t73XCL18KrHrF1m58qFxPBDl1US6bswE65IbQ==", + "dev": true, + "requires": { + "strip-json-comments": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.2.tgz" + } + }, + "eslint-plugin-react": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", + "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", + "dev": true, + "requires": { + "array.prototype.find": "2.0.4", + "doctrine": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "has": "1.0.1", + "jsx-ast-utils": "1.4.1", + "object.assign": "4.0.4" + } + }, + "espree": { + "version": "https://registry.npmjs.org/espree/-/espree-3.4.0.tgz", + "integrity": "sha1-QWVvpWKOBCh4Al70Z+ePEly4bh0=", + "dev": true, + "requires": { + "acorn": "https://registry.npmjs.org/acorn/-/acorn-4.0.4.tgz", + "acorn-jsx": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz" + } + }, + "esrecurse": { + "version": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz", + "integrity": "sha1-RxO2U2rffyrE8yfVWed1a/9kgiA=", + "dev": true, + "requires": { + "estraverse": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + }, + "dependencies": { + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz", + "integrity": "sha1-9srKcokzqFDvkGYdDheYK6RxEaI=", + "dev": true + } + } + }, + "estraverse": { + "version": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "esutils": { + "version": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", + "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", + "dev": true + }, + "event-emitter": { + "version": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz", + "integrity": "sha1-jWPd+0z+H647MsomXExyAiIIC7U=", + "dev": true, + "requires": { + "d": "https://registry.npmjs.org/d/-/d-0.1.1.tgz", + "es5-ext": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz" + } + }, + "exit-hook": { + "version": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "fast-levenshtein": { + "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + } + }, + "file-entry-cache": { + "version": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz" + } + }, + "flat-cache": { + "version": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.2.tgz", + "integrity": "sha1-+oZxTnLCHbiGAXYezy9VXRq8a5Y=", + "dev": true, + "requires": { + "circular-json": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz", + "del": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", + "graceful-fs": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "write": "https://registry.npmjs.org/write/-/write-0.2.1.tgz" + }, + "dependencies": { + "graceful-fs": { + "version": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + } + } + }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, + "fs.realpath": { + "version": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "generate-function": { + "version": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", + "dev": true + }, + "generate-object-property": { + "version": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + } + }, + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "0.3.0" + } + }, + "glob-all": { + "version": "https://registry.npmjs.org/glob-all/-/glob-all-3.1.0.tgz", + "integrity": "sha1-iRPd+17hrHgSZWJBsD1SF8ZLAqs=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "yargs": "https://registry.npmjs.org/yargs/-/yargs-1.2.6.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + } + } + } + }, + "globals": { + "version": "https://registry.npmjs.org/globals/-/globals-9.14.0.tgz", + "integrity": "sha1-iFmTavADh0EmMFOznQ52yiQeQDQ=", + "dev": true + }, + "globby": { + "version": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", + "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", + "dev": true, + "requires": { + "array-union": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "arrify": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "pify": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "pinkie-promise": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + } + } + } + }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, + "growl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz", + "integrity": "sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8=", + "dev": true + }, + "has": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", + "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", + "dev": true, + "requires": { + "function-bind": "1.1.1" + } + }, + "has-ansi": { + "version": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "ignore": { + "version": "https://registry.npmjs.org/ignore/-/ignore-3.2.2.tgz", + "integrity": "sha1-HFHh71O6tt3BXbTZrE7BOezrNBA=", + "dev": true + }, + "imurmurhash": { + "version": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "inherits": { + "version": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true + }, + "inquirer": { + "version": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "cli-cursor": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "cli-width": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", + "figures": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "readline2": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "run-async": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "rx-lite": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "string-width": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "through": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + } + }, + "interpret": { + "version": "https://registry.npmjs.org/interpret/-/interpret-1.0.1.tgz", + "integrity": "sha1-1Xn7f2k7hYAElHrzn6DbSfeVYCw=", + "dev": true + }, + "invariant": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "integrity": "sha1-nh9WrArNtr8wMwbzOL47IErmA2A=", + "dev": true, + "requires": { + "loose-envify": "1.3.1" + } + }, + "is-callable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", + "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", + "dev": true + }, + "is-date-object": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", + "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" + } + }, + "is-my-json-valid": { + "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", + "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=", + "dev": true, + "requires": { + "generate-function": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", + "generate-object-property": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "jsonpointer": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" + } + }, + "is-path-cwd": { + "version": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", + "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", + "dev": true + }, + "is-path-in-cwd": { + "version": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "integrity": "sha1-ZHdYK4IU1gI0YJRWcAO+ip6sBNw=", + "dev": true, + "requires": { + "is-path-inside": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz" + } + }, + "is-path-inside": { + "version": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz", + "integrity": "sha1-/AbloWg/vaE95mev9xe7wQpI838=", + "dev": true, + "requires": { + "path-is-inside": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz" + } + }, + "is-property": { + "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-regex": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", + "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", + "dev": true, + "requires": { + "has": "1.0.1" + } + }, + "is-resolvable": { + "version": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz", + "integrity": "sha1-jfV8YeouPFAUCNEA+wE8+NbgzGI=", + "dev": true, + "requires": { + "tryit": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz" + } + }, + "is-symbol": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", + "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", + "dev": true + }, + "isarray": { + "version": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "jade": { + "version": "0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", + "integrity": "sha1-jxDXl32NefL2/4YqgbBRPMslaGw=", + "dev": true, + "requires": { + "commander": "0.6.1", + "mkdirp": "0.3.0" + }, + "dependencies": { + "commander": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-0.6.1.tgz", + "integrity": "sha1-+mihT2qUXVTbvlDYzbMyDp47GgY=", + "dev": true + }, + "mkdirp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz", + "integrity": "sha1-G79asbqCevI1dRQ0kEJkVfSB/h4=", + "dev": true + } + } + }, + "js-tokens": { + "version": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz", + "integrity": "sha1-COnxMkhKLEWjCQfp3E1VZ7fxFNc=", + "dev": true + }, + "js-yaml": { + "version": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.1.tgz", + "integrity": "sha1-eCulAgC+e55ahTcAG3gE2zrQJig=", + "dev": true, + "requires": { + "argparse": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", + "esprima": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz" + }, + "dependencies": { + "esprima": { + "version": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", + "dev": true + } + } + }, + "js2xmlparser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-3.0.0.tgz", + "integrity": "sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=", + "dev": true, + "requires": { + "xmlcreate": "1.0.2" + } + }, + "jsdoc": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-3.5.5.tgz", + "integrity": "sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==", + "dev": true, + "requires": { + "babylon": "7.0.0-beta.19", + "bluebird": "3.5.1", + "catharsis": "0.8.9", + "escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "js2xmlparser": "3.0.0", + "klaw": "2.0.0", + "marked": "0.3.6", + "mkdirp": "0.5.1", + "requizzle": "0.2.1", + "strip-json-comments": "2.0.1", + "taffydb": "2.6.2", + "underscore": "1.8.3" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.19", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.19.tgz", + "integrity": "sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, + "json-stable-stringify": { + "version": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + } + }, + "jsonify": { + "version": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true + }, + "jsx-ast-utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", + "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", + "dev": true + }, + "klaw": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-2.0.0.tgz", + "integrity": "sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11" + } + }, + "levn": { + "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "type-check": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz" + } + }, + "lodash": { + "version": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", + "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", + "dev": true, + "requires": { + "js-tokens": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.1.tgz" + } + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "marked": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", + "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=", + "dev": true + }, + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2.7.3", + "sigmund": "1.0.1" + } + }, + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", + "integrity": "sha1-HXMHam35hs2TROFecfzAWkyavxI=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "mocha": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-2.5.3.tgz", + "integrity": "sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg=", + "dev": true, + "requires": { + "commander": "2.3.0", + "debug": "2.2.0", + "diff": "1.4.0", + "escape-string-regexp": "1.0.2", + "glob": "3.2.11", + "growl": "1.9.2", + "jade": "0.26.3", + "mkdirp": "0.5.1", + "supports-color": "1.2.0", + "to-iso-string": "0.0.2" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "integrity": "sha1-+HBX6ZWxofauaklgZkE3vFbwOdo=", + "dev": true, + "requires": { + "ms": "0.7.1" + } + }, + "escape-string-regexp": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz", + "integrity": "sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + }, + "ms": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", + "integrity": "sha1-nNE8A62/8ltl7/3nzoZO6VIBcJg=", + "dev": true + }, + "supports-color": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-1.2.0.tgz", + "integrity": "sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4=", + "dev": true + } + } + }, + "mocha-eslint": { + "version": "https://registry.npmjs.org/mocha-eslint/-/mocha-eslint-3.0.1.tgz", + "integrity": "sha1-rnKrxWHLKJ0ubBQ3iO4YKsoaBNs=", + "dev": true, + "requires": { + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "eslint": "https://registry.npmjs.org/eslint/-/eslint-3.15.0.tgz", + "glob-all": "https://registry.npmjs.org/glob-all/-/glob-all-3.1.0.tgz", + "replaceall": "https://registry.npmjs.org/replaceall/-/replaceall-0.1.6.tgz" + } + }, + "ms": { + "version": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz", + "integrity": "sha1-riXPJRKziFodldfwN4aNhDESR2U=", + "dev": true + }, + "mute-stream": { + "version": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "natural-compare": { + "version": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "number-is-nan": { + "version": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-keys": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", + "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", + "dev": true + }, + "object.assign": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.0.4.tgz", + "integrity": "sha1-scnMBE7xuf5jYG/BQau7MuFHMMw=", + "dev": true, + "requires": { + "define-properties": "1.1.2", + "function-bind": "1.1.1", + "object-keys": "1.0.11" + } + }, + "once": { + "version": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + } + }, + "onetime": { + "version": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optionator": { + "version": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "dev": true, + "requires": { + "deep-is": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "fast-levenshtein": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "levn": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "prelude-ls": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "type-check": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "wordwrap": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" + } + }, + "os-homedir": { + "version": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "path-is-absolute": { + "version": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "pify": { + "version": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, + "pinkie": { + "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz" + } + }, + "pluralize": { + "version": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true + }, + "progress": { + "version": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "readable-stream": { + "version": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.2.tgz", + "integrity": "sha1-qeb+w8fdqF+LsbO6cChgRVb8gl4=", + "dev": true, + "requires": { + "buffer-shims": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", + "core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "isarray": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", + "string_decoder": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + } + }, + "readline2": { + "version": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "mute-stream": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz" + } + }, + "rechoir": { + "version": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "https://registry.npmjs.org/resolve/-/resolve-1.2.0.tgz" + } + }, + "regenerator-runtime": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz", + "integrity": "sha512-/aA0kLeRb5N9K0d4fw7ooEbI+xDe+DKD499EQqygGqeS8N3xto15p09uY2xj7ixP81sNPXvRLnAQIqdVStgb1A==", + "dev": true + }, + "replaceall": { + "version": "https://registry.npmjs.org/replaceall/-/replaceall-0.1.6.tgz", + "integrity": "sha1-gdgax663LX9cSUKt8ml6MiBojY4=", + "dev": true + }, + "require-uncached": { + "version": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "resolve-from": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz" + } + }, + "requizzle": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.1.tgz", + "integrity": "sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=", + "dev": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, + "resolve": { + "version": "https://registry.npmjs.org/resolve/-/resolve-1.2.0.tgz", + "integrity": "sha1-lYnD8vYUnRQXpAvswWY9tuxrwmw=", + "dev": true + }, + "resolve-from": { + "version": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "onetime": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz" + } + }, + "rimraf": { + "version": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + } + } + } + }, + "run-async": { + "version": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + } + }, + "rx-lite": { + "version": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "shelljs": { + "version": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.6.tgz", + "integrity": "sha1-N5zM+1a5HIYB5HkzVutTgpJN6a0=", + "dev": true, + "requires": { + "glob": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "interpret": "https://registry.npmjs.org/interpret/-/interpret-1.0.1.tgz", + "rechoir": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz" + }, + "dependencies": { + "glob": { + "version": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz", + "integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=", + "dev": true, + "requires": { + "fs.realpath": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "inflight": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "minimatch": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "once": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "path-is-absolute": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + } + }, + "minimatch": { + "version": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=", + "dev": true, + "requires": { + "brace-expansion": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" + } + } + } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "slice-ansi": { + "version": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "sprintf-js": { + "version": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + } + }, + "string_decoder": { + "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + }, + "strip-ansi": { + "version": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" + } + }, + "strip-bom": { + "version": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.2.tgz", + "integrity": "sha1-WkirlgI9usG3uND/q/b2PxZ3vp8=", + "dev": true + }, + "supports-color": { + "version": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "ajv": "https://registry.npmjs.org/ajv/-/ajv-4.11.2.tgz", + "ajv-keywords": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "chalk": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "slice-ansi": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "string-width": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz", + "integrity": "sha1-Y1xUNsxypuDDh87KJ41OLuxSaH4=", + "dev": true, + "requires": { + "is-fullwidth-code-point": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz" + } + } + } + }, + "taffydb": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.6.2.tgz", + "integrity": "sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=", + "dev": true + }, + "text-table": { + "version": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "to-fast-properties": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", + "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", + "dev": true + }, + "to-iso-string": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/to-iso-string/-/to-iso-string-0.0.2.tgz", + "integrity": "sha1-TcGeZk38y+Jb2NtQiwDG2hWCVdE=", + "dev": true + }, + "tryit": { + "version": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz", + "integrity": "sha1-OTvnMKlEb9Hq1tpZoBQwjzbCics=", + "dev": true + }, + "type-check": { + "version": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + } + }, + "type-detect": { + "version": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + }, + "typedarray": { + "version": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "underscore": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz", + "integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=", + "dev": true + }, + "underscore-contrib": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/underscore-contrib/-/underscore-contrib-0.3.0.tgz", + "integrity": "sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=", + "dev": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=", + "dev": true + } + } + }, + "user-home": { + "version": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz" + } + }, + "util-deprecate": { + "version": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "wordwrap": { + "version": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + }, + "wrappy": { + "version": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write": { + "version": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" + }, + "dependencies": { + "mkdirp": { + "version": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + } + }, + "xmlcreate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-1.0.2.tgz", + "integrity": "sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=", + "dev": true + }, + "xtend": { + "version": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "yargs": { + "version": "https://registry.npmjs.org/yargs/-/yargs-1.2.6.tgz", + "integrity": "sha1-nHtKgv1dWVsr8Xq23MQxNUMv40s=", + "dev": true, + "requires": { + "minimist": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz" + }, + "dependencies": { + "minimist": { + "version": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", + "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=", + "dev": true + } + } + } + } +} diff --git a/package.json b/package.json index 6fac2dc..e650711 100644 --- a/package.json +++ b/package.json @@ -35,14 +35,14 @@ }, "dependencies": {}, "devDependencies": { - "axios": "~0.5.4", - "babel-eslint": "^7.1.1", - "chai": "~2.3.0", - "es6-promise": "^4.0.5", - "eslint-config-airbnb-es5": "^1.1.0", - "eslint-plugin-react": "^6.7.1", - "jsdoc": "~3.3.0", - "mocha": "~2.2.5", + "axios": "^0.5.4", + "babel-eslint": "^7.2.3", + "chai": "^2.3.0", + "es6-promise": "^4.1.1", + "eslint-config-airbnb-es5": "^1.2.0", + "eslint-plugin-react": "^6.10.3", + "jsdoc": "^3.5.5", + "mocha": "^2.5.3", "mocha-eslint": "^3.0.1" }, "engines": {