UNPKG

forerunnerdb

Version:

A NoSQL document store database for browsers and Node.js.

727 lines (601 loc) 23.2 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: NodeApiClient.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: NodeApiClient.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>"use strict"; // Tell JSHint about EventSource /*global EventSource */ // Import external names locally var Shared = require('./Shared'), Core, CoreInit, Collection, NodeApiClient, Overload; /** * The NodeApiClient class. * @class * @constructor */ NodeApiClient = function () { this.init.apply(this, arguments); }; /** * The init method that can be overridden or extended. * @param {Core} core The ForerunnerDB core instance. */ NodeApiClient.prototype.init = function (core) { var self = this; self._core = core; self.rootPath('/fdb'); }; Shared.addModule('NodeApiClient', NodeApiClient); Shared.mixin(NodeApiClient.prototype, 'Mixin.Common'); Shared.mixin(NodeApiClient.prototype, 'Mixin.Events'); Shared.mixin(NodeApiClient.prototype, 'Mixin.ChainReactor'); Core = Shared.modules.Core; CoreInit = Core.prototype.init; Collection = Shared.modules.Collection; Overload = Shared.overload; /** * Gets / sets the rootPath for the client to use in each API call. * @name rootPath * @method NodeApiClient.rootPath * @param {String=} val The path to set. * @returns {*} */ Shared.synthesize(NodeApiClient.prototype, 'rootPath'); /** * Set the url of the server to use for API. * @param {String} host The server host name including protocol. E.g. * "https://0.0.0.0". * @param {String} port The server port number e.g. "8080". */ NodeApiClient.prototype.server = function (host, port) { if (host !== undefined) { if (host.substr(host.length - 1, 1) === '/') { // Strip trailing / host = host.substr(0, host.length - 1); } if (port !== undefined) { this._server = host + ":" + port; } else { this._server = host; } this._host = host; this._port = port; return this; } if (port !== undefined) { return { host: this._host, port: this._port, url: this._server }; } else { return this._server; } }; NodeApiClient.prototype.http = function (method, url, data, options, callback) { var self = this, finalUrl, sessionData, bodyData, xmlHttp = new XMLHttpRequest(); method = method.toUpperCase(); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState === 4) { if (xmlHttp.status === 200) { // Tell the callback about success if (xmlHttp.responseText) { callback(false, self.jParse(xmlHttp.responseText)); } else { callback(false, {}); } } else if (xmlHttp.status === 204) { callback(false, {}); } else { // Tell the callback about the error callback(xmlHttp.status, xmlHttp.responseText); // Emit the error code self.emit('httpError', xmlHttp.status, xmlHttp.responseText); } } }; switch (method) { case 'GET': case 'DELETE': case 'HEAD': // Check for global auth if (this._sessionData) { data = data !== undefined ? data : {}; // Add the session data to the key specified data[this._sessionData.key] = this._sessionData.obj; } finalUrl = url + (data !== undefined ? '?' + self.jStringify(data) : ''); bodyData = null; break; case 'POST': case 'PUT': case 'PATCH': // Check for global auth if (this._sessionData) { sessionData = {}; // Add the session data to the key specified sessionData[this._sessionData.key] = this._sessionData.obj; } finalUrl = url + (sessionData !== undefined ? '?' + self.jStringify(sessionData) : ''); bodyData = (data !== undefined ? self.jStringify(data) : null); break; default: return false; } xmlHttp.open(method, finalUrl, true); xmlHttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); xmlHttp.send(bodyData); return this; }; // Define HTTP helper methods NodeApiClient.prototype.head = new Overload({ 'string, function': function (path, callback) { return this.$main.call(this, path, undefined, {}, callback); }, 'string, *, function': function (path, data, callback) { return this.$main.call(this, path, data, {}, callback); }, 'string, *, object, function': function (path, data, options, callback) { return this.$main.call(this, path, data, options, callback); }, '$main': function (path, data, options, callback) { return this.http('HEAD', this.server() + this._rootPath + path, data, options, callback); } }); NodeApiClient.prototype.get = new Overload({ 'string, function': function (path, callback) { return this.$main.call(this, path, undefined, {}, callback); }, 'string, *, function': function (path, data, callback) { return this.$main.call(this, path, data, {}, callback); }, 'string, *, object, function': function (path, data, options, callback) { return this.$main.call(this, path, data, options, callback); }, '$main': function (path, data, options, callback) { return this.http('GET', this.server() + this._rootPath + path, data, options, callback); } }); NodeApiClient.prototype.put = new Overload({ 'string, function': function (path, callback) { return this.$main.call(this, path, undefined, {}, callback); }, 'string, *, function': function (path, data, callback) { return this.$main.call(this, path, data, {}, callback); }, 'string, *, object, function': function (path, data, options, callback) { return this.$main.call(this, path, data, options, callback); }, '$main': function (path, data, options, callback) { return this.http('PUT', this.server() + this._rootPath + path, data, options, callback); } }); NodeApiClient.prototype.post = new Overload({ 'string, function': function (path, callback) { return this.$main.call(this, path, undefined, {}, callback); }, 'string, *, function': function (path, data, callback) { return this.$main.call(this, path, data, {}, callback); }, 'string, *, object, function': function (path, data, options, callback) { return this.$main.call(this, path, data, options, callback); }, '$main': function (path, data, options, callback) { return this.http('POST', this.server() + this._rootPath + path, data, options, callback); } }); NodeApiClient.prototype.patch = new Overload({ 'string, function': function (path, callback) { return this.$main.call(this, path, undefined, {}, callback); }, 'string, *, function': function (path, data, callback) { return this.$main.call(this, path, data, {}, callback); }, 'string, *, object, function': function (path, data, options, callback) { return this.$main.call(this, path, data, options, callback); }, '$main': function (path, data, options, callback) { return this.http('PATCH', this.server() + this._rootPath + path, data, options, callback); } }); NodeApiClient.prototype.postPatch = function (path, id, data, options, callback) { var self = this; // Determine if the item exists or not this.head(path + '/' + id, undefined, {}, function (err, headData) { if (err) { if (err === 404) { // Item does not exist, run post return self.http('POST', self.server() + self._rootPath + path, data, options, callback); } else { callback(err, data); } } else { // Item already exists, run patch return self.http('PATCH', self.server() + self._rootPath + path + '/' + id, data, options, callback); } }); }; NodeApiClient.prototype.delete = new Overload({ 'string, function': function (path, callback) { return this.$main.call(this, path, undefined, {}, callback); }, 'string, *, function': function (path, data, callback) { return this.$main.call(this, path, data, {}, callback); }, 'string, *, object, function': function (path, data, options, callback) { return this.$main.call(this, path, data, options, callback); }, '$main': function (path, data, options, callback) { return this.http('DELETE', this.server() + this._rootPath + path, data, options, callback); } }); /** * Gets/ sets a global object that will be sent up with client * requests to the API or REST server. * @param {String} key The key to send the session object up inside. * @param {*} obj The object / value to send up with all requests. If * a request has its own data to send up, this session data will be * mixed in to the request data under the specified key. */ NodeApiClient.prototype.session = function (key, obj) { if (key !== undefined &amp;&amp; obj !== undefined) { this._sessionData = { key: key, obj: obj }; return this; } return this._sessionData; }; /** * Initiates a client connection to the API server. * @param collectionInstance * @param path * @param query * @param options * @param callback */ NodeApiClient.prototype.sync = function (collectionInstance, path, query, options, callback) { var self = this, source, finalPath, queryParams, queryString = '', connecting = true; if (this.debug()) { console.log(this.logIdentifier() + ' Connecting to API server ' + this.server() + this._rootPath + path); } finalPath = this.server() + this._rootPath + path + '/_sync'; // Check for global auth if (this._sessionData) { queryParams = queryParams || {}; if (this._sessionData.key) { // Add the session data to the key specified queryParams[this._sessionData.key] = this._sessionData.obj; } else { // Add the session data to the root query object Shared.mixin(queryParams, this._sessionData.obj); } } if (query) { queryParams = queryParams || {}; queryParams.$query = query; } if (options) { queryParams = queryParams || {}; if (options.$initialData === undefined) { options.$initialData = true; } queryParams.$options = options; } if (queryParams) { queryString = this.jStringify(queryParams); finalPath += '?' + queryString; } source = new EventSource(finalPath); collectionInstance.__apiConnection = source; source.addEventListener('open', function (e) { if (!options || (options &amp;&amp; options.$initialData)) { // The connection is open, grab the initial data self.get(path, queryParams, function (err, data) { if (!err) { collectionInstance.upsert(data); } }); } }, false); source.addEventListener('error', function (e) { if (source.readyState === 2) { // The connection is dead, remove the connection collectionInstance.unSync(); } if (connecting) { connecting = false; callback(e); } }, false); source.addEventListener('insert', function(e) { var data = self.jParse(e.data); collectionInstance.insert(data.dataSet); }, false); source.addEventListener('update', function(e) { var data = self.jParse(e.data); collectionInstance.update(data.query, data.update); }, false); source.addEventListener('remove', function(e) { var data = self.jParse(e.data); collectionInstance.remove(data.query); }, false); if (callback) { source.addEventListener('connected', function (e) { if (connecting) { connecting = false; callback(false); } }, false); } }; Collection.prototype.sync = new Overload({ /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'function': function (callback) { this.$main.call(this, '/' + this._db.name() + '/collection/' + this.name(), null, null, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {String} collectionName The collection to sync from. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'string, function': function (collectionName, callback) { this.$main.call(this, '/' + this._db.name() + '/collection/' + collectionName, null, null, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {Object} query A query object. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'object, function': function (query, callback) { this.$main.call(this, '/' + this._db.name() + '/collection/' + this.name(), query, null, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {String} objectType The type of object to sync to e.g. * "collection" or "view". * @param {String} objectName The name of the object to sync from. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'string, string, function': function (objectType, objectName, callback) { this.$main.call(this, '/' + this._db.name() + '/' + objectType + '/' + objectName, null, null, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {String} collectionName The collection to sync from. * @param {Object} query A query object. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'string, object, function': function (collectionName, query, callback) { this.$main.call(this, '/' + this._db.name() + '/collection/' + collectionName, query, null, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {String} objectType The type of object to sync to e.g. * "collection" or "view". * @param {String} objectName The name of the object to sync from. * @param {Object} query A query object. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'string, string, object, function': function (objectType, objectName, query, callback) { this.$main.call(this, '/' + this._db.name() + '/' + objectType + '/' + objectName, query, null, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {Object} query A query object. * @param {Object} options An options object. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'object, object, function': function (query, options, callback) { this.$main.call(this, '/' + this._db.name() + '/collection/' + this.name(), query, options, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {String} collectionName The collection to sync from. * @param {Object} query A query object. * @param {Object} options An options object. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'string, object, object, function': function (collectionName, query, options, callback) { this.$main.call(this, '/' + this._db.name() + '/collection/' + collectionName, query, options, callback); }, /** * Sync with this collection on the server-side. * @name sync * @method Collection.sync * @param {String} objectType The type of object to sync to e.g. * "collection" or "view". * @param {String} objectName The name of the object to sync from. * @param {Object} query A query object. * @param {Object} options An options object. * @param {Function} callback The callback method to call once * the connection to the server has been established. */ 'string, string, object, object, function': function (objectType, objectName, query, options, callback) { this.$main.call(this, '/' + this._db.name() + '/' + objectType + '/' + objectName, query, options, callback); }, '$main': function (path, query, options, callback) { var self = this; if (this._db &amp;&amp; this._db._core) { // Kill any existing sync connection this.unSync(); // Create new sync connection this._db._core.api.sync(this, path, query, options, callback); // Hook on drop to call unsync this.on('drop', function () { self.unSync(); }); } else { throw(this.logIdentifier() + ' Cannot sync for an anonymous collection! (Collection must be attached to a database)'); } } }); /** * Disconnects an existing connection to a sync server. * @returns {boolean} True if a connection existed, false * if no connection existed. */ Collection.prototype.unSync = function () { if (this.__apiConnection) { if (this.__apiConnection.readyState !== 2) { this.__apiConnection.close(); } delete this.__apiConnection; return true; } return false; }; Collection.prototype.http = new Overload({ 'string, function': function (method, callback) { this.$main.call(this, method, '/' + this._db.name() + '/collection/' + this.name(), undefined, undefined, {}, callback); }, '$main': function (method, path, queryObj, queryOptions, options, callback) { if (this._db &amp;&amp; this._db._core) { return this._db._core.api.http('GET', this._db._core.api.server() + this._rootPath + path, {"$query": queryObj, "$options": queryOptions}, options, callback); } else { throw(this.logIdentifier() + ' Cannot do HTTP for an anonymous collection! (Collection must be attached to a database)'); } } }); Collection.prototype.autoHttp = new Overload({ 'string, function': function (method, callback) { this.$main.call(this, method, '/' + this._db.name() + '/collection/' + this.name(), undefined, undefined, {}, callback); }, 'string, string, function': function (method, collectionName, callback) { this.$main.call(this, method, '/' + this._db.name() + '/collection/' + collectionName, undefined, undefined, {}, callback); }, 'string, string, string, function': function (method, objType, objName, callback) { this.$main.call(this, method, '/' + this._db.name() + '/' + objType + '/' + objName, undefined, undefined, {}, callback); }, 'string, string, string, object, function': function (method, objType, objName, queryObj, callback) { this.$main.call(this, method, '/' + this._db.name() + '/' + objType + '/' + objName, queryObj, undefined, {}, callback); }, 'string, string, string, object, object, function': function (method, objType, objName, queryObj, queryOptions, callback) { this.$main.call(this, method, '/' + this._db.name() + '/' + objType + '/' + objName, queryObj, queryOptions, {}, callback); }, '$main': function (method, path, queryObj, queryOptions, options, callback) { var self = this; if (this._db &amp;&amp; this._db._core) { return this._db._core.api.http('GET', this._db._core.api.server() + this._db._core.api._rootPath + path, {"$query": queryObj, "$options": queryOptions}, options, function (err, data) { var i; if (!err &amp;&amp; data) { // Check the type of method we used and operate on the collection accordingly switch (method) { // Find insert case 'GET': self.insert(data); break; // Insert case 'POST': if (data.inserted &amp;&amp; data.inserted.length) { self.insert(data.inserted); } break; // Update overwrite case 'PUT': case 'PATCH': if (data instanceof Array) { // Update each document for (i = 0; i &lt; data.length; i++) { self.updateById(data[i]._id, {$overwrite: data[i]}); } } else { // Update single document self.updateById(data._id, {$overwrite: data}); } break; // Remove case 'DELETE': self.remove(data); break; default: // Nothing to do with this method break; } } // Send the data back to the callback callback(err, data); }); } else { throw(this.logIdentifier() + ' Cannot do HTTP for an anonymous collection! (Collection must be attached to a database)'); } } }); // Override the Core init to instantiate the plugin Core.prototype.init = function () { CoreInit.apply(this, arguments); this.api = new NodeApiClient(this); }; Shared.finishModule('NodeApiClient'); module.exports = NodeApiClient;</code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ActiveBucket.html">ActiveBucket</a></li><li><a href="Angular.html">Angular</a></li><li><a href="AutoBind.html">AutoBind</a></li><li><a href="Collection.html">Collection</a></li><li><a href="CollectionGroup.html">CollectionGroup</a></li><li><a href="Condition.html">Condition</a></li><li><a href="Core.html">Core</a></li><li><a href="Db.html">Db</a></li><li><a href="Document.html">Document</a></li><li><a href="Grid.html">Grid</a></li><li><a href="Highchart.html">Highchart</a></li><li><a href="Index2d.html">Index2d</a></li><li><a href="IndexBinaryTree.html">IndexBinaryTree</a></li><li><a href="IndexHashMap.html">IndexHashMap</a></li><li><a href="Infinilist.html">Infinilist</a></li><li><a href="KeyValueStore.html">KeyValueStore</a></li><li><a href="Metrics.html">Metrics</a></li><li><a href="MyModule.html">MyModule</a></li><li><a href="NodeApiClient.html">NodeApiClient</a></li><li><a href="NodeApiServer.html">NodeApiServer</a></li><li><a href="NodeRAS.html">NodeRAS</a></li><li><a href="Odm.html">Odm</a></li><li><a href="OldView.html">OldView</a></li><li><a href="Operation.html">Operation</a></li><li><a href="Overload.html">Overload</a></li><li><a href="Overview.html">Overview</a></li><li><a href="Overview_init.html">init</a></li><li><a href="Path.html">Path</a></li><li><a href="Persist.html">Persist</a></li><li><a href="Procedure.html">Procedure</a></li><li><a href="ReactorIO.html">ReactorIO</a></li><li><a href="Section.html">Section</a></li><li><a href="Serialiser.html">Serialiser</a></li><li><a href="Shared.overload.html">overload</a></li><li><a href="View.html">View</a></li></ul><h3>Mixins</h3><ul><li><a href="ChainReactor.html">ChainReactor</a></li><li><a href="Common.html">Common</a></li><li><a href="Constants.html">Constants</a></li><li><a href="Events.html">Events</a></li><li><a href="Matching.html">Matching</a></li><li><a href="Shared.html">Shared</a></li><li><a href="Sorting.html">Sorting</a></li><li><a href="Tags.html">Tags</a></li><li><a href="Triggers.html">Triggers</a></li><li><a href="Updating.html">Updating</a></li></ul><h3><a href="global.html">Global</a></h3> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.0</a> on Thu Mar 01 2018 11:34:22 GMT+0000 (GMT) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>