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