Repository: parse-community/Parse-SDK-JS
Pull request #489 (2017-10-10 12:58:30)

Support for Parse Schema
Pending: https://github.com/parse-community/parse-server/pull/4240

Adds CRUD support for new Parse.Schema class.

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=h1) Report
> Merging [#489](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/293e2e13fa8eb1c62786fecf6aa6cd4458670b7f?src=pr&el=desc) will **decrease** coverage by `2.33%`.
> The diff coverage is `5.17%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489/graphs/tree.svg?width=650&token=8nCENM0QHj&height=150&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #489      +/-   ##
==========================================
- Coverage   84.03%   81.69%   -2.34%     
==========================================
  Files          47       48       +1     
  Lines        3795     3911     +116     
  Branches      868      888      +20     
==========================================
+ Hits         3189     3195       +6     
- Misses        606      716     +110
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/Parse.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=tree#diff-c3JjL1BhcnNlLmpz) | `82.08% <100%> (+0.27%)` | :arrow_up: |
| [src/ParseSchema.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=tree#diff-c3JjL1BhcnNlU2NoZW1hLmpz) | `2.67% <2.67%> (ø)` | |
| [src/CoreManager.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=tree#diff-c3JjL0NvcmVNYW5hZ2VyLmpz) | `98.48% <66.66%> (-1.52%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=footer). Last update [293e2e1...fadc6e7](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/489?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.gitignore b/.gitignore
index 6cadcfc..b07d5b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,4 +9,5 @@ integration/test_logs
 .DS_Store
 .idea/
 integration/test_logs
+out/
 
diff --git a/.travis.yml b/.travis.yml
index 2856174..fc6cec0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,7 @@
 ---
 language: node_js
 node_js:
-- '5'
+- '6.11.4'
 
 branches:
   only:
diff --git a/README.md b/README.md
index f8a513d..d8e8fc3 100644
--- a/README.md
+++ b/README.md
@@ -36,8 +36,6 @@ For React Native applications, include `'parse/react-native'`:
 var Parse = require('parse/react-native');
 ```
 
-> As of `v1.10`, Parse-SDK-JS supports React Native 0.43+. Please use `v1.9.2` for previous versions of React Native.
-
 ## License
 
 ```
diff --git a/integration/package.json b/integration/package.json
index b5a35b9..5927ccb 100644
--- a/integration/package.json
+++ b/integration/package.json
@@ -3,7 +3,7 @@
   "dependencies": {
     "express": "^4.13.4",
     "mocha": "^2.4.5",
-    "parse-server": "^2.6.0"
+    "parse-server": "^2.7.0"
   },
   "scripts": {
     "test": "mocha --reporter dot -t 5000"
diff --git a/jsdoc-conf.json b/jsdoc-conf.json
new file mode 100644
index 0000000..b6ad741
--- /dev/null
+++ b/jsdoc-conf.json
@@ -0,0 +1,20 @@
+{
+  "plugins": ["node_modules/jsdoc-babel"],
+  "babel": {
+      "plugins": ["transform-flow-strip-types"]
+  },
+  "source": {
+    "include": ["./package.json", "./README.md"],
+    "excludePattern": "(^|\\/|\\\\)_"
+  },
+  "templates": {
+    "default": {
+      "outputSourceFiles": false
+    },
+    "cleverLinks": false,
+    "monospaceLinks": false
+  },
+  "opts": {
+    "recurse": true
+  }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 927df45..a1c5b5c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "parse",
-  "version": "1.10.0",
+  "version": "1.10.2",
   "description": "The Parse JavaScript SDK",
   "homepage": "https://www.parse.com",
   "keywords": [
@@ -29,7 +29,7 @@
   },
   "dependencies": {
     "babel-runtime": "^6.11.6",
-    "ws": "^2.2.3",
+    "ws": "^3.3.1",
     "xmlhttprequest": "^1.7.0"
   },
   "devDependencies": {
@@ -53,12 +53,15 @@
     "gulp-uglify": "^2.1.2",
     "jasmine-reporters": "~2.2.1",
     "jest-cli": "^19.0.2",
+    "jsdoc": "^3.5.5",
+    "jsdoc-babel": "^0.3.0",
     "vinyl-source-stream": "^1.1.0"
   },
   "scripts": {
     "build": "./build_releases.sh",
     "release": "./build_releases.sh && npm publish",
-    "test": "PARSE_BUILD=node jest"
+    "test": "PARSE_BUILD=node jest",
+    "docs": "jsdoc -c ./jsdoc-conf.json ./src"
   },
   "jest": {
     "automock": true,
@@ -70,7 +73,9 @@
       "/node_modules/",
       "/test_helpers/"
     ],
-    "transform": {".*": "./babel-jest.js"},
+    "transform": {
+      ".*": "./babel-jest.js"
+    },
     "setupTestFrameworkScriptFile": "./setup-jest.js"
   }
 }
diff --git a/src/Analytics.js b/src/Analytics.js
index f3a21e4..ed7d04d 100644
--- a/src/Analytics.js
+++ b/src/Analytics.js
@@ -19,6 +19,7 @@ import type ParsePromise from './ParsePromise';
  *
  * @class Parse.Analytics
  * @static
+ * @hideconstructor
  */
 
  /**
@@ -44,6 +45,7 @@ import type ParsePromise from './ParsePromise';
   * There is a default limit of 8 dimensions per event tracked.
   *
   * @method track
+  * @name Parse.Analytics.track
   * @param {String} name The name of the custom event to report to Parse as
   * having happened.
   * @param {Object} dimensions The dictionary of information by which to
diff --git a/src/Cloud.js b/src/Cloud.js
index f2dc1ff..74c91fa 100644
--- a/src/Cloud.js
+++ b/src/Cloud.js
@@ -24,11 +24,13 @@ import ParsePromise from './ParsePromise';
  *
  * @class Parse.Cloud
  * @static
+ * @hideconstructor
  */
 
  /**
   * Makes a call to a cloud function.
   * @method run
+  * @name Parse.Cloud.run
   * @param {String} name The function name.
   * @param {Object} data The parameters to send to the cloud function.
   * @param {Object} options A Backbone-style options object
diff --git a/src/CoreManager.js b/src/CoreManager.js
index 8dac296..87dcf3f 100644
--- a/src/CoreManager.js
+++ b/src/CoreManager.js
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Parse, LLC.
  * All rights reserved.
  *
@@ -71,11 +71,19 @@ type PushController = {
 };
 type QueryController = {
   find: (className: string, params: QueryJSON, options: RequestOptions) => ParsePromise;
+  aggregate: (className: string, params: any, options: RequestOptions) => ParsePromise;
 };
 type RESTController = {
   request: (method: string, path: string, data: mixed) => ParsePromise;
   ajax: (method: string, url: string, data: any, headers?: any) => ParsePromise;
 };
+type SchemaController = {
+  get: (className: string, options: RequestOptions) => ParsePromise;
+  delete: (className: string, options: RequestOptions) => ParsePromise;
+  create: (className: string, params: any, options: RequestOptions) => ParsePromise;
+  update: (className: string, params: any, options: RequestOptions) => ParsePromise;
+  send(className: string, method: string, params: any, options: RequestOptions): ParsePromise;
+};
 type SessionController = {
   getSession: (token: RequestOptions) => ParsePromise;
 };
@@ -131,6 +139,7 @@ type Config = {
   PushController?: PushController,
   QueryController?: QueryController,
   RESTController?: RESTController,
+  SchemaController?: SchemaController,
   SessionController?: SessionController,
   StorageController?: StorageController,
   UserController?: UserController,
@@ -268,7 +277,7 @@ module.exports = {
   },
 
   setQueryController(controller: QueryController) {
-    requireMethods('QueryController', ['find'], controller);
+    requireMethods('QueryController', ['find', 'aggregate'], controller);
     config['QueryController'] = controller;
   },
 
@@ -285,6 +294,15 @@ module.exports = {
     return config['RESTController'];
   },
 
+  setSchemaController(controller: SchemaController) {
+    requireMethods('SchemaController', ['get', 'create', 'update', 'delete', 'send'], controller);
+    config['SchemaController'] = controller;
+  },
+
+  getSchemaController(): SchemaController {
+    return config['SchemaController'];
+  },
+
   setSessionController(controller: SessionController) {
     requireMethods('SessionController', ['getSession'], controller);
     config['SessionController'] = controller;
diff --git a/src/FacebookUtils.js b/src/FacebookUtils.js
index 0fb3ce4..0906508 100644
--- a/src/FacebookUtils.js
+++ b/src/FacebookUtils.js
@@ -93,6 +93,7 @@ var provider = {
  * Provides a set of utilities for using Parse with Facebook.
  * @class Parse.FacebookUtils
  * @static
+ * @hideconstructor
  */
 var FacebookUtils = {
   /**
@@ -105,6 +106,7 @@ var FacebookUtils = {
    * with these arguments.
    *
    * @method init
+   * @name Parse.FacebookUtils.init
    * @param {Object} options Facebook options argument as described here:
    *   <a href=
    *   "https://developers.facebook.com/docs/reference/javascript/FB.init/">
@@ -141,6 +143,7 @@ var FacebookUtils = {
    * Gets whether the user has their account linked to Facebook.
    *
    * @method isLinked
+   * @name Parse.FacebookUtils.isLinked
    * @param {Parse.User} user User to check for a facebook link.
    *     The user must be logged in on this device.
    * @return {Boolean} <code>true</code> if the user has their account
@@ -156,13 +159,15 @@ var FacebookUtils = {
    * creates, in the case where it is a new user) a Parse.User.
    *
    * @method logIn
-   * @param {String, Object} permissions The permissions required for Facebook
+   * @name Parse.FacebookUtils.logIn
+   * @param {(String|Object)} permissions The permissions required for Facebook
    *    log in.  This is a comma-separated string of permissions.
    *    Alternatively, supply a Facebook authData object as described in our
    *    REST API docs if you want to handle getting facebook auth tokens
    *    yourself.
    * @param {Object} options Standard options object with success and error
    *    callbacks.
+   * @returns {Parse.Promise}
    */
   logIn(permissions, options) {
     if (!permissions || typeof permissions === 'string') {
@@ -191,15 +196,17 @@ var FacebookUtils = {
    * the account to the Parse.User.
    *
    * @method link
+   * @name Parse.FacebookUtils.link
    * @param {Parse.User} user User to link to Facebook. This must be the
    *     current user.
-   * @param {String, Object} permissions The permissions required for Facebook
+   * @param {(String|Object)} permissions The permissions required for Facebook
    *    log in.  This is a comma-separated string of permissions.
    *    Alternatively, supply a Facebook authData object as described in our
    *    REST API docs if you want to handle getting facebook auth tokens
    *    yourself.
    * @param {Object} options Standard options object with success and error
    *    callbacks.
+   * @returns {Parse.Promise}
    */
   link(user, permissions, options) {
     if (!permissions || typeof permissions === 'string') {
@@ -226,10 +233,12 @@ var FacebookUtils = {
    * Unlinks the Parse.User from a Facebook account.
    *
    * @method unlink
+   * @name Parse.FacebookUtils.unlink
    * @param {Parse.User} user User to unlink from Facebook. This must be the
    *     current user.
    * @param {Object} options Standard options object with success and error
    *    callbacks.
+   * @returns {Parse.Promise}
    */
   unlink: function(user, options) {
     if (!initialized) {
diff --git a/src/LiveQueryClient.js b/src/LiveQueryClient.js
index e6a43a2..292f03c 100644
--- a/src/LiveQueryClient.js
+++ b/src/LiveQueryClient.js
@@ -78,16 +78,6 @@ let generateInterval = (k) => {
  *
  * javascriptKey and masterKey are used for verifying the LiveQueryClient when it tries
  * to connect to the LiveQuery server
- * 
- * @class Parse.LiveQueryClient
- * @constructor
- * @param {Object} options
- * @param {string} options.applicationId - applicationId of your Parse app
- * @param {string} options.serverURL - <b>the URL of your LiveQuery server</b>
- * @param {string} options.javascriptKey (optional)
- * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
- * @param {string} options.sessionToken (optional)
- *
  *
  * We expose three events to help you monitor the status of the LiveQueryClient.
  *
@@ -119,10 +109,9 @@ let generateInterval = (k) => {
  * client.on('error', (error) => {
  * 
  * });</pre>
- * 
- * 
+ * @alias Parse.LiveQueryClient
  */
-export default class LiveQueryClient extends EventEmitter {
+class LiveQueryClient extends EventEmitter {
   attempts: number;
   id: number;
   requestId: number;
@@ -136,6 +125,14 @@ export default class LiveQueryClient extends EventEmitter {
   socket: any;
   state: string;
 
+  /**
+   * @param {Object} options
+   * @param {string} options.applicationId - applicationId of your Parse app
+   * @param {string} options.serverURL - <b>the URL of your LiveQuery server</b>
+   * @param {string} options.javascriptKey (optional)
+   * @param {string} options.masterKey (optional) Your Parse Master Key. (Node.js only!)
+   * @param {string} options.sessionToken (optional)
+   */
   constructor({
     applicationId,
     serverURL,
@@ -177,7 +174,6 @@ export default class LiveQueryClient extends EventEmitter {
    * <a href="https://github.com/ParsePlatform/parse-server/wiki/Parse-LiveQuery-Protocol-Specification">here</a> for more details. The subscription you get is the same subscription you get 
    * from our Standard API.
    * 
-   * @method subscribe
    * @param {Object} query - the ParseQuery you want to subscribe to
    * @param {string} sessionToken (optional) 
    * @return {Object} subscription
@@ -218,7 +214,6 @@ export default class LiveQueryClient extends EventEmitter {
   /**
    * After calling unsubscribe you'll stop receiving events from the subscription object.
    * 
-   * @method unsubscribe
    * @param {Object} subscription - subscription you would like to unsubscribe from.
    */ 
   unsubscribe(subscription: Object) {
@@ -240,7 +235,6 @@ export default class LiveQueryClient extends EventEmitter {
    * After open is called, the LiveQueryClient will try to send a connect request
    * to the LiveQuery server.
    * 
-   * @method open
    */ 
   open() {
     let WebSocketImplementation = this._getWebSocketImplementation();
@@ -303,7 +297,6 @@ export default class LiveQueryClient extends EventEmitter {
    * This method will close the WebSocket connection to this LiveQueryClient, 
    * cancel the auto reconnect and unsubscribe all subscriptions based on it.
    * 
-   * @method close
    */ 
   close() {
     if (this.state === CLIENT_STATE.INITIALIZED || this.state === CLIENT_STATE.DISCONNECTED) {
@@ -448,3 +441,5 @@ export default class LiveQueryClient extends EventEmitter {
     }).bind(this), time);
   }
 }
+
+export default LiveQueryClient;
diff --git a/src/LiveQuerySubscription.js b/src/LiveQuerySubscription.js
index 1df9018..7185092 100644
--- a/src/LiveQuerySubscription.js
+++ b/src/LiveQuerySubscription.js
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Parse, LLC.
  * All rights reserved.
  *
@@ -16,10 +16,6 @@ import CoreManager from './CoreManager';
  * Extends events.EventEmitter
  * <a href="https://nodejs.org/api/events.html#events_class_eventemitter">cloud functions</a>.
  * 
- * @constructor
- * @param {string} id - subscription id
- * @param {string} query - query to subscribe to
- * @param {string} sessionToken - optional session token
  *
  * <p>Open Event - When you call query.subscribe(), we send a subscribe request to 
  * the LiveQuery server, when we get the confirmation from the LiveQuery server,
@@ -88,9 +84,14 @@ import CoreManager from './CoreManager';
  * 
  * });</pre></p>
  *
- * 
+ * @alias Parse.LiveQuerySubscription
  */
-export default class Subscription extends EventEmitter {
+class Subscription extends EventEmitter {
+  /*
+   * @param {string} id - subscription id
+   * @param {string} query - query to subscribe to
+   * @param {string} sessionToken - optional session token
+   */
   constructor(id, query, sessionToken) {
     super();
     this.id = id;
@@ -99,7 +100,7 @@ export default class Subscription extends EventEmitter {
   }
 
   /**
-   * @method unsubscribe
+   * closes the subscription
    */
   unsubscribe() {
     let _this = this;
@@ -110,3 +111,5 @@ export default class Subscription extends EventEmitter {
     });
   }
 }
+
+export default Subscription;
diff --git a/src/Parse.js b/src/Parse.js
index 872f4ae..7e6255b 100644
--- a/src/Parse.js
+++ b/src/Parse.js
@@ -16,14 +16,15 @@ import RESTController from './RESTController';
 
 /**
  * Contains all Parse API classes and functions.
- * @class Parse
  * @static
+ * @global
+ * @class
+ * @hideconstructor
  */
 var Parse = {
   /**
    * Call this method first to set up your authentication tokens for Parse.
    * You can get your keys from the Data Browser on parse.com.
-   * @method initialize
    * @param {String} applicationId Your Parse Application ID.
    * @param {String} javaScriptKey (optional) Your Parse JavaScript Key (Not needed for parse-server)
    * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!)
@@ -48,6 +49,11 @@ var Parse = {
 };
 
 /** These legacy setters may eventually be deprecated **/
+/** 
+ * @member Parse.applicationId
+ * @type string
+ * @static
+ */
 Object.defineProperty(Parse, 'applicationId', {
   get() {
     return CoreManager.get('APPLICATION_ID');
@@ -56,6 +62,12 @@ Object.defineProperty(Parse, 'applicationId', {
     CoreManager.set('APPLICATION_ID', value);
   }
 });
+
+/** 
+ * @member Parse.javaScriptKey
+ * @type string
+ * @static
+ */
 Object.defineProperty(Parse, 'javaScriptKey', {
   get() {
     return CoreManager.get('JAVASCRIPT_KEY');
@@ -64,6 +76,12 @@ Object.defineProperty(Parse, 'javaScriptKey', {
     CoreManager.set('JAVASCRIPT_KEY', value);
   }
 });
+
+/** 
+ * @member Parse.masterKey
+ * @type string
+ * @static
+ */
 Object.defineProperty(Parse, 'masterKey', {
   get() {
     return CoreManager.get('MASTER_KEY');
@@ -72,6 +90,12 @@ Object.defineProperty(Parse, 'masterKey', {
     CoreManager.set('MASTER_KEY', value);
   }
 });
+
+/** 
+ * @member Parse.serverURL
+ * @type string
+ * @static
+ */
 Object.defineProperty(Parse, 'serverURL', {
   get() {
     return CoreManager.get('SERVER_URL');
@@ -80,6 +104,11 @@ Object.defineProperty(Parse, 'serverURL', {
     CoreManager.set('SERVER_URL', value);
   }
 });
+/** 
+ * @member Parse.liveQueryServerURL
+ * @type string
+ * @static
+ */
 Object.defineProperty(Parse, 'liveQueryServerURL', {
   get() {
     return CoreManager.get('LIVEQUERY_SERVER_URL');
@@ -88,7 +117,7 @@ Object.defineProperty(Parse, 'liveQueryServerURL', {
     CoreManager.set('LIVEQUERY_SERVER_URL', value);
   }
 });
-/** End setters **/
+/* End setters */
 
 Parse.ACL = require('./ParseACL').default;
 Parse.Analytics = require('./Analytics');
@@ -116,6 +145,7 @@ Parse.Push = require('./Push');
 Parse.Query = require('./ParseQuery').default;
 Parse.Relation = require('./ParseRelation').default;
 Parse.Role = require('./ParseRole').default;
+Parse.Schema = require('./ParseSchema').default;
 Parse.Session = require('./ParseSession').default;
 Parse.Storage = require('./Storage');
 Parse.User = require('./ParseUser').default;
diff --git a/src/ParseACL.js b/src/ParseACL.js
index ba944a6..4b63edb 100644
--- a/src/ParseACL.js
+++ b/src/ParseACL.js
@@ -24,16 +24,18 @@ var PUBLIC_KEY = '*';
  *   permission for only that user.
  * If the argument is any other JSON object, that object will be interpretted
  *   as a serialized ACL created with toJSON().
- * @class Parse.ACL
- * @constructor
- *
+ * 
  * <p>An ACL, or Access Control List can be added to any
  * <code>Parse.Object</code> to restrict access to only a subset of users
  * of your application.</p>
+ * @alias Parse.ACL
  */
-export default class ParseACL {
+class ParseACL {
   permissionsById: ByIdMap;
 
+  /**
+   * @param {(Parse.User|Object)} user The user to initialize the ACL for
+   */
   constructor(arg1: ParseUser | ByIdMap) {
     this.permissionsById = {};
     if (arg1 && typeof arg1 === 'object') {
@@ -74,7 +76,6 @@ export default class ParseACL {
 
   /**
    * Returns a JSON-encoded version of the ACL.
-   * @method toJSON
    * @return {Object}
    */
   toJSON(): ByIdMap {
@@ -87,7 +88,6 @@ export default class ParseACL {
 
   /**
    * Returns whether this ACL is equal to another object
-   * @method equals
    * @param other The other object to compare to
    * @return {Boolean}
    */
@@ -176,7 +176,6 @@ export default class ParseACL {
 
   /**
    * Sets whether the given user is allowed to read this object.
-   * @method setReadAccess
    * @param userId An instance of Parse.User or its objectId.
    * @param {Boolean} allowed Whether that user should have read access.
    */
@@ -189,7 +188,6 @@ export default class ParseACL {
    * Even if this returns false, the user may still be able to access it if
    * getPublicReadAccess returns true or a role that the user belongs to has
    * write access.
-   * @method getReadAccess
    * @param userId An instance of Parse.User or its objectId, or a Parse.Role.
    * @return {Boolean}
    */
@@ -199,7 +197,6 @@ export default class ParseACL {
 
   /**
    * Sets whether the given user id is allowed to write this object.
-   * @method setWriteAccess
    * @param userId An instance of Parse.User or its objectId, or a Parse.Role..
    * @param {Boolean} allowed Whether that user should have write access.
    */
@@ -212,7 +209,6 @@ export default class ParseACL {
    * Even if this returns false, the user may still be able to write it if
    * getPublicWriteAccess returns true or a role that the user belongs to has
    * write access.
-   * @method getWriteAccess
    * @param userId An instance of Parse.User or its objectId, or a Parse.Role.
    * @return {Boolean}
    */
@@ -222,7 +218,6 @@ export default class ParseACL {
 
   /**
    * Sets whether the public is allowed to read this object.
-   * @method setPublicReadAccess
    * @param {Boolean} allowed
    */
   setPublicReadAccess(allowed: boolean) {
@@ -231,7 +226,6 @@ export default class ParseACL {
 
   /**
    * Gets whether the public is allowed to read this object.
-   * @method getPublicReadAccess
    * @return {Boolean}
    */
   getPublicReadAccess(): boolean {
@@ -240,7 +234,6 @@ export default class ParseACL {
 
   /**
    * Sets whether the public is allowed to write this object.
-   * @method setPublicWriteAccess
    * @param {Boolean} allowed
    */
   setPublicWriteAccess(allowed: boolean) {
@@ -249,7 +242,6 @@ export default class ParseACL {
 
   /**
    * Gets whether the public is allowed to write this object.
-   * @method getPublicWriteAccess
    * @return {Boolean}
    */
   getPublicWriteAccess(): boolean {
@@ -261,7 +253,6 @@ export default class ParseACL {
    * to read this object. Even if this returns false, the role may
    * still be able to write it if a parent role has read access.
    *
-   * @method getRoleReadAccess
    * @param role The name of the role, or a Parse.Role object.
    * @return {Boolean} true if the role has read access. false otherwise.
    * @throws {TypeError} If role is neither a Parse.Role nor a String.
@@ -284,7 +275,6 @@ export default class ParseACL {
    * to write this object. Even if this returns false, the role may
    * still be able to write it if a parent role has write access.
    *
-   * @method getRoleWriteAccess
    * @param role The name of the role, or a Parse.Role object.
    * @return {Boolean} true if the role has write access. false otherwise.
    * @throws {TypeError} If role is neither a Parse.Role nor a String.
@@ -306,7 +296,6 @@ export default class ParseACL {
    * Sets whether users belonging to the given role are allowed
    * to read this object.
    *
-   * @method setRoleReadAccess
    * @param role The name of the role, or a Parse.Role object.
    * @param {Boolean} allowed Whether the given role can read this object.
    * @throws {TypeError} If role is neither a Parse.Role nor a String.
@@ -328,7 +317,6 @@ export default class ParseACL {
    * Sets whether users belonging to the given role are allowed
    * to write this object.
    *
-   * @method setRoleWriteAccess
    * @param role The name of the role, or a Parse.Role object.
    * @param {Boolean} allowed Whether the given role can write this object.
    * @throws {TypeError} If role is neither a Parse.Role nor a String.
@@ -346,3 +334,5 @@ export default class ParseACL {
     this.setWriteAccess('role:' + role, allowed);
   }
 }
+
+export default ParseACL;
diff --git a/src/ParseConfig.js b/src/ParseConfig.js
index 73ccabc..bc9d7e3 100644
--- a/src/ParseConfig.js
+++ b/src/ParseConfig.js
@@ -21,11 +21,10 @@ import Storage from './Storage';
  * Parse.Config is a local representation of configuration data that
  * can be set from the Parse dashboard.
  *
- * @class Parse.Config
- * @constructor
+ * @alias Parse.Config
  */
 
-export default class ParseConfig {
+class ParseConfig {
   attributes: { [key: string]: any };
   _escapedAttributes: { [key: string]: any };
 
@@ -36,7 +35,6 @@ export default class ParseConfig {
 
   /**
    * Gets the value of an attribute.
-   * @method get
    * @param {String} attr The name of an attribute.
    */
   get(attr: string): any {
@@ -45,7 +43,6 @@ export default class ParseConfig {
 
   /**
    * Gets the HTML-escaped value of an attribute.
-   * @method escape
    * @param {String} attr The name of an attribute.
    */
   escape(attr: string): string {
@@ -66,7 +63,6 @@ export default class ParseConfig {
    * Retrieves the most recently-fetched configuration object, either from
    * memory or from local storage if necessary.
    *
-   * @method current
    * @static
    * @return {Config} The most recently-fetched Parse.Config if it
    *     exists, else an empty Parse.Config.
@@ -78,7 +74,6 @@ export default class ParseConfig {
 
   /**
    * Gets a new configuration object from the server.
-   * @method get
    * @static
    * @param {Object} options A Backbone-style options object.
    * Valid options are:<ul>
@@ -176,3 +171,5 @@ var DefaultController = {
 };
 
 CoreManager.setConfigController(DefaultController);
+
+export default ParseConfig;
diff --git a/src/ParseError.js b/src/ParseError.js
index ebfe1de..e03499e 100644
--- a/src/ParseError.js
+++ b/src/ParseError.js
@@ -9,12 +9,13 @@
 
  /**
   * Constructs a new Parse.Error object with the given code and message.
-  * @class Parse.Error
-  * @constructor
-  * @param {Number} code An error code constant from <code>Parse.Error</code>.
-  * @param {String} message A detailed description of the error.
+  * @alias Parse.Error
   */
-export default class ParseError {
+class ParseError {
+  /**
+   * @param {Number} code An error code constant from <code>Parse.Error</code>.
+   * @param {String} message A detailed description of the error.
+   */
   constructor(code, message) {
     this.code = code;
     this.message = message;
@@ -493,3 +494,5 @@ ParseError.FILE_READ_ERROR = 601;
  * @final
  */
 ParseError.X_DOMAIN_REQUEST = 602;
+
+export default ParseError;
diff --git a/src/ParseFile.js b/src/ParseFile.js
index 8e179e6..06f71d8 100644
--- a/src/ParseFile.js
+++ b/src/ParseFile.js
@@ -49,39 +49,40 @@ function b64Digit(number: number): string {
 /**
  * A Parse.File is a local representation of a file that is saved to the Parse
  * cloud.
- * @class Parse.File
- * @constructor
- * @param name {String} The file's name. This will be prefixed by a unique
- *     value once the file has finished saving. The file name must begin with
- *     an alphanumeric character, and consist of alphanumeric characters,
- *     periods, spaces, underscores, or dashes.
- * @param data {Array} The data for the file, as either:
- *     1. an Array of byte value Numbers, or
- *     2. an Object like { base64: "..." } with a base64-encoded String.
- *     3. a File object selected with a file upload control. (3) only works
- *        in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
- *        For example:<pre>
- * var fileUploadControl = $("#profilePhotoFileUpload")[0];
- * if (fileUploadControl.files.length > 0) {
- *   var file = fileUploadControl.files[0];
- *   var name = "photo.jpg";
- *   var parseFile = new Parse.File(name, file);
- *   parseFile.save().then(function() {
- *     // The file has been saved to Parse.
- *   }, function(error) {
- *     // The file either could not be read, or could not be saved to Parse.
- *   });
- * }</pre>
- * @param type {String} Optional Content-Type header to use for the file. If
- *     this is omitted, the content type will be inferred from the name's
- *     extension.
+ * @alias Parse.File
  */
-export default class ParseFile {
+class ParseFile {
   _name: string;
   _url: ?string;
   _source: FileSource;
   _previousSave: ?ParsePromise;
 
+  /**
+   * @param name {String} The file's name. This will be prefixed by a unique
+   *     value once the file has finished saving. The file name must begin with
+   *     an alphanumeric character, and consist of alphanumeric characters,
+   *     periods, spaces, underscores, or dashes.
+   * @param data {Array} The data for the file, as either:
+   *     1. an Array of byte value Numbers, or
+   *     2. an Object like { base64: "..." } with a base64-encoded String.
+   *     3. a File object selected with a file upload control. (3) only works
+   *        in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
+   *        For example:<pre>
+   * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+   * if (fileUploadControl.files.length > 0) {
+   *   var file = fileUploadControl.files[0];
+   *   var name = "photo.jpg";
+   *   var parseFile = new Parse.File(name, file);
+   *   parseFile.save().then(function() {
+   *     // The file has been saved to Parse.
+   *   }, function(error) {
+   *     // The file either could not be read, or could not be saved to Parse.
+   *   });
+   * }</pre>
+   * @param type {String} Optional Content-Type header to use for the file. If
+   *     this is omitted, the content type will be inferred from the name's
+   *     extension.
+   */
   constructor(name: string, data?: FileData, type?: string) {
     var specifiedType = type || '';
 
@@ -129,7 +130,6 @@ export default class ParseFile {
    * Gets the name of the file. Before save is called, this is the filename
    * given by the user. After save is called, that name gets prefixed with a
    * unique identifier.
-   * @method name
    * @return {String}
    */
   name(): string {
@@ -139,7 +139,6 @@ export default class ParseFile {
   /**
    * Gets the url of the file. It is only available after you save the file or
    * after you get the file from a Parse.Object.
-   * @method url
    * @param {Object} options An object to specify url options
    * @return {String}
    */
@@ -157,7 +156,6 @@ export default class ParseFile {
 
   /**
    * Saves the file to the Parse cloud.
-   * @method save
    * @param {Object} options A Backbone-style options object.
    * @return {Parse.Promise} Promise that is resolved when the save finishes.
    */
@@ -272,3 +270,5 @@ var DefaultController = {
 };
 
 CoreManager.setFileController(DefaultController);
+
+export default ParseFile;
diff --git a/src/ParseGeoPoint.js b/src/ParseGeoPoint.js
index c531f2b..433c0d2 100644
--- a/src/ParseGeoPoint.js
+++ b/src/ParseGeoPoint.js
@@ -20,9 +20,6 @@ import ParsePromise from './ParsePromise';
  *   new GeoPoint({latitude: 30, longitude: 30})
  *   new GeoPoint()  // defaults to (0, 0)
  *   </pre>
- * @class Parse.GeoPoint
- * @constructor
- *
  * <p>Represents a latitude / longitude point that may be associated
  * with a key in a ParseObject or used as a reference point for geo queries.
  * This allows proximity-based queries on the key.</p>
@@ -34,11 +31,16 @@ import ParsePromise from './ParsePromise';
  *   var object = new Parse.Object("PlaceObject");
  *   object.set("location", point);
  *   object.save();</pre></p>
+ * @alias Parse.GeoPoint
  */
-export default class ParseGeoPoint {
+class ParseGeoPoint {
   _latitude: number;
   _longitude: number;
 
+  /**
+   * @param {(Number[]|Object|Number)} options Either a list of coordinate pairs, an object with `latitude`, `longitude`, or the latitude or the point. 
+   * @param {Number} longitude The longitude of the GeoPoint
+   */
   constructor(
     arg1: Array<number> |
     { latitude: number; longitude: number } |
@@ -94,7 +96,7 @@ export default class ParseGeoPoint {
 
   /**
    * Returns a JSON representation of the GeoPoint, suitable for Parse.
-   * @method toJSON
+
    * @return {Object}
    */
   toJSON(): { __type: string; latitude: number; longitude: number } {
@@ -116,7 +118,7 @@ export default class ParseGeoPoint {
 
   /**
    * Returns the distance from this GeoPoint to another in radians.
-   * @method radiansTo
+
    * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
    * @return {Number}
    */
@@ -140,7 +142,7 @@ export default class ParseGeoPoint {
 
   /**
    * Returns the distance from this GeoPoint to another in kilometers.
-   * @method kilometersTo
+
    * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
    * @return {Number}
    */
@@ -150,7 +152,7 @@ export default class ParseGeoPoint {
 
   /**
    * Returns the distance from this GeoPoint to another in miles.
-   * @method milesTo
+
    * @param {Parse.GeoPoint} point the other Parse.GeoPoint.
    * @return {Number}
    */
@@ -158,7 +160,7 @@ export default class ParseGeoPoint {
     return this.radiansTo(point) * 3958.8;
   }
 
-  /**
+  /*
    * Throws an exception if the given lat-long is out of bounds.
    */
   static _validate(latitude: number, longitude: number) {
@@ -192,7 +194,7 @@ export default class ParseGeoPoint {
   /**
    * Creates a GeoPoint with the user's current location, if available.
    * Calls options.success with a new GeoPoint instance or calls options.error.
-   * @method current
+
    * @param {Object} options An object with success and error callbacks.
    * @static
    */
@@ -209,3 +211,5 @@ export default class ParseGeoPoint {
     return promise._thenRunCallbacks(options);
   }
 }
+
+export default ParseGeoPoint;
diff --git a/src/ParseLiveQuery.js b/src/ParseLiveQuery.js
index 3642c14..5523d2a 100644
--- a/src/ParseLiveQuery.js
+++ b/src/ParseLiveQuery.js
@@ -59,7 +59,7 @@ let LiveQuery = new EventEmitter();
  * After open is called, the LiveQuery will try to send a connect request
  * to the LiveQuery server.
  * 
- * @method open
+
  */ 
 LiveQuery.open = open;
 
@@ -70,7 +70,7 @@ LiveQuery.open = open;
  * If you call query.subscribe() after this, we'll create a new WebSocket
  * connection to the LiveQuery server.
  * 
- * @method close
+
  */
 
 LiveQuery.close = close;
diff --git a/src/ParseObject.js b/src/ParseObject.js
index 9d82a60..9973947 100644
--- a/src/ParseObject.js
+++ b/src/ParseObject.js
@@ -82,37 +82,28 @@ function getServerUrlPath() {
 
 /**
  * Creates a new model with defined attributes.
- *
- * <p>You won't normally call this method directly.  It is recommended that
- * you use a subclass of <code>Parse.Object</code> instead, created by calling
- * <code>extend</code>.</p>
- *
- * <p>However, if you don't want to use a subclass, or aren't sure which
- * subclass is appropriate, you can use this form:<pre>
- *     var object = new Parse.Object("ClassName");
- * </pre>
- * That is basically equivalent to:<pre>
- *     var MyClass = Parse.Object.extend("ClassName");
- *     var object = new MyClass();
- * </pre></p>
- *
- * @class Parse.Object
- * @constructor
- * @param {String} className The class name for the object
- * @param {Object} attributes The initial set of data to store in the object.
- * @param {Object} options The options for this object instance.
+  *
+  * <p>You won't normally call this method directly.  It is recommended that
+  * you use a subclass of <code>Parse.Object</code> instead, created by calling
+  * <code>extend</code>.</p>
+  *
+  * <p>However, if you don't want to use a subclass, or aren't sure which
+  * subclass is appropriate, you can use this form:<pre>
+  *     var object = new Parse.Object("ClassName");
+  * </pre>
+  * That is basically equivalent to:<pre>
+  *     var MyClass = Parse.Object.extend("ClassName");
+  *     var object = new MyClass();
+  * </pre></p>
+  *
+ * @alias Parse.Object
  */
-export default class ParseObject {
+class ParseObject {
   /**
-   * The ID of this object, unique within its class.
-   * @property id
-   * @type String
+   * @param {String} className The class name for the object
+   * @param {Object} attributes The initial set of data to store in the object.
+   * @param {Object} options The options for this object instance.
    */
-  id: ?string;
-  _localId: ?string;
-  _objCount: number;
-  className: string;
-
   constructor(className: ?string | { className: string, [attr: string]: mixed }, attributes?: { [attr: string]: mixed }, options?: { ignoreValidation: boolean }) {
     // Enable legacy initializers
     if (typeof this.initialize === 'function') {
@@ -143,6 +134,16 @@ export default class ParseObject {
     }
   }
 
+  /**
+   * The ID of this object, unique within its class.
+   * @property id
+   * @type String
+   */
+  id: ?string;
+  _localId: ?string;
+  _objCount: number;
+  className: string;
+
   /** Prototype getters / setters **/
 
   get attributes(): AttributeMap {
@@ -407,7 +408,6 @@ export default class ParseObject {
 
   /**
    * Returns a JSON version of the object suitable for saving to Parse.
-   * @method toJSON
    * @return {Object}
    */
   toJSON(seen: Array<any> | void): AttributeMap {
@@ -435,7 +435,7 @@ export default class ParseObject {
 
   /**
    * Determines whether this ParseObject is equal to another ParseObject
-   * @method equals
+   * @param {Object} other - An other object ot compare
    * @return {Boolean}
    */
   equals(other: mixed): boolean {
@@ -454,7 +454,6 @@ export default class ParseObject {
    * Returns true if this object has been modified since its last
    * save/refresh.  If an attribute is specified, it returns true only if that
    * particular attribute has been modified since the last save/refresh.
-   * @method dirty
    * @param {String} attr An attribute name (optional).
    * @return {Boolean}
    */
@@ -486,8 +485,7 @@ export default class ParseObject {
 
   /**
    * Returns an array of keys that have been modified since last save/refresh
-   * @method dirtyKeys
-   * @return {Array of string}
+   * @return {String[]}
    */
   dirtyKeys(): Array<string> {
     var pendingOps = this._getPendingOps();
@@ -506,8 +504,7 @@ export default class ParseObject {
 
   /**
    * Gets a Pointer referencing this Object.
-   * @method toPointer
-   * @return {Object}
+   * @return {Pointer}
    */
   toPointer(): Pointer {
     if (!this.id) {
@@ -522,7 +519,6 @@ export default class ParseObject {
 
   /**
    * Gets the value of an attribute.
-   * @method get
    * @param {String} attr The string name of an attribute.
    */
   get(attr: string): mixed {
@@ -531,8 +527,8 @@ export default class ParseObject {
 
   /**
    * Gets a relation on the given class for the attribute.
-   * @method relation
    * @param String attr The attribute to get the relation for.
+   * @return {Parse.Relation}
    */
   relation(attr: string): ParseRelation {
     var value = this.get(attr);
@@ -548,7 +544,6 @@ export default class ParseObject {
 
   /**
    * Gets the HTML-escaped value of an attribute.
-   * @method escape
    * @param {String} attr The string name of an attribute.
    */
   escape(attr: string): string {
@@ -569,7 +564,6 @@ export default class ParseObject {
   /**
    * Returns <code>true</code> if the attribute contains a value that is not
    * null or undefined.
-   * @method has
    * @param {String} attr The string name of the attribute.
    * @return {Boolean}
    */
@@ -603,7 +597,6 @@ export default class ParseObject {
    *
    *   game.set("finished", true);</pre></p>
    *
-   * @method set
    * @param {String} key The key to set.
    * @param {} value The value to give it.
    * @param {Object} options A set of options for the set.
@@ -697,7 +690,6 @@ export default class ParseObject {
   /**
    * Remove an attribute from the model. This is a noop if the attribute doesn't
    * exist.
-   * @method unset
    * @param {String} attr The string name of an attribute.
    */
   unset(attr: string, options?: { [opt: string]: mixed }): ParseObject | boolean {
@@ -710,7 +702,6 @@ export default class ParseObject {
    * Atomically increments the value of the given attribute the next time the
    * object is saved. If no amount is specified, 1 is used by default.
    *
-   * @method increment
    * @param attr {String} The key.
    * @param amount {Number} The amount to increment by (optional).
    */
@@ -727,9 +718,9 @@ export default class ParseObject {
   /**
    * Atomically add an object to the end of the array associated with a given
    * key.
-   * @method add
    * @param attr {String} The key.
    * @param item {} The item to add.
+   * @return {(ParseObject|Boolean)}
    */
   add(attr: string, item: mixed): ParseObject | boolean {
     return this.set(attr, new AddOp([item]));
@@ -738,9 +729,8 @@ export default class ParseObject {
   /**
    * Atomically add the objects to the end of the array associated with a given
    * key.
-   * @method addAll
    * @param attr {String} The key.
-   * @param items {[]} The items to add.
+   * @param items {Object[]} The items to add.
    */
   addAll(attr: string, items: Array<mixed>): ParseObject | boolean {
     return this.set(attr, new AddOp(items));
@@ -751,7 +741,6 @@ export default class ParseObject {
    * if it is not already present in the array. The position of the insert is
    * not guaranteed.
    *
-   * @method addUnique
    * @param attr {String} The key.
    * @param item {} The object to add.
    */
@@ -764,9 +753,8 @@ export default class ParseObject {
    * if it is not already present in the array. The position of the insert is
    * not guaranteed.
    *
-   * @method addAllUnique
    * @param attr {String} The key.
-   * @param items {[]} The objects to add.
+   * @param items {Object[]} The objects to add.
    */
   addAllUnique(attr: string, items: Array<mixed>): ParseObject | boolean {
     return this.set(attr, new AddUniqueOp(items));
@@ -776,7 +764,6 @@ export default class ParseObject {
    * Atomically remove all instances of an object from the array associated
    * with a given key.
    *
-   * @method remove
    * @param attr {String} The key.
    * @param item {} The object to remove.
    */
@@ -788,9 +775,8 @@ export default class ParseObject {
    * Atomically remove all instances of the objects from the array associated
    * with a given key.
    *
-   * @method removeAll
    * @param attr {String} The key.
-   * @param items {[]} The object to remove.
+   * @param items {Object[]} The object to remove.
    */
   removeAll(attr: string, items: Array<mixed>): ParseObject | boolean {
     return this.set(attr, new RemoveOp(items));
@@ -802,7 +788,6 @@ export default class ParseObject {
    * saved. For example, after calling object.increment("x"), calling
    * object.op("x") would return an instance of Parse.Op.Increment.
    *
-   * @method op
    * @param attr {String} The key.
    * @returns {Parse.Op} The operation, or undefined if none.
    */
@@ -817,7 +802,6 @@ export default class ParseObject {
 
   /**
    * Creates a new model with identical attributes to this one, similar to Backbone.Model's clone()
-   * @method clone
    * @return {Parse.Object}
    */
   clone(): any {
@@ -846,7 +830,6 @@ export default class ParseObject {
 
   /**
    * Creates a new instance of this object. Not to be confused with clone()
-   * @method newInstance
    * @return {Parse.Object}
    */
   newInstance(): any {
@@ -869,7 +852,6 @@ export default class ParseObject {
 
   /**
    * Returns true if this object has never been saved to Parse.
-   * @method isNew
    * @return {Boolean}
    */
   isNew(): boolean {
@@ -880,7 +862,6 @@ export default class ParseObject {
    * Returns true if this object was created by the Parse server when the
    * object might have already been there (e.g. in the case of a Facebook
    * login)
-   * @method existed
    * @return {Boolean}
    */
   existed(): boolean {
@@ -897,7 +878,6 @@ export default class ParseObject {
 
   /**
    * Checks if the model is currently in a valid state.
-   * @method isValid
    * @return {Boolean}
    */
   isValid(): boolean {
@@ -910,7 +890,6 @@ export default class ParseObject {
    * to provide additional validation on <code>set</code> and
    * <code>save</code>.  Your implementation should return
    *
-   * @method validate
    * @param {Object} attrs The current data to validate.
    * @return {} False if the data is valid.  An error object otherwise.
    * @see Parse.Object#set
@@ -932,7 +911,6 @@ export default class ParseObject {
 
   /**
    * Returns the ACL for this object.
-   * @method getACL
    * @returns {Parse.ACL} An instance of Parse.ACL.
    * @see Parse.Object#get
    */
@@ -946,7 +924,6 @@ export default class ParseObject {
 
   /**
    * Sets the ACL to be used for this object.
-   * @method setACL
    * @param {Parse.ACL} acl An instance of Parse.ACL.
    * @param {Object} options Optional Backbone-like options object to be
    *     passed in to set.
@@ -959,7 +936,6 @@ export default class ParseObject {
 
   /**
    * Clears any changes to this object made since the last call to save()
-   * @method revert
    */
   revert(): void {
     this._clearPendingOps();
@@ -967,7 +943,7 @@ export default class ParseObject {
 
   /**
    * Clears all attributes on a model
-   * @method clear
+   * @return {(ParseObject | boolean)}
    */
   clear(): ParseObject | boolean {
     var attributes = this.attributes;
@@ -988,7 +964,6 @@ export default class ParseObject {
    * Fetch the model from the server. If the server's representation of the
    * model differs from its current attributes, they will be overriden.
    *
-   * @method fetch
    * @param {Object} options A Backbone-style callback object.
    * Valid options are:<ul>
    *   <li>success: A Backbone-style success callback.
@@ -1048,7 +1023,6 @@ export default class ParseObject {
    *     // The save failed.  Error is an instance of Parse.Error.
    *   });</pre>
    *
-   * @method save
    * @param {Object} options A Backbone-style callback object.
    * Valid options are:<ul>
    *   <li>success: A Backbone-style success callback.
@@ -1124,7 +1098,6 @@ export default class ParseObject {
    * If `wait: true` is passed, waits for the server to respond
    * before removal.
    *
-   * @method destroy
    * @param {Object} options A Backbone-style callback object.
    * Valid options are:<ul>
    *   <li>success: A Backbone-style success callback
@@ -1177,7 +1150,6 @@ export default class ParseObject {
    *   });
    * </pre>
    *
-   * @method fetchAll
    * @param {Array} list A list of <code>Parse.Object</code>.
    * @param {Object} options A Backbone-style callback object.
    * @static
@@ -1218,7 +1190,6 @@ export default class ParseObject {
    *   });
    * </pre>
    *
-   * @method fetchAllIfNeeded
    * @param {Array} list A list of <code>Parse.Object</code>.
    * @param {Object} options A Backbone-style callback object.
    * @static
@@ -1286,7 +1257,6 @@ export default class ParseObject {
    *   });
    * </pre>
    *
-   * @method destroyAll
    * @param {Array} list A list of <code>Parse.Object</code>.
    * @param {Object} options A Backbone-style callback object.
    * @static
@@ -1330,7 +1300,6 @@ export default class ParseObject {
    *   });
    * </pre>
    *
-   * @method saveAll
    * @param {Array} list A list of <code>Parse.Object</code>.
    * @param {Object} options A Backbone-style callback object.
    * @static
@@ -1367,7 +1336,6 @@ export default class ParseObject {
    *  pointerToFoo.id = "myObjectId";
    * </pre>
    *
-   * @method createWithoutData
    * @param {String} id The ID of the object to create a reference to.
    * @static
    * @return {Parse.Object} A Parse.Object reference.
@@ -1380,7 +1348,6 @@ export default class ParseObject {
 
   /**
    * Creates a new instance of a Parse Object from a JSON representation.
-   * @method fromJSON
    * @param {Object} json The JSON map of the Object's data
    * @param {boolean} override In single instance mode, all old server data
    *   is overwritten if this is set to true
@@ -1425,7 +1392,6 @@ export default class ParseObject {
    * When objects of that class are retrieved from a query, they will be
    * instantiated with this subclass.
    * This is only necessary when using ES6 subclassing.
-   * @method registerSubclass
    * @param {String} className The class name of the subclass
    * @param {Class} constructor The subclass
    */
@@ -1478,7 +1444,6 @@ export default class ParseObject {
    *         <i>Class properties</i>
    *     });</pre></p>
    *
-   * @method extend
    * @param {String} className The name of the Parse class backing this model.
    * @param {Object} protoProps Instance properties to add to instances of the
    *     class returned from this method.
@@ -1577,7 +1542,7 @@ export default class ParseObject {
    * share the same attributes, and stay synchronized with each other.
    * This is disabled by default in server environments, since it can lead to
    * security issues.
-   * @method enableSingleInstance
+   * @static
    */
   static enableSingleInstance() {
     singleInstance = true;
@@ -1589,7 +1554,7 @@ export default class ParseObject {
    * share the same attributes, and stay synchronized with each other.
    * When disabled, you can have two instances of the same object in memory
    * without them sharing attributes.
-   * @method disableSingleInstance
+   * @static
    */
   static disableSingleInstance() {
     singleInstance = false;
@@ -1888,3 +1853,5 @@ var DefaultController = {
 }
 
 CoreManager.setObjectController(DefaultController);
+
+export default ParseObject;
diff --git a/src/ParsePolygon.js b/src/ParsePolygon.js
index 0eed04c..8219b5c 100644
--- a/src/ParsePolygon.js
+++ b/src/ParsePolygon.js
@@ -17,9 +17,7 @@ import ParseGeoPoint from './ParseGeoPoint';
  *   new Polygon([[0,0],[0,1],[1,1],[1,0]])
  *   new Polygon([GeoPoint, GeoPoint, GeoPoint])
  *   </pre>
- * @class Parse.GeoPoint
- * @constructor
- *
+ * 
  * <p>Represents a coordinates that may be associated
  * with a key in a ParseObject or used as a reference point for geo queries.
  * This allows proximity-based queries on the key.</p>
@@ -29,10 +27,14 @@ import ParseGeoPoint from './ParseGeoPoint';
  *   var object = new Parse.Object("PlaceObject");
  *   object.set("area", polygon);
  *   object.save();</pre></p>
+ * @alias Parse.Polygon
  */
-export default class ParsePolygon {
+class ParsePolygon {
   _coordinates: Array;
 
+  /**
+   * @param {(Number[][]|Parse.GeoPoint[])} coordinates An Array of coordinate pairs
+   */
   constructor(
     arg1: Array,
   ) {
@@ -55,7 +57,6 @@ export default class ParsePolygon {
 
   /**
    * Returns a JSON representation of the GeoPoint, suitable for Parse.
-   * @method toJSON
    * @return {Object}
    */
   toJSON(): { __type: string; coordinates: Array;} {
@@ -66,6 +67,11 @@ export default class ParsePolygon {
     };
   }
 
+  /**
+   * Checks if two polygons are equal
+   * @param {(Parse.Polygon|Object)} other
+   * @returns {Boolean}
+   */
   equals(other: mixed): boolean {
     if (!(other instanceof ParsePolygon) || (this.coordinates.length !== other.coordinates.length)) {
       return false;
@@ -82,6 +88,11 @@ export default class ParsePolygon {
     return isEqual;
   }
 
+  /**
+   * 
+   * @param {Parse.GeoPoint} point 
+   * @returns {Boolean} wether the points is contained into the polygon
+   */
   containsPoint(point: ParseGeoPoint): boolean {
     let minX = this._coordinates[0][0];
     let maxX = this._coordinates[0][0];
@@ -119,8 +130,9 @@ export default class ParsePolygon {
   }
 
   /**
-   * Throws an exception if the given lat-long is out of bounds.
-   * @return {Array}
+   * Validates that the list of coordinates can form a valid polygon
+   * @param {Array} coords the list of coordinated to validate as a polygon
+   * @throws {TypeError}
    */
   static _validate(coords: Array) {
     if (!Array.isArray(coords)) {
@@ -145,3 +157,5 @@ export default class ParsePolygon {
     return points;
   }
 }
+
+export default ParsePolygon;
diff --git a/src/ParsePromise.js b/src/ParsePromise.js
index c5625bc..f1b2c01 100644
--- a/src/ParsePromise.js
+++ b/src/ParsePromise.js
@@ -7,6 +7,9 @@
  * of patent rights can be found in the PATENTS file in the same directory.
  */
 
+ /**
+  * @private
+  */
 var isPromisesAPlusCompliant = true;
 
 /**
@@ -22,10 +25,9 @@ var isPromisesAPlusCompliant = true;
  *    });
  * </pre></p>
  *
- * @class Parse.Promise
- * @constructor
+ * @alias Parse.Promise
  */
-export default class ParsePromise {
+class ParsePromise {
   constructor(executor) {
     this._resolved = false;
     this._rejected = false;
@@ -39,7 +41,7 @@ export default class ParsePromise {
 
   /**
    * Marks this promise as fulfilled, firing any callbacks waiting on it.
-   * @method resolve
+
    * @param {Object} result the result to pass to the callbacks.
    */
   resolve(...results) {
@@ -61,7 +63,7 @@ export default class ParsePromise {
 
   /**
    * Marks this promise as fulfilled, firing any callbacks waiting on it.
-   * @method reject
+
    * @param {Object} error the error to pass to the callbacks.
    */
   reject(error) {
@@ -86,7 +88,7 @@ export default class ParsePromise {
    * chaining. If the callback itself returns a Promise, then the one returned
    * by "then" will not be fulfilled until that one returned by the callback
    * is fulfilled.
-   * @method then
+
    * @param {Function} resolvedCallback Function that is called when this
    * Promise is resolved. Once the callback is complete, then the Promise
    * returned by "then" will also be fulfilled.
@@ -191,7 +193,7 @@ export default class ParsePromise {
   /**
    * Add handlers to be called when the promise
    * is either resolved or rejected
-   * @method always
+
    */
   always(callback) {
     return this.then(callback, callback);
@@ -199,7 +201,7 @@ export default class ParsePromise {
 
   /**
    * Add handlers to be called when the Promise object is resolved
-   * @method done
+
    */
   done(callback) {
     return this.then(callback);
@@ -208,7 +210,7 @@ export default class ParsePromise {
   /**
    * Add handlers to be called when the Promise object is rejected
    * Alias for catch().
-   * @method fail
+
    */
   fail(callback) {
     return this.then(null, callback);
@@ -216,7 +218,7 @@ export default class ParsePromise {
 
   /**
    * Add handlers to be called when the Promise object is rejected
-   * @method catch
+
    */
   catch(callback) {
     return this.then(null, callback);
@@ -224,7 +226,7 @@ export default class ParsePromise {
 
   /**
    * Run the given callbacks after this promise is fulfilled.
-   * @method _thenRunCallbacks
+
    * @param optionsOrCallback {} A Backbone-style options callback, or a
    * callback function. If this is an options object and contains a "model"
    * attributes, that will be passed to error callbacks as the first argument.
@@ -276,7 +278,7 @@ export default class ParsePromise {
    * array of results for its first argument, or the error as its second,
    * depending on whether this Promise was rejected or resolved. Returns a
    * new Promise, like "then" would.
-   * @method _continueWith
+
    * @param {Function} continuation the callback.
    */
   _continueWith(continuation) {
@@ -289,7 +291,7 @@ export default class ParsePromise {
 
   /**
    * Returns true iff the given object fulfils the Promise interface.
-   * @method is
+
    * @param {Object} promise The object to test
    * @static
    * @return {Boolean}
@@ -303,7 +305,7 @@ export default class ParsePromise {
 
   /**
    * Returns a new promise that is resolved with a given value.
-   * @method as
+
    * @param value The value to resolve the promise with
    * @static
    * @return {Parse.Promise} the new promise.
@@ -319,7 +321,7 @@ export default class ParsePromise {
    * If that value is a thenable Promise (has a .then() prototype
    * method), the new promise will be chained to the end of the
    * value.
-   * @method resolve
+
    * @param value The value to resolve the promise with
    * @static
    * @return {Parse.Promise} the new promise.
@@ -336,7 +338,7 @@ export default class ParsePromise {
 
   /**
    * Returns a new promise that is rejected with a given error.
-   * @method error
+
    * @param error The error to reject the promise with
    * @static
    * @return {Parse.Promise} the new promise.
@@ -351,7 +353,7 @@ export default class ParsePromise {
    * Returns a new promise that is rejected with a given error.
    * This is an alias for Parse.Promise.error, for compliance with
    * the ES6 implementation.
-   * @method reject
+
    * @param error The error to reject the promise with
    * @static
    * @return {Parse.Promise} the new promise.
@@ -383,7 +385,7 @@ export default class ParsePromise {
    *     console.log(results);  // prints [1,2,3]
    *   });
    * </pre>
-   * @method when
+
    * @param {Array} promises a list of promises to wait for.
    * @static
    * @return {Parse.Promise} the new promise.
@@ -464,7 +466,7 @@ export default class ParsePromise {
    *     console.log(r3);  // prints 3
    *   });</pre>
    *
-   * @method all
+
    * @param {Iterable} promises an iterable of promises to wait for.
    * @static
    * @return {Parse.Promise} the new promise.
@@ -521,7 +523,7 @@ export default class ParsePromise {
    * complete is rejected, the returned promise will be rejected with the
    * same reason.
    *
-   * @method race
+
    * @param {Iterable} promises an iterable of promises to wait for.
    * @static
    * @return {Parse.Promise} the new promise.
@@ -557,7 +559,7 @@ export default class ParsePromise {
    * Runs the given asyncFunction repeatedly, as long as the predicate
    * function returns a truthy value. Stops repeating if asyncFunction returns
    * a rejected promise.
-   * @method _continueWhile
+
    * @param {Function} predicate should return false when ready to stop.
    * @param {Function} asyncFunction should return a Promise.
    * @static
@@ -583,3 +585,5 @@ export default class ParsePromise {
     isPromisesAPlusCompliant = false;
   }
 }
+
+export default ParsePromise;
diff --git a/src/ParseQuery.js b/src/ParseQuery.js
index 77fc9b1..7bea22e 100644
--- a/src/ParseQuery.js
+++ b/src/ParseQuery.js
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Parse, LLC.
  * All rights reserved.
  *
@@ -40,12 +40,13 @@ export type QueryJSON = {
  * Converts a string into a regex that matches it.
  * Surrounding with \Q .. \E does this, we just need to escape any \E's in
  * the text separately.
+ * @private
  */
 function quote(s: string) {
   return '\\Q' + s.replace('\\E', '\\E\\\\E\\Q') + '\\E';
 }
 
-/**
+/*
  * Handles pre-populating the result data of a query with select fields,
  * making sure that the data object contains keys for all objects that have
  * been requested with a select, so that our cached state updates correctly.
@@ -67,16 +68,19 @@ function handleSelectResult(data: any, select: Array<string>){
 
       pathComponents.forEach((component, index, arr) => {
         // add keys if the expected data is missing
-        if (!obj[component]) {
-          obj[component] = (index == arr.length-1) ? undefined : {};
+        if (obj && !obj.hasOwnProperty(component)) {
+          obj[component] = undefined;
+        }
+        if (obj !== undefined) {
+          obj = obj[component];
         }
-        obj = obj[component];
 
         //add this path component to the server mask so we can fill it in later if needed
         if (index < arr.length-1) {
           if (!serverMask[component]) {
             serverMask[component] = {};
           }
+          serverMask = serverMask[component];
         }
       });
     }
@@ -100,8 +104,10 @@ function handleSelectResult(data: any, select: Array<string>){
         }
       }
       for (var key in mask) {
-        //traverse into objects as needed
-        copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+        if (dest[key] !== undefined && dest[key] !== null && src !== undefined && src !== null) {
+          //traverse into objects as needed
+          copyMissingDataWithMask(src[key], dest[key], mask[key], true);
+        }
       }
     }
 
@@ -111,61 +117,63 @@ function handleSelectResult(data: any, select: Array<string>){
 
 /**
  * Creates a new parse Parse.Query for the given Parse.Object subclass.
- * @class Parse.Query
- * @constructor
- * @param {} objectClass An instance of a subclass of Parse.Object, or a Parse className string.
  *
  * <p>Parse.Query defines a query that is used to fetch Parse.Objects. The
  * most common use case is finding all objects that match a query through the
- * <code>find</code> method. For example, this sample code fetches all objects
- * of class <code>MyClass</code>. It calls a different function depending on
+ * <code>find</code> method. for example, this sample code fetches all objects
+ * of class <code>myclass</code>. it calls a different function depending on
  * whether the fetch succeeded or not.
  *
  * <pre>
- * var query = new Parse.Query(MyClass);
+ * var query = new parse.query(myclass);
  * query.find({
  *   success: function(results) {
- *     // results is an array of Parse.Object.
+ *     // results is an array of parse.object.
  *   },
  *
  *   error: function(error) {
- *     // error is an instance of Parse.Error.
+ *     // error is an instance of parse.error.
  *   }
  * });</pre></p>
  *
- * <p>A Parse.Query can also be used to retrieve a single object whose id is
- * known, through the get method. For example, this sample code fetches an
- * object of class <code>MyClass</code> and id <code>myId</code>. It calls a
+ * <p>a parse.query can also be used to retrieve a single object whose id is
+ * known, through the get method. for example, this sample code fetches an
+ * object of class <code>myclass</code> and id <code>myid</code>. it calls a
  * different function depending on whether the fetch succeeded or not.
  *
  * <pre>
- * var query = new Parse.Query(MyClass);
- * query.get(myId, {
+ * var query = new parse.query(myclass);
+ * query.get(myid, {
  *   success: function(object) {
- *     // object is an instance of Parse.Object.
+ *     // object is an instance of parse.object.
  *   },
  *
  *   error: function(object, error) {
- *     // error is an instance of Parse.Error.
+ *     // error is an instance of parse.error.
  *   }
  * });</pre></p>
  *
- * <p>A Parse.Query can also be used to count the number of objects that match
- * the query without retrieving all of those objects. For example, this
- * sample code counts the number of objects of the class <code>MyClass</code>
+ * <p>a parse.query can also be used to count the number of objects that match
+ * the query without retrieving all of those objects. for example, this
+ * sample code counts the number of objects of the class <code>myclass</code>
  * <pre>
- * var query = new Parse.Query(MyClass);
+ * var query = new parse.query(myclass);
  * query.count({
  *   success: function(number) {
- *     // There are number instances of MyClass.
+ *     // there are number instances of myclass.
  *   },
  *
  *   error: function(error) {
  *     // error is an instance of Parse.Error.
  *   }
  * });</pre></p>
+ * @alias Parse.Query
  */
-export default class ParseQuery {
+class ParseQuery {
+  /**
+   * @property className
+   * @type String
+   */
   className: string;
   _where: any;
   _include: Array<string>;
@@ -175,6 +183,9 @@ export default class ParseQuery {
   _order: Array<string>;
   _extraOptions: { [key: string]: mixed };
 
+  /**
+   * @param {(String|Parse.Object)} objectClass An instance of a subclass of Parse.Object, or a Parse className string.
+   */
   constructor(objectClass: string | ParseObject) {
     if (typeof objectClass === 'string') {
       if (objectClass === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) {
@@ -206,7 +217,6 @@ export default class ParseQuery {
 
   /**
    * Adds constraint that at least one of the passed in queries matches.
-   * @method _orQuery
    * @param {Array} queries
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -232,7 +242,6 @@ export default class ParseQuery {
 
   /**
    * Returns a JSON representation of this query.
-   * @method toJSON
    * @return {Object} The JSON representation of the query.
    */
   toJSON(): QueryJSON {
@@ -279,7 +288,6 @@ export default class ParseQuery {
    *
    * and continue to query...
    * query.skip(100).find().then(...);
-   * @method withJSON
    * @param {QueryJSON} json from Parse.Query.toJSON() method
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -336,7 +344,6 @@ export default class ParseQuery {
    * the server.  Either options.success or options.error is called when the
    * find completes.
    *
-   * @method get
    * @param {String} objectId The id of the object to be fetched.
    * @param {Object} options A Backbone-style options object.
    * Valid options are:<ul>
@@ -380,7 +387,6 @@ export default class ParseQuery {
    * Either options.success or options.error is called when the find
    * completes.
    *
-   * @method find
    * @param {Object} options A Backbone-style options object. Valid options
    * are:<ul>
    *   <li>success: Function to call when the find completes successfully.
@@ -439,7 +445,6 @@ export default class ParseQuery {
    * Either options.success or options.error is called when the count
    * completes.
    *
-   * @method count
    * @param {Object} options A Backbone-style options object. Valid options
    * are:<ul>
    *   <li>success: Function to call when the count completes successfully.
@@ -479,13 +484,97 @@ export default class ParseQuery {
     })._thenRunCallbacks(options);
   }
 
+  /**
+   * Executes a distinct query and returns unique values
+   *
+   * @param {String} key A field to find distinct values
+   * @param {Object} options A Backbone-style options object. Valid options
+   * are:<ul>
+   *   <li>success: Function to call when the count completes successfully.
+   *   <li>error: Function to call when the find fails.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the query completes.
+   */
+  distinct(key: string, options?: FullOptions): ParsePromise {
+    options = options || {};
+
+    const distinctOptions = {
+      useMasterKey: true
+    };
+    if (options.hasOwnProperty('sessionToken')) {
+      distinctOptions.sessionToken = options.sessionToken;
+    }
+    const controller = CoreManager.getQueryController();
+    const params = {
+      distinct: key,
+      where: this._where
+    };
+
+    return controller.aggregate(
+      this.className,
+      params,
+      distinctOptions
+    ).then((results) => {
+      return results.results;
+    })._thenRunCallbacks(options);
+  }
+
+   /**
+   * Executes an aggregate query and returns aggregate results
+   *
+   * @param {Mixed} pipeline Array or Object of stages to process query
+   * @param {Object} options A Backbone-style options object. Valid options
+   * are:<ul>
+   *   <li>success: Function to call when the count completes successfully.
+   *   <li>error: Function to call when the find fails.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the query completes.
+   */
+  aggregate(pipeline: mixed, options?: FullOptions): ParsePromise {
+    options = options || {};
+
+    const aggregateOptions = {
+      useMasterKey: true
+    };
+    if (options.hasOwnProperty('sessionToken')) {
+      aggregateOptions.sessionToken = options.sessionToken;
+    }
+    const controller = CoreManager.getQueryController();
+    let stages = {};
+
+    if (Array.isArray(pipeline)) {
+      pipeline.forEach((stage) => {
+        for (let op in stage) {
+          stages[op] = stage[op];
+        }
+      });
+    } else if (pipeline && typeof pipeline === 'object') {
+      stages = pipeline;
+    } else {
+      throw new Error('Invalid pipeline must be Array or Object');
+    }
+
+    return controller.aggregate(
+      this.className,
+      stages,
+      aggregateOptions
+    ).then((results) => {
+      return results.results;
+    })._thenRunCallbacks(options);
+  }
+
   /**
    * Retrieves at most one Parse.Object that satisfies this query.
    *
    * Either options.success or options.error is called when it completes.
    * success is passed the object if there is one. otherwise, undefined.
    *
-   * @method first
    * @param {Object} options A Backbone-style options object. Valid options
    * are:<ul>
    *   <li>success: Function to call when the find completes successfully.
@@ -548,7 +637,6 @@ export default class ParseQuery {
    * promise, then iteration will stop with that error. The items are
    * processed in an unspecified order. The query may not have any sort order,
    * and may not use limit or skip.
-   * @method each
    * @param {Function} callback Callback that will be called with each result
    *     of the query.
    * @param {Object} options A Backbone-style options object. Valid options
@@ -642,7 +730,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be equal to the provided value.
-   * @method equalTo
    * @param {String} key The key to check.
    * @param value The value that the Parse.Object must contain.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -659,7 +746,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be not equal to the provided value.
-   * @method notEqualTo
    * @param {String} key The key to check.
    * @param value The value that must not be equalled.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -671,7 +757,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be less than the provided value.
-   * @method lessThan
    * @param {String} key The key to check.
    * @param value The value that provides an upper bound.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -683,7 +768,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be greater than the provided value.
-   * @method greaterThan
    * @param {String} key The key to check.
    * @param value The value that provides an lower bound.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -695,7 +779,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be less than or equal to the provided value.
-   * @method lessThanOrEqualTo
    * @param {String} key The key to check.
    * @param value The value that provides an upper bound.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -707,7 +790,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be greater than or equal to the provided value.
-   * @method greaterThanOrEqualTo
    * @param {String} key The key to check.
    * @param value The value that provides an lower bound.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -719,7 +801,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * be contained in the provided list of values.
-   * @method containedIn
    * @param {String} key The key to check.
    * @param {Array} values The values that will match.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -731,7 +812,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * not be contained in the provided list of values.
-   * @method notContainedIn
    * @param {String} key The key to check.
    * @param {Array} values The values that will not match.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -743,7 +823,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint to the query that requires a particular key's value to
    * contain each one of the provided list of values.
-   * @method containsAll
    * @param {String} key The key to check.  This key's value must be an array.
    * @param {Array} values The values that will match.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -754,7 +833,6 @@ export default class ParseQuery {
 
   /**
    * Adds a constraint for finding objects that contain the given key.
-   * @method exists
    * @param {String} key The key that should exist.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -764,7 +842,6 @@ export default class ParseQuery {
 
   /**
    * Adds a constraint for finding objects that do not contain a given key.
-   * @method doesNotExist
    * @param {String} key The key that should not exist
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -776,7 +853,6 @@ export default class ParseQuery {
    * Adds a regular expression constraint for finding string values that match
    * the provided regular expression.
    * This may be slow for large datasets.
-   * @method matches
    * @param {String} key The key that the string to match is stored in.
    * @param {RegExp} regex The regular expression pattern to match.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -801,7 +877,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint that requires that a key's value matches a Parse.Query
    * constraint.
-   * @method matchesQuery
    * @param {String} key The key that the contains the object to match the
    *                     query.
    * @param {Parse.Query} query The query that should match.
@@ -816,7 +891,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint that requires that a key's value not matches a
    * Parse.Query constraint.
-   * @method doesNotMatchQuery
    * @param {String} key The key that the contains the object to match the
    *                     query.
    * @param {Parse.Query} query The query that should not match.
@@ -831,7 +905,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint that requires that a key's value matches a value in
    * an object returned by a different Parse.Query.
-   * @method matchesKeyInQuery
    * @param {String} key The key that contains the value that is being
    *                     matched.
    * @param {String} queryKey The key in the objects returned by the query to
@@ -851,7 +924,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint that requires that a key's value not match a value in
    * an object returned by a different Parse.Query.
-   * @method doesNotMatchKeyInQuery
    * @param {String} key The key that contains the value that is being
    *                     excluded.
    * @param {String} queryKey The key in the objects returned by the query to
@@ -871,7 +943,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint for finding string values that contain a provided
    * string.  This may be slow for large datasets.
-   * @method contains
    * @param {String} key The key that the string to match is stored in.
    * @param {String} substring The substring that the value must contain.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -887,7 +958,6 @@ export default class ParseQuery {
    * Adds a constraint for finding string values that start with a provided
    * string.  This query will use the backend index, so it will be fast even
    * for large datasets.
-   * @method startsWith
    * @param {String} key The key that the string to match is stored in.
    * @param {String} prefix The substring that the value must start with.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -902,7 +972,6 @@ export default class ParseQuery {
   /**
    * Adds a constraint for finding string values that end with a provided
    * string.  This will be slow for large datasets.
-   * @method endsWith
    * @param {String} key The key that the string to match is stored in.
    * @param {String} suffix The substring that the value must end with.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -917,7 +986,6 @@ export default class ParseQuery {
   /**
    * Adds a proximity based constraint for finding objects with key point
    * values near the point given.
-   * @method near
    * @param {String} key The key that the Parse.GeoPoint is stored in.
    * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -933,7 +1001,6 @@ export default class ParseQuery {
   /**
    * Adds a proximity based constraint for finding objects with key point
    * values near the point given and within the maximum distance given.
-   * @method withinRadians
    * @param {String} key The key that the Parse.GeoPoint is stored in.
    * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
    * @param {Number} maxDistance Maximum distance (in radians) of results to
@@ -949,7 +1016,6 @@ export default class ParseQuery {
    * Adds a proximity based constraint for finding objects with key point
    * values near the point given and within the maximum distance given.
    * Radius of earth used is 3958.8 miles.
-   * @method withinMiles
    * @param {String} key The key that the Parse.GeoPoint is stored in.
    * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
    * @param {Number} maxDistance Maximum distance (in miles) of results to
@@ -964,7 +1030,6 @@ export default class ParseQuery {
    * Adds a proximity based constraint for finding objects with key point
    * values near the point given and within the maximum distance given.
    * Radius of earth used is 6371.0 kilometers.
-   * @method withinKilometers
    * @param {String} key The key that the Parse.GeoPoint is stored in.
    * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used.
    * @param {Number} maxDistance Maximum distance (in kilometers) of results
@@ -979,7 +1044,6 @@ export default class ParseQuery {
    * Adds a constraint to the query that requires a particular key's
    * coordinates be contained within a given rectangular geographic bounding
    * box.
-   * @method withinGeoBox
    * @param {String} key The key to be constrained.
    * @param {Parse.GeoPoint} southwest
    *     The lower-left inclusive corner of the box.
@@ -1005,7 +1069,6 @@ export default class ParseQuery {
    *
    * Polygon must have at least 3 points
    *
-   * @method withinPolygon
    * @param {String} key The key to be constrained.
    * @param {Array} array of geopoints
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -1018,7 +1081,6 @@ export default class ParseQuery {
    * Add a constraint to the query that requires a particular key's
    * coordinates that contains a ParseGeoPoint
    *
-   * @method polygonContains
    * @param {String} key The key to be constrained.
    * @param {Parse.GeoPoint} GeoPoint
    * @return {Parse.Query} Returns the query, so you can chain this call.
@@ -1032,8 +1094,7 @@ export default class ParseQuery {
   /**
    * Sorts the results in ascending order by the given key.
    *
-   * @method ascending
-   * @param {(String|String[]|...String} key The key to order by, which is a
+   * @param {(String|String[]|...String)} key The key to order by, which is a
    * string of comma separated values, or an Array of keys, or multiple keys.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1046,8 +1107,7 @@ export default class ParseQuery {
    * Sorts the results in ascending order by the given key,
    * but can also add secondary sort descriptors without overwriting _order.
    *
-   * @method addAscending
-   * @param {(String|String[]|...String} key The key to order by, which is a
+   * @param {(String|String[]|...String)} key The key to order by, which is a
    * string of comma separated values, or an Array of keys, or multiple keys.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1068,8 +1128,7 @@ export default class ParseQuery {
   /**
    * Sorts the results in descending order by the given key.
    *
-   * @method descending
-   * @param {(String|String[]|...String} key The key to order by, which is a
+   * @param {(String|String[]|...String)} key The key to order by, which is a
    * string of comma separated values, or an Array of keys, or multiple keys.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1082,8 +1141,7 @@ export default class ParseQuery {
    * Sorts the results in descending order by the given key,
    * but can also add secondary sort descriptors without overwriting _order.
    *
-   * @method addDescending
-   * @param {(String|String[]|...String} key The key to order by, which is a
+   * @param {(String|String[]|...String)} key The key to order by, which is a
    * string of comma separated values, or an Array of keys, or multiple keys.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1111,7 +1169,6 @@ export default class ParseQuery {
    * Sets the number of results to skip before returning any results.
    * This is useful for pagination.
    * Default is to skip zero results.
-   * @method skip
    * @param {Number} n the number of results to skip.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1126,7 +1183,6 @@ export default class ParseQuery {
   /**
    * Sets the limit of the number of results to return. The default limit is
    * 100, with a maximum of 1000 results being returned at a time.
-   * @method limit
    * @param {Number} n the number of results to limit to.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1141,7 +1197,6 @@ export default class ParseQuery {
   /**
    * Includes nested Parse.Objects for the provided key.  You can use dot
    * notation to specify which fields in the included object are also fetched.
-   * @method include
    * @param {String} key The name of the key to include.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1160,7 +1215,6 @@ export default class ParseQuery {
    * Restricts the fields of the returned Parse.Objects to include only the
    * provided keys.  If this is called multiple times, then all of the keys
    * specified in each of the calls will be included.
-   * @method select
    * @param {Array} keys The names of the keys to include.
    * @return {Parse.Query} Returns the query, so you can chain this call.
    */
@@ -1180,7 +1234,6 @@ export default class ParseQuery {
 
   /**
    * Subscribe this query to get liveQuery updates
-   * @method subscribe
    * @return {LiveQuerySubscription} Returns the liveQuerySubscription, it's an event emitter
    * which can be used to get liveQuery updates.
    */
@@ -1196,7 +1249,6 @@ export default class ParseQuery {
    *
    * will create a compoundQuery that is an or of the query1, query2, and
    * query3.
-   * @method or
    * @param {...Parse.Query} var_args The list of queries to OR.
    * @static
    * @return {Parse.Query} The query that is the OR of the passed in queries.
@@ -1229,7 +1281,20 @@ var DefaultController = {
       params,
       options
     );
+  },
+
+  aggregate(className: string, params: any, options: RequestOptions): ParsePromise {
+    const RESTController = CoreManager.getRESTController();
+
+    return RESTController.request(
+      'GET',
+      'aggregate/' + className,
+      params,
+      options
+    );
   }
 };
 
 CoreManager.setQueryController(DefaultController);
+
+export default ParseQuery;
diff --git a/src/ParseRelation.js b/src/ParseRelation.js
index 9d56e51..febb7a4 100644
--- a/src/ParseRelation.js
+++ b/src/ParseRelation.js
@@ -17,29 +17,30 @@ import ParseQuery from './ParseQuery';
  * Creates a new Relation for the given parent object and key. This
  * constructor should rarely be used directly, but rather created by
  * Parse.Object.relation.
- * @class Parse.Relation
- * @constructor
- * @param {Parse.Object} parent The parent of this relation.
- * @param {String} key The key for this relation on the parent.
  *
  * <p>
  * A class that is used to access all of the children of a many-to-many
  * relationship.  Each instance of Parse.Relation is associated with a
  * particular parent object and key.
  * </p>
+ * @alias Parse.Relation
  */
-export default class ParseRelation {
+class ParseRelation {
   parent: ?ParseObject;
   key: ?string;
   targetClassName: ?string;
 
+  /**
+   * @param {Parse.Object} parent The parent of this relation.
+   * @param {String} key The key for this relation on the parent.
+   */
   constructor(parent: ?ParseObject, key: ?string) {
     this.parent = parent;
     this.key = key;
     this.targetClassName = null;
   }
 
-  /**
+  /*
    * Makes sure that this relation has the right parent and key.
    */
   _ensureParentAndKey(parent: ParseObject, key: string) {
@@ -71,7 +72,7 @@ export default class ParseRelation {
 
   /**
    * Adds a Parse.Object or an array of Parse.Objects to the relation.
-   * @method add
+
    * @param {} objects The item or items to add.
    */
   add(objects: ParseObject | Array<ParseObject | string>): ParseObject {
@@ -91,7 +92,7 @@ export default class ParseRelation {
 
   /**
    * Removes a Parse.Object or an array of Parse.Objects from this relation.
-   * @method remove
+
    * @param {} objects The item or items to remove.
    */
   remove(objects: ParseObject | Array<ParseObject | string>) {
@@ -109,7 +110,7 @@ export default class ParseRelation {
 
   /**
    * Returns a JSON version of the object suitable for saving to disk.
-   * @method toJSON
+
    * @return {Object}
    */
   toJSON(): { __type: 'Relation', className: ?string } {
@@ -122,7 +123,7 @@ export default class ParseRelation {
   /**
    * Returns a Parse.Query that is limited to objects in this
    * relation.
-   * @method query
+
    * @return {Parse.Query}
    */
   query(): ParseQuery {
@@ -147,3 +148,5 @@ export default class ParseRelation {
     return query;
   }
 }
+
+export default ParseRelation;
diff --git a/src/ParseRole.js b/src/ParseRole.js
index 1248ec0..b313188 100644
--- a/src/ParseRole.js
+++ b/src/ParseRole.js
@@ -25,14 +25,16 @@ import type ParseRelation from './ParseRelation';
  *
  * <p>Roles must have a name (which cannot be changed after creation of the
  * role), and must specify an ACL.</p>
- * @class Parse.Role
- * @constructor
- * @param {String} name The name of the Role to create.
- * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL.
- * A Parse.Role is a local representation of a role persisted to the Parse
- * cloud.
+ * @alias Parse.Role
+ * @extends Parse.Object
  */
-export default class ParseRole extends ParseObject {
+class ParseRole extends ParseObject {
+  /**
+   * @param {String} name The name of the Role to create.
+   * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL.
+   * A Parse.Role is a local representation of a role persisted to the Parse
+   * cloud.
+   */
   constructor(name: string, acl: ParseACL) {
     super('_Role');
     if (typeof name === 'string' && (acl instanceof ParseACL)) {
@@ -44,7 +46,7 @@ export default class ParseRole extends ParseObject {
   /**
    * Gets the name of the role.  You can alternatively call role.get("name")
    *
-   * @method getName
+
    * @return {String} the name of the role.
    */
   getName(): ?string {
@@ -67,7 +69,7 @@ export default class ParseRole extends ParseObject {
    *
    * <p>This is equivalent to calling role.set("name", name)</p>
    *
-   * @method setName
+
    * @param {String} name The name of the role.
    * @param {Object} options Standard options object with success and error
    *     callbacks.
@@ -84,7 +86,7 @@ export default class ParseRole extends ParseObject {
    *
    * <p>This is equivalent to calling role.relation("users")</p>
    *
-   * @method getUsers
+
    * @return {Parse.Relation} the relation for the users belonging to this
    *     role.
    */
@@ -100,7 +102,7 @@ export default class ParseRole extends ParseObject {
    *
    * <p>This is equivalent to calling role.relation("roles")</p>
    *
-   * @method getRoles
+
    * @return {Parse.Relation} the relation for the roles belonging to this
    *     role.
    */
@@ -144,3 +146,5 @@ export default class ParseRole extends ParseObject {
 }
 
 ParseObject.registerSubclass('_Role', ParseRole);
+
+export default ParseRole;
diff --git a/src/ParseSchema.js b/src/ParseSchema.js
new file mode 100644
index 0000000..4eb8199
--- /dev/null
+++ b/src/ParseSchema.js
@@ -0,0 +1,445 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * @flow
+ */
+
+import CoreManager from './CoreManager';
+import ParsePromise from './ParsePromise';
+
+import type { RequestOptions, FullOptions } from './RESTController';
+
+const FIELD_TYPES = ['String', 'Number', 'Boolean', 'Date', 'File', 'GeoPoint', 'Array', 'Object', 'Pointer', 'Relation'];
+
+/**
+ * A Parse.Schema object is for handling schema data from Parse.
+ * <p>All the schemas methods require MasterKey.
+ *
+ * <pre>
+ * const schema = new Parse.Schema('MyClass');
+ * schema.addString('field');
+ * schema.addIndex('index_name', {'field', 1});
+ * schema.save();
+ * </pre>
+ * </p>
+ * @alias Parse.Schema
+ */
+class ParseSchema {
+  className: string;
+  _fields: { [key: string]: mixed };
+  _indexes: { [key: string]: mixed };
+
+  /**
+   * @param {String} className Parse Class string.
+   */
+  constructor(className: string) {
+    if (typeof className === 'string') {
+      if (className === 'User' && CoreManager.get('PERFORM_USER_REWRITE')) {
+        this.className = '_User';
+      } else {
+        this.className = className;
+      }
+    }
+
+    this._fields = {};
+    this._indexes = {};
+  }
+
+  /**
+   * Static method to get all schemas
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  static all(options: FullOptions) {
+    options = options || {};
+    const controller = CoreManager.getSchemaController();
+
+    return controller.get('', options)
+      .then((response) => {
+        if (response.results.length === 0) {
+          throw new Error('Schema not found.');
+        }
+        return response.results;
+      })._thenRunCallbacks(options);
+  }
+
+  /**
+   * Get the Schema from Parse
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  get(options: FullOptions) {
+    this.assertClassName();
+
+    options = options || {};
+    const controller = CoreManager.getSchemaController();
+
+    return controller.get(this.className, options)
+      .then((response) => {
+        if (!response) {
+          throw new Error('Schema not found.');
+        }
+        return response;
+      })._thenRunCallbacks(options);
+  }
+
+  /**
+   * Create a new Schema on Parse
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  save(options: FullOptions) {
+    this.assertClassName();
+
+    options = options || {};
+    const controller = CoreManager.getSchemaController();
+    const params = {
+      className: this.className,
+      fields: this._fields,
+      indexes: this._indexes,
+    };
+
+    return controller.create(this.className, params, options)
+      .then((response) => {
+        return response;
+      })._thenRunCallbacks(options);
+  }
+
+  /**
+   * Update a Schema on Parse
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  update(options: FullOptions) {
+    this.assertClassName();
+
+    options = options || {};
+    const controller = CoreManager.getSchemaController();
+    const params = {
+      className: this.className,
+      fields: this._fields,
+      indexes: this._indexes,
+    };
+
+    this._fields = {};
+    this._indexes = {};
+
+    return controller.update(this.className, params, options)
+      .then((response) => {
+        return response;
+      })._thenRunCallbacks(options);
+  }
+
+  /**
+   * Removing a Schema from Parse
+   *
+   * @param {Object} options A Backbone-style options object.
+   * Valid options are:<ul>
+   *   <li>success: A Backbone-style success callback
+   *   <li>error: An Backbone-style error callback.
+   *   <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
+   *     be used for this request.
+   *   <li>sessionToken: A valid session token, used for making a request on
+   *       behalf of a specific user.
+   * </ul>
+   *
+   * @return {Parse.Promise} A promise that is resolved with the result when
+   * the query completes.
+   */
+  delete(options: FullOptions) {
+    this.assertClassName();
+
+    options = options || {};
+    const controller = CoreManager.getSchemaController();
+
+    return controller.delete(this.className, options)
+      .then((response) => {
+        return response;
+      })._thenRunCallbacks(options);
+  }
+
+  /**
+   * Assert if ClassName has been filled
+   * @private
+   */
+  assertClassName() {
+    if (!this.className) {
+      throw new Error('You must set a Class Name before making any request.');
+    }
+  }
+
+  /**
+   * Adding a Field to Create / Update a Schema
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @param {String} type TheCan be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation)
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addField(name: string, type: string) {
+    type = type || 'String';
+
+    if (!name) {
+      throw new Error('field name may not be null.');
+    }
+    if (FIELD_TYPES.indexOf(type) === -1) {
+      throw new Error(`${type} is not a valid type.`);
+    }
+
+    this._fields[name] = { type };
+
+    return this;
+  }
+
+  /**
+   * Adding an Index to Create / Update a Schema
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @param {String} type Can be a (String|Number|Boolean|Date|Parse.File|Parse.GeoPoint|Array|Object|Pointer|Parse.Relation)
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addIndex(name: string, index: any) {
+    if (!name) {
+      throw new Error('index name may not be null.');
+    }
+    if (!index) {
+      throw new Error('index may not be null.');
+    }
+
+    this._indexes[name] = index;
+
+    return this;
+  }
+
+  /**
+   * Adding String Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addString(name: string) {
+    return this.addField(name, 'String');
+  }
+
+  /**
+   * Adding Number Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addNumber(name: string) {
+    return this.addField(name, 'Number');
+  }
+
+  /**
+   * Adding Boolean Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addBoolean(name: string) {
+    return this.addField(name, 'Boolean');
+  }
+
+  /**
+   * Adding Date Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addDate(name: string) {
+    return this.addField(name, 'Date');
+  }
+
+  /**
+   * Adding File Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addFile(name: string) {
+    return this.addField(name, 'File');
+  }
+
+  /**
+   * Adding GeoPoint Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addGeoPoint(name: string) {
+    return this.addField(name, 'GeoPoint');
+  }
+
+  /**
+   * Adding Array Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addArray(name: string) {
+    return this.addField(name, 'Array');
+  }
+
+  /**
+   * Adding Object Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addObject(name: string) {
+    return this.addField(name, 'Object');
+  }
+
+  /**
+   * Adding Pointer Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @param {String} targetClass Name of the target Pointer Class
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addPointer(name: string, targetClass: string) {
+    if (!name) {
+      throw new Error('field name may not be null.');
+    }
+    if (!targetClass) {
+      throw new Error('You need to set the targetClass of the Pointer.');
+    }
+
+    this._fields[name] = {
+      type: 'Pointer',
+      targetClass
+    };
+
+    return this;
+  }
+
+  /**
+   * Adding Relation Field
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @param {String} targetClass Name of the target Pointer Class
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  addRelation(name: string, targetClass: string) {
+    if (!name) {
+      throw new Error('field name may not be null.');
+    }
+    if (!targetClass) {
+      throw new Error('You need to set the targetClass of the Relation.');
+    }
+
+    this._fields[name] = {
+      type: 'Relation',
+      targetClass
+    };
+
+    return this;
+  }
+
+  /**
+   * Deleting a Field to Update on a Schema
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @param {String} targetClass Name of the target Pointer Class
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  deleteField(name: string) {
+    this._fields[name] = { __op: 'Delete'};
+  }
+
+   /**
+   * Deleting an Index to Update on a Schema
+   *
+   * @param {String} name Name of the field that will be created on Parse
+   * @param {String} targetClass Name of the target Pointer Class
+   * @return {Parse.Schema} Returns the schema, so you can chain this call.
+   */
+  deleteIndex(name: string) {
+    this._indexes[name] = { __op: 'Delete'};
+  }
+}
+
+const DefaultController = {
+  send(className: string, method: string, params: any, options: RequestOptions): ParsePromise {
+    const RESTController = CoreManager.getRESTController();
+    const requestOptions = { useMasterKey: true };
+    if (options.hasOwnProperty('sessionToken')) {
+      requestOptions.sessionToken = options.sessionToken;
+    }
+    return RESTController.request(
+      method,
+      `schemas/${className}`,
+      params,
+      requestOptions
+    );
+  },
+
+  get(className: string, options: RequestOptions): ParsePromise {
+    return this.send(className, 'GET', {}, options);
+  },
+
+  create(className: string, params: any, options: RequestOptions): ParsePromise {
+    return this.send(className, 'POST', params, options);
+  },
+
+  update(className: string, params: any, options: RequestOptions): ParsePromise {
+    return this.send(className, 'PUT', params, options);
+  },
+
+  delete(className: string, options: RequestOptions): ParsePromise {
+    return this.send(className, 'DELETE', {}, options);
+  }
+};
+
+CoreManager.setSchemaController(DefaultController);
+
+export default ParseSchema;
diff --git a/src/ParseSession.js b/src/ParseSession.js
index 2e9c0f1..878f953 100644
--- a/src/ParseSession.js
+++ b/src/ParseSession.js
@@ -19,14 +19,17 @@ import type { AttributeMap } from './ObjectStateMutations';
 import type { RequestOptions, FullOptions } from './RESTController';
 
 /**
- * @class Parse.Session
- * @constructor
- *
  * <p>A Parse.Session object is a local representation of a revocable session.
  * This class is a subclass of a Parse.Object, and retains the same
  * functionality of a Parse.Object.</p>
+ * @alias Parse.Session
+ * @extends Parse.Object
  */
-export default class ParseSession extends ParseObject {
+class ParseSession extends ParseObject {
+  /**
+   * 
+   * @param {Object} attributes The initial set of data to store in the user.
+   */
   constructor(attributes: ?AttributeMap) {
     super('_Session');
     if (attributes && typeof attributes === 'object'){
@@ -38,7 +41,7 @@ export default class ParseSession extends ParseObject {
 
   /**
    * Returns the session token string.
-   * @method getSessionToken
+
    * @return {String}
    */
   getSessionToken(): string {
@@ -62,7 +65,7 @@ export default class ParseSession extends ParseObject {
 
   /**
    * Retrieves the Session object for the currently logged in session.
-   * @method current
+
    * @static
    * @return {Parse.Promise} A promise that is resolved with the Parse.Session
    *   object after it has been fetched. If there is no current user, the
@@ -92,7 +95,7 @@ export default class ParseSession extends ParseObject {
    * use revocable sessions. If you are migrating an app that uses the Parse
    * SDK in the browser only, please use Parse.User.enableRevocableSession()
    * instead, so that sessions can be automatically upgraded.
-   * @method isCurrentSessionRevocable
+
    * @static
    * @return {Boolean}
    */
@@ -123,3 +126,5 @@ var DefaultController = {
 };
 
 CoreManager.setSessionController(DefaultController);
+
+export default ParseSession;
diff --git a/src/ParseUser.js b/src/ParseUser.js
index ff0c588..ac04bb0 100644
--- a/src/ParseUser.js
+++ b/src/ParseUser.js
@@ -30,16 +30,18 @@ var currentUserCache = null;
 var authProviders = {};
 
 /**
- * @class Parse.User
- * @constructor
- *
  * <p>A Parse.User object is a local representation of a user persisted to the
  * Parse cloud. This class is a subclass of a Parse.Object, and retains the
  * same functionality of a Parse.Object, but also extends it with various
  * user specific methods, like authentication, signing up, and validation of
  * uniqueness.</p>
+ * @alias Parse.User
+ * @extends Parse.Object
  */
-export default class ParseUser extends ParseObject {
+class ParseUser extends ParseObject {
+  /**
+   * @param {Object} attributes The initial set of data to store in the user.
+   */
   constructor(attributes: ?AttributeMap) {
     super('_User');
     if (attributes && typeof attributes === 'object'){
@@ -51,7 +53,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Request a revocable session token to replace the older style of token.
-   * @method _upgradeToRevocableSession
+
    * @param {Object} options A Backbone-style options object.
    * @return {Parse.Promise} A promise that is resolved when the replacement
    *   token has been fetched.
@@ -74,7 +76,6 @@ export default class ParseUser extends ParseObject {
   /**
    * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can
    * call linkWith on the user (even if it doesn't exist yet on the server).
-   * @method _linkWith
    */
   _linkWith(provider: any, options: { authData?: AuthData }): ParsePromise {
     var authType;
@@ -128,7 +129,7 @@ export default class ParseUser extends ParseObject {
   /**
    * Synchronizes auth data for a provider (e.g. puts the access token in the
    * right place to be used by the Facebook SDK).
-   * @method _synchronizeAuthData
+
    */
   _synchronizeAuthData(provider: string) {
     if (!this.isCurrent() || !provider) {
@@ -153,7 +154,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Synchronizes authData for all providers.
-   * @method _synchronizeAllAuthData
+
    */
   _synchronizeAllAuthData() {
     var authData = this.get('authData');
@@ -169,7 +170,7 @@ export default class ParseUser extends ParseObject {
   /**
    * Removes null values from authData (which exist temporarily for
    * unlinking)
-   * @method _cleanupAuthData
+
    */
   _cleanupAuthData() {
     if (!this.isCurrent()) {
@@ -189,7 +190,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Unlinks a user from a service.
-   * @method _unlinkFrom
+
    */
   _unlinkFrom(provider: any, options?: FullOptions) {
     var authType;
@@ -207,7 +208,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Checks whether a user is linked to a service.
-   * @method _isLinked
+
    */
   _isLinked(provider: any): boolean {
     var authType;
@@ -225,7 +226,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Deauthenticates all providers.
-   * @method _logOutWithAll
+
    */
   _logOutWithAll() {
     var authData = this.get('authData');
@@ -241,7 +242,7 @@ export default class ParseUser extends ParseObject {
   /**
    * Deauthenticates a single provider (e.g. removing access tokens from the
    * Facebook SDK).
-   * @method _logOutWith
+
    */
   _logOutWith(provider: any) {
     if (!this.isCurrent()) {
@@ -267,7 +268,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Returns true if <code>current</code> would return this user.
-   * @method isCurrent
+
    * @return {Boolean}
    */
   isCurrent(): boolean {
@@ -277,7 +278,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Returns get("username").
-   * @method getUsername
+
    * @return {String}
    */
   getUsername(): ?string {
@@ -290,7 +291,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Calls set("username", username, options) and returns the result.
-   * @method setUsername
+
    * @param {String} username
    * @param {Object} options A Backbone-style options object.
    * @return {Boolean}
@@ -308,7 +309,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Calls set("password", password, options) and returns the result.
-   * @method setPassword
+
    * @param {String} password
    * @param {Object} options A Backbone-style options object.
    * @return {Boolean}
@@ -319,7 +320,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Returns get("email").
-   * @method getEmail
+
    * @return {String}
    */
   getEmail(): ?string {
@@ -332,7 +333,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Calls set("email", email, options) and returns the result.
-   * @method setEmail
+
    * @param {String} email
    * @param {Object} options A Backbone-style options object.
    * @return {Boolean}
@@ -345,7 +346,7 @@ export default class ParseUser extends ParseObject {
    * Returns the session token for this user, if the user has been logged in,
    * or if it is the result of a query with the master key. Otherwise, returns
    * undefined.
-   * @method getSessionToken
+
    * @return {String} the session token, or undefined
    */
   getSessionToken(): ?string {
@@ -358,7 +359,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Checks whether this user is the current user and has been authenticated.
-   * @method authenticated
+
    * @return (Boolean) whether this user is the current user and is logged in.
    */
   authenticated(): boolean {
@@ -380,7 +381,7 @@ export default class ParseUser extends ParseObject {
    *
    * <p>Calls options.success or options.error on completion.</p>
    *
-   * @method signUp
+
    * @param {Object} attrs Extra fields to set on the new user, or null.
    * @param {Object} options A Backbone-style options object.
    * @return {Parse.Promise} A promise that is fulfilled when the signup
@@ -414,7 +415,7 @@ export default class ParseUser extends ParseObject {
    *
    * <p>Calls options.success or options.error on completion.</p>
    *
-   * @method logIn
+
    * @param {Object} options A Backbone-style options object.
    * @return {Parse.Promise} A promise that is fulfilled with the user when
    *     the login is complete.
@@ -479,7 +480,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Adds functionality to the existing Parse.User class
-   * @method extend
+
    * @param {Object} protoProps A set of properties to add to the prototype
    * @param {Object} classProps A set of static properties to add to the class
    * @static
@@ -518,7 +519,7 @@ export default class ParseUser extends ParseObject {
   /**
    * Retrieves the currently logged in ParseUser with a valid session,
    * either from memory or localStorage, if necessary.
-   * @method current
+
    * @static
    * @return {Parse.Object} The currently logged in Parse.User.
    */
@@ -532,7 +533,7 @@ export default class ParseUser extends ParseObject {
 
   /**
    * Retrieves the currently logged in ParseUser from asynchronous Storage.
-   * @method currentAsync
+
    * @static
    * @return {Parse.Promise} A Promise that is resolved with the currently
    *   logged in Parse User
@@ -553,7 +554,7 @@ export default class ParseUser extends ParseObject {
    *
    * <p>Calls options.success or options.error on completion.</p>
    *
-   * @method signUp
+
    * @param {String} username The username (or email) to sign up with.
    * @param {String} password The password to sign up with.
    * @param {Object} attrs Extra fields to set on the new user.
@@ -577,7 +578,7 @@ export default class ParseUser extends ParseObject {
    *
    * <p>Calls options.success or options.error on completion.</p>
    *
-   * @method logIn
+
    * @param {String} username The username (or email) to log in with.
    * @param {String} password The password to log in with.
    * @param {Object} options A Backbone-style options object.
@@ -613,7 +614,7 @@ export default class ParseUser extends ParseObject {
    *
    * <p>Calls options.success or options.error on completion.</p>
    *
-   * @method become
+
    * @param {String} sessionToken The sessionToken to log in with.
    * @param {Object} options A Backbone-style options object.
    * @static
@@ -647,7 +648,7 @@ export default class ParseUser extends ParseObject {
    * Logs out the currently logged in user session. This will remove the
    * session from disk, log out of linked services, and future calls to
    * <code>current</code> will return <code>null</code>.
-   * @method logOut
+
    * @static
    * @return {Parse.Promise} A promise that is resolved when the session is
    *   destroyed on the server.
@@ -670,11 +671,12 @@ export default class ParseUser extends ParseObject {
    *
    * <p>Calls options.success or options.error on completion.</p>
    *
-   * @method requestPasswordReset
+
    * @param {String} email The email address associated with the user that
    *     forgot their password.
    * @param {Object} options A Backbone-style options object.
    * @static
+   * @returns {Parse.Promise}
    */
   static requestPasswordReset(email, options) {
     options = options || {};
@@ -696,7 +698,7 @@ export default class ParseUser extends ParseObject {
    * User to _User for legacy reasons. This allows developers to
    * override that behavior.
    *
-   * @method allowCustomUserClass
+
    * @param {Boolean} isAllowed Whether or not to allow custom User class
    * @static
    */
@@ -711,7 +713,7 @@ export default class ParseUser extends ParseObject {
    * It is not necessary to call this method from cloud code unless you are
    * handling user signup or login from the server side. In a cloud code call,
    * this function will not attempt to upgrade the current token.
-   * @method enableRevocableSession
+
    * @param {Object} options A Backbone-style options object.
    * @static
    * @return {Parse.Promise} A promise that is resolved when the process has
@@ -734,7 +736,7 @@ export default class ParseUser extends ParseObject {
    * Enables the use of become or the current user in a server
    * environment. These features are disabled by default, since they depend on
    * global objects that are not memory-safe for most servers.
-   * @method enableUnsafeCurrentUser
+
    * @static
    */
   static enableUnsafeCurrentUser() {
@@ -745,7 +747,7 @@ export default class ParseUser extends ParseObject {
    * Disables the use of become or the current user in any environment.
    * These features are disabled on servers by default, since they depend on
    * global objects that are not memory-safe for most servers.
-   * @method disableUnsafeCurrentUser
+
    * @static
    */
   static disableUnsafeCurrentUser() {
@@ -1029,3 +1031,5 @@ var DefaultController = {
 };
 
 CoreManager.setUserController(DefaultController);
+
+export default ParseUser;
diff --git a/src/Push.js b/src/Push.js
index b3c1911..b5c9101 100644
--- a/src/Push.js
+++ b/src/Push.js
@@ -27,11 +27,13 @@ export type PushData = {
  * Contains functions to deal with Push in Parse.
  * @class Parse.Push
  * @static
+ * @hideconstructor
  */
 
  /**
   * Sends a push notification.
   * @method send
+  * @name Parse.Push.send
   * @param {Object} data -  The data of the push notification.  Valid fields
   * are:
   *   <ol>
diff --git a/src/StorageController.react-native.js b/src/StorageController.react-native.js
index e13de1a..7c2c6a1 100644
--- a/src/StorageController.react-native.js
+++ b/src/StorageController.react-native.js
@@ -10,8 +10,15 @@
  */
 
 import ParsePromise from './ParsePromise';
+
 // RN packager nonsense
-import { AsyncStorage } from 'react-native/Libraries/react-native/react-native-implementation';
+let AsyncStorage;
+try {
+  // for React Native 0.43+
+  AsyncStorage = require('react-native/Libraries/react-native/react-native-implementation').AsyncStorage;
+} catch (error) {
+  AsyncStorage = require('react-native/Libraries/react-native/react-native.js').AsyncStorage;
+}
 
 var StorageController = {
   async: 1,
diff --git a/src/escape.js b/src/escape.js
index 54c3bb1..3a29725 100644
--- a/src/escape.js
+++ b/src/escape.js
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2015-present, Parse, LLC.
  * All rights reserved.
  *


Test patch:
diff --git a/integration/test/ParseQueryAggregateTest.js b/integration/test/ParseQueryAggregateTest.js
new file mode 100644
index 0000000..d31d1d7
--- /dev/null
+++ b/integration/test/ParseQueryAggregateTest.js
@@ -0,0 +1,78 @@
+'use strict';
+
+const assert = require('assert');
+const clear = require('./clear');
+const mocha = require('mocha');
+const Parse = require('../../node');
+
+const TestObject = Parse.Object.extend('TestObject');
+
+describe('Parse Aggregate Query', () => {
+  before((done) => {
+    Parse.initialize('integration', null, 'notsosecret');
+    Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse');
+    Parse.Storage._clear();
+    clear().then(() => {
+      const obj1 = new TestObject({score: 10, name: 'foo'});
+      const obj2 = new TestObject({score: 10, name: 'foo'});
+      const obj3 = new TestObject({score: 10, name: 'bar'});
+      const obj4 = new TestObject({score: 20, name: 'dpl'});
+      return Parse.Object.saveAll([obj1, obj2, obj3, obj4]);
+    }).then(() => {
+      return Parse.User.logOut();
+    })
+    .then(() => { done() }, () => { done() });
+  });
+
+  it('aggregate pipeline object query', (done) => {
+    const pipeline = {
+      group: { objectId: '$name' }
+    };
+    const query = new Parse.Query(TestObject);
+    query.aggregate(pipeline).then((results) => {
+      assert.equal(results.length, 3);
+      done();
+    });
+  });
+
+  it('aggregate pipeline array query', (done) => {
+    const pipeline = [
+      { group: { objectId: '$name' } }
+    ];
+    const query = new Parse.Query(TestObject);
+    query.aggregate(pipeline).then((results) => {
+      assert.equal(results.length, 3);
+      done();
+    });
+  });
+
+  it('aggregate pipeline invalid query', (done) => {
+    const pipeline = 1234;
+    const query = new Parse.Query(TestObject);
+    try {
+      query.aggregate(pipeline).then(() => {});
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('distinct query', (done) => {
+    const query = new Parse.Query(TestObject);
+    query.distinct('score').then((results) => {
+      assert.equal(results.length, 2);
+      assert.equal(results[0], 10);
+      assert.equal(results[1], 20);
+      done();
+    });
+  });
+
+  it('distinct equalTo query', (done) => {
+    const query = new Parse.Query(TestObject);
+    query.equalTo('name', 'foo')
+    query.distinct('score').then((results) => {
+      assert.equal(results.length, 1);
+      assert.equal(results[0], 10);
+      done();
+    });
+  });
+});
diff --git a/integration/test/ParseSchemaTest.js b/integration/test/ParseSchemaTest.js
new file mode 100644
index 0000000..91dfbff
--- /dev/null
+++ b/integration/test/ParseSchemaTest.js
@@ -0,0 +1,262 @@
+const assert = require('assert');
+const clear = require('./clear');
+const Parse = require('../../node');
+
+describe('Schema', () => {
+  before(() => {
+    Parse.initialize('integration');
+    Parse.CoreManager.set('SERVER_URL', 'http://localhost:1337/parse');
+    Parse.CoreManager.set('MASTER_KEY', 'notsosecret');
+    Parse.Storage._clear();
+  });
+
+  beforeEach((done) => {
+    clear().then(() => {
+      done();
+    });
+  });
+
+  it('invalid get all no schema', (done) => {
+    Parse.Schema.all().then(() => {}).fail((e) => {
+      done();
+    });
+  });
+
+  it('invalid get no schema', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.get().then(() => {}).fail((e) => {
+      done();
+    });
+  });
+
+  it('save', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.save().then((result) => {
+      assert.equal(result.className, 'SchemaTest');
+      done();
+    });
+  });
+
+  it('get', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema
+      .addField('defaultFieldString')
+      .addString('stringField')
+      .addNumber('numberField')
+      .addBoolean('booleanField')
+      .addDate('dateField')
+      .addFile('fileField')
+      .addGeoPoint('geoPointField')
+      .addArray('arrayField')
+      .addObject('objectField')
+      .addPointer('pointerField', '_User')
+      .addRelation('relationField', '_User');
+
+    testSchema.save().then(() => {
+      return testSchema.get();
+    }).then((result) => {
+      assert.equal(result.fields.defaultFieldString.type, 'String');
+      assert.equal(result.fields.stringField.type, 'String');
+      assert.equal(result.fields.numberField.type, 'Number');
+      assert.equal(result.fields.booleanField.type, 'Boolean');
+      assert.equal(result.fields.dateField.type, 'Date');
+      assert.equal(result.fields.fileField.type, 'File');
+      assert.equal(result.fields.geoPointField.type, 'GeoPoint');
+      assert.equal(result.fields.arrayField.type, 'Array');
+      assert.equal(result.fields.objectField.type, 'Object');
+      assert.equal(result.fields.pointerField.type, 'Pointer');
+      assert.equal(result.fields.relationField.type, 'Relation');
+      assert.equal(result.fields.pointerField.targetClass, '_User');
+      assert.equal(result.fields.relationField.targetClass, '_User');
+      done();
+    });
+  });
+
+  it('all', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.save().then(() => {
+      return Parse.Schema.all();
+    }).then((results) => {
+      assert.equal(results.length, 1);
+      done();
+    });
+  });
+
+  it('update', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.addString('name');
+    testSchema.save().then(() => {
+      testSchema.deleteField('name');
+      testSchema.addNumber('quantity');
+      testSchema.addBoolean('status');
+      return testSchema.update();
+    }).then((result) => {
+      assert.equal(result.fields.status.type, 'Boolean');
+      assert.equal(result.fields.quantity.type, 'Number');
+      assert.equal(result.fields.name, undefined);
+      done();
+    });
+  });
+
+  it('multiple update', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.save().then(() => {
+      testSchema.addString('name');
+      return testSchema.update();
+    }).then(() => {
+      return testSchema.update();
+    }).then(() => {
+      return testSchema.get();
+    }).then((result) => {
+      assert.equal(Object.keys(result.fields).length, 5);
+      done();
+    });
+  });
+
+  it('delete', (done) => {
+    const testSchema1 = new Parse.Schema('SchemaTest1');
+    const testSchema2 = new Parse.Schema('SchemaTest2');
+    testSchema1.save().then(() => {
+      return testSchema2.save();
+    }).then(() => {
+      return Parse.Schema.all();
+    }).then((results) => {
+      assert.equal(results.length, 2);
+      return testSchema1.delete();
+    }).then(() => {
+      return Parse.Schema.all();
+    }).then((results) => {
+      assert.equal(results.length, 1);
+      assert.equal(results[0].className, 'SchemaTest2');
+      done();
+    });
+  });
+
+  it('save index', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    const index = {
+      name: 1
+    };
+    testSchema.addString('name');
+    testSchema.addIndex('test_index', index);
+    testSchema.save().then((result) => {
+      assert.notEqual(result.indexes.test_index, undefined);
+      done();
+    });
+  });
+
+  it('update index', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.save().then((result) => {
+        const index = {
+        name: 1
+      };
+      testSchema.addString('name');
+      testSchema.addIndex('test_index', index);
+      return testSchema.update();
+    }).then((result) => {
+      assert.notEqual(result.indexes.test_index, undefined);
+      done();
+    });
+  });
+
+  it('delete index', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    testSchema.save().then((result) => {
+        const index = {
+        name: 1
+      };
+      testSchema.addString('name');
+      testSchema.addIndex('test_index', index);
+      return testSchema.update();
+    }).then((result) => {
+      assert.notEqual(result.indexes.test_index, undefined);
+      testSchema.deleteIndex('test_index');
+      return testSchema.update();
+    }).then((result) => {
+      assert.equal(result.indexes.test_index, undefined);
+      done();
+    });
+  });
+
+  it('invalid field name', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addField(null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid field type', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addField('name', 'UnknownType');
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid index name', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addIndex(null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid index', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addIndex('name', null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid pointer name', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addPointer(null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid pointer class', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addPointer('name', null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid relation name', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addRelation(null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('invalid relation class', (done) => {
+    const testSchema = new Parse.Schema('SchemaTest');
+    try {
+      testSchema.addRelation('name', null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('assert class name', (done) => {
+    const testSchema = new Parse.Schema();
+    try {
+      testSchema.assertClassName();
+    } catch (e) {
+      done();
+    }
+  });
+});
diff --git a/src/__tests__/CoreManager-test.js b/src/__tests__/CoreManager-test.js
index 5429907..259fb13 100644
--- a/src/__tests__/CoreManager-test.js
+++ b/src/__tests__/CoreManager-test.js
@@ -220,13 +220,15 @@ describe('CoreManager', () => {
     );
 
     expect(CoreManager.setQueryController.bind(null, {
-      find: function() {}
+      find: function() {},
+      aggregate: function() {}
     })).not.toThrow();
   });
 
   it('can set and get QueryController', () => {
     var controller = {
-      find: function() {}
+      find: function() {},
+      aggregate: function() {}
     };
 
     CoreManager.setQueryController(controller);
@@ -315,4 +317,31 @@ describe('CoreManager', () => {
     CoreManager.setStorageController(controller);
     expect(CoreManager.getStorageController()).toBe(controller);
   });
+
+  it('requires SchemaController to implement certain functionality', () => {
+    expect(CoreManager.setSchemaController.bind(null, {})).toThrow(
+      'SchemaController must implement get()'
+    );
+
+    expect(CoreManager.setSchemaController.bind(null, {
+      send: function() {},
+      get: function() {},
+      create: function() {},
+      update: function() {},
+      delete: function() {}
+    })).not.toThrow();
+  });
+
+  it('can set and get SchemaController', () => {
+    var controller = {
+      send: function() {},
+      get: function() {},
+      create: function() {},
+      update: function() {},
+      delete: function() {}
+    };
+
+    CoreManager.setSchemaController(controller);
+    expect(CoreManager.getSchemaController()).toBe(controller);
+  });
 });
diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js
index 61d3b6c..78c01e5 100644
--- a/src/__tests__/ParseQuery-test.js
+++ b/src/__tests__/ParseQuery-test.js
@@ -885,6 +885,7 @@ describe('ParseQuery', () => {
 
   it('can get the first object of a query', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -917,6 +918,7 @@ describe('ParseQuery', () => {
 
   it('can pass options to a first() query', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -947,6 +949,7 @@ describe('ParseQuery', () => {
 
   it('can get a single object by id', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -979,6 +982,7 @@ describe('ParseQuery', () => {
 
   it('will error when getting a nonexistent object', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1008,6 +1012,7 @@ describe('ParseQuery', () => {
 
   it('can pass options to a get() query', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1039,6 +1044,7 @@ describe('ParseQuery', () => {
 
   it('can issue a count query', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1065,6 +1071,7 @@ describe('ParseQuery', () => {
 
   it('can pass options to a count query', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1085,6 +1092,7 @@ describe('ParseQuery', () => {
       }
     });
 
+
     var q = new ParseQuery('Item');
     q.equalTo('size', 'small').count({
       useMasterKey: true,
@@ -1097,6 +1105,7 @@ describe('ParseQuery', () => {
 
   it('can issue a query to the controller', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1145,6 +1154,7 @@ describe('ParseQuery', () => {
 
   it('can pass options to find()', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1178,6 +1188,7 @@ describe('ParseQuery', () => {
 
   it('can iterate over results with each()', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1234,6 +1245,7 @@ describe('ParseQuery', () => {
 
   it('can pass options to each()', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         expect(className).toBe('Item');
         expect(params).toEqual({
@@ -1302,6 +1314,7 @@ describe('ParseQuery', () => {
 
   it('does not override the className if it comes from the server', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         return ParsePromise.as({
           results: [
@@ -1320,6 +1333,7 @@ describe('ParseQuery', () => {
 
   it('can override the className with a name from the server', (done) => {
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         return ParsePromise.as({
           results: [
@@ -1338,7 +1352,7 @@ describe('ParseQuery', () => {
   });
 
 
-  
+
   it('overrides cached object with query results', (done) => {
     jest.dontMock("../ParseObject");
     jest.resetModules();
@@ -1347,16 +1361,17 @@ describe('ParseQuery', () => {
     ParseQuery = require('../ParseQuery').default;
 
     ParseObject.enableSingleInstance();
-    
-    var objectToReturn = { 
-      objectId: 'T01', 
-      name: 'Name', 
-      other: 'other', 
-      className:"Thing", 
+
+    var objectToReturn = {
+      objectId: 'T01',
+      name: 'Name',
+      other: 'other',
+      className:"Thing",
       createdAt: '2017-01-10T10:00:00Z'
     };
 
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         return ParsePromise.as({
           results: [objectToReturn]
@@ -1368,10 +1383,10 @@ describe('ParseQuery', () => {
     var testObject;
     q.find().then((results) => {
       testObject = results[0];
-      
+
       expect(testObject.get("name")).toBe("Name");
       expect(testObject.get("other")).toBe("other");
-      
+
       objectToReturn = { objectId: 'T01', name: 'Name2'};
       var q2 = new ParseQuery("Thing");
       return q2.find();
@@ -1393,18 +1408,19 @@ describe('ParseQuery', () => {
     ParseQuery = require('../ParseQuery').default;
 
     ParseObject.enableSingleInstance();
-    
-    var objectToReturn = { 
-      objectId: 'T01', 
-      name: 'Name', 
-      other: 'other', 
-      tbd: 'exists', 
-      className:"Thing", 
+
+    var objectToReturn = {
+      objectId: 'T01',
+      name: 'Name',
+      other: 'other',
+      tbd: 'exists',
+      className:"Thing",
       createdAt: '2017-01-10T10:00:00Z',
       subObject: {key1:"value", key2:"value2", key3:"thisWillGoAway"}
     };
 
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         return ParsePromise.as({
           results: [objectToReturn]
@@ -1416,14 +1432,14 @@ describe('ParseQuery', () => {
     var testObject;
     return q.find().then((results) => {
       testObject = results[0];
-      
+
       expect(testObject.get("name")).toBe("Name");
       expect(testObject.get("other")).toBe("other");
       expect(testObject.has("tbd")).toBe(true);
       expect(testObject.get("subObject").key1).toBe("value");
       expect(testObject.get("subObject").key2).toBe("value2");
       expect(testObject.get("subObject").key3).toBe("thisWillGoAway");
-      
+
       var q2 = new ParseQuery("Thing");
       q2.select("other", "tbd", "subObject.key1", "subObject.key3");
       objectToReturn = { objectId: 'T01', other: 'other2', subObject:{key1:"updatedValue"}};
@@ -1442,7 +1458,7 @@ describe('ParseQuery', () => {
       expect(testObject.has("tbd")).toBe(false);
       expect(testObject.get("subObject").key1).toBe("updatedValue");
       expect(testObject.get("subObject").key2).toBe("value2");
-      expect(testObject.get("subObject").key3).toBeUndefined();  
+      expect(testObject.get("subObject").key3).toBeUndefined();
       done();
     }, (error) => {
       done.fail(error);
@@ -1457,16 +1473,17 @@ describe('ParseQuery', () => {
     ParseQuery = require('../ParseQuery').default;
 
     ParseObject.enableSingleInstance();
-    
-    var objectToReturn = { 
-      objectId: 'T01', 
-      name: 'Name', 
-      other: 'other', 
-      className:"Thing", 
+
+    var objectToReturn = {
+      objectId: 'T01',
+      name: 'Name',
+      other: 'other',
+      className:"Thing",
       createdAt: '2017-01-10T10:00:00Z'
     };
 
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         return ParsePromise.as({
           results: [objectToReturn]
@@ -1478,10 +1495,10 @@ describe('ParseQuery', () => {
     var testObject;
     q.first().then((result) => {
       testObject = result;
-      
+
       expect(testObject.get("name")).toBe("Name");
       expect(testObject.get("other")).toBe("other");
-      
+
       objectToReturn = { objectId: 'T01', name: 'Name2'};
       var q2 = new ParseQuery("Thing");
       return q2.first();
@@ -1503,18 +1520,19 @@ describe('ParseQuery', () => {
     ParseQuery = require('../ParseQuery').default;
 
     ParseObject.enableSingleInstance();
-    
-    var objectToReturn = { 
-      objectId: 'T01', 
-      name: 'Name', 
-      other: 'other', 
-      tbd: 'exists', 
-      className:"Thing", 
+
+    var objectToReturn = {
+      objectId: 'T01',
+      name: 'Name',
+      other: 'other',
+      tbd: 'exists',
+      className:"Thing",
       subObject: {key1:"value", key2:"value2", key3:"thisWillGoAway"},
       createdAt: '2017-01-10T10:00:00Z',
     };
 
     CoreManager.setQueryController({
+      aggregate() {},
       find(className, params, options) {
         return ParsePromise.as({
           results: [objectToReturn]
@@ -1526,11 +1544,11 @@ describe('ParseQuery', () => {
     var testObject;
     return q.first().then((result) => {
       testObject = result;
-      
+
       expect(testObject.get("name")).toBe("Name");
       expect(testObject.get("other")).toBe("other");
       expect(testObject.has("tbd")).toBe(true);
-      
+
       var q2 = new ParseQuery("Thing");
       q2.select("other", "tbd", "subObject.key1", "subObject.key3");
       objectToReturn = { objectId: 'T01', other: 'other2', subObject:{key1:"updatedValue"}};
@@ -1546,10 +1564,10 @@ describe('ParseQuery', () => {
     }).then(() => {
       expect(testObject.get("name")).toBe("Name");
       expect(testObject.get("other")).toBe("other2");
-      expect(testObject.has("tbd")).toBe(false);  
+      expect(testObject.has("tbd")).toBe(false);
       expect(testObject.get("subObject").key1).toBe("updatedValue");
       expect(testObject.get("subObject").key2).toBe("value2");
-      expect(testObject.get("subObject").key3).toBeUndefined(); 
+      expect(testObject.get("subObject").key3).toBeUndefined();
       done();
     }, (error) => {
       done.fail(error);
@@ -1582,6 +1600,268 @@ describe('ParseQuery', () => {
         size: 'medium'
       }
     });
+  });
+
+  it('can issue a distinct query', (done) => {
+    CoreManager.setQueryController({
+      find() {},
+      aggregate(className, params, options) {
+        expect(className).toBe('Item');
+        expect(params).toEqual({
+          distinct: 'size',
+          where: {
+            size: 'small'
+          }
+        });
+        expect(options).toEqual({ useMasterKey: true });
+        return ParsePromise.as({
+          results: ['L'],
+        });
+      }
+    });
 
+    var q = new ParseQuery('Item');
+    q.equalTo('size', 'small').distinct('size').then((results) => {
+      expect(results[0]).toBe('L');
+      done();
+    });
   });
+
+  it('can pass options to a distinct query', (done) => {
+    CoreManager.setQueryController({
+      find() {},
+      aggregate(className, params, options) {
+        expect(className).toBe('Item');
+        expect(params).toEqual({
+          distinct: 'size',
+          where: {
+            size: 'small'
+          }
+        });
+        expect(options).toEqual({
+          useMasterKey: true,
+          sessionToken: '1234'
+        });
+        return ParsePromise.as({
+          results: ['L']
+        });
+      }
+    });
+
+
+    var q = new ParseQuery('Item');
+    q.equalTo('size', 'small').distinct('size', {
+      sessionToken: '1234'
+    }).then((results) => {
+      expect(results[0]).toBe('L');
+      done();
+    });
+  });
+
+  it('can issue an aggregate query with array pipeline', (done) => {
+    const pipeline = [
+      { group: { objectId: '$name' } }
+    ];
+    CoreManager.setQueryController({
+      find() {},
+      aggregate(className, params, options) {
+        expect(className).toBe('Item');
+        expect(params).toEqual({
+          group: { objectId: '$name' }
+        });
+        expect(options).toEqual({ useMasterKey: true });
+        return ParsePromise.as({
+          results: [],
+        });
+      }
+    });
+
+    var q = new ParseQuery('Item');
+    q.aggregate(pipeline).then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('can issue an aggregate query with object pipeline', (done) => {
+    const pipeline = {
+      group: { objectId: '$name' }
+    };
+    CoreManager.setQueryController({
+      find() {},
+      aggregate(className, params, options) {
+        expect(className).toBe('Item');
+        expect(params).toEqual({
+          group: { objectId: '$name' }
+        });
+        expect(options).toEqual({ useMasterKey: true });
+        return ParsePromise.as({
+          results: [],
+        });
+      }
+    });
+
+    var q = new ParseQuery('Item');
+    q.aggregate(pipeline).then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('cannot issue an aggregate query with invalid pipeline', (done) => {
+    const pipeline = 1234;
+    CoreManager.setQueryController({
+      find() {},
+      aggregate(className, params, options) {
+        expect(className).toBe('Item');
+        expect(params).toEqual({
+          group: { objectId: '$name' }
+        });
+        expect(options).toEqual({ useMasterKey: true });
+        return ParsePromise.as({
+          results: [],
+        });
+      }
+    });
+
+    try {
+      var q = new ParseQuery('Item');
+      q.aggregate(pipeline).then(() => {});
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('can pass options to an aggregate query', (done) => {
+    const pipeline = [
+      { group: { objectId: '$name' } }
+    ];
+    CoreManager.setQueryController({
+      find() {},
+      aggregate(className, params, options) {
+        expect(className).toBe('Item');
+        expect(params).toEqual({
+          group: { objectId: '$name' }
+        });
+        expect(options).toEqual({
+          useMasterKey: true,
+          sessionToken: '1234'
+        });
+        return ParsePromise.as({
+          results: []
+        });
+      }
+    });
+
+    var q = new ParseQuery('Item');
+    q.aggregate(pipeline, {
+      sessionToken: '1234'
+    }).then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('selecting sub-objects does not inject objects when sub-object does not exist', (done) => {
+    jest.dontMock("../ParseObject");
+    jest.resetModules();
+    ParseObject = require('../ParseObject').default;
+    CoreManager = require('../CoreManager');
+    ParseQuery = require('../ParseQuery').default;
+
+    ParseObject.enableSingleInstance();
+
+    var objectToReturn = {
+      objectId: 'T01',
+      name: 'Name',
+      tbd: 'exists',
+      className:"Thing",
+      createdAt: '2017-01-10T10:00:00Z'
+    };
+
+    CoreManager.setQueryController({
+      aggregate() {},
+      find(className, params, options) {
+        return ParsePromise.as({
+          results: [objectToReturn]
+        });
+      }
+    });
+
+    var q = new ParseQuery("Thing");
+    q.select("other", "tbd", "subObject.key1")
+    var testObject;
+    return q.find().then((results) => {
+      testObject = results[0];
+
+      expect(testObject.get("name")).toBe("Name");
+      expect(testObject.has("other")).toBe(false);
+      expect(testObject.has("subObject")).toBe(false);
+
+    }).then(() => {
+      done();
+    }, (error) => {
+      done.fail(error);
+    });
+  });
+
+  it('removes missing sub objects from the cached object when they are selected', (done) => {
+    jest.dontMock("../ParseObject");
+    jest.resetModules();
+    ParseObject = require('../ParseObject').default;
+    CoreManager = require('../CoreManager');
+    ParseQuery = require('../ParseQuery').default;
+
+    ParseObject.enableSingleInstance();
+
+    var objectToReturn = {
+      objectId: 'T01',
+      name: 'Name',
+      tbd: 'exists',
+      className:"Thing",
+      subObject1: {foo:"bar"},
+      subObject2: {foo:"bar"},
+      subObject3: {foo:"bar"},
+      subObject5: {subSubObject:{foo:"foo", bar:"bar"}},
+      createdAt: '2017-01-10T10:00:00Z'
+    };
+
+    CoreManager.setQueryController({
+      aggregate() {},
+      find(className, params, options) {
+        return ParsePromise.as({
+          results: [objectToReturn]
+        });
+      }
+    });
+
+    var q = new ParseQuery("Thing");
+    var testObject;
+    return q.find().then((results) => {
+      testObject = results[0];
+
+      expect(testObject.has("subObject1")).toBe(true);
+      expect(testObject.has("subObject2")).toBe(true);
+      expect(testObject.has("subObject3")).toBe(true);
+      expect(testObject.has("subObject4")).toBe(false);
+
+      var q2 = new ParseQuery("Thing");
+      q.select("name","subObject1", "subObject2.foo", "subObject4.foo", "subObject5.subSubObject.foo");
+      objectToReturn = { objectId: 'T01', name:"Name", subObject4: {foo:"bar"}, subObject5: {subSubObject:{}}};
+      return q.find();
+    }).then((results)=>{
+      expect(testObject.has("subObject1")).toBe(false); //selected and not returned
+      expect(testObject.has("subObject2")).toBe(false); //selected and not returned
+      expect(testObject.has("subObject3")).toBe(true); //not selected, so should still be there
+      expect(testObject.has("subObject4")).toBe(true); //selected and just added
+      expect(testObject.has("subObject5")).toBe(true);
+      expect(testObject.get("subObject5").subSubObject).toBeDefined();
+      expect(testObject.get("subObject5").subSubObject.bar).toBeDefined(); //not selected but a sibiling was, so should still be there
+    }).then(() => {
+      done();
+    }, (error) => {
+      done.fail(error);
+    });
+  });
+
 });
diff --git a/src/__tests__/ParseSchema-test.js b/src/__tests__/ParseSchema-test.js
new file mode 100644
index 0000000..c99311c
--- /dev/null
+++ b/src/__tests__/ParseSchema-test.js
@@ -0,0 +1,432 @@
+/**
+ * Copyright (c) 2015-present, Parse, LLC.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+jest.autoMockOff();
+
+var ParseSchema = require('../ParseSchema').default;
+var ParsePromise = require('../ParsePromise').default;
+var CoreManager = require('../CoreManager');
+
+function generateSaveMock(prefix) {
+  return function(name, payload) {
+    return ParsePromise.as({
+      name: name,
+      url: prefix + name
+    });
+  };
+}
+
+var defaultController = CoreManager.getSchemaController();
+
+describe('ParseSchema', () => {
+  it('can create schema', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    expect(schema.className, 'SchemaTest');
+    done();
+  });
+
+  it('can create schema with User Class', (done) => {
+    var schema = new ParseSchema('User');
+    expect(schema.className, '_User');
+    done();
+  });
+
+  it('cannot use schema without class', (done) => {
+    try {
+      var schema = new ParseSchema();
+      schema.assertClassName();
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('can create schema fields', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema
+      .addField('defaultFieldString')
+      .addString('stringField')
+      .addNumber('numberField')
+      .addBoolean('booleanField')
+      .addDate('dateField')
+      .addFile('fileField')
+      .addGeoPoint('geoPointField')
+      .addArray('arrayField')
+      .addObject('objectField')
+      .addPointer('pointerField', '_User')
+      .addRelation('relationField', '_User');
+
+    expect(schema._fields.defaultFieldString.type, 'String');
+    expect(schema._fields.stringField.type, 'String');
+    expect(schema._fields.numberField.type, 'Number');
+    expect(schema._fields.booleanField.type, 'Boolean');
+    expect(schema._fields.dateField.type, 'Date');
+    expect(schema._fields.fileField.type, 'File');
+    expect(schema._fields.geoPointField.type, 'GeoPoint');
+    expect(schema._fields.arrayField.type, 'Array');
+    expect(schema._fields.objectField.type, 'Object');
+    expect(schema._fields.pointerField.type, 'Pointer');
+    expect(schema._fields.relationField.type, 'Relation');
+    expect(schema._fields.pointerField.targetClass, '_User');
+    expect(schema._fields.relationField.targetClass, '_User');
+    done();
+  });
+
+  it('can create schema indexes', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.addIndex('testIndex', { name: 1 });
+
+    expect(schema._indexes.name, 1);
+    done();
+  });
+
+  it('cannot add field with null name', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addField(null, 'string');
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add field with invalid type', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addField('testField', 'unknown');
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add index with null name', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addIndex(null, {'name': 1});
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add index with null index', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addIndex('testIndex', null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add pointer with null name', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addPointer(null, 'targetClass');
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add pointer with null targetClass', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addPointer('pointerField', null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add relation with null name', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addRelation(null, 'targetClass');
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('cannot add relation with null targetClass', (done) => {
+    try {
+      var schema = new ParseSchema('SchemaTest');
+      schema.addRelation('relationField', null);
+    } catch (e) {
+      done();
+    }
+  });
+
+  it('can delete schema field', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.deleteField('testField');
+    expect(schema._fields.testField._op, 'Delete');
+    done();
+  });
+
+  it('can delete schema index', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.deleteIndex('testIndex');
+    expect(schema._indexes.testIndex._op, 'Delete');
+    done();
+  });
+
+  // CoreManager.setSchemaController({
+  //   send() {},
+  //   get() {},
+  //   create() {},
+  //   update() {},
+  //   delete() {},
+  // });
+  it('can save schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      get() {},
+      update() {},
+      delete() {},
+      create(className, params, options) {
+        expect(className).toBe('SchemaTest');
+        expect(params).toEqual({
+          className: 'SchemaTest',
+          fields: { name: { type: 'String'} },
+          indexes: { testIndex: { name: 1 } }
+        });
+        expect(options).toEqual({});
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.addField('name');
+    schema.addIndex('testIndex', {'name': 1});
+    schema.save().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('can update schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      get() {},
+      create() {},
+      delete() {},
+      update(className, params, options) {
+        expect(className).toBe('SchemaTest');
+        expect(params).toEqual({
+          className: 'SchemaTest',
+          fields: { name: { type: 'String'} },
+          indexes: { testIndex: { name: 1 } }
+        });
+        expect(options).toEqual({});
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.addField('name');
+    schema.addIndex('testIndex', {'name': 1});
+    schema.update().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('can delete schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      get() {},
+      delete(className, options) {
+        expect(className).toBe('SchemaTest');
+        expect(options).toEqual({});
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.delete().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('can get schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      delete() {},
+      get(className, options) {
+        expect(className).toBe('SchemaTest');
+        expect(options).toEqual({});
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.get().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('can get schema with options', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      delete() {},
+      get(className, options) {
+        expect(className).toBe('SchemaTest');
+        expect(options).toEqual({ sessionToken: 1234 });
+        return ParsePromise.as([]);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.get({ sessionToken: 1234 }).then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('cannot get empty schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      delete() {},
+      get(className, options) {
+        expect(className).toBe('SchemaTest');
+        expect(options).toEqual({});
+        return ParsePromise.as(null);
+      },
+    });
+
+    var schema = new ParseSchema('SchemaTest');
+    schema.get().then(() => {
+      // Should never reach
+      expect(true).toBe(false);
+      done();
+    }, (error) => {
+      expect(error.message).toBe('Schema not found.');
+      done();
+    });
+  });
+
+  it('can get all schema', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      delete() {},
+      get(className, options) {
+        expect(className).toBe('');
+        expect(options).toEqual({});
+        return ParsePromise.as({
+          results: ['all']
+        });
+      },
+    });
+
+    ParseSchema.all().then((results) => {
+      expect(results[0]).toEqual('all');
+      done();
+    });
+  });
+
+  it('can get all schema with options', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      delete() {},
+      get(className, options) {
+        expect(className).toBe('');
+        expect(options).toEqual({ sessionToken: 1234 });
+        return ParsePromise.as({
+          results: ['all']
+        });
+      },
+    });
+
+    ParseSchema.all({ sessionToken: 1234 }).then((results) => {
+      expect(results[0]).toEqual('all');
+      done();
+    });
+  });
+
+  it('cannot get all schema when empty', (done) => {
+    CoreManager.setSchemaController({
+      send() {},
+      create() {},
+      update() {},
+      delete() {},
+      get(className, options) {
+        expect(className).toBe('');
+        expect(options).toEqual({});
+        return ParsePromise.as({
+          results: []
+        });
+      },
+    });
+
+    ParseSchema.all().then(() => {
+      // Should never reach
+      expect(true).toBe(false);
+      done();
+    }, (error) => {
+      expect(error.message).toBe('Schema not found.');
+      done();
+    });
+  });
+});
+
+describe('SchemaController', () => {
+  beforeEach(() => {
+    CoreManager.setSchemaController(defaultController);
+    var request = function(method, path, data, options) {
+      var name = path.substr(path.indexOf('/') + 1);
+      return ParsePromise.as([]);
+    };
+    var ajax = function(method, path, data, headers) {
+      var name = path.substr(path.indexOf('/') + 1);
+      return ParsePromise.as([]);
+    };
+    CoreManager.setRESTController({ request: request, ajax: ajax });
+  });
+
+  it('save schema with sessionToken', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.save({ sessionToken: 1234 }).then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('get schema', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.get().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('update schema', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.update().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+
+  it('delete schema', (done) => {
+    var schema = new ParseSchema('SchemaTest');
+    schema.delete().then((results) => {
+      expect(results).toEqual([]);
+      done();
+    });
+  });
+});
diff --git a/src/__tests__/Storage-test.js b/src/__tests__/Storage-test.js
index 76df4db..d0eecbb 100644
--- a/src/__tests__/Storage-test.js
+++ b/src/__tests__/Storage-test.js
@@ -29,10 +29,15 @@ var mockRNStorageInterface = {
   },
 };
 
+// for React Native 0.43+
 jest.mock('react-native/Libraries/react-native/react-native-implementation', () => {
   return {AsyncStorage: mockRNStorageInterface};
 }, {virtual: true});
 
+jest.mock('react-native/Libraries/react-native/react-native.js', () => {
+  return {AsyncStorage: mockRNStorageInterface};
+}, {virtual: true});
+
 var CoreManager = require('../CoreManager');
 var ParsePromise = require('../ParsePromise').default;