You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@usergrid.apache.org by mr...@apache.org on 2016/09/02 16:27:13 UTC

[09/13] usergrid-javascript git commit: Initial commit of Usergrid Javascript SDK into its own repo

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Client.js
----------------------------------------------------------------------
diff --git a/lib/modules/Client.js b/lib/modules/Client.js
new file mode 100644
index 0000000..e27ae65
--- /dev/null
+++ b/lib/modules/Client.js
@@ -0,0 +1,883 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+(function() {
+  var name = 'Client',
+    global = this,
+    overwrittenName = global[name],
+    exports;
+  var AUTH_ERRORS = [
+    "auth_expired_session_token",
+    "auth_missing_credentials",
+    "auth_unverified_oath",
+    "expired_token",
+    "unauthorized",
+    "auth_invalid"
+  ];
+  Usergrid.Client = function(options) {
+    //usergrid endpoint
+    this.URI = options.URI || 'https://api.usergrid.com';
+
+    //Find your Orgname and Appname in the Admin portal (http://apigee.com/usergrid)
+    if (options.orgName) {
+      this.set('orgName', options.orgName);
+    }
+    if (options.appName) {
+      this.set('appName', options.appName);
+    }
+    if (options.qs) {
+      this.setObject('default_qs', options.qs);
+    }
+    //other options
+    this.buildCurl = options.buildCurl || false;
+    this.logging = options.logging || false;
+
+    //timeout and callbacks
+    // this.logoutCallback =  options.logoutCallback || null;
+  };
+
+  /*
+   *  Main function for making requests to the API.  Can be called directly.
+   *
+   *  options object:
+   *  `method` - http method (GET, POST, PUT, or DELETE), defaults to GET
+   *  `qs` - object containing querystring values to be appended to the uri
+   *  `body` - object containing entity body for POST and PUT requests
+   *  `endpoint` - API endpoint, for example 'users/fred'
+   *  `mQuery` - boolean, set to true if running management query, defaults to false
+   *
+   *  @method request
+   *  @public
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.request = function(options, callback) {
+    var method = options.method || 'GET';
+    var endpoint = options.endpoint;
+    var body = options.body || {};
+    var qs = options.qs || {};
+    var mQuery = options.mQuery || false; //is this a query to the management endpoint?
+    var orgName = this.get('orgName');
+    var appName = this.get('appName');
+    var default_qs = this.getObject('default_qs');
+    var uri;
+    /*var logoutCallback=function(){
+        if (typeof(this.logoutCallback) === 'function') {
+            return this.logoutCallback(true, 'no_org_or_app_name_specified');
+        }
+    }.bind(this);*/
+    if (!mQuery && !orgName && !appName) {
+      return logoutCallback();
+    }
+    if (mQuery) {
+      uri = this.URI + '/' + endpoint;
+    } else {
+      uri = this.URI + '/' + orgName + '/' + appName + '/' + endpoint;
+    }
+    if (this.getToken()) {
+      qs.access_token = this.getToken();
+      /*
+       **NOTE** The token can also be passed as a header on the request
+
+       xhr.setRequestHeader("Authorization", "Bearer " + self.getToken());
+       xhr.withCredentials = true;
+       */
+    }
+    if (default_qs) {
+      qs = propCopy(qs, default_qs);
+    }
+    var self=this;
+    var req = new Usergrid.Request(method, uri, qs, body, function(err, response) {
+      /*if (AUTH_ERRORS.indexOf(response.error) !== -1) {
+            return logoutCallback();
+        }*/
+      if(err){
+        doCallback(callback, [err, response, self], self);
+      }else{
+        doCallback(callback, [null, response, self], self);
+      }
+      //p.done(err, response);
+    });
+  };
+
+  /*
+   *  function for building asset urls
+   *
+   *  @method buildAssetURL
+   *  @public
+   *  @params {string} uuid
+   *  @return {string} assetURL
+   */
+  Usergrid.Client.prototype.buildAssetURL = function(uuid) {
+    var self = this;
+    var qs = {};
+    var assetURL = this.URI + '/' + this.orgName + '/' + this.appName + '/assets/' + uuid + '/data';
+
+    if (self.getToken()) {
+      qs.access_token = self.getToken();
+    }
+
+    //append params to the path
+    var encoded_params = encodeParams(qs);
+    if (encoded_params) {
+      assetURL += "?" + encoded_params;
+    }
+
+    return assetURL;
+  };
+
+  /*
+   *  Main function for creating new groups. Call this directly.
+   *
+   *  @method createGroup
+   *  @public
+   *  @params {string} path
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.createGroup = function(options, callback) {
+    var group = new Usergrid.Group({
+      path: options.path,
+      client: this,
+      data: options
+    });
+    group.save(function(err, response) {
+      doCallback(callback, [err, response, group], group);
+    });
+  };
+
+  /*
+   *  Main function for creating new entities - should be called directly.
+   *
+   *  options object: options {data:{'type':'collection_type', 'key':'value'}, uuid:uuid}}
+   *
+   *  @method createEntity
+   *  @public
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.createEntity = function(options, callback) {
+    var entity = new Usergrid.Entity({
+      client: this,
+      data: options
+    });
+    entity.save(function(err, response) {
+      doCallback(callback, [err, response, entity], entity);
+    });
+  };
+  /*
+   *  Main function for getting existing entities - should be called directly.
+   *
+   *  You must supply a uuid or (username or name). Username only applies to users.
+   *  Name applies to all custom entities
+   *
+   *  options object: options {data:{'type':'collection_type', 'name':'value', 'username':'value'}, uuid:uuid}}
+   *
+   *  @method createEntity
+   *  @public
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.getEntity = function(options, callback) {
+    var entity = new Usergrid.Entity({
+      client: this,
+      data: options
+    });
+    entity.fetch(function(err, response) {
+      doCallback(callback, [err, response, entity], entity);
+    });
+  };
+
+  /*
+   *  Main function for restoring an entity from serialized data.
+   *
+   *  serializedObject should have come from entityObject.serialize();
+   *
+   *  @method restoreEntity
+   *  @public
+   *  @param {string} serializedObject
+   *  @return {object} Entity Object
+   */
+  Usergrid.Client.prototype.restoreEntity = function(serializedObject) {
+    var data = JSON.parse(serializedObject);
+    var options = {
+      client: this,
+      data: data
+    };
+    var entity = new Usergrid.Entity(options);
+    return entity;
+  };
+  /*
+   *  Main function for creating new counters - should be called directly.
+   *
+   *  options object: options {timestamp:0, category:'value', counters:{name : value}}
+   *
+   *  @method createCounter
+   *  @public
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, response, counter)
+   */
+  Usergrid.Client.prototype.createCounter = function(options, callback) {
+    var counter = new Usergrid.Counter({
+      client: this,
+      data: options
+    });
+    counter.save(callback);
+  };
+  /*
+   *  Main function for creating new assets - should be called directly.
+   *
+   *  options object: options {name:"photo.jpg", path:"/user/uploads", "content-type":"image/jpeg", owner:"F01DE600-0000-0000-0000-000000000000", file: FileOrBlobObject }
+   *
+   *  @method createCounter
+   *  @public
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, response, counter)
+   */
+  Usergrid.Client.prototype.createAsset = function(options, callback) {
+    var file=options.file;
+    if(file){
+      options.name=options.name||file.name;
+      options['content-type']=options['content-type']||file.type;
+      options.path=options.path||'/';
+      delete options.file;
+    }
+    var asset = new Usergrid.Asset({
+      client: this,
+      data: options
+    });
+    asset.save(function(err, response, asset){
+      if(file && !err){
+        asset.upload(file, callback);
+      }else{
+        doCallback(callback, [err, response, asset], asset);
+      }
+    });
+  };
+
+  /*
+   *  Main function for creating new collections - should be called directly.
+   *
+   *  options object: options {client:client, type: type, qs:qs}
+   *
+   *  @method createCollection
+   *  @public
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.createCollection = function (options, callback) {
+    options.client = this;
+    return new Usergrid.Collection(options, function(err, data, collection) {
+        console.log("createCollection", arguments);
+        doCallback(callback, [err, collection, data]);
+    });
+  };
+
+  /*
+   *  Main function for restoring a collection from serialized data.
+   *
+   *  serializedObject should have come from collectionObject.serialize();
+   *
+   *  @method restoreCollection
+   *  @public
+   *  @param {string} serializedObject
+   *  @return {object} Collection Object
+   */
+  Usergrid.Client.prototype.restoreCollection = function(serializedObject) {
+    var data = JSON.parse(serializedObject);
+    data.client = this;
+    var collection = new Usergrid.Collection(data);
+    return collection;
+  };
+
+  /*
+   *  Main function for retrieving a user's activity feed.
+   *
+   *  @method getFeedForUser
+   *  @public
+   *  @params {string} username
+   *  @param {function} callback
+   *  @return {callback} callback(err, data, activities)
+   */
+  Usergrid.Client.prototype.getFeedForUser = function(username, callback) {
+    var options = {
+      method: "GET",
+      endpoint: "users/" + username + "/feed"
+    };
+
+    this.request(options, function(err, data) {
+      if (err) {
+        doCallback(callback, [err]);
+      } else {
+        doCallback(callback, [err, data, data.getEntities()]);
+      }
+    });
+  };
+
+  /*
+   *  Function for creating new activities for the current user - should be called directly.
+   *
+   *  //user can be any of the following: "me", a uuid, a username
+   *  Note: the "me" alias will reference the currently logged in user (e.g. 'users/me/activties')
+   *
+   *  //build a json object that looks like this:
+   *  var options =
+   *  {
+   *    "actor" : {
+   *      "displayName" :"myusername",
+   *      "uuid" : "myuserid",
+   *      "username" : "myusername",
+   *      "email" : "myemail",
+   *      "picture": "http://path/to/picture",
+   *      "image" : {
+   *          "duration" : 0,
+   *          "height" : 80,
+   *          "url" : "http://www.gravatar.com/avatar/",
+   *          "width" : 80
+   *      },
+   *    },
+   *    "verb" : "post",
+   *    "content" : "My cool message",
+   *    "lat" : 48.856614,
+   *    "lon" : 2.352222
+   *  }
+   *
+   *  @method createEntity
+   *  @public
+   *  @params {string} user // "me", a uuid, or a username
+   *  @params {object} options
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.createUserActivity = function(user, options, callback) {
+    options.type = 'users/' + user + '/activities';
+    options = {
+      client: this,
+      data: options
+    };
+    var entity = new Usergrid.Entity(options);
+    entity.save(function(err, data) {
+      doCallback(callback, [err, data, entity]);
+    });
+  };
+
+  /*
+   *  Function for creating user activities with an associated user entity.
+   *
+   *  user object:
+   *  The user object passed into this function is an instance of Usergrid.Entity.
+   *
+   *  @method createUserActivityWithEntity
+   *  @public
+   *  @params {object} user
+   *  @params {string} content
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.createUserActivityWithEntity = function(user, content, callback) {
+    var username = user.get("username");
+    var options = {
+      actor: {
+        "displayName": username,
+        "uuid": user.get("uuid"),
+        "username": username,
+        "email": user.get("email"),
+        "picture": user.get("picture"),
+        "image": {
+          "duration": 0,
+          "height": 80,
+          "url": user.get("picture"),
+          "width": 80
+        }
+      },
+      "verb": "post",
+      "content": content
+    };
+
+    this.createUserActivity(username, options, callback);
+
+  };
+
+  /*
+   *  A private method to get call timing of last call
+   */
+  Usergrid.Client.prototype.calcTimeDiff = function() {
+    var seconds = 0;
+    var time = this._end - this._start;
+    try {
+      seconds = ((time / 10) / 60).toFixed(2);
+    } catch (e) {
+      return 0;
+    }
+    return seconds;
+  };
+
+  /*
+   *  A public method to store the OAuth token for later use - uses localstorage if available
+   *
+   *  @method setToken
+   *  @public
+   *  @params {string} token
+   *  @return none
+   */
+  Usergrid.Client.prototype.setToken = function(token) {
+    this.set('token', token);
+  };
+
+  /*
+   *  A public method to get the OAuth token
+   *
+   *  @method getToken
+   *  @public
+   *  @return {string} token
+   */
+  Usergrid.Client.prototype.getToken = function() {
+    return this.get('token');
+  };
+
+  Usergrid.Client.prototype.setObject = function(key, value) {
+    if (value) {
+      value = JSON.stringify(value);
+    }
+    this.set(key, value);
+  };
+
+  Usergrid.Client.prototype.set = function(key, value) {
+    var keyStore = 'apigee_' + key;
+    this[key] = value;
+    if (typeof(Storage) !== "undefined") {
+      if (value) {
+        localStorage.setItem(keyStore, value);
+      } else {
+        localStorage.removeItem(keyStore);
+      }
+    }
+  };
+
+  Usergrid.Client.prototype.getObject = function(key) {
+    return JSON.parse(this.get(key));
+  };
+
+  Usergrid.Client.prototype.get = function(key) {
+    var keyStore = 'apigee_' + key;
+    var value = null;
+    if (this[key]) {
+      value = this[key];
+    } else if (typeof(Storage) !== "undefined") {
+      value = localStorage.getItem(keyStore);
+    }
+    return value;
+  };
+
+  /*
+   * A public facing helper method for signing up users
+   *
+   * @method signup
+   * @public
+   * @params {string} username
+   * @params {string} password
+   * @params {string} email
+   * @params {string} name
+   * @param {function} callback
+   * @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.signup = function(username, password, email, name, callback) {
+    var self = this;
+    var options = {
+      type: "users",
+      username: username,
+      password: password,
+      email: email,
+      name: name
+    };
+
+    this.createEntity(options, callback);
+  };
+
+  /*
+   *
+   *  A public method to log in an app user - stores the token for later use
+   *
+   *  @method login
+   *  @public
+   *  @params {string} username
+   *  @params {string} password
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.login = function(username, password, callback) {
+    var self = this;
+    var options = {
+      method: 'POST',
+      endpoint: 'token',
+      body: {
+        username: username,
+        password: password,
+        grant_type: 'password'
+      }
+    };
+    self.request(options, function(err, response) {
+      var user = {};
+      if (err) {
+        if (self.logging) console.log('error trying to log user in');
+      } else {
+        var options = {
+          client: self,
+          data: response.user
+        };
+        user = new Usergrid.Entity(options);
+        self.setToken(response.access_token);
+      }
+      doCallback(callback, [err, response, user]);
+    });
+  };
+  
+  Usergrid.Client.prototype.adminlogin = function(username, password, callback) {
+    var self = this;
+    var options = {
+        method: "POST",
+        endpoint:'management/token',
+        body: {
+            username: username,
+            password: password,
+            grant_type: "password"
+        },
+        mQuery:true
+    };
+    self.request(options, function(err, response) {
+        var user = {};
+        if (err) {
+            if (self.logging) console.log("error trying to log adminuser in");
+        } else {
+            var options = {
+                client: self,
+                data: response.user
+            };
+            user = new Usergrid.Entity(options);
+            self.setToken(response.access_token);
+        }
+        doCallback(callback, [ err, response, user ]);
+    });
+  };
+
+  Usergrid.Client.prototype.reAuthenticateLite = function(callback) {
+    var self = this;
+    var options = {
+      method: 'GET',
+      endpoint: 'management/me',
+      mQuery: true
+    };
+    this.request(options, function(err, response) {
+      if (err && self.logging) {
+        console.log('error trying to re-authenticate user');
+      } else {
+
+        //save the re-authed token and current email/username
+        self.setToken(response.data.access_token);
+
+      }
+      doCallback(callback, [err]);
+
+    });
+  };
+
+
+  Usergrid.Client.prototype.reAuthenticate = function(email, callback) {
+    var self = this;
+    var options = {
+      method: 'GET',
+      endpoint: 'management/users/' + email,
+      mQuery: true
+    };
+    this.request(options, function(err, response) {
+      var organizations = {};
+      var applications = {};
+      var user = {};
+      var data;
+      if (err && self.logging) {
+        console.log('error trying to full authenticate user');
+      } else {
+        data = response.data;
+        self.setToken(data.token);
+        self.set('email', data.email);
+
+        //delete next block and corresponding function when iframes are refactored
+        localStorage.setItem('accessToken', data.token);
+        localStorage.setItem('userUUID', data.uuid);
+        localStorage.setItem('userEmail', data.email);
+        //end delete block
+
+
+        var userData = {
+          "username": data.username,
+          "email": data.email,
+          "name": data.name,
+          "uuid": data.uuid
+        };
+        var options = {
+          client: self,
+          data: userData
+        };
+        user = new Usergrid.Entity(options);
+
+        organizations = data.organizations;
+        var org = '';
+        try {
+          //if we have an org stored, then use that one. Otherwise, use the first one.
+          var existingOrg = self.get('orgName');
+          org = (organizations[existingOrg]) ? organizations[existingOrg] : organizations[Object.keys(organizations)[0]];
+          self.set('orgName', org.name);
+        } catch (e) {
+          err = true;
+          if (self.logging) {
+            console.log('error selecting org');
+          }
+        } //should always be an org
+
+        applications = self.parseApplicationsArray(org);
+        self.selectFirstApp(applications);
+
+        self.setObject('organizations', organizations);
+        self.setObject('applications', applications);
+
+      }
+      doCallback(callback, [err, data, user, organizations, applications], self);
+    });
+  };
+
+  /*
+   *  A public method to log in an app user with facebook - stores the token for later use
+   *
+   *  @method loginFacebook
+   *  @public
+   *  @params {string} username
+   *  @params {string} password
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.loginFacebook = function(facebookToken, callback) {
+    var self = this;
+    var options = {
+      method: 'GET',
+      endpoint: 'auth/facebook',
+      qs: {
+        fb_access_token: facebookToken
+      }
+    };
+    this.request(options, function(err, data) {
+      var user = {};
+      if (err && self.logging) {
+        console.log('error trying to log user in');
+      } else {
+        var options = {
+          client: self,
+          data: data.user
+        };
+        user = new Usergrid.Entity(options);
+        self.setToken(data.access_token);
+      }
+      doCallback(callback, [err, data, user], self);
+    });
+  };
+
+  /*
+   *  A public method to get the currently logged in user entity
+   *
+   *  @method getLoggedInUser
+   *  @public
+   *  @param {function} callback
+   *  @return {callback} callback(err, data)
+   */
+  Usergrid.Client.prototype.getLoggedInUser = function(callback) {
+    var self = this;
+    if (!this.getToken()) {
+        doCallback(callback, [new UsergridError("Access Token not set"), null, self], self);
+    } else {
+      var options = {
+        method: 'GET',
+        endpoint: 'users/me'
+      };
+      this.request(options, function(err, response) {
+        if (err) {
+          if (self.logging) {
+            console.log('error trying to log user in');
+          }
+          console.error(err, response);
+          doCallback(callback, [err, response, self], self);
+        } else {
+          var options = {
+            client: self,
+            data: response.getEntity()
+          };
+          var user = new Usergrid.Entity(options);
+          doCallback(callback, [null, response, user], self);
+        }
+      });
+    }
+  };
+
+  /*
+   *  A public method to test if a user is logged in - does not guarantee that the token is still valid,
+   *  but rather that one exists
+   *
+   *  @method isLoggedIn
+   *  @public
+   *  @return {boolean} Returns true the user is logged in (has token and uuid), false if not
+   */
+  Usergrid.Client.prototype.isLoggedIn = function() {
+    var token = this.getToken();
+    return "undefined" !== typeof token && token !== null;
+  };
+
+  /*
+   *  A public method to log out an app user - clears all user fields from client
+   *
+   *  @method logout
+   *  @public
+   *  @return none
+   */
+  Usergrid.Client.prototype.logout = function() {
+    this.setToken();
+  };
+
+  /*
+   *  A public method to destroy access tokens on the server
+   *
+   *  @method logout
+   *  @public
+   *  @param {string} username	the user associated with the token to revoke
+   *  @param {string} token set to 'null' to revoke the token of the currently logged in user
+   *    or set to token value to revoke a specific token
+   *  @param {string} revokeAll set to 'true' to revoke all tokens for the user
+   *  @return none
+   */
+  Usergrid.Client.prototype.destroyToken = function(username, token, revokeAll, callback) {
+    var options = {
+      client: self,
+      method: 'PUT',
+    };
+
+    if (revokeAll === true) {
+      options.endpoint = 'users/' + username + '/revoketokens';
+    } else if (token === null) {
+      options.endpoint = 'users/' + username + '/revoketoken?token=' + this.getToken();
+    } else {
+      options.endpoint = 'users/' + username + '/revoketoken?token=' + token;
+    }
+    this.request(options, function(err, data) {
+      if (err) {
+        if (self.logging) {
+          console.log('error destroying access token');
+        }
+        doCallback(callback, [err, data, null], self);
+      } else {
+        if (revokeAll === true) {
+          console.log('all user tokens invalidated');
+        } else {
+          console.log('token invalidated');
+        }
+        doCallback(callback, [err, data, null], self);
+      }
+    });
+  };
+
+  /*
+   *  A public method to log out an app user - clears all user fields from client
+   *  and destroys the access token on the server.
+   *
+   *  @method logout
+   *  @public
+   *  @param {string} username the user associated with the token to revoke
+   *  @param {string} token set to 'null' to revoke the token of the currently logged in user
+   *   or set to token value to revoke a specific token
+   *  @param {string} revokeAll set to 'true' to revoke all tokens for the user
+   *  @return none
+   */
+  Usergrid.Client.prototype.logoutAndDestroyToken = function(username, token, revokeAll, callback) {
+    if (username === null) {
+      console.log('username required to revoke tokens');
+    } else {
+      this.destroyToken(username, token, revokeAll, callback);
+      if (revokeAll === true || token === this.getToken() || token === null) {
+        this.setToken(null);
+      }
+    }
+  };
+
+  /*
+   *  A private method to build the curl call to display on the command line
+   *
+   *  @method buildCurlCall
+   *  @private
+   *  @param {object} options
+   *  @return {string} curl
+   */
+  Usergrid.Client.prototype.buildCurlCall = function(options) {
+    var curl = ['curl'];
+    var method = (options.method || 'GET').toUpperCase();
+    var body = options.body;
+    var uri = options.uri;
+
+    //curl - add the method to the command (no need to add anything for GET)
+    curl.push('-X');
+    curl.push((['POST', 'PUT', 'DELETE'].indexOf(method) >= 0) ? method : 'GET');
+
+    //curl - append the path
+    curl.push(uri);
+
+    if ("object" === typeof body && Object.keys(body).length > 0 && ['POST', 'PUT'].indexOf(method) !== -1) {
+      curl.push('-d');
+      curl.push("'" + JSON.stringify(body) + "'");
+    }
+    curl = curl.join(' ');
+    //log the curl command to the console
+    console.log(curl);
+
+    return curl;
+  };
+
+  Usergrid.Client.prototype.getDisplayImage = function(email, picture, size) {
+    size = size || 50;
+    var image = 'https://apigee.com/usergrid/images/user_profile.png';
+    try {
+      if (picture) {
+        image = picture;
+      } else if (email.length) {
+        image = 'https://secure.gravatar.com/avatar/' + MD5(email) + '?s=' + size + encodeURI("&d=https://apigee.com/usergrid/images/user_profile.png");
+      }
+    } catch (e) {
+      //TODO do something with this error?
+    } finally {
+      return image;
+    }
+  };
+  global[name] = Usergrid.Client;
+  global[name].noConflict = function() {
+    if (overwrittenName) {
+      global[name] = overwrittenName;
+    }
+    return exports;
+  };
+  return global[name];
+}());

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Collection.js
----------------------------------------------------------------------
diff --git a/lib/modules/Collection.js b/lib/modules/Collection.js
new file mode 100644
index 0000000..d137125
--- /dev/null
+++ b/lib/modules/Collection.js
@@ -0,0 +1,483 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+
+/*
+ *  The Collection class models Usergrid Collections.  It essentially
+ *  acts as a container for holding Entity objects, while providing
+ *  additional funcitonality such as paging, and saving
+ *
+ *  @constructor
+ *  @param {string} options - configuration object
+ *  @return {Collection} collection
+ */
+Usergrid.Collection = function(options) {
+
+  if (options) {
+    this._client = options.client;
+    this._type = options.type;
+    this.qs = options.qs || {};
+
+    //iteration
+    this._list = options.list || [];
+    this._iterator = options.iterator || -1; //first thing we do is increment, so set to -1
+
+    //paging
+    this._previous = options.previous || [];
+    this._next = options.next || null;
+    this._cursor = options.cursor || null;
+
+    //restore entities if available
+    if (options.list) {
+      var count = options.list.length;
+      for (var i = 0; i < count; i++) {
+        //make new entity with
+        var entity = this._client.restoreEntity(options.list[i]);
+        this._list[i] = entity;
+      }
+    }
+  }
+  /*if (callback) {
+    //populate the collection
+    this.fetch(callback);
+  }*/
+
+};
+
+
+/*
+ *  method to determine whether or not the passed variable is a Usergrid Collection
+ *
+ *  @method isCollection
+ *  @public
+ *  @params {any} obj - any variable
+ *  @return {boolean} Returns true or false
+ */
+Usergrid.isCollection = function(obj) {
+  return (obj && obj instanceof Usergrid.Collection);
+};
+
+
+/*
+ *  gets the data from the collection object for serialization
+ *
+ *  @method serialize
+ *  @return {object} data
+ */
+Usergrid.Collection.prototype.serialize = function() {
+
+  //pull out the state from this object and return it
+  var data = {};
+  data.type = this._type;
+  data.qs = this.qs;
+  data.iterator = this._iterator;
+  data.previous = this._previous;
+  data.next = this._next;
+  data.cursor = this._cursor;
+
+  this.resetEntityPointer();
+  var i = 0;
+  data.list = [];
+  while (this.hasNextEntity()) {
+    var entity = this.getNextEntity();
+    data.list[i] = entity.serialize();
+    i++;
+  }
+
+  data = JSON.stringify(data);
+  return data;
+};
+//addCollection is deprecated?
+/*Usergrid.Collection.prototype.addCollection = function (collectionName, options, callback) {
+  self = this;
+  options.client = this._client;
+  var collection = new Usergrid.Collection(options, function(err, data) {
+    if (typeof(callback) === 'function') {
+
+      collection.resetEntityPointer();
+      while(collection.hasNextEntity()) {
+        var user = collection.getNextEntity();
+        var email = user.get('email');
+        var image = self._client.getDisplayImage(user.get('email'), user.get('picture'));
+        user._portal_image_icon = image;
+      }
+
+      self[collectionName] = collection;
+      doCallback(callback, [err, collection], self);
+    }
+  });
+};*/
+
+/*
+ *  Populates the collection from the server
+ *
+ *  @method fetch
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ */
+Usergrid.Collection.prototype.fetch = function(callback) {
+  var self = this;
+  var qs = this.qs;
+
+  //add in the cursor if one is available
+  if (this._cursor) {
+    qs.cursor = this._cursor;
+  } else {
+    delete qs.cursor;
+  }
+  var options = {
+    method: 'GET',
+    endpoint: this._type,
+    qs: this.qs
+  };
+  this._client.request(options, function(err, response) {
+    if (err && self._client.logging) {
+      console.log('error getting collection');
+    } else {
+      //save the cursor if there is one
+      self.saveCursor(response.cursor || null);
+      self.resetEntityPointer();
+      //save entities locally
+      self._list = response.getEntities()
+        .filter(function(entity) {
+          return isUUID(entity.uuid);
+        })
+        .map(function(entity) {
+          var ent = new Usergrid.Entity({
+            client: self._client
+          });
+          ent.set(entity);
+          ent.type = self._type;
+          //ent._json = JSON.stringify(entity, null, 2);
+          return ent;
+        });
+    }
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+/*
+ *  Adds a new Entity to the collection (saves, then adds to the local object)
+ *
+ *  @method addNewEntity
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data, entity)
+ */
+Usergrid.Collection.prototype.addEntity = function(entityObject, callback) {
+  var self = this;
+  entityObject.type = this._type;
+
+  //create the new entity
+  this._client.createEntity(entityObject, function(err, response, entity) {
+    if (!err) {
+      //then add the entity to the list
+      self.addExistingEntity(entity);
+    }
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+Usergrid.Collection.prototype.addExistingEntity = function(entity) {
+  //entity should already exist in the db, so just add it to the list
+  var count = this._list.length;
+  this._list[count] = entity;
+};
+
+/*
+ *  Removes the Entity from the collection, then destroys the object on the server
+ *
+ *  @method destroyEntity
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ */
+Usergrid.Collection.prototype.destroyEntity = function(entity, callback) {
+  var self = this;
+  entity.destroy(function(err, response) {
+    if (err) {
+      if (self._client.logging) {
+        console.log('could not destroy entity');
+      }
+      doCallback(callback, [err, response, self], self);
+    } else {
+      //destroy was good, so repopulate the collection
+      self.fetch(callback);
+    }
+    //remove entity from the local store
+    self.removeEntity(entity);
+  });
+};
+
+/*
+ * Filters the list of entities based on the supplied criteria function
+ * works like Array.prototype.filter
+ *
+ *  @method getEntitiesByCriteria
+ *  @param {function} criteria  A function that takes each entity as an argument and returns true or false
+ *  @return {Entity[]} returns a list of entities that caused the criteria function to return true
+ */
+Usergrid.Collection.prototype.getEntitiesByCriteria = function(criteria) {
+  return this._list.filter(criteria);
+};
+/*
+ * Returns the first entity from the list of entities based on the supplied criteria function
+ * works like Array.prototype.filter
+ *
+ *  @method getEntitiesByCriteria
+ *  @param {function} criteria  A function that takes each entity as an argument and returns true or false
+ *  @return {Entity[]} returns a list of entities that caused the criteria function to return true
+ */
+Usergrid.Collection.prototype.getEntityByCriteria = function(criteria) {
+  return this.getEntitiesByCriteria(criteria).shift();
+};
+/*
+ * Removed an entity from the collection without destroying it on the server
+ *
+ *  @method removeEntity
+ *  @param {object} entity
+ *  @return {Entity} returns the removed entity or undefined if it was not found
+ */
+Usergrid.Collection.prototype.removeEntity = function(entity) {
+  var removedEntity = this.getEntityByCriteria(function(item) {
+    return entity.uuid === item.get('uuid');
+  });
+  delete this._list[this._list.indexOf(removedEntity)];
+  return removedEntity;
+};
+
+/*
+ *  Looks up an Entity by UUID
+ *
+ *  @method getEntityByUUID
+ *  @param {string} UUID
+ *  @param {function} callback
+ *  @return {callback} callback(err, data, entity)
+ */
+Usergrid.Collection.prototype.getEntityByUUID = function(uuid, callback) {
+  var entity = this.getEntityByCriteria(function(item) {
+    return item.get('uuid') === uuid;
+  });
+  if (entity) {
+    doCallback(callback, [null, entity, entity], this);
+  } else {
+    //get the entity from the database
+    var options = {
+      data: {
+        type: this._type,
+        uuid: uuid
+      },
+      client: this._client
+    };
+    entity = new Usergrid.Entity(options);
+    entity.fetch(callback);
+  }
+};
+
+/*
+ *  Returns the first Entity of the Entity list - does not affect the iterator
+ *
+ *  @method getFirstEntity
+ *  @return {object} returns an entity object
+ */
+Usergrid.Collection.prototype.getFirstEntity = function() {
+  var count = this._list.length;
+  if (count > 0) {
+    return this._list[0];
+  }
+  return null;
+};
+
+/*
+ *  Returns the last Entity of the Entity list - does not affect the iterator
+ *
+ *  @method getLastEntity
+ *  @return {object} returns an entity object
+ */
+Usergrid.Collection.prototype.getLastEntity = function() {
+  var count = this._list.length;
+  if (count > 0) {
+    return this._list[count - 1];
+  }
+  return null;
+};
+
+/*
+ *  Entity iteration -Checks to see if there is a "next" entity
+ *  in the list.  The first time this method is called on an entity
+ *  list, or after the resetEntityPointer method is called, it will
+ *  return true referencing the first entity in the list
+ *
+ *  @method hasNextEntity
+ *  @return {boolean} true if there is a next entity, false if not
+ */
+Usergrid.Collection.prototype.hasNextEntity = function() {
+  var next = this._iterator + 1;
+  var hasNextElement = (next >= 0 && next < this._list.length);
+  if (hasNextElement) {
+    return true;
+  }
+  return false;
+};
+
+/*
+ *  Entity iteration - Gets the "next" entity in the list.  The first
+ *  time this method is called on an entity list, or after the method
+ *  resetEntityPointer is called, it will return the,
+ *  first entity in the list
+ *
+ *  @method hasNextEntity
+ *  @return {object} entity
+ */
+Usergrid.Collection.prototype.getNextEntity = function() {
+  this._iterator++;
+  var hasNextElement = (this._iterator >= 0 && this._iterator <= this._list.length);
+  if (hasNextElement) {
+    return this._list[this._iterator];
+  }
+  return false;
+};
+
+/*
+ *  Entity iteration - Checks to see if there is a "previous"
+ *  entity in the list.
+ *
+ *  @method hasPrevEntity
+ *  @return {boolean} true if there is a previous entity, false if not
+ */
+Usergrid.Collection.prototype.hasPrevEntity = function() {
+  var previous = this._iterator - 1;
+  var hasPreviousElement = (previous >= 0 && previous < this._list.length);
+  if (hasPreviousElement) {
+    return true;
+  }
+  return false;
+};
+
+/*
+ *  Entity iteration - Gets the "previous" entity in the list.
+ *
+ *  @method getPrevEntity
+ *  @return {object} entity
+ */
+Usergrid.Collection.prototype.getPrevEntity = function() {
+  this._iterator--;
+  var hasPreviousElement = (this._iterator >= 0 && this._iterator <= this._list.length);
+  if (hasPreviousElement) {
+    return this._list[this._iterator];
+  }
+  return false;
+};
+
+/*
+ *  Entity iteration - Resets the iterator back to the beginning
+ *  of the list
+ *
+ *  @method resetEntityPointer
+ *  @return none
+ */
+Usergrid.Collection.prototype.resetEntityPointer = function() {
+  this._iterator = -1;
+};
+
+/*
+ * Method to save off the cursor just returned by the last API call
+ *
+ * @public
+ * @method saveCursor
+ * @return none
+ */
+Usergrid.Collection.prototype.saveCursor = function(cursor) {
+  //if current cursor is different, grab it for next cursor
+  if (this._next !== cursor) {
+    this._next = cursor;
+  }
+};
+
+/*
+ * Resets the paging pointer (back to original page)
+ *
+ * @public
+ * @method resetPaging
+ * @return none
+ */
+Usergrid.Collection.prototype.resetPaging = function() {
+  this._previous = [];
+  this._next = null;
+  this._cursor = null;
+};
+
+/*
+ *  Paging -  checks to see if there is a next page od data
+ *
+ *  @method hasNextPage
+ *  @return {boolean} returns true if there is a next page of data, false otherwise
+ */
+Usergrid.Collection.prototype.hasNextPage = function() {
+  return (this._next);
+};
+
+/*
+ *  Paging - advances the cursor and gets the next
+ *  page of data from the API.  Stores returned entities
+ *  in the Entity list.
+ *
+ *  @method getNextPage
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ */
+Usergrid.Collection.prototype.getNextPage = function(callback) {
+  if (this.hasNextPage()) {
+    //set the cursor to the next page of data
+    this._previous.push(this._cursor);
+    this._cursor = this._next;
+    //empty the list
+    this._list = [];
+    this.fetch(callback);
+  }
+};
+
+/*
+ *  Paging -  checks to see if there is a previous page od data
+ *
+ *  @method hasPreviousPage
+ *  @return {boolean} returns true if there is a previous page of data, false otherwise
+ */
+Usergrid.Collection.prototype.hasPreviousPage = function() {
+  return (this._previous.length > 0);
+};
+
+/*
+ *  Paging - reverts the cursor and gets the previous
+ *  page of data from the API.  Stores returned entities
+ *  in the Entity list.
+ *
+ *  @method getPreviousPage
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ */
+Usergrid.Collection.prototype.getPreviousPage = function(callback) {
+  if (this.hasPreviousPage()) {
+    this._next = null; //clear out next so the comparison will find the next item
+    this._cursor = this._previous.pop();
+    //empty the list
+    this._list = [];
+    this.fetch(callback);
+  }
+};

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Counter.js
----------------------------------------------------------------------
diff --git a/lib/modules/Counter.js b/lib/modules/Counter.js
new file mode 100644
index 0000000..7b120cc
--- /dev/null
+++ b/lib/modules/Counter.js
@@ -0,0 +1,196 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+
+/*
+ *  A class to model a Usergrid event.
+ *
+ *  @constructor
+ *  @param {object} options {timestamp:0, category:'value', counters:{name : value}}
+ *  @returns {callback} callback(err, event)
+ */
+Usergrid.Counter = function(options) {
+  // var self=this;
+  this._client = options.client;
+  this._data = options.data || {};
+  this._data.category = options.category || "UNKNOWN";
+  this._data.timestamp = options.timestamp || 0;
+  this._data.type = "events";
+  this._data.counters = options.counters || {};
+  // doCallback(callback, [false, this], this);
+  //this.save(callback);
+};
+var COUNTER_RESOLUTIONS = [
+  'all', 'minute', 'five_minutes', 'half_hour',
+  'hour', 'six_day', 'day', 'week', 'month'
+];
+/*
+ *  Inherit from Usergrid.Entity.
+ *  Note: This only accounts for data on the group object itself.
+ *  You need to use add and remove to manipulate group membership.
+ */
+Usergrid.Counter.prototype = new Usergrid.Entity();
+
+/*
+ * overrides Entity.prototype.fetch. Returns all data for counters
+ * associated with the object as specified in the constructor
+ *
+ * @public
+ * @method increment
+ * @param {function} callback
+ * @returns {callback} callback(err, event)
+ */
+Usergrid.Counter.prototype.fetch = function(callback) {
+  this.getData({}, callback);
+};
+/*
+ * increments the counter for a specific event
+ *
+ * options object: {name: counter_name}
+ *
+ * @public
+ * @method increment
+ * @params {object} options
+ * @param {function} callback
+ * @returns {callback} callback(err, event)
+ */
+Usergrid.Counter.prototype.increment = function(options, callback) {
+  var self = this,
+    name = options.name,
+    value = options.value;
+  if (!name) {
+    return doCallback(callback, [new UsergridInvalidArgumentError("'name' for increment, decrement must be a number"), null, self], self);
+  } else if (isNaN(value)) {
+    return doCallback(callback, [new UsergridInvalidArgumentError("'value' for increment, decrement must be a number"), null, self], self);
+  } else {
+    self._data.counters[name] = (parseInt(value)) || 1;
+    return self.save(callback);
+  }
+};
+/*
+ * decrements the counter for a specific event
+ *
+ * options object: {name: counter_name}
+ *
+ * @public
+ * @method decrement
+ * @params {object} options
+ * @param {function} callback
+ * @returns {callback} callback(err, event)
+ */
+
+Usergrid.Counter.prototype.decrement = function(options, callback) {
+  var self = this,
+    name = options.name,
+    value = options.value;
+  self.increment({
+    name: name,
+    value: -((parseInt(value)) || 1)
+  }, callback);
+};
+/*
+ * resets the counter for a specific event
+ *
+ * options object: {name: counter_name}
+ *
+ * @public
+ * @method reset
+ * @params {object} options
+ * @param {function} callback
+ * @returns {callback} callback(err, event)
+ */
+
+Usergrid.Counter.prototype.reset = function(options, callback) {
+  var self = this,
+    name = options.name;
+  self.increment({
+    name: name,
+    value: 0
+  }, callback);
+};
+
+/*
+ * gets data for one or more counters over a given
+ * time period at a specified resolution
+ *
+ * options object: {
+ *                   counters: ['counter1', 'counter2', ...],
+ *                   start: epoch timestamp or ISO date string,
+ *                   end: epoch timestamp or ISO date string,
+ *                   resolution: one of ('all', 'minute', 'five_minutes', 'half_hour', 'hour', 'six_day', 'day', 'week', or 'month')
+ *                   }
+ *
+ * @public
+ * @method getData
+ * @params {object} options
+ * @param {function} callback
+ * @returns {callback} callback(err, event)
+ */
+Usergrid.Counter.prototype.getData = function(options, callback) {
+  var start_time,
+    end_time,
+    start = options.start || 0,
+    end = options.end || Date.now(),
+    resolution = (options.resolution || 'all').toLowerCase(),
+    counters = options.counters || Object.keys(this._data.counters),
+    res = (resolution || 'all').toLowerCase();
+  if (COUNTER_RESOLUTIONS.indexOf(res) === -1) {
+    res = 'all';
+  }
+  start_time = getSafeTime(start);
+  end_time = getSafeTime(end);
+  var self = this;
+  //https://api.usergrid.com/yourorgname/sandbox/counters?counter=test_counter
+  var params = Object.keys(counters).map(function(counter) {
+    return ["counter", encodeURIComponent(counters[counter])].join('=');
+  });
+  params.push('resolution=' + res);
+  params.push('start_time=' + String(start_time));
+  params.push('end_time=' + String(end_time));
+
+  var endpoint = "counters?" + params.join('&');
+  this._client.request({
+    endpoint: endpoint
+  }, function(err, data) {
+    if (data.counters && data.counters.length) {
+      data.counters.forEach(function(counter) {
+        self._data.counters[counter.name] = counter.value || counter.values;
+      });
+    }
+    return doCallback(callback, [err, data, self], self);
+  });
+};
+
+function getSafeTime(prop) {
+  var time;
+  switch (typeof prop) {
+    case "undefined":
+      time = Date.now();
+      break;
+    case "number":
+      time = prop;
+      break;
+    case "string":
+      time = (isNaN(prop)) ? Date.parse(prop) : parseInt(prop);
+      break;
+    default:
+      time = Date.parse(prop.toString());
+  }
+  return time;
+}

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Entity.js
----------------------------------------------------------------------
diff --git a/lib/modules/Entity.js b/lib/modules/Entity.js
new file mode 100644
index 0000000..029951c
--- /dev/null
+++ b/lib/modules/Entity.js
@@ -0,0 +1,767 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+
+var ENTITY_SYSTEM_PROPERTIES = ['metadata', 'created', 'modified', 'oldpassword', 'newpassword', 'type', 'activated', 'uuid'];
+
+/*
+ *  A class to Model a Usergrid Entity.
+ *  Set the type and uuid of entity in the 'data' json object
+ *
+ *  @constructor
+ *  @param {object} options {client:client, data:{'type':'collection_type', uuid:'uuid', 'key':'value'}}
+ */
+Usergrid.Entity = function(options) {
+  this._data={};
+  this._client=undefined;
+  if (options) {
+    //this._data = options.data || {};
+    this.set(options.data || {});
+    this._client = options.client || {};
+  }
+};
+
+/*
+ *  method to determine whether or not the passed variable is a Usergrid Entity
+ *
+ *  @method isEntity
+ *  @public
+ *  @params {any} obj - any variable
+ *  @return {boolean} Returns true or false
+ */
+Usergrid.Entity.isEntity = function(obj) {
+  return (obj && obj instanceof Usergrid.Entity);
+};
+
+/*
+ *  method to determine whether or not the passed variable is a Usergrid Entity
+ *  That has been saved.
+ *
+ *  @method isPersistedEntity
+ *  @public
+ *  @params {any} obj - any variable
+ *  @return {boolean} Returns true or false
+ */
+Usergrid.Entity.isPersistedEntity = function(obj) {
+  return (isEntity(obj) && isUUID(obj.get('uuid')));
+};
+
+/*
+ *  returns a serialized version of the entity object
+ *
+ *  Note: use the client.restoreEntity() function to restore
+ *
+ *  @method serialize
+ *  @return {string} data
+ */
+Usergrid.Entity.prototype.serialize = function() {
+  return JSON.stringify(this._data);
+};
+
+/*
+ *  gets a specific field or the entire data object. If null or no argument
+ *  passed, will return all data, else, will return a specific field
+ *
+ *  @method get
+ *  @param {string} field
+ *  @return {string} || {object} data
+ */
+Usergrid.Entity.prototype.get = function(key) {
+  var value;
+  if (arguments.length === 0) {
+    value = this._data;
+  } else if (arguments.length > 1) {
+    key = [].slice.call(arguments).reduce(function(p, c, i, a) {
+      if (c instanceof Array) {
+        p = p.concat(c);
+      } else {
+        p.push(c);
+      }
+      return p;
+    }, []);
+  }
+  if (key instanceof Array) {
+    var self = this;
+    value = key.map(function(k) {
+      return self.get(k);
+    });
+  } else if ("undefined" !== typeof key) {
+    value = this._data[key];
+  }
+  return value;
+};
+/*
+ *  adds a specific key value pair or object to the Entity's data
+ *  is additive - will not overwrite existing values unless they
+ *  are explicitly specified
+ *
+ *  @method set
+ *  @param {string} key || {object}
+ *  @param {string} value
+ *  @return none
+ */
+Usergrid.Entity.prototype.set = function(key, value) {
+  if (typeof key === 'object') {
+    for (var field in key) {
+      this._data[field] = key[field];
+    }
+  } else if (typeof key === 'string') {
+    if (value === null) {
+      delete this._data[key];
+    } else {
+      this._data[key] = value;
+    }
+  } else {
+    this._data = {};
+  }
+};
+
+Usergrid.Entity.prototype.getEndpoint = function() {
+  var type = this.get('type'),
+    nameProperties = ['uuid', 'name'],
+    name;
+  if (type === undefined) {
+    throw new UsergridError('cannot fetch entity, no entity type specified', 'no_type_specified');
+  } else if (/^users?$/.test(type)) {
+    nameProperties.unshift('username');
+  }
+  name = this.get(nameProperties)
+    .filter(function(x) {
+      return (x !== null && "undefined" !== typeof x);
+    })
+    .shift();
+  return (name) ? [type, name].join('/') : type;
+};
+/*
+ *  Saves the entity back to the database
+ *
+ *  @method save
+ *  @public
+ *  @param {function} callback
+ *  @return {callback} callback(err, response, self)
+ */
+Usergrid.Entity.prototype.save = function(callback) {
+  var self = this,
+    type = this.get('type'),
+    method = 'POST',
+    entityId = this.get("uuid"),
+    changePassword,
+    entityData = this.get(),
+    options = {
+      method: method,
+      endpoint: type
+    };
+  //update the entity if the UUID is present
+  if (entityId) {
+    options.method = 'PUT';
+    options.endpoint += '/' + entityId;
+  }
+  //remove system-specific properties
+  options.body = Object.keys(entityData)
+    .filter(function(key) {
+      return (ENTITY_SYSTEM_PROPERTIES.indexOf(key) === -1);
+    })
+    .reduce(function(data, key) {
+      data[key] = entityData[key];
+      return data;
+    }, {});
+  self._client.request(options, function(err, response) {
+    var entity = response.getEntity();
+    if (entity) {
+      self.set(entity);
+      self.set('type', (/^\//.test(response.path)) ? response.path.substring(1) : response.path);
+    }
+    if (err && self._client.logging) {
+      console.log('could not save entity');
+    }
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+/*
+ *
+ * Updates the user's password
+ */
+Usergrid.Entity.prototype.changePassword = function(oldpassword, newpassword, callback) {
+  //Note: we have a ticket in to change PUT calls to /users to accept the password change
+  //      once that is done, we will remove this call and merge it all into one
+  var self = this;
+  if ("function" === typeof oldpassword && callback === undefined) {
+    callback = oldpassword;
+    oldpassword = self.get("oldpassword");
+    newpassword = self.get("newpassword");
+  }
+  //clear out pw info if present
+  self.set({
+    'password': null,
+    'oldpassword': null,
+    'newpassword': null
+  });
+  if ((/^users?$/.test(self.get('type'))) && oldpassword && newpassword) {
+    var options = {
+      method: 'PUT',
+      endpoint: 'users/' + self.get("uuid") + '/password',
+      body: {
+        uuid: self.get("uuid"),
+        username: self.get("username"),
+        oldpassword: oldpassword,
+        newpassword: newpassword
+      }
+    };
+    self._client.request(options, function(err, response) {
+      if (err && self._client.logging) {
+        console.log('could not update user');
+      }
+      //remove old and new password fields so they don't end up as part of the entity object
+      doCallback(callback, [err, response, self], self);
+    });
+  } else {
+    throw new UsergridInvalidArgumentError("Invalid arguments passed to 'changePassword'");
+  }
+};
+/*
+ *  refreshes the entity by making a GET call back to the database
+ *
+ *  @method fetch
+ *  @public
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ */
+Usergrid.Entity.prototype.fetch = function(callback) {
+  var endpoint, self = this;
+  endpoint = this.getEndpoint();
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, response) {
+    var entity = response.getEntity();
+    if (entity) {
+      self.set(entity);
+    }
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+/*
+ *  deletes the entity from the database - will only delete
+ *  if the object has a valid uuid
+ *
+ *  @method destroy
+ *  @public
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ *
+ */
+Usergrid.Entity.prototype.destroy = function(callback) {
+  var self = this;
+  var endpoint = this.getEndpoint();
+
+  var options = {
+    method: 'DELETE',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, response) {
+    if (!err) {
+      self.set(null);
+    }
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+/*
+ *  connects one entity to another
+ *
+ *  @method connect
+ *  @public
+ *  @param {string} connection
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ *
+ */
+Usergrid.Entity.prototype.connect = function(connection, entity, callback) {
+  this.addOrRemoveConnection("POST", connection, entity, callback);
+};
+
+/*
+ *  disconnects one entity from another
+ *
+ *  @method disconnect
+ *  @public
+ *  @param {string} connection
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ *
+ */
+Usergrid.Entity.prototype.disconnect = function(connection, entity, callback) {
+  this.addOrRemoveConnection("DELETE", connection, entity, callback);
+};
+/*
+ *  adds or removes a connection between two entities
+ *
+ *  @method addOrRemoveConnection
+ *  @public
+ *  @param {string} method
+ *  @param {string} connection
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ *
+ */
+Usergrid.Entity.prototype.addOrRemoveConnection = function(method, connection, entity, callback) {
+  var self = this;
+  if (['POST', 'DELETE'].indexOf(method.toUpperCase()) == -1) {
+    throw new UsergridInvalidArgumentError("invalid method for connection call. must be 'POST' or 'DELETE'");
+  }
+  //connectee info
+  var connecteeType = entity.get('type');
+  var connectee = this.getEntityId(entity);
+  if (!connectee) {
+    throw new UsergridInvalidArgumentError("connectee could not be identified");
+  }
+
+  //connector info
+  var connectorType = this.get('type');
+  var connector = this.getEntityId(this);
+  if (!connector) {
+    throw new UsergridInvalidArgumentError("connector could not be identified");
+  }
+
+  var endpoint = [connectorType, connector, connection, connecteeType, connectee].join('/');
+  var options = {
+    method: method,
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, response) {
+    if (err && self._client.logging) {
+      console.log('There was an error with the connection call');
+    }
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+/*
+ *  returns a unique identifier for an entity
+ *
+ *  @method connect
+ *  @public
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data)
+ *
+ */
+Usergrid.Entity.prototype.getEntityId = function(entity) {
+    var id;
+    if (isUUID(entity.get("uuid"))) {
+        id = entity.get("uuid");
+    } else if (this.get("type") === "users" || this.get("type") === "user") {
+        id = entity.get("username");
+    } else  {
+        id = entity.get("name");
+    }
+    return id;
+};
+
+/*
+ *  gets an entities connections
+ *
+ *  @method getConnections
+ *  @public
+ *  @param {string} connection
+ *  @param {object} entity
+ *  @param {function} callback
+ *  @return {callback} callback(err, data, connections)
+ *
+ */
+Usergrid.Entity.prototype.getConnections = function(connection, callback) {
+
+  var self = this;
+
+  //connector info
+  var connectorType = this.get('type');
+  var connector = this.getEntityId(this);
+  if (!connector) {
+    if (typeof(callback) === 'function') {
+      var error = 'Error in getConnections - no uuid specified.';
+      if (self._client.logging) {
+        console.log(error);
+      }
+      doCallback(callback, [true, error], self);
+    }
+    return;
+  }
+
+  var endpoint = connectorType + '/' + connector + '/' + connection + '/';
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, data) {
+    if (err && self._client.logging) {
+      console.log('entity could not be connected');
+    }
+
+    self[connection] = {};
+
+    var length = (data && data.entities) ? data.entities.length : 0;
+    for (var i = 0; i < length; i++) {
+      if (data.entities[i].type === 'user') {
+        self[connection][data.entities[i].username] = data.entities[i];
+      } else {
+        self[connection][data.entities[i].name] = data.entities[i];
+      }
+    }
+
+    doCallback(callback, [err, data, data.entities], self);
+  });
+
+};
+
+Usergrid.Entity.prototype.getGroups = function(callback) {
+
+  var self = this;
+
+  var endpoint = 'users' + '/' + this.get('uuid') + '/groups';
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, data) {
+    if (err && self._client.logging) {
+      console.log('entity could not be connected');
+    }
+
+    self.groups = data.entities;
+
+    doCallback(callback, [err, data, data.entities], self);
+  });
+
+};
+
+Usergrid.Entity.prototype.getActivities = function(callback) {
+
+  var self = this;
+
+  var endpoint = this.get('type') + '/' + this.get('uuid') + '/activities';
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, data) {
+    if (err && self._client.logging) {
+      console.log('entity could not be connected');
+    }
+
+    for (var entity in data.entities) {
+      data.entities[entity].createdDate = (new Date(data.entities[entity].created)).toUTCString();
+    }
+
+    self.activities = data.entities;
+
+    doCallback(callback, [err, data, data.entities], self);
+  });
+
+};
+
+Usergrid.Entity.prototype.getFollowing = function(callback) {
+
+  var self = this;
+
+  var endpoint = 'users' + '/' + this.get('uuid') + '/following';
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, data) {
+    if (err && self._client.logging) {
+      console.log('could not get user following');
+    }
+
+    for (var entity in data.entities) {
+      data.entities[entity].createdDate = (new Date(data.entities[entity].created)).toUTCString();
+      var image = self._client.getDisplayImage(data.entities[entity].email, data.entities[entity].picture);
+      data.entities[entity]._portal_image_icon = image;
+    }
+
+    self.following = data.entities;
+
+    doCallback(callback, [err, data, data.entities], self);
+  });
+
+};
+
+
+Usergrid.Entity.prototype.getFollowers = function(callback) {
+
+  var self = this;
+
+  var endpoint = 'users' + '/' + this.get('uuid') + '/followers';
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, data) {
+    if (err && self._client.logging) {
+      console.log('could not get user followers');
+    }
+
+    for (var entity in data.entities) {
+      data.entities[entity].createdDate = (new Date(data.entities[entity].created)).toUTCString();
+      var image = self._client.getDisplayImage(data.entities[entity].email, data.entities[entity].picture);
+      data.entities[entity]._portal_image_icon = image;
+    }
+
+    self.followers = data.entities;
+
+    doCallback(callback, [err, data, data.entities], self);
+  });
+
+};
+
+Usergrid.Client.prototype.createRole = function(roleName, permissions, callback) {
+    
+    var options = {
+        type: 'role',
+        name: roleName        
+    };
+
+    this.createEntity(options, function(err, response, entity) {
+        if (err) {
+            doCallback(callback, [ err, response, self ]);    
+        } else {
+            entity.assignPermissions(permissions, function (err, data) {
+                if (err) {
+                    doCallback(callback, [ err, response, self ]);    
+                } else {
+                    doCallback(callback, [ err, data, data.data ], self);
+                }
+            })
+        }        
+    });
+
+};
+
+Usergrid.Entity.prototype.getRoles = function(callback) {
+    var self = this;
+    var endpoint = this.get("type") + "/" + this.get("uuid") + "/roles";
+    var options = {
+        method: "GET",
+        endpoint: endpoint
+    };
+    this._client.request(options, function(err, data) {
+        if (err && self._client.logging) {
+            console.log("could not get user roles");
+        }
+        self.roles = data.entities;
+        doCallback(callback, [ err, data, data.entities ], self);
+    });
+};
+
+Usergrid.Entity.prototype.assignRole = function(roleName, callback) {
+    
+    var self = this;
+    var type = self.get('type');
+    var collection = type + 's';
+    var entityID;
+
+    if (type == 'user' && this.get('username') != null) {
+        entityID = self.get('username');
+    } else if (type == 'group' && this.get('name') != null) {
+        entityID = self.get('name');
+    } else if (this.get('uuid') != null) {
+        entityID = self.get('uuid');
+    }
+
+    if (type != 'users' && type != 'groups') {
+        doCallback(callback, [ new UsergridError('entity must be a group or user', 'invalid_entity_type'), null, this ], this);
+    }
+
+    var endpoint = 'roles/' + roleName + '/' + collection + '/' + entityID;
+    var options = {
+        method: 'POST',
+        endpoint: endpoint        
+    };
+
+    this._client.request(options, function(err, response) {
+        if (err) {
+            console.log('Could not assign role.');
+        }        
+        doCallback(callback, [ err, response, self ]);
+    });
+
+};
+
+Usergrid.Entity.prototype.removeRole = function(roleName, callback) {
+    
+    var self = this;
+    var type = self.get('type');
+    var collection = type + 's';
+    var entityID;
+
+    if (type == 'user' && this.get('username') != null) {
+        entityID = this.get('username');
+    } else if (type == 'group' && this.get('name') != null) {
+        entityID = this.get('name');
+    } else if (this.get('uuid') != null) {
+        entityID = this.get('uuid');
+    }
+
+    if (type != 'users' && type != 'groups') {
+        doCallback(callback, [ new UsergridError('entity must be a group or user', 'invalid_entity_type'), null, this ], this);
+    }
+
+    var endpoint = 'roles/' + roleName + '/' + collection + '/' + entityID;
+    var options = {
+        method: 'DELETE',
+        endpoint: endpoint        
+    };
+
+    this._client.request(options, function(err, response) {
+        if (err) {
+            console.log('Could not assign role.');
+        }        
+        doCallback(callback, [ err, response, self ]);
+    });
+
+};
+
+Usergrid.Entity.prototype.assignPermissions = function(permissions, callback) {
+    var self = this;
+    var entityID;
+    var type = this.get('type');
+
+    if (type != 'user' && type != 'users' && type != 'group' && type != 'groups' && type != 'role' && type != 'roles') {
+        doCallback(callback, [ new UsergridError('entity must be a group, user, or role', 'invalid_entity_type'), null, this ], this);
+    }
+
+    if (type == 'user' && this.get('username') != null) {
+        entityID = this.get('username');
+    } else if (type == 'group' && this.get('name') != null) {
+        entityID = this.get('name');
+    } else if (this.get('uuid') != null) {
+        entityID = this.get('uuid');
+    }
+
+    var endpoint = type + '/' + entityID + '/permissions';
+    var options = {
+        method: 'POST',
+        endpoint: endpoint,
+        body: {
+            'permission': permissions
+        }
+    };
+    this._client.request(options, function(err, data) {
+        if (err && self._client.logging) {
+            console.log('could not assign permissions');
+        }
+        doCallback(callback, [ err, data, data.data ], self);
+    });
+};
+
+Usergrid.Entity.prototype.removePermissions = function(permissions, callback) {
+    var self = this;
+    var entityID;
+    var type = this.get('type');
+
+    if (type != 'user' && type != 'users' && type != 'group' && type != 'groups' && type != 'role' && type != 'roles') {
+        doCallback(callback, [ new UsergridError('entity must be a group, user, or role', 'invalid_entity_type'), null, this ], this);
+    }
+
+    if (type == 'user' && this.get('username') != null) {
+        entityID = this.get('username');
+    } else if (type == 'group' && this.get('name') != null) {
+        entityID = this.get('name');
+    } else if (this.get('uuid') != null) {
+        entityID = this.get('uuid');
+    }
+
+    var endpoint = type + '/' + entityID + '/permissions';
+    var options = {
+        method: 'DELETE',
+        endpoint: endpoint,
+        qs: {
+            'permission': permissions
+        }
+    };
+    this._client.request(options, function(err, data) {
+        if (err && self._client.logging) {
+            console.log('could not remove permissions');
+        }
+        doCallback(callback, [ err, data, data.params.permission ], self);
+    });
+};
+
+Usergrid.Entity.prototype.getPermissions = function(callback) {
+
+  var self = this;
+
+  var endpoint = this.get('type') + '/' + this.get('uuid') + '/permissions';
+  var options = {
+    method: 'GET',
+    endpoint: endpoint
+  };
+  this._client.request(options, function(err, data) {
+    if (err && self._client.logging) {
+      console.log('could not get user permissions');
+    }
+
+    var permissions = [];
+    if (data.data) {
+      var perms = data.data;
+      var count = 0;
+
+      for (var i in perms) {
+        count++;
+        var perm = perms[i];
+        var parts = perm.split(':');
+        var ops_part = "";
+        var path_part = parts[0];
+
+        if (parts.length > 1) {
+          ops_part = parts[0];
+          path_part = parts[1];
+        }
+
+        ops_part=ops_part.replace("*", "get,post,put,delete");
+        var ops = ops_part.split(',');
+        var ops_object = {};
+        ops_object.get = 'no';
+        ops_object.post = 'no';
+        ops_object.put = 'no';
+        ops_object.delete = 'no';
+        for (var j in ops) {
+          ops_object[ops[j]] = 'yes';
+        }
+
+        permissions.push({
+          operations: ops_object,
+          path: path_part,
+          perm: perm
+        });
+      }
+    }
+
+    self.permissions = permissions;
+
+    doCallback(callback, [err, data, data.entities], self);
+  });
+
+};

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Error.js
----------------------------------------------------------------------
diff --git a/lib/modules/Error.js b/lib/modules/Error.js
new file mode 100644
index 0000000..5cc5807
--- /dev/null
+++ b/lib/modules/Error.js
@@ -0,0 +1,155 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+//noinspection ThisExpressionReferencesGlobalObjectJS
+
+/**
+ * Created by ryan bridges on 2014-02-05.
+ */
+(function(global) {
+    //noinspection JSUnusedAssignment
+    var name = 'UsergridError',
+        short,
+        _name = global[name],
+        _short = (short && short !== undefined) ? global[short] : undefined;
+
+    /*
+     *  Instantiates a new UsergridError
+     *
+     *  @method UsergridError
+     *  @public
+     *  @params {<string>} message
+     *  @params {<string>} id       - the error code, id, or name
+     *  @params {<int>} timestamp
+     *  @params {<int>} duration
+     *  @params {<string>} exception    - the Java exception from Usergrid
+     *  @return Returns - a new UsergridError object
+     *
+     *  Example:
+     *
+     *  UsergridError(message);
+     */
+
+    function UsergridError(message, name, timestamp, duration, exception) {
+        this.message = message;
+        this.name = name;
+        this.timestamp = timestamp || Date.now();
+        this.duration = duration || 0;
+        this.exception = exception;
+    }
+    UsergridError.prototype = new Error();
+    UsergridError.prototype.constructor = UsergridError;
+    /*
+     *  Creates a UsergridError from the JSON response returned from the backend
+     *
+     *  @method fromResponse
+     *  @public
+     *  @params {object} response - the deserialized HTTP response from the Usergrid API
+     *  @return Returns a new UsergridError object.
+     *
+     *  Example:
+     *  {
+     *  "error":"organization_application_not_found",
+     *  "timestamp":1391618508079,
+     *  "duration":0,
+     *  "exception":"org.usergrid.rest.exceptions.OrganizationApplicationNotFoundException",
+     *  "error_description":"Could not find application for yourorgname/sandboxxxxx from URI: yourorgname/sandboxxxxx"
+     *  }
+     */
+    UsergridError.fromResponse = function(response) {
+        if (response && "undefined" !== typeof response) {
+            return new UsergridError(response.error_description, response.error, response.timestamp, response.duration, response.exception);
+        } else {
+            return new UsergridError();
+        }
+    };
+    UsergridError.createSubClass = function(name) {
+        if (name in global && global[name]) return global[name];
+        global[name] = function() {};
+        global[name].name = name;
+        global[name].prototype = new UsergridError();
+        return global[name];
+    };
+
+    function UsergridHTTPResponseError(message, name, timestamp, duration, exception) {
+        this.message = message;
+        this.name = name;
+        this.timestamp = timestamp || Date.now();
+        this.duration = duration || 0;
+        this.exception = exception;
+    }
+    UsergridHTTPResponseError.prototype = new UsergridError();
+
+    function UsergridInvalidHTTPMethodError(message, name, timestamp, duration, exception) {
+        this.message = message;
+        this.name = name || 'invalid_http_method';
+        this.timestamp = timestamp || Date.now();
+        this.duration = duration || 0;
+        this.exception = exception;
+    }
+    UsergridInvalidHTTPMethodError.prototype = new UsergridError();
+
+    function UsergridInvalidURIError(message, name, timestamp, duration, exception) {
+        this.message = message;
+        this.name = name || 'invalid_uri';
+        this.timestamp = timestamp || Date.now();
+        this.duration = duration || 0;
+        this.exception = exception;
+    }
+    UsergridInvalidURIError.prototype = new UsergridError();
+
+    function UsergridInvalidArgumentError(message, name, timestamp, duration, exception) {
+        this.message = message;
+        this.name = name || 'invalid_argument';
+        this.timestamp = timestamp || Date.now();
+        this.duration = duration || 0;
+        this.exception = exception;
+    }
+    UsergridInvalidArgumentError.prototype = new UsergridError();
+
+    function UsergridKeystoreDatabaseUpgradeNeededError(message, name, timestamp, duration, exception) {
+        this.message = message;
+        this.name = name;
+        this.timestamp = timestamp || Date.now();
+        this.duration = duration || 0;
+        this.exception = exception;
+    }
+    UsergridKeystoreDatabaseUpgradeNeededError.prototype = new UsergridError();
+
+    global.UsergridHTTPResponseError = UsergridHTTPResponseError;
+    global.UsergridInvalidHTTPMethodError = UsergridInvalidHTTPMethodError;
+    global.UsergridInvalidURIError = UsergridInvalidURIError;
+    global.UsergridInvalidArgumentError = UsergridInvalidArgumentError;
+    global.UsergridKeystoreDatabaseUpgradeNeededError = UsergridKeystoreDatabaseUpgradeNeededError;
+
+    global[name] = UsergridError;
+    if (short !== undefined) {
+        //noinspection JSUnusedAssignment
+        global[short] = UsergridError;
+    }
+    global[name].noConflict = function() {
+        if (_name) {
+            global[name] = _name;
+        }
+        if (short !== undefined) {
+            global[short] = _short;
+        }
+        return UsergridError;
+    };
+    return global[name];
+}(this));

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Folder.js
----------------------------------------------------------------------
diff --git a/lib/modules/Folder.js b/lib/modules/Folder.js
new file mode 100644
index 0000000..9d13155
--- /dev/null
+++ b/lib/modules/Folder.js
@@ -0,0 +1,190 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+/*
+ *  A class to model a Usergrid folder.
+ *
+ *  @constructor
+ *  @param {object} options {name:"MyPhotos", path:"/user/uploads", owner:"00000000-0000-0000-0000-000000000000" }
+ *  @returns {callback} callback(err, folder)
+ */
+Usergrid.Folder = function(options, callback) {
+	var self = this,
+		messages = [];
+	console.log("FOLDER OPTIONS", options);
+	self._client = options.client;
+	self._data = options.data || {};
+	self._data.type = "folders";
+	var missingData = ["name", "owner", "path"].some(function(required) {
+		return !(required in self._data);
+	});
+	if (missingData) {
+		return doCallback(callback, [new UsergridInvalidArgumentError("Invalid asset data: 'name', 'owner', and 'path' are required properties."), null, self], self);
+	}
+	self.save(function(err, response) {
+		if (err) {
+			doCallback(callback, [new UsergridError(response), response, self], self);
+		} else {
+			if (response && response.entities && response.entities.length) {
+				self.set(response.entities[0]);
+			}
+			doCallback(callback, [null, response, self], self);
+		}
+	});
+};
+/*
+ *  Inherit from Usergrid.Entity.
+ */
+Usergrid.Folder.prototype = new Usergrid.Entity();
+
+
+/*
+ *  fetch the folder and associated assets
+ *
+ *  @method fetch
+ *  @public
+ *  @param {function} callback(err, self)
+ *  @returns {callback} callback(err, self)
+ */
+Usergrid.Folder.prototype.fetch = function(callback) {
+	var self = this;
+	Usergrid.Entity.prototype.fetch.call(self, function(err, data) {
+		console.log("self", self.get());
+		console.log("data", data);
+		if (!err) {
+			self.getAssets(function(err, response) {
+				if (err) {
+					doCallback(callback, [new UsergridError(response), resonse, self], self);
+				} else {
+					doCallback(callback, [null, self], self);
+				}
+			});
+		} else {
+			doCallback(callback, [null, data, self], self);
+		}
+	});
+};
+/*
+ *  Add an asset to the folder.
+ *
+ *  @method addAsset
+ *  @public
+ *  @param {object} options {asset:(uuid || Usergrid.Asset || {name:"photo.jpg", path:"/user/uploads", "content-type":"image/jpeg", owner:"F01DE600-0000-0000-0000-000000000000" }) }
+ *  @returns {callback} callback(err, folder)
+ */
+Usergrid.Folder.prototype.addAsset = function(options, callback) {
+	var self = this;
+	if (('asset' in options)) {
+		var asset = null;
+		switch (typeof options.asset) {
+			case 'object':
+				asset = options.asset;
+				if (!(asset instanceof Usergrid.Entity)) {
+					asset = new Usergrid.Asset(asset);
+				}
+				break;
+			case 'string':
+				if (isUUID(options.asset)) {
+					asset = new Usergrid.Asset({
+						client: self._client,
+						data: {
+							uuid: options.asset,
+							type: "assets"
+						}
+					});
+				}
+				break;
+		}
+		if (asset && asset instanceof Usergrid.Entity) {
+			asset.fetch(function(err, data) {
+				if (err) {
+					doCallback(callback, [new UsergridError(data), data, self], self);
+				} else {
+					var endpoint = ["folders", self.get("uuid"), "assets", asset.get("uuid")].join('/');
+					var options = {
+						method: 'POST',
+						endpoint: endpoint
+					};
+					self._client.request(options, callback);
+				}
+			});
+		}
+	} else {
+		//nothing to add
+		doCallback(callback, [new UsergridInvalidArgumentError("No asset specified"), null, self], self);
+	}
+};
+
+/*
+ *  Remove an asset from the folder.
+ *
+ *  @method removeAsset
+ *  @public
+ *  @param {object} options {asset:(uuid || Usergrid.Asset || {name:"photo.jpg", path:"/user/uploads", "content-type":"image/jpeg", owner:"F01DE600-0000-0000-0000-000000000000" }) }
+ *  @returns {callback} callback(err, folder)
+ */
+Usergrid.Folder.prototype.removeAsset = function(options, callback) {
+	var self = this;
+	if (('asset' in options)) {
+		var asset = null;
+		switch (typeof options.asset) {
+			case 'object':
+				asset = options.asset;
+				break;
+			case 'string':
+				if (isUUID(options.asset)) {
+					asset = new Usergrid.Asset({
+						client: self._client,
+						data: {
+							uuid: options.asset,
+							type: "assets"
+						}
+					});
+				}
+				break;
+		}
+		if (asset && asset !== null) {
+			var endpoint = ["folders", self.get("uuid"), "assets", asset.get("uuid")].join('/');
+			self._client.request({
+				method: 'DELETE',
+				endpoint: endpoint
+			}, function(err, response) {
+				if (err) {
+					doCallback(callback, [new UsergridError(response), response, self], self);
+				} else {
+					doCallback(callback, [null, response, self], self);
+				}
+			});
+		}
+	} else {
+		//nothing to add
+		doCallback(callback, [new UsergridInvalidArgumentError("No asset specified"), null, self], self);
+	}
+};
+
+/*
+ *  List the assets in the folder.
+ *
+ *  @method getAssets
+ *  @public
+ *  @returns {callback} callback(err, assets)
+ */
+Usergrid.Folder.prototype.getAssets = function(callback) {
+	return this.getConnections("assets", callback);
+};

http://git-wip-us.apache.org/repos/asf/usergrid-javascript/blob/94020d26/lib/modules/Group.js
----------------------------------------------------------------------
diff --git a/lib/modules/Group.js b/lib/modules/Group.js
new file mode 100644
index 0000000..1ea27c2
--- /dev/null
+++ b/lib/modules/Group.js
@@ -0,0 +1,231 @@
+/*
+ *Licensed to the Apache Software Foundation (ASF) under one
+ *or more contributor license agreements.  See the NOTICE file
+ *distributed with this work for additional information
+ *regarding copyright ownership.  The ASF licenses this file
+ *to you under the Apache License, Version 2.0 (the
+ *"License"); you may not use this file except in compliance
+ *with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+
+ *Unless required by applicable law or agreed to in writing,
+ *software distributed under the License is distributed on an
+ *"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *KIND, either express or implied.  See the License for the
+ *specific language governing permissions and limitations
+ *under the License.
+ */
+
+
+/*
+ *  A class to model a Usergrid group.
+ *  Set the path in the options object.
+ *
+ *  @constructor
+ *  @param {object} options {client:client, data: {'key': 'value'}, path:'path'}
+ */
+Usergrid.Group = function(options, callback) {
+  this._path = options.path;
+  this._list = [];
+  this._client = options.client;
+  this._data = options.data || {};
+  this._data.type = "groups";
+};
+
+/*
+ *  Inherit from Usergrid.Entity.
+ *  Note: This only accounts for data on the group object itself.
+ *  You need to use add and remove to manipulate group membership.
+ */
+Usergrid.Group.prototype = new Usergrid.Entity();
+
+/*
+ *  Fetches current group data, and members.
+ *
+ *  @method fetch
+ *  @public
+ *  @param {function} callback
+ *  @returns {function} callback(err, data)
+ */
+Usergrid.Group.prototype.fetch = function(callback) {
+  var self = this;
+  var groupEndpoint = 'groups/' + this._path;
+  var memberEndpoint = 'groups/' + this._path + '/users';
+
+  var groupOptions = {
+    method: 'GET',
+    endpoint: groupEndpoint
+  };
+
+  var memberOptions = {
+    method: 'GET',
+    endpoint: memberEndpoint
+  };
+
+  this._client.request(groupOptions, function(err, response) {
+    if (err) {
+      if (self._client.logging) {
+        console.log('error getting group');
+      }
+      doCallback(callback, [err, response], self);
+    } else {
+      var entities = response.getEntities();
+      if (entities && entities.length) {
+        var groupresponse = entities.shift();
+        //self._response = groupresponse || {};
+        self._client.request(memberOptions, function(err, response) {
+          if (err && self._client.logging) {
+            console.log('error getting group users');
+          } else {
+            self._list = response.getEntities()
+              .filter(function(entity) {
+                return isUUID(entity.uuid);
+              })
+              .map(function(entity) {
+                return new Usergrid.Entity({
+                  type: entity.type,
+                  client: self._client,
+                  uuid: entity.uuid,
+                  response: entity //TODO: deprecate this property
+                });
+              });
+          }
+          doCallback(callback, [err, response, self], self);
+        });
+      }
+    }
+  });
+};
+
+/*
+ *  Retrieves the members of a group.
+ *
+ *  @method members
+ *  @public
+ *  @param {function} callback
+ *  @return {function} callback(err, data);
+ */
+Usergrid.Group.prototype.members = function(callback) {
+  //doCallback(callback, [null, this._list, this], this);
+  return this._list;
+};
+
+/*
+ *  Adds an existing user to the group, and refreshes the group object.
+ *
+ *  Options object: {user: user_entity}
+ *
+ *  @method add
+ *  @public
+ *  @params {object} options
+ *  @param {function} callback
+ *  @return {function} callback(err, data)
+ */
+Usergrid.Group.prototype.add = function(options, callback) {
+  var self = this;
+  if (options.user) {
+    options = {
+      method: "POST",
+      endpoint: "groups/" + this._path + "/users/" + options.user.get('username')
+    };
+    this._client.request(options, function(error, response) {
+      if (error) {
+        doCallback(callback, [error, response, self], self);
+      } else {
+        self.fetch(callback);
+      }
+    });
+  } else {
+    doCallback(callback, [new UsergridError("no user specified", 'no_user_specified'), null, this], this);
+  }
+};
+
+/*
+ *  Removes a user from a group, and refreshes the group object.
+ *
+ *  Options object: {user: user_entity}
+ *
+ *  @method remove
+ *  @public
+ *  @params {object} options
+ *  @param {function} callback
+ *  @return {function} callback(err, data)
+ */
+Usergrid.Group.prototype.remove = function(options, callback) {
+  var self = this;
+  if (options.user) {
+    options = {
+      method: "DELETE",
+      endpoint: "groups/" + this._path + "/users/" + options.user.username
+    };
+    this._client.request(options, function(error, response) {
+      if (error) {
+        doCallback(callback, [error, response, self], self);
+      } else {
+        self.fetch(callback);
+      }
+    });
+  } else {
+    doCallback(callback, [new UsergridError("no user specified", 'no_user_specified'), null, this], this);
+  }
+};
+
+/*
+ * Gets feed for a group.
+ *
+ * @public
+ * @method feed
+ * @param {function} callback
+ * @returns {callback} callback(err, data, activities)
+ */
+Usergrid.Group.prototype.feed = function(callback) {
+  var self = this;
+  var options = {
+    method: "GET",
+    endpoint: "groups/" + this._path + "/feed"
+  };
+  this._client.request(options, function(err, response) {
+    doCallback(callback, [err, response, self], self);
+  });
+};
+
+/*
+ * Creates activity and posts to group feed.
+ *
+ * options object: {user: user_entity, content: "activity content"}
+ *
+ * @public
+ * @method createGroupActivity
+ * @params {object} options
+ * @param {function} callback
+ * @returns {callback} callback(err, entity)
+ */
+Usergrid.Group.prototype.createGroupActivity = function(options, callback) {
+  var self = this;
+  var user = options.user;
+  var entity = new Usergrid.Entity({
+    client: this._client,
+    data: {
+      actor: {
+        "displayName": user.get("username"),
+        "uuid": user.get("uuid"),
+        "username": user.get("username"),
+        "email": user.get("email"),
+        "picture": user.get("picture"),
+        "image": {
+          "duration": 0,
+          "height": 80,
+          "url": user.get("picture"),
+          "width": 80
+        },
+      },
+      "verb": "post",
+      "content": options.content,
+      "type": 'groups/' + this._path + '/activities'
+    }
+  });
+  entity.save(function(err, response, entity) {
+    doCallback(callback, [err, response, self]);
+  });
+};