Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Actually make http requests (closes #4) #8

Merged
merged 18 commits into from
Apr 26, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ node_modules/
*.sublime*
sketch
test.html
.c9/
*.log
98 changes: 47 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,73 @@

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
## Install

`bower install lightblue.js --save`

`npm install lightblue.js --save`

`git clone https://github.com/alechenninger/lightblue.js.git`

# Usage

It does still sort of work! It won't actually make any requests, but gives you an API for building the key components of the request: the HTTP method, the URL, and the request body. The idea is this information could then be used easily with either XMLHttpRequest, jQuery.ajax, Angular's $http service, or a Node.JS HTTP client. All it takes is a little glue code to tie the necessary components with one of the aforementioned common AJAX mechanisms.

Use browserify `require` or commonjs `define`, or just include dist/lightblue.min.js and use the namespace `lightblue`.
## Imports

## Imports:
### Vanilla.js

```javascript
// Plain old HTML
<script src="lightblue.min.js"></script>
// No module framework (use window.lightblue)
<script src="lightblue.min.js" type="text/javascript"></script>
```

// NodeJS or Browserify
var lightblue = require("./lightblue.min.js");
### Browserify (CommonJS) or RequireJS (AMD)

// CommonJS and RequireJS work too but I don't have an example
```js
// commonjs
var lightblue = require("lightblue");

// asynchronous module definition (amd)
require(["lightblue"], function(lightblue) {
...
});
```

## Construct a find request:
Once you have a `lightblue` object, you can get a client:

```javascript
```js
// Assumes /data and /metadata for data and metadata services respectively,
// but you can override.
var client = lightblue.getClient("http://my.lightblue.host.com/rest");
```

### AngularJS
If angular is detected, a "lightblue" module will be registered with a
"lightblue" service as the client.

```js
var app = angular.module("app", ["lightblue"]);

app.config(["lightblueProvider", function(lightblueProvider) {
lightblueProvider.setHost("http://my.lightblue.com");
}]);

app.controller("ctrl", ["lightblue", function(lightblueClient) {
lightblueClient.data.find(...)
.then(...);
}]);
```

**At the moment you will also need to use the global "lightblue" namespace if
you want query builder API. So don't name your client variable `lightblue`
just yet. See
[issue #9](https://github.com/alechenninger/lightblue.js/issues/9).**

## Construct a find request

```javascript
var field = lightblue.field;

var find = client.data.find({
Expand All @@ -51,39 +80,6 @@ var find = client.data.find({
.and(field("age").greaterThan(4))),
// No projection builder yet but it would be something like this:
projection: include("*").recursively()
});

assertEquals("http://my.lightblue.host.com/rest/data/find/User/1.0.0", find.url);
assertEquals("post", find.method);
assertEquals({
objectType: "User",
version: "1.0.0",
query: {
$or: [
{
field: "username",
op: "$eq",
rvalue: "bob"
},
{
$and: [
{
field: "firstName",
op: "$eq",
rfield: "username"
},
{
field: "age",
op: "$gt",
rvalue: 4
}
]
}
]
},
projection: {
field: "*",
recursive: true
}
}, find.body);
})
.then(console.log);
```
6 changes: 6 additions & 0 deletions lib/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = LightblueClient;

function LightblueClient(dataClient, metadataClient) {
this.data = dataClient;
this.metadata = metadataClient;
}
51 changes: 49 additions & 2 deletions lib/clientutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,55 @@ 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";
}

var assertArg = {
isInstance: function(arg, ctor, name) {
if (!(arg instanceof ctor)) {
throw new Error("Expected instanceof " + ctor + " 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;
},
isTypeOf: function(arg, type, name) {
if (typeof arg !== type) {
throw new Error("Expected a typeof " + type + " but " + name + " was " +
typeof arg);
}

return arg;
}
};

exports.assertArg = assertArg;
exports.ifDefined = ifDefined;
exports.isDefined = isDefined;
exports.isObject = isObject;
56 changes: 35 additions & 21 deletions lib/data.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,55 @@
var resolve = require("./clientutil").resolve;
var RestRequest = require("./rest").RestRequest;
// TODO: Consider using url.resolve?
var resolve = require("./clientutil").resolve;
var http = require("./http");

var HttpRequest = http.HttpRequest;

module.exports = LightblueDataClient;

/**
* Client for making requests against a Lightblue data endpoint.
* @constructor
* @param {String} host - The URL where the rest data server is deployed. This
* @param {HttpClient} httpClient
* @param {String} host The URL where the rest data server is deployed. This
* path will be the base for requests, like ${host}/find, so if the app is
* 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 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 this._execute(new InsertRequest(this._host, config));
};

LightblueDataClient.prototype.update = function(config) {
return new UpdateRequest(this.host, config);
return this._execute(new UpdateRequest(this._host, config));
};

LightblueDataClient.prototype.save = function(config) {
return new SaveRequest(this.host, config);
return this._execute(new SaveRequest(this._host, config));
};

LightblueDataClient.prototype.delete = function(config) {
return new DeleteRequest(this.host, config);
return this._execute(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 body = {
objectType: config.entity,
version: config.version,
Expand All @@ -57,54 +67,57 @@ function FindRequest(host, config) {
body.range[1] = config.range.to || config.range[1];
}

RestRequest.call(this, "post", url, body);
HttpRequest.call(this, "post", url, body);
}

FindRequest.prototype = Object.create(RestRequest.prototype);
FindRequest.prototype = Object.create(HttpRequest.prototype);
FindRequest.prototype.constructor = FindRequest;

/**
* @param {Object} config The config object for the request.
*/
function InsertRequest(host, config) {
var url = resolve(host, "insert", config.entity, config.version);

var body = {
objectType: config.entity,
version: config.version,
data: config.data,
projection: config.projection
};

RestRequest.call(this, "put", url, body);
HttpRequest.call(this, "put", url, body);
}

InsertRequest.prototype = Object.create(RestRequest.prototype);
InsertRequest.prototype = Object.create(HttpRequest.prototype);
InsertRequest.prototype.constructor = InsertRequest;

/**
* @param {Object} config The config object for the request.
*/
function SaveRequest(host, config) {
var url = resolve(host, "save", config.entity, config.version);

var body = {
objectType: config.entity,
version: config.version,
data: config.data,
upsert: config.upsert,
projection: config.projection
}
};

RestRequest.call(this, "post", url, body);
HttpRequest.call(this, "post", url, body);
}

SaveRequest.prototype = Object.create(RestRequest.prototype);
SaveRequest.prototype = Object.create(HttpRequest.prototype);
SaveRequest.prototype.constructor = SaveRequest;

/**
* @param {Object} config The config object for the request.
*/
function UpdateRequest(host, config) {
var url = resolve(host, "update", config.entity, config.version);

var body = {
objectType: config.entity,
version: config.version,
Expand All @@ -113,25 +126,26 @@ function UpdateRequest(host, config) {
projection: config.projection
};

RestRequest.call(this, "post", url, body);
HttpRequest.call(this, "post", url, body);
}

UpdateRequest.prototype = Object.create(RestRequest.prototype);
UpdateRequest.prototype = Object.create(HttpRequest.prototype);
UpdateRequest.prototype.constructor = UpdateRequest;

/**
* @param {Object} config The config object for the request.
*/
function DeleteRequest(host, config) {
var url = resolve(host, "delete", config.entity, config.version);

var body = {
objectType: config.entity,
version: config.version,
query: config.query
};

RestRequest.call(this, "post", url, body);
HttpRequest.call(this, "post", url, body);
}

DeleteRequest.prototype = Object.create(RestRequest.prototype);
DeleteRequest.prototype = Object.create(HttpRequest.prototype);
DeleteRequest.prototype.constructor = DeleteRequest;
25 changes: 25 additions & 0 deletions lib/http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
var assertArg = require("./clientutil.js").assertArg;

/**
* @constructor
* @param {String} method Http method, case insensitive.
* @param {Url} url
* @param {String=} body The request body.
*/
exports.HttpRequest = function(method, url, body) {
this.method = assertArg.isNotBlankString(method, "method");
this.url = assertArg.isTypeOf(url, "string", "url");
this.body = body;

this.METHOD = this.method.toUpperCase();
};

/**
* @interface HttpClient
*/

/**
* @function
* @name HttpClient#execute
* @return {Promise}
*/