diff --git a/README.md b/README.md index a4337dd..1db5379 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,10 @@ A [lightblue](https://github.com/lightblue-platform) client written in Javascript. -Conceivably one day useful for: +Write... - Node.JS apps talking to a Lightblue REST service - Client side apps communicating with a server that forwards requests to a Lightblue REST service -At the moment this is really just a rough sketch of an idea and will change drastically. - # Install `bower install lightblue.js --save` @@ -18,18 +16,20 @@ At the moment this is really just a rough sketch of an idea and will change dras # Usage -Use browserify `require` or commonjs `define`, or just include dist/lightblue.min.js and use the namespace `lightblue`. +Use browserify `require` or requirejs `define`, or just include dist/lightblue.min.js and use the namespace `lightblue`. + +For frontend angular applications, use the `lightblue` module (TODO: document). ## Imports: ```javascript -// Plain old HTML +// No module framework (use window.lightblue) -// NodeJS or Browserify +// NodeJS or Browserify or CommonJS var lightblue = require("./lightblue.min.js"); -// CommonJS and RequireJS work too but I don't have an example +// RequireJS works too but I don't have an example ``` ## Construct a find request: diff --git a/lib/clientutil.js b/lib/clientutil.js index c611019..caba278 100644 --- a/lib/clientutil.js +++ b/lib/clientutil.js @@ -22,12 +22,47 @@ exports.resolve = function() { .filter(notEmpty) .map(trimSlashes) .join("/"); -} +}; exports.isEmpty = function(s) { return typeof s === "undefined" || s === ""; +}; + +function ifDefined(it, otherwise) { + return isDefined(it) ? it : otherwise; +} + +function isDefined(it) { + return typeof it !== "undefined"; +} + +function isObject(it) { + return typeof it === "object"; } -exports.ifDefined = function(it, otherwise) { - return (typeof it !== "undefined") ? it : otherwise; -} \ No newline at end of file +var assertArg = { + isInstance: function(arg, type, name) { + if (!(arg instanceof type)) { + throw new Error("Expected instanceof " + type + " but " + name + " was " + + isObject(arg) ? arg.constructor : "undefined"); + } + + return arg; + }, + isNotBlankString: function(arg, name) { + if (typeof arg !== "string") { + throw new Error("Expected a string but " + name + " was " + typeof arg); + } + + if (arg.trim().length === 0) { + throw new Error("Expected non-blank string but " + name + " was empty."); + } + + return arg; + } +}; + +exports.assertArg = assertArg; +exports.ifDefined = ifDefined; +exports.isDefined = isDefined; +exports.isObject = isObject; \ No newline at end of file diff --git a/lib/data.js b/lib/data.js index f3c7377..5749445 100644 --- a/lib/data.js +++ b/lib/data.js @@ -14,35 +14,43 @@ module.exports = LightblueDataClient; * deployed under a particular context (like /rest/data), this must be * included in the host. */ -function LightblueDataClient(host) { - this.host = host; +function LightblueDataClient(httpClient, host) { + this._httpClient = httpClient; + this._host = host; +} + +LightblueDataClient.prototype._execute = function(request) { + return this._httpClient.execute(request); } LightblueDataClient.prototype.find = function(config) { - return this._execute(new FindRequest(this.host, config)); + return this._execute(new FindRequest(this._host, config)); }; LightblueDataClient.prototype.insert = function(config) { - return new InsertRequest(this.host, config); + return new InsertRequest(this._host, config); }; LightblueDataClient.prototype.update = function(config) { - return new UpdateRequest(this.host, config); + return new UpdateRequest(this._host, config); }; LightblueDataClient.prototype.save = function(config) { - return new SaveRequest(this.host, config); + return new SaveRequest(this._host, config); }; LightblueDataClient.prototype.delete = function(config) { - return new DeleteRequest(this.host, config); + return new DeleteRequest(this._host, config); }; /** * @param {Object} config The config object for the request. */ function FindRequest(host, config) { - var url = resolve(host, "find", config.entity, config.version); + var url = new Url(host, { + path: resolve("find", config.entity, config.version) + }); + var body = { objectType: config.entity, version: config.version, diff --git a/lib/http.js b/lib/http.js index 50a8c2f..c9b0b30 100644 --- a/lib/http.js +++ b/lib/http.js @@ -1,3 +1,5 @@ +var assertArg = require("./clientutil.js").assertArg; + /** * @constructor * @param {String} method Http method, case insensitive. @@ -5,12 +7,14 @@ * @param {String=} body The request body. */ exports.HttpRequest = function(method, url, body) { - this.method = (typeof method === "string") ? method.toLowerCase() : ""; + this.method = assertArg.isNotBlankString(method, "method"); + this.body = (typeof body === "string") ? body : ""; + this.url = assertArg.isInstance(url, Url, "url"); + + // derived properties this.METHOD = this.method.toUpperCase(); - this.body = body; - this.url = url; this.href = url.toString(); -} +}; /** * @constructor @@ -22,14 +26,26 @@ exports.HttpRequest = function(method, url, body) { exports.Url = Url; function Url(host, options) { - this.host = host; - - if (options instanceof Object) { - this.port = options.port || 80; - this.path = options.path || "/"; - } + if (!/https?:\/\/[^\s]+$/.test(host)) { + throw new Error("Invalid url host syntax: " + host); + } + + this.host = host; + + var port, path; + + if (options instanceof Object) { + port = options.port || 80; + path = options.path || "/"; + } else { + port = 80; + path = "/"; + } + + this.port = port; + this.path = path; } Url.prototype.toString = function() { - return this.host + ":" + this.port + this.path; -} \ No newline at end of file + return this.host + ":" + this.port + this.path; +}; \ No newline at end of file diff --git a/lib/metadata.js b/lib/metadata.js index d9be7cd..1e84579 100644 --- a/lib/metadata.js +++ b/lib/metadata.js @@ -1,5 +1,8 @@ -var util = require("./clientutil"); -var RestRequest = require("./rest").RestRequest; +var resolve = require("./clientutil").resolve; +var http = require("./http"); + +var HttpRequest = http.HttpRequest; +var Url = http.Url; module.exports = LightblueMetadataClient; diff --git a/lib/node_http.js b/lib/node_http.js index a75beed..14ca8eb 100644 --- a/lib/node_http.js +++ b/lib/node_http.js @@ -11,26 +11,80 @@ function NodeHttpClient(options) { this._execSsl = https.request; if (options instanceof Object) { - this._auth = ifDefined(options.auth, null); - //this. + this._auth = ifDefined(options.auth, undefined); + // TODO: Support ssl certs } } /** - * @param {RestRequest} req + * @param {HttpRequest} requestOpts */ -NodeHttpClient.prototype.execute = function(req) { +NodeHttpClient.prototype.execute = function(requestOpts) { var deferred = q.defer(); + var promise = deferred.promise; - // TODO: Lookup how angular does this for reference - return { - success: function(callback) { - + var exec; + var hostname; + + if (requestOpts.href.indexOf("https") === 0) { + hostname = requestOpts.url.host.substr("https://".length); + exec = this._execSsl; + } else { + hostname = requestOpts.url.host.substr("http://".length); + exec = this._exec; + } + + var options = { + hostname: hostname, + port: requestOpts.url.port, + path: requestOpts.url.path, + method: requestOpts.METHOD, + headers: { + 'Content-Type': 'application/json', + 'Content-Length': requestOpts.body.length }, - error: function(callback) { - - } + // TODO: Does this work? + auth: this._auth }; + + var request = exec(options, function(res) { + var responseBody = ""; + + res.setEncoding("utf8"); + + res.on('data', function(chunk) { + responseBody += chunk; + }); + + res.on('end', function() { + // TODO: resolve with more than just body? + // TODO: error on lightblue error? + deferred.resolve(responseBody); + }); + + res.on('error', function(error) { + // TODO: what is this resolving with? + deferred.reject(error); + }); + }); + + request.write(requestOpts.body, 'utf8'); + request.end(); + + request.on('error', function(error) { + // TODO: reject with something consistent + deferred.reject(error); + }); + + promise.success = function(callback) { + return promise.then(callback); + }; + + promise.error = function(callback) { + return promise.then(null, callback); + }; + + return promise; }; function ifDefined(it, otherwise) {