From 1ce4c3bb5ad0193fbf5baf0ef1ae5d6e587f3466 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Tue, 24 Jun 2014 21:42:28 -0600 Subject: [PATCH 01/10] Fixes #76. --- .jshintrc | 69 +- CHANGELOG.md | 5 + README.md | 4 +- TRANSITION.md | 94 + bower.json | 4 +- dist/angular-data.js | 4596 +++++++++-------- dist/angular-data.min.js | 6 +- guide/nav.html | 4 +- package.json | 4 +- src/adapters/http.js | 786 ++- src/adapters/localStorage.js | 320 +- src/datastore/async_methods/create.js | 88 +- src/datastore/async_methods/destroy.js | 66 +- src/datastore/async_methods/destroyAll.js | 72 +- src/datastore/async_methods/find.js | 98 +- src/datastore/async_methods/findAll.js | 178 +- src/datastore/async_methods/index.js | 162 +- src/datastore/async_methods/refresh.js | 30 +- src/datastore/async_methods/save.js | 128 +- src/datastore/async_methods/update.js | 106 +- src/datastore/async_methods/updateAll.js | 116 +- src/datastore/index.js | 442 +- src/datastore/sync_methods/bindAll.js | 55 +- src/datastore/sync_methods/bindOne.js | 38 +- src/datastore/sync_methods/changes.js | 28 +- src/datastore/sync_methods/defineResource.js | 126 +- src/datastore/sync_methods/digest.js | 22 +- src/datastore/sync_methods/eject.js | 74 +- src/datastore/sync_methods/ejectAll.js | 68 +- src/datastore/sync_methods/filter.js | 148 +- src/datastore/sync_methods/get.js | 44 +- src/datastore/sync_methods/hasChanges.js | 36 +- src/datastore/sync_methods/index.js | 252 +- src/datastore/sync_methods/inject.js | 194 +- src/datastore/sync_methods/lastModified.js | 32 +- src/datastore/sync_methods/lastSaved.js | 26 +- src/datastore/sync_methods/previous.js | 22 +- src/errors.js | 220 +- src/index.js | 148 +- src/utils.js | 141 +- test/integration/adapters/http/create.test.js | 78 +- .../integration/adapters/http/destroy.test.js | 66 +- .../adapters/http/destroyAll.test.js | 80 +- test/integration/adapters/http/find.test.js | 66 +- .../integration/adapters/http/findAll.test.js | 80 +- test/integration/adapters/http/update.test.js | 78 +- .../adapters/http/updateAll.test.js | 80 +- .../adapters/localStorage/destroy.test.js | 76 +- .../adapters/localStorage/find.test.js | 60 +- .../adapters/localStorage/update.test.js | 64 +- .../datastore/DSCacheFactory.test.js | 186 +- .../datastore/async_methods/create.test.js | 70 +- .../datastore/async_methods/destroy.test.js | 68 +- .../async_methods/destroyAll.test.js | 120 +- .../datastore/async_methods/find.test.js | 224 +- .../datastore/async_methods/findAll.test.js | 393 +- .../datastore/async_methods/refresh.test.js | 116 +- .../datastore/async_methods/save.test.js | 226 +- .../datastore/async_methods/update.test.js | 150 +- .../datastore/async_methods/updateAll.test.js | 164 +- .../datastore/cacheFactory.test.js | 106 +- .../datastore/sync_methods/bindAll.test.js | 98 +- .../datastore/sync_methods/bindOne.test.js | 96 +- .../datastore/sync_methods/changes.test.js | 50 +- .../sync_methods/defineResource.test.js | 232 +- .../datastore/sync_methods/eject.test.js | 60 +- .../datastore/sync_methods/ejectAll.test.js | 132 +- .../datastore/sync_methods/filter.test.js | 649 ++- .../datastore/sync_methods/get.test.js | 80 +- .../datastore/sync_methods/hasChanges.test.js | 46 +- .../datastore/sync_methods/inject.test.js | 106 +- .../sync_methods/lastModified.test.js | 80 +- .../datastore/sync_methods/lastSaved.test.js | 58 +- .../datastore/sync_methods/previous.test.js | 86 +- 74 files changed, 6737 insertions(+), 6639 deletions(-) create mode 100644 TRANSITION.md diff --git a/.jshintrc b/.jshintrc index c40ad52..e408dcf 100644 --- a/.jshintrc +++ b/.jshintrc @@ -1,34 +1,39 @@ { - "node": false, - "browser": true, - "es5": true, - "esnext": true, - "bitwise": true, - "camelcase": true, - "curly": true, - "eqeqeq": true, - "immed": true, - "indent": 2, - "latedef": true, - "newcap": true, - "noarg": true, - "quotmark": "single", - "regexp": true, - "undef": true, - "unused": true, - "strict": true, - "trailing": true, - "smarttabs": true, - "predef": [ - "inject", - "describe", - "it", - "beforeEach", - "afterEach", - "assert", - "require", - "module", - "exports", - "angular" - ] + "node": false, + "browser": true, + "es5": true, + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": true, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": false, + "trailing": true, + "smarttabs": true, + "globals": { + "inject": true, + "describe": true, + "it": true, + "beforeEach": true, + "afterEach": true, + "assert": true, + "startInjector": true, + "DS": true, + "fail": true, + "$httpBackend": true, + "console": true, + "require": true, + "module": true, + "exports": true, + "angular": true + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index 60bd3a7..22fdfe8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +##### 0.10.0 - 24 June 2014 + +###### Breaking API changes +- #76 - Queries and filtering. See [TRANSITION.md](https://github.com/jmdobry/angular-data/blob/master/TRANSITION.md). + ##### 0.9.1 - 30 May 2014 ###### Backwards compatible bug fixes diff --git a/README.md b/README.md index a36ceba..5891181 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ __Data store for Angular.js.__ -__Latest Release:__ [0.9.1](http://angular-data.codetrain.io/) -__master:__ [0.9.1](http://angular-data-next.codetrain.io/) +__Latest Release:__ [0.9.1](http://angular-data.pseudobry.com/) +__master:__ [0.9.1](http://angular-data-next.pseudobry.com/) Angular-data is in a pre-1.0.0 development stage; the API is fluctuating, not a lot of tests yet, etc. diff --git a/TRANSITION.md b/TRANSITION.md new file mode 100644 index 0000000..4f07850 --- /dev/null +++ b/TRANSITION.md @@ -0,0 +1,94 @@ +### 0.9.x. ---> 0.10.0 - 24 June 2014 + +#### Breaking API changes +##### #76 - Queries and filtering. + +###### Before +```javascript +DS.findAll('post', { + query: { + where: { + name: 'John' + } + } +}) +``` + +###### After +```javascript +DS.findAll('post', { + where: { + name: 'John' + } +}) +``` + +###### Before +```javascript +DS.filter('post', { + query: { + where: { + name: 'John' + } + } +}) +``` + +###### After +```javascript +DS.filter('post', { + where: { + name: 'John' + } +}) +``` + +###### Before +```javascript +// override how DS.filter handles the "where" clause +DSProvider.defaults.filter = function (resourceName, where, attrs) { + // return true to keep the item in the result set + // return false to exclude it +}; +``` + +###### After +```javascript +// override how DS.filter handles the "where", "skip", "limit" and "orderBy" clauses +DSProvider.defaults.filter = function (collection, resourceName, params, options) { + // examine params and + // decide whether to exclude items, skip items, start from an offset, or sort the items + + // see the [default implementation that ships with angular-data](https://github.com/jmdobry/angular-data/blob/master/src/datastore/index.js#L12) + // overriding this method is useful when our server only understands a certain + // params format and you want angular-data's filter to behave the same as your server + + // angular-data looks for the following fields: + // - where + // - skip (or offset) + // - limit + // - orderBy (or sort) + + // return the filtered collection +}; +``` + +###### Before +```javascript +DSHttpAdapter.defaults.queryTransform = function (resourceName, query) { + // the second argument was the "query" field of the "params" passed in the DSHttpAdapter method + // return the transformed query +}; +``` + +###### After +```javascript +// This is useful when you don't want to implement the filter method above +// and instead rely on angular-data's expectation of where, skip, limit, orderBy, etc. +// This transform is useful when you want to change the where, skip, limit, orderBy, etc. fields +// into something your server understands. +DSHttpAdapter.defaults.queryTransform = function (resourceName, params) { + // the second argument is now the whole "params" object passed in the DSHttpAdapter method + // return the transformed params +}; +``` diff --git a/bower.json b/bower.json index a3bcbc1..7dafcef 100644 --- a/bower.json +++ b/bower.json @@ -2,8 +2,8 @@ "author": "Jason Dobry", "name": "angular-data", "description": "Data store for Angular.js.", - "version": "0.9.1", - "homepage": "http://angular-data.codetrain.io/", + "version": "0.10.0", + "homepage": "http://angular-data.pseudobry.com/", "repository": { "type": "git", "url": "git://github.com/jmdobry/angular-data.git" diff --git a/dist/angular-data.js b/dist/angular-data.js index b2fde84..de37760 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -1,7 +1,7 @@ /** * @author Jason Dobry * @file angular-data.js -* @version 0.9.1 - Homepage +* @version 0.10.0 - Homepage * @copyright (c) 2014 Jason Dobry * @license MIT * @@ -530,7 +530,7 @@ var makeIterator = require('../function/makeIterator_'); -},{"../function/makeIterator_":11}],4:[function(require,module,exports){ +},{"../function/makeIterator_":12}],4:[function(require,module,exports){ /** @@ -605,6 +605,30 @@ var filter = require('./filter'); },{"./filter":3}],7:[function(require,module,exports){ +var makeIterator = require('../function/makeIterator_'); + + /** + * Array map + */ + function map(arr, callback, thisObj) { + callback = makeIterator(callback, thisObj); + var results = []; + if (arr == null){ + return results; + } + + var i = -1, len = arr.length; + while (++i < len) { + results[i] = callback(arr[i], i, arr); + } + + return results; + } + + module.exports = map; + + +},{"../function/makeIterator_":12}],8:[function(require,module,exports){ /** @@ -641,7 +665,7 @@ var filter = require('./filter'); -},{}],8:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ /** @@ -698,7 +722,7 @@ var filter = require('./filter'); -},{}],9:[function(require,module,exports){ +},{}],10:[function(require,module,exports){ var isFunction = require('../lang/isFunction'); /** @@ -728,7 +752,7 @@ var isFunction = require('../lang/isFunction'); module.exports = toLookup; -},{"../lang/isFunction":15}],10:[function(require,module,exports){ +},{"../lang/isFunction":16}],11:[function(require,module,exports){ /** @@ -742,7 +766,7 @@ var isFunction = require('../lang/isFunction'); -},{}],11:[function(require,module,exports){ +},{}],12:[function(require,module,exports){ var identity = require('./identity'); var prop = require('./prop'); var deepMatches = require('../object/deepMatches'); @@ -778,7 +802,7 @@ var deepMatches = require('../object/deepMatches'); -},{"../object/deepMatches":20,"./identity":10,"./prop":12}],12:[function(require,module,exports){ +},{"../object/deepMatches":21,"./identity":11,"./prop":13}],13:[function(require,module,exports){ /** @@ -794,7 +818,7 @@ var deepMatches = require('../object/deepMatches'); -},{}],13:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ var isKind = require('./isKind'); /** */ @@ -804,7 +828,7 @@ var isKind = require('./isKind'); module.exports = isArray; -},{"./isKind":16}],14:[function(require,module,exports){ +},{"./isKind":17}],15:[function(require,module,exports){ var forOwn = require('../object/forOwn'); var isArray = require('./isArray'); @@ -830,7 +854,7 @@ var isArray = require('./isArray'); -},{"../object/forOwn":23,"./isArray":13}],15:[function(require,module,exports){ +},{"../object/forOwn":24,"./isArray":14}],16:[function(require,module,exports){ var isKind = require('./isKind'); /** */ @@ -840,7 +864,7 @@ var isKind = require('./isKind'); module.exports = isFunction; -},{"./isKind":16}],16:[function(require,module,exports){ +},{"./isKind":17}],17:[function(require,module,exports){ var kindOf = require('./kindOf'); /** * Check if value is from a specific "kind". @@ -851,7 +875,7 @@ var kindOf = require('./kindOf'); module.exports = isKind; -},{"./kindOf":18}],17:[function(require,module,exports){ +},{"./kindOf":19}],18:[function(require,module,exports){ /** @@ -866,7 +890,7 @@ var kindOf = require('./kindOf'); -},{}],18:[function(require,module,exports){ +},{}],19:[function(require,module,exports){ var _rKind = /^\[object (.*)\]$/, @@ -888,7 +912,7 @@ var kindOf = require('./kindOf'); module.exports = kindOf; -},{}],19:[function(require,module,exports){ +},{}],20:[function(require,module,exports){ /** @@ -903,7 +927,7 @@ var kindOf = require('./kindOf'); -},{}],20:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ var forOwn = require('./forOwn'); var isArray = require('../lang/isArray'); @@ -960,7 +984,7 @@ var isArray = require('../lang/isArray'); -},{"../lang/isArray":13,"./forOwn":23}],21:[function(require,module,exports){ +},{"../lang/isArray":14,"./forOwn":24}],22:[function(require,module,exports){ var forOwn = require('./forOwn'); var isPlainObject = require('../lang/isPlainObject'); @@ -996,7 +1020,7 @@ var isPlainObject = require('../lang/isPlainObject'); -},{"../lang/isPlainObject":17,"./forOwn":23}],22:[function(require,module,exports){ +},{"../lang/isPlainObject":18,"./forOwn":24}],23:[function(require,module,exports){ var hasOwn = require('./hasOwn'); var _hasDontEnumBug, @@ -1074,7 +1098,7 @@ var hasOwn = require('./hasOwn'); -},{"./hasOwn":24}],23:[function(require,module,exports){ +},{"./hasOwn":25}],24:[function(require,module,exports){ var hasOwn = require('./hasOwn'); var forIn = require('./forIn'); @@ -1095,7 +1119,7 @@ var forIn = require('./forIn'); -},{"./forIn":22,"./hasOwn":24}],24:[function(require,module,exports){ +},{"./forIn":23,"./hasOwn":25}],25:[function(require,module,exports){ /** @@ -1109,7 +1133,7 @@ var forIn = require('./forIn'); -},{}],25:[function(require,module,exports){ +},{}],26:[function(require,module,exports){ var forEach = require('../array/forEach'); /** @@ -1130,7 +1154,7 @@ var forEach = require('../array/forEach'); -},{"../array/forEach":4}],26:[function(require,module,exports){ +},{"../array/forEach":4}],27:[function(require,module,exports){ var slice = require('../array/slice'); /** @@ -1150,7 +1174,7 @@ var slice = require('../array/slice'); -},{"../array/slice":7}],27:[function(require,module,exports){ +},{"../array/slice":8}],28:[function(require,module,exports){ var namespace = require('./namespace'); /** @@ -1169,7 +1193,7 @@ var namespace = require('./namespace'); -},{"./namespace":25}],28:[function(require,module,exports){ +},{"./namespace":26}],29:[function(require,module,exports){ var join = require('../array/join'); var slice = require('../array/slice'); @@ -1186,7 +1210,7 @@ var slice = require('../array/slice'); module.exports = makePath; -},{"../array/join":6,"../array/slice":7}],29:[function(require,module,exports){ +},{"../array/join":6,"../array/slice":8}],30:[function(require,module,exports){ var toString = require('../lang/toString'); /** * "Safer" String.toUpperCase() @@ -1198,7 +1222,7 @@ var toString = require('../lang/toString'); module.exports = upperCase; -},{"../lang/toString":19}],30:[function(require,module,exports){ +},{"../lang/toString":20}],31:[function(require,module,exports){ /** * @doc function * @id DSHttpAdapterProvider @@ -1206,434 +1230,428 @@ var toString = require('../lang/toString'); */ function DSHttpAdapterProvider() { - /** - * @doc property - * @id DSHttpAdapterProvider.properties:defaults - * @name defaults - * @description - * Default configuration for this adapter. - * - * Properties: - * - * - `{function}` - `queryTransform` - See [the guide](/documentation/guide/adapters/index). Default: No-op. - */ - var defaults = this.defaults = { - /** - * @doc property - * @id DSHttpAdapterProvider.properties:defaults.queryTransform - * @name defaults.queryTransform - * @description - * Transform the angular-data query to something your server understands. You might just do this on the server instead. - * - * @param {string} resourceName The name of the resource. - * @param {object} query Response object from `$http()`. - * @returns {*} Returns `query` as-is. - */ - queryTransform: function (resourceName, query) { - return query; - } - }; + /** + * @doc property + * @id DSHttpAdapterProvider.properties:defaults + * @name defaults + * @description + * Default configuration for this adapter. + * + * Properties: + * + * - `{function}` - `queryTransform` - See [the guide](/documentation/guide/adapters/index). Default: No-op. + */ + var defaults = this.defaults = { + /** + * @doc property + * @id DSHttpAdapterProvider.properties:defaults.queryTransform + * @name defaults.queryTransform + * @description + * Transform the angular-data query to something your server understands. You might just do this on the server instead. + * + * @param {string} resourceName The name of the resource. + * @param {object} params Params sent through from `$http()`. + * @returns {*} Returns `params` as-is. + */ + queryTransform: function (resourceName, params) { + return params; + } + }; - this.$get = ['$http', '$log', 'DSUtils', function ($http, $log, DSUtils) { + this.$get = ['$http', '$log', 'DSUtils', function ($http, $log, DSUtils) { - /** - * @doc interface - * @id DSHttpAdapter - * @name DSHttpAdapter - * @description - * Default adapter used by angular-data. This adapter uses AJAX and JSON to send/retrieve data to/from a server. - * Developers may provide custom adapters that implement the adapter interface. - */ - return { - /** - * @doc property - * @id DSHttpAdapter.properties:defaults - * @name defaults - * @description - * Reference to [DSHttpAdapterProvider.defaults](/documentation/api/api/DSHttpAdapterProvider.properties:defaults). - */ - defaults: defaults, - - /** - * @doc method - * @id DSHttpAdapter.methods:HTTP - * @name HTTP - * @description - * Wrapper for `$http()`. - * - * ## Signature: - * ```js - * DS.HTTP(config) - * ``` - * - * ## Example: - * - * ```js - * works the same as $http() - * ``` - * - * @param {object} config Configuration for the request. - * @returns {Promise} Promise produced by the `$q` service. - */ - HTTP: HTTP, - - /** - * @doc method - * @id DSHttpAdapter.methods:GET - * @name GET - * @description - * Wrapper for `$http.get()`. - * - * ## Signature: - * ```js - * DS.GET(url[, config]) - * ``` - * - * ## Example: - * - * ```js - * Works the same as $http.get() - * ``` - * - * @param {string} url The url of the request. - * @param {object=} config Configuration for the request. - * @returns {Promise} Promise produced by the `$q` service. - */ - GET: GET, - - /** - * @doc method - * @id DSHttpAdapter.methods:POST - * @name POST - * @description - * Wrapper for `$http.post()`. - * - * ## Signature: - * ```js - * DS.POST(url[, attrs][, config]) - * ``` - * - * ## Example: - * - * ```js - * Works the same as $http.post() - * ``` - * - * @param {string} url The url of the request. - * @param {object=} attrs Request payload. - * @param {object=} config Configuration for the request. - * @returns {Promise} Promise produced by the `$q` service. - */ - POST: POST, - - /** - * @doc method - * @id DSHttpAdapter.methods:PUT - * @name PUT - * @description - * Wrapper for `$http.put()`. - * - * ## Signature: - * ```js - * DS.PUT(url[, attrs][, config]) - * ``` - * - * ## Example: - * - * ```js - * Works the same as $http.put() - * ``` - * - * @param {string} url The url of the request. - * @param {object=} attrs Request payload. - * @param {object=} config Configuration for the request. - * @returns {Promise} Promise produced by the `$q` service. - */ - PUT: PUT, - - /** - * @doc method - * @id DSHttpAdapter.methods:DEL - * @name DEL - * @description - * Wrapper for `$http.delete()`. - * - * ## Signature: - * ```js - * DS.DEL(url[, config]) - * ``` - * - * ## Example: - * - * ```js - * Works the same as $http.delete - * ``` - * - * @param {string} url The url of the request. - * @param {object} config Configuration for the request. - * @returns {Promise} Promise produced by the `$q` service. - */ - DEL: DEL, - - /** - * @doc method - * @id DSHttpAdapter.methods:find - * @name find - * @description - * Retrieve a single entity from the server. - * - * Sends a `GET` request to `:baseUrl/:endpoint/:id`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {string|number} id The primary key of the entity to retrieve. - * @param {object=} options Optional configuration. Refer to the documentation for `$http.get`. - * @returns {Promise} Promise. - */ - find: find, - - /** - * @doc method - * @id DSHttpAdapter.methods:findAll - * @name findAll - * @description - * Retrieve a collection of entities from the server. - * - * Sends a `GET` request to `:baseUrl/:endpoint`. - * - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). - * @param {object=} options Optional configuration. Refer to the documentation for `$http.get`. - * @returns {Promise} Promise. - */ - findAll: findAll, - - /** - * @doc method - * @id DSHttpAdapter.methods:findAll - * @name find - * @description - * Create a new entity on the server. - * - * Sends a `POST` request to `:baseUrl/:endpoint`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). - * @param {object=} options Optional configuration. Refer to the documentation for `$http.post`. - * @returns {Promise} Promise. - */ - create: create, - - /** - * @doc method - * @id DSHttpAdapter.methods:update - * @name update - * @description - * Update an entity on the server. - * - * Sends a `PUT` request to `:baseUrl/:endpoint/:id`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {string|number} id The primary key of the entity to update. - * @param {object} attrs The attributes with which to update the entity. See the [query guide](/documentation/guide/queries/index). - * @param {object=} options Optional configuration. Refer to the documentation for `$http.put`. - * @returns {Promise} Promise. - */ - update: update, - - /** - * @doc method - * @id DSHttpAdapter.methods:updateAll - * @name updateAll - * @description - * Update a collection of entities on the server. - * - * Sends a `PUT` request to `:baseUrl/:endpoint`. - * - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). - * @param {object=} options Optional configuration. Refer to the documentation for `$http.put`. - * @returns {Promise} Promise. - */ - updateAll: updateAll, - - /** - * @doc method - * @id DSHttpAdapter.methods:destroy - * @name destroy - * @description - * destroy an entity on the server. - * - * Sends a `PUT` request to `:baseUrl/:endpoint/:id`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {string|number} id The primary key of the entity to destroy. - * @param {object=} options Optional configuration. Refer to the documentation for `$http.delete`. - * @returns {Promise} Promise. - */ - destroy: destroy, - - /** - * @doc method - * @id DSHttpAdapter.methods:destroyAll - * @name destroyAll - * @description - * Retrieve a collection of entities from the server. - * - * Sends a `DELETE` request to `:baseUrl/:endpoint`. - * - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). - * @param {object=} options Optional configuration. Refer to the documentation for `$http.delete`. - * @returns {Promise} Promise. - */ - destroyAll: destroyAll - }; + /** + * @doc interface + * @id DSHttpAdapter + * @name DSHttpAdapter + * @description + * Default adapter used by angular-data. This adapter uses AJAX and JSON to send/retrieve data to/from a server. + * Developers may provide custom adapters that implement the adapter interface. + */ + return { + /** + * @doc property + * @id DSHttpAdapter.properties:defaults + * @name defaults + * @description + * Reference to [DSHttpAdapterProvider.defaults](/documentation/api/api/DSHttpAdapterProvider.properties:defaults). + */ + defaults: defaults, + + /** + * @doc method + * @id DSHttpAdapter.methods:HTTP + * @name HTTP + * @description + * Wrapper for `$http()`. + * + * ## Signature: + * ```js + * DS.HTTP(config) + * ``` + * + * ## Example: + * + * ```js + * works the same as $http() + * ``` + * + * @param {object} config Configuration for the request. + * @returns {Promise} Promise produced by the `$q` service. + */ + HTTP: HTTP, + + /** + * @doc method + * @id DSHttpAdapter.methods:GET + * @name GET + * @description + * Wrapper for `$http.get()`. + * + * ## Signature: + * ```js + * DS.GET(url[, config]) + * ``` + * + * ## Example: + * + * ```js + * Works the same as $http.get() + * ``` + * + * @param {string} url The url of the request. + * @param {object=} config Configuration for the request. + * @returns {Promise} Promise produced by the `$q` service. + */ + GET: GET, + + /** + * @doc method + * @id DSHttpAdapter.methods:POST + * @name POST + * @description + * Wrapper for `$http.post()`. + * + * ## Signature: + * ```js + * DS.POST(url[, attrs][, config]) + * ``` + * + * ## Example: + * + * ```js + * Works the same as $http.post() + * ``` + * + * @param {string} url The url of the request. + * @param {object=} attrs Request payload. + * @param {object=} config Configuration for the request. + * @returns {Promise} Promise produced by the `$q` service. + */ + POST: POST, + + /** + * @doc method + * @id DSHttpAdapter.methods:PUT + * @name PUT + * @description + * Wrapper for `$http.put()`. + * + * ## Signature: + * ```js + * DS.PUT(url[, attrs][, config]) + * ``` + * + * ## Example: + * + * ```js + * Works the same as $http.put() + * ``` + * + * @param {string} url The url of the request. + * @param {object=} attrs Request payload. + * @param {object=} config Configuration for the request. + * @returns {Promise} Promise produced by the `$q` service. + */ + PUT: PUT, + + /** + * @doc method + * @id DSHttpAdapter.methods:DEL + * @name DEL + * @description + * Wrapper for `$http.delete()`. + * + * ## Signature: + * ```js + * DS.DEL(url[, config]) + * ``` + * + * ## Example: + * + * ```js + * Works the same as $http.delete + * ``` + * + * @param {string} url The url of the request. + * @param {object} config Configuration for the request. + * @returns {Promise} Promise produced by the `$q` service. + */ + DEL: DEL, + + /** + * @doc method + * @id DSHttpAdapter.methods:find + * @name find + * @description + * Retrieve a single entity from the server. + * + * Sends a `GET` request to `:baseUrl/:endpoint/:id`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {string|number} id The primary key of the entity to retrieve. + * @param {object=} options Optional configuration. Refer to the documentation for `$http.get`. + * @returns {Promise} Promise. + */ + find: find, + + /** + * @doc method + * @id DSHttpAdapter.methods:findAll + * @name findAll + * @description + * Retrieve a collection of entities from the server. + * + * Sends a `GET` request to `:baseUrl/:endpoint`. + * + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). + * @param {object=} options Optional configuration. Refer to the documentation for `$http.get`. + * @returns {Promise} Promise. + */ + findAll: findAll, + + /** + * @doc method + * @id DSHttpAdapter.methods:findAll + * @name find + * @description + * Create a new entity on the server. + * + * Sends a `POST` request to `:baseUrl/:endpoint`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). + * @param {object=} options Optional configuration. Refer to the documentation for `$http.post`. + * @returns {Promise} Promise. + */ + create: create, + + /** + * @doc method + * @id DSHttpAdapter.methods:update + * @name update + * @description + * Update an entity on the server. + * + * Sends a `PUT` request to `:baseUrl/:endpoint/:id`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {string|number} id The primary key of the entity to update. + * @param {object} attrs The attributes with which to update the entity. See the [query guide](/documentation/guide/queries/index). + * @param {object=} options Optional configuration. Refer to the documentation for `$http.put`. + * @returns {Promise} Promise. + */ + update: update, + + /** + * @doc method + * @id DSHttpAdapter.methods:updateAll + * @name updateAll + * @description + * Update a collection of entities on the server. + * + * Sends a `PUT` request to `:baseUrl/:endpoint`. + * + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). + * @param {object=} options Optional configuration. Refer to the documentation for `$http.put`. + * @returns {Promise} Promise. + */ + updateAll: updateAll, + + /** + * @doc method + * @id DSHttpAdapter.methods:destroy + * @name destroy + * @description + * destroy an entity on the server. + * + * Sends a `PUT` request to `:baseUrl/:endpoint/:id`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {string|number} id The primary key of the entity to destroy. + * @param {object=} options Optional configuration. Refer to the documentation for `$http.delete`. + * @returns {Promise} Promise. + */ + destroy: destroy, + + /** + * @doc method + * @id DSHttpAdapter.methods:destroyAll + * @name destroyAll + * @description + * Retrieve a collection of entities from the server. + * + * Sends a `DELETE` request to `:baseUrl/:endpoint`. + * + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {object=} params Search query parameters. See the [query guide](/documentation/guide/queries/index). + * @param {object=} options Optional configuration. Refer to the documentation for `$http.delete`. + * @returns {Promise} Promise. + */ + destroyAll: destroyAll + }; - function HTTP(config) { - var start = new Date().getTime(); + function HTTP(config) { + var start = new Date().getTime(); - return $http(config).then(function (data) { - $log.debug(data.config.method + ' request:' + data.config.url + ' Time taken: ' + (new Date().getTime() - start) + 'ms', arguments); - return data; - }); - } + return $http(config).then(function (data) { + $log.debug(data.config.method + ' request:' + data.config.url + ' Time taken: ' + (new Date().getTime() - start) + 'ms', arguments); + return data; + }); + } - function GET(url, config) { - config = config || {}; - return HTTP(DSUtils.deepMixIn(config, { - url: url, - method: 'GET' - })); - } + function GET(url, config) { + config = config || {}; + return HTTP(DSUtils.deepMixIn(config, { + url: url, + method: 'GET' + })); + } - function POST(url, attrs, config) { - config = config || {}; - return HTTP(DSUtils.deepMixIn(config, { - url: url, - data: attrs, - method: 'POST' - })); - } + function POST(url, attrs, config) { + config = config || {}; + return HTTP(DSUtils.deepMixIn(config, { + url: url, + data: attrs, + method: 'POST' + })); + } - function PUT(url, attrs, config) { - config = config || {}; - return HTTP(DSUtils.deepMixIn(config, { - url: url, - data: attrs, - method: 'PUT' - })); - } + function PUT(url, attrs, config) { + config = config || {}; + return HTTP(DSUtils.deepMixIn(config, { + url: url, + data: attrs, + method: 'PUT' + })); + } - function DEL(url, config) { - config = config || {}; - return this.HTTP(DSUtils.deepMixIn(config, { - url: url, - method: 'DELETE' - })); - } + function DEL(url, config) { + config = config || {}; + return this.HTTP(DSUtils.deepMixIn(config, { + url: url, + method: 'DELETE' + })); + } - function create(resourceConfig, attrs, options) { - options = options || {}; - return this.POST( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), - attrs, - options - ); - } + function create(resourceConfig, attrs, options) { + options = options || {}; + return this.POST( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), + attrs, + options + ); + } - function destroy(resourceConfig, id, options) { - options = options || {}; - return this.DEL( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), - options - ); - } + function destroy(resourceConfig, id, options) { + options = options || {}; + return this.DEL( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), + options + ); + } - function destroyAll(resourceConfig, params, options) { - options = options || {}; - options.params = options.params || {}; - if (params) { - if (params.query) { - params.query = defaults.queryTransform(resourceConfig.name, params.query); - } - DSUtils.deepMixIn(options.params, params); - } - return this.DEL( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), - options - ); - } + function destroyAll(resourceConfig, params, options) { + options = options || {}; + options.params = options.params || {}; + if (params) { + params = defaults.queryTransform(resourceConfig.name, params); + DSUtils.deepMixIn(options.params, params); + } + return this.DEL( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), + options + ); + } - function find(resourceConfig, id, options) { - options = options || {}; - return this.GET( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), - options - ); - } + function find(resourceConfig, id, options) { + options = options || {}; + return this.GET( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), + options + ); + } - function findAll(resourceConfig, params, options) { - options = options || {}; - options.params = options.params || {}; - if (params) { - if (params.query) { - params.query = defaults.queryTransform(resourceConfig.name, params.query); - } - DSUtils.deepMixIn(options.params, params); - } - return this.GET( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), - options - ); - } + function findAll(resourceConfig, params, options) { + options = options || {}; + options.params = options.params || {}; + if (params) { + params = defaults.queryTransform(resourceConfig.name, params); + DSUtils.deepMixIn(options.params, params); + } + return this.GET( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), + options + ); + } - function update(resourceConfig, id, attrs, options) { - options = options || {}; - return this.PUT( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), - attrs, - options - ); - } + function update(resourceConfig, id, attrs, options) { + options = options || {}; + return this.PUT( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), + attrs, + options + ); + } - function updateAll(resourceConfig, attrs, params, options) { - options = options || {}; - options.params = options.params || {}; - if (params) { - if (params.query) { - params.query = defaults.queryTransform(resourceConfig.name, params.query); - } - DSUtils.deepMixIn(options.params, params); - } - return this.PUT( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), - attrs, - options - ); - } - }]; + function updateAll(resourceConfig, attrs, params, options) { + options = options || {}; + options.params = options.params || {}; + if (params) { + params = defaults.queryTransform(resourceConfig.name, params); + DSUtils.deepMixIn(options.params, params); + } + return this.PUT( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint), + attrs, + options + ); + } + }]; } module.exports = DSHttpAdapterProvider; -},{}],31:[function(require,module,exports){ +},{}],32:[function(require,module,exports){ /** * @doc function * @id DSLocalStorageProvider @@ -1641,184 +1659,184 @@ module.exports = DSHttpAdapterProvider; */ function DSLocalStorageProvider() { - this.$get = ['$q', 'DSUtils', function ($q, DSUtils) { + this.$get = ['$q', 'DSUtils', function ($q, DSUtils) { - /** - * @doc interface - * @id DSLocalStorage - * @name DSLocalStorage - * @description - * Default adapter used by angular-data. This adapter uses AJAX and JSON to send/retrieve data to/from a server. - * Developers may provide custom adapters that implement the adapter interface. - */ - return { - /** - * @doc method - * @id DSLocalStorage.methods:find - * @name find - * @description - * Retrieve a single entity from localStorage. - * - * Calls `localStorage.getItem(key)`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `namespace` - Namespace path for the resource. - * @param {string|number} id The primary key of the entity to retrieve. - * @returns {Promise} Promise. - */ - find: find, - - /** - * @doc method - * @id DSLocalStorage.methods:findAll - * @name findAll - * @description - * Not supported. - */ - findAll: function () { - throw new Error('Not supported!'); - }, - - /** - * @doc method - * @id DSLocalStorage.methods:findAll - * @name find - * @description - * Not supported. - */ - create: function () { - throw new Error('Not supported!'); - }, - - /** - * @doc method - * @id DSLocalStorage.methods:update - * @name update - * @description - * Update an entity in localStorage. - * - * Calls `localStorage.setItem(key, value)`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `namespace` - Namespace path for the resource. - * @param {string|number} id The primary key of the entity to update. - * @param {object} attrs The attributes with which to update the entity. - * @returns {Promise} Promise. - */ - update: update, - - /** - * @doc method - * @id DSLocalStorage.methods:updateAll - * @name updateAll - * @description - * Not supported. - */ - updateAll: function () { - throw new Error('Not supported!'); - }, - - /** - * @doc method - * @id DSLocalStorage.methods:destroy - * @name destroy - * @description - * Destroy an entity from localStorage. - * - * Calls `localStorage.removeItem(key)`. - * - * @param {object} resourceConfig Properties: - * - `{string}` - `baseUrl` - Base url. - * - `{string=}` - `endpoint` - Endpoint path for the resource. - * @param {string|number} id The primary key of the entity to destroy. - * @returns {Promise} Promise. - */ - destroy: destroy, - - /** - * @doc method - * @id DSLocalStorage.methods:destroyAll - * @name destroyAll - * @description - * Not supported. - */ - destroyAll: function () { - throw new Error('Not supported!'); - } - }; + /** + * @doc interface + * @id DSLocalStorage + * @name DSLocalStorage + * @description + * Default adapter used by angular-data. This adapter uses AJAX and JSON to send/retrieve data to/from a server. + * Developers may provide custom adapters that implement the adapter interface. + */ + return { + /** + * @doc method + * @id DSLocalStorage.methods:find + * @name find + * @description + * Retrieve a single entity from localStorage. + * + * Calls `localStorage.getItem(key)`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `namespace` - Namespace path for the resource. + * @param {string|number} id The primary key of the entity to retrieve. + * @returns {Promise} Promise. + */ + find: find, + + /** + * @doc method + * @id DSLocalStorage.methods:findAll + * @name findAll + * @description + * Not supported. + */ + findAll: function () { + throw new Error('Not supported!'); + }, + + /** + * @doc method + * @id DSLocalStorage.methods:findAll + * @name find + * @description + * Not supported. + */ + create: function () { + throw new Error('Not supported!'); + }, + + /** + * @doc method + * @id DSLocalStorage.methods:update + * @name update + * @description + * Update an entity in localStorage. + * + * Calls `localStorage.setItem(key, value)`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `namespace` - Namespace path for the resource. + * @param {string|number} id The primary key of the entity to update. + * @param {object} attrs The attributes with which to update the entity. + * @returns {Promise} Promise. + */ + update: update, + + /** + * @doc method + * @id DSLocalStorage.methods:updateAll + * @name updateAll + * @description + * Not supported. + */ + updateAll: function () { + throw new Error('Not supported!'); + }, + + /** + * @doc method + * @id DSLocalStorage.methods:destroy + * @name destroy + * @description + * Destroy an entity from localStorage. + * + * Calls `localStorage.removeItem(key)`. + * + * @param {object} resourceConfig Properties: + * - `{string}` - `baseUrl` - Base url. + * - `{string=}` - `endpoint` - Endpoint path for the resource. + * @param {string|number} id The primary key of the entity to destroy. + * @returns {Promise} Promise. + */ + destroy: destroy, + + /** + * @doc method + * @id DSLocalStorage.methods:destroyAll + * @name destroyAll + * @description + * Not supported. + */ + destroyAll: function () { + throw new Error('Not supported!'); + } + }; - function GET(key) { - var deferred = $q.defer(); - try { - var item = localStorage.getItem(key); - deferred.resolve(item ? angular.fromJson(item) : undefined); - } catch (err) { - deferred.reject(err); - } - return deferred.promise; - } + function GET(key) { + var deferred = $q.defer(); + try { + var item = localStorage.getItem(key); + deferred.resolve(item ? angular.fromJson(item) : undefined); + } catch (err) { + deferred.reject(err); + } + return deferred.promise; + } - function PUT(key, value) { - var deferred = $q.defer(); - try { - var item = localStorage.getItem(key); - if (item) { - item = angular.fromJson(item); - DSUtils.deepMixIn(item, value); - deferred.resolve(localStorage.setItem(key, angular.toJson(item))); - } else { - deferred.resolve(localStorage.setItem(key, angular.toJson(value))); - } - } catch (err) { - deferred.reject(err); - } - return deferred.promise; - } + function PUT(key, value) { + var deferred = $q.defer(); + try { + var item = localStorage.getItem(key); + if (item) { + item = angular.fromJson(item); + DSUtils.deepMixIn(item, value); + deferred.resolve(localStorage.setItem(key, angular.toJson(item))); + } else { + deferred.resolve(localStorage.setItem(key, angular.toJson(value))); + } + } catch (err) { + deferred.reject(err); + } + return deferred.promise; + } - function DEL(key) { - var deferred = $q.defer(); - try { - deferred.resolve(localStorage.removeItem(key)); - } catch (err) { - deferred.reject(err); - } - return deferred.promise; - } + function DEL(key) { + var deferred = $q.defer(); + try { + deferred.resolve(localStorage.removeItem(key)); + } catch (err) { + deferred.reject(err); + } + return deferred.promise; + } - function destroy(resourceConfig, id, options) { - options = options || {}; - return DEL( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), - options - ); - } + function destroy(resourceConfig, id, options) { + options = options || {}; + return DEL( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), + options + ); + } - function find(resourceConfig, id, options) { - options = options || {}; - return GET( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), - options - ); - } + function find(resourceConfig, id, options) { + options = options || {}; + return GET( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), + options + ); + } - function update(resourceConfig, id, attrs, options) { - options = options || {}; - return PUT( - DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), - attrs, - options - ).then(function () { - return GET(DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id)); - }); - } - }]; + function update(resourceConfig, id, attrs, options) { + options = options || {}; + return PUT( + DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id), + attrs, + options + ).then(function () { + return GET(DSUtils.makePath(options.baseUrl || resourceConfig.baseUrl, resourceConfig.endpoint, id)); + }); + } + }]; } module.exports = DSLocalStorageProvider; -},{}],32:[function(require,module,exports){ +},{}],33:[function(require,module,exports){ var errorPrefix = 'DS.create(resourceName, attrs[, options]): '; /** @@ -1864,60 +1882,60 @@ var errorPrefix = 'DS.create(resourceName, attrs[, options]): '; * - `{UnhandledError}` */ function create(resourceName, attrs, options) { - var deferred = this.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isObject(attrs)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!', { attrs: { actual: typeof attrs, expected: 'object' } })); - } else { - try { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - promise = promise - .then(function (attrs) { - return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.validate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.beforeCreate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.adapters[options.adapter || definition.defaultAdapter].create(definition, definition.serialize(resourceName, attrs), options); - }) - .then(function (res) { - return _this.$q.promisify(definition.afterCreate)(resourceName, definition.deserialize(resourceName, res)); - }) - .then(function (data) { - var created = _this.inject(definition.name, data); - var id = created[definition.idAttribute]; - resource.previousAttributes[id] = _this.utils.deepMixIn({}, created); - resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); - return _this.get(definition.name, id); - }); - - deferred.resolve(attrs); - } catch (err) { - deferred.reject(new this.errors.UnhandledError(err)); - } - } + var deferred = this.$q.defer(), + promise = deferred.promise; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isObject(attrs)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!', { attrs: { actual: typeof attrs, expected: 'object' } })); + } else { + try { + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + promise = promise + .then(function (attrs) { + return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.beforeCreate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.adapters[options.adapter || definition.defaultAdapter].create(definition, definition.serialize(resourceName, attrs), options); + }) + .then(function (res) { + return _this.$q.promisify(definition.afterCreate)(resourceName, definition.deserialize(resourceName, res)); + }) + .then(function (data) { + var created = _this.inject(definition.name, data); + var id = created[definition.idAttribute]; + resource.previousAttributes[id] = _this.utils.deepMixIn({}, created); + resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); + return _this.get(definition.name, id); + }); - return promise; + deferred.resolve(attrs); + } catch (err) { + deferred.reject(new this.errors.UnhandledError(err)); + } + } + + return promise; } module.exports = create; -},{}],33:[function(require,module,exports){ +},{}],34:[function(require,module,exports){ var errorPrefix = 'DS.destroy(resourceName, id): '; /** @@ -1963,48 +1981,48 @@ var errorPrefix = 'DS.destroy(resourceName, id): '; * - `{UnhandledError}` */ function destroy(resourceName, id, options) { - var deferred = this.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); - } else { - var item = this.get(resourceName, id); - if (!item) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); - } else { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - promise = promise - .then(function (attrs) { - return _this.$q.promisify(definition.beforeDestroy)(resourceName, attrs); - }) - .then(function () { - return _this.adapters[options.adapter || definition.defaultAdapter].destroy(definition, id, options); - }) - .then(function () { - return _this.$q.promisify(definition.afterDestroy)(resourceName, item); - }) - .then(function () { - _this.eject(resourceName, id); - return id; - }); - deferred.resolve(item); - } - } + var deferred = this.$q.defer(), + promise = deferred.promise; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); + } else { + var item = this.get(resourceName, id); + if (!item) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); + } else { + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + promise = promise + .then(function (attrs) { + return _this.$q.promisify(definition.beforeDestroy)(resourceName, attrs); + }) + .then(function () { + return _this.adapters[options.adapter || definition.defaultAdapter].destroy(definition, id, options); + }) + .then(function () { + return _this.$q.promisify(definition.afterDestroy)(resourceName, item); + }) + .then(function () { + _this.eject(resourceName, id); + return id; + }); + deferred.resolve(item); + } + } - return promise; + return promise; } module.exports = destroy; -},{}],34:[function(require,module,exports){ +},{}],35:[function(require,module,exports){ var errorPrefix = 'DS.destroyAll(resourceName, params[, options]): '; /** @@ -2023,7 +2041,7 @@ var errorPrefix = 'DS.destroyAll(resourceName, params[, options]): '; * ## Example: * * ```js - * var query = { + * var params = { * where: { * author: { * '==': 'John Anderson' @@ -2031,13 +2049,9 @@ var errorPrefix = 'DS.destroyAll(resourceName, params[, options]): '; * } * }; * - * DS.destroyAll('document', { - * query: query - * }).then(function (documents) { + * DS.destroyAll('document', params).then(function (documents) { * // The documents are gone from the data store - * DS.filter('document', { - * query: query - * }); // [] + * DS.filter('document', params); // [] * * }, function (err) { * // handle error @@ -2047,11 +2061,11 @@ var errorPrefix = 'DS.destroyAll(resourceName, params[, options]): '; * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. * @param {object} params Parameter object that is serialized into the query string. Properties: * - * - `{object=}` - `query` - The query object by which to filter items of the type specified by `resourceName`. Properties: - * - `{object=}` - `where` - Where clause. - * - `{number=}` - `limit` - Limit clause. - * - `{skip=}` - `skip` - Skip clause. - * - `{orderBy=}` - `orderBy` - OrderBy clause. + * - `{object=}` - `where` - Where clause. + * - `{number=}` - `limit` - Limit clause. + * - `{number=}` - `skip` - Skip clause. + * - `{number=}` - `offset` - Same as skip. + * - `{string|array=}` - `orderBy` - OrderBy clause. * * @param {object=} options Optional configuration. Properties: * - `{boolean=}` - `bypassCache` - Bypass the cache. Default: `false`. @@ -2065,41 +2079,41 @@ var errorPrefix = 'DS.destroyAll(resourceName, params[, options]): '; * - `{UnhandledError}` */ function destroyAll(resourceName, params, options) { - var deferred = this.$q.defer(), - promise = deferred.promise, - _this = this; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isObject(params)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!')); - } else if (!this.utils.isObject(options)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); - } else { - try { - var definition = this.definitions[resourceName]; - - promise = promise - .then(function () { - return _this.adapters[options.adapter || definition.defaultAdapter].destroyAll(definition, params, options); - }) - .then(function () { - return _this.ejectAll(resourceName, params); - }); - deferred.resolve(); - } catch (err) { - deferred.reject(new this.errors.UnhandledError(err)); - } - } + var deferred = this.$q.defer(), + promise = deferred.promise, + _this = this; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isObject(params)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!')); + } else if (!this.utils.isObject(options)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); + } else { + try { + var definition = this.definitions[resourceName]; + + promise = promise + .then(function () { + return _this.adapters[options.adapter || definition.defaultAdapter].destroyAll(definition, params, options); + }) + .then(function () { + return _this.ejectAll(resourceName, params); + }); + deferred.resolve(); + } catch (err) { + deferred.reject(new this.errors.UnhandledError(err)); + } + } - return promise; + return promise; } module.exports = destroyAll; -},{}],35:[function(require,module,exports){ +},{}],36:[function(require,module,exports){ var errorPrefix = 'DS.find(resourceName, id[, options]): '; /** @@ -2147,122 +2161,122 @@ var errorPrefix = 'DS.find(resourceName, id[, options]): '; * - `{UnhandledError}` */ function find(resourceName, id, options) { - var deferred = this.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); - } else if (!this.utils.isObject(options)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); - } else { - if (!('cacheResponse' in options)) { - options.cacheResponse = true; - } else { - options.cacheResponse = !!options.cacheResponse; - } - try { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - if (options.bypassCache) { - delete resource.completedQueries[id]; - } - - if (!(id in resource.completedQueries)) { - if (!(id in resource.pendingQueries)) { - promise = resource.pendingQueries[id] = _this.adapters[options.adapter || definition.defaultAdapter].find(definition, id, options) - .then(function (res) { - var data = definition.deserialize(resourceName, res); - if (options.cacheResponse) { - // Query is no longer pending - delete resource.pendingQueries[id]; - resource.completedQueries[id] = new Date().getTime(); - return _this.inject(resourceName, data); - } else { - return data; - } - }, function (err) { - delete resource.pendingQueries[id]; - return _this.$q.reject(err); - }); - } + var deferred = this.$q.defer(), + promise = deferred.promise; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); + } else if (!this.utils.isObject(options)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); + } else { + if (!('cacheResponse' in options)) { + options.cacheResponse = true; + } else { + options.cacheResponse = !!options.cacheResponse; + } + try { + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + if (options.bypassCache) { + delete resource.completedQueries[id]; + } + + if (!(id in resource.completedQueries)) { + if (!(id in resource.pendingQueries)) { + promise = resource.pendingQueries[id] = _this.adapters[options.adapter || definition.defaultAdapter].find(definition, id, options) + .then(function (res) { + var data = definition.deserialize(resourceName, res); + if (options.cacheResponse) { + // Query is no longer pending + delete resource.pendingQueries[id]; + resource.completedQueries[id] = new Date().getTime(); + return _this.inject(resourceName, data); + } else { + return data; + } + }, function (err) { + delete resource.pendingQueries[id]; + return _this.$q.reject(err); + }); + } - return resource.pendingQueries[id]; - } else { - deferred.resolve(_this.get(resourceName, id)); - } - } catch (err) { - deferred.reject(err); - } - } + return resource.pendingQueries[id]; + } else { + deferred.resolve(_this.get(resourceName, id)); + } + } catch (err) { + deferred.reject(err); + } + } - return promise; + return promise; } module.exports = find; -},{}],36:[function(require,module,exports){ +},{}],37:[function(require,module,exports){ var errorPrefix = 'DS.findAll(resourceName, params[, options]): '; function processResults(utils, data, resourceName, queryHash) { - var resource = this.store[resourceName]; + var resource = this.store[resourceName]; - data = data || []; + data = data || []; - // Query is no longer pending - delete resource.pendingQueries[queryHash]; - resource.completedQueries[queryHash] = new Date().getTime(); + // Query is no longer pending + delete resource.pendingQueries[queryHash]; + resource.completedQueries[queryHash] = new Date().getTime(); - // Update modified timestamp of collection - resource.collectionModified = utils.updateTimestamp(resource.collectionModified); + // Update modified timestamp of collection + resource.collectionModified = utils.updateTimestamp(resource.collectionModified); - // Merge the new values into the cache - return this.inject(resourceName, data); + // Merge the new values into the cache + return this.inject(resourceName, data); } function _findAll(utils, resourceName, params, options) { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this, - queryHash = utils.toJson(params); - - if (options.bypassCache) { - delete resource.completedQueries[queryHash]; - } - - if (!(queryHash in resource.completedQueries)) { - // This particular query has never been completed - - if (!(queryHash in resource.pendingQueries)) { - - // This particular query has never even been made - resource.pendingQueries[queryHash] = _this.adapters[options.adapter || definition.defaultAdapter].findAll(definition, params, options) - .then(function (res) { - var data = definition.deserialize(resourceName, res); - if (options.cacheResponse) { - try { - return processResults.apply(_this, [utils, data, resourceName, queryHash]); - } catch (err) { - return _this.$q.reject(_this.errors.UnhandledError(err)); - } - } else { - return data; - } - }, function (err) { - delete resource.pendingQueries[queryHash]; - return _this.$q.reject(err); - }); - } + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this, + queryHash = utils.toJson(params); + + if (options.bypassCache) { + delete resource.completedQueries[queryHash]; + } + + if (!(queryHash in resource.completedQueries)) { + // This particular query has never been completed + + if (!(queryHash in resource.pendingQueries)) { + + // This particular query has never even been made + resource.pendingQueries[queryHash] = _this.adapters[options.adapter || definition.defaultAdapter].findAll(definition, params, options) + .then(function (res) { + var data = definition.deserialize(resourceName, res); + if (options.cacheResponse) { + try { + return processResults.apply(_this, [utils, data, resourceName, queryHash]); + } catch (err) { + return _this.$q.reject(_this.errors.UnhandledError(err)); + } + } else { + return data; + } + }, function (err) { + delete resource.pendingQueries[queryHash]; + return _this.$q.reject(err); + }); + } - return resource.pendingQueries[queryHash]; - } else { - return this.filter(resourceName, params, options); - } + return resource.pendingQueries[queryHash]; + } else { + return this.filter(resourceName, params, options); + } } /** @@ -2281,7 +2295,7 @@ function _findAll(utils, resourceName, params, options) { * ## Example: * * ```js - * var query = { + * var params = { * where: { * author: { * '==': 'John Anderson' @@ -2289,17 +2303,13 @@ function _findAll(utils, resourceName, params, options) { * } * }; * - * DS.findAll('document', { - * query: query - * }).then(function (documents) { - * documents; // [{ id: 'aab7ff66-e21e-46e2-8be8-264d82aee535', author: 'John Anderson', title: 'How to cook' }, - * // { id: 'ee7f3f4d-98d5-4934-9e5a-6a559b08479f', author: 'John Anderson', title: 'How NOT to cook' }] + * DS.findAll('document', params).then(function (documents) { + * documents; // [{ id: '1', author: 'John Anderson', title: 'How to cook' }, + * // { id: '2', author: 'John Anderson', title: 'How NOT to cook' }] * * // The documents are now in the data store - * DS.filter('document', { - * query: query - * }); // [{ id: 'aab7ff66-e21e-46e2-8be8-264d82aee535', author: 'John Anderson', title: 'How to cook' }, - * // { id: 'ee7f3f4d-98d5-4934-9e5a-6a559b08479f', author: 'John Anderson', title: 'How NOT to cook' }] + * DS.filter('document', params); // [{ id: '1', author: 'John Anderson', title: 'How to cook' }, + * // { id: '2', author: 'John Anderson', title: 'How NOT to cook' }] * * }, function (err) { * // handle error @@ -2309,11 +2319,11 @@ function _findAll(utils, resourceName, params, options) { * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. * @param {object=} params Parameter object that is serialized into the query string. Properties: * - * - `{object=}` - `query` - The query object by which to filter items of the type specified by `resourceName`. Properties: - * - `{object=}` - `where` - Where clause. - * - `{number=}` - `limit` - Limit clause. - * - `{skip=}` - `skip` - Skip clause. - * - `{orderBy=}` - `orderBy` - OrderBy clause. + * - `{object=}` - `where` - Where clause. + * - `{number=}` - `limit` - Limit clause. + * - `{number=}` - `skip` - Skip clause. + * - `{number=}` - `offset` - Same as skip. + * - `{string|array=}` - `orderBy` - OrderBy clause. * * @param {object=} options Optional configuration. Properties: * - `{boolean=}` - `bypassCache` - Bypass the cache. Default: `false`. @@ -2332,134 +2342,134 @@ function _findAll(utils, resourceName, params, options) { * - `{UnhandledError}` */ function findAll(resourceName, params, options) { - var deferred = this.$q.defer(), - promise = deferred.promise, - _this = this; - - options = options || {}; - params = params || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isObject(params)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!')); - } else if (!this.utils.isObject(options)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); - } else { - if (!('cacheResponse' in options)) { - options.cacheResponse = true; - } else { - options.cacheResponse = !!options.cacheResponse; - } - try { - promise = promise.then(function () { - return _findAll.apply(_this, [_this.utils, resourceName, params, options]); - }); - deferred.resolve(); - } catch (err) { - deferred.reject(new this.errors.UnhandledError(err)); - } - } + var deferred = this.$q.defer(), + promise = deferred.promise, + _this = this; + + options = options || {}; + params = params || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isObject(params)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!')); + } else if (!this.utils.isObject(options)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); + } else { + if (!('cacheResponse' in options)) { + options.cacheResponse = true; + } else { + options.cacheResponse = !!options.cacheResponse; + } + try { + promise = promise.then(function () { + return _findAll.apply(_this, [_this.utils, resourceName, params, options]); + }); + deferred.resolve(); + } catch (err) { + deferred.reject(new this.errors.UnhandledError(err)); + } + } - return promise; + return promise; } module.exports = findAll; -},{}],37:[function(require,module,exports){ +},{}],38:[function(require,module,exports){ module.exports = { - /** - * @doc method - * @id DS.async_methods:create - * @name create - * @methodOf DS - * @description - * See [DS.create](/documentation/api/api/DS.async_methods:create). - */ - create: require('./create'), - - /** - * @doc method - * @id DS.async_methods:destroy - * @name destroy - * @methodOf DS - * @description - * See [DS.destroy](/documentation/api/api/DS.async_methods:destroy). - */ - destroy: require('./destroy'), - - /** - * @doc method - * @id DS.async_methods:destroyAll - * @name destroyAll - * @methodOf DS - * @description - * See [DS.destroyAll](/documentation/api/api/DS.async_methods:destroyAll). - */ - destroyAll: require('./destroyAll'), - - /** - * @doc method - * @id DS.async_methods:find - * @name find - * @methodOf DS - * @description - * See [DS.find](/documentation/api/api/DS.async_methods:find). - */ - find: require('./find'), - - /** - * @doc method - * @id DS.async_methods:findAll - * @name findAll - * @methodOf DS - * @description - * See [DS.findAll](/documentation/api/api/DS.async_methods:findAll). - */ - findAll: require('./findAll'), - - /** - * @doc method - * @id DS.async_methods:refresh - * @name refresh - * @methodOf DS - * @description - * See [DS.refresh](/documentation/api/api/DS.async_methods:refresh). - */ - refresh: require('./refresh'), - - /** - * @doc method - * @id DS.async_methods:save - * @name save - * @methodOf DS - * @description - * See [DS.save](/documentation/api/api/DS.async_methods:save). - */ - save: require('./save'), - - /** - * @doc method - * @id DS.async_methods:update - * @name update - * @methodOf DS - * @description - * See [DS.update](/documentation/api/api/DS.async_methods:update). - */ - update: require('./update'), - - /** - * @doc method - * @id DS.async_methods:updateAll - * @name updateAll - * @methodOf DS - * @description - * See [DS.updateAll](/documentation/api/api/DS.async_methods:updateAll). - */ - updateAll: require('./updateAll') + /** + * @doc method + * @id DS.async_methods:create + * @name create + * @methodOf DS + * @description + * See [DS.create](/documentation/api/api/DS.async_methods:create). + */ + create: require('./create'), + + /** + * @doc method + * @id DS.async_methods:destroy + * @name destroy + * @methodOf DS + * @description + * See [DS.destroy](/documentation/api/api/DS.async_methods:destroy). + */ + destroy: require('./destroy'), + + /** + * @doc method + * @id DS.async_methods:destroyAll + * @name destroyAll + * @methodOf DS + * @description + * See [DS.destroyAll](/documentation/api/api/DS.async_methods:destroyAll). + */ + destroyAll: require('./destroyAll'), + + /** + * @doc method + * @id DS.async_methods:find + * @name find + * @methodOf DS + * @description + * See [DS.find](/documentation/api/api/DS.async_methods:find). + */ + find: require('./find'), + + /** + * @doc method + * @id DS.async_methods:findAll + * @name findAll + * @methodOf DS + * @description + * See [DS.findAll](/documentation/api/api/DS.async_methods:findAll). + */ + findAll: require('./findAll'), + + /** + * @doc method + * @id DS.async_methods:refresh + * @name refresh + * @methodOf DS + * @description + * See [DS.refresh](/documentation/api/api/DS.async_methods:refresh). + */ + refresh: require('./refresh'), + + /** + * @doc method + * @id DS.async_methods:save + * @name save + * @methodOf DS + * @description + * See [DS.save](/documentation/api/api/DS.async_methods:save). + */ + save: require('./save'), + + /** + * @doc method + * @id DS.async_methods:update + * @name update + * @methodOf DS + * @description + * See [DS.update](/documentation/api/api/DS.async_methods:update). + */ + update: require('./update'), + + /** + * @doc method + * @id DS.async_methods:updateAll + * @name updateAll + * @methodOf DS + * @description + * See [DS.updateAll](/documentation/api/api/DS.async_methods:updateAll). + */ + updateAll: require('./updateAll') }; -},{"./create":32,"./destroy":33,"./destroyAll":34,"./find":35,"./findAll":36,"./refresh":38,"./save":39,"./update":40,"./updateAll":41}],38:[function(require,module,exports){ +},{"./create":33,"./destroy":34,"./destroyAll":35,"./find":36,"./findAll":37,"./refresh":39,"./save":40,"./update":41,"./updateAll":42}],39:[function(require,module,exports){ var errorPrefix = 'DS.refresh(resourceName, id[, options]): '; /** @@ -2512,28 +2522,28 @@ var errorPrefix = 'DS.refresh(resourceName, id[, options]): '; * - `{UnhandledError}` */ function refresh(resourceName, id, options) { - options = options || {}; - - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } else if (!this.utils.isObject(options)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); - } else { - options.bypassCache = true; - - if (this.get(resourceName, id)) { - return this.find(resourceName, id, options); - } else { - return false; - } - } + options = options || {}; + + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } else if (!this.utils.isObject(options)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); + } else { + options.bypassCache = true; + + if (this.get(resourceName, id)) { + return this.find(resourceName, id, options); + } else { + return false; + } + } } module.exports = refresh; -},{}],39:[function(require,module,exports){ +},{}],40:[function(require,module,exports){ var errorPrefix = 'DS.save(resourceName, id[, options]): '; /** @@ -2579,80 +2589,80 @@ var errorPrefix = 'DS.save(resourceName, id[, options]): '; * - `{UnhandledError}` */ function save(resourceName, id, options) { - var deferred = this.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); - } else if (!this.utils.isObject(options)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); - } else { - var item = this.get(resourceName, id); - if (!item) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); - } else { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - promise = promise - .then(function (attrs) { - return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.validate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs); - }) - .then(function (attrs) { - if (options.changesOnly) { - resource.observers[id].deliver(); - var toKeep = [], - changes = _this.changes(resourceName, id); - - for (var key in changes.added) { - toKeep.push(key); - } - for (key in changes.changed) { - toKeep.push(key); - } - changes = _this.utils.pick(attrs, toKeep); - if (_this.utils.isEmpty(changes)) { - // no changes, return - return attrs; - } else { - attrs = changes; - } - } - return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, definition.serialize(resourceName, attrs), options); - }) - .then(function (res) { - return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res)); - }) - .then(function (data) { - _this.inject(definition.name, data, options); - resource.previousAttributes[id] = _this.utils.deepMixIn({}, data); - resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); - return _this.get(resourceName, id); - }); - - deferred.resolve(item); - } - } - return promise; + var deferred = this.$q.defer(), + promise = deferred.promise; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); + } else if (!this.utils.isObject(options)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); + } else { + var item = this.get(resourceName, id); + if (!item) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); + } else { + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + promise = promise + .then(function (attrs) { + return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs); + }) + .then(function (attrs) { + if (options.changesOnly) { + resource.observers[id].deliver(); + var toKeep = [], + changes = _this.changes(resourceName, id); + + for (var key in changes.added) { + toKeep.push(key); + } + for (key in changes.changed) { + toKeep.push(key); + } + changes = _this.utils.pick(attrs, toKeep); + if (_this.utils.isEmpty(changes)) { + // no changes, return + return attrs; + } else { + attrs = changes; + } + } + return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, definition.serialize(resourceName, attrs), options); + }) + .then(function (res) { + return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res)); + }) + .then(function (data) { + _this.inject(definition.name, data, options); + resource.previousAttributes[id] = _this.utils.deepMixIn({}, data); + resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); + return _this.get(resourceName, id); + }); + + deferred.resolve(item); + } + } + return promise; } module.exports = save; -},{}],40:[function(require,module,exports){ +},{}],41:[function(require,module,exports){ var errorPrefix = 'DS.update(resourceName, id, attrs[, options]): '; /** @@ -2701,69 +2711,69 @@ var errorPrefix = 'DS.update(resourceName, id, attrs[, options]): '; * - `{UnhandledError}` */ function update(resourceName, id, attrs, options) { - var deferred = this.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!')); - } else if (!this.utils.isObject(attrs)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!')); - } else if (!this.utils.isObject(options)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); - } else { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - if (!('cacheResponse' in options)) { - options.cacheResponse = true; - } else { - options.cacheResponse = !!options.cacheResponse; - } + var deferred = this.$q.defer(), + promise = deferred.promise; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!')); + } else if (!this.utils.isObject(attrs)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!')); + } else if (!this.utils.isObject(options)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); + } else { + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + if (!('cacheResponse' in options)) { + options.cacheResponse = true; + } else { + options.cacheResponse = !!options.cacheResponse; + } - promise = promise - .then(function (attrs) { - return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.validate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, definition.serialize(resourceName, attrs), options); - }) - .then(function (res) { - return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res)); - }) - .then(function (data) { - if (options.cacheResponse) { - var updated = _this.inject(definition.name, data, options); - var id = updated[definition.idAttribute]; - resource.previousAttributes[id] = _this.utils.deepMixIn({}, updated); - resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); - return _this.get(definition.name, id); - } else { - return data; - } - }); + promise = promise + .then(function (attrs) { + return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.adapters[options.adapter || definition.defaultAdapter].update(definition, id, definition.serialize(resourceName, attrs), options); + }) + .then(function (res) { + return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res)); + }) + .then(function (data) { + if (options.cacheResponse) { + var updated = _this.inject(definition.name, data, options); + var id = updated[definition.idAttribute]; + resource.previousAttributes[id] = _this.utils.deepMixIn({}, updated); + resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); + return _this.get(definition.name, id); + } else { + return data; + } + }); - deferred.resolve(attrs); - } - return promise; + deferred.resolve(attrs); + } + return promise; } module.exports = update; -},{}],41:[function(require,module,exports){ +},{}],42:[function(require,module,exports){ var errorPrefix = 'DS.updateAll(resourceName, attrs, params[, options]): '; /** @@ -2786,11 +2796,9 @@ var errorPrefix = 'DS.updateAll(resourceName, attrs, params[, options]): '; * DS.filter('document'); // [] * * DS.updateAll('document', 5, { author: 'Sally' }, { - * query: { - * where: { - * author: { - * '==': 'John Anderson' - * } + * where: { + * author: { + * '==': 'John Anderson' * } * } * }) @@ -2806,11 +2814,11 @@ var errorPrefix = 'DS.updateAll(resourceName, attrs, params[, options]): '; * @param {object} attrs The attributes with which to update the items. * @param {object} params Parameter object that is serialized into the query string. Properties: * - * - `{object=}` - `query` - The query object by which to filter items of the type specified by `resourceName`. Properties: - * - `{object=}` - `where` - Where clause. - * - `{number=}` - `limit` - Limit clause. - * - `{skip=}` - `skip` - Skip clause. - * - `{orderBy=}` - `orderBy` - OrderBy clause. + * - `{object=}` - `where` - Where clause. + * - `{number=}` - `limit` - Limit clause. + * - `{number=}` - `skip` - Skip clause. + * - `{number=}` - `offset` - Same as skip. + * - `{string|array=}` - `orderBy` - OrderBy clause. * * @param {object=} options Optional configuration. Properties: * - `{boolean=}` - `cacheResponse` - Inject the data returned by the server into the data store. Default: `true`. @@ -2828,69 +2836,69 @@ var errorPrefix = 'DS.updateAll(resourceName, attrs, params[, options]): '; * - `{UnhandledError}` */ function updateAll(resourceName, attrs, params, options) { - var deferred = this.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!this.definitions[resourceName]) { - deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!this.utils.isObject(attrs)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!')); - } else if (!this.utils.isObject(params)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!')); - } else if (!this.utils.isObject(options)) { - deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); - } else { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - if (!('cacheResponse' in options)) { - options.cacheResponse = true; - } else { - options.cacheResponse = !!options.cacheResponse; - } + var deferred = this.$q.defer(), + promise = deferred.promise; + + options = options || {}; + + if (!this.definitions[resourceName]) { + deferred.reject(new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!this.utils.isObject(attrs)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!')); + } else if (!this.utils.isObject(params)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!')); + } else if (!this.utils.isObject(options)) { + deferred.reject(new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!')); + } else { + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + if (!('cacheResponse' in options)) { + options.cacheResponse = true; + } else { + options.cacheResponse = !!options.cacheResponse; + } - promise = promise - .then(function (attrs) { - return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.validate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.adapters[options.adapter || definition.defaultAdapter].updateAll(definition, definition.serialize(resourceName, attrs), params, options); - }) - .then(function (res) { - return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res)); - }) - .then(function (data) { - if (options.cacheResponse) { - return _this.inject(definition.name, data, options); - } else { - return data; - } - }); + promise = promise + .then(function (attrs) { + return _this.$q.promisify(definition.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.$q.promisify(definition.beforeUpdate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.adapters[options.adapter || definition.defaultAdapter].updateAll(definition, definition.serialize(resourceName, attrs), params, options); + }) + .then(function (res) { + return _this.$q.promisify(definition.afterUpdate)(resourceName, definition.deserialize(resourceName, res)); + }) + .then(function (data) { + if (options.cacheResponse) { + return _this.inject(definition.name, data, options); + } else { + return data; + } + }); - deferred.resolve(attrs); - } - return promise; + deferred.resolve(attrs); + } + return promise; } module.exports = updateAll; -},{}],42:[function(require,module,exports){ +},{}],43:[function(require,module,exports){ var utils = require('../utils')[0](); function lifecycleNoop(resourceName, attrs, cb) { - cb(null, attrs); + cb(null, attrs); } function BaseConfig() { @@ -2898,37 +2906,125 @@ function BaseConfig() { BaseConfig.prototype.idAttribute = 'id'; BaseConfig.prototype.defaultAdapter = 'DSHttpAdapter'; -BaseConfig.prototype.filter = function (resourceName, where, attrs) { - var keep = true; - utils.forOwn(where, function (clause, field) { - if (utils.isString(clause)) { - clause = { - '===': clause - }; - } else if (utils.isNumber(clause)) { - clause = { - '==': clause - }; - } - if ('==' in clause) { - keep = keep && (attrs[field] == clause['==']); - } else if ('===' in clause) { - keep = keep && (attrs[field] === clause['===']); - } else if ('!=' in clause) { - keep = keep && (attrs[field] != clause['!=']); - } else if ('>' in clause) { - keep = keep && (attrs[field] > clause['>']); - } else if ('>=' in clause) { - keep = keep && (attrs[field] >= clause['>=']); - } else if ('<' in clause) { - keep = keep && (attrs[field] < clause['<']); - } else if ('<=' in clause) { - keep = keep && (attrs[field] <= clause['<=']); - } else if ('in' in clause) { - keep = keep && utils.contains(clause['in'], attrs[field]); - } - }); - return keep; +BaseConfig.prototype.filter = function (collection, resourceName, params) { + var _this = this; + var filtered = collection; + var where = null; + + if (this.utils.isObject(params.where)) { + where = params.where; + filtered = this.utils.filter(filtered, function (attrs) { + var keep = true; + utils.forOwn(where, function (clause, field) { + if (utils.isString(clause)) { + clause = { + '===': clause + }; + } else if (utils.isNumber(clause)) { + clause = { + '==': clause + }; + } + if ('==' in clause) { + keep = keep && (attrs[field] == clause['==']); + } else if ('===' in clause) { + keep = keep && (attrs[field] === clause['===']); + } else if ('!=' in clause) { + keep = keep && (attrs[field] != clause['!=']); + } else if ('>' in clause) { + keep = keep && (attrs[field] > clause['>']); + } else if ('>=' in clause) { + keep = keep && (attrs[field] >= clause['>=']); + } else if ('<' in clause) { + keep = keep && (attrs[field] < clause['<']); + } else if ('<=' in clause) { + keep = keep && (attrs[field] <= clause['<=']); + } else if ('in' in clause) { + keep = keep && utils.contains(clause['in'], attrs[field]); + } + }); + return keep; + }); + } + + var orderBy = null; + + if (this.utils.isString(params.orderBy)) { + orderBy = [ + [params.orderBy, 'ASC'] + ]; + } else if (this.utils.isArray(params.orderBy)) { + orderBy = params.orderBy; + } + + if (!orderBy && this.utils.isString(params.sort)) { + orderBy = [ + [params.sort, 'ASC'] + ]; + } else if (!orderBy && this.utils.isArray(params.sort)) { + orderBy = params.sort; + } + + // Apply 'orderBy' + if (orderBy) { + angular.forEach(orderBy, function (def) { + if (_this.utils.isString(def)) { + def = [def, 'ASC']; + } else if (!_this.utils.isArray(def)) { + throw new _this.errors.IllegalArgumentError('DS.filter(resourceName[, params][, options]): ' + angular.toJson(def) + ': Must be a string or an array!', { params: { 'orderBy[i]': { actual: typeof def, expected: 'string|array' } } }); + } + filtered = _this.utils.sort(filtered, function (a, b) { + var cA = a[def[0]], cB = b[def[0]]; + if (_this.utils.isString(cA)) { + cA = _this.utils.upperCase(cA); + } + if (_this.utils.isString(cB)) { + cB = _this.utils.upperCase(cB); + } + if (def[1] === 'DESC') { + if (cB < cA) { + return -1; + } else if (cB > cA) { + return 1; + } else { + return 0; + } + } else { + if (cA < cB) { + return -1; + } else if (cA > cB) { + return 1; + } else { + return 0; + } + } + }); + }); + } + + var limit = angular.isNumber(params.limit) ? params.limit : null; + var skip = null; + + if (angular.isNumber(params.skip)) { + skip = params.skip; + } else if (angular.isNumber(params.offset)) { + skip = params.offset; + } + + // Apply 'limit' and 'skip' + if (limit && skip) { + filtered = this.utils.slice(filtered, skip, Math.min(filtered.length, skip + limit)); + } else if (this.utils.isNumber(limit)) { + filtered = this.utils.slice(filtered, 0, Math.min(filtered.length, limit)); + } else if (this.utils.isNumber(skip)) { + if (skip < filtered.length) { + filtered = this.utils.slice(filtered, skip); + } else { + filtered = []; + } + } + + return filtered; }; BaseConfig.prototype.baseUrl = ''; BaseConfig.prototype.endpoint = ''; @@ -3236,7 +3332,7 @@ BaseConfig.prototype.afterDestroy = lifecycleNoop; * @param {object} attrs Attributes of the item moving through the lifecycle. */ BaseConfig.prototype.beforeInject = function (resourceName, attrs) { - return attrs; + return attrs; }; /** * @doc property @@ -3263,7 +3359,7 @@ BaseConfig.prototype.beforeInject = function (resourceName, attrs) { * @param {object} attrs Attributes of the item moving through the lifecycle. */ BaseConfig.prototype.afterInject = function (resourceName, attrs) { - return attrs; + return attrs; }; /** * @doc property @@ -3287,7 +3383,7 @@ BaseConfig.prototype.afterInject = function (resourceName, attrs) { * @returns {*} By default returns `data` as-is. */ BaseConfig.prototype.serialize = function (resourceName, data) { - return data; + return data; }; /** @@ -3310,7 +3406,7 @@ BaseConfig.prototype.serialize = function (resourceName, data) { * @returns {*} By default returns `data.data`. */ BaseConfig.prototype.deserialize = function (resourceName, data) { - return data.data; + return data.data; }; /** @@ -3320,152 +3416,152 @@ BaseConfig.prototype.deserialize = function (resourceName, data) { */ function DSProvider() { - /** - * @doc property - * @id DSProvider.properties:defaults - * @name defaults - * @description - * See the [configuration guide](/documentation/guide/configure/global). - * - * Properties: - * - * - `{string}` - `baseUrl` - The url relative to which all AJAX requests will be made. - * - `{string}` - `idAttribute` - Default: `"id"` - The attribute that specifies the primary key for resources. - * - `{string}` - `defaultAdapter` - Default: `"DSHttpAdapter"` - * - `{function}` - `filter` - Default: See [angular-data query language](/documentation/guide/queries/custom). - * - `{function}` - `beforeValidate` - See [DSProvider.defaults.beforeValidate](/documentation/api/angular-data/DSProvider.properties:defaults.beforeValidate). Default: No-op - * - `{function}` - `validate` - See [DSProvider.defaults.validate](/documentation/api/angular-data/DSProvider.properties:defaults.validate). Default: No-op - * - `{function}` - `afterValidate` - See [DSProvider.defaults.afterValidate](/documentation/api/angular-data/DSProvider.properties:defaults.afterValidate). Default: No-op - * - `{function}` - `beforeCreate` - See [DSProvider.defaults.beforeCreate](/documentation/api/angular-data/DSProvider.properties:defaults.beforeCreate). Default: No-op - * - `{function}` - `afterCreate` - See [DSProvider.defaults.afterCreate](/documentation/api/angular-data/DSProvider.properties:defaults.afterCreate). Default: No-op - * - `{function}` - `beforeUpdate` - See [DSProvider.defaults.beforeUpdate](/documentation/api/angular-data/DSProvider.properties:defaults.beforeUpdate). Default: No-op - * - `{function}` - `afterUpdate` - See [DSProvider.defaults.afterUpdate](/documentation/api/angular-data/DSProvider.properties:defaults.afterUpdate). Default: No-op - * - `{function}` - `beforeDestroy` - See [DSProvider.defaults.beforeDestroy](/documentation/api/angular-data/DSProvider.properties:defaults.beforeDestroy). Default: No-op - * - `{function}` - `afterDestroy` - See [DSProvider.defaults.afterDestroy](/documentation/api/angular-data/DSProvider.properties:defaults.afterDestroy). Default: No-op - * - `{function}` - `afterInject` - See [DSProvider.defaults.afterInject](/documentation/api/angular-data/DSProvider.properties:defaults.afterInject). Default: No-op - * - `{function}` - `beforeInject` - See [DSProvider.defaults.beforeInject](/documentation/api/angular-data/DSProvider.properties:defaults.beforeInject). Default: No-op - * - `{function}` - `serialize` - See [DSProvider.defaults.serialize](/documentation/api/angular-data/DSProvider.properties:defaults.serialize). Default: No-op - * - `{function}` - `deserialize` - See [DSProvider.defaults.deserialize](/documentation/api/angular-data/DSProvider.properties:defaults.deserialize). Default: No-op - */ - var defaults = this.defaults = new BaseConfig(); - - this.$get = [ - '$rootScope', '$log', '$q', 'DSHttpAdapter', 'DSLocalStorageAdapter', 'DSUtils', 'DSErrors', - function ($rootScope, $log, $q, DSHttpAdapter, DSLocalStorageAdapter, DSUtils, DSErrors) { - - var syncMethods = require('./sync_methods'), - asyncMethods = require('./async_methods'), - cache; - - try { - cache = angular.injector(['angular-data.DSCacheFactory']).get('DSCacheFactory'); - } catch (err) { - $log.warn(err); - $log.warn('DSCacheFactory is unavailable. Resorting to the lesser capabilities of $cacheFactory.'); - cache = angular.injector(['ng']).get('$cacheFactory'); - } - - /** - * @doc interface - * @id DS - * @name DS - * @description - * Public data store interface. Consists of several properties and a number of methods. Injectable as `DS`. - * - * See the [guide](/documentation/guide/overview/index). - */ - var DS = { - $rootScope: $rootScope, - $log: $log, - $q: $q, - - cacheFactory: cache, - - /** - * @doc property - * @id DS.properties:defaults - * @name defaults - * @description - * Reference to [DSProvider.defaults](/documentation/api/api/DSProvider.properties:defaults). - */ - defaults: defaults, - - /*! - * @doc property - * @id DS.properties:store - * @name store - * @description - * Meta data for each registered resource. - */ - store: {}, - - /*! - * @doc property - * @id DS.properties:definitions - * @name definitions - * @description - * Registered resource definitions available to the data store. - */ - definitions: {}, - - /** - * @doc property - * @id DS.properties:adapters - * @name adapters - * @description - * Registered adapters available to the data store. Object consists of key-values pairs where the key is - * the name of the adapter and the value is the adapter itself. - */ - adapters: { - DSHttpAdapter: DSHttpAdapter, - DSLocalStorageAdapter: DSLocalStorageAdapter - }, - - /** - * @doc property - * @id DS.properties:errors - * @name errors - * @description - * References to the various [error types](/documentation/api/api/errors) used by angular-data. - */ - errors: DSErrors, - - /*! - * @doc property - * @id DS.properties:utils - * @name utils - * @description - * Utility functions used internally by angular-data. - */ - utils: DSUtils - }; - - DSUtils.deepFreeze(syncMethods); - DSUtils.deepFreeze(asyncMethods); - - DSUtils.deepMixIn(DS, syncMethods); - DSUtils.deepMixIn(DS, asyncMethods); - - DSUtils.deepFreeze(DS.errors); - DSUtils.deepFreeze(DS.utils); - - var $dirtyCheckScope = $rootScope.$new(); - - $dirtyCheckScope.$watch(function () { - // Throttle angular-data's digest loop to tenths of a second - // TODO: Is this okay? - return new Date().getTime() / 100 | 0; - }, function () { - DS.digest(); - }); - - return DS; - }]; + /** + * @doc property + * @id DSProvider.properties:defaults + * @name defaults + * @description + * See the [configuration guide](/documentation/guide/configure/global). + * + * Properties: + * + * - `{string}` - `baseUrl` - The url relative to which all AJAX requests will be made. + * - `{string}` - `idAttribute` - Default: `"id"` - The attribute that specifies the primary key for resources. + * - `{string}` - `defaultAdapter` - Default: `"DSHttpAdapter"` + * - `{function}` - `filter` - Default: See [angular-data query language](/documentation/guide/queries/custom). + * - `{function}` - `beforeValidate` - See [DSProvider.defaults.beforeValidate](/documentation/api/angular-data/DSProvider.properties:defaults.beforeValidate). Default: No-op + * - `{function}` - `validate` - See [DSProvider.defaults.validate](/documentation/api/angular-data/DSProvider.properties:defaults.validate). Default: No-op + * - `{function}` - `afterValidate` - See [DSProvider.defaults.afterValidate](/documentation/api/angular-data/DSProvider.properties:defaults.afterValidate). Default: No-op + * - `{function}` - `beforeCreate` - See [DSProvider.defaults.beforeCreate](/documentation/api/angular-data/DSProvider.properties:defaults.beforeCreate). Default: No-op + * - `{function}` - `afterCreate` - See [DSProvider.defaults.afterCreate](/documentation/api/angular-data/DSProvider.properties:defaults.afterCreate). Default: No-op + * - `{function}` - `beforeUpdate` - See [DSProvider.defaults.beforeUpdate](/documentation/api/angular-data/DSProvider.properties:defaults.beforeUpdate). Default: No-op + * - `{function}` - `afterUpdate` - See [DSProvider.defaults.afterUpdate](/documentation/api/angular-data/DSProvider.properties:defaults.afterUpdate). Default: No-op + * - `{function}` - `beforeDestroy` - See [DSProvider.defaults.beforeDestroy](/documentation/api/angular-data/DSProvider.properties:defaults.beforeDestroy). Default: No-op + * - `{function}` - `afterDestroy` - See [DSProvider.defaults.afterDestroy](/documentation/api/angular-data/DSProvider.properties:defaults.afterDestroy). Default: No-op + * - `{function}` - `afterInject` - See [DSProvider.defaults.afterInject](/documentation/api/angular-data/DSProvider.properties:defaults.afterInject). Default: No-op + * - `{function}` - `beforeInject` - See [DSProvider.defaults.beforeInject](/documentation/api/angular-data/DSProvider.properties:defaults.beforeInject). Default: No-op + * - `{function}` - `serialize` - See [DSProvider.defaults.serialize](/documentation/api/angular-data/DSProvider.properties:defaults.serialize). Default: No-op + * - `{function}` - `deserialize` - See [DSProvider.defaults.deserialize](/documentation/api/angular-data/DSProvider.properties:defaults.deserialize). Default: No-op + */ + var defaults = this.defaults = new BaseConfig(); + + this.$get = [ + '$rootScope', '$log', '$q', 'DSHttpAdapter', 'DSLocalStorageAdapter', 'DSUtils', 'DSErrors', + function ($rootScope, $log, $q, DSHttpAdapter, DSLocalStorageAdapter, DSUtils, DSErrors) { + + var syncMethods = require('./sync_methods'), + asyncMethods = require('./async_methods'), + cache; + + try { + cache = angular.injector(['angular-data.DSCacheFactory']).get('DSCacheFactory'); + } catch (err) { + $log.warn(err); + $log.warn('DSCacheFactory is unavailable. Resorting to the lesser capabilities of $cacheFactory.'); + cache = angular.injector(['ng']).get('$cacheFactory'); + } + + /** + * @doc interface + * @id DS + * @name DS + * @description + * Public data store interface. Consists of several properties and a number of methods. Injectable as `DS`. + * + * See the [guide](/documentation/guide/overview/index). + */ + var DS = { + $rootScope: $rootScope, + $log: $log, + $q: $q, + + cacheFactory: cache, + + /** + * @doc property + * @id DS.properties:defaults + * @name defaults + * @description + * Reference to [DSProvider.defaults](/documentation/api/api/DSProvider.properties:defaults). + */ + defaults: defaults, + + /*! + * @doc property + * @id DS.properties:store + * @name store + * @description + * Meta data for each registered resource. + */ + store: {}, + + /*! + * @doc property + * @id DS.properties:definitions + * @name definitions + * @description + * Registered resource definitions available to the data store. + */ + definitions: {}, + + /** + * @doc property + * @id DS.properties:adapters + * @name adapters + * @description + * Registered adapters available to the data store. Object consists of key-values pairs where the key is + * the name of the adapter and the value is the adapter itself. + */ + adapters: { + DSHttpAdapter: DSHttpAdapter, + DSLocalStorageAdapter: DSLocalStorageAdapter + }, + + /** + * @doc property + * @id DS.properties:errors + * @name errors + * @description + * References to the various [error types](/documentation/api/api/errors) used by angular-data. + */ + errors: DSErrors, + + /*! + * @doc property + * @id DS.properties:utils + * @name utils + * @description + * Utility functions used internally by angular-data. + */ + utils: DSUtils + }; + + DSUtils.deepFreeze(syncMethods); + DSUtils.deepFreeze(asyncMethods); + + DSUtils.deepMixIn(DS, syncMethods); + DSUtils.deepMixIn(DS, asyncMethods); + + DSUtils.deepFreeze(DS.errors); + DSUtils.deepFreeze(DS.utils); + + var $dirtyCheckScope = $rootScope.$new(); + + $dirtyCheckScope.$watch(function () { + // Throttle angular-data's digest loop to tenths of a second + // TODO: Is this okay? + return new Date().getTime() / 100 | 0; + }, function () { + DS.digest(); + }); + + return DS; + }]; } module.exports = DSProvider; -},{"../utils":60,"./async_methods":37,"./sync_methods":53}],43:[function(require,module,exports){ +},{"../utils":61,"./async_methods":38,"./sync_methods":54}],44:[function(require,module,exports){ var errorPrefix = 'DS.bindAll(scope, expr, resourceName, params): '; /** @@ -3485,10 +3581,8 @@ var errorPrefix = 'DS.bindAll(scope, expr, resourceName, params): '; * ```js * // bind the documents with ownerId of 5 to the 'docs' property of the $scope * var deregisterFunc = DS.bindAll($scope, 'docs', 'document', { - * query: { - * criteria: { - * ownerId: 5 - * } + * where: { + * ownerId: 5 * } * }); * ``` @@ -3504,40 +3598,41 @@ var errorPrefix = 'DS.bindAll(scope, expr, resourceName, params): '; * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. * @param {object} params Parameter object that is used in filtering the collection. Properties: * - * - `{object=}` - `query` - The query object by which to filter items of the type specified by `resourceName`. Properties: - * - `{object=}` - `where` - Where clause. - * - `{number=}` - `limit` - Limit clause. - * - `{skip=}` - `skip` - Skip clause. - * - `{orderBy=}` - `orderBy` - OrderBy clause. + * - `{object=}` - `where` - Where clause. + * - `{number=}` - `limit` - Limit clause. + * - `{number=}` - `skip` - Skip clause. + * - `{number=}` - `offset` - Same as skip. + * - `{string|array=}` - `orderBy` - OrderBy clause. + * * @returns {function} Scope $watch deregistration function. */ function bindOne(scope, expr, resourceName, params) { - if (!this.utils.isObject(scope)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'scope: Must be an object!'); - } else if (!this.utils.isString(expr)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'expr: Must be a string!'); - } else if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isObject(params)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!'); - } - - var _this = this; - - try { - return scope.$watch(function () { - return _this.lastModified(resourceName); - }, function () { - _this.utils.set(scope, expr, _this.filter(resourceName, params)); - }); - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.utils.isObject(scope)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'scope: Must be an object!'); + } else if (!this.utils.isString(expr)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'expr: Must be a string!'); + } else if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isObject(params)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!'); + } + + var _this = this; + + try { + return scope.$watch(function () { + return _this.lastModified(resourceName); + }, function () { + _this.utils.set(scope, expr, _this.filter(resourceName, params)); + }); + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = bindOne; -},{}],44:[function(require,module,exports){ +},{}],45:[function(require,module,exports){ var errorPrefix = 'DS.bindOne(scope, expr, resourceName, id): '; /** @@ -3572,32 +3667,32 @@ var errorPrefix = 'DS.bindOne(scope, expr, resourceName, id): '; * @returns {function} Scope $watch deregistration function. */ function bindOne(scope, expr, resourceName, id) { - if (!this.utils.isObject(scope)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'scope: Must be an object!'); - } else if (!this.utils.isString(expr)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'expr: Must be a string!'); - } else if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!'); - } - - var _this = this; - - try { - return scope.$watch(function () { - return _this.lastModified(resourceName, id); - }, function () { - _this.utils.set(scope, expr, _this.get(resourceName, id)); - }); - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.utils.isObject(scope)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'scope: Must be an object!'); + } else if (!this.utils.isString(expr)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'expr: Must be a string!'); + } else if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!'); + } + + var _this = this; + + try { + return scope.$watch(function () { + return _this.lastModified(resourceName, id); + }, function () { + _this.utils.set(scope, expr, _this.get(resourceName, id)); + }); + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = bindOne; -},{}],45:[function(require,module,exports){ +},{}],46:[function(require,module,exports){ var errorPrefix = 'DS.changes(resourceName, id): '; /** @@ -3635,38 +3730,38 @@ var errorPrefix = 'DS.changes(resourceName, id): '; * @returns {object} The changes of the item of the type specified by `resourceName` with the primary key specified by `id`. */ function changes(resourceName, id) { - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } - - try { - var item = this.get(resourceName, id); - if (item) { - this.store[resourceName].observers[id].deliver(); - return this.utils.diffObjectFromOldObject(item, this.store[resourceName].previousAttributes[id]); - } - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } + + try { + var item = this.get(resourceName, id); + if (item) { + this.store[resourceName].observers[id].deliver(); + return this.utils.diffObjectFromOldObject(item, this.store[resourceName].previousAttributes[id]); + } + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = changes; -},{}],46:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ /*jshint evil:true*/ var errorPrefix = 'DS.defineResource(definition): '; function Resource(utils, options) { - utils.deepMixIn(this, options); + utils.deepMixIn(this, options); - if ('endpoint' in options) { - this.endpoint = options.endpoint; - } else { - this.endpoint = this.name; - } + if ('endpoint' in options) { + this.endpoint = options.endpoint; + } else { + this.endpoint = this.name; + } } /** @@ -3730,73 +3825,73 @@ function Resource(utils, options) { * See [DSProvider.defaults](/documentation/api/angular-data/DSProvider.properties:defaults). */ function defineResource(definition) { - if (this.utils.isString(definition)) { - definition = { - name: definition - }; - } - if (!this.utils.isObject(definition)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'definition: Must be an object!', { definition: { actual: typeof definition, expected: 'object' } }); - } else if (!this.utils.isString(definition.name)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'definition.name: Must be a string!', { definition: { name: { actual: typeof definition.name, expected: 'string' } } }); - } else if (definition.idAttribute && !this.utils.isString(definition.idAttribute)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'definition.idAttribute: Must be a string!', { definition: { idAttribute: { actual: typeof definition.idAttribute, expected: 'string' } } }); - } else if (definition.endpoint && !this.utils.isString(definition.endpoint)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'definition.endpoint: Must be a string!', { definition: { endpoint: { actual: typeof definition.endpoint, expected: 'string' } } }); - } else if (this.store[definition.name]) { - throw new this.errors.RuntimeError(errorPrefix + definition.name + ' is already registered!'); - } - - try { - Resource.prototype = this.defaults; - this.definitions[definition.name] = new Resource(this.utils, definition); - - var _this = this, - def = this.definitions[definition.name]; - - var cache = this.cacheFactory('DS.' + def.name, { - maxAge: def.maxAge || null, - recycleFreq: def.recycleFreq || 1000, - cacheFlushInterval: def.cacheFlushInterval || null, - deleteOnExpire: def.deleteOnExpire || 'none', - onExpire: function (id) { - _this.eject(def.name, id); - }, - capacity: Number.MAX_VALUE, - storageMode: 'memory', - storageImpl: null, - disabled: false, - storagePrefix: 'DS.' + def.name - }); - - if (def.methods) { - def.class = definition.name[0].toUpperCase() + definition.name.substring(1); - eval('function ' + def.class + '() {}'); - def[def.class] = eval(def.class); - this.utils.deepMixIn(def[def.class].prototype, def.methods); - } + if (this.utils.isString(definition)) { + definition = { + name: definition + }; + } + if (!this.utils.isObject(definition)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'definition: Must be an object!', { definition: { actual: typeof definition, expected: 'object' } }); + } else if (!this.utils.isString(definition.name)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'definition.name: Must be a string!', { definition: { name: { actual: typeof definition.name, expected: 'string' } } }); + } else if (definition.idAttribute && !this.utils.isString(definition.idAttribute)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'definition.idAttribute: Must be a string!', { definition: { idAttribute: { actual: typeof definition.idAttribute, expected: 'string' } } }); + } else if (definition.endpoint && !this.utils.isString(definition.endpoint)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'definition.endpoint: Must be a string!', { definition: { endpoint: { actual: typeof definition.endpoint, expected: 'string' } } }); + } else if (this.store[definition.name]) { + throw new this.errors.RuntimeError(errorPrefix + definition.name + ' is already registered!'); + } + + try { + Resource.prototype = this.defaults; + this.definitions[definition.name] = new Resource(this.utils, definition); + + var _this = this, + def = this.definitions[definition.name]; + + var cache = this.cacheFactory('DS.' + def.name, { + maxAge: def.maxAge || null, + recycleFreq: def.recycleFreq || 1000, + cacheFlushInterval: def.cacheFlushInterval || null, + deleteOnExpire: def.deleteOnExpire || 'none', + onExpire: function (id) { + _this.eject(def.name, id); + }, + capacity: Number.MAX_VALUE, + storageMode: 'memory', + storageImpl: null, + disabled: false, + storagePrefix: 'DS.' + def.name + }); + + if (def.methods) { + def.class = definition.name[0].toUpperCase() + definition.name.substring(1); + eval('function ' + def.class + '() {}'); + def[def.class] = eval(def.class); + this.utils.deepMixIn(def[def.class].prototype, def.methods); + } - this.store[def.name] = { - collection: [], - completedQueries: {}, - pendingQueries: {}, - index: cache, - modified: {}, - saved: {}, - previousAttributes: {}, - observers: {}, - collectionModified: 0 - }; - } catch (err) { - delete this.definitions[definition.name]; - delete this.store[definition.name]; - throw new this.errors.UnhandledError(err); - } + this.store[def.name] = { + collection: [], + completedQueries: {}, + pendingQueries: {}, + index: cache, + modified: {}, + saved: {}, + previousAttributes: {}, + observers: {}, + collectionModified: 0 + }; + } catch (err) { + delete this.definitions[definition.name]; + delete this.store[definition.name]; + throw new this.errors.UnhandledError(err); + } } module.exports = defineResource; -},{}],47:[function(require,module,exports){ +},{}],48:[function(require,module,exports){ var observe = require('../../../lib/observe-js/observe-js'); /** @@ -3823,42 +3918,42 @@ var observe = require('../../../lib/observe-js/observe-js'); * - `{UnhandledError}` */ function digest() { - try { - if (!this.$rootScope.$$phase) { - this.$rootScope.$apply(function () { - observe.Platform.performMicrotaskCheckpoint(); - }); - } else { - observe.Platform.performMicrotaskCheckpoint(); - } - } catch (err) { - throw new this.errors.UnhandledError(err); - } + try { + if (!this.$rootScope.$$phase) { + this.$rootScope.$apply(function () { + observe.Platform.performMicrotaskCheckpoint(); + }); + } else { + observe.Platform.performMicrotaskCheckpoint(); + } + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = digest; -},{"../../../lib/observe-js/observe-js":1}],48:[function(require,module,exports){ +},{"../../../lib/observe-js/observe-js":1}],49:[function(require,module,exports){ var errorPrefix = 'DS.eject(resourceName, id): '; function _eject(definition, resource, id) { - var found = false; - for (var i = 0; i < resource.collection.length; i++) { - if (resource.collection[i][definition.idAttribute] == id) { - found = true; - break; - } - } - if (found) { - resource.collection.splice(i, 1); - resource.observers[id].close(); - delete resource.observers[id]; - - resource.index.remove(id); - delete resource.previousAttributes[id]; - delete resource.modified[id]; - delete resource.saved[id]; - } + var found = false; + for (var i = 0; i < resource.collection.length; i++) { + if (resource.collection[i][definition.idAttribute] == id) { + found = true; + break; + } + } + if (found) { + resource.collection.splice(i, 1); + resource.observers[id].close(); + delete resource.observers[id]; + + resource.index.remove(id); + delete resource.previousAttributes[id]; + delete resource.modified[id]; + delete resource.saved[id]; + } } /** @@ -3894,45 +3989,47 @@ function _eject(definition, resource, id) { * @param {string|number} id The primary key of the item to eject. */ function eject(resourceName, id) { - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } - - var resource = this.store[resourceName], - _this = this; - - try { - if (!this.$rootScope.$$phase) { - this.$rootScope.$apply(function () { - _eject(_this.definitions[resourceName], resource, id); - resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); - }); - } else { - _eject(_this.definitions[resourceName], resource, id); - resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); - } - delete this.store[resourceName].completedQueries[id]; - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } + + var resource = this.store[resourceName], + _this = this; + + try { + if (!this.$rootScope.$$phase) { + this.$rootScope.$apply(function () { + _eject(_this.definitions[resourceName], resource, id); + resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); + }); + } else { + _eject(_this.definitions[resourceName], resource, id); + resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); + } + delete this.store[resourceName].completedQueries[id]; + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = eject; -},{}],49:[function(require,module,exports){ +},{}],50:[function(require,module,exports){ var errorPrefix = 'DS.ejectAll(resourceName[, params]): '; function _ejectAll(definition, resource, params) { - var queryHash = this.utils.toJson(params), - items = this.filter(definition.name, params); + var queryHash = this.utils.toJson(params); + var items = this.filter(definition.name, params); + var ids = this.utils.toLookup(items, definition.idAttribute); + var _this = this; - for (var i = 0; i < items.length; i++) { - this.eject(definition.name, items[i][definition.idAttribute]); - } + angular.forEach(ids, function (item, id) { + _this.eject(definition.name, id); + }); - delete resource.completedQueries[queryHash]; + delete resource.completedQueries[queryHash]; } /** @@ -3965,7 +4062,7 @@ function _ejectAll(definition, resource, params) { * DS.filter('document'); // [ { title: 'How to Cook', id: 45, author: 'John Anderson' }, * // { title: 'How to Eat', id: 46, author: 'Sally Jane' } ] * - * DS.ejectAll('document', { query: { where: { author: 'Sally Jane' } } }); + * DS.ejectAll('document', { where: { author: 'Sally Jane' } }); * * DS.filter('document'); // [ { title: 'How to Cook', id: 45, author: 'John Anderson' } ] * ``` @@ -3990,44 +4087,44 @@ function _ejectAll(definition, resource, params) { * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. * @param {object} params Parameter object that is serialized into the query string. Properties: * - * - `{object=}` - `query` - The query object by which to filter items of the type specified by `resourceName`. Properties: - * - `{object=}` - `where` - Where clause. - * - `{number=}` - `limit` - Limit clause. - * - `{skip=}` - `skip` - Skip clause. - * - `{orderBy=}` - `orderBy` - OrderBy clause. + * - `{object=}` - `where` - Where clause. + * - `{number=}` - `limit` - Limit clause. + * - `{number=}` - `skip` - Skip clause. + * - `{number=}` - `offset` - Same as skip. + * - `{string|array=}` - `orderBy` - OrderBy clause. */ function ejectAll(resourceName, params) { - params = params || {}; - - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isObject(params)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!', { params: { actual: typeof params, expected: 'object' } }); - } - - var resource = this.store[resourceName], - _this = this; - - try { - if (!this.$rootScope.$$phase) { - this.$rootScope.$apply(function () { - _ejectAll.apply(_this, [_this.definitions[resourceName], resource, params]); - resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); - }); - } else { - _ejectAll.apply(_this, [_this.definitions[resourceName], resource, params]); - resource.collectionModified = this.utils.updateTimestamp(resource.collectionModified); - } - } catch (err) { - throw new this.errors.UnhandledError(err); - } + params = params || {}; + + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isObject(params)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!', { params: { actual: typeof params, expected: 'object' } }); + } + + var resource = this.store[resourceName]; + var _this = this; + + try { + if (!this.$rootScope.$$phase) { + this.$rootScope.$apply(function () { + _ejectAll.apply(_this, [_this.definitions[resourceName], resource, params]); + resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); + }); + } else { + _ejectAll.apply(_this, [_this.definitions[resourceName], resource, params]); + resource.collectionModified = this.utils.updateTimestamp(resource.collectionModified); + } + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = ejectAll; -},{}],50:[function(require,module,exports){ +},{}],51:[function(require,module,exports){ /* jshint loopfunc: true */ -var errorPrefix = 'DS.filter(resourceName, params[, options]): '; +var errorPrefix = 'DS.filter(resourceName[, params][, options]): '; /** * @doc method @@ -4038,7 +4135,7 @@ var errorPrefix = 'DS.filter(resourceName, params[, options]): '; * * ## Signature: * ```js - * DS.filter(resourceName, params[, options]) + * DS.filter(resourceName[, params][, options]) * ``` * * ## Example: @@ -4056,134 +4153,58 @@ var errorPrefix = 'DS.filter(resourceName, params[, options]): '; * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. * @param {object=} params Parameter object that is serialized into the query string. Properties: * - * - `{object=}` - `query` - The query object by which to filter items of the type specified by `resourceName`. Properties: - * - `{object=}` - `where` - Where clause. - * - `{number=}` - `limit` - Limit clause. - * - `{skip=}` - `skip` - Skip clause. - * - `{orderBy=}` - `orderBy` - OrderBy clause. + * - `{object=}` - `where` - Where clause. + * - `{number=}` - `limit` - Limit clause. + * - `{number=}` - `skip` - Skip clause. + * - `{number=}` - `offset` - Same as skip. + * - `{string|array=}` - `orderBy` - OrderBy clause. * * @param {object=} options Optional configuration. Properties: * - `{boolean=}` - `loadFromServer` - Send the query to server if it has not been sent yet. Default: `false`. * @returns {array} The filtered collection of items of the type specified by `resourceName`. */ function filter(resourceName, params, options) { - options = options || {}; - - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (params && !this.utils.isObject(params)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!', { params: { actual: typeof params, expected: 'object' } }); - } else if (!this.utils.isObject(options)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); - } + options = options || {}; - try { - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (params && !this.utils.isObject(params)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'params: Must be an object!', { params: { actual: typeof params, expected: 'object' } }); + } else if (!this.utils.isObject(options)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); + } - // Protect against null - params = params || {}; + try { + var definition = this.definitions[resourceName]; + var resource = this.store[resourceName]; - var queryHash = this.utils.toJson(params); + // Protect against null + params = params || {}; - if (!(queryHash in resource.completedQueries) && options.loadFromServer) { - // This particular query has never been completed + var queryHash = this.utils.toJson(params); - if (!resource.pendingQueries[queryHash]) { - // This particular query has never even been started - this.findAll(resourceName, params, options); - } - } + if (!(queryHash in resource.completedQueries) && options.loadFromServer) { + // This particular query has never been completed - params.query = params.query || {}; - // The query has been completed, so hit the cache with the query - var filtered = this.utils.filter(resource.collection, function (attrs) { - var keep = true, - where = params.query.where; - - // Apply 'where' clauses - if (where) { - if (!_this.utils.isObject(where)) { - throw new _this.errors.IllegalArgumentError(errorPrefix + 'params.query.where: Must be an object!', { params: { query: { where: { actual: typeof params.query.where, expected: 'object' } } } }); - } - keep = definition.filter(resourceName, where, attrs); - } - return keep; - }); - - // Apply 'orderBy' - if (params.query.orderBy) { - if (this.utils.isString(params.query.orderBy)) { - params.query.orderBy = [ - [params.query.orderBy, 'ASC'] - ]; - } - if (this.utils.isArray(params.query.orderBy)) { - for (var i = 0; i < params.query.orderBy.length; i++) { - if (this.utils.isString(params.query.orderBy[i])) { - params.query.orderBy[i] = [params.query.orderBy[i], 'ASC']; - } else if (!this.utils.isArray(params.query.orderBy[i])) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'params.query.orderBy[' + i + ']: Must be a string or an array!', { params: { query: { 'orderBy[i]': { actual: typeof params.query.orderBy[i], expected: 'string|array' } } } }); - } - filtered = this.utils.sort(filtered, function (a, b) { - var cA = a[params.query.orderBy[i][0]], cB = b[params.query.orderBy[i][0]]; - if (_this.utils.isString(cA)) { - cA = _this.utils.upperCase(cA); - } - if (_this.utils.isString(cB)) { - cB = _this.utils.upperCase(cB); - } - if (params.query.orderBy[i][1] === 'DESC') { - if (cB < cA) { - return -1; - } else if (cB > cA) { - return 1; - } else { - return 0; - } - } else { - if (cA < cB) { - return -1; - } else if (cA > cB) { - return 1; - } else { - return 0; - } - } - }); - } - } else { - throw new this.errors.IllegalArgumentError(errorPrefix + 'params.query.orderBy: Must be a string or an array!', { params: { query: { orderBy: { actual: typeof params.query.orderBy, expected: 'string|array' } } } }); - } - } - - // Apply 'limit' and 'skip' - if (this.utils.isNumber(params.query.limit) && this.utils.isNumber(params.query.skip)) { - filtered = this.utils.slice(filtered, params.query.skip, Math.min(filtered.length, params.query.skip + params.query.limit)); - } else if (this.utils.isNumber(params.query.limit)) { - filtered = this.utils.slice(filtered, 0, Math.min(filtered.length, params.query.limit)); - } else if (this.utils.isNumber(params.query.skip)) { - if (params.query.skip < filtered.length) { - filtered = this.utils.slice(filtered, params.query.skip); - } else { - filtered = []; - } - } + if (!resource.pendingQueries[queryHash]) { + // This particular query has never even been started + this.findAll(resourceName, params, options); + } + } - return filtered; - } catch (err) { - if (err instanceof this.errors.IllegalArgumentError) { - throw err; - } else { - throw new this.errors.UnhandledError(err); - } - } + return definition.filter.call(this, resource.collection, resourceName, params, options); + } catch (err) { + if (err instanceof this.errors.IllegalArgumentError) { + throw err; + } else { + throw new this.errors.UnhandledError(err); + } + } } module.exports = filter; -},{}],51:[function(require,module,exports){ +},{}],52:[function(require,module,exports){ var errorPrefix = 'DS.get(resourceName, id[, options]): '; /** @@ -4218,42 +4239,42 @@ var errorPrefix = 'DS.get(resourceName, id[, options]): '; * @returns {object} The item of the type specified by `resourceName` with the primary key specified by `id`. */ function get(resourceName, id, options) { - options = options || {}; - - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } else if (!this.utils.isObject(options)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); - } - var _this = this; - - try { - // cache miss, request resource from server - var item = this.store[resourceName].index.get(id); - if (!item && options.loadFromServer) { - this.find(resourceName, id).then(null, function (err) { - return _this.$q.reject(err); - }); - } + options = options || {}; + + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } else if (!this.utils.isObject(options)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); + } + var _this = this; + + try { + // cache miss, request resource from server + var item = this.store[resourceName].index.get(id); + if (!item && options.loadFromServer) { + this.find(resourceName, id).then(null, function (err) { + return _this.$q.reject(err); + }); + } - // return resource from cache - return item; - } catch (err) { - throw new this.errors.UnhandledError(err); - } + // return resource from cache + return item; + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = get; -},{}],52:[function(require,module,exports){ +},{}],53:[function(require,module,exports){ var errorPrefix = 'DS.hasChanges(resourceName, id): '; function diffIsEmpty(utils, diff) { - return !(utils.isEmpty(diff.added) && - utils.isEmpty(diff.removed) && - utils.isEmpty(diff.changed)); + return !(utils.isEmpty(diff.added) && + utils.isEmpty(diff.removed) && + utils.isEmpty(diff.changed)); } /** @@ -4290,251 +4311,251 @@ function diffIsEmpty(utils, diff) { * @returns {boolean} Whether the item of the type specified by `resourceName` with the primary key specified by `id` has changes. */ function hasChanges(resourceName, id) { - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } - - try { - // return resource from cache - if (this.get(resourceName, id)) { - return diffIsEmpty(this.utils, this.changes(resourceName, id)); - } else { - return false; - } - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } + + try { + // return resource from cache + if (this.get(resourceName, id)) { + return diffIsEmpty(this.utils, this.changes(resourceName, id)); + } else { + return false; + } + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = hasChanges; -},{}],53:[function(require,module,exports){ +},{}],54:[function(require,module,exports){ module.exports = { - /** - * @doc method - * @id DS.sync_methods:defineResource - * @name defineResource - * @methodOf DS - * @description - * See [DS.defineResource](/documentation/api/api/DS.sync_methods:defineResource). - */ - defineResource: require('./defineResource'), - - /** - * @doc method - * @id DS.sync_methods:bindOne - * @name bindOne - * @methodOf DS - * @description - * See [DS.bindOne](/documentation/api/api/DS.sync_methods:bindOne). - */ - bindOne: require('./bindOne'), - - /** - * @doc method - * @id DS.sync_methods:bindAll - * @name bindAll - * @methodOf DS - * @description - * See [DS.bindAll](/documentation/api/api/DS.sync_methods:bindAll). - */ - bindAll: require('./bindAll'), - - /** - * @doc method - * @id DS.sync_methods:eject - * @name eject - * @methodOf DS - * @description - * See [DS.eject](/documentation/api/api/DS.sync_methods:eject). - */ - eject: require('./eject'), - - /** - * @doc method - * @id DS.sync_methods:ejectAll - * @name ejectAll - * @methodOf DS - * @description - * See [DS.ejectAll](/documentation/api/api/DS.sync_methods:ejectAll). - */ - ejectAll: require('./ejectAll'), - - /** - * @doc method - * @id DS.sync_methods:filter - * @name filter - * @methodOf DS - * @description - * See [DS.filter](/documentation/api/api/DS.sync_methods:filter). - */ - filter: require('./filter'), - - /** - * @doc method - * @id DS.sync_methods:get - * @name get - * @methodOf DS - * @description - * See [DS.get](/documentation/api/api/DS.sync_methods:get). - */ - get: require('./get'), - - /** - * @doc method - * @id DS.sync_methods:inject - * @name inject - * @methodOf DS - * @description - * See [DS.inject](/documentation/api/api/DS.sync_methods:inject). - */ - inject: require('./inject'), - - /** - * @doc method - * @id DS.sync_methods:lastModified - * @name lastModified - * @methodOf DS - * @description - * See [DS.lastModified](/documentation/api/api/DS.sync_methods:lastModified). - */ - lastModified: require('./lastModified'), - - /** - * @doc method - * @id DS.sync_methods:lastSaved - * @name lastSaved - * @methodOf DS - * @description - * See [DS.lastSaved](/documentation/api/api/DS.sync_methods:lastSaved). - */ - lastSaved: require('./lastSaved'), - - /** - * @doc method - * @id DS.sync_methods:digest - * @name digest - * @methodOf DS - * @description - * See [DS.digest](/documentation/api/api/DS.sync_methods:digest). - */ - digest: require('./digest'), - - /** - * @doc method - * @id DS.sync_methods:changes - * @name changes - * @methodOf DS - * @description - * See [DS.changes](/documentation/api/api/DS.sync_methods:changes). - */ - changes: require('./changes'), - - /** - * @doc method - * @id DS.sync_methods:previous - * @name previous - * @methodOf DS - * @description - * See [DS.previous](/documentation/api/api/DS.sync_methods:previous). - */ - previous: require('./previous'), - - /** - * @doc method - * @id DS.sync_methods:hasChanges - * @name hasChanges - * @methodOf DS - * @description - * See [DS.hasChanges](/documentation/api/api/DS.sync_methods:hasChanges). - */ - hasChanges: require('./hasChanges') + /** + * @doc method + * @id DS.sync_methods:defineResource + * @name defineResource + * @methodOf DS + * @description + * See [DS.defineResource](/documentation/api/api/DS.sync_methods:defineResource). + */ + defineResource: require('./defineResource'), + + /** + * @doc method + * @id DS.sync_methods:bindOne + * @name bindOne + * @methodOf DS + * @description + * See [DS.bindOne](/documentation/api/api/DS.sync_methods:bindOne). + */ + bindOne: require('./bindOne'), + + /** + * @doc method + * @id DS.sync_methods:bindAll + * @name bindAll + * @methodOf DS + * @description + * See [DS.bindAll](/documentation/api/api/DS.sync_methods:bindAll). + */ + bindAll: require('./bindAll'), + + /** + * @doc method + * @id DS.sync_methods:eject + * @name eject + * @methodOf DS + * @description + * See [DS.eject](/documentation/api/api/DS.sync_methods:eject). + */ + eject: require('./eject'), + + /** + * @doc method + * @id DS.sync_methods:ejectAll + * @name ejectAll + * @methodOf DS + * @description + * See [DS.ejectAll](/documentation/api/api/DS.sync_methods:ejectAll). + */ + ejectAll: require('./ejectAll'), + + /** + * @doc method + * @id DS.sync_methods:filter + * @name filter + * @methodOf DS + * @description + * See [DS.filter](/documentation/api/api/DS.sync_methods:filter). + */ + filter: require('./filter'), + + /** + * @doc method + * @id DS.sync_methods:get + * @name get + * @methodOf DS + * @description + * See [DS.get](/documentation/api/api/DS.sync_methods:get). + */ + get: require('./get'), + + /** + * @doc method + * @id DS.sync_methods:inject + * @name inject + * @methodOf DS + * @description + * See [DS.inject](/documentation/api/api/DS.sync_methods:inject). + */ + inject: require('./inject'), + + /** + * @doc method + * @id DS.sync_methods:lastModified + * @name lastModified + * @methodOf DS + * @description + * See [DS.lastModified](/documentation/api/api/DS.sync_methods:lastModified). + */ + lastModified: require('./lastModified'), + + /** + * @doc method + * @id DS.sync_methods:lastSaved + * @name lastSaved + * @methodOf DS + * @description + * See [DS.lastSaved](/documentation/api/api/DS.sync_methods:lastSaved). + */ + lastSaved: require('./lastSaved'), + + /** + * @doc method + * @id DS.sync_methods:digest + * @name digest + * @methodOf DS + * @description + * See [DS.digest](/documentation/api/api/DS.sync_methods:digest). + */ + digest: require('./digest'), + + /** + * @doc method + * @id DS.sync_methods:changes + * @name changes + * @methodOf DS + * @description + * See [DS.changes](/documentation/api/api/DS.sync_methods:changes). + */ + changes: require('./changes'), + + /** + * @doc method + * @id DS.sync_methods:previous + * @name previous + * @methodOf DS + * @description + * See [DS.previous](/documentation/api/api/DS.sync_methods:previous). + */ + previous: require('./previous'), + + /** + * @doc method + * @id DS.sync_methods:hasChanges + * @name hasChanges + * @methodOf DS + * @description + * See [DS.hasChanges](/documentation/api/api/DS.sync_methods:hasChanges). + */ + hasChanges: require('./hasChanges') }; -},{"./bindAll":43,"./bindOne":44,"./changes":45,"./defineResource":46,"./digest":47,"./eject":48,"./ejectAll":49,"./filter":50,"./get":51,"./hasChanges":52,"./inject":54,"./lastModified":55,"./lastSaved":56,"./previous":57}],54:[function(require,module,exports){ +},{"./bindAll":44,"./bindOne":45,"./changes":46,"./defineResource":47,"./digest":48,"./eject":49,"./ejectAll":50,"./filter":51,"./get":52,"./hasChanges":53,"./inject":55,"./lastModified":56,"./lastSaved":57,"./previous":58}],55:[function(require,module,exports){ var observe = require('../../../lib/observe-js/observe-js'), - errorPrefix = 'DS.inject(resourceName, attrs[, options]): '; + errorPrefix = 'DS.inject(resourceName, attrs[, options]): '; function _inject(definition, resource, attrs) { - var _this = this, - $log = _this.$log; - - function _react(added, removed, changed, getOldValueFn) { - try { - var innerId = getOldValueFn(definition.idAttribute); - - resource.modified[innerId] = _this.utils.updateTimestamp(resource.modified[innerId]); - resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); - - if (definition.idAttribute in changed) { - $log.error('Doh! You just changed the primary key of an object! ' + - 'I don\'t know how to handle this yet, so your data for the "' + definition.name + - '" resource is now in an undefined (probably broken) state.'); - } - } catch (err) { - throw new _this.errors.UnhandledError(err); - } - } + var _this = this, + $log = _this.$log; + + function _react(added, removed, changed, getOldValueFn) { + try { + var innerId = getOldValueFn(definition.idAttribute); + + resource.modified[innerId] = _this.utils.updateTimestamp(resource.modified[innerId]); + resource.collectionModified = _this.utils.updateTimestamp(resource.collectionModified); + + if (definition.idAttribute in changed) { + $log.error('Doh! You just changed the primary key of an object! ' + + 'I don\'t know how to handle this yet, so your data for the "' + definition.name + + '" resource is now in an undefined (probably broken) state.'); + } + } catch (err) { + throw new _this.errors.UnhandledError(err); + } + } - var injected; - if (_this.utils.isArray(attrs)) { - injected = []; - for (var i = 0; i < attrs.length; i++) { - injected.push(_inject.call(_this, definition, resource, attrs[i])); - } - } else { - if (!(definition.idAttribute in attrs)) { - throw new _this.errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!'); - } else { - try { - definition.beforeInject(definition.name, attrs); - var id = attrs[definition.idAttribute], - item = this.get(definition.name, id); - - if (!item) { - if (definition.class) { - if (attrs instanceof definition[definition.class]) { - item = attrs; - } else { - item = new definition[definition.class](); - } - } else { - item = {}; - } - resource.previousAttributes[id] = {}; + var injected; + if (_this.utils.isArray(attrs)) { + injected = []; + for (var i = 0; i < attrs.length; i++) { + injected.push(_inject.call(_this, definition, resource, attrs[i])); + } + } else { + if (!(definition.idAttribute in attrs)) { + throw new _this.errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!'); + } else { + try { + definition.beforeInject(definition.name, attrs); + var id = attrs[definition.idAttribute], + item = this.get(definition.name, id); + + if (!item) { + if (definition.class) { + if (attrs instanceof definition[definition.class]) { + item = attrs; + } else { + item = new definition[definition.class](); + } + } else { + item = {}; + } + resource.previousAttributes[id] = {}; - _this.utils.deepMixIn(item, attrs); - _this.utils.deepMixIn(resource.previousAttributes[id], attrs); + _this.utils.deepMixIn(item, attrs); + _this.utils.deepMixIn(resource.previousAttributes[id], attrs); - resource.collection.push(item); + resource.collection.push(item); - resource.observers[id] = new observe.ObjectObserver(item, _react); - resource.index.put(id, item); + resource.observers[id] = new observe.ObjectObserver(item, _react); + resource.index.put(id, item); - _react({}, {}, {}, function () { - return id; - }); - } else { - _this.utils.deepMixIn(item, attrs); - if (typeof resource.index.touch === 'function') { - resource.index.touch(id); - } else { - resource.index.put(id, resource.index.get(id)); - } - resource.observers[id].deliver(); - } - resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); - definition.afterInject(definition.name, item); - injected = item; - } catch (err) { - $log.error(err); - $log.error('inject failed!', definition.name, attrs); - } - } - } - return injected; + _react({}, {}, {}, function () { + return id; + }); + } else { + _this.utils.deepMixIn(item, attrs); + if (typeof resource.index.touch === 'function') { + resource.index.touch(id); + } else { + resource.index.put(id, resource.index.get(id)); + } + resource.observers[id].deliver(); + } + resource.saved[id] = _this.utils.updateTimestamp(resource.saved[id]); + definition.afterInject(definition.name, item); + injected = item; + } catch (err) { + $log.error(err); + $log.error('inject failed!', definition.name, attrs); + } + } + } + return injected; } /** @@ -4583,42 +4604,42 @@ function _inject(definition, resource, attrs) { * the items that were injected into the data store. */ function inject(resourceName, attrs, options) { - options = options || {}; - - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isObject(attrs) && !this.utils.isArray(attrs)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object or an array!', { attrs: { actual: typeof attrs, expected: 'object|array' } }); - } else if (!this.utils.isObject(options)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); - } - - var definition = this.definitions[resourceName], - resource = this.store[resourceName], - _this = this; - - try { - var injected; - if (!this.$rootScope.$$phase) { - this.$rootScope.$apply(function () { - injected = _inject.apply(_this, [definition, resource, attrs]); - }); - } else { - injected = _inject.apply(_this, [definition, resource, attrs]); - } - return injected; - } catch (err) { - if (!(err instanceof this.errors.RuntimeError)) { - throw new this.errors.UnhandledError(err); - } else { - throw err; - } - } + options = options || {}; + + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isObject(attrs) && !this.utils.isArray(attrs)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object or an array!', { attrs: { actual: typeof attrs, expected: 'object|array' } }); + } else if (!this.utils.isObject(options)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); + } + + var definition = this.definitions[resourceName], + resource = this.store[resourceName], + _this = this; + + try { + var injected; + if (!this.$rootScope.$$phase) { + this.$rootScope.$apply(function () { + injected = _inject.apply(_this, [definition, resource, attrs]); + }); + } else { + injected = _inject.apply(_this, [definition, resource, attrs]); + } + return injected; + } catch (err) { + if (!(err instanceof this.errors.RuntimeError)) { + throw new this.errors.UnhandledError(err); + } else { + throw err; + } + } } module.exports = inject; -},{"../../../lib/observe-js/observe-js":1}],55:[function(require,module,exports){ +},{"../../../lib/observe-js/observe-js":1}],56:[function(require,module,exports){ var errorPrefix = 'DS.lastModified(resourceName[, id]): '; /** @@ -4656,27 +4677,27 @@ var errorPrefix = 'DS.lastModified(resourceName[, id]): '; * `resourceName` with the given primary key was modified. */ function lastModified(resourceName, id) { - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (id && !this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } - try { - if (id) { - if (!(id in this.store[resourceName].modified)) { - this.store[resourceName].modified[id] = 0; - } - return this.store[resourceName].modified[id]; - } - return this.store[resourceName].collectionModified; - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (id && !this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } + try { + if (id) { + if (!(id in this.store[resourceName].modified)) { + this.store[resourceName].modified[id] = 0; + } + return this.store[resourceName].modified[id]; + } + return this.store[resourceName].collectionModified; + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = lastModified; -},{}],56:[function(require,module,exports){ +},{}],57:[function(require,module,exports){ var errorPrefix = 'DS.lastSaved(resourceName[, id]): '; /** @@ -4720,24 +4741,24 @@ var errorPrefix = 'DS.lastSaved(resourceName[, id]): '; * @returns {number} The timestamp of the last time the item of type `resourceName` with the given primary key was saved. */ function lastSaved(resourceName, id) { - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } - try { - if (!(id in this.store[resourceName].saved)) { - this.store[resourceName].saved[id] = 0; - } - return this.store[resourceName].saved[id]; - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } + try { + if (!(id in this.store[resourceName].saved)) { + this.store[resourceName].saved[id] = 0; + } + return this.store[resourceName].saved[id]; + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = lastSaved; -},{}],57:[function(require,module,exports){ +},{}],58:[function(require,module,exports){ var errorPrefix = 'DS.previous(resourceName, id): '; /** @@ -4776,23 +4797,23 @@ var errorPrefix = 'DS.previous(resourceName, id): '; * @returns {object} The previous attributes of the item of the type specified by `resourceName` with the primary key specified by `id`. */ function previous(resourceName, id) { - if (!this.definitions[resourceName]) { - throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { - throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } - - try { - // return resource from cache - return angular.copy(this.store[resourceName].previousAttributes[id]); - } catch (err) { - throw new this.errors.UnhandledError(err); - } + if (!this.definitions[resourceName]) { + throw new this.errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!this.utils.isString(id) && !this.utils.isNumber(id)) { + throw new this.errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } + + try { + // return resource from cache + return angular.copy(this.store[resourceName].previousAttributes[id]); + } catch (err) { + throw new this.errors.UnhandledError(err); + } } module.exports = previous; -},{}],58:[function(require,module,exports){ +},{}],59:[function(require,module,exports){ /** * @doc function * @id errors.types:UnhandledError @@ -4802,49 +4823,49 @@ module.exports = previous; * @returns {UnhandledError} A new instance of `UnhandledError`. */ function UnhandledError(error) { - Error.call(this); - if (typeof Error.captureStackTrace === 'function') { - Error.captureStackTrace(this, this.constructor); - } - - error = error || {}; - - /** - * @doc property - * @id errors.types:UnhandledError.type - * @name type - * @propertyOf errors.types:UnhandledError - * @description Name of error type. Default: `"UnhandledError"`. - */ - this.type = this.constructor.name; - - /** - * @doc property - * @id errors.types:UnhandledError.originalError - * @name originalError - * @propertyOf errors.types:UnhandledError - * @description A reference to the original error that was thrown. - */ - this.originalError = error; - - /** - * @doc property - * @id errors.types:UnhandledError.message - * @name message - * @propertyOf errors.types:UnhandledError - * @description Message and stack trace. Same as `UnhandledError#stack`. - */ - this.message = 'UnhandledError: This is an uncaught exception. Please consider submitting an issue at https://github.com/jmdobry/angular-data/issues.\n\n' + - 'Original Uncaught Exception:\n' + (error.stack ? error.stack.toString() : error.stack); - - /** - * @doc property - * @id errors.types:UnhandledError.stack - * @name stack - * @propertyOf errors.types:UnhandledError - * @description Message and stack trace. Same as `UnhandledError#message`. - */ - this.stack = this.message; + Error.call(this); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, this.constructor); + } + + error = error || {}; + + /** + * @doc property + * @id errors.types:UnhandledError.type + * @name type + * @propertyOf errors.types:UnhandledError + * @description Name of error type. Default: `"UnhandledError"`. + */ + this.type = this.constructor.name; + + /** + * @doc property + * @id errors.types:UnhandledError.originalError + * @name originalError + * @propertyOf errors.types:UnhandledError + * @description A reference to the original error that was thrown. + */ + this.originalError = error; + + /** + * @doc property + * @id errors.types:UnhandledError.message + * @name message + * @propertyOf errors.types:UnhandledError + * @description Message and stack trace. Same as `UnhandledError#stack`. + */ + this.message = 'UnhandledError: This is an uncaught exception. Please consider submitting an issue at https://github.com/jmdobry/angular-data/issues.\n\n' + + 'Original Uncaught Exception:\n' + (error.stack ? error.stack.toString() : error.stack); + + /** + * @doc property + * @id errors.types:UnhandledError.stack + * @name stack + * @propertyOf errors.types:UnhandledError + * @description Message and stack trace. Same as `UnhandledError#message`. + */ + this.stack = this.message; } UnhandledError.prototype = Object.create(Error.prototype); @@ -4860,37 +4881,37 @@ UnhandledError.prototype.constructor = UnhandledError; * @returns {IllegalArgumentError} A new instance of `IllegalArgumentError`. */ function IllegalArgumentError(message, errors) { - Error.call(this); - if (typeof Error.captureStackTrace === 'function') { - Error.captureStackTrace(this, this.constructor); - } - - /** - * @doc property - * @id errors.types:IllegalArgumentError.type - * @name type - * @propertyOf errors.types:IllegalArgumentError - * @description Name of error type. Default: `"IllegalArgumentError"`. - */ - this.type = this.constructor.name; - - /** - * @doc property - * @id errors.types:IllegalArgumentError.errors - * @name errors - * @propertyOf errors.types:IllegalArgumentError - * @description Object containing information about the error. - */ - this.errors = errors || {}; - - /** - * @doc property - * @id errors.types:IllegalArgumentError.message - * @name message - * @propertyOf errors.types:IllegalArgumentError - * @description Error message. Default: `"Illegal Argument!"`. - */ - this.message = message || 'Illegal Argument!'; + Error.call(this); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, this.constructor); + } + + /** + * @doc property + * @id errors.types:IllegalArgumentError.type + * @name type + * @propertyOf errors.types:IllegalArgumentError + * @description Name of error type. Default: `"IllegalArgumentError"`. + */ + this.type = this.constructor.name; + + /** + * @doc property + * @id errors.types:IllegalArgumentError.errors + * @name errors + * @propertyOf errors.types:IllegalArgumentError + * @description Object containing information about the error. + */ + this.errors = errors || {}; + + /** + * @doc property + * @id errors.types:IllegalArgumentError.message + * @name message + * @propertyOf errors.types:IllegalArgumentError + * @description Error message. Default: `"Illegal Argument!"`. + */ + this.message = message || 'Illegal Argument!'; } IllegalArgumentError.prototype = Object.create(Error.prototype); @@ -4906,37 +4927,37 @@ IllegalArgumentError.prototype.constructor = IllegalArgumentError; * @returns {RuntimeError} A new instance of `RuntimeError`. */ function RuntimeError(message, errors) { - Error.call(this); - if (typeof Error.captureStackTrace === 'function') { - Error.captureStackTrace(this, this.constructor); - } - - /** - * @doc property - * @id errors.types:RuntimeError.type - * @name type - * @propertyOf errors.types:RuntimeError - * @description Name of error type. Default: `"RuntimeError"`. - */ - this.type = this.constructor.name; - - /** - * @doc property - * @id errors.types:RuntimeError.errors - * @name errors - * @propertyOf errors.types:RuntimeError - * @description Object containing information about the error. - */ - this.errors = errors || {}; - - /** - * @doc property - * @id errors.types:RuntimeError.message - * @name message - * @propertyOf errors.types:RuntimeError - * @description Error message. Default: `"Runtime Error!"`. - */ - this.message = message || 'RuntimeError Error!'; + Error.call(this); + if (typeof Error.captureStackTrace === 'function') { + Error.captureStackTrace(this, this.constructor); + } + + /** + * @doc property + * @id errors.types:RuntimeError.type + * @name type + * @propertyOf errors.types:RuntimeError + * @description Name of error type. Default: `"RuntimeError"`. + */ + this.type = this.constructor.name; + + /** + * @doc property + * @id errors.types:RuntimeError.errors + * @name errors + * @propertyOf errors.types:RuntimeError + * @description Object containing information about the error. + */ + this.errors = errors || {}; + + /** + * @doc property + * @id errors.types:RuntimeError.message + * @name message + * @propertyOf errors.types:RuntimeError + * @description Error message. Default: `"Runtime Error!"`. + */ + this.message = message || 'RuntimeError Error!'; } RuntimeError.prototype = Object.create(Error.prototype); @@ -4956,176 +4977,177 @@ RuntimeError.prototype.constructor = RuntimeError; * References to the constructor functions of these errors can be found in `DS.errors`. */ module.exports = [function () { - return { - UnhandledError: UnhandledError, - IllegalArgumentError: IllegalArgumentError, - RuntimeError: RuntimeError - }; + return { + UnhandledError: UnhandledError, + IllegalArgumentError: IllegalArgumentError, + RuntimeError: RuntimeError + }; }]; -},{}],59:[function(require,module,exports){ +},{}],60:[function(require,module,exports){ (function (window, angular, undefined) { - 'use strict'; + 'use strict'; + + /** + * @doc overview + * @id angular-data + * @name angular-data + * @description + * __Version:__ 0.9.1 + * + * ## Install + * + * #### Bower + * ```text + * bower install angular-data + * ``` + * + * Load `dist/angular-data.js` or `dist/angular-data.min.js` onto your web page after Angular.js. + * + * #### Npm + * ```text + * npm install angular-data + * ``` + * + * Load `dist/angular-data.js` or `dist/angular-data.min.js` onto your web page after Angular.js. + * + * #### Manual download + * Download angular-data-<%= pkg.version %>.js from the [Releases](https://github.com/jmdobry/angular-data/releases) + * section of the angular-data GitHub project. + * + * ## Load into Angular + * Your Angular app must depend on the module `"angular-data.DS"` in order to use angular-data. Loading + * angular-data into your app allows you to inject the following: + * + * - `DS` + * - `DSHttpAdapter` + * - `DSUtils` + * - `DSErrors` + * + * [DS](/documentation/api/api/DS) is the Data Store itself, which you will inject often. + * [DSHttpAdapter](/documentation/api/api/DSHttpAdapter) is useful as a wrapper for `$http` and is configurable. + * [DSUtils](/documentation/api/api/DSUtils) has some useful utility methods. + * [DSErrors](/documentation/api/api/DSErrors) provides references to the various errors thrown by the data store. + */ + angular.module('angular-data.DS', ['ng']) + .factory('DSUtils', require('./utils')) + .factory('DSErrors', require('./errors')) + .provider('DSHttpAdapter', require('./adapters/http')) + .provider('DSLocalStorageAdapter', require('./adapters/localStorage')) + .provider('DS', require('./datastore')) + .config(['$provide', function ($provide) { + $provide.decorator('$q', ['$delegate', function ($delegate) { + // do whatever you you want + $delegate.promisify = function (fn, target) { + var _this = this; + return function () { + var deferred = _this.defer(), + args = Array.prototype.slice.apply(arguments); + + args.push(function (err, result) { + if (err) { + deferred.reject(err); + } else { + deferred.resolve(result); + } + }); + + try { + fn.apply(target || this, args); + } catch (err) { + deferred.reject(err); + } - /** - * @doc overview - * @id angular-data - * @name angular-data - * @description - * __Version:__ 0.9.1 - * - * ## Install - * - * #### Bower - * ```text - * bower install angular-data - * ``` - * - * Load `dist/angular-data.js` or `dist/angular-data.min.js` onto your web page after Angular.js. - * - * #### Npm - * ```text - * npm install angular-data - * ``` - * - * Load `dist/angular-data.js` or `dist/angular-data.min.js` onto your web page after Angular.js. - * - * #### Manual download - * Download angular-data-<%= pkg.version %>.js from the [Releases](https://github.com/jmdobry/angular-data/releases) - * section of the angular-data GitHub project. - * - * ## Load into Angular - * Your Angular app must depend on the module `"angular-data.DS"` in order to use angular-data. Loading - * angular-data into your app allows you to inject the following: - * - * - `DS` - * - `DSHttpAdapter` - * - `DSUtils` - * - `DSErrors` - * - * [DS](/documentation/api/api/DS) is the Data Store itself, which you will inject often. - * [DSHttpAdapter](/documentation/api/api/DSHttpAdapter) is useful as a wrapper for `$http` and is configurable. - * [DSUtils](/documentation/api/api/DSUtils) has some useful utility methods. - * [DSErrors](/documentation/api/api/DSErrors) provides references to the various errors thrown by the data store. - */ - angular.module('angular-data.DS', ['ng']) - .factory('DSUtils', require('./utils')) - .factory('DSErrors', require('./errors')) - .provider('DSHttpAdapter', require('./adapters/http')) - .provider('DSLocalStorageAdapter', require('./adapters/localStorage')) - .provider('DS', require('./datastore')) - .config(['$provide', function ($provide) { - $provide.decorator('$q', ['$delegate', function ($delegate) { - // do whatever you you want - $delegate.promisify = function (fn, target) { - var _this = this; - return function () { - var deferred = _this.defer(), - args = Array.prototype.slice.apply(arguments); - - args.push(function (err, result) { - if (err) { - deferred.reject(err); - } else { - deferred.resolve(result); - } - }); - - try { - fn.apply(target || this, args); - } catch (err) { - deferred.reject(err); - } - - return deferred.promise; - }; - }; - return $delegate; - }]); - }]); + return deferred.promise; + }; + }; + return $delegate; + }]); + }]); })(window, window.angular); -},{"./adapters/http":30,"./adapters/localStorage":31,"./datastore":42,"./errors":58,"./utils":60}],60:[function(require,module,exports){ +},{"./adapters/http":31,"./adapters/localStorage":32,"./datastore":43,"./errors":59,"./utils":61}],61:[function(require,module,exports){ module.exports = [function () { - return { - isString: angular.isString, - isArray: angular.isArray, - isObject: angular.isObject, - isNumber: angular.isNumber, - isFunction: angular.isFunction, - isEmpty: require('mout/lang/isEmpty'), - toJson: angular.toJson, - makePath: require('mout/string/makePath'), - upperCase: require('mout/string/upperCase'), - deepMixIn: require('mout/object/deepMixIn'), - forOwn: require('mout/object/forOwn'), - pick: require('mout/object/pick'), - set: require('mout/object/set'), - contains: require('mout/array/contains'), - filter: require('mout/array/filter'), - toLookup: require('mout/array/toLookup'), - slice: require('mout/array/slice'), - sort: require('mout/array/sort'), - updateTimestamp: function (timestamp) { - var newTimestamp = typeof Date.now === 'function' ? Date.now() : new Date().getTime(); - if (timestamp && newTimestamp <= timestamp) { - return timestamp + 1; - } else { - return newTimestamp; - } - }, - deepFreeze: function deepFreeze(o) { - if (typeof Object.freeze === 'function') { - var prop, propKey; - Object.freeze(o); // First freeze the object. - for (propKey in o) { - prop = o[propKey]; - if (!o.hasOwnProperty(propKey) || typeof prop !== 'object' || Object.isFrozen(prop)) { - // If the object is on the prototype, not an object, or is already frozen, - // skip it. Note that this might leave an unfrozen reference somewhere in the - // object if there is an already frozen object containing an unfrozen object. - continue; - } - - deepFreeze(prop); // Recursively call deepFreeze. - } - } - }, - diffObjectFromOldObject: function (object, oldObject) { - var added = {}; - var removed = {}; - var changed = {}; - - for (var prop in oldObject) { - var newValue = object[prop]; - - if (newValue !== undefined && newValue === oldObject[prop]) - continue; - - if (!(prop in object)) { - removed[prop] = undefined; - continue; - } + return { + isString: angular.isString, + isArray: angular.isArray, + isObject: angular.isObject, + isNumber: angular.isNumber, + isFunction: angular.isFunction, + isEmpty: require('mout/lang/isEmpty'), + toJson: angular.toJson, + makePath: require('mout/string/makePath'), + upperCase: require('mout/string/upperCase'), + deepMixIn: require('mout/object/deepMixIn'), + forOwn: require('mout/object/forOwn'), + pick: require('mout/object/pick'), + set: require('mout/object/set'), + contains: require('mout/array/contains'), + filter: require('mout/array/filter'), + toLookup: require('mout/array/toLookup'), + map: require('mout/array/map'), + slice: require('mout/array/slice'), + sort: require('mout/array/sort'), + updateTimestamp: function (timestamp) { + var newTimestamp = typeof Date.now === 'function' ? Date.now() : new Date().getTime(); + if (timestamp && newTimestamp <= timestamp) { + return timestamp + 1; + } else { + return newTimestamp; + } + }, + deepFreeze: function deepFreeze(o) { + if (typeof Object.freeze === 'function') { + var prop, propKey; + Object.freeze(o); // First freeze the object. + for (propKey in o) { + prop = o[propKey]; + if (!o.hasOwnProperty(propKey) || typeof prop !== 'object' || Object.isFrozen(prop)) { + // If the object is on the prototype, not an object, or is already frozen, + // skip it. Note that this might leave an unfrozen reference somewhere in the + // object if there is an already frozen object containing an unfrozen object. + continue; + } + + deepFreeze(prop); // Recursively call deepFreeze. + } + } + }, + diffObjectFromOldObject: function (object, oldObject) { + var added = {}; + var removed = {}; + var changed = {}; + + for (var prop in oldObject) { + var newValue = object[prop]; + + if (newValue !== undefined && newValue === oldObject[prop]) + continue; + + if (!(prop in object)) { + removed[prop] = undefined; + continue; + } - if (newValue !== oldObject[prop]) - changed[prop] = newValue; - } + if (newValue !== oldObject[prop]) + changed[prop] = newValue; + } - for (var prop2 in object) { - if (prop2 in oldObject) - continue; + for (var prop2 in object) { + if (prop2 in oldObject) + continue; - added[prop2] = object[prop2]; - } + added[prop2] = object[prop2]; + } - return { - added: added, - removed: removed, - changed: changed - }; - } - }; + return { + added: added, + removed: removed, + changed: changed + }; + } + }; }]; -},{"mout/array/contains":2,"mout/array/filter":3,"mout/array/slice":7,"mout/array/sort":8,"mout/array/toLookup":9,"mout/lang/isEmpty":14,"mout/object/deepMixIn":21,"mout/object/forOwn":23,"mout/object/pick":26,"mout/object/set":27,"mout/string/makePath":28,"mout/string/upperCase":29}]},{},[59]) \ No newline at end of file +},{"mout/array/contains":2,"mout/array/filter":3,"mout/array/map":7,"mout/array/slice":8,"mout/array/sort":9,"mout/array/toLookup":10,"mout/lang/isEmpty":15,"mout/object/deepMixIn":22,"mout/object/forOwn":24,"mout/object/pick":27,"mout/object/set":28,"mout/string/makePath":29,"mout/string/upperCase":30}]},{},[60]) \ No newline at end of file diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index 410397c..bf21ebd 100644 --- a/dist/angular-data.min.js +++ b/dist/angular-data.min.js @@ -1,11 +1,11 @@ /** * @author Jason Dobry * @file angular-data.min.js -* @version 0.9.1 - Homepage +* @version 0.10.0 - Homepage * @copyright (c) 2014 Jason Dobry * @license MIT * * @overview Data store for Angular.js. */ -!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gb&&a.check();)a.report(),b++}function e(a){for(var b in a)return!1;return!0}function f(a){return e(a.added)&&e(a.removed)&&e(a.changed)}function g(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var f in a)f in b||(c[f]=a[f]);return Array.isArray(a)&&a.length!==b.length&&(e.length=a.length),{added:c,removed:d,changed:e}}function h(a,b){var c=b||(Array.isArray(a)?[]:{});for(var d in a)c[d]=a[d];return Array.isArray(a)&&(c.length=a.length),c}function i(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,n){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}j(this),this.connect(),this.sync(!0)}function j(a){u&&(t.push(a),i._allObserversCount++)}function k(a,b,c,d){i.call(this,a,b,c,d)}function l(a){this.arr=[],this.callback=a,this.isObserved=!0}function m(a,b,c){for(var d={},e={},f=0;fa&&b.anyChanged);i._allObserversCount=t.length,v=!1}}},u&&(a.Platform.clearObservers=function(){t=[]}),k.prototype=r({__proto__:i.prototype,connect:function(){n&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){n||(this.oldObject=h(this.object))},check:function(a){var b,c;if(n){if(!a)return!1;c={},b=m(this.object,a,c)}else c=this.oldObject,b=g(this.object,this.oldObject);return f(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){n?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}});var x=Object.getPrototypeOf({}),y=Object.getPrototypeOf([]);l.prototype={reset:function(){this.isObserved=!this.isObserved},observe:function(a){if(c(a)&&a!==x&&a!==y){var b=this.arr.indexOf(a);b>=0&&this.arr[b+1]===this.isObserved||(0>b&&(b=this.arr.length,this.arr[b]=a,Object.observe(a,this.callback)),this.arr[b+1]=this.isObserved,this.observe(Object.getPrototypeOf(a)))}},cleanup:function(){for(var a=0,b=0,c=this.isObserved;ba&&(this.arr[a]=d,this.arr[a+1]=c),a+=2):Object.unobserve(d,this.callback),b+=2}this.arr.length=a}};var z={"new":!0,updated:!0,deleted:!0};a.Observer=i,a.Observer.hasObjectObserve=n,a.ObjectObserver=k}((c.Number={isNaN:window.isNaN})?c:c)}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b){function c(a,b){return-1!==d(a,b)}var d=a("./indexOf");b.exports=c},{"./indexOf":5}],3:[function(a,b){function c(a,b,c){b=d(b,c);var e=[];if(null==a)return e;for(var f,g=-1,h=a.length;++gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],6:[function(a,b){function c(a){return null!=a&&""!==a}function d(a,b){return b=b||"",e(a,c).join(b)}var e=a("./filter");b.exports=d},{"./filter":3}],7:[function(a,b){function c(a,b,c){var d=a.length;b=null==b?0:0>b?Math.max(d+b,0):Math.min(b,d),c=null==c?d:0>c?Math.max(d+c,0):Math.min(c,d);for(var e=[];c>b;)e.push(a[b++]);return e}b.exports=c},{}],8:[function(a,b){function c(a,b){if(null==a)return[];if(a.length<2)return a;null==b&&(b=d);var f,g,h;return f=~~(a.length/2),g=c(a.slice(0,f),b),h=c(a.slice(f,a.length),b),e(g,h,b)}function d(a,b){return b>a?-1:a>b?1:0}function e(a,b,c){for(var d=[];a.length&&b.length;)d.push(c(a[0],b[0])<=0?a.shift():b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],9:[function(a,b){function c(a,b){var c={};if(null==a)return c;var e,f=-1,g=a.length;if(d(b))for(;++f"in a?d=d&&c[b]>a[">"]:">="in a?d=d&&c[b]>=a[">="]:"<"in a?d=d&&c[b]e?-1:e>d?1:0:e>d?-1:d>e?1:0})}}return this.utils.isNumber(b.query.limit)&&this.utils.isNumber(b.query.skip)?i=this.utils.slice(i,b.query.skip,Math.min(i.length,b.query.skip+b.query.limit)):this.utils.isNumber(b.query.limit)?i=this.utils.slice(i,0,Math.min(i.length,b.query.limit)):this.utils.isNumber(b.query.skip)&&(i=b.query.skip=b?a+1:b},deepFreeze:function b(a){if("function"==typeof Object.freeze){var c,d;Object.freeze(a);for(d in a)c=a[d],a.hasOwnProperty(d)&&"object"==typeof c&&!Object.isFrozen(c)&&b(c)}},diffObjectFromOldObject:function(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var h in a)h in b||(c[h]=a[h]);return{added:c,removed:d,changed:e}}}}]},{"mout/array/contains":2,"mout/array/filter":3,"mout/array/slice":7,"mout/array/sort":8,"mout/array/toLookup":9,"mout/lang/isEmpty":14,"mout/object/deepMixIn":21,"mout/object/forOwn":23,"mout/object/pick":26,"mout/object/set":27,"mout/string/makePath":28,"mout/string/upperCase":29}]},{},[59]); \ No newline at end of file +!function a(b,c,d){function e(g,h){if(!c[g]){if(!b[g]){var i="function"==typeof require&&require;if(!h&&i)return i(g,!0);if(f)return f(g,!0);throw new Error("Cannot find module '"+g+"'")}var j=c[g]={exports:{}};b[g][0].call(j.exports,function(a){var c=b[g][1][a];return e(c?c:a)},j,j.exports,a,b,c,d)}return c[g].exports}for(var f="function"==typeof require&&require,g=0;gb&&a.check();)a.report(),b++}function e(a){for(var b in a)return!1;return!0}function f(a){return e(a.added)&&e(a.removed)&&e(a.changed)}function g(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var f in a)f in b||(c[f]=a[f]);return Array.isArray(a)&&a.length!==b.length&&(e.length=a.length),{added:c,removed:d,changed:e}}function h(a,b){var c=b||(Array.isArray(a)?[]:{});for(var d in a)c[d]=a[d];return Array.isArray(a)&&(c.length=a.length),c}function i(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,n){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}j(this),this.connect(),this.sync(!0)}function j(a){u&&(t.push(a),i._allObserversCount++)}function k(a,b,c,d){i.call(this,a,b,c,d)}function l(a){this.arr=[],this.callback=a,this.isObserved=!0}function m(a,b,c){for(var d={},e={},f=0;fa&&b.anyChanged);i._allObserversCount=t.length,v=!1}}},u&&(a.Platform.clearObservers=function(){t=[]}),k.prototype=r({__proto__:i.prototype,connect:function(){n&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){n||(this.oldObject=h(this.object))},check:function(a){var b,c;if(n){if(!a)return!1;c={},b=m(this.object,a,c)}else c=this.oldObject,b=g(this.object,this.oldObject);return f(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){n?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}});var x=Object.getPrototypeOf({}),y=Object.getPrototypeOf([]);l.prototype={reset:function(){this.isObserved=!this.isObserved},observe:function(a){if(c(a)&&a!==x&&a!==y){var b=this.arr.indexOf(a);b>=0&&this.arr[b+1]===this.isObserved||(0>b&&(b=this.arr.length,this.arr[b]=a,Object.observe(a,this.callback)),this.arr[b+1]=this.isObserved,this.observe(Object.getPrototypeOf(a)))}},cleanup:function(){for(var a=0,b=0,c=this.isObserved;ba&&(this.arr[a]=d,this.arr[a+1]=c),a+=2):Object.unobserve(d,this.callback),b+=2}this.arr.length=a}};var z={"new":!0,updated:!0,deleted:!0};a.Observer=i,a.Observer.hasObjectObserve=n,a.ObjectObserver=k}((c.Number={isNaN:window.isNaN})?c:c)}).call(this,"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{}],2:[function(a,b){function c(a,b){return-1!==d(a,b)}var d=a("./indexOf");b.exports=c},{"./indexOf":5}],3:[function(a,b){function c(a,b,c){b=d(b,c);var e=[];if(null==a)return e;for(var f,g=-1,h=a.length;++gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],6:[function(a,b){function c(a){return null!=a&&""!==a}function d(a,b){return b=b||"",e(a,c).join(b)}var e=a("./filter");b.exports=d},{"./filter":3}],7:[function(a,b){function c(a,b,c){b=d(b,c);var e=[];if(null==a)return e;for(var f=-1,g=a.length;++fb?Math.max(d+b,0):Math.min(b,d),c=null==c?d:0>c?Math.max(d+c,0):Math.min(c,d);for(var e=[];c>b;)e.push(a[b++]);return e}b.exports=c},{}],9:[function(a,b){function c(a,b){if(null==a)return[];if(a.length<2)return a;null==b&&(b=d);var f,g,h;return f=~~(a.length/2),g=c(a.slice(0,f),b),h=c(a.slice(f,a.length),b),e(g,h,b)}function d(a,b){return b>a?-1:a>b?1:0}function e(a,b,c){for(var d=[];a.length&&b.length;)d.push(c(a[0],b[0])<=0?a.shift():b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],10:[function(a,b){function c(a,b){var c={};if(null==a)return c;var e,f=-1,g=a.length;if(d(b))for(;++f"in c?b=b&&a[d]>c[">"]:">="in c?b=b&&a[d]>=c[">="]:"<"in c?b=b&&a[d]f?-1:f>e?1:0:f>e?-1:e>f?1:0})});var i=angular.isNumber(c.limit)?c.limit:null,j=null;return angular.isNumber(c.skip)?j=c.skip:angular.isNumber(c.offset)&&(j=c.offset),i&&j?e=this.utils.slice(e,j,Math.min(e.length,j+i)):this.utils.isNumber(i)?e=this.utils.slice(e,0,Math.min(e.length,i)):this.utils.isNumber(j)&&(e=j=b?a+1:b},deepFreeze:function b(a){if("function"==typeof Object.freeze){var c,d;Object.freeze(a);for(d in a)c=a[d],a.hasOwnProperty(d)&&"object"==typeof c&&!Object.isFrozen(c)&&b(c)}},diffObjectFromOldObject:function(a,b){var c={},d={},e={};for(var f in b){var g=a[f];(void 0===g||g!==b[f])&&(f in a?g!==b[f]&&(e[f]=g):d[f]=void 0)}for(var h in a)h in b||(c[h]=a[h]);return{added:c,removed:d,changed:e}}}}]},{"mout/array/contains":2,"mout/array/filter":3,"mout/array/map":7,"mout/array/slice":8,"mout/array/sort":9,"mout/array/toLookup":10,"mout/lang/isEmpty":15,"mout/object/deepMixIn":22,"mout/object/forOwn":24,"mout/object/pick":27,"mout/object/set":28,"mout/string/makePath":29,"mout/string/upperCase":30}]},{},[60]); \ No newline at end of file diff --git a/guide/nav.html b/guide/nav.html index 2593d85..f4fc5d3 100644 --- a/guide/nav.html +++ b/guide/nav.html @@ -72,7 +72,7 @@ API