You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@olingo.apache.org by ko...@apache.org on 2015/03/23 14:54:05 UTC

[8/8] olingo-odata4-js git commit: [OLINGO-442] Cleanup bevore release II and beta-name removal

[OLINGO-442] Cleanup bevore release II and beta-name removal


Project: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/repo
Commit: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/commit/27f830e6
Tree: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/tree/27f830e6
Diff: http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/diff/27f830e6

Branch: refs/heads/master
Commit: 27f830e61e5f3af6e53fcc2fea412543c32b2d71
Parents: 4621d41
Author: Sven Kobler <sv...@sap.com>
Authored: Mon Mar 23 14:53:52 2015 +0100
Committer: Sven Kobler <sv...@sap.com>
Committed: Mon Mar 23 14:53:52 2015 +0100

----------------------------------------------------------------------
 .gitignore                        |    2 +-
 Gruntfile.js                      |    8 +-
 README.md                         |    4 +-
 grunt-config/custom-tasks/sign.js |    8 +-
 grunt-config/nugetpack.nuspec     |    2 +-
 grunt-config/rat-config.js        |    6 +-
 grunt-config/release-config.js    |    4 +-
 grunt-config/sign-config.js       |    6 +-
 index-browser.js                  |   36 +
 index.js                          |   47 ++
 lib/cache.js                      | 1445 ++++++++++++++++++++++++++++++++
 lib/cache/source.js               |  206 +++++
 lib/deferred.js                   |  190 +++++
 lib/odata.js                      |  180 ++++
 lib/odata/batch.js                |  377 +++++++++
 lib/odata/handler.js              |  284 +++++++
 lib/odata/json.js                 | 1008 ++++++++++++++++++++++
 lib/odata/metadata.js             |  523 ++++++++++++
 lib/odata/net-browser.js          |  344 ++++++++
 lib/odata/net.js                  |  195 +++++
 lib/odata/odatautils.js           | 1273 ++++++++++++++++++++++++++++
 lib/store.js                      |   67 ++
 lib/store/dom.js                  |  332 ++++++++
 lib/store/indexeddb.js            |  447 ++++++++++
 lib/store/memory.js               |  247 ++++++
 lib/utils.js                      |  581 +++++++++++++
 lib/xml.js                        |  824 ++++++++++++++++++
 package.json                      |    2 +-
 src/index-browser.js              |   36 -
 src/index-node.js                 |   47 --
 src/index.js                      |   47 --
 src/lib/cache.js                  | 1445 --------------------------------
 src/lib/cache/source.js           |  206 -----
 src/lib/deferred.js               |  190 -----
 src/lib/odata.js                  |  180 ----
 src/lib/odata/batch.js            |  377 ---------
 src/lib/odata/handler.js          |  284 -------
 src/lib/odata/json.js             | 1008 ----------------------
 src/lib/odata/metadata.js         |  523 ------------
 src/lib/odata/net-browser.js      |  344 --------
 src/lib/odata/net.js              |  195 -----
 src/lib/odata/odatautils.js       | 1273 ----------------------------
 src/lib/store.js                  |   67 --
 src/lib/store/dom.js              |  332 --------
 src/lib/store/indexeddb.js        |  447 ----------
 src/lib/store/memory.js           |  247 ------
 src/lib/utils.js                  |  581 -------------
 src/lib/xml.js                    |  824 ------------------
 test/test-demo-scenario.js        |    2 +-
 test/test-odata-json.js           |    2 +-
 test/test-utils.js                |    2 +-
 tests/node-test-setup.js          |    2 +-
 52 files changed, 8631 insertions(+), 8678 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/.gitignore
----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index 7ff281f..02c5d17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,7 +8,7 @@ _*
 localgrunt.config
 
 # testing copies
-../demo/scripts/odatajs-4.0.0-beta-01*
+../demo/scripts/odatajs-4.0.0-*
 
 # build artefacts
 #build/

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/Gruntfile.js
----------------------------------------------------------------------
diff --git a/Gruntfile.js b/Gruntfile.js
index 6bf2290..faff8d0 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -36,8 +36,8 @@ module.exports = function(grunt) {
 
     "toBrowser" : {
       "release" : {
-          options: { index : "src/index-browser.js" },
-          src: ["src/lib/**/*.js", '!**/*-node.*'], 
+          options: { index : "index-browser.js" },
+          src: ["lib/**/*.js", '!**/*-node.*'],
           dest: "_build/lib/<%= artifactname %>.js"
       }
     },
@@ -55,7 +55,7 @@ module.exports = function(grunt) {
     },
     "jsdoc" : {
       "src" : {
-          src: ["src/**/*.js"], 
+          src: ["index.js","lib/**/*.js"],
           options: { destination: "_build/doc-src", verbose : true, debug : true, pedantic : true }
       }
     },
@@ -135,7 +135,7 @@ module.exports = function(grunt) {
   ]);
 
   
-  grunt.registerTask('release_sign','Sign the files which are released (run "grunt release" before"',[
+  grunt.registerTask('release-sign','Sign the files which are released (run "grunt release" before"',[
     'sign:release','sign:asc','sign:asc-verify'
   ]);
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index 83d0f68..62ee639 100644
--- a/README.md
+++ b/README.md
@@ -19,13 +19,13 @@ under the License.
 ## Olingo OData Client for JavaScript
 The Olingo OData Client for JavaScript (odatajs) is a library written in JavaScript that enables browser based frontend applications to easily use the OData protocol for communication with application servers.
 
-This library "odatajs-4.0.0-beta-01.min.js" supports only the OData V4 protocol.
+This library "odatajs-4.0.0.min.js" supports only the OData V4 protocol.
 
 For using the OData protocols V1-V3 please refer to the [datajs library](http://datajs.codeplex.com/)
 
 The odatajs library can be included in any html page with the script tag (for example)
 ```
-<script type="text/javascript" src="./sources/odatajs-4.0.0-beta-01.min.js"></script>
+<script type="text/javascript" src="./sources/odatajs-4.0.0.min.js"></script>
 ```
 and its features can be used through the `odatajs` namespace (or `window.odatajs`). The odatajs library can be used together with the datajs library which uses the `window.OData` namespace.
 

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/grunt-config/custom-tasks/sign.js
----------------------------------------------------------------------
diff --git a/grunt-config/custom-tasks/sign.js b/grunt-config/custom-tasks/sign.js
index 7f6319c..3db3f73 100644
--- a/grunt-config/custom-tasks/sign.js
+++ b/grunt-config/custom-tasks/sign.js
@@ -59,7 +59,7 @@ module.exports = function(grunt) {
                 pipeTo = workItem.src+'.md5';
 
                 grunt.log.writeln(chalk.yellow('Signing ('+workItem.type+') ' + fileName + " ..."));
-                //openssl dgst -md5 odatajs.4.0.0-beta01.nupkg
+                //openssl dgst -md5 odatajs.4.0.0.nupkg
                 taskOptions = { 
                     cmd : 'openssl', 
                     args: ['dgst','-md5',fileName],
@@ -70,7 +70,7 @@ module.exports = function(grunt) {
 
                 grunt.log.writeln(chalk.yellow('Signing ('+workItem.type+') ' + fileName + " ..."));
 
-                //gpg --print-md SHA512 odatajs-4.0.0-beta-01-RC02-doc.zip
+                //gpg --print-md SHA512 odatajs-4.0.0-doc.zip
                 taskOptions = { 
                     cmd : 'gpg', 
                     args: ['--print-md','SHA512',fileName],
@@ -81,7 +81,7 @@ module.exports = function(grunt) {
 
                 grunt.log.writeln(chalk.yellow('Signing ('+workItem.type+') ' + fileName + " ..."));
                 
-                //gpg --armor --detach-sign odatajs-4.0.0-beta-01-RC02-sources.zip
+                //gpg --armor --detach-sign odatajs-4.0.0-sources.zip
                 taskOptions = { 
                     cmd : 'gpg', 
                     args: ['--armor','--detach-sign',fileName],
@@ -93,7 +93,7 @@ module.exports = function(grunt) {
 
                 grunt.log.writeln(chalk.yellow('Verify ('+workItem.type+') ' +fileName+ '.asc' + " ..."));
                 
-                //gpg --verify --detach-sign odatajs-4.0.0-beta-01-RC02-sources.zip.asc 
+                //gpg --verify --detach-sign odatajs-4.0.0-sources.zip.asc
                 taskOptions = { 
                     cmd : 'gpg', 
                     args: ['--verify', fileName+'.asc'],

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/grunt-config/nugetpack.nuspec
----------------------------------------------------------------------
diff --git a/grunt-config/nugetpack.nuspec b/grunt-config/nugetpack.nuspec
index 120cd7d..a0c7861 100644
--- a/grunt-config/nugetpack.nuspec
+++ b/grunt-config/nugetpack.nuspec
@@ -4,7 +4,7 @@
     <title>Olingo OData Client for JavaScript</title>
     <id>odatajs</id>
     <tags>restful api open protocol odata client javascript</tags>
-    <version>4.0.0-beta01</version>
+    <version>4.0.0</version>
     <authors>Apache Olingo (Challen He, Kobler-Morris Sven)</authors>
     <owners>Apache Olingo</owners>
     <licenseUrl>http://www.apache.org/licenses/LICENSE-2.0</licenseUrl>

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/grunt-config/rat-config.js
----------------------------------------------------------------------
diff --git a/grunt-config/rat-config.js b/grunt-config/rat-config.js
index 23871ae..2ee5b77 100644
--- a/grunt-config/rat-config.js
+++ b/grunt-config/rat-config.js
@@ -1,4 +1,3 @@
-
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -17,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 module.exports = function(grunt) {
   
   
@@ -31,7 +31,7 @@ module.exports = function(grunt) {
         ] },
       files: [
         /*{ src: ['./../_dist/<%= artifactname %>/doc'], options:{ tag:"dist-doc"}},generated*/
-        { src: ['./src'], options:{ tag:"src"}},
+        { src: ['./lib'], options:{ tag:"lib"}},
         { src: ['./tests'], options:{ tag:"tests"}},
         { src: ['./demo'], options:{ tag:"demo"}},
         { src: ['./grunt-config'], options:{ tag:"grunt-config" }},
@@ -48,7 +48,7 @@ module.exports = function(grunt) {
           "JSLib.sln","package.json", "mocha.opts","info.txt"
         ] },
       files: [
-        { src: ['./src'], options:{ tag:"src"}},
+        { src: ['./lib'], options:{ tag:"lib"}},
         { src: ['./tests'], options:{ tag:"tests"}},
         { src: ['./demo'], options:{ tag:"demo"}},
         { src: ['./grunt-config'], options:{ tag:"grunt-config" }},

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/grunt-config/release-config.js
----------------------------------------------------------------------
diff --git a/grunt-config/release-config.js b/grunt-config/release-config.js
index 6865bb1..3df970e 100644
--- a/grunt-config/release-config.js
+++ b/grunt-config/release-config.js
@@ -48,7 +48,7 @@ module.exports = function(grunt) {
   grunt.config.merge( { 
     'jsdoc' : { // generate documentation
       "release-doc-src" : {
-        src: ['src/**/*.js'], 
+        src: ['**/*.js'],
         options: {
           destination: './_dist/<%= artifactname %>/doc',
           verbose : false 
@@ -71,7 +71,7 @@ module.exports = function(grunt) {
       },
       "release-nuget": {
           files: [
-              { expand: true, cwd: '_build', src: ['odatajs.4.0.0-beta01.nupkg'], dest: './_dist/<%= artifactname %>', filter: 'isFile' },
+              { expand: true, cwd: '_build', src: ['odatajs.4.0.0.nupkg'], dest: './_dist/<%= artifactname %>', filter: 'isFile' },
           ]
       },
       "release-doc" : {

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/grunt-config/sign-config.js
----------------------------------------------------------------------
diff --git a/grunt-config/sign-config.js b/grunt-config/sign-config.js
index a0ff676..6c827e3 100644
--- a/grunt-config/sign-config.js
+++ b/grunt-config/sign-config.js
@@ -28,7 +28,7 @@ module.exports = function(grunt) {
         cwd : './_dist/<%= artifactname %>/',
         src : [ 
           '<%= artifactname %>-lib.zip',
-          'odatajs.4.0.0-beta01.nupkg',
+          'odatajs.4.0.0.nupkg',
           '<%= artifactname %>-doc.zip',
           '<%= artifactname %>-sources.zip'
         ]
@@ -39,7 +39,7 @@ module.exports = function(grunt) {
         cwd : './_dist/<%= artifactname %>/',
         src : [ 
           '<%= artifactname %>-lib.zip',
-          'odatajs.4.0.0-beta01.nupkg',
+          'odatajs.4.0.0.nupkg',
           '<%= artifactname %>-doc.zip',
           '<%= artifactname %>-sources.zip'
         ]
@@ -50,7 +50,7 @@ module.exports = function(grunt) {
         cwd : './_dist/<%= artifactname %>/',
         src : [ 
           '<%= artifactname %>-lib.zip',
-          'odatajs.4.0.0-beta01.nupkg',
+          'odatajs.4.0.0.nupkg',
           '<%= artifactname %>-doc.zip',
           '<%= artifactname %>-sources.zip'
         ]

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/index-browser.js
----------------------------------------------------------------------
diff --git a/index-browser.js b/index-browser.js
new file mode 100644
index 0000000..0524487
--- /dev/null
+++ b/index-browser.js
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// version information 
+exports.version = { major: 4, minor: 0, build: 0 };
+
+// core stuff, always needed
+exports.deferred = require('./lib/deferred.js');
+exports.utils = require('./lib/utils.js');
+
+// only needed for xml metadata 
+exports.xml = require('./lib/xml.js');
+
+// only need in browser case
+exports.oData = require('./lib/odata.js');
+exports.store = require('./lib/store.js');
+exports.cache = require('./lib/cache.js');
+
+
+

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/index.js
----------------------------------------------------------------------
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..85aea5b
--- /dev/null
+++ b/index.js
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+var odatajs = {};
+
+odatajs.version = {
+    major: 4,
+    minor: 0,
+    build: 0
+};
+
+// core stuff, alway needed
+odatajs.deferred = require('./lib/deferred.js');
+odatajs.utils = require('./lib/utils.js');
+
+// only neede for xml metadata
+odatajs.xml = require('./lib/xml.js');
+
+// only need in browser case
+odatajs.oData = require('./lib/odata.js');
+odatajs.store = require('./lib/store.js');
+odatajs.cache = require('./lib/cache.js');
+
+if (typeof window !== 'undefined') {
+    //expose to browsers window object
+    window.odatajs = odatajs;
+} else {
+    //expose in commonjs style
+    odatajs.node = "node";
+    module.exports = odatajs;
+}

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/cache.js
----------------------------------------------------------------------
diff --git a/lib/cache.js b/lib/cache.js
new file mode 100644
index 0000000..dce74b6
--- /dev/null
+++ b/lib/cache.js
@@ -0,0 +1,1445 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+ /** @module cache */
+
+//var odatajs = require('./odatajs/utils.js');
+var utils =  require('./utils.js');
+var deferred = require('./deferred.js');
+var storeReq = require('./store.js');
+var cacheSource = require('./cache/source.js');
+
+
+var assigned = utils.assigned;
+var delay = utils.delay;
+var extend = utils.extend;
+var djsassert = utils.djsassert;
+var isArray = utils.isArray;
+var normalizeURI = utils.normalizeURI;
+var parseInt10 = utils.parseInt10;
+var undefinedDefault = utils.undefinedDefault;
+
+var createDeferred = deferred.createDeferred;
+var DjsDeferred = deferred.DjsDeferred;
+
+
+var getJsonValueArraryLength = utils.getJsonValueArraryLength;
+var sliceJsonValueArray = utils.sliceJsonValueArray;
+var concatJsonValueArray = utils.concatJsonValueArray;
+
+
+
+/** Appends a page's data to the operation data.
+ * @param {Object} operation - Operation with  (i)ndex, (c)ount and (d)ata.
+ * @param {Object} page - Page with (i)ndex, (c)ount and (d)ata.
+ */
+function appendPage(operation, page) {
+
+    var intersection = intersectRanges(operation, page);
+    var start = 0;
+    var end = 0;
+    if (intersection) {
+        start = intersection.i - page.i;
+        end = start + (operation.c - getJsonValueArraryLength(operation.d));
+    }
+
+    operation.d = concatJsonValueArray(operation.d, sliceJsonValueArray(page.d, start, end));
+}
+
+/** Returns the {(i)ndex, (c)ount} range for the intersection of x and y.
+ * @param {Object} x - Range with (i)ndex and (c)ount members.
+ * @param {Object} y - Range with (i)ndex and (c)ount members.
+ * @returns {Object} The intersection (i)ndex and (c)ount; undefined if there is no intersection.
+ */
+function intersectRanges(x, y) {
+
+    var xLast = x.i + x.c;
+    var yLast = y.i + y.c;
+    var resultIndex = (x.i > y.i) ? x.i : y.i;
+    var resultLast = (xLast < yLast) ? xLast : yLast;
+    var result;
+    if (resultLast >= resultIndex) {
+        result = { i: resultIndex, c: resultLast - resultIndex };
+    }
+
+    return result;
+}
+
+/** Checks whether val is a defined number with value zero or greater.
+ * @param {Number} val - Value to check.
+ * @param {String} name - Parameter name to use in exception.
+ * @throws Throws an exception if the check fails
+ */
+function checkZeroGreater(val, name) {
+
+    if (val === undefined || typeof val !== "number") {
+        throw { message: "'" + name + "' must be a number." };
+    }
+
+    if (isNaN(val) || val < 0 || !isFinite(val)) {
+        throw { message: "'" + name + "' must be greater than or equal to zero." };
+    }
+}
+
+/** Checks whether val is undefined or a number with value greater than zero.
+ * @param {Number} val - Value to check.
+ * @param {String} name - Parameter name to use in exception.
+ * @throws Throws an exception if the check fails
+ */
+function checkUndefinedGreaterThanZero(val, name) {
+
+    if (val !== undefined) {
+        if (typeof val !== "number") {
+            throw { message: "'" + name + "' must be a number." };
+        }
+
+        if (isNaN(val) || val <= 0 || !isFinite(val)) {
+            throw { message: "'" + name + "' must be greater than zero." };
+        }
+    }
+}
+
+/** Checks whether val is undefined or a number
+ * @param {Number} val - Value to check.
+ * @param {String} name - Parameter name to use in exception.
+ * @throws Throws an exception if the check fails
+ */
+function checkUndefinedOrNumber(val, name) {
+    if (val !== undefined && (typeof val !== "number" || isNaN(val) || !isFinite(val))) {
+        throw { message: "'" + name + "' must be a number." };
+    }
+}
+
+/** Performs a linear search on the specified array and removes the first instance of 'item'.
+ * @param {Array} arr - Array to search.
+ * @param {*} item - Item being sought.
+ * @returns {Boolean} true if the item was removed otherwise false
+ */
+function removeFromArray(arr, item) {
+
+    var i, len;
+    for (i = 0, len = arr.length; i < len; i++) {
+        if (arr[i] === item) {
+            arr.splice(i, 1);
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/** Estimates the size of an object in bytes.
+ * Object trees are traversed recursively
+ * @param {Object} object - Object to determine the size of.
+ * @returns {Number} Estimated size of the object in bytes.
+ */
+function estimateSize(object) {
+    var size = 0;
+    var type = typeof object;
+
+    if (type === "object" && object) {
+        for (var name in object) {
+            size += name.length * 2 + estimateSize(object[name]);
+        }
+    } else if (type === "string") {
+        size = object.length * 2;
+    } else {
+        size = 8;
+    }
+    return size;
+}
+
+/** Snaps low and high indices into page sizes and returns a range.
+ * @param {Number} lowIndex - Low index to snap to a lower value.
+ * @param {Number} highIndex - High index to snap to a higher value.
+ * @param {Number} pageSize - Page size to snap to.
+ * @returns {Object} A range with (i)ndex and (c)ount of elements.
+ */
+function snapToPageBoundaries(lowIndex, highIndex, pageSize) {
+    lowIndex = Math.floor(lowIndex / pageSize) * pageSize;
+    highIndex = Math.ceil((highIndex + 1) / pageSize) * pageSize;
+    return { i: lowIndex, c: highIndex - lowIndex };
+}
+
+// The DataCache is implemented using state machines.  The following constants are used to properly
+// identify and label the states that these machines transition to.
+var CACHE_STATE_DESTROY  = "destroy";
+var CACHE_STATE_IDLE     = "idle";
+var CACHE_STATE_INIT     = "init";
+var CACHE_STATE_READ     = "read";
+var CACHE_STATE_PREFETCH = "prefetch";
+var CACHE_STATE_WRITE    = "write";
+
+// DataCacheOperation state machine states.
+// Transitions on operations also depend on the cache current of the cache.
+var OPERATION_STATE_CANCEL = "cancel";
+var OPERATION_STATE_END    = "end";
+var OPERATION_STATE_ERROR  = "error";
+var OPERATION_STATE_START  = "start";
+var OPERATION_STATE_WAIT   = "wait";
+
+// Destroy state machine states
+var DESTROY_STATE_CLEAR = "clear";
+
+// Read / Prefetch state machine states
+var READ_STATE_DONE   = "done";
+var READ_STATE_LOCAL  = "local";
+var READ_STATE_SAVE   = "save";
+var READ_STATE_SOURCE = "source";
+
+/** Creates a new operation object.
+ * @class DataCacheOperation
+ * @param {Function} stateMachine - State machine that describes the specific behavior of the operation.
+ * @param {DjsDeferred} promise - Promise for requested values.
+ * @param {Boolean} isCancelable - Whether this operation can be canceled or not.
+ * @param {Number} index - Index of first item requested.
+ * @param {Number} count - Count of items requested.
+ * @param {Array} data - Array with the items requested by the operation.
+ * @param {Number} pending - Total number of pending prefetch records.
+ * @returns {DataCacheOperation} A new data cache operation instance.
+ */
+function DataCacheOperation(stateMachine, promise, isCancelable, index, count, data, pending) {
+
+   var stateData;
+    var cacheState;
+    var that = this;
+
+    that.p = promise;
+    that.i = index;
+    that.c = count;
+    that.d = data;
+    that.s = OPERATION_STATE_START;
+
+    that.canceled = false;
+    that.pending = pending;
+    that.oncomplete = null;
+
+    /** Transitions this operation to the cancel state and sets the canceled flag to true.
+     * The function is a no-op if the operation is non-cancelable.
+     * @method DataCacheOperation#cancel
+     */
+    that.cancel = function cancel() {
+
+        if (!isCancelable) {
+            return;
+        }
+
+        var state = that.s;
+        if (state !== OPERATION_STATE_ERROR && state !== OPERATION_STATE_END && state !== OPERATION_STATE_CANCEL) {
+            that.canceled = true;
+            that.transition(OPERATION_STATE_CANCEL, stateData);
+        }
+    };
+
+    /** Transitions this operation to the end state.
+     * @method DataCacheOperation#complete
+     */
+    that.complete = function () {
+
+        djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.complete() - operation is in the end state", that);
+        that.transition(OPERATION_STATE_END, stateData);
+    };
+
+    /** Transitions this operation to the error state.
+     * @method DataCacheOperation#error
+     */
+    that.error = function (err) {
+        if (!that.canceled) {
+            djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.error() - operation is in the end state", that);
+            djsassert(that.s !== OPERATION_STATE_ERROR, "DataCacheOperation.error() - operation is in the error state", that);
+            that.transition(OPERATION_STATE_ERROR, err);
+        }
+    };
+
+    /** Executes the operation's current state in the context of a new cache state.
+     * @method DataCacheOperation#run
+     * @param {Object} state - New cache state.
+     */
+    that.run = function (state) {
+
+        cacheState = state;
+        that.transition(that.s, stateData);
+    };
+
+    /** Transitions this operation to the wait state.
+     * @method DataCacheOperation#wait
+     */
+    that.wait = function (data) {
+
+        djsassert(that.s !== OPERATION_STATE_END, "DataCacheOperation.wait() - operation is in the end state", that);
+        that.transition(OPERATION_STATE_WAIT, data);
+    };
+
+    /** State machine that describes all operations common behavior.
+     * @method DataCacheOperation#operationStateMachine
+     * @param {Object} opTargetState - Operation state to transition to.
+     * @param {Object} cacheState - Current cache state.
+     * @param {Object} [data] - Additional data passed to the state.
+     */
+    var operationStateMachine = function (opTargetState, cacheState, data) {
+
+        switch (opTargetState) {
+            case OPERATION_STATE_START:
+                // Initial state of the operation. The operation will remain in this state until the cache has been fully initialized.
+                if (cacheState !== CACHE_STATE_INIT) {
+                    stateMachine(that, opTargetState, cacheState, data);
+                }
+                break;
+
+            case OPERATION_STATE_WAIT:
+                // Wait state indicating that the operation is active but waiting for an asynchronous operation to complete.
+                stateMachine(that, opTargetState, cacheState, data);
+                break;
+
+            case OPERATION_STATE_CANCEL:
+                // Cancel state.
+                stateMachine(that, opTargetState, cacheState, data);
+                that.fireCanceled();
+                that.transition(OPERATION_STATE_END);
+                break;
+
+            case OPERATION_STATE_ERROR:
+                // Error state. Data is expected to be an object detailing the error condition.
+                stateMachine(that, opTargetState, cacheState, data);
+                that.canceled = true;
+                that.fireRejected(data);
+                that.transition(OPERATION_STATE_END);
+                break;
+
+            case OPERATION_STATE_END:
+                // Final state of the operation.
+                if (that.oncomplete) {
+                    that.oncomplete(that);
+                }
+                if (!that.canceled) {
+                    that.fireResolved();
+                }
+                stateMachine(that, opTargetState, cacheState, data);
+                break;
+
+            default:
+                // Any other state is passed down to the state machine describing the operation's specific behavior.
+
+                if (true) {
+                    // Check that the state machine actually handled the sate.
+                    var handled = stateMachine(that, opTargetState, cacheState, data);
+                    djsassert(handled, "Bad operation state: " + opTargetState + " cacheState: " + cacheState, this);
+                } else {
+
+                    stateMachine(that, opTargetState, cacheState, data);
+
+                }
+
+                break;
+        }
+    };
+
+
+
+    /** Transitions this operation to a new state.
+     * @method DataCacheOperation#transition
+     * @param {Object} state - State to transition the operation to.
+     * @param {Object} [data] - 
+     */
+    that.transition = function (state, data) {
+        that.s = state;
+        stateData = data;
+        operationStateMachine(state, cacheState, data);
+    };
+    
+    return that;
+}
+
+/** Fires a resolved notification as necessary.
+ * @method DataCacheOperation#fireResolved
+ */
+DataCacheOperation.prototype.fireResolved = function () {
+
+    // Fire the resolve just once.
+    var p = this.p;
+    if (p) {
+        this.p = null;
+        p.resolve(this.d);
+    }
+};
+
+/** Fires a rejected notification as necessary.
+ * @method DataCacheOperation#fireRejected
+ */
+DataCacheOperation.prototype.fireRejected = function (reason) {
+
+    // Fire the rejection just once.
+    var p = this.p;
+    if (p) {
+        this.p = null;
+        p.reject(reason);
+    }
+};
+
+/** Fires a canceled notification as necessary.
+ * @method DataCacheOperation#fireCanceled
+ */
+DataCacheOperation.prototype.fireCanceled = function () {
+
+    this.fireRejected({ canceled: true, message: "Operation canceled" });
+};
+
+
+/** Creates a data cache for a collection that is efficiently loaded on-demand.
+ * @class DataCache
+ * @param options - Options for the data cache, including name, source, pageSize,
+ * prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
+ * @returns {DataCache} A new data cache instance.
+ */
+function DataCache(options) {
+
+    var state = CACHE_STATE_INIT;
+    var stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
+
+    var clearOperations = [];
+    var readOperations = [];
+    var prefetchOperations = [];
+
+    var actualCacheSize = 0;                                             // Actual cache size in bytes.
+    var allDataLocal = false;                                            // Whether all data is local.
+    var cacheSize = undefinedDefault(options.cacheSize, 1048576);        // Requested cache size in bytes, default 1 MB.
+    var collectionCount = 0;                                             // Number of elements in the server collection.
+    var highestSavedPage = 0;                                            // Highest index of all the saved pages.
+    var highestSavedPageSize = 0;                                        // Item count of the saved page with the highest index.
+    var overflowed = cacheSize === 0;                                    // If the cache has overflowed (actualCacheSize > cacheSize or cacheSize == 0);
+    var pageSize = undefinedDefault(options.pageSize, 50);               // Number of elements to store per page.
+    var prefetchSize = undefinedDefault(options.prefetchSize, pageSize); // Number of elements to prefetch from the source when the cache is idling.
+    var version = "1.0";
+    var cacheFailure;
+
+    var pendingOperations = 0;
+
+    var source = options.source;
+    if (typeof source === "string") {
+        // Create a new cache source.
+        source = new cacheSource.ODataCacheSource(options);
+    }
+    source.options = options;
+
+    // Create a cache local store.
+    var store = storeReq.createStore(options.name, options.mechanism);
+
+    var that = this;
+
+    that.onidle = options.idle;
+    that.stats = stats;
+
+    /** Counts the number of items in the collection.
+     * @method DataCache#count
+     * @returns {Object} A promise with the number of items.
+     */
+    that.count = function () {
+
+        if (cacheFailure) {
+            throw cacheFailure;
+        }
+
+        var deferred = createDeferred();
+        var canceled = false;
+
+        if (allDataLocal) {
+            delay(function () {
+                deferred.resolve(collectionCount);
+            });
+
+            return deferred.promise();
+        }
+
+        // TODO: Consider returning the local data count instead once allDataLocal flag is set to true.
+        var request = source.count(function (count) {
+            request = null;
+            stats.counts++;
+            deferred.resolve(count);
+        }, function (err) {
+            request = null;
+            deferred.reject(extend(err, { canceled: canceled }));
+        });
+
+        return extend(deferred.promise(), {
+
+             /** Aborts the count operation (used within promise callback)
+              * @method DataCache#cancelCount
+              */
+            cancel: function () {
+               
+                if (request) {
+                    canceled = true;
+                    request.abort();
+                    request = null;
+                }
+            }
+        });
+    };
+
+    /** Cancels all running operations and clears all local data associated with this cache.
+     * New read requests made while a clear operation is in progress will not be canceled.
+     * Instead they will be queued for execution once the operation is completed.
+     * @method DataCache#clear
+     * @returns {Object} A promise that has no value and can't be canceled.
+     */
+    that.clear = function () {
+
+        if (cacheFailure) {
+            throw cacheFailure;
+        }
+
+        if (clearOperations.length === 0) {
+            var deferred = createDeferred();
+            var op = new DataCacheOperation(destroyStateMachine, deferred, false);
+            queueAndStart(op, clearOperations);
+            return deferred.promise();
+        }
+        return clearOperations[0].p;
+    };
+
+    /** Filters the cache data based a predicate.
+     * Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+     * @method DataCache#filterForward
+     * @param {Number} index - The index of the item to start filtering forward from.
+     * @param {Number} count - Maximum number of items to include in the result.
+     * @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
+     * @returns {DjsDeferred} A promise for an array of results.
+     */
+    that.filterForward = function (index, count, predicate) {
+        return filter(index, count, predicate, false);
+    };
+
+    /** Filters the cache data based a predicate.
+     * Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+     * @method DataCache#filterBack
+     * @param {Number} index - The index of the item to start filtering backward from.
+     * @param {Number} count - Maximum number of items to include in the result.
+     * @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
+     * @returns {DjsDeferred} A promise for an array of results.
+     */
+    that.filterBack = function (index, count, predicate) {
+        return filter(index, count, predicate, true);
+    };
+
+    /** Reads a range of adjacent records.
+     * New read requests made while a clear operation is in progress will not be canceled.
+     * Instead they will be queued for execution once the operation is completed.
+     * @method DataCache#readRange
+     * @param {Number} index - Zero-based index of record range to read.
+     * @param {Number} count - Number of records in the range.
+     * @returns {DjsDeferred} A promise for an array of records; less records may be returned if the
+     * end of the collection is found.
+     */
+    that.readRange = function (index, count) {
+
+        checkZeroGreater(index, "index");
+        checkZeroGreater(count, "count");
+
+        if (cacheFailure) {
+            throw cacheFailure;
+        }
+
+        var deferred = createDeferred();
+
+        // Merging read operations would be a nice optimization here.
+        var op = new DataCacheOperation(readStateMachine, deferred, true, index, count, {}, 0);
+        queueAndStart(op, readOperations);
+
+        return extend(deferred.promise(), {
+            cancel: function () {
+                /** Aborts the readRange operation  (used within promise callback)
+                 * @method DataCache#cancelReadRange
+                 */
+                op.cancel();
+            }
+        });
+    };
+
+    /** Creates an Observable object that enumerates all the cache contents.
+     * @method DataCache#toObservable
+     * @returns A new Observable object that enumerates all the cache contents.
+     */
+    that.ToObservable = that.toObservable = function () {
+        if ( !utils.inBrowser()) {
+            throw { message: "Only in broser supported" };
+        }
+
+        if (!window.Rx || !window.Rx.Observable) {
+            throw { message: "Rx library not available - include rx.js" };
+        }
+
+        if (cacheFailure) {
+            throw cacheFailure;
+        }
+
+        //return window.Rx.Observable.create(function (obs) {
+        return new window.Rx.Observable(function (obs) {
+            var disposed = false;
+            var index = 0;
+
+            var errorCallback = function (error) {
+                if (!disposed) {
+                    obs.onError(error);
+                }
+            };
+
+            var successCallback = function (data) {
+                if (!disposed) {
+                    var i, len;
+                    for (i = 0, len = data.value.length; i < len; i++) {
+                        // The wrapper automatically checks for Dispose
+                        // on the observer, so we don't need to check it here.
+                        //obs.next(data.value[i]);
+                        obs.onNext(data.value[i]);
+                    }
+
+                    if (data.value.length < pageSize) {
+                        //obs.completed();
+                        obs.onCompleted();
+                    } else {
+                        index += pageSize;
+                        that.readRange(index, pageSize).then(successCallback, errorCallback);
+                    }
+                }
+            };
+
+            that.readRange(index, pageSize).then(successCallback, errorCallback);
+
+            return { Dispose: function () { 
+                obs.dispose(); // otherwise the check isStopped obs.onNext(data.value[i]);
+                disposed = true; 
+                } };
+        });
+    };
+
+    /** Creates a function that handles a callback by setting the cache into failure mode.
+     * @method DataCache~cacheFailureCallback
+     * @param {String} message - Message text.
+     * @returns {Function} Function to use as error callback.
+     * This function will specifically handle problems with critical store resources
+     * during cache initialization.
+     */
+    var cacheFailureCallback = function (message) {
+        
+
+        return function (error) {
+            cacheFailure = { message: message, error: error };
+
+            // Destroy any pending clear or read operations.
+            // At this point there should be no prefetch operations.
+            // Count operations will go through but are benign because they
+            // won't interact with the store.
+            djsassert(prefetchOperations.length === 0, "prefetchOperations.length === 0");
+            var i, len;
+            for (i = 0, len = readOperations.length; i < len; i++) {
+                readOperations[i].fireRejected(cacheFailure);
+            }
+            for (i = 0, len = clearOperations.length; i < len; i++) {
+                clearOperations[i].fireRejected(cacheFailure);
+            }
+
+            // Null out the operation arrays.
+            readOperations = clearOperations = null;
+        };
+    };
+
+    /** Updates the cache's state and signals all pending operations of the change.
+     * @method DataCache~changeState
+     * @param {Object} newState - New cache state.
+     * This method is a no-op if the cache's current state and the new state are the same.
+     */
+    var changeState = function (newState) {
+
+        if (newState !== state) {
+            state = newState;
+            var operations = clearOperations.concat(readOperations, prefetchOperations);
+            var i, len;
+            for (i = 0, len = operations.length; i < len; i++) {
+                operations[i].run(state);
+            }
+        }
+    };
+
+    /** Removes all the data stored in the cache.
+     * @method DataCache~clearStore
+     * @returns {DjsDeferred} A promise with no value.
+     */
+    var clearStore = function () {
+        djsassert(state === CACHE_STATE_DESTROY || state === CACHE_STATE_INIT, "DataCache.clearStore() - cache is not on the destroy or initialize state, current sate = " + state);
+
+        var deferred = new DjsDeferred();
+        store.clear(function () {
+
+            // Reset the cache settings.
+            actualCacheSize = 0;
+            allDataLocal = false;
+            collectionCount = 0;
+            highestSavedPage = 0;
+            highestSavedPageSize = 0;
+            overflowed = cacheSize === 0;
+
+            // version is not reset, in case there is other state in eg V1.1 that is still around.
+
+            // Reset the cache stats.
+            stats = { counts: 0, netReads: 0, prefetches: 0, cacheReads: 0 };
+            that.stats = stats;
+
+            store.close();
+            deferred.resolve();
+        }, function (err) {
+            deferred.reject(err);
+        });
+        return deferred;
+    };
+
+    /** Removes an operation from the caches queues and changes the cache state to idle.
+     * @method DataCache~dequeueOperation
+     * @param {DataCacheOperation} operation - Operation to dequeue.
+     * This method is used as a handler for the operation's oncomplete event.
+    */
+    var dequeueOperation = function (operation) {
+
+        var removed = removeFromArray(clearOperations, operation);
+        if (!removed) {
+            removed = removeFromArray(readOperations, operation);
+            if (!removed) {
+                removeFromArray(prefetchOperations, operation);
+            }
+        }
+
+        pendingOperations--;
+        changeState(CACHE_STATE_IDLE);
+    };
+
+    /** Requests data from the cache source.
+     * @method DataCache~fetchPage
+     * @param {Number} start - Zero-based index of items to request.
+     * @returns {DjsDeferred} A promise for a page object with (i)ndex, (c)ount, (d)ata.
+     */
+    var fetchPage = function (start) {
+
+        djsassert(state !== CACHE_STATE_DESTROY, "DataCache.fetchPage() - cache is on the destroy state");
+        djsassert(state !== CACHE_STATE_IDLE, "DataCache.fetchPage() - cache is on the idle state");
+
+        var deferred = new DjsDeferred();
+        var canceled = false;
+
+        var request = source.read(start, pageSize, function (data) {
+            var length = getJsonValueArraryLength(data);
+            var page = { i: start, c: length, d: data };
+            deferred.resolve(page);
+        }, function (err) {
+            deferred.reject(err);
+        });
+
+        return extend(deferred, {
+            cancel: function () {
+                if (request) {
+                    request.abort();
+                    canceled = true;
+                    request = null;
+                }
+            }
+        });
+    };
+
+    /** Filters the cache data based a predicate.
+     * @method DataCache~filter
+     * @param {Number} index - The index of the item to start filtering from.
+     * @param {Number} count - Maximum number of items to include in the result.
+     * @param {Function} predicate - Callback function returning a boolean that determines whether an item should be included in the result or not.
+     * @param {Boolean} backwards - True if the filtering should move backward from the specified index, falsey otherwise.
+     * Specifying a negative count value will yield all the items in the cache that satisfy the predicate.
+     * @returns {DjsDeferred} A promise for an array of results.
+     */
+    var filter = function (index, count, predicate, backwards) {
+
+        index = parseInt10(index);
+        count = parseInt10(count);
+
+        if (isNaN(index)) {
+            throw { message: "'index' must be a valid number.", index: index };
+        }
+        if (isNaN(count)) {
+            throw { message: "'count' must be a valid number.", count: count };
+        }
+
+        if (cacheFailure) {
+            throw cacheFailure;
+        }
+
+        index = Math.max(index, 0);
+
+        var deferred = createDeferred();
+        var returnData = {};
+        returnData.value = [];
+        var canceled = false;
+        var pendingReadRange = null;
+
+        var readMore = function (readIndex, readCount) {
+            if (!canceled) {
+                if (count > 0 && returnData.value.length >= count) {
+                    deferred.resolve(returnData);
+                } else {
+                    pendingReadRange = that.readRange(readIndex, readCount).then(function (data) {
+                        if (data["@odata.context"] && !returnData["@odata.context"]) {
+                            returnData["@odata.context"] = data["@odata.context"];
+                        }
+                        
+                        for (var i = 0, length = data.value.length; i < length && (count < 0 || returnData.value.length < count); i++) {
+                            var dataIndex = backwards ? length - i - 1 : i;
+                            var item = data.value[dataIndex];
+                            if (predicate(item)) {
+                                var element = {
+                                    index: readIndex + dataIndex,
+                                    item: item
+                                };
+
+                                backwards ? returnData.value.unshift(element) : returnData.value.push(element);
+                            }
+                        }
+
+                        // Have we reached the end of the collection?
+                        if ((!backwards && data.value.length < readCount) || (backwards && readIndex <= 0)) {
+                            deferred.resolve(returnData);
+                        } else {
+                            var nextIndex = backwards ? Math.max(readIndex - pageSize, 0) : readIndex + readCount;
+                            readMore(nextIndex, pageSize);
+                        }
+                    }, function (err) {
+                        deferred.reject(err);
+                    });
+                }
+            }
+        };
+
+        // Initially, we read from the given starting index to the next/previous page boundary
+        var initialPage = snapToPageBoundaries(index, index, pageSize);
+        var initialIndex = backwards ? initialPage.i : index;
+        var initialCount = backwards ? index - initialPage.i + 1 : initialPage.i + initialPage.c - index;
+        readMore(initialIndex, initialCount);
+
+        return extend(deferred.promise(), {
+            /** Aborts the filter operation (used within promise callback)
+            * @method DataCache#cancelFilter
+             */
+            cancel: function () {
+
+                if (pendingReadRange) {
+                    pendingReadRange.cancel();
+                }
+                canceled = true;
+            }
+        });
+    };
+
+    /** Fires an onidle event if any functions are assigned.
+     * @method DataCache~fireOnIdle
+    */
+    var fireOnIdle = function () {
+
+        if (that.onidle && pendingOperations === 0) {
+            that.onidle();
+        }
+    };
+
+    /** Creates and starts a new prefetch operation.
+     * @method DataCache~prefetch
+     * @param {Number} start - Zero-based index of the items to prefetch.
+     * This method is a no-op if any of the following conditions is true:
+     *     1.- prefetchSize is 0
+     *     2.- All data has been read and stored locally in the cache.
+     *     3.- There is already an all data prefetch operation queued.
+     *     4.- The cache has run out of available space (overflowed).
+    */
+    var prefetch = function (start) {
+        
+
+        if (allDataLocal || prefetchSize === 0 || overflowed) {
+            return;
+        }
+
+        djsassert(state === CACHE_STATE_READ, "DataCache.prefetch() - cache is not on the read state, current state: " + state);
+
+        if (prefetchOperations.length === 0 || (prefetchOperations[0] && prefetchOperations[0].c !== -1)) {
+            // Merging prefetch operations would be a nice optimization here.
+            var op = new DataCacheOperation(prefetchStateMachine, null, true, start, prefetchSize, null, prefetchSize);
+            queueAndStart(op, prefetchOperations);
+        }
+    };
+
+    /** Queues an operation and runs it.
+     * @param {DataCacheOperation} op - Operation to queue.
+     * @param {Array} queue - Array that will store the operation.
+     */
+    var queueAndStart = function (op, queue) {
+
+        op.oncomplete = dequeueOperation;
+        queue.push(op);
+        pendingOperations++;
+        op.run(state);
+    };
+
+    /** Requests a page from the cache local store.
+     * @method DataCache~readPage    
+     * @param {Number} key - Zero-based index of the reuqested page.
+     * @returns {DjsDeferred} A promise for a found flag and page object with (i)ndex, (c)ount, (d)ata, and (t)icks.
+     */
+    var readPage = function (key) {
+
+        djsassert(state !== CACHE_STATE_DESTROY, "DataCache.readPage() - cache is on the destroy state");
+
+        var canceled = false;
+        var deferred = extend(new DjsDeferred(), {
+            /** Aborts the readPage operation. (used within promise callback)
+             * @method DataCache#cancelReadPage
+             */
+            cancel: function () {
+                canceled = true;
+            }
+        });
+
+        var error = storeFailureCallback(deferred, "Read page from store failure");
+
+        store.contains(key, function (contained) {
+            if (canceled) {
+                return;
+            }
+            if (contained) {
+                store.read(key, function (_, data) {
+                    if (!canceled) {
+                        deferred.resolve(data !== undefined, data);
+                    }
+                }, error);
+                return;
+            }
+            deferred.resolve(false);
+        }, error);
+        return deferred;
+    };
+
+    /** Saves a page to the cache local store.
+     * @method DataCache~savePage    
+     * @param {Number} key - Zero-based index of the requested page.
+     * @param {Object} page - Object with (i)ndex, (c)ount, (d)ata, and (t)icks.
+     * @returns {DjsDeferred} A promise with no value.
+     */
+    var savePage = function (key, page) {
+
+        djsassert(state !== CACHE_STATE_DESTROY, "DataCache.savePage() - cache is on the destroy state");
+        djsassert(state !== CACHE_STATE_IDLE, "DataCache.savePage() - cache is on the idle state");
+
+        var canceled = false;
+
+        var deferred = extend(new DjsDeferred(), {
+            /** Aborts the savePage operation. (used within promise callback)
+             * @method DataCache#cancelReadPage
+             */
+            cancel: function () {
+                canceled = true;
+            }
+        });
+
+        var error = storeFailureCallback(deferred, "Save page to store failure");
+
+        var resolve = function () {
+            deferred.resolve(true);
+        };
+
+        if (page.c > 0) {
+            var pageBytes = estimateSize(page);
+            overflowed = cacheSize >= 0 && cacheSize < actualCacheSize + pageBytes;
+
+            if (!overflowed) {
+                store.addOrUpdate(key, page, function () {
+                    updateSettings(page, pageBytes);
+                    saveSettings(resolve, error);
+                }, error);
+            } else {
+                resolve();
+            }
+        } else {
+            updateSettings(page, 0);
+            saveSettings(resolve, error);
+        }
+        return deferred;
+    };
+
+    /** Saves the cache's current settings to the local store.
+     * @method DataCache~saveSettings    
+     * @param {Function} success - Success callback.
+     * @param {Function} error - Errror callback.
+     */
+    var saveSettings = function (success, error) {
+
+        var settings = {
+            actualCacheSize: actualCacheSize,
+            allDataLocal: allDataLocal,
+            cacheSize: cacheSize,
+            collectionCount: collectionCount,
+            highestSavedPage: highestSavedPage,
+            highestSavedPageSize: highestSavedPageSize,
+            pageSize: pageSize,
+            sourceId: source.identifier,
+            version: version
+        };
+
+        store.addOrUpdate("__settings", settings, success, error);
+    };
+
+    /** Creates a function that handles a store error.
+     * @method DataCache~storeFailureCallback    
+     * @param {DjsDeferred} deferred - Deferred object to resolve.
+     * @returns {Function} Function to use as error callback.
+    
+     * This function will specifically handle problems when interacting with the store.
+     */
+    var storeFailureCallback = function (deferred/*, message*/) {
+        
+
+        return function (/*error*/) {
+            // var console = windo1w.console;
+            // if (console && console.log) {
+            //    console.log(message);
+            //    console.dir(error);
+            // }
+            deferred.resolve(false);
+        };
+    };
+
+    /** Updates the cache's settings based on a page object.
+     * @method DataCache~updateSettings    
+     * @param {Object} page - Object with (i)ndex, (c)ount, (d)ata.
+     * @param {Number} pageBytes - Size of the page in bytes.
+     */
+    var updateSettings = function (page, pageBytes) {
+
+        var pageCount = page.c;
+        var pageIndex = page.i;
+
+        // Detect the collection size.
+        if (pageCount === 0) {
+            if (highestSavedPage === pageIndex - pageSize) {
+                collectionCount = highestSavedPage + highestSavedPageSize;
+            }
+        } else {
+            highestSavedPage = Math.max(highestSavedPage, pageIndex);
+            if (highestSavedPage === pageIndex) {
+                highestSavedPageSize = pageCount;
+            }
+            actualCacheSize += pageBytes;
+            if (pageCount < pageSize && !collectionCount) {
+                collectionCount = pageIndex + pageCount;
+            }
+        }
+
+        // Detect the end of the collection.
+        if (!allDataLocal && collectionCount === highestSavedPage + highestSavedPageSize) {
+            allDataLocal = true;
+        }
+    };
+
+    /** State machine describing the behavior for cancelling a read or prefetch operation.
+     * @method DataCache~cancelStateMachine    
+     * @param {DataCacheOperation} operation - Operation being run.
+     * @param {Object} opTargetState - Operation state to transition to.
+     * @param {Object} cacheState - Current cache state.
+     * @param {Object} [data] - 
+     * This state machine contains behavior common to read and prefetch operations.
+     */
+    var cancelStateMachine = function (operation, opTargetState, cacheState, data) {
+        
+
+        var canceled = operation.canceled && opTargetState !== OPERATION_STATE_END;
+        if (canceled) {
+            if (opTargetState === OPERATION_STATE_CANCEL) {
+                // Cancel state.
+                // Data is expected to be any pending request made to the cache.
+                if (data && data.cancel) {
+                    data.cancel();
+                }
+            }
+        }
+        return canceled;
+    };
+
+    /** State machine describing the behavior of a clear operation.
+     * @method DataCache~destroyStateMachine    
+     * @param {DataCacheOperation} operation - Operation being run.
+     * @param {Object} opTargetState - Operation state to transition to.
+     * @param {Object} cacheState - Current cache state.
+    
+     * Clear operations have the highest priority and can't be interrupted by other operations; however,
+     * they will preempt any other operation currently executing.
+     */
+    var destroyStateMachine = function (operation, opTargetState, cacheState) {
+        
+
+        var transition = operation.transition;
+
+        // Signal the cache that a clear operation is running.
+        if (cacheState !== CACHE_STATE_DESTROY) {
+            changeState(CACHE_STATE_DESTROY);
+            return true;
+        }
+
+        switch (opTargetState) {
+            case OPERATION_STATE_START:
+                // Initial state of the operation.
+                transition(DESTROY_STATE_CLEAR);
+                break;
+
+            case OPERATION_STATE_END:
+                // State that signals the operation is done.
+                fireOnIdle();
+                break;
+
+            case DESTROY_STATE_CLEAR:
+                // State that clears all the local data of the cache.
+                clearStore().then(function () {
+                    // Terminate the operation once the local store has been cleared.
+                    operation.complete();
+                });
+                // Wait until the clear request completes.
+                operation.wait();
+                break;
+
+            default:
+                return false;
+        }
+        return true;
+    };
+
+    /** State machine describing the behavior of a prefetch operation.
+     * @method DataCache~prefetchStateMachine    
+     * @param {DataCacheOperation} operation - Operation being run.
+     * @param {Object} opTargetState - Operation state to transition to.
+     * @param {Object} cacheState - Current cache state.
+     * @param {Object} [data] - 
+    
+     *  Prefetch operations have the lowest priority and will be interrupted by operations of
+     *  other kinds. A preempted prefetch operation will resume its execution only when the state
+     *  of the cache returns to idle.
+     * 
+     *  If a clear operation starts executing then all the prefetch operations are canceled,
+     *  even if they haven't started executing yet.
+     */
+    var prefetchStateMachine = function (operation, opTargetState, cacheState, data) {
+        
+
+        // Handle cancelation
+        if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
+
+            var transition = operation.transition;
+
+            // Handle preemption
+            if (cacheState !== CACHE_STATE_PREFETCH) {
+                if (cacheState === CACHE_STATE_DESTROY) {
+                    if (opTargetState !== OPERATION_STATE_CANCEL) {
+                        operation.cancel();
+                    }
+                } else if (cacheState === CACHE_STATE_IDLE) {
+                    // Signal the cache that a prefetch operation is running.
+                    changeState(CACHE_STATE_PREFETCH);
+                }
+                return true;
+            }
+
+            switch (opTargetState) {
+                case OPERATION_STATE_START:
+                    // Initial state of the operation.
+                    if (prefetchOperations[0] === operation) {
+                        transition(READ_STATE_LOCAL, operation.i);
+                    }
+                    break;
+
+                case READ_STATE_DONE:
+                    // State that determines if the operation can be resolved or has to
+                    // continue processing.
+                    // Data is expected to be the read page.
+                    var pending = operation.pending;
+
+                    if (pending > 0) {
+                        pending -= Math.min(pending, data.c);
+                    }
+
+                    // Are we done, or has all the data been stored?
+                    if (allDataLocal || pending === 0 || data.c < pageSize || overflowed) {
+                        operation.complete();
+                    } else {
+                        // Continue processing the operation.
+                        operation.pending = pending;
+                        transition(READ_STATE_LOCAL, data.i + pageSize);
+                    }
+                    break;
+
+                default:
+                    return readSaveStateMachine(operation, opTargetState, cacheState, data, true);
+            }
+        }
+        return true;
+    };
+
+    /** State machine describing the behavior of a read operation.
+     * @method DataCache~readStateMachine    
+     * @param {DataCacheOperation} operation - Operation being run.
+     * @param {Object} opTargetState - Operation state to transition to.
+     * @param {Object} cacheState - Current cache state.
+     * @param {Object} [data] - 
+    
+     * Read operations have a higher priority than prefetch operations, but lower than
+     * clear operations. They will preempt any prefetch operation currently running
+     * but will be interrupted by a clear operation.
+     *          
+     * If a clear operation starts executing then all the currently running
+     * read operations are canceled. Read operations that haven't started yet will
+     * wait in the start state until the destory operation finishes.
+     */
+    var readStateMachine = function (operation, opTargetState, cacheState, data) {
+        
+
+        // Handle cancelation
+        if (!cancelStateMachine(operation, opTargetState, cacheState, data)) {
+
+            var transition = operation.transition;
+
+            // Handle preemption
+            if (cacheState !== CACHE_STATE_READ && opTargetState !== OPERATION_STATE_START) {
+                if (cacheState === CACHE_STATE_DESTROY) {
+                    if (opTargetState !== OPERATION_STATE_START) {
+                        operation.cancel();
+                    }
+                } else if (cacheState !== CACHE_STATE_WRITE) {
+                    // Signal the cache that a read operation is running.
+                    djsassert(state == CACHE_STATE_IDLE || state === CACHE_STATE_PREFETCH, "DataCache.readStateMachine() - cache is not on the read or idle state.");
+                    changeState(CACHE_STATE_READ);
+                }
+
+                return true;
+            }
+
+            switch (opTargetState) {
+                case OPERATION_STATE_START:
+                    // Initial state of the operation.
+                    // Wait until the cache is idle or prefetching.
+                    if (cacheState === CACHE_STATE_IDLE || cacheState === CACHE_STATE_PREFETCH) {
+                        // Signal the cache that a read operation is running.
+                        changeState(CACHE_STATE_READ);
+                        if (operation.c >= 0) {
+                            // Snap the requested range to a page boundary.
+                            var range = snapToPageBoundaries(operation.i, operation.c, pageSize);
+                            transition(READ_STATE_LOCAL, range.i);
+                        } else {
+                            transition(READ_STATE_DONE, operation);
+                        }
+                    }
+                    break;
+
+                case READ_STATE_DONE:
+                    // State that determines if the operation can be resolved or has to
+                    // continue processing.
+                    // Data is expected to be the read page.
+                    appendPage(operation, data);
+                    var len = getJsonValueArraryLength(operation.d);
+                    // Are we done?
+                    if (operation.c === len || data.c < pageSize) {
+                        // Update the stats, request for a prefetch operation.
+                        stats.cacheReads++;
+                        prefetch(data.i + data.c);
+                        // Terminate the operation.
+                        operation.complete();
+                    } else {
+                        // Continue processing the operation.
+                        transition(READ_STATE_LOCAL, data.i + pageSize);
+                    }
+                    break;
+
+                default:
+                    return readSaveStateMachine(operation, opTargetState, cacheState, data, false);
+            }
+        }
+
+        return true;
+    };
+
+    /** State machine describing the behavior for reading and saving data into the cache.
+     * @method DataCache~readSaveStateMachine    
+     * @param {DataCacheOperation} operation - Operation being run.
+     * @param {Object} opTargetState - Operation state to transition to.
+     * @param {Object} cacheState - Current cache state.
+     * @param {Object} [data] - 
+     * @param {Boolean} isPrefetch - Flag indicating whether a read (false) or prefetch (true) operation is running.
+     * This state machine contains behavior common to read and prefetch operations.
+    */
+    var readSaveStateMachine = function (operation, opTargetState, cacheState, data, isPrefetch) {
+
+        var error = operation.error;
+        var transition = operation.transition;
+        var wait = operation.wait;
+        var request;
+
+        switch (opTargetState) {
+            case OPERATION_STATE_END:
+                // State that signals the operation is done.
+                fireOnIdle();
+                break;
+
+            case READ_STATE_LOCAL:
+                // State that requests for a page from the local store.
+                // Data is expected to be the index of the page to request.
+                request = readPage(data).then(function (found, page) {
+                    // Signal the cache that a read operation is running.
+                    if (!operation.canceled) {
+                        if (found) {
+                            // The page is in the local store, check if the operation can be resolved.
+                            transition(READ_STATE_DONE, page);
+                        } else {
+                            // The page is not in the local store, request it from the source.
+                            transition(READ_STATE_SOURCE, data);
+                        }
+                    }
+                });
+                break;
+
+            case READ_STATE_SOURCE:
+                // State that requests for a page from the cache source.
+                // Data is expected to be the index of the page to request.
+                request = fetchPage(data).then(function (page) {
+                    // Signal the cache that a read operation is running.
+                    if (!operation.canceled) {
+                        // Update the stats and save the page to the local store.
+                        if (isPrefetch) {
+                            stats.prefetches++;
+                        } else {
+                            stats.netReads++;
+                        }
+                        transition(READ_STATE_SAVE, page);
+                    }
+                }, error);
+                break;
+
+            case READ_STATE_SAVE:
+                // State that saves a  page to the local store.
+                // Data is expected to be the page to save.
+                // Write access to the store is exclusive.
+                if (cacheState !== CACHE_STATE_WRITE) {
+                    changeState(CACHE_STATE_WRITE);
+                    request = savePage(data.i, data).then(function (saved) {
+                        if (!operation.canceled) {
+                            if (!saved && isPrefetch) {
+                                operation.pending = 0;
+                            }
+                            // Check if the operation can be resolved.
+                            transition(READ_STATE_DONE, data);
+                        }
+                        changeState(CACHE_STATE_IDLE);
+                    });
+                }
+                break;
+
+            default:
+                // Unknown state that can't be handled by this state machine.
+                return false;
+        }
+
+        if (request) {
+            // The operation might have been canceled between stack frames do to the async calls.
+            if (operation.canceled) {
+                request.cancel();
+            } else if (operation.s === opTargetState) {
+                // Wait for the request to complete.
+                wait(request);
+            }
+        }
+
+        return true;
+    };
+
+    // Initialize the cache.
+    store.read("__settings", function (_, settings) {
+        if (assigned(settings)) {
+            var settingsVersion = settings.version;
+            if (!settingsVersion || settingsVersion.indexOf("1.") !== 0) {
+                cacheFailureCallback("Unsupported cache store version " + settingsVersion)();
+                return;
+            }
+
+            if (pageSize !== settings.pageSize || source.identifier !== settings.sourceId) {
+                // The shape or the source of the data was changed so invalidate the store.
+                clearStore().then(function () {
+                    // Signal the cache is fully initialized.
+                    changeState(CACHE_STATE_IDLE);
+                }, cacheFailureCallback("Unable to clear store during initialization"));
+            } else {
+                // Restore the saved settings.
+                actualCacheSize = settings.actualCacheSize;
+                allDataLocal = settings.allDataLocal;
+                cacheSize = settings.cacheSize;
+                collectionCount = settings.collectionCount;
+                highestSavedPage = settings.highestSavedPage;
+                highestSavedPageSize = settings.highestSavedPageSize;
+                version = settingsVersion;
+
+                // Signal the cache is fully initialized.
+                changeState(CACHE_STATE_IDLE);
+            }
+        } else {
+            // This is a brand new cache.
+            saveSettings(function () {
+                // Signal the cache is fully initialized.
+                changeState(CACHE_STATE_IDLE);
+            }, cacheFailureCallback("Unable to write settings during initialization."));
+        }
+    }, cacheFailureCallback("Unable to read settings from store."));
+
+    return that;
+}
+
+/** Creates a data cache for a collection that is efficiently loaded on-demand.
+ * @param options 
+ * Options for the data cache, including name, source, pageSize, TODO check doku
+ * prefetchSize, cacheSize, storage mechanism, and initial prefetch and local-data handler.
+ * @returns {DataCache} A new data cache instance.
+ */
+function createDataCache (options) {
+    checkUndefinedGreaterThanZero(options.pageSize, "pageSize");
+    checkUndefinedOrNumber(options.cacheSize, "cacheSize");
+    checkUndefinedOrNumber(options.prefetchSize, "prefetchSize");
+
+    if (!assigned(options.name)) {
+        throw { message: "Undefined or null name", options: options };
+    }
+
+    if (!assigned(options.source)) {
+        throw { message: "Undefined source", options: options };
+    }
+
+    return new DataCache(options);
+}
+
+
+/** estimateSize (see {@link estimateSize}) */
+exports.estimateSize = estimateSize;
+
+/** createDataCache */  
+exports.createDataCache = createDataCache;
+
+
+

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/cache/source.js
----------------------------------------------------------------------
diff --git a/lib/cache/source.js b/lib/cache/source.js
new file mode 100644
index 0000000..08dea0a
--- /dev/null
+++ b/lib/cache/source.js
@@ -0,0 +1,206 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+ /** @module cache/source */
+ 
+var utils = require("./../utils.js");
+var odataRequest = require("./../odata.js");
+
+var parseInt10 = utils.parseInt10;
+var normalizeURICase = utils.normalizeURICase;
+
+
+
+
+/** Appends the specified escaped query option to the specified URI.
+ * @param {String} uri - URI to append option to.
+ * @param {String} queryOption - Escaped query option to append.
+ */
+function appendQueryOption(uri, queryOption) {
+    var separator = (uri.indexOf("?") >= 0) ? "&" : "?";
+    return uri + separator + queryOption;
+}
+
+/** Appends the specified segment to the given URI.
+ * @param {String} uri - URI to append a segment to.
+ * @param {String} segment - Segment to append.
+ * @returns {String} The original URI with a new segment appended.
+ */
+function appendSegment(uri, segment) {
+    var index = uri.indexOf("?");
+    var queryPortion = "";
+    if (index >= 0) {
+        queryPortion = uri.substr(index);
+        uri = uri.substr(0, index);
+    }
+
+    if (uri[uri.length - 1] !== "/") {
+        uri += "/";
+    }
+    return uri + segment + queryPortion;
+}
+
+/** Builds a request object to GET the specified URI.
+ * @param {String} uri - URI for request.
+ * @param {Object} options - Additional options.
+ */
+function buildODataRequest(uri, options) {
+    return {
+        method: "GET",
+        requestUri: uri,
+        user: options.user,
+        password: options.password,
+        enableJsonpCallback: options.enableJsonpCallback,
+        callbackParameterName: options.callbackParameterName,
+        formatQueryString: options.formatQueryString
+    };
+}
+
+/** Finds the index where the value of a query option starts.
+ * @param {String} uri - URI to search in.
+ * @param {String} name - Name to look for.
+ * @returns {Number} The index where the query option starts.
+ */
+function findQueryOptionStart(uri, name) {
+    var result = -1;
+    var queryIndex = uri.indexOf("?");
+    if (queryIndex !== -1) {
+        var start = uri.indexOf("?" + name + "=", queryIndex);
+        if (start === -1) {
+            start = uri.indexOf("&" + name + "=", queryIndex);
+        }
+        if (start !== -1) {
+            result = start + name.length + 2;
+        }
+    }
+    return result;
+}
+
+/** Gets data from an OData service.
+ * @param {String} uri - URI to the OData service.
+ * @param {Object} options - Object with additional well-known request options.
+ * @param {Function} success - Success callback.
+ * @param {Function} error - Error callback.
+ * @returns {Object} Object with an abort method.
+ */
+function queryForData (uri, options, success, error) {
+    return queryForDataInternal(uri, options, {}, success, error);
+}
+
+/** Gets data from an OData service taking into consideration server side paging.
+ * @param {String} uri - URI to the OData service.
+ * @param {Object} options - Object with additional well-known request options.
+ * @param {Array} data - Array that stores the data provided by the OData service.
+ * @param {Function} success - Success callback.
+ * @param {Function} error - Error callback.
+ * @returns {Object} Object with an abort method.
+ */
+function queryForDataInternal(uri, options, data, success, error) {
+
+    var request = buildODataRequest(uri, options);
+    var currentRequest = odataRequest.request(request, function (newData) {
+        var nextLink = newData["@odata.nextLink"];
+        if (nextLink) {
+            var index = uri.indexOf(".svc/", 0);
+            if (index != -1) {
+                nextLink = uri.substring(0, index + 5) + nextLink;
+            }
+        }
+
+        if (data.value && newData.value) {
+            data.value = data.value.concat(newData.value);
+        }
+        else {
+            for (var property in newData) {
+                if (property != "@odata.nextLink") {
+                    data[property] = newData[property];
+                }
+            }
+        }
+
+        if (nextLink) {
+            currentRequest = queryForDataInternal(nextLink, options, data, success, error);
+        }
+        else {
+            success(data);
+        }
+    }, error, undefined, options.httpClient, options.metadata);
+
+    return {
+        abort: function () {
+            currentRequest.abort();
+        }
+    };
+}
+
+/** Creates a data cache source object for requesting data from an OData service.
+ * @class ODataCacheSource
+ * @param options - Options for the cache data source.
+ * @returns {ODataCacheSource} A new data cache source instance.
+ */
+function ODataCacheSource (options) {
+    var that = this;
+    var uri = options.source;
+    
+    that.identifier = normalizeURICase(encodeURI(decodeURI(uri)));
+    that.options = options;
+
+    /** Gets the number of items in the collection.
+     * @method ODataCacheSource#count
+     * @param {Function} success - Success callback with the item count.
+     * @param {Function} error - Error callback.
+     * @returns {Object} Request object with an abort method.
+     */
+    that.count = function (success, error) {
+        var options = that.options;
+        return odataRequest.request(
+            buildODataRequest(appendSegment(uri, "$count"), options),
+            function (data) {
+                var count = parseInt10(data.toString());
+                if (isNaN(count)) {
+                    error({ message: "Count is NaN", count: count });
+                } else {
+                    success(count);
+                }
+            }, error, undefined, options.httpClient, options.metadata
+        );
+    };
+    
+    /** Gets a number of consecutive items from the collection.
+     * @method ODataCacheSource#read
+     * @param {Number} index - Zero-based index of the items to retrieve.
+     * @param {Number} count - Number of items to retrieve.
+     * @param {Function} success - Success callback with the requested items.
+     * @param {Function} error - Error callback.
+     * @returns {Object} Request object with an abort method.
+    */
+    that.read = function (index, count, success, error) {
+
+        var queryOptions = "$skip=" + index + "&$top=" + count;
+        return queryForData(appendQueryOption(uri, queryOptions), that.options, success, error);
+    };
+
+    return that;
+}
+
+
+
+/** ODataCacheSource (see {@link ODataCacheSource}) */
+exports.ODataCacheSource = ODataCacheSource;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/olingo-odata4-js/blob/27f830e6/lib/deferred.js
----------------------------------------------------------------------
diff --git a/lib/deferred.js b/lib/deferred.js
new file mode 100644
index 0000000..520a857
--- /dev/null
+++ b/lib/deferred.js
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+'use strict';
+
+/** @module odatajs/deferred */
+
+
+
+/** Creates a new function to forward a call.
+ * @param {Object} thisValue - Value to use as the 'this' object.
+ * @param {String} name - Name of function to forward to.
+ * @param {Object} returnValue - Return value for the forward call (helps keep identity when chaining calls).
+ * @returns {Function} A new function that will forward a call.
+ */
+function forwardCall(thisValue, name, returnValue) {
+    return function () {
+        thisValue[name].apply(thisValue, arguments);
+        return returnValue;
+    };
+}
+
+/** Initializes a new DjsDeferred object.
+ * <ul>
+ * <li> Compability Note A - Ordering of callbacks through chained 'then' invocations <br>
+ *
+ * The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
+ * implies that .then() returns a distinct object.
+ *
+ * For compatibility with http://api.jquery.com/category/deferred-object/
+ * we return this same object. This affects ordering, as
+ * the jQuery version will fire callbacks in registration
+ * order regardless of whether they occur on the result
+ * or the original object.
+ * </li>
+ * <li>Compability Note B - Fulfillment value <br>
+ *
+ * The Wiki entry at http://wiki.commonjs.org/wiki/Promises/A
+ * implies that the result of a success callback is the
+ * fulfillment value of the object and is received by
+ * other success callbacks that are chained.
+ *
+ * For compatibility with http://api.jquery.com/category/deferred-object/
+ * we disregard this value instead.
+ * </li></ul>
+ * @class DjsDeferred 
+ */
+ function DjsDeferred() {
+    this._arguments = undefined;
+    this._done = undefined;
+    this._fail = undefined;
+    this._resolved = false;
+    this._rejected = false;
+}
+
+
+DjsDeferred.prototype = {
+
+    /** Adds success and error callbacks for this deferred object.
+     * See Compatibility Note A.
+     * @method DjsDeferred#then
+     * @param {function} [fulfilledHandler] - Success callback ( may be null)
+     * @param {function} [errorHandler] - Error callback ( may be null)
+     */
+    then: function (fulfilledHandler, errorHandler) {
+
+        if (fulfilledHandler) {
+            if (!this._done) {
+                this._done = [fulfilledHandler];
+            } else {
+                this._done.push(fulfilledHandler);
+            }
+        }
+
+        if (errorHandler) {
+            if (!this._fail) {
+                this._fail = [errorHandler];
+            } else {
+                this._fail.push(errorHandler);
+            }
+        }
+
+        //// See Compatibility Note A in the DjsDeferred constructor.
+        //// if (!this._next) {
+        ////    this._next = createDeferred();
+        //// }
+        //// return this._next.promise();
+
+        if (this._resolved) {
+            this.resolve.apply(this, this._arguments);
+        } else if (this._rejected) {
+            this.reject.apply(this, this._arguments);
+        }
+
+        return this;
+    },
+
+    /** Invokes success callbacks for this deferred object.
+     * All arguments are forwarded to success callbacks.
+     * @method DjsDeferred#resolve
+     */
+    resolve: function (/* args */) {
+        if (this._done) {
+            var i, len;
+            for (i = 0, len = this._done.length; i < len; i++) {
+                //// See Compability Note B - Fulfillment value.
+                //// var nextValue =
+                this._done[i].apply(null, arguments);
+            }
+
+            //// See Compatibility Note A in the DjsDeferred constructor.
+            //// this._next.resolve(nextValue);
+            //// delete this._next;
+
+            this._done = undefined;
+            this._resolved = false;
+            this._arguments = undefined;
+        } else {
+            this._resolved = true;
+            this._arguments = arguments;
+        }
+    },
+
+    /** Invokes error callbacks for this deferred object.
+     * All arguments are forwarded to error callbacks.
+     * @method DjsDeferred#reject
+     */
+    reject: function (/* args */) {
+        
+        if (this._fail) {
+            var i, len;
+            for (i = 0, len = this._fail.length; i < len; i++) {
+                this._fail[i].apply(null, arguments);
+            }
+
+            this._fail = undefined;
+            this._rejected = false;
+            this._arguments = undefined;
+        } else {
+            this._rejected = true;
+            this._arguments = arguments;
+        }
+    },
+
+    /** Returns a version of this object that has only the read-only methods available.
+     * @method DjsDeferred#promise
+     * @returns An object with only the promise object.
+     */
+
+    promise: function () {
+        var result = {};
+        result.then = forwardCall(this, "then", result);
+        return result;
+    }
+};
+
+/** Creates a deferred object.
+ * @returns {DjsDeferred} A new deferred object. If jQuery is installed, then a jQueryDeferred object is returned, which provides a superset of features.
+*/
+function createDeferred() {
+    if (window.jQuery && window.jQuery.Deferred) {
+        return new window.jQuery.Deferred();
+    } else {
+        return new DjsDeferred();
+    }
+}
+
+
+
+
+/** createDeferred (see {@link module:datajs/deferred~createDeferred}) */
+exports.createDeferred = createDeferred;
+
+/** DjsDeferred (see {@link DjsDeferred}) */
+exports.DjsDeferred = DjsDeferred;
\ No newline at end of file