From a06704574b1c3dcc4e02d31857c1177416192c38 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Wed, 15 Jan 2014 17:11:56 -0700 Subject: [PATCH 1/7] Start 0.5.0 development. --- bower.json | 2 +- dist/angular-data.js | 8 ++++---- dist/angular-data.min.js | 4 ++-- package.json | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bower.json b/bower.json index 5dcaeb7..6421beb 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,7 @@ "author": "Jason Dobry", "name": "angular-data", "description": "Data store for Angular.js.", - "version": "0.4.2", + "version": "0.5.0", "homepage": "http://jmdobry.github.io/angular-data/", "repository": { "type": "git", diff --git a/dist/angular-data.js b/dist/angular-data.js index 6e19924..e2b607b 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -1,7 +1,7 @@ /** * @author Jason Dobry * @file angular-data.js - * @version 0.4.2 - Homepage + * @version 0.5.0 - Homepage * @copyright (c) 2014 Jason Dobry * @license MIT * @@ -2781,9 +2781,7 @@ function DSProvider() { module.exports = DSProvider; -},{"./async_methods":31,"./http":34,"./sync_methods":45,"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],"services":[function(require,module,exports){ -module.exports=require('cX8q+p'); -},{}],"cX8q+p":[function(require,module,exports){ +},{"./async_methods":31,"./http":34,"./sync_methods":45,"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],"cX8q+p":[function(require,module,exports){ module.exports = { config: { idAttribute: 'id' @@ -2791,6 +2789,8 @@ module.exports = { store: {} }; +},{}],"services":[function(require,module,exports){ +module.exports=require('cX8q+p'); },{}],38:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index 50179f2..9bb7217 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.4.2 - Homepage +* @version 0.5.0 - Homepage * @copyright (c) 2014 Jason Dobry * @license MIT * * @overview Data store for Angular.js. */ -require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=$q.defer();f.store[a]?d.isObject(b)||c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name);h.validate?h.validate(b,null,function(a){a?c.reject(a):i.POST(j,b,null).then(function(a){try{c.resolve(i.inject(h.name,a))}catch(b){c.reject(b)}},c.reject)}):i.POST(j,b,null).then(function(a){try{c.resolve(i.inject(h.name,a))}catch(b){c.reject(b)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=$q.defer();f.store[a]?d.isString(b)||d.isNumber(b)||c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name,b);i.DEL(j,null).then(function(){try{i.eject(a,b),c.resolve(b)}catch(d){c.reject(d)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=$q.defer();if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c)){var j=this;try{var k=f.store[a];if(b in k.index&&!c.bypassCache)i.resolve(j.get(a,b));else{var l=d.makePath(k.baseUrl||f.config.baseUrl,k.endpoint||k.name,b),m=null;c.bypassCache&&(m={headers:{"Last-Modified":new Date(k.modified[b])}}),g(l,m).then(function(c){try{j.inject(a,c),i.resolve(j.get(a,b))}catch(d){i.reject(d)}},i.reject)}}catch(n){n instanceof e.UnhandledError?i.reject(n):i.reject(new e.UnhandledError(n))}}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return i.promise}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;ic?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=$q.defer();f.store[a]?d.isObject(b)||c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name);h.validate?h.validate(b,null,function(a){a?c.reject(a):i.POST(j,b,null).then(function(a){try{c.resolve(i.inject(h.name,a))}catch(b){c.reject(b)}},c.reject)}):i.POST(j,b,null).then(function(a){try{c.resolve(i.inject(h.name,a))}catch(b){c.reject(b)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=$q.defer();f.store[a]?d.isString(b)||d.isNumber(b)||c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name,b);i.DEL(j,null).then(function(){try{i.eject(a,b),c.resolve(b)}catch(d){c.reject(d)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=$q.defer();if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c)){var j=this;try{var k=f.store[a];if(b in k.index&&!c.bypassCache)i.resolve(j.get(a,b));else{var l=d.makePath(k.baseUrl||f.config.baseUrl,k.endpoint||k.name,b),m=null;c.bypassCache&&(m={headers:{"Last-Modified":new Date(k.modified[b])}}),g(l,m).then(function(c){try{j.inject(a,c),i.resolve(j.get(a,b))}catch(d){i.reject(d)}},i.reject)}}catch(n){n instanceof e.UnhandledError?i.reject(n):i.reject(new e.UnhandledError(n))}}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return i.promise}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file diff --git a/package.json b/package.json index 97023f8..12c095d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "angular-data", "description": "Data store for Angular.js.", - "version": "0.4.2", + "version": "0.5.0", "homepage": "http://github.com/jmdobry/angular-data", "repository": { "type": "git", From 079a9d92788149255a9147133ac2b6574ebdc99e Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Wed, 15 Jan 2014 20:20:45 -0700 Subject: [PATCH 2/7] Fixes #19. Added lifecycle hooks to DS.save and DS.create. #7 --- dist/angular-data.js | 347 ++++++++++++------ dist/angular-data.min.js | 4 +- src/datastore/async_methods/create/index.js | 65 ++-- src/datastore/async_methods/destroy/index.js | 2 +- src/datastore/async_methods/find/index.js | 2 +- src/datastore/async_methods/findAll/index.js | 34 +- src/datastore/async_methods/save/index.js | 66 ++-- src/datastore/index.js | 34 +- src/datastore/services/index.js | 67 +++- .../sync_methods/defineResource/index.js | 41 ++- src/index.js | 30 +- .../sync_methods/filter/index.test.js | 2 +- 12 files changed, 478 insertions(+), 216 deletions(-) diff --git a/dist/angular-data.js b/dist/angular-data.js index e2b607b..5bd6e01 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -1830,47 +1830,48 @@ var utils = require('utils'), * - `{UnhandledError}` */ function create(resourceName, attrs) { - var deferred = $q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise; + if (!services.store[resourceName]) { deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); } else if (!utils.isObject(attrs)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!', { attrs: { actual: typeof attrs, expected: 'object' } })); - } + } else { + try { + var resource = services.store[resourceName], + _this = this; + + promise = promise + .then(function (attrs) { + return services.$q.promisify(resource.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.beforeCreate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.POST(utils.makePath(resource.baseUrl, resource.endpoint), attrs, null); + }) + .then(function (data) { + return services.$q.promisify(resource.afterCreate)(resourceName, data); + }) + .then(function (data) { + return _this.inject(resource.name, data); + }); - try { - var resource = services.store[resourceName], - _this = this, - url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name); - - if (resource.validate) { - resource.validate(attrs, null, function (err) { - if (err) { - deferred.reject(err); - } else { - - _this.POST(url, attrs, null).then(function (data) { - try { - deferred.resolve(_this.inject(resource.name, data)); - } catch (err) { - deferred.reject(err); - } - }, deferred.reject); - } - }); - } else { - _this.POST(url, attrs, null).then(function (data) { - try { - deferred.resolve(_this.inject(resource.name, data)); - } catch (err) { - deferred.reject(err); - } - }, deferred.reject); + deferred.resolve(attrs); + } catch (err) { + deferred.reject(new errors.UnhandledError(err)); } - } catch (err) { - deferred.reject(new errors.UnhandledError(err)); } - return deferred.promise; + return promise; } module.exports = create; @@ -1923,7 +1924,7 @@ var utils = require('utils'), * - `{UnhandledError}` */ function destroy(resourceName, id) { - var deferred = $q.defer(); + var deferred = service.$q.defer(); if (!services.store[resourceName]) { deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); } else if (!utils.isString(id) && !utils.isNumber(id)) { @@ -2004,7 +2005,7 @@ var utils = require('utils'), * - `{UnhandledError}` */ function find(resourceName, id, options) { - var deferred = $q.defer(); + var deferred = services.$q.defer(); options = options || {}; if (!services.store[resourceName]) { @@ -2084,7 +2085,7 @@ function processResults(data, resourceName, queryHash) { return data; } -function _findAll(deferred, resourceName, params, options) { +function _findAll(resourceName, params, options) { var resource = services.store[resourceName], _this = this; @@ -2099,18 +2100,19 @@ function _findAll(deferred, resourceName, params, options) { if (!resource.pendingQueries[queryHash]) { - // This particular query has never even been started - var url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name); - resource.pendingQueries[queryHash] = GET(url, { params: params }).then(function (data) { - try { - deferred.resolve(processResults.apply(_this, [data, resourceName, queryHash])); - } catch (err) { - deferred.reject(new errors.UnhandledError(err)); - } - }, deferred.reject); + // This particular query has never even been made + resource.pendingQueries[queryHash] = GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) + .then(function (data) { + try { + return processResults.apply(_this, [data, resourceName, queryHash]); + } catch (err) { + throw new errors.UnhandledError(err); + } + }); } + return resource.pendingQueries[queryHash]; } else { - deferred.resolve(this.filter(resourceName, params, options)); + return this.filter(resourceName, params, options); } } @@ -2182,7 +2184,9 @@ function _findAll(deferred, resourceName, params, options) { * - `{UnhandledError}` */ function findAll(resourceName, params, options) { - var deferred = services.$q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise, + _this = this; options = options || {}; @@ -2194,13 +2198,16 @@ function findAll(resourceName, params, options) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); } else { try { - _findAll.apply(this, [deferred, resourceName, params, options]); + promise = promise.then(function () { + return _findAll.apply(_this, [resourceName, params, options]); + }); + deferred.resolve(); } catch (err) { deferred.reject(new errors.UnhandledError(err)); } } - return deferred.promise; + return promise; } module.exports = findAll; @@ -2350,19 +2357,8 @@ module.exports = refresh; var utils = require('utils'), errors = require('errors'), services = require('services'), - PUT = require('../../http').PUT, errorPrefix = 'DS.save(resourceName, id[, options]): '; -function _save(deferred, resource, id, options) { - var _this = this; - var url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name, id); - PUT(url, resource.index[id], null).then(function (data) { - var saved = _this.inject(resource.name, data, options); - resource.saved[id] = utils.updateTimestamp(resource.saved[id]); - deferred.resolve(saved); - }, deferred.reject); -} - /** * @doc method * @id DS.async_methods:save @@ -2407,7 +2403,8 @@ function _save(deferred, resource, id, options) { * - `{UnhandledError}` */ function save(resourceName, id, options) { - var deferred = $q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise; options = options || {}; @@ -2417,38 +2414,46 @@ function save(resourceName, id, options) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); } else if (!utils.isObject(options)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); + } else if (!(id in services.store[resourceName].index)) { + deferred.reject(new errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); } else { - var _this = this; - - try { - var resource = services.store[resourceName]; + var resource = services.store[resourceName], + _this = this; + + promise = promise + .then(function (attrs) { + return services.$q.promisify(resource.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.beforeUpdate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.PUT(utils.makePath(resource.baseUrl, resource.endpoint, id), attrs, null); + }) + .then(function (data) { + return services.$q.promisify(resource.afterUpdate)(resourceName, data); + }) + .then(function (data) { + var saved = _this.inject(resource.name, data, options); + resource.saved[id] = utils.updateTimestamp(resource.saved[id]); + return saved; + }); - if (resource.schema) { - resource.schema.validate(resource.index[id], function (err) { - if (err) { - deferred.reject(err); - } else { - _save.call(_this, deferred, resource, id, options); - } - }); - } else { - _save.call(_this, deferred, resource, id, options); - } - } catch (err) { - if (!(err instanceof errors.UnhandledError)) { - deferred.reject(new errors.UnhandledError(err)); - } else { - deferred.reject(err); - } - } + deferred.resolve(resource.index[id]); } - return deferred.promise; + return promise; } module.exports = save; -},{"../../http":34,"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],34:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],34:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'); @@ -2683,7 +2688,6 @@ module.exports = { },{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],35:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), - IllegalArgumentError = errors.IllegalArgumentError, services = require('services'), errorPrefix = 'DSProvider.config(options): '; @@ -2702,7 +2706,8 @@ var utils = require('utils'), * ## Example: * ```js * DSProvider.config({ - * baseUrl: 'http://myapp.com/api' + * baseUrl: 'http://myapp.com/api', + * idAttribute: '_id' * }); * ``` * @@ -2716,12 +2721,34 @@ function config(options) { options = options || {}; if (!utils.isObject(options)) { - throw new IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); - } else if (!utils.isString(options.baseUrl)) { - throw new IllegalArgumentError(errorPrefix + 'options: Must be an object!', { baseUrl: { actual: typeof options, expected: 'object' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); + } else if ('baseUrl' in options && !utils.isString(options.baseUrl)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.baseUrl: Must be a string!', { baseUrl: { actual: typeof options.baseUrl, expected: 'string' } }); + } else if ('idAttribute' in options && !utils.isString(options.idAttribute)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.idAttribute: Must be a string!', { idAttribute: { actual: typeof options.idAttribute, expected: 'string' } }); + } else if ('mergeStrategy' in options && !utils.isString(options.mergeStrategy)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.mergeStrategy: Must be a string!', { mergeStrategy: { actual: typeof options.mergeStrategy, expected: 'string' } }); + } else if ('beforeValidate' in options && !utils.isFunction(options.beforeValidate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeValidate: Must be a function!', { beforeValidate: { actual: typeof options.beforeValidate, expected: 'function' } }); + } else if ('validate' in options && !utils.isFunction(options.validate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.validate: Must be a function!', { validate: { actual: typeof options.validate, expected: 'function' } }); + } else if ('afterValidate' in options && !utils.isFunction(options.afterValidate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterValidate: Must be a function!', { afterValidate: { actual: typeof options.afterValidate, expected: 'function' } }); + } else if ('beforeCreate' in options && !utils.isFunction(options.beforeCreate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeCreate: Must be a function!', { beforeCreate: { actual: typeof options.beforeCreate, expected: 'function' } }); + } else if ('afterCreate' in options && !utils.isFunction(options.afterCreate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterCreate: Must be a function!', { afterCreate: { actual: typeof options.afterCreate, expected: 'function' } }); + } else if ('beforeUpdate' in options && !utils.isFunction(options.beforeUpdate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeUpdate: Must be a function!', { beforeUpdate: { actual: typeof options.beforeUpdate, expected: 'function' } }); + } else if ('afterUpdate' in options && !utils.isFunction(options.afterUpdate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterUpdate: Must be a function!', { afterUpdate: { actual: typeof options.afterUpdate, expected: 'function' } }); + } else if ('beforeDestroy' in options && !utils.isFunction(options.beforeDestroy)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeDestroy: Must be a function!', { beforeDestroy: { actual: typeof options.beforeDestroy, expected: 'function' } }); + } else if ('afterDestroy' in options && !utils.isFunction(options.afterDestroy)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterDestroy: Must be a function!', { afterDestroy: { actual: typeof options.afterDestroy, expected: 'function' } }); } - utils.deepMixIn(services.config, options); + services.config = new services.BaseConfig(options); } /** @@ -2782,13 +2809,76 @@ function DSProvider() { module.exports = DSProvider; },{"./async_methods":31,"./http":34,"./sync_methods":45,"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],"cX8q+p":[function(require,module,exports){ -module.exports = { +function lifecycleNoop(resourceName, attrs, cb) { + cb(null, attrs); +} + +var services = module.exports = { config: { idAttribute: 'id' }, - store: {} + store: {}, + BaseConfig: function (options) { + if ('idAttribute' in options) { + this.idAttribute = options.idAttribute; + } + + if ('baseUrl' in options) { + this.baseUrl = options.baseUrl; + } + + if ('beforeValidate' in options) { + this.beforeValidate = options.beforeValidate; + } + + if ('validate' in options) { + this.validate = options.validate; + } + + if ('afterValidate' in options) { + this.afterValidate = options.afterValidate; + } + + if ('beforeCreate' in options) { + this.beforeCreate = options.beforeCreate; + } + + if ('afterCreate' in options) { + this.afterCreate = options.afterCreate; + } + + if ('beforeUpdate' in options) { + this.beforeUpdate = options.beforeUpdate; + } + + if ('afterUpdate' in options) { + this.afterUpdate = options.afterUpdate; + } + + if ('beforeDestroy' in options) { + this.beforeDestroy = options.beforeDestroy; + } + + if ('afterDestroy' in options) { + this.afterDestroy = options.afterDestroy; + } + } }; + +services.BaseConfig.prototype.idAttribute = 'id'; +services.BaseConfig.prototype.baseUrl = ''; +services.BaseConfig.prototype.endpoint = ''; +services.BaseConfig.prototype.beforeValidate = lifecycleNoop; +services.BaseConfig.prototype.validate = lifecycleNoop; +services.BaseConfig.prototype.afterValidate = lifecycleNoop; +services.BaseConfig.prototype.beforeCreate = lifecycleNoop; +services.BaseConfig.prototype.afterCreate = lifecycleNoop; +services.BaseConfig.prototype.beforeUpdate = lifecycleNoop; +services.BaseConfig.prototype.afterUpdate = lifecycleNoop; +services.BaseConfig.prototype.beforeDestroy = lifecycleNoop; +services.BaseConfig.prototype.afterDestroy = lifecycleNoop; + },{}],"services":[function(require,module,exports){ module.exports=require('cX8q+p'); },{}],38:[function(require,module,exports){ @@ -2853,6 +2943,31 @@ var utils = require('utils'), services = require('services'), errorPrefix = 'DS.defineResource(definition): '; +function Resource(options) { + services.BaseConfig.apply(this, [options]); + + if ('name' in options) { + this.name = options.name; + } + + if ('endpoint' in options) { + this.endpoint = options.endpoint; + } else { + this.endpoint = this.name; + } + + this.collection = []; + this.completedQueries = {}; + this.pendingQueries = {}; + this.index = {}; + this.modified = {}; + this.changes = {}; + this.previous_attributes = {}; + this.saved = {}; + this.observers = {}; + this.collectionModified = 0; +} + /** * @doc method * @id DS.sync_methods:defineResource @@ -2911,20 +3026,8 @@ function defineResource(definition) { } try { - services.store[definition.name] = definition; - - var resource = services.store[definition.name]; - resource.collection = []; - resource.completedQueries = {}; - resource.pendingQueries = {}; - resource.index = {}; - resource.modified = {}; - resource.changes = {}; - resource.previous_attributes = {}; - resource.saved = {}; - resource.observers = {}; - resource.collectionModified = 0; - resource.idAttribute = resource.idAttribute || services.config.idAttribute || 'id'; + Resource.prototype = services.config; + services.store[definition.name] = new Resource(definition); } catch (err) { delete services.store[definition.name]; throw new errors.UnhandledError(err); @@ -4064,7 +4167,35 @@ module.exports = { // } // angular.module('jmdobry.binary-heap').provider('BinaryHeap', BinaryHeapProvider); - angular.module('jmdobry.angular-data', ['ng'/*, 'jmdobry.binary-heap'*/]); + angular.module('jmdobry.angular-data', ['ng'/*, 'jmdobry.binary-heap'*/]).config(['$provide', function ($provide) { + $provide.decorator('$q', 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; + }); + }]); angular.module('jmdobry.angular-data').provider('DS', require('./datastore')); })(window, window.angular); diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index 9bb7217..ae031ac 100644 --- a/dist/angular-data.min.js +++ b/dist/angular-data.min.js @@ -7,5 +7,5 @@ * * @overview Data store for Angular.js. */ -require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=$q.defer();f.store[a]?d.isObject(b)||c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name);h.validate?h.validate(b,null,function(a){a?c.reject(a):i.POST(j,b,null).then(function(a){try{c.resolve(i.inject(h.name,a))}catch(b){c.reject(b)}},c.reject)}):i.POST(j,b,null).then(function(a){try{c.resolve(i.inject(h.name,a))}catch(b){c.reject(b)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=$q.defer();f.store[a]?d.isString(b)||d.isNumber(b)||c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name,b);i.DEL(j,null).then(function(){try{i.eject(a,b),c.resolve(b)}catch(d){c.reject(d)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=$q.defer();if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c)){var j=this;try{var k=f.store[a];if(b in k.index&&!c.bypassCache)i.resolve(j.get(a,b));else{var l=d.makePath(k.baseUrl||f.config.baseUrl,k.endpoint||k.name,b),m=null;c.bypassCache&&(m={headers:{"Last-Modified":new Date(k.modified[b])}}),g(l,m).then(function(c){try{j.inject(a,c),i.resolve(j.get(a,b))}catch(d){i.reject(d)}},i.reject)}}catch(n){n instanceof e.UnhandledError?i.reject(n):i.reject(new e.UnhandledError(n))}}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return i.promise}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file +require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=service.$q.defer();f.store[a]?d.isString(b)||d.isNumber(b)||c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name,b);i.DEL(j,null).then(function(){try{i.eject(a,b),c.resolve(b)}catch(d){c.reject(d)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=f.$q.defer();if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c)){var j=this;try{var k=f.store[a];if(b in k.index&&!c.bypassCache)i.resolve(j.get(a,b));else{var l=d.makePath(k.baseUrl||f.config.baseUrl,k.endpoint||k.name,b),m=null;c.bypassCache&&(m={headers:{"Last-Modified":new Date(k.modified[b])}}),g(l,m).then(function(c){try{j.inject(a,c),i.resolve(j.get(a,b))}catch(d){i.reject(d)}},i.reject)}}catch(n){n instanceof e.UnhandledError?i.reject(n):i.reject(new e.UnhandledError(n))}}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return i.promise}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file diff --git a/src/datastore/async_methods/create/index.js b/src/datastore/async_methods/create/index.js index 13b0b49..5dcc4b3 100644 --- a/src/datastore/async_methods/create/index.js +++ b/src/datastore/async_methods/create/index.js @@ -45,47 +45,48 @@ var utils = require('utils'), * - `{UnhandledError}` */ function create(resourceName, attrs) { - var deferred = $q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise; + if (!services.store[resourceName]) { deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); } else if (!utils.isObject(attrs)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'attrs: Must be an object!', { attrs: { actual: typeof attrs, expected: 'object' } })); - } - - try { - var resource = services.store[resourceName], - _this = this, - url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name); + } else { + try { + var resource = services.store[resourceName], + _this = this; - if (resource.validate) { - resource.validate(attrs, null, function (err) { - if (err) { - deferred.reject(err); - } else { + promise = promise + .then(function (attrs) { + return services.$q.promisify(resource.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.beforeCreate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.POST(utils.makePath(resource.baseUrl, resource.endpoint), attrs, null); + }) + .then(function (data) { + return services.$q.promisify(resource.afterCreate)(resourceName, data); + }) + .then(function (data) { + return _this.inject(resource.name, data); + }); - _this.POST(url, attrs, null).then(function (data) { - try { - deferred.resolve(_this.inject(resource.name, data)); - } catch (err) { - deferred.reject(err); - } - }, deferred.reject); - } - }); - } else { - _this.POST(url, attrs, null).then(function (data) { - try { - deferred.resolve(_this.inject(resource.name, data)); - } catch (err) { - deferred.reject(err); - } - }, deferred.reject); + deferred.resolve(attrs); + } catch (err) { + deferred.reject(new errors.UnhandledError(err)); } - } catch (err) { - deferred.reject(new errors.UnhandledError(err)); } - return deferred.promise; + return promise; } module.exports = create; diff --git a/src/datastore/async_methods/destroy/index.js b/src/datastore/async_methods/destroy/index.js index b0034f3..0b46b8e 100644 --- a/src/datastore/async_methods/destroy/index.js +++ b/src/datastore/async_methods/destroy/index.js @@ -45,7 +45,7 @@ var utils = require('utils'), * - `{UnhandledError}` */ function destroy(resourceName, id) { - var deferred = $q.defer(); + var deferred = service.$q.defer(); if (!services.store[resourceName]) { deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); } else if (!utils.isString(id) && !utils.isNumber(id)) { diff --git a/src/datastore/async_methods/find/index.js b/src/datastore/async_methods/find/index.js index 3da3ea3..a5a1fe5 100644 --- a/src/datastore/async_methods/find/index.js +++ b/src/datastore/async_methods/find/index.js @@ -49,7 +49,7 @@ var utils = require('utils'), * - `{UnhandledError}` */ function find(resourceName, id, options) { - var deferred = $q.defer(); + var deferred = services.$q.defer(); options = options || {}; if (!services.store[resourceName]) { diff --git a/src/datastore/async_methods/findAll/index.js b/src/datastore/async_methods/findAll/index.js index 81ecea4..b91a99f 100644 --- a/src/datastore/async_methods/findAll/index.js +++ b/src/datastore/async_methods/findAll/index.js @@ -26,7 +26,7 @@ function processResults(data, resourceName, queryHash) { return data; } -function _findAll(deferred, resourceName, params, options) { +function _findAll(resourceName, params, options) { var resource = services.store[resourceName], _this = this; @@ -41,18 +41,19 @@ function _findAll(deferred, resourceName, params, options) { if (!resource.pendingQueries[queryHash]) { - // This particular query has never even been started - var url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name); - resource.pendingQueries[queryHash] = GET(url, { params: params }).then(function (data) { - try { - deferred.resolve(processResults.apply(_this, [data, resourceName, queryHash])); - } catch (err) { - deferred.reject(new errors.UnhandledError(err)); - } - }, deferred.reject); + // This particular query has never even been made + resource.pendingQueries[queryHash] = GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) + .then(function (data) { + try { + return processResults.apply(_this, [data, resourceName, queryHash]); + } catch (err) { + throw new errors.UnhandledError(err); + } + }); } + return resource.pendingQueries[queryHash]; } else { - deferred.resolve(this.filter(resourceName, params, options)); + return this.filter(resourceName, params, options); } } @@ -124,7 +125,9 @@ function _findAll(deferred, resourceName, params, options) { * - `{UnhandledError}` */ function findAll(resourceName, params, options) { - var deferred = services.$q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise, + _this = this; options = options || {}; @@ -136,13 +139,16 @@ function findAll(resourceName, params, options) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); } else { try { - _findAll.apply(this, [deferred, resourceName, params, options]); + promise = promise.then(function () { + return _findAll.apply(_this, [resourceName, params, options]); + }); + deferred.resolve(); } catch (err) { deferred.reject(new errors.UnhandledError(err)); } } - return deferred.promise; + return promise; } module.exports = findAll; diff --git a/src/datastore/async_methods/save/index.js b/src/datastore/async_methods/save/index.js index 05073d8..8aec482 100644 --- a/src/datastore/async_methods/save/index.js +++ b/src/datastore/async_methods/save/index.js @@ -1,19 +1,8 @@ var utils = require('utils'), errors = require('errors'), services = require('services'), - PUT = require('../../http').PUT, errorPrefix = 'DS.save(resourceName, id[, options]): '; -function _save(deferred, resource, id, options) { - var _this = this; - var url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name, id); - PUT(url, resource.index[id], null).then(function (data) { - var saved = _this.inject(resource.name, data, options); - resource.saved[id] = utils.updateTimestamp(resource.saved[id]); - deferred.resolve(saved); - }, deferred.reject); -} - /** * @doc method * @id DS.async_methods:save @@ -58,7 +47,8 @@ function _save(deferred, resource, id, options) { * - `{UnhandledError}` */ function save(resourceName, id, options) { - var deferred = $q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise; options = options || {}; @@ -68,33 +58,41 @@ function save(resourceName, id, options) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); } else if (!utils.isObject(options)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); + } else if (!(id in services.store[resourceName].index)) { + deferred.reject(new errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); } else { - var _this = this; + var resource = services.store[resourceName], + _this = this; - try { - var resource = services.store[resourceName]; + promise = promise + .then(function (attrs) { + return services.$q.promisify(resource.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.beforeUpdate)(resourceName, attrs); + }) + .then(function (attrs) { + return _this.PUT(utils.makePath(resource.baseUrl, resource.endpoint, id), attrs, null); + }) + .then(function (data) { + return services.$q.promisify(resource.afterUpdate)(resourceName, data); + }) + .then(function (data) { + var saved = _this.inject(resource.name, data, options); + resource.saved[id] = utils.updateTimestamp(resource.saved[id]); + return saved; + }); - if (resource.schema) { - resource.schema.validate(resource.index[id], function (err) { - if (err) { - deferred.reject(err); - } else { - _save.call(_this, deferred, resource, id, options); - } - }); - } else { - _save.call(_this, deferred, resource, id, options); - } - } catch (err) { - if (!(err instanceof errors.UnhandledError)) { - deferred.reject(new errors.UnhandledError(err)); - } else { - deferred.reject(err); - } - } + deferred.resolve(resource.index[id]); } - return deferred.promise; + return promise; } module.exports = save; diff --git a/src/datastore/index.js b/src/datastore/index.js index e533cdb..729578c 100644 --- a/src/datastore/index.js +++ b/src/datastore/index.js @@ -1,6 +1,5 @@ var utils = require('utils'), errors = require('errors'), - IllegalArgumentError = errors.IllegalArgumentError, services = require('services'), errorPrefix = 'DSProvider.config(options): '; @@ -19,7 +18,8 @@ var utils = require('utils'), * ## Example: * ```js * DSProvider.config({ - * baseUrl: 'http://myapp.com/api' + * baseUrl: 'http://myapp.com/api', + * idAttribute: '_id' * }); * ``` * @@ -33,12 +33,34 @@ function config(options) { options = options || {}; if (!utils.isObject(options)) { - throw new IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); - } else if (!utils.isString(options.baseUrl)) { - throw new IllegalArgumentError(errorPrefix + 'options: Must be an object!', { baseUrl: { actual: typeof options, expected: 'object' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); + } else if ('baseUrl' in options && !utils.isString(options.baseUrl)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.baseUrl: Must be a string!', { baseUrl: { actual: typeof options.baseUrl, expected: 'string' } }); + } else if ('idAttribute' in options && !utils.isString(options.idAttribute)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.idAttribute: Must be a string!', { idAttribute: { actual: typeof options.idAttribute, expected: 'string' } }); + } else if ('mergeStrategy' in options && !utils.isString(options.mergeStrategy)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.mergeStrategy: Must be a string!', { mergeStrategy: { actual: typeof options.mergeStrategy, expected: 'string' } }); + } else if ('beforeValidate' in options && !utils.isFunction(options.beforeValidate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeValidate: Must be a function!', { beforeValidate: { actual: typeof options.beforeValidate, expected: 'function' } }); + } else if ('validate' in options && !utils.isFunction(options.validate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.validate: Must be a function!', { validate: { actual: typeof options.validate, expected: 'function' } }); + } else if ('afterValidate' in options && !utils.isFunction(options.afterValidate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterValidate: Must be a function!', { afterValidate: { actual: typeof options.afterValidate, expected: 'function' } }); + } else if ('beforeCreate' in options && !utils.isFunction(options.beforeCreate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeCreate: Must be a function!', { beforeCreate: { actual: typeof options.beforeCreate, expected: 'function' } }); + } else if ('afterCreate' in options && !utils.isFunction(options.afterCreate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterCreate: Must be a function!', { afterCreate: { actual: typeof options.afterCreate, expected: 'function' } }); + } else if ('beforeUpdate' in options && !utils.isFunction(options.beforeUpdate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeUpdate: Must be a function!', { beforeUpdate: { actual: typeof options.beforeUpdate, expected: 'function' } }); + } else if ('afterUpdate' in options && !utils.isFunction(options.afterUpdate)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterUpdate: Must be a function!', { afterUpdate: { actual: typeof options.afterUpdate, expected: 'function' } }); + } else if ('beforeDestroy' in options && !utils.isFunction(options.beforeDestroy)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeDestroy: Must be a function!', { beforeDestroy: { actual: typeof options.beforeDestroy, expected: 'function' } }); + } else if ('afterDestroy' in options && !utils.isFunction(options.afterDestroy)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options.afterDestroy: Must be a function!', { afterDestroy: { actual: typeof options.afterDestroy, expected: 'function' } }); } - utils.deepMixIn(services.config, options); + services.config = new services.BaseConfig(options); } /** diff --git a/src/datastore/services/index.js b/src/datastore/services/index.js index e31f130..87e2ae4 100644 --- a/src/datastore/services/index.js +++ b/src/datastore/services/index.js @@ -1,6 +1,69 @@ -module.exports = { +function lifecycleNoop(resourceName, attrs, cb) { + cb(null, attrs); +} + +var services = module.exports = { config: { idAttribute: 'id' }, - store: {} + store: {}, + BaseConfig: function (options) { + if ('idAttribute' in options) { + this.idAttribute = options.idAttribute; + } + + if ('baseUrl' in options) { + this.baseUrl = options.baseUrl; + } + + if ('beforeValidate' in options) { + this.beforeValidate = options.beforeValidate; + } + + if ('validate' in options) { + this.validate = options.validate; + } + + if ('afterValidate' in options) { + this.afterValidate = options.afterValidate; + } + + if ('beforeCreate' in options) { + this.beforeCreate = options.beforeCreate; + } + + if ('afterCreate' in options) { + this.afterCreate = options.afterCreate; + } + + if ('beforeUpdate' in options) { + this.beforeUpdate = options.beforeUpdate; + } + + if ('afterUpdate' in options) { + this.afterUpdate = options.afterUpdate; + } + + if ('beforeDestroy' in options) { + this.beforeDestroy = options.beforeDestroy; + } + + if ('afterDestroy' in options) { + this.afterDestroy = options.afterDestroy; + } + } }; + + +services.BaseConfig.prototype.idAttribute = 'id'; +services.BaseConfig.prototype.baseUrl = ''; +services.BaseConfig.prototype.endpoint = ''; +services.BaseConfig.prototype.beforeValidate = lifecycleNoop; +services.BaseConfig.prototype.validate = lifecycleNoop; +services.BaseConfig.prototype.afterValidate = lifecycleNoop; +services.BaseConfig.prototype.beforeCreate = lifecycleNoop; +services.BaseConfig.prototype.afterCreate = lifecycleNoop; +services.BaseConfig.prototype.beforeUpdate = lifecycleNoop; +services.BaseConfig.prototype.afterUpdate = lifecycleNoop; +services.BaseConfig.prototype.beforeDestroy = lifecycleNoop; +services.BaseConfig.prototype.afterDestroy = lifecycleNoop; diff --git a/src/datastore/sync_methods/defineResource/index.js b/src/datastore/sync_methods/defineResource/index.js index 4209f87..a0cf7f8 100644 --- a/src/datastore/sync_methods/defineResource/index.js +++ b/src/datastore/sync_methods/defineResource/index.js @@ -3,6 +3,31 @@ var utils = require('utils'), services = require('services'), errorPrefix = 'DS.defineResource(definition): '; +function Resource(options) { + services.BaseConfig.apply(this, [options]); + + if ('name' in options) { + this.name = options.name; + } + + if ('endpoint' in options) { + this.endpoint = options.endpoint; + } else { + this.endpoint = this.name; + } + + this.collection = []; + this.completedQueries = {}; + this.pendingQueries = {}; + this.index = {}; + this.modified = {}; + this.changes = {}; + this.previous_attributes = {}; + this.saved = {}; + this.observers = {}; + this.collectionModified = 0; +} + /** * @doc method * @id DS.sync_methods:defineResource @@ -61,20 +86,8 @@ function defineResource(definition) { } try { - services.store[definition.name] = definition; - - var resource = services.store[definition.name]; - resource.collection = []; - resource.completedQueries = {}; - resource.pendingQueries = {}; - resource.index = {}; - resource.modified = {}; - resource.changes = {}; - resource.previous_attributes = {}; - resource.saved = {}; - resource.observers = {}; - resource.collectionModified = 0; - resource.idAttribute = resource.idAttribute || services.config.idAttribute || 'id'; + Resource.prototype = services.config; + services.store[definition.name] = new Resource(definition); } catch (err) { delete services.store[definition.name]; throw new errors.UnhandledError(err); diff --git a/src/index.js b/src/index.js index b380cfc..977190d 100644 --- a/src/index.js +++ b/src/index.js @@ -40,7 +40,35 @@ // } // angular.module('jmdobry.binary-heap').provider('BinaryHeap', BinaryHeapProvider); - angular.module('jmdobry.angular-data', ['ng'/*, 'jmdobry.binary-heap'*/]); + angular.module('jmdobry.angular-data', ['ng'/*, 'jmdobry.binary-heap'*/]).config(['$provide', function ($provide) { + $provide.decorator('$q', 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; + }); + }]); angular.module('jmdobry.angular-data').provider('DS', require('./datastore')); })(window, window.angular); diff --git a/test/unit/datastore/sync_methods/filter/index.test.js b/test/unit/datastore/sync_methods/filter/index.test.js index 34091c7..69d5b94 100644 --- a/test/unit/datastore/sync_methods/filter/index.test.js +++ b/test/unit/datastore/sync_methods/filter/index.test.js @@ -69,7 +69,7 @@ describe('DS.filter(resourceName, params[, options])', function () { done(); }); it('should return an empty array if the query has never been made before', function (done) { - $httpBackend.expectGET(new RegExp('\\' + app.baseUrl + '/posts' + '\\?query=.*')).respond(200, [p1]); + $httpBackend.expectGET('http://test.angular-cache.com/posts?query=%7B%22where%22:%7B%22author%22:%7B%22%3D%3D%22:%22John%22%7D%7D%7D').respond(200, [p1]); assert.deepEqual(DS.filter('post', { query: { From 82225b7cf9a0f8950cdeeedda5197d5c4d9267d9 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Wed, 15 Jan 2014 22:38:09 -0700 Subject: [PATCH 3/7] Closes #7. Implemented all lifecycle hooks, including tests. Cleaned up use of promises in async methods. --- dist/angular-data.js | 155 ++++++++++-------- dist/angular-data.min.js | 4 +- karma.start.js | 73 ++++++++- src/datastore/async_methods/destroy/index.js | 37 +++-- src/datastore/async_methods/find/index.js | 53 +++--- src/datastore/async_methods/findAll/index.js | 11 +- src/datastore/async_methods/refresh/index.js | 5 +- src/datastore/async_methods/save/index.js | 2 +- src/datastore/index.js | 19 ++- .../sync_methods/defineResource/index.js | 19 ++- src/datastore/sync_methods/get/index.js | 4 +- src/utils/index.js | 1 + .../async_methods/create/index.test.js | 41 +++++ .../async_methods/destroy/index.test.js | 45 +++++ .../async_methods/find/index.test.js | 82 +++++++++ .../async_methods/save/index.test.js | 69 ++++++++ .../datastore/sync_methods/get/index.test.js | 47 ++++++ 17 files changed, 525 insertions(+), 142 deletions(-) create mode 100644 test/unit/datastore/async_methods/create/index.test.js create mode 100644 test/unit/datastore/async_methods/destroy/index.test.js create mode 100644 test/unit/datastore/async_methods/find/index.test.js create mode 100644 test/unit/datastore/async_methods/save/index.test.js create mode 100644 test/unit/datastore/sync_methods/get/index.test.js diff --git a/dist/angular-data.js b/dist/angular-data.js index 5bd6e01..99dc5b2 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -1,12 +1,3 @@ -/** - * @author Jason Dobry - * @file angular-data.js - * @version 0.5.0 - Homepage - * @copyright (c) 2014 Jason Dobry - * @license MIT - * - * @overview Data store for Angular.js. - */ require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=service.$q.defer();f.store[a]?d.isString(b)||d.isNumber(b)||c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}})):c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));try{var h=f.store[a],i=this,j=d.makePath(h.baseUrl||f.config.baseUrl,h.endpoint||h.name,b);i.DEL(j,null).then(function(){try{i.eject(a,b),c.resolve(b)}catch(d){c.reject(d)}},c.reject)}catch(k){c.reject(new e.UnhandledError(k))}return c.promise}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=f.$q.defer();if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c)){var j=this;try{var k=f.store[a];if(b in k.index&&!c.bypassCache)i.resolve(j.get(a,b));else{var l=d.makePath(k.baseUrl||f.config.baseUrl,k.endpoint||k.name,b),m=null;c.bypassCache&&(m={headers:{"Last-Modified":new Date(k.modified[b])}}),g(l,m).then(function(c){try{j.inject(a,c),i.resolve(j.get(a,b))}catch(d){i.reject(d)}},i.reject)}}catch(n){n instanceof e.UnhandledError?i.reject(n):i.reject(new e.UnhandledError(n))}}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return i.promise}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file +require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isString(b)||d.isNumber(b)){var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeDestroy)(a,b)}).then(function(){return j.DEL(d.makePath(i.baseUrl,i.endpoint,b),null)}).then(function(){return f.$q.promisify(i.afterDestroy)(a,i.index[b])}).then(function(){return j.eject(a,b),b}),c.resolve(i.index[b])}else c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=f.$q.defer(),j=i.promise;if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c))try{var k=f.store[a],l=this;if(c.bypassCache&&delete k.completedQueries[b],!(b in k.completedQueries))return b in k.pendingQueries||(j=k.pendingQueries[b]=g(d.makePath(k.baseUrl,k.endpoint,b),null).then(function(c){return delete k.pendingQueries[b],k.completedQueries[b]=(new Date).getTime(),l.inject(a,c)})),k.pendingQueries[b];i.resolve(l.get(a,b))}catch(m){i.reject(m)}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return j}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file diff --git a/karma.start.js b/karma.start.js index 1709ffd..d46e42d 100644 --- a/karma.start.js +++ b/karma.start.js @@ -1,5 +1,7 @@ // Setup global test variables -var $rootScope, $q, $log, DS, app, $httpBackend, p1, p2, p3, p4; +var $rootScope, $q, $log, DSProvider, DS, app, $httpBackend, p1, p2, p3, p4; + +var lifecycle = {}; // Helper globals var fail = function (msg) { @@ -27,20 +29,66 @@ angular.module('app', ['ng', 'jmdobry.angular-data']); // Setup before each test beforeEach(function (done) { - module('app'); + lifecycle.beforeValidate = function (resourceName, attrs, cb) { + lifecycle.beforeValidate.callCount += 1; + cb(null, attrs); + }; + lifecycle.validate = function (resourceName, attrs, cb) { + lifecycle.validate.callCount += 1; + cb(null, attrs); + }; + lifecycle.afterValidate = function (resourceName, attrs, cb) { + lifecycle.afterValidate.callCount += 1; + cb(null, attrs); + }; + lifecycle.beforeCreate = function (resourceName, attrs, cb) { + lifecycle.beforeCreate.callCount += 1; + cb(null, attrs); + }; + lifecycle.afterCreate = function (resourceName, attrs, cb) { + lifecycle.afterCreate.callCount += 1; + cb(null, attrs); + }; + lifecycle.beforeUpdate = function (resourceName, attrs, cb) { + lifecycle.beforeUpdate.callCount += 1; + cb(null, attrs); + }; + lifecycle.afterUpdate = function (resourceName, attrs, cb) { + lifecycle.afterUpdate.callCount += 1; + cb(null, attrs); + }; + lifecycle.beforeDestroy = function (resourceName, attrs, cb) { + lifecycle.beforeDestroy.callCount += 1; + cb(null, attrs); + }; + lifecycle.afterDestroy = function (resourceName, attrs, cb) { + lifecycle.afterDestroy.callCount += 1; + cb(null, attrs); + }; + module('app', function (_DSProvider_) { + DSProvider = _DSProvider_; + DSProvider.config({ + baseUrl: 'http://test.angular-cache.com', + beforeValidate: lifecycle.beforeValidate, + validate: lifecycle.validate, + afterValidate: lifecycle.afterValidate, + beforeCreate: lifecycle.beforeCreate, + afterCreate: lifecycle.afterCreate, + beforeUpdate: lifecycle.beforeUpdate, + afterUpdate: lifecycle.afterUpdate, + beforeDestroy: lifecycle.beforeDestroy, + afterDestroy: lifecycle.afterDestroy + }); + }); inject(function (_$rootScope_, _$q_, _$httpBackend_, _DS_) { // Setup global mocks $q = _$q_; $rootScope = _$rootScope_; DS = _DS_; $httpBackend = _$httpBackend_; - app = { - baseUrl: 'http://test.angular-cache.com' - }; DS.defineResource({ name: 'post', - endpoint: '/posts', - baseUrl: app.baseUrl + endpoint: '/posts' }); $log = { warn: function () { @@ -61,6 +109,16 @@ beforeEach(function (done) { sinon.spy($log, 'info'); sinon.spy($log, 'error'); sinon.spy($log, 'debug'); + + lifecycle.beforeValidate.callCount = 0; + lifecycle.validate.callCount = 0; + lifecycle.afterValidate.callCount = 0; + lifecycle.beforeCreate.callCount = 0; + lifecycle.afterCreate.callCount = 0; + lifecycle.beforeUpdate.callCount = 0; + lifecycle.afterUpdate.callCount = 0; + lifecycle.beforeDestroy.callCount = 0; + lifecycle.afterDestroy.callCount = 0; }); p1 = { author: 'John', age: 30, id: 5 }; @@ -79,6 +137,7 @@ afterEach(function () { $log.info.restore(); $log.error.restore(); $log.debug.restore(); + $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); }); diff --git a/src/datastore/async_methods/destroy/index.js b/src/datastore/async_methods/destroy/index.js index 0b46b8e..4dfe842 100644 --- a/src/datastore/async_methods/destroy/index.js +++ b/src/datastore/async_methods/destroy/index.js @@ -45,31 +45,36 @@ var utils = require('utils'), * - `{UnhandledError}` */ function destroy(resourceName, id) { - var deferred = service.$q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise; + if (!services.store[resourceName]) { deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); } else if (!utils.isString(id) && !utils.isNumber(id)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); - } - - try { + } else { var resource = services.store[resourceName], - _this = this, - url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name, id); + _this = this; - _this.DEL(url, null).then(function () { - try { + promise = promise + .then(function (attrs) { + return services.$q.promisify(resource.beforeDestroy)(resourceName, attrs); + }) + .then(function () { + return _this.DEL(utils.makePath(resource.baseUrl, resource.endpoint, id), null); + }) + .then(function () { + return services.$q.promisify(resource.afterDestroy)(resourceName, resource.index[id]); + }) + .then(function () { _this.eject(resourceName, id); - deferred.resolve(id); - } catch (err) { - deferred.reject(err); - } - }, deferred.reject); - } catch (err) { - deferred.reject(new errors.UnhandledError(err)); + return id; + }); + + deferred.resolve(resource.index[id]); } - return deferred.promise; + return promise; } module.exports = destroy; diff --git a/src/datastore/async_methods/find/index.js b/src/datastore/async_methods/find/index.js index a5a1fe5..12ac463 100644 --- a/src/datastore/async_methods/find/index.js +++ b/src/datastore/async_methods/find/index.js @@ -1,7 +1,6 @@ var utils = require('utils'), errors = require('errors'), services = require('services'), - GET = require('../../http').GET, errorPrefix = 'DS.find(resourceName, id[, options]): '; /** @@ -49,7 +48,9 @@ var utils = require('utils'), * - `{UnhandledError}` */ function find(resourceName, id, options) { - var deferred = services.$q.defer(); + var deferred = services.$q.defer(), + promise = deferred.promise; + options = options || {}; if (!services.store[resourceName]) { @@ -59,43 +60,35 @@ function find(resourceName, id, options) { } else if (!utils.isObject(options)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); } else { - var _this = this; - try { - var resource = services.store[resourceName]; + var resource = services.store[resourceName], + _this = this; - if (id in resource.index && !options.bypassCache) { - deferred.resolve(_this.get(resourceName, id)); - } else { - var url = utils.makePath(resource.baseUrl || services.config.baseUrl, resource.endpoint || resource.name, id), - config = null; + if (options.bypassCache) { + delete resource.completedQueries[id]; + } - if (options.bypassCache) { - config = { - headers: { - 'Last-Modified': new Date(resource.modified[id]) - } - }; + if (!(id in resource.completedQueries)) { + if (!(id in resource.pendingQueries)) { + promise = resource.pendingQueries[id] = _this.GET(utils.makePath(resource.baseUrl, resource.endpoint, id), null) + .then(function (data) { + // Query is no longer pending + delete resource.pendingQueries[id]; + resource.completedQueries[id] = new Date().getTime(); + return _this.inject(resourceName, data); + }); } - GET(url, config).then(function (data) { - try { - _this.inject(resourceName, data); - deferred.resolve(_this.get(resourceName, id)); - } catch (err) { - deferred.reject(err); - } - }, deferred.reject); - } - } catch (err) { - if (!(err instanceof errors.UnhandledError)) { - deferred.reject(new errors.UnhandledError(err)); + + return resource.pendingQueries[id]; } else { - deferred.reject(err); + deferred.resolve(_this.get(resourceName, id)); } + } catch (err) { + deferred.reject(err); } } - return deferred.promise; + return promise; } module.exports = find; diff --git a/src/datastore/async_methods/findAll/index.js b/src/datastore/async_methods/findAll/index.js index b91a99f..e24da21 100644 --- a/src/datastore/async_methods/findAll/index.js +++ b/src/datastore/async_methods/findAll/index.js @@ -1,7 +1,6 @@ var utils = require('utils'), errors = require('errors'), services = require('services'), - GET = require('../../http').GET, errorPrefix = 'DS.findAll(resourceName, params[, options]): '; function processResults(data, resourceName, queryHash) { @@ -28,9 +27,8 @@ function processResults(data, resourceName, queryHash) { function _findAll(resourceName, params, options) { var resource = services.store[resourceName], - _this = this; - - var queryHash = utils.toJson(params); + _this = this, + queryHash = utils.toJson(params); if (options.bypassCache) { delete resource.completedQueries[queryHash]; @@ -39,10 +37,10 @@ function _findAll(resourceName, params, options) { if (!(queryHash in resource.completedQueries)) { // This particular query has never been completed - if (!resource.pendingQueries[queryHash]) { + if (!(queryHash in resource.pendingQueries)) { // This particular query has never even been made - resource.pendingQueries[queryHash] = GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) + resource.pendingQueries[queryHash] = _this.GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) .then(function (data) { try { return processResults.apply(_this, [data, resourceName, queryHash]); @@ -51,6 +49,7 @@ function _findAll(resourceName, params, options) { } }); } + return resource.pendingQueries[queryHash]; } else { return this.filter(resourceName, params, options); diff --git a/src/datastore/async_methods/refresh/index.js b/src/datastore/async_methods/refresh/index.js index e11633b..51b6c57 100644 --- a/src/datastore/async_methods/refresh/index.js +++ b/src/datastore/async_methods/refresh/index.js @@ -1,7 +1,6 @@ var utils = require('utils'), errors = require('errors'), services = require('services'), - PUT = require('../../http').PUT, errorPrefix = 'DS.refresh(resourceName, id): '; /** @@ -61,13 +60,13 @@ function refresh(resourceName, id, options) { if (!services.store[resourceName]) { throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); } else if (!utils.isString(id) && !utils.isNumber(id)) { - throw new errors.IllegalArgumentError('DS.refresh(resourceName, id): id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); } else if (!utils.isObject(options)) { throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); } if (id in services.store[resourceName].index) { - return this.find(resourceName, id, true); + return this.find(resourceName, id, { bypassCache: true }); } else { return false; } diff --git a/src/datastore/async_methods/save/index.js b/src/datastore/async_methods/save/index.js index 8aec482..e104a08 100644 --- a/src/datastore/async_methods/save/index.js +++ b/src/datastore/async_methods/save/index.js @@ -57,7 +57,7 @@ function save(resourceName, id, options) { } else if (!utils.isString(id) && !utils.isNumber(id)) { deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); } else if (!utils.isObject(options)) { - deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); + deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); } else if (!(id in services.store[resourceName].index)) { deferred.reject(new errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); } else { diff --git a/src/datastore/index.js b/src/datastore/index.js index 729578c..b8651db 100644 --- a/src/datastore/index.js +++ b/src/datastore/index.js @@ -19,7 +19,11 @@ var utils = require('utils'), * ```js * DSProvider.config({ * baseUrl: 'http://myapp.com/api', - * idAttribute: '_id' + * idAttribute: '_id', + * validate: function (resourceName, attrs, cb) { + * console.log('looks good to me'); + * cb(null, attrs); + * } * }); * ``` * @@ -27,7 +31,18 @@ var utils = require('utils'), * * - `{IllegalArgumentError}` * - * @param {object} options Configuration for the data store. + * @param {object} options Global configuration for the data store. Properties: + * - `{string=}` - `baseUrl` - The default base url to be used by the data store. Can be overridden via `DS.defineResource`. + * - `{string=}` - `idAttribute` - The default property that specifies the primary key of an object. Default: `"id"`. + * - `{function=}` - `beforeValidate` - Global lifecycle hook. Signature: `beforeValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `validate` - Global lifecycle hook. Signature: `validate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterValidate` - Global lifecycle hook. Signature: `afterValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `beforeCreate` - Global lifecycle hook. Signature: `beforeCreate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterCreate` - Global lifecycle hook. Signature: `afterCreate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `beforeUpdate` - Global lifecycle hook. Signature: `beforeUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterUpdate` - Global lifecycle hook. Signature: `afterUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `beforeDestroy` - Global lifecycle hook. Signature: `beforeDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterDestroy` - Global lifecycle hook. Signature: `afterDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. */ function config(options) { options = options || {}; diff --git a/src/datastore/sync_methods/defineResource/index.js b/src/datastore/sync_methods/defineResource/index.js index a0cf7f8..0c8aef9 100644 --- a/src/datastore/sync_methods/defineResource/index.js +++ b/src/datastore/sync_methods/defineResource/index.js @@ -28,6 +28,8 @@ function Resource(options) { this.collectionModified = 0; } +Resource.prototype = services.config; + /** * @doc method * @id DS.sync_methods:defineResource @@ -48,9 +50,9 @@ function Resource(options) { * idAttribute: '_id', * endpoint: '/documents * baseUrl: 'http://myapp.com/api', - * validate: function (attrs, options, cb) { + * beforeDestroy: function (resourceName attrs, cb) { * console.log('looks good to me'); - * cb(null); + * cb(null, attrs); * } * }); * ``` @@ -66,8 +68,16 @@ function Resource(options) { * - `{string}` - `name` - The name by which this resource will be identified. * - `{string="id"}` - `idAttribute` - The attribute that specifies the primary key for this resource. * - `{string=}` - `endpoint` - The attribute that specifies the primary key for this resource. Default is the value of `name`. - * - `{string="/"}` - `baseUrl` - The url relative to which all AJAX requests will be made. - * - `{function=}` - `validate` - The validation function to be executed before create operations. + * - `{string=}` - `baseUrl` - The url relative to which all AJAX requests will be made. + * - `{function=}` - `beforeValidate` - Lifecycle hook. Overrides global. Signature: `beforeValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `validate` - Lifecycle hook. Overrides global. Signature: `validate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterValidate` - Lifecycle hook. Overrides global. Signature: `afterValidate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `beforeCreate` - Lifecycle hook. Overrides global. Signature: `beforeCreate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterCreate` - Lifecycle hook. Overrides global. Signature: `afterCreate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `beforeUpdate` - Lifecycle hook. Overrides global. Signature: `beforeUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterUpdate` - Lifecycle hook. Overrides global. Signature: `afterUpdate(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `beforeDestroy` - Lifecycle hook. Overrides global. Signature: `beforeDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. + * - `{function=}` - `afterDestroy` - Lifecycle hook. Overrides global. Signature: `afterDestroy(resourceName, attrs, cb)`. Callback signature: `cb(err, attrs)`. */ function defineResource(definition) { if (utils.isString(definition)) { @@ -86,7 +96,6 @@ function defineResource(definition) { } try { - Resource.prototype = services.config; services.store[definition.name] = new Resource(definition); } catch (err) { delete services.store[definition.name]; diff --git a/src/datastore/sync_methods/get/index.js b/src/datastore/sync_methods/get/index.js index 77e9ea3..b313f89 100644 --- a/src/datastore/sync_methods/get/index.js +++ b/src/datastore/sync_methods/get/index.js @@ -48,7 +48,9 @@ function get(resourceName, id, options) { try { // cache miss, request resource from server if (!(id in services.store[resourceName].index) && options.loadFromServer) { - this.find(resourceName, id); + this.find(resourceName, id).then(null, function (err) { + throw err; + }); } // return resource from cache diff --git a/src/utils/index.js b/src/utils/index.js index 4f24371..8491f3e 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -3,6 +3,7 @@ module.exports = { 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'), diff --git a/test/unit/datastore/async_methods/create/index.test.js b/test/unit/datastore/async_methods/create/index.test.js new file mode 100644 index 0000000..6cf8355 --- /dev/null +++ b/test/unit/datastore/async_methods/create/index.test.js @@ -0,0 +1,41 @@ +describe('DS.create(resourceName, attrs)', function () { + var errorPrefix = 'DS.create(resourceName, attrs): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + DS.create('does not exist', 5).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.RuntimeError); + assert.equal(err.message, errorPrefix + 'does not exist is not a registered resource!'); + }); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + DS.create('post', key).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.IllegalArgumentError); + assert.equal(err.message, errorPrefix + 'attrs: Must be an object!'); + }); + }); + + done(); + }); + it('should create an item and save it to the server', function (done) { + $httpBackend.expectPOST('http://test.angular-cache.com/posts').respond(200, p1); + + DS.create('post', { author: 'John', age: 30 }).then(function (post) { + assert.deepEqual(post, p1, 'post 5 should have been created'); + }, function (err) { + console.error(err.stack); + fail('should not have rejected'); + }); + + $httpBackend.flush(); + + assert.equal(lifecycle.beforeCreate.callCount, 1, 'beforeCreate should have been called'); + assert.equal(lifecycle.afterCreate.callCount, 1, 'afterCreate should have been called'); + assert.deepEqual(DS.get('post', 5), p1); + + done(); + }); +}); diff --git a/test/unit/datastore/async_methods/destroy/index.test.js b/test/unit/datastore/async_methods/destroy/index.test.js new file mode 100644 index 0000000..ade9bf3 --- /dev/null +++ b/test/unit/datastore/async_methods/destroy/index.test.js @@ -0,0 +1,45 @@ +describe('DS.destroy(resourceName, id)', function () { + var errorPrefix = 'DS.destroy(resourceName, id): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + DS.destroy('does not exist', 5).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.RuntimeError); + assert.equal(err.message, errorPrefix + 'does not exist is not a registered resource!'); + }); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + DS.destroy('post', key).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.IllegalArgumentError); + assert.equal(err.message, errorPrefix + 'id: Must be a string or a number!'); + }); + }); + + done(); + }); + it('should delete an item from the data store', function (done) { + $httpBackend.expectDELETE('http://test.angular-cache.com/posts/5').respond(200, 5); + + DS.inject('post', p1); + + DS.destroy('post', 5).then(function (id) { + assert.equal(id, 5, 'post 5 should have been deleted'); + }, function (err) { + console.error(err.stack); + fail('should not have rejected'); + }); + + $httpBackend.flush(); + + assert.equal(lifecycle.beforeDestroy.callCount, 1, 'beforeDestroy should have been called'); + assert.equal(lifecycle.afterDestroy.callCount, 1, 'afterDestroy should have been called'); + assert.isUndefined(DS.get('post', 5)); + assert.equal(DS.lastModified('post', 5), 0); + assert.equal(DS.lastSaved('post', 5), 0); + + done(); + }); +}); diff --git a/test/unit/datastore/async_methods/find/index.test.js b/test/unit/datastore/async_methods/find/index.test.js new file mode 100644 index 0000000..030d7bf --- /dev/null +++ b/test/unit/datastore/async_methods/find/index.test.js @@ -0,0 +1,82 @@ +describe('DS.find(resourceName, id[, options]): ', function () { + var errorPrefix = 'DS.find(resourceName, id[, options]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + DS.find('does not exist', 5).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.RuntimeError); + assert.equal(err.message, errorPrefix + 'does not exist is not a registered resource!'); + }); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + DS.find('post', key).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.IllegalArgumentError); + assert.equal(err.message, errorPrefix + 'id: Must be a string or a number!'); + }); + }); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + if (key) { + DS.find('post', 5, key).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.IllegalArgumentError); + assert.equal(err.message, errorPrefix + 'options: Must be an object!'); + }); + } + }); + + done(); + }); + it('should get an item from the server', function (done) { + $httpBackend.expectGET('http://test.angular-cache.com/posts/5').respond(200, p1); + + DS.find('post', 5).then(function (post) { + assert.deepEqual(post, p1); + }, function (err) { + console.error(err.stack); + fail('Should not have rejected!'); + }); + + assert.isUndefined(DS.get('post', 5), 'The post should not be in the store yet'); + + // Should have no effect because there is already a pending query + DS.find('post', 5).then(function (post) { + assert.deepEqual(post, p1); + }, function (err) { + console.error(err.stack); + fail('Should not have rejected!'); + }); + + $httpBackend.flush(); + + assert.deepEqual(DS.get('post', 5), p1, 'The post is now in the store'); + assert.isNumber(DS.lastModified('post', 5)); + assert.isNumber(DS.lastSaved('post', 5)); + + // Should not make a request because the request was already completed + DS.find('post', 5).then(function (post) { + assert.deepEqual(post, p1); + }, function (err) { + console.error(err.stack); + fail('Should not have rejected!'); + }); + + $httpBackend.expectGET('http://test.angular-cache.com/posts/5').respond(200, p1); + + // Should make a request because loadFromServer is set to true + DS.find('post', 5, { bypassCache: true }).then(function (post) { + assert.deepEqual(post, p1); + }, function (err) { + console.error(err.stack); + fail('Should not have rejected!'); + }); + + $httpBackend.flush(); + + done(); + }); +}); diff --git a/test/unit/datastore/async_methods/save/index.test.js b/test/unit/datastore/async_methods/save/index.test.js new file mode 100644 index 0000000..2f269a1 --- /dev/null +++ b/test/unit/datastore/async_methods/save/index.test.js @@ -0,0 +1,69 @@ +describe('DS.save(resourceName, id[, options])', function () { + var errorPrefix = 'DS.save(resourceName, id[, options]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + DS.save('does not exist', 5).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.RuntimeError); + assert.equal(err.message, errorPrefix + 'does not exist is not a registered resource!'); + }); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + DS.save('post', key).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.IllegalArgumentError); + assert.equal(err.message, errorPrefix + 'id: Must be a string or a number!'); + }); + }); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + if (key) { + DS.save('post', 5, key).then(function () { + fail('should have rejected'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.IllegalArgumentError); + assert.equal(err.message, errorPrefix + 'options: Must be an object!'); + }); + } + }); + + done(); + }); + it('should save an item to the server', function (done) { + $httpBackend.expectPUT('http://test.angular-cache.com/posts/5').respond(200, p1); + + DS.inject('post', p1); + + var initialModified = DS.lastModified('post', 5), + initialSaved = DS.lastSaved('post', 5); + + p1.author = 'Jake'; + + DS.save('post', 5).then(function (post) { + assert.deepEqual(post, p1, 'post 5 should have been saved'); + assert.equal(post.author, 'Jake'); + }, function (err) { + console.error(err.stack); + fail('should not have rejected'); + }); + + $httpBackend.flush(); + + assert.equal(lifecycle.beforeUpdate.callCount, 1, 'beforeUpdate should have been called'); + assert.equal(lifecycle.afterUpdate.callCount, 1, 'afterUpdate should have been called'); + assert.deepEqual(DS.get('post', 5), p1); + assert.notEqual(DS.lastModified('post', 5), initialModified); + assert.notEqual(DS.lastSaved('post', 5), initialSaved); + + DS.save('post', 6).then(function () { + fail('should not have succeeded'); + }, function (err) { + assert.isTrue(err instanceof DS.errors.RuntimeError); + assert.equal(err.message, errorPrefix + 'id: "6" not found!'); + }); + + done(); + }); +}); diff --git a/test/unit/datastore/sync_methods/get/index.test.js b/test/unit/datastore/sync_methods/get/index.test.js new file mode 100644 index 0000000..f3eb412 --- /dev/null +++ b/test/unit/datastore/sync_methods/get/index.test.js @@ -0,0 +1,47 @@ +describe('DS.get(resourceName, id[, options])', function () { + var errorPrefix = 'DS.get(resourceName, id[, options]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throw(function () { + DS.get('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + assert.throw(function () { + DS.get('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + }); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + if (key) { + assert.throw(function () { + DS.get('post', 5, key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options: Must be an object!'); + } + }); + + done(); + }); + it('should return undefined if the query has never been made before', function (done) { + + assert.isUndefined(DS.get('post', 5), 'should be undefined'); + + done(); + }); + it('should return undefined and send the query to the server if the query has never been made before and loadFromServer is set to true', function (done) { + $httpBackend.expectGET('http://test.angular-cache.com/posts/5').respond(200, p1); + + assert.isUndefined(DS.get('post', 5, { loadFromServer: true }), 'should be undefined'); + + // There should only be one GET request, so this should have no effect. + assert.isUndefined(DS.get('post', 5, { loadFromServer: true }), 'should be undefined'); + + $httpBackend.flush(); + + assert.deepEqual(DS.get('post', 5), p1, 'p1 should now be in the store'); + assert.isNumber(DS.lastModified('post', 5)); + assert.isNumber(DS.lastSaved('post', 5)); + + done(); + }); +}); From 9777e5bc03423826c6a702e8dd2c6728052f3812 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Thu, 16 Jan 2014 07:14:39 -0700 Subject: [PATCH 4/7] Finished integration tests for async methods. #15 --- dist/angular-data.js | 52 +++++++++------- dist/angular-data.min.js | 4 +- karma.conf.js | 2 +- src/datastore/async_methods/findAll/index.js | 2 +- src/datastore/async_methods/refresh/index.js | 14 +++-- .../async_methods/create/index.test.js | 0 .../async_methods/destroy/index.test.js | 0 .../async_methods/find/index.test.js | 2 +- .../async_methods/refresh/index.test.js | 62 +++++++++++++++++++ .../async_methods/save/index.test.js | 0 .../sync_methods/filter/index.test.js | 0 .../datastore/sync_methods/get/index.test.js | 0 12 files changed, 105 insertions(+), 33 deletions(-) rename test/{unit => integration}/datastore/async_methods/create/index.test.js (100%) rename test/{unit => integration}/datastore/async_methods/destroy/index.test.js (100%) rename test/{unit => integration}/datastore/async_methods/find/index.test.js (97%) create mode 100644 test/integration/datastore/async_methods/refresh/index.test.js rename test/{unit => integration}/datastore/async_methods/save/index.test.js (100%) rename test/{unit => integration}/datastore/sync_methods/filter/index.test.js (100%) rename test/{unit => integration}/datastore/sync_methods/get/index.test.js (100%) diff --git a/dist/angular-data.js b/dist/angular-data.js index 99dc5b2..4c7d071 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -1,3 +1,12 @@ +/** + * @author Jason Dobry + * @file angular-data.js + * @version 0.5.0 - Homepage + * @copyright (c) 2014 Jason Dobry + * @license MIT + * + * @overview Data store for Angular.js. + */ require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;oc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isString(b)||d.isNumber(b)){var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeDestroy)(a,b)}).then(function(){return j.DEL(d.makePath(i.baseUrl,i.endpoint,b),null)}).then(function(){return f.$q.promisify(i.afterDestroy)(a,i.index[b])}).then(function(){return j.eject(a,b),b}),c.resolve(i.index[b])}else c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var i=f.$q.defer(),j=i.promise;if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c))try{var k=f.store[a],l=this;if(c.bypassCache&&delete k.completedQueries[b],!(b in k.completedQueries))return b in k.pendingQueries||(j=k.pendingQueries[b]=g(d.makePath(k.baseUrl,k.endpoint,b),null).then(function(c){return delete k.pendingQueries[b],k.completedQueries[b]=(new Date).getTime(),l.inject(a,c)})),k.pendingQueries[b];i.resolve(l.get(a,b))}catch(m){i.reject(m)}else i.reject(new e.IllegalArgumentError(h+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else i.reject(new e.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else i.reject(new e.RuntimeError(h+a+" is not a registered resource!"));return j}var d=a("utils"),e=a("errors"),f=a("services"),g=a("../../http").GET,h="DS.find(resourceName, id[, options]): ";b.exports=c},{"../../http":34,errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file +require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isString(b)||d.isNumber(b)){var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeDestroy)(a,b)}).then(function(){return j.DEL(d.makePath(i.baseUrl,i.endpoint,b),null)}).then(function(){return f.$q.promisify(i.afterDestroy)(a,i.index[b])}).then(function(){return j.eject(a,b),b}),c.resolve(i.index[b])}else c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var h=f.$q.defer(),i=h.promise;if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c))try{var j=f.store[a],k=this;if(c.bypassCache&&delete j.completedQueries[b],!(b in j.completedQueries))return b in j.pendingQueries||(i=j.pendingQueries[b]=k.GET(d.makePath(j.baseUrl,j.endpoint,b),null).then(function(c){return delete j.pendingQueries[b],j.completedQueries[b]=(new Date).getTime(),k.inject(a,c)})),j.pendingQueries[b];h.resolve(k.get(a,b))}catch(l){h.reject(l)}else h.reject(new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else h.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else h.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return i}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.find(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file diff --git a/karma.conf.js b/karma.conf.js index 80993d9..3db8161 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -22,7 +22,7 @@ module.exports = function (config) { 'bower_components/angular/angular.js', 'bower_components/angular-mocks/angular-mocks.js', 'dist/angular-data.js', - 'test/**/*.js', + 'test/integration/**/*.js', 'karma.start.js' ], diff --git a/src/datastore/async_methods/findAll/index.js b/src/datastore/async_methods/findAll/index.js index e24da21..66d7b89 100644 --- a/src/datastore/async_methods/findAll/index.js +++ b/src/datastore/async_methods/findAll/index.js @@ -18,7 +18,7 @@ function processResults(data, resourceName, queryHash) { } // Update the data store's index for this resource - resource.index = utils.toLookup(resource.collection, resource.idAttribute || services.config.idAttribute || 'id'); + resource.index = utils.toLookup(resource.collection, resource.idAttribute); // Update modified timestamp of collection resource.collectionModified = utils.updateTimestamp(resource.collectionModified); diff --git a/src/datastore/async_methods/refresh/index.js b/src/datastore/async_methods/refresh/index.js index 51b6c57..c4e0a25 100644 --- a/src/datastore/async_methods/refresh/index.js +++ b/src/datastore/async_methods/refresh/index.js @@ -1,7 +1,7 @@ var utils = require('utils'), errors = require('errors'), services = require('services'), - errorPrefix = 'DS.refresh(resourceName, id): '; + errorPrefix = 'DS.refresh(resourceName, id[, options]): '; /** * @doc method @@ -63,12 +63,14 @@ function refresh(resourceName, id, options) { throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); } else if (!utils.isObject(options)) { throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); - } - - if (id in services.store[resourceName].index) { - return this.find(resourceName, id, { bypassCache: true }); } else { - return false; + options.bypassCache = true; + + if (id in services.store[resourceName].index) { + return this.find(resourceName, id, options); + } else { + return false; + } } } diff --git a/test/unit/datastore/async_methods/create/index.test.js b/test/integration/datastore/async_methods/create/index.test.js similarity index 100% rename from test/unit/datastore/async_methods/create/index.test.js rename to test/integration/datastore/async_methods/create/index.test.js diff --git a/test/unit/datastore/async_methods/destroy/index.test.js b/test/integration/datastore/async_methods/destroy/index.test.js similarity index 100% rename from test/unit/datastore/async_methods/destroy/index.test.js rename to test/integration/datastore/async_methods/destroy/index.test.js diff --git a/test/unit/datastore/async_methods/find/index.test.js b/test/integration/datastore/async_methods/find/index.test.js similarity index 97% rename from test/unit/datastore/async_methods/find/index.test.js rename to test/integration/datastore/async_methods/find/index.test.js index 030d7bf..7b2bb97 100644 --- a/test/unit/datastore/async_methods/find/index.test.js +++ b/test/integration/datastore/async_methods/find/index.test.js @@ -67,7 +67,7 @@ describe('DS.find(resourceName, id[, options]): ', function () { $httpBackend.expectGET('http://test.angular-cache.com/posts/5').respond(200, p1); - // Should make a request because loadFromServer is set to true + // Should make a request because bypassCache is set to true DS.find('post', 5, { bypassCache: true }).then(function (post) { assert.deepEqual(post, p1); }, function (err) { diff --git a/test/integration/datastore/async_methods/refresh/index.test.js b/test/integration/datastore/async_methods/refresh/index.test.js new file mode 100644 index 0000000..88d99e2 --- /dev/null +++ b/test/integration/datastore/async_methods/refresh/index.test.js @@ -0,0 +1,62 @@ +describe('DS.refresh(resourceName, id[, options]): ', function () { + var errorPrefix = 'DS.refresh(resourceName, id[, options]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.refresh('does not exist', 5); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + assert.throws(function () { + DS.refresh('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + }); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + if (key) { + assert.throws(function () { + DS.refresh('post', 5, key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options: Must be an object!'); + } + }); + + done(); + }); + it('should get an item from the server', function (done) { + + // Should do nothing because the data isn't in the store + assert.isFalse(DS.refresh('post', 5)); + + assert.isUndefined(DS.get('post', 5), 'The post should not be in the store yet'); + + DS.inject('post', p1); + assert.deepEqual(DS.get('post', 5), p1, 'The post is now in the store'); + + var initialLastModified = DS.lastModified('post', 5); + + $httpBackend.expectGET('http://test.angular-cache.com/posts/5').respond(200, { author: 'John', age: 31, id: 5 }); + + // Should refresh the item that's in the store + DS.refresh('post', 5).then(function (post) { + assert.deepEqual(post, { author: 'John', age: 31, id: 5 }); + }, function (err) { + console.error(err.stack); + fail('Should not have rejected!'); + }); + + // Should have no effect because the request is already pending + DS.refresh('post', 5).then(function (post) { + assert.deepEqual(post, { author: 'John', age: 31, id: 5 }); + }, function (err) { + console.error(err.stack); + fail('Should not have rejected!'); + }); + + $httpBackend.flush(); + + assert.deepEqual(DS.get('post', 5), { author: 'John', age: 31, id: 5 }, 'The post has been refreshed'); + assert.notEqual(DS.lastModified('post', 5), initialLastModified); + + done(); + }); +}); diff --git a/test/unit/datastore/async_methods/save/index.test.js b/test/integration/datastore/async_methods/save/index.test.js similarity index 100% rename from test/unit/datastore/async_methods/save/index.test.js rename to test/integration/datastore/async_methods/save/index.test.js diff --git a/test/unit/datastore/sync_methods/filter/index.test.js b/test/integration/datastore/sync_methods/filter/index.test.js similarity index 100% rename from test/unit/datastore/sync_methods/filter/index.test.js rename to test/integration/datastore/sync_methods/filter/index.test.js diff --git a/test/unit/datastore/sync_methods/get/index.test.js b/test/integration/datastore/sync_methods/get/index.test.js similarity index 100% rename from test/unit/datastore/sync_methods/get/index.test.js rename to test/integration/datastore/sync_methods/get/index.test.js From 989e2564f71eed6103b12d0e88bc23851b2e15f7 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Thu, 16 Jan 2014 12:33:01 -0700 Subject: [PATCH 5/7] Wrote several test for sync methods. #15 --- dist/angular-data.js | 100 ++++++++++-------- dist/angular-data.min.js | 4 +- karma.start.js | 32 +----- .../sync_methods/defineResource/index.js | 2 + .../sync_methods/hasChanges/index.js | 10 +- src/datastore/sync_methods/inject/index.js | 88 ++++++++------- .../sync_methods/defineResource/index.test.js | 90 ++++++++++++++++ .../sync_methods/filter/index.test.js | 14 +-- .../datastore/sync_methods/get/index.test.js | 6 +- .../sync_methods/hasChanges/index.test.js | 37 +++++++ .../sync_methods/inject/index.test.js | 74 +++++++++++++ 11 files changed, 332 insertions(+), 125 deletions(-) create mode 100644 test/integration/datastore/sync_methods/defineResource/index.test.js create mode 100644 test/integration/datastore/sync_methods/hasChanges/index.test.js create mode 100644 test/integration/datastore/sync_methods/inject/index.test.js diff --git a/dist/angular-data.js b/dist/angular-data.js index 4c7d071..71cfecc 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -3044,6 +3044,8 @@ function defineResource(definition) { throw new errors.IllegalArgumentError(errorPrefix + 'definition.name: Must be a string!', { definition: { name: { actual: typeof definition.name, expected: 'string' } } }); } else if (definition.idAttribute && !utils.isString(definition.idAttribute)) { throw new errors.IllegalArgumentError(errorPrefix + 'definition.idAttribute: Must be a string!', { definition: { idAttribute: { actual: typeof definition.idAttribute, expected: 'string' } } }); + } else if (definition.endpoint && !utils.isString(definition.endpoint)) { + throw new errors.IllegalArgumentError(errorPrefix + 'definition.endpoint: Must be a string!', { definition: { endpoint: { actual: typeof definition.endpoint, expected: 'string' } } }); } else if (services.store[definition.name]) { throw new errors.RuntimeError(errorPrefix + definition.name + ' is already registered!'); } @@ -3448,9 +3450,9 @@ var utils = require('utils'), errorPrefix = 'DS.hasChanges(resourceName, id): '; function diffIsEmpty(diff) { - return utils.isEmpty(diff.added) && + return !(utils.isEmpty(diff.added) && utils.isEmpty(diff.removed) && - utils.isEmpty(diff.changed); + utils.isEmpty(diff.changed)); } /** @@ -3495,7 +3497,11 @@ function hasChanges(resourceName, id) { try { // return resource from cache - return diffIsEmpty(services.store[resourceName].changes[id]); + if (id in services.store[resourceName].changes) { + return diffIsEmpty(services.store[resourceName].changes[id]); + } else { + return false; + } } catch (err) { throw new errors.UnhandledError(err); } @@ -3625,43 +3631,52 @@ var utils = require('utils'), function _inject(resource, attrs) { var _this = this; + function _react(added, removed, changed, getOldValueFn) { + try { + var innerId = getOldValueFn(resource.idAttribute); + + resource.changes[innerId] = utils.diffObjectFromOldObject(resource.index[innerId], resource.previous_attributes[innerId]); + resource.modified[innerId] = utils.updateTimestamp(resource.modified[innerId]); + resource.collectionModified = utils.updateTimestamp(resource.collectionModified); + + if (resource.idAttribute in changed) { + services.$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 "' + resource.name + + '" resource is now in an undefined (probably broken) state.'); + } + } catch (err) { + throw new errors.UnhandledError(err); + } + } + if (utils.isArray(attrs)) { for (var i = 0; i < attrs.length; i++) { _inject.call(_this, resource, attrs[i]); } } else { - var id = attrs[resource.idAttribute || 'id']; - - if (!(id in resource.index)) { - resource.index[id] = {}; - resource.previous_attributes[id] = {}; + if (!(resource.idAttribute in attrs)) { + throw new errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!'); + } else { + var id = attrs[resource.idAttribute]; - utils.deepMixIn(resource.index[id], attrs); - utils.deepMixIn(resource.previous_attributes[id], attrs); + if (!(id in resource.index)) { + resource.index[id] = {}; + resource.previous_attributes[id] = {}; - resource.collection.push(resource.index[id]); + utils.deepMixIn(resource.index[id], attrs); + utils.deepMixIn(resource.previous_attributes[id], attrs); - resource.observers[id] = new observe.ObjectObserver(resource.index[id], function (added, removed, changed, getOldValueFn) { - try { - var innerId = getOldValueFn(resource.idAttribute || 'id'); + resource.collection.push(resource.index[id]); - if (resource.index[innerId][resource.idAttribute || 'id'] != innerId) { - resource.index[innerId][resource.idAttribute || 'id'] = innerId; - services.$log.error('You cannot change the primary key of an object! Reverting change to primary key.'); - } + resource.observers[id] = new observe.ObjectObserver(resource.index[id], _react); - resource.changes[innerId] = utils.diffObjectFromOldObject(resource.index[innerId], resource.previous_attributes[innerId]); - resource.modified[innerId] = utils.updateTimestamp(resource.modified[innerId]); - resource.collectionModified = utils.updateTimestamp(resource.collectionModified); - } catch (err) { - throw new errors.UnhandledError(err); - } - }); - - resource.observers[id].deliver(); - } else { - utils.deepMixIn(resource.index[id], attrs); - resource.observers[id].deliver(); + _react({}, {}, {}, function () { + return id; + }); + } else { + utils.deepMixIn(resource.index[id], attrs); + resource.observers[id].deliver(); + } } } } @@ -3726,22 +3741,21 @@ function inject(resourceName, attrs, options) { var resource = services.store[resourceName], _this = this; - var idAttribute = resource.idAttribute || 'id'; - if (!attrs[idAttribute]) { - throw new errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute` in the resource definition!'); - } else { - try { - if (!services.$rootScope.$$phase) { - services.$rootScope.$apply(function () { - _inject.apply(_this, [services.store[resourceName], attrs]); - }); - } else { + try { + if (!services.$rootScope.$$phase) { + services.$rootScope.$apply(function () { _inject.apply(_this, [services.store[resourceName], attrs]); - } - } catch (err) { + }); + } else { + _inject.apply(_this, [services.store[resourceName], attrs]); + } + return attrs; + } catch (err) { + if (!(err instanceof errors.RuntimeError)) { throw new errors.UnhandledError(err); + } else { + throw err; } - return resource.index[attrs[idAttribute]]; } } diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index 6006479..bcfa5d8 100644 --- a/dist/angular-data.min.js +++ b/dist/angular-data.min.js @@ -7,5 +7,5 @@ * * @overview Data store for Angular.js. */ -require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isString(b)||d.isNumber(b)){var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeDestroy)(a,b)}).then(function(){return j.DEL(d.makePath(i.baseUrl,i.endpoint,b),null)}).then(function(){return f.$q.promisify(i.afterDestroy)(a,i.index[b])}).then(function(){return j.eject(a,b),b}),c.resolve(i.index[b])}else c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var h=f.$q.defer(),i=h.promise;if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c))try{var j=f.store[a],k=this;if(c.bypassCache&&delete j.completedQueries[b],!(b in j.completedQueries))return b in j.pendingQueries||(i=j.pendingQueries[b]=k.GET(d.makePath(j.baseUrl,j.endpoint,b),null).then(function(c){return delete j.pendingQueries[b],j.completedQueries[b]=(new Date).getTime(),k.inject(a,c)})),j.pendingQueries[b];h.resolve(k.get(a,b))}catch(l){h.reject(l)}else h.reject(new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else h.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else h.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return i}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.find(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed)}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return c(g.store[a].changes[b])}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){var d=this;if(e.isArray(b))for(var i=0;i=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file +require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isString(b)||d.isNumber(b)){var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeDestroy)(a,b)}).then(function(){return j.DEL(d.makePath(i.baseUrl,i.endpoint,b),null)}).then(function(){return f.$q.promisify(i.afterDestroy)(a,i.index[b])}).then(function(){return j.eject(a,b),b}),c.resolve(i.index[b])}else c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var h=f.$q.defer(),i=h.promise;if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c))try{var j=f.store[a],k=this;if(c.bypassCache&&delete j.completedQueries[b],!(b in j.completedQueries))return b in j.pendingQueries||(i=j.pendingQueries[b]=k.GET(d.makePath(j.baseUrl,j.endpoint,b),null).then(function(c){return delete j.pendingQueries[b],j.completedQueries[b]=(new Date).getTime(),k.inject(a,c)})),j.pendingQueries[b];h.resolve(k.get(a,b))}catch(l){h.reject(l)}else h.reject(new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else h.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else h.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return i}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.find(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return!(e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed))}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return b in g.store[a].changes?c(g.store[a].changes[b]):!1}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){function d(b,c,d,h){try{var i=h(a.idAttribute);a.changes[i]=e.diffObjectFromOldObject(a.index[i],a.previous_attributes[i]),a.modified[i]=e.updateTimestamp(a.modified[i]),a.collectionModified=e.updateTimestamp(a.collectionModified),a.idAttribute in d&&g.$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 \""+a.name+'" resource is now in an undefined (probably broken) state.')}catch(j){throw new f.UnhandledError(j)}}var j=this;if(e.isArray(b))for(var k=0;k=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file diff --git a/karma.start.js b/karma.start.js index d46e42d..5be18c8 100644 --- a/karma.start.js +++ b/karma.start.js @@ -11,6 +11,8 @@ var fail = function (msg) { }], TYPES_EXCEPT_STRING_OR_ARRAY = [123, 123.123, null, undefined, {}, true, false, function () { }], + TYPES_EXCEPT_STRING_OR_OBJECT = [123, 123.123, null, undefined, [], true, false, function () { + }], TYPES_EXCEPT_ARRAY = ['string', 123, 123.123, null, undefined, {}, true, false, function () { }], TYPES_EXCEPT_STRING_OR_NUMBER = [null, undefined, {}, [], true, false, function () { @@ -80,7 +82,7 @@ beforeEach(function (done) { afterDestroy: lifecycle.afterDestroy }); }); - inject(function (_$rootScope_, _$q_, _$httpBackend_, _DS_) { + inject(function (_$rootScope_, _$q_, _$httpBackend_, _DS_, _$log_) { // Setup global mocks $q = _$q_; $rootScope = _$rootScope_; @@ -90,25 +92,7 @@ beforeEach(function (done) { name: 'post', endpoint: '/posts' }); - $log = { - warn: function () { - }, - log: function () { - }, - info: function () { - }, - error: function () { - }, - debug: function () { - } - }; - - // Setup global spies - sinon.spy($log, 'warn'); - sinon.spy($log, 'log'); - sinon.spy($log, 'info'); - sinon.spy($log, 'error'); - sinon.spy($log, 'debug'); + $log = _$log_; lifecycle.beforeValidate.callCount = 0; lifecycle.validate.callCount = 0; @@ -131,13 +115,7 @@ beforeEach(function (done) { // Clean up after each test afterEach(function () { - // Tear down global spies - $log.warn.restore(); - $log.log.restore(); - $log.info.restore(); - $log.error.restore(); - $log.debug.restore(); - $httpBackend.verifyNoOutstandingExpectation(); $httpBackend.verifyNoOutstandingRequest(); + $log.reset(); }); diff --git a/src/datastore/sync_methods/defineResource/index.js b/src/datastore/sync_methods/defineResource/index.js index 0c8aef9..e78bdd2 100644 --- a/src/datastore/sync_methods/defineResource/index.js +++ b/src/datastore/sync_methods/defineResource/index.js @@ -91,6 +91,8 @@ function defineResource(definition) { throw new errors.IllegalArgumentError(errorPrefix + 'definition.name: Must be a string!', { definition: { name: { actual: typeof definition.name, expected: 'string' } } }); } else if (definition.idAttribute && !utils.isString(definition.idAttribute)) { throw new errors.IllegalArgumentError(errorPrefix + 'definition.idAttribute: Must be a string!', { definition: { idAttribute: { actual: typeof definition.idAttribute, expected: 'string' } } }); + } else if (definition.endpoint && !utils.isString(definition.endpoint)) { + throw new errors.IllegalArgumentError(errorPrefix + 'definition.endpoint: Must be a string!', { definition: { endpoint: { actual: typeof definition.endpoint, expected: 'string' } } }); } else if (services.store[definition.name]) { throw new errors.RuntimeError(errorPrefix + definition.name + ' is already registered!'); } diff --git a/src/datastore/sync_methods/hasChanges/index.js b/src/datastore/sync_methods/hasChanges/index.js index c79ce90..4c33549 100644 --- a/src/datastore/sync_methods/hasChanges/index.js +++ b/src/datastore/sync_methods/hasChanges/index.js @@ -4,9 +4,9 @@ var utils = require('utils'), errorPrefix = 'DS.hasChanges(resourceName, id): '; function diffIsEmpty(diff) { - return utils.isEmpty(diff.added) && + return !(utils.isEmpty(diff.added) && utils.isEmpty(diff.removed) && - utils.isEmpty(diff.changed); + utils.isEmpty(diff.changed)); } /** @@ -51,7 +51,11 @@ function hasChanges(resourceName, id) { try { // return resource from cache - return diffIsEmpty(services.store[resourceName].changes[id]); + if (id in services.store[resourceName].changes) { + return diffIsEmpty(services.store[resourceName].changes[id]); + } else { + return false; + } } catch (err) { throw new errors.UnhandledError(err); } diff --git a/src/datastore/sync_methods/inject/index.js b/src/datastore/sync_methods/inject/index.js index 11cf21a..fc960b0 100644 --- a/src/datastore/sync_methods/inject/index.js +++ b/src/datastore/sync_methods/inject/index.js @@ -7,43 +7,52 @@ var utils = require('utils'), function _inject(resource, attrs) { var _this = this; + function _react(added, removed, changed, getOldValueFn) { + try { + var innerId = getOldValueFn(resource.idAttribute); + + resource.changes[innerId] = utils.diffObjectFromOldObject(resource.index[innerId], resource.previous_attributes[innerId]); + resource.modified[innerId] = utils.updateTimestamp(resource.modified[innerId]); + resource.collectionModified = utils.updateTimestamp(resource.collectionModified); + + if (resource.idAttribute in changed) { + services.$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 "' + resource.name + + '" resource is now in an undefined (probably broken) state.'); + } + } catch (err) { + throw new errors.UnhandledError(err); + } + } + if (utils.isArray(attrs)) { for (var i = 0; i < attrs.length; i++) { _inject.call(_this, resource, attrs[i]); } } else { - var id = attrs[resource.idAttribute || 'id']; - - if (!(id in resource.index)) { - resource.index[id] = {}; - resource.previous_attributes[id] = {}; - - utils.deepMixIn(resource.index[id], attrs); - utils.deepMixIn(resource.previous_attributes[id], attrs); + if (!(resource.idAttribute in attrs)) { + throw new errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!'); + } else { + var id = attrs[resource.idAttribute]; - resource.collection.push(resource.index[id]); + if (!(id in resource.index)) { + resource.index[id] = {}; + resource.previous_attributes[id] = {}; - resource.observers[id] = new observe.ObjectObserver(resource.index[id], function (added, removed, changed, getOldValueFn) { - try { - var innerId = getOldValueFn(resource.idAttribute || 'id'); + utils.deepMixIn(resource.index[id], attrs); + utils.deepMixIn(resource.previous_attributes[id], attrs); - if (resource.index[innerId][resource.idAttribute || 'id'] != innerId) { - resource.index[innerId][resource.idAttribute || 'id'] = innerId; - services.$log.error('You cannot change the primary key of an object! Reverting change to primary key.'); - } + resource.collection.push(resource.index[id]); - resource.changes[innerId] = utils.diffObjectFromOldObject(resource.index[innerId], resource.previous_attributes[innerId]); - resource.modified[innerId] = utils.updateTimestamp(resource.modified[innerId]); - resource.collectionModified = utils.updateTimestamp(resource.collectionModified); - } catch (err) { - throw new errors.UnhandledError(err); - } - }); + resource.observers[id] = new observe.ObjectObserver(resource.index[id], _react); - resource.observers[id].deliver(); - } else { - utils.deepMixIn(resource.index[id], attrs); - resource.observers[id].deliver(); + _react({}, {}, {}, function () { + return id; + }); + } else { + utils.deepMixIn(resource.index[id], attrs); + resource.observers[id].deliver(); + } } } } @@ -108,22 +117,21 @@ function inject(resourceName, attrs, options) { var resource = services.store[resourceName], _this = this; - var idAttribute = resource.idAttribute || 'id'; - if (!attrs[idAttribute]) { - throw new errors.RuntimeError(errorPrefix + 'attrs: Must contain the property specified by `idAttribute` in the resource definition!'); - } else { - try { - if (!services.$rootScope.$$phase) { - services.$rootScope.$apply(function () { - _inject.apply(_this, [services.store[resourceName], attrs]); - }); - } else { + try { + if (!services.$rootScope.$$phase) { + services.$rootScope.$apply(function () { _inject.apply(_this, [services.store[resourceName], attrs]); - } - } catch (err) { + }); + } else { + _inject.apply(_this, [services.store[resourceName], attrs]); + } + return attrs; + } catch (err) { + if (!(err instanceof errors.RuntimeError)) { throw new errors.UnhandledError(err); + } else { + throw err; } - return resource.index[attrs[idAttribute]]; } } diff --git a/test/integration/datastore/sync_methods/defineResource/index.test.js b/test/integration/datastore/sync_methods/defineResource/index.test.js new file mode 100644 index 0000000..62368c5 --- /dev/null +++ b/test/integration/datastore/sync_methods/defineResource/index.test.js @@ -0,0 +1,90 @@ +describe('DS.defineResource(definition)', function () { + var errorPrefix = 'DS.defineResource(definition): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + angular.forEach(TYPES_EXCEPT_STRING_OR_OBJECT, function (key) { + if (!angular.isArray(key)) { + assert.throws(function () { + DS.defineResource(key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'definition: Must be an object!'); + } + }); + + angular.forEach(TYPES_EXCEPT_STRING, function (key) { + assert.throws(function () { + DS.defineResource({ name: key }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'definition.name: Must be a string!'); + }); + + angular.forEach(TYPES_EXCEPT_STRING, function (key) { + if (key) { + assert.throws(function () { + DS.defineResource({ name: 'name', idAttribute: key }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'definition.idAttribute: Must be a string!'); + } + }); + + angular.forEach(TYPES_EXCEPT_STRING, function (key) { + if (key) { + assert.throws(function () { + DS.defineResource({ name: 'name', endpoint: key }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'definition.endpoint: Must be a string!'); + } + }); + + DS.defineResource('name'); + + assert.throws(function () { + DS.defineResource('name'); + }, DS.errors.RuntimeError, errorPrefix + 'name is already registered!'); + + assert.doesNotThrow(function () { + DS.defineResource('new resource'); + }, 'Should not throw'); + + done(); + }); + + it('should correctly register a resource', function (done) { + + var callCount = 0, + test = { + validate: function (resourceName, attrs, cb) { + callCount += 1; + cb(null, attrs); + } + }; + + DS.defineResource({ + name: 'comment', + baseUrl: 'hello/', + validate: test.validate + }); + + assert.doesNotThrow(function () { + assert.isUndefined(DS.get('comment', 5), 'should be undefined'); + }); + + // Should override global baseUrl + $httpBackend.expectGET('hello/comment/1').respond(200, { name: 'Sally', id: 1 }); + + assert.isUndefined(DS.get('comment', 1, { loadFromServer: true }), 'should be undefined'); + + $httpBackend.flush(); + + assert.deepEqual(DS.get('comment', 1), { name: 'Sally', id: 1 }); + + $httpBackend.expectPOST('hello/comment').respond(200, { name: 'John', id: 2 }); + + DS.create('comment', { name: 'John' }).then(function (comment) { + assert.deepEqual(comment, { name: 'John', id: 2 }); + }); + + $httpBackend.flush(); + + assert.equal(callCount, 1, 'overridden validate should have been called once'); + assert.equal(lifecycle.validate.callCount, 0, 'global validate should not have been called'); + + done(); + }); +}); diff --git a/test/integration/datastore/sync_methods/filter/index.test.js b/test/integration/datastore/sync_methods/filter/index.test.js index 69d5b94..6de9ce3 100644 --- a/test/integration/datastore/sync_methods/filter/index.test.js +++ b/test/integration/datastore/sync_methods/filter/index.test.js @@ -2,18 +2,18 @@ describe('DS.filter(resourceName, params[, options])', function () { var errorPrefix = 'DS.filter(resourceName, params[, options]): '; it('should throw an error when method pre-conditions are not met', function (done) { - assert.throw(function () { + assert.throws(function () { DS.filter('does not exist', {}); }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { - assert.throw(function () { + assert.throws(function () { DS.filter('post', key); }, DS.errors.IllegalArgumentError, errorPrefix + 'params: Must be an object!'); }); angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { - assert.throw(function () { + assert.throws(function () { DS.filter('post', key); }, DS.errors.IllegalArgumentError, errorPrefix + 'params: Must be an object!'); }); @@ -22,7 +22,7 @@ describe('DS.filter(resourceName, params[, options])', function () { angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { if (key) { - assert.throw(function () { + assert.throws(function () { DS.filter('post', { query: { where: key @@ -34,7 +34,7 @@ describe('DS.filter(resourceName, params[, options])', function () { angular.forEach(TYPES_EXCEPT_STRING_OR_ARRAY, function (key) { if (key) { - assert.throw(function () { + assert.throws(function () { DS.filter('post', { query: { orderBy: key @@ -46,7 +46,7 @@ describe('DS.filter(resourceName, params[, options])', function () { angular.forEach(TYPES_EXCEPT_STRING_OR_ARRAY, function (key) { if (key) { - assert.throw(function () { + assert.throws(function () { DS.filter('post', { query: { orderBy: [key] @@ -58,7 +58,7 @@ describe('DS.filter(resourceName, params[, options])', function () { angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { if (key) { - assert.throw(function () { + assert.throws(function () { DS.filter('post', {}, key); }, DS.errors.IllegalArgumentError, errorPrefix + 'options: Must be an object!'); } diff --git a/test/integration/datastore/sync_methods/get/index.test.js b/test/integration/datastore/sync_methods/get/index.test.js index f3eb412..1ef3337 100644 --- a/test/integration/datastore/sync_methods/get/index.test.js +++ b/test/integration/datastore/sync_methods/get/index.test.js @@ -2,19 +2,19 @@ describe('DS.get(resourceName, id[, options])', function () { var errorPrefix = 'DS.get(resourceName, id[, options]): '; it('should throw an error when method pre-conditions are not met', function (done) { - assert.throw(function () { + assert.throws(function () { DS.get('does not exist', {}); }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { - assert.throw(function () { + assert.throws(function () { DS.get('post', key); }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); }); angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { if (key) { - assert.throw(function () { + assert.throws(function () { DS.get('post', 5, key); }, DS.errors.IllegalArgumentError, errorPrefix + 'options: Must be an object!'); } diff --git a/test/integration/datastore/sync_methods/hasChanges/index.test.js b/test/integration/datastore/sync_methods/hasChanges/index.test.js new file mode 100644 index 0000000..29574bd --- /dev/null +++ b/test/integration/datastore/sync_methods/hasChanges/index.test.js @@ -0,0 +1,37 @@ +describe('DS.hasChanges(resourceName, id)', function () { + var errorPrefix = 'DS.hasChanges(resourceName, id): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.hasChanges('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + assert.throws(function () { + DS.hasChanges('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + }); + + done(); + }); + it('should return false if the item is not in the store', function (done) { + + assert.isFalse(DS.hasChanges('post', 5)); + done(); + }); + it('should return undefined and send the query to the server if the query has never been made before and loadFromServer is set to true', function (done) { + + DS.inject('post', p1); + + assert.isFalse(DS.hasChanges('post', 5)); + + var post = DS.get('post', 5); + post.author = 'Jake'; + + DS.digest(); + + assert.isTrue(DS.hasChanges('post', 5)); + + done(); + }); +}); diff --git a/test/integration/datastore/sync_methods/inject/index.test.js b/test/integration/datastore/sync_methods/inject/index.test.js new file mode 100644 index 0000000..f17937c --- /dev/null +++ b/test/integration/datastore/sync_methods/inject/index.test.js @@ -0,0 +1,74 @@ +describe('DS.inject(resourceName, attrs[, options])', function () { + var errorPrefix = 'DS.inject(resourceName, attrs[, options]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.inject('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + assert.throws(function () { + DS.inject('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'attrs: Must be an object or an array!'); + }); + + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + if (key) { + assert.throws(function () { + DS.inject('post', {}, key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options: Must be an object!'); + } + }); + + assert.throws(function () { + DS.inject('post', {}); + }, DS.errors.RuntimeError, errorPrefix + 'attrs: Must contain the property specified by `idAttribute`!'); + + done(); + }); + it('should inject an item into the store', function (done) { + + assert.equal(DS.lastModified('post', 5), 0); + assert.doesNotThrow(function () { + assert.deepEqual(DS.inject('post', p1), p1); + }); + assert.notEqual(DS.lastModified('post', 5), 0); + assert.isNumber(DS.lastModified('post', 5)); + + done(); + }); + it('should inject an item into the store', function (done) { + + assert.equal(DS.lastModified('post', 5), 0); + assert.doesNotThrow(function () { + assert.deepEqual(DS.inject('post', p1), p1); + }); + assert.notEqual(DS.lastModified('post', 5), 0); + assert.isNumber(DS.lastModified('post', 5)); + + var post = DS.get('post', 5); + + post.id = 10; + + DS.digest(); + + assert.deepEqual('Doh! You just changed the primary key of an object! ' + + 'I don\'t know how to handle this yet, so your data for the "post' + + '" resource is now in an undefined (probably broken) state.', $log.error.logs[0][0]); + + done(); + }); + it('should inject multiple items into the store', function (done) { + + assert.doesNotThrow(function () { + assert.deepEqual(DS.inject('post', [p1, p2, p3, p4]), [p1, p2, p3, p4]); + }); + + assert.deepEqual(DS.get('post', 5), p1); + assert.deepEqual(DS.get('post', 6), p2); + assert.deepEqual(DS.get('post', 7), p3); + assert.deepEqual(DS.get('post', 8), p4); + + done(); + }); +}); From 6a06611e49812833c13da275539f1bdc4616c974 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Thu, 16 Jan 2014 15:58:26 -0700 Subject: [PATCH 6/7] Finished integration tests. --- Gruntfile.js | 2 +- dist/angular-data.js | 1755 +++++------------ dist/angular-data.min.js | 4 +- lib/observe-js.js | 468 +++++ src/datastore/async_methods/save/index.js | 1 + src/datastore/sync_methods/changes/index.js | 2 +- src/datastore/sync_methods/eject/index.js | 27 +- .../sync_methods/lastModified/index.js | 2 +- src/datastore/sync_methods/lastSaved/index.js | 2 +- test/integration/datastore/index.test.js | 115 ++ .../sync_methods/changes/index.test.js | 39 + .../sync_methods/eject/index.test.js | 66 + .../sync_methods/hasChanges/index.test.js | 2 +- .../sync_methods/lastModified/index.test.js | 79 + .../sync_methods/lastSaved/index.test.js | 64 + .../sync_methods/previous/index.test.js | 48 + 16 files changed, 1428 insertions(+), 1248 deletions(-) create mode 100644 lib/observe-js.js create mode 100644 test/integration/datastore/index.test.js create mode 100644 test/integration/datastore/sync_methods/changes/index.test.js create mode 100644 test/integration/datastore/sync_methods/eject/index.test.js create mode 100644 test/integration/datastore/sync_methods/lastModified/index.test.js create mode 100644 test/integration/datastore/sync_methods/lastSaved/index.test.js create mode 100644 test/integration/datastore/sync_methods/previous/index.test.js diff --git a/Gruntfile.js b/Gruntfile.js index cbdb4b5..9d8410b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -73,7 +73,7 @@ module.exports = function (grunt) { // TODO: There's got to be a better way to consume observe-js without it polluting the global space options: { alias: [ - 'node_modules/observe-js/src/observe.js:observejs', + 'lib/observe-js.js:observejs', 'src/datastore/services/index.js:services', 'src/errors/index.js:errors', 'src/utils/index.js:utils' diff --git a/dist/angular-data.js b/dist/angular-data.js index 71cfecc..e70ee40 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -7,7 +7,479 @@ * * @overview Data store for Angular.js. */ -require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o= 0 && this.arr[i+1] === this.isObserved) + return; + + if (i < 0) { + i = this.arr.length; + this.arr[i] = obj; + Object.observe(obj, this.callback); + } + + this.arr[i+1] = this.isObserved; + this.observe(Object.getPrototypeOf(obj)); + }, + + cleanup: function() { + var i = 0, j = 0; + var isObserved = this.isObserved; + while(j < this.arr.length) { + var obj = this.arr[j]; + if (this.arr[j + 1] == isObserved) { + if (i < j) { + this.arr[i] = obj; + this.arr[i + 1] = isObserved; + } + i += 2; + } else { + Object.unobserve(obj, this.callback); + } + j += 2; + } + + this.arr.length = i; + } + }; + + var knownRecordTypes = { + 'new': true, + 'updated': true, + 'deleted': true + }; + + function diffObjectFromChangeRecords(object, changeRecords, oldValues) { + var added = {}; + var removed = {}; + + for (var i = 0; i < changeRecords.length; i++) { + var record = changeRecords[i]; + if (!knownRecordTypes[record.type]) { + console.error('Unknown changeRecord type: ' + record.type); + console.error(record); + continue; + } + + if (!(record.name in oldValues)) + oldValues[record.name] = record.oldValue; + + if (record.type == 'updated') + continue; + + if (record.type == 'new') { + if (record.name in removed) + delete removed[record.name]; + else + added[record.name] = true; + + continue; + } + + // type = 'deleted' + if (record.name in added) { + delete added[record.name]; + delete oldValues[record.name]; + } else { + removed[record.name] = true; + } + } + + for (var prop in added) + added[prop] = object[prop]; + + for (var prop in removed) + removed[prop] = undefined; + + var changed = {}; + for (var prop in oldValues) { + if (prop in added || prop in removed) + continue; + + var newValue = object[prop]; + if (oldValues[prop] !== newValue) + changed[prop] = newValue; + } + + return { + added: added, + removed: removed, + changed: changed + }; + } + + global.Observer = Observer; + global.Observer.hasObjectObserve = hasObserve; + global.ObjectObserver = ObjectObserver; +})((exports.Number = { isNaN: window.isNaN }) ? exports : exports); + +},{}],"observejs":[function(require,module,exports){ +module.exports=require('u+GZEJ'); +},{}],3:[function(require,module,exports){ var indexOf = require('./indexOf'); /** @@ -19,7 +491,7 @@ var indexOf = require('./indexOf'); module.exports = contains; -},{"./indexOf":3}],2:[function(require,module,exports){ +},{"./indexOf":5}],4:[function(require,module,exports){ var makeIterator = require('../function/makeIterator_'); /** @@ -47,7 +519,7 @@ var makeIterator = require('../function/makeIterator_'); -},{"../function/makeIterator_":9}],3:[function(require,module,exports){ +},{"../function/makeIterator_":11}],5:[function(require,module,exports){ /** @@ -77,7 +549,7 @@ var makeIterator = require('../function/makeIterator_'); module.exports = indexOf; -},{}],4:[function(require,module,exports){ +},{}],6:[function(require,module,exports){ var filter = require('./filter'); function isValidString(val) { @@ -96,7 +568,7 @@ var filter = require('./filter'); module.exports = join; -},{"./filter":2}],5:[function(require,module,exports){ +},{"./filter":4}],7:[function(require,module,exports){ var arrSlice = Array.prototype.slice; @@ -112,7 +584,7 @@ var filter = require('./filter'); -},{}],6:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ /** @@ -169,7 +641,7 @@ var filter = require('./filter'); -},{}],7:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ var isFunction = require('../lang/isFunction'); /** @@ -199,7 +671,7 @@ var isFunction = require('../lang/isFunction'); module.exports = toLookup; -},{"../lang/isFunction":13}],8:[function(require,module,exports){ +},{"../lang/isFunction":15}],10:[function(require,module,exports){ /** @@ -213,7 +685,7 @@ var isFunction = require('../lang/isFunction'); -},{}],9:[function(require,module,exports){ +},{}],11:[function(require,module,exports){ var identity = require('./identity'); var prop = require('./prop'); var deepMatches = require('../object/deepMatches'); @@ -249,7 +721,7 @@ var deepMatches = require('../object/deepMatches'); -},{"../object/deepMatches":18,"./identity":8,"./prop":10}],10:[function(require,module,exports){ +},{"../object/deepMatches":20,"./identity":10,"./prop":12}],12:[function(require,module,exports){ /** @@ -265,7 +737,7 @@ var deepMatches = require('../object/deepMatches'); -},{}],11:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ var isKind = require('./isKind'); /** */ @@ -275,7 +747,7 @@ var isKind = require('./isKind'); module.exports = isArray; -},{"./isKind":14}],12:[function(require,module,exports){ +},{"./isKind":16}],14:[function(require,module,exports){ var forOwn = require('../object/forOwn'); var isArray = require('./isArray'); @@ -301,7 +773,7 @@ var isArray = require('./isArray'); -},{"../object/forOwn":21,"./isArray":11}],13:[function(require,module,exports){ +},{"../object/forOwn":23,"./isArray":13}],15:[function(require,module,exports){ var isKind = require('./isKind'); /** */ @@ -311,7 +783,7 @@ var isKind = require('./isKind'); module.exports = isFunction; -},{"./isKind":14}],14:[function(require,module,exports){ +},{"./isKind":16}],16:[function(require,module,exports){ var kindOf = require('./kindOf'); /** * Check if value is from a specific "kind". @@ -322,7 +794,7 @@ var kindOf = require('./kindOf'); module.exports = isKind; -},{"./kindOf":16}],15:[function(require,module,exports){ +},{"./kindOf":18}],17:[function(require,module,exports){ /** @@ -337,7 +809,7 @@ var kindOf = require('./kindOf'); -},{}],16:[function(require,module,exports){ +},{}],18:[function(require,module,exports){ var _rKind = /^\[object (.*)\]$/, @@ -359,7 +831,7 @@ var kindOf = require('./kindOf'); module.exports = kindOf; -},{}],17:[function(require,module,exports){ +},{}],19:[function(require,module,exports){ /** @@ -374,7 +846,7 @@ var kindOf = require('./kindOf'); -},{}],18:[function(require,module,exports){ +},{}],20:[function(require,module,exports){ var forOwn = require('./forOwn'); var isArray = require('../lang/isArray'); @@ -431,7 +903,7 @@ var isArray = require('../lang/isArray'); -},{"../lang/isArray":11,"./forOwn":21}],19:[function(require,module,exports){ +},{"../lang/isArray":13,"./forOwn":23}],21:[function(require,module,exports){ var forOwn = require('./forOwn'); var isPlainObject = require('../lang/isPlainObject'); @@ -467,7 +939,7 @@ var isPlainObject = require('../lang/isPlainObject'); -},{"../lang/isPlainObject":15,"./forOwn":21}],20:[function(require,module,exports){ +},{"../lang/isPlainObject":17,"./forOwn":23}],22:[function(require,module,exports){ var _hasDontEnumBug, @@ -531,7 +1003,7 @@ var isPlainObject = require('../lang/isPlainObject'); -},{}],21:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ var hasOwn = require('./hasOwn'); var forIn = require('./forIn'); @@ -552,7 +1024,7 @@ var forIn = require('./forIn'); -},{"./forIn":20,"./hasOwn":22}],22:[function(require,module,exports){ +},{"./forIn":22,"./hasOwn":24}],24:[function(require,module,exports){ /** @@ -566,7 +1038,7 @@ var forIn = require('./forIn'); -},{}],23:[function(require,module,exports){ +},{}],25:[function(require,module,exports){ var join = require('../array/join'); var slice = require('../array/slice'); @@ -583,7 +1055,7 @@ var slice = require('../array/slice'); module.exports = makePath; -},{"../array/join":4,"../array/slice":5}],24:[function(require,module,exports){ +},{"../array/join":6,"../array/slice":7}],26:[function(require,module,exports){ var toString = require('../lang/toString'); /** * "Safer" String.toUpperCase() @@ -595,1194 +1067,7 @@ var toString = require('../lang/toString'); module.exports = upperCase; -},{"../lang/toString":17}],"observejs":[function(require,module,exports){ -module.exports=require('q+M0EE'); -},{}],"q+M0EE":[function(require,module,exports){ -var global=typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {};// Copyright 2012 Google Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -(function(global) { - 'use strict'; - - function detectObjectObserve() { - if (typeof Object.observe !== 'function' || - typeof Array.observe !== 'function') { - return false; - } - - var gotSplice = false; - function callback(records) { - if (records[0].type === 'splice' && records[1].type === 'splice') - gotSplice = true; - } - - var test = [0]; - Array.observe(test, callback); - test[1] = 1; - test.length = 0; - Object.deliverChangeRecords(callback); - return gotSplice; - } - - var hasObserve = detectObjectObserve(); - - var hasEval = false; - try { - var f = new Function('', 'return true;'); - hasEval = f(); - } catch (ex) { - } - - function isIndex(s) { - return +s === s >>> 0; - } - - function toNumber(s) { - return +s; - } - - function isObject(obj) { - return obj === Object(obj); - } - - var numberIsNaN = global.Number.isNaN || function isNaN(value) { - return typeof value === 'number' && global.isNaN(value); - } - - function areSameValue(left, right) { - if (left === right) - return left !== 0 || 1 / left === 1 / right; - if (numberIsNaN(left) && numberIsNaN(right)) - return true; - - return left !== left && right !== right; - } - - var createObject = ('__proto__' in {}) ? - function(obj) { return obj; } : - function(obj) { - var proto = obj.__proto__; - if (!proto) - return obj; - var newObject = Object.create(proto); - Object.getOwnPropertyNames(obj).forEach(function(name) { - Object.defineProperty(newObject, name, - Object.getOwnPropertyDescriptor(obj, name)); - }); - return newObject; - }; - - var identStart = '[\$_a-zA-Z]'; - var identPart = '[\$_a-zA-Z0-9]'; - var ident = identStart + '+' + identPart + '*'; - var elementIndex = '(?:[0-9]|[1-9]+[0-9]+)'; - var identOrElementIndex = '(?:' + ident + '|' + elementIndex + ')'; - var path = '(?:' + identOrElementIndex + ')(?:\\.' + identOrElementIndex + ')*'; - var pathRegExp = new RegExp('^' + path + '$'); - - function isPathValid(s) { - if (typeof s != 'string') - return false; - s = s.replace(/\s/g, ''); - - if (s == '') - return true; - - if (s[0] == '.') - return false; - - return pathRegExp.test(s); - } - - // TODO(rafaelw): Make simple LRU cache - var pathCache = {}; - - function getPath(str) { - var path = pathCache[str]; - if (path) - return path; - if (!isPathValid(str)) - return; - var path = new Path(str); - pathCache[str] = path; - return path; - } - - function Path(s) { - if (s.trim() == '') - return this; - - if (isIndex(s)) { - this.push(String(s)); - return this; - } - - s.split(/\./).filter(function(part) { - return part; - }).forEach(function(part) { - this.push(part); - }, this); - - if (hasEval && this.length) { - this.getValueFrom = this.compiledGetValueFromFn(); - } - } - - Path.prototype = createObject({ - __proto__: [], - - toString: function() { - return this.join('.'); - }, - - getValueFrom: function(obj, allValues) { - for (var i = 0; i < this.length; i++) { - if (obj === undefined || obj === null) - return; - obj = obj[this[i]]; - } - - return obj; - }, - - getValueFromObserved: function(obj, observedSet) { - observedSet.reset(); - for (var i = 0; i < this.length; i++) { - if (obj === undefined || obj === null) { - observedSet.cleanup(); - return; - } - observedSet.observe(obj); - obj = obj[this[i]]; - } - - return obj; - }, - - compiledGetValueFromFn: function() { - var accessors = this.map(function(ident) { - return isIndex(ident) ? '["' + ident + '"]' : '.' + ident; - }); - - var str = ''; - var pathString = 'obj'; - str += 'if (obj !== null && obj !== undefined'; - var i = 0; - for (; i < (this.length - 1); i++) { - var ident = this[i]; - pathString += accessors[i]; - str += ' &&\n ' + pathString + ' !== null && ' + - pathString + ' !== undefined'; - } - str += ')\n'; - - pathString += accessors[i]; - - str += ' return ' + pathString + ';\nelse\n return undefined;'; - return new Function('obj', str); - }, - - setValueFrom: function(obj, value) { - if (!this.length) - return false; - - for (var i = 0; i < this.length - 1; i++) { - if (obj === undefined || obj === null) - return false; - obj = obj[this[i]]; - } - - if (obj === undefined || obj === null) - return false; - - obj[this[this.length - 1]] = value; - return true; - } - }); - - var MAX_DIRTY_CHECK_CYCLES = 1000; - - function dirtyCheck(observer) { - var cycles = 0; - while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check()) { - observer.report(); - cycles++; - } - } - - function objectIsEmpty(object) { - for (var prop in object) - return false; - return true; - } - - function diffIsEmpty(diff) { - return objectIsEmpty(diff.added) && - objectIsEmpty(diff.removed) && - objectIsEmpty(diff.changed); - } - - function diffObjectFromOldObject(object, oldObject) { - var added = {}; - var removed = {}; - var changed = {}; - var oldObjectHas = {}; - - 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; - } - - for (var prop in object) { - if (prop in oldObject) - continue; - - added[prop] = object[prop]; - } - - if (Array.isArray(object) && object.length !== oldObject.length) - changed.length = object.length; - - return { - added: added, - removed: removed, - changed: changed - }; - } - - function copyObject(object, opt_copy) { - var copy = opt_copy || (Array.isArray(object) ? [] : {}); - for (var prop in object) { - copy[prop] = object[prop]; - }; - if (Array.isArray(object)) - copy.length = object.length; - return copy; - } - - function Observer(object, callback, target, token) { - this.closed = false; - this.object = object; - this.callback = callback; - // TODO(rafaelw): Hold this.target weakly when WeakRef is available. - this.target = target; - this.token = token; - this.reporting = true; - if (hasObserve) { - var self = this; - this.boundInternalCallback = function(records) { - self.internalCallback(records); - }; - } - - addToAll(this); - this.connect(); - this.sync(true); - } - - Observer.prototype = { - internalCallback: function(records) { - if (this.closed) - return; - if (this.reporting && this.check(records)) { - this.report(); - if (this.testingResults) - this.testingResults.anyChanged = true; - } - }, - - close: function() { - if (this.closed) - return; - if (this.object && typeof this.object.unobserved === 'function') - this.object.unobserved(); - - this.disconnect(); - this.object = undefined; - this.closed = true; - }, - - deliver: function(testingResults) { - if (this.closed) - return; - if (hasObserve) { - this.testingResults = testingResults; - Object.deliverChangeRecords(this.boundInternalCallback); - this.testingResults = undefined; - } else { - dirtyCheck(this); - } - }, - - report: function() { - if (!this.reporting) - return; - - this.sync(false); - this.reportArgs.push(this.token); - this.invokeCallback(this.reportArgs); - this.reportArgs = undefined; - }, - - invokeCallback: function(args) { - try { - this.callback.apply(this.target, args); - } catch (ex) { - Observer._errorThrownDuringCallback = true; - console.error('Exception caught during observer callback: ' + ex); - } - }, - - reset: function() { - if (this.closed) - return; - - if (hasObserve) { - this.reporting = false; - Object.deliverChangeRecords(this.boundInternalCallback); - this.reporting = true; - } - - this.sync(true); - } - } - - var collectObservers = !hasObserve || global.forceCollectObservers; - var allObservers; - Observer._allObserversCount = 0; - - if (collectObservers) { - allObservers = []; - } - - function addToAll(observer) { - if (!collectObservers) - return; - - allObservers.push(observer); - Observer._allObserversCount++; - } - - var runningMicrotaskCheckpoint = false; - - var hasDebugForceFullDelivery = typeof Object.deliverAllChangeRecords == 'function'; - - global.Platform = global.Platform || {}; - - global.Platform.performMicrotaskCheckpoint = function() { - if (runningMicrotaskCheckpoint) - return; - - if (hasDebugForceFullDelivery) { - Object.deliverAllChangeRecords(); - return; - } - - if (!collectObservers) - return; - - runningMicrotaskCheckpoint = true; - - var cycles = 0; - var results = {}; - - do { - cycles++; - var toCheck = allObservers; - allObservers = []; - results.anyChanged = false; - - for (var i = 0; i < toCheck.length; i++) { - var observer = toCheck[i]; - if (observer.closed) - continue; - - if (hasObserve) { - observer.deliver(results); - } else if (observer.check()) { - results.anyChanged = true; - observer.report(); - } - - allObservers.push(observer); - } - } while (cycles < MAX_DIRTY_CHECK_CYCLES && results.anyChanged); - - Observer._allObserversCount = allObservers.length; - runningMicrotaskCheckpoint = false; - }; - - if (collectObservers) { - global.Platform.clearObservers = function() { - allObservers = []; - }; - } - - function ObjectObserver(object, callback, target, token) { - Observer.call(this, object, callback, target, token); - } - - ObjectObserver.prototype = createObject({ - __proto__: Observer.prototype, - - connect: function() { - if (hasObserve) - Object.observe(this.object, this.boundInternalCallback); - }, - - sync: function(hard) { - if (!hasObserve) - this.oldObject = copyObject(this.object); - }, - - check: function(changeRecords) { - var diff; - var oldValues; - if (hasObserve) { - if (!changeRecords) - return false; - - oldValues = {}; - diff = diffObjectFromChangeRecords(this.object, changeRecords, - oldValues); - } else { - oldValues = this.oldObject; - diff = diffObjectFromOldObject(this.object, this.oldObject); - } - - if (diffIsEmpty(diff)) - return false; - - this.reportArgs = - [diff.added || {}, diff.removed || {}, diff.changed || {}]; - this.reportArgs.push(function(property) { - return oldValues[property]; - }); - - return true; - }, - - disconnect: function() { - if (!hasObserve) - this.oldObject = undefined; - else if (this.object) - Object.unobserve(this.object, this.boundInternalCallback); - } - }); - - function ArrayObserver(array, callback, target, token) { - if (!Array.isArray(array)) - throw Error('Provided object is not an Array'); - Observer.call(this, array, callback, target, token); - } - - ArrayObserver.prototype = createObject({ - __proto__: ObjectObserver.prototype, - - connect: function() { - if (hasObserve) - Array.observe(this.object, this.boundInternalCallback); - }, - - sync: function() { - if (!hasObserve) - this.oldObject = this.object.slice(); - }, - - check: function(changeRecords) { - var splices; - if (hasObserve) { - if (!changeRecords) - return false; - splices = projectArraySplices(this.object, changeRecords); - } else { - splices = calcSplices(this.object, 0, this.object.length, - this.oldObject, 0, this.oldObject.length); - } - - if (!splices || !splices.length) - return false; - - this.reportArgs = [splices]; - return true; - } - }); - - ArrayObserver.applySplices = function(previous, current, splices) { - splices.forEach(function(splice) { - var spliceArgs = [splice.index, splice.removed.length]; - var addIndex = splice.index; - while (addIndex < splice.index + splice.addedCount) { - spliceArgs.push(current[addIndex]); - addIndex++; - } - - Array.prototype.splice.apply(previous, spliceArgs); - }); - }; - - function getPathValue(object, path) { - return path.getValueFrom(object); - } - - function ObservedSet(callback) { - this.arr = []; - this.callback = callback; - this.isObserved = true; - } - - var objProto = Object.getPrototypeOf({}); - var arrayProto = Object.getPrototypeOf([]); - ObservedSet.prototype = { - reset: function() { - this.isObserved = !this.isObserved; - }, - - observe: function(obj) { - if (!isObject(obj) || obj === objProto || obj === arrayProto) - return; - var i = this.arr.indexOf(obj); - if (i >= 0 && this.arr[i+1] === this.isObserved) - return; - - if (i < 0) { - i = this.arr.length; - this.arr[i] = obj; - Object.observe(obj, this.callback); - } - - this.arr[i+1] = this.isObserved; - this.observe(Object.getPrototypeOf(obj)); - }, - - cleanup: function() { - var i = 0, j = 0; - var isObserved = this.isObserved; - while(j < this.arr.length) { - var obj = this.arr[j]; - if (this.arr[j + 1] == isObserved) { - if (i < j) { - this.arr[i] = obj; - this.arr[i + 1] = isObserved; - } - i += 2; - } else { - Object.unobserve(obj, this.callback); - } - j += 2; - } - - this.arr.length = i; - } - }; - - function PathObserver(object, pathString, callback, target, token) { - this.value = undefined; - - var path = getPath(pathString); - if (!path) { - this.closed = true; - this.value = undefined; - return; - } - - if (!path.length) { - this.closed = true; - this.value = object; - return; - } - - if (!isObject(object)) { - this.closed = true; - this.value = undefined; - return; - } - - this.path = path; - Observer.call(this, object, callback, target, token); - } - - PathObserver.prototype = createObject({ - __proto__: Observer.prototype, - - connect: function() { - if (hasObserve) - this.observedSet = new ObservedSet(this.boundInternalCallback); - }, - - disconnect: function() { - this.value = undefined; - if (hasObserve) { - this.observedSet.reset(); - this.observedSet.cleanup(); - this.observedSet = undefined; - } - }, - - check: function() { - this.value = !hasObserve ? this.path.getValueFrom(this.object) : - this.path.getValueFromObserved(this.object, this.observedSet); - if (areSameValue(this.value, this.oldValue)) - return false; - - this.reportArgs = [this.value, this.oldValue]; - return true; - }, - - sync: function(hard) { - if (hard) { - this.value = !hasObserve ? this.path.getValueFrom(this.object) : - this.path.getValueFromObserved(this.object, this.observedSet); - } - this.oldValue = this.value; - } - }); - - PathObserver.getValueAtPath = function(obj, pathString) { - var path = getPath(pathString); - if (!path) - return; - return path.getValueFrom(obj); - } - - PathObserver.setValueAtPath = function(obj, pathString, value) { - var path = getPath(pathString); - if (!path) - return; - - path.setValueFrom(obj, value); - }; - - var knownRecordTypes = { - 'new': true, - 'updated': true, - 'deleted': true - }; - - function notifyFunction(object, name) { - if (typeof Object.observe !== 'function') - return; - - var notifier = Object.getNotifier(object); - return function(type, oldValue) { - var changeRecord = { - object: object, - type: type, - name: name - }; - if (arguments.length === 2) - changeRecord.oldValue = oldValue; - notifier.notify(changeRecord); - } - } - - // TODO(rafaelw): It should be possible for the Object.observe case to have - // every PathObserver used by defineProperty share a single Object.observe - // callback, and thus get() can simply call observer.deliver() and any changes - // to any dependent value will be observed. - PathObserver.defineProperty = function(object, name, descriptor) { - // TODO(rafaelw): Validate errors - var obj = descriptor.object; - var path = getPath(descriptor.path); - var notify = notifyFunction(object, name); - - var observer = new PathObserver(obj, descriptor.path, - function(newValue, oldValue) { - if (notify) - notify('updated', oldValue); - } - ); - - Object.defineProperty(object, name, { - get: function() { - return path.getValueFrom(obj); - }, - set: function(newValue) { - path.setValueFrom(obj, newValue); - }, - configurable: true - }); - - return { - close: function() { - var oldValue = path.getValueFrom(obj); - if (notify) - observer.deliver(); - observer.close(); - Object.defineProperty(object, name, { - value: oldValue, - writable: true, - configurable: true - }); - } - }; - } - - function diffObjectFromChangeRecords(object, changeRecords, oldValues) { - var added = {}; - var removed = {}; - - for (var i = 0; i < changeRecords.length; i++) { - var record = changeRecords[i]; - if (!knownRecordTypes[record.type]) { - console.error('Unknown changeRecord type: ' + record.type); - console.error(record); - continue; - } - - if (!(record.name in oldValues)) - oldValues[record.name] = record.oldValue; - - if (record.type == 'updated') - continue; - - if (record.type == 'new') { - if (record.name in removed) - delete removed[record.name]; - else - added[record.name] = true; - - continue; - } - - // type = 'deleted' - if (record.name in added) { - delete added[record.name]; - delete oldValues[record.name]; - } else { - removed[record.name] = true; - } - } - - for (var prop in added) - added[prop] = object[prop]; - - for (var prop in removed) - removed[prop] = undefined; - - var changed = {}; - for (var prop in oldValues) { - if (prop in added || prop in removed) - continue; - - var newValue = object[prop]; - if (oldValues[prop] !== newValue) - changed[prop] = newValue; - } - - return { - added: added, - removed: removed, - changed: changed - }; - } - - // Note: This function is *based* on the computation of the Levenshtein - // "edit" distance. The one change is that "updates" are treated as two - // edits - not one. With Array splices, an update is really a delete - // followed by an add. By retaining this, we optimize for "keeping" the - // maximum array items in the original array. For example: - // - // 'xxxx123' -> '123yyyy' - // - // With 1-edit updates, the shortest path would be just to update all seven - // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This - // leaves the substring '123' intact. - function calcEditDistances(current, currentStart, currentEnd, - old, oldStart, oldEnd) { - // "Deletion" columns - var rowCount = oldEnd - oldStart + 1; - var columnCount = currentEnd - currentStart + 1; - var distances = new Array(rowCount); - - // "Addition" rows. Initialize null column. - for (var i = 0; i < rowCount; i++) { - distances[i] = new Array(columnCount); - distances[i][0] = i; - } - - // Initialize null row - for (var j = 0; j < columnCount; j++) - distances[0][j] = j; - - for (var i = 1; i < rowCount; i++) { - for (var j = 1; j < columnCount; j++) { - if (old[oldStart + i - 1] === current[currentStart + j - 1]) - distances[i][j] = distances[i - 1][j - 1]; - else { - var north = distances[i - 1][j] + 1; - var west = distances[i][j - 1] + 1; - distances[i][j] = north < west ? north : west; - } - } - } - - return distances; - } - - var EDIT_LEAVE = 0; - var EDIT_UPDATE = 1; - var EDIT_ADD = 2; - var EDIT_DELETE = 3; - - // This starts at the final weight, and walks "backward" by finding - // the minimum previous weight recursively until the origin of the weight - // matrix. - function spliceOperationsFromEditDistances(distances) { - var i = distances.length - 1; - var j = distances[0].length - 1; - var current = distances[i][j]; - var edits = []; - while (i > 0 || j > 0) { - if (i == 0) { - edits.push(EDIT_ADD); - j--; - continue; - } - if (j == 0) { - edits.push(EDIT_DELETE); - i--; - continue; - } - var northWest = distances[i - 1][j - 1]; - var west = distances[i - 1][j]; - var north = distances[i][j - 1]; - - var min; - if (west < north) - min = west < northWest ? west : northWest; - else - min = north < northWest ? north : northWest; - - if (min == northWest) { - if (northWest == current) { - edits.push(EDIT_LEAVE); - } else { - edits.push(EDIT_UPDATE); - current = northWest; - } - i--; - j--; - } else if (min == west) { - edits.push(EDIT_DELETE); - i--; - current = west; - } else { - edits.push(EDIT_ADD); - j--; - current = north; - } - } - - edits.reverse(); - return edits; - } - - function sharedPrefix(arr1, arr2, searchLength) { - for (var i = 0; i < searchLength; i++) - if (arr1[i] !== arr2[i]) - return i; - return searchLength; - } - - function sharedSuffix(arr1, arr2, searchLength) { - var index1 = arr1.length; - var index2 = arr2.length; - var count = 0; - while (count < searchLength && arr1[--index1] === arr2[--index2]) - count++; - - return count; - } - - function newSplice(index, removed, addedCount) { - return { - index: index, - removed: removed, - addedCount: addedCount - }; - } - - /** - * Splice Projection functions: - * - * A splice map is a representation of how a previous array of items - * was transformed into a new array of items. Conceptually it is a list of - * tuples of - * - * - * - * which are kept in ascending index order of. The tuple represents that at - * the |index|, |removed| sequence of items were removed, and counting forward - * from |index|, |addedCount| items were added. - */ - - /** - * Lacking individual splice mutation information, the minimal set of - * splices can be synthesized given the previous state and final state of an - * array. The basic approach is to calculate the edit distance matrix and - * choose the shortest path through it. - * - * Complexity: O(l * p) - * l: The length of the current array - * p: The length of the old array - */ - function calcSplices(current, currentStart, currentEnd, - old, oldStart, oldEnd) { - var prefixCount = 0; - var suffixCount = 0; - - var minLength = Math.min(currentEnd - currentStart, oldEnd - oldStart); - if (currentStart == 0 && oldStart == 0) - prefixCount = sharedPrefix(current, old, minLength); - - if (currentEnd == current.length && oldEnd == old.length) - suffixCount = sharedSuffix(current, old, minLength - prefixCount); - - currentStart += prefixCount; - oldStart += prefixCount; - currentEnd -= suffixCount; - oldEnd -= suffixCount; - - if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) - return []; - - if (currentStart == currentEnd) { - var splice = newSplice(currentStart, [], 0); - while (oldStart < oldEnd) - splice.removed.push(old[oldStart++]); - - return [ splice ]; - } else if (oldStart == oldEnd) - return [ newSplice(currentStart, [], currentEnd - currentStart) ]; - - var ops = spliceOperationsFromEditDistances(calcEditDistances(current, currentStart, currentEnd, - old, oldStart, oldEnd)); - - var splice = undefined; - var splices = []; - var index = currentStart; - var oldIndex = oldStart; - for (var i = 0; i < ops.length; i++) { - switch(ops[i]) { - case EDIT_LEAVE: - if (splice) { - splices.push(splice); - splice = undefined; - } - - index++; - oldIndex++; - break; - case EDIT_UPDATE: - if (!splice) - splice = newSplice(index, [], 0); - - splice.addedCount++; - index++; - - splice.removed.push(old[oldIndex]); - oldIndex++; - break; - case EDIT_ADD: - if (!splice) - splice = newSplice(index, [], 0); - - splice.addedCount++; - index++; - break; - case EDIT_DELETE: - if (!splice) - splice = newSplice(index, [], 0); - - splice.removed.push(old[oldIndex]); - oldIndex++; - break; - } - } - - if (splice) { - splices.push(splice); - } - return splices; - } - - function intersect(start1, end1, start2, end2) { - // Disjoint - if (end1 < start2 || end2 < start1) - return -1; - - // Adjacent - if (end1 == start2 || end2 == start1) - return 0; - - // Non-zero intersect, span1 first - if (start1 < start2) { - if (end1 < end2) - return end1 - start2; // Overlap - else - return end2 - start2; // Contained - } else { - // Non-zero intersect, span2 first - if (end2 < end1) - return end2 - start1; // Overlap - else - return end1 - start1; // Contained - } - } - - function mergeSplice(splices, index, removed, addedCount) { - - var splice = newSplice(index, removed, addedCount); - - var inserted = false; - var insertionOffset = 0; - - for (var i = 0; i < splices.length; i++) { - var current = splices[i]; - current.index += insertionOffset; - - if (inserted) - continue; - - var intersectCount = intersect(splice.index, - splice.index + splice.removed.length, - current.index, - current.index + current.addedCount); - - if (intersectCount >= 0) { - // Merge the two splices - - splices.splice(i, 1); - i--; - - insertionOffset -= current.addedCount - current.removed.length; - - splice.addedCount += current.addedCount - intersectCount; - var deleteCount = splice.removed.length + - current.removed.length - intersectCount; - - if (!splice.addedCount && !deleteCount) { - // merged splice is a noop. discard. - inserted = true; - } else { - var removed = current.removed; - - if (splice.index < current.index) { - // some prefix of splice.removed is prepended to current.removed. - var prepend = splice.removed.slice(0, current.index - splice.index); - Array.prototype.push.apply(prepend, removed); - removed = prepend; - } - - if (splice.index + splice.removed.length > current.index + current.addedCount) { - // some suffix of splice.removed is appended to current.removed. - var append = splice.removed.slice(current.index + current.addedCount - splice.index); - Array.prototype.push.apply(removed, append); - } - - splice.removed = removed; - if (current.index < splice.index) { - splice.index = current.index; - } - } - } else if (splice.index < current.index) { - // Insert splice here. - - inserted = true; - - splices.splice(i, 0, splice); - i++; - - var offset = splice.addedCount - splice.removed.length - current.index += offset; - insertionOffset += offset; - } - } - - if (!inserted) - splices.push(splice); - } - - function createInitialSplices(array, changeRecords) { - var splices = []; - - for (var i = 0; i < changeRecords.length; i++) { - var record = changeRecords[i]; - switch(record.type) { - case 'splice': - mergeSplice(splices, record.index, record.removed.slice(), record.addedCount); - break; - case 'new': - case 'updated': - case 'deleted': - if (!isIndex(record.name)) - continue; - var index = toNumber(record.name); - if (index < 0) - continue; - mergeSplice(splices, index, [record.oldValue], 1); - break; - default: - console.error('Unexpected record type: ' + JSON.stringify(record)); - break; - } - } - - return splices; - } - - function projectArraySplices(array, changeRecords) { - var splices = []; - - createInitialSplices(array, changeRecords).forEach(function(splice) { - if (splice.addedCount == 1 && splice.removed.length == 1) { - if (splice.removed[0] !== array[splice.index]) - splices.push(splice); - - return - }; - - splices = splices.concat(calcSplices(array, splice.index, splice.index + splice.addedCount, - splice.removed, 0, splice.removed.length)); - }); - - return splices; - } - - global.Observer = Observer; - global.Observer.hasObjectObserve = hasObserve; - global.ArrayObserver = ArrayObserver; - global.ArrayObserver.calculateSplices = function(current, previous) { - return calcSplices(current, 0, current.length, previous, 0, previous.length); - }; - global.ObjectObserver = ObjectObserver; - global.PathObserver = PathObserver; - global.Path = Path; -})((exports.Number = { isNaN: window.isNaN }) ? exports : exports); - -},{}],27:[function(require,module,exports){ +},{"../lang/toString":19}],27:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2439,6 +1724,7 @@ function save(resourceName, id, options) { }) .then(function (data) { var saved = _this.inject(resource.name, data, options); + resource.previous_attributes[id] = utils.deepMixIn({}, data); resource.saved[id] = utils.updateTimestamp(resource.saved[id]); return saved; }); @@ -2942,7 +2228,7 @@ function changes(resourceName, id) { } try { - return utils.deepMixIn({}, services.store[resourceName].changes[id]); + return angular.copy(services.store[resourceName].changes[id]); } catch (err) { throw new errors.UnhandledError(err); } @@ -3105,7 +2391,7 @@ function digest() { module.exports = digest; -},{"errors":"hIh4e1","observejs":"q+M0EE","services":"cX8q+p","utils":"uE/lJt"}],41:[function(require,module,exports){ +},{"errors":"hIh4e1","observejs":"u+GZEJ","services":"cX8q+p","utils":"uE/lJt"}],41:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -3113,23 +2399,30 @@ var utils = require('utils'), function _eject(resource, id) { if (id) { + var found = false; for (var i = 0; i < resource.collection.length; i++) { - if (resource.collection[i][resource.idAttribute || 'id'] == id) { + if (resource.collection[i][resource.idAttribute] == id) { + found = true; break; } } - resource.collection.splice(i, 1); - resource.observers[id].close(); - delete resource.observers[id]; - delete resource.index[id]; - delete resource.changes[id]; - delete resource.previous_attributes[id]; - delete resource.modified[id]; - delete resource.saved[id]; + if (found) { + resource.collection.splice(i, 1); + resource.observers[id].close(); + delete resource.observers[id]; + delete resource.index[id]; + delete resource.changes[id]; + delete resource.previous_attributes[id]; + delete resource.modified[id]; + delete resource.saved[id]; + } } else { resource.collection = []; resource.index = {}; resource.modified = {}; + resource.saved = {}; + resource.changes = {}; + resource.previous_attributes = {}; } resource.collectionModified = utils.updateTimestamp(resource.collectionModified); } @@ -3175,7 +2468,7 @@ function _eject(resource, id) { * - `{UnhandledError}` * * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. - * @param {string|number} id The primary key of the item to eject. + * @param {string|number=} id The primary key of the item to eject. */ function eject(resourceName, id) { if (!services.store[resourceName]) { @@ -3761,7 +3054,7 @@ function inject(resourceName, attrs, options) { module.exports = inject; -},{"errors":"hIh4e1","observejs":"q+M0EE","services":"cX8q+p","utils":"uE/lJt"}],47:[function(require,module,exports){ +},{"errors":"hIh4e1","observejs":"u+GZEJ","services":"cX8q+p","utils":"uE/lJt"}],47:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -3805,7 +3098,7 @@ function lastModified(resourceName, id) { if (!services.store[resourceName]) { throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); } else if (id && !utils.isString(id) && !utils.isNumber(id)) { - throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or number!', { id: { actual: typeof id, expected: 'string|number' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); } try { if (id) { @@ -3873,7 +3166,7 @@ function lastSaved(resourceName, id) { if (!services.store[resourceName]) { throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); } else if (id && !utils.isString(id) && !utils.isNumber(id)) { - throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or number!', { id: { actual: typeof id, expected: 'string|number' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); } try { if (id) { @@ -4238,7 +3531,9 @@ module.exports = { })(window, window.angular); -},{"./datastore":35}],"uE/lJt":[function(require,module,exports){ +},{"./datastore":35}],"utils":[function(require,module,exports){ +module.exports=require('uE/lJt'); +},{}],"uE/lJt":[function(require,module,exports){ module.exports = { isString: angular.isString, isArray: angular.isArray, @@ -4316,6 +3611,4 @@ module.exports = { } }; -},{"mout/array/contains":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],"utils":[function(require,module,exports){ -module.exports=require('uE/lJt'); -},{}]},{},[52]) +},{"mout/array/contains":3,"mout/array/filter":4,"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/string/makePath":25,"mout/string/upperCase":26}]},{},[52]) diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index bcfa5d8..10df5e4 100644 --- a/dist/angular-data.min.js +++ b/dist/angular-data.min.js @@ -7,5 +7,5 @@ * * @overview Data store for Angular.js. */ -require=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;gc?d+c:c;d>e;){if(a[e]===b)return e;e++}return-1}b.exports=c},{}],4:[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":2}],5:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;b.exports=c},{}],6:[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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(b.shift());return a.length&&d.push.apply(d,a),b.length&&d.push.apply(d,b),d}b.exports=c},{}],7:[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>>0}function d(a){return+a}function e(a){return a===Object(a)}function f(a,b){return a===b?0!==a||1/a===1/b:K(a)&&K(b)?!0:a!==a&&b!==b}function g(a){return"string"!=typeof a?!1:(a=a.replace(/\s/g,""),""==a?!0:"."==a[0]?!1:S.test(a))}function h(a){var b=T[a];if(b)return b;if(g(a)){var b=new i(a);return T[a]=b,b}}function i(a){return""==a.trim()?this:c(a)?(this.push(String(a)),this):(a.split(/\./).filter(function(a){return a}).forEach(function(a){this.push(a)},this),H&&this.length&&(this.getValueFrom=this.compiledGetValueFromFn()),void 0)}function j(a){for(var b=0;U>b&&a.check();)a.report(),b++}function k(a){for(var b in a)return!1;return!0}function l(a){return k(a.added)&&k(a.removed)&&k(a.changed)}function m(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 n(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 o(a,b,c,d){if(this.closed=!1,this.object=a,this.callback=b,this.target=c,this.token=d,this.reporting=!0,G){var e=this;this.boundInternalCallback=function(a){e.internalCallback(a)}}p(this),this.connect(),this.sync(!0)}function p(a){W&&(V.push(a),o._allObserversCount++)}function q(a,b,c,d){o.call(this,a,b,c,d)}function r(a,b,c,d){if(!Array.isArray(a))throw Error("Provided object is not an Array");o.call(this,a,b,c,d)}function s(a){this.arr=[],this.callback=a,this.isObserved=!0}function t(a,b,c,d,f){this.value=void 0;var g=h(b);return g?g.length?e(a)?(this.path=g,o.call(this,a,c,d,f),void 0):(this.closed=!0,this.value=void 0,void 0):(this.closed=!0,this.value=a,void 0):(this.closed=!0,this.value=void 0,void 0)}function u(a,b){if("function"==typeof Object.observe){var c=Object.getNotifier(a);return function(d,e){var f={object:a,type:d,name:b};2===arguments.length&&(f.oldValue=e),c.notify(f)}}}function v(a,b,c){for(var d={},e={},f=0;fj;j++)i[j]=new Array(h),i[j][0]=j;for(var k=0;h>k;k++)i[0][k]=k;for(var j=1;g>j;j++)for(var k=1;h>k;k++)if(d[e+j-1]===a[b+k-1])i[j][k]=i[j-1][k-1];else{var l=i[j-1][k]+1,m=i[j][k-1]+1;i[j][k]=m>l?l:m}return i}function x(a){for(var b=a.length-1,c=a[0].length-1,d=a[b][c],e=[];b>0||c>0;)if(0!=b)if(0!=c){var f,g=a[b-1][c-1],h=a[b-1][c],i=a[b][c-1];f=i>h?g>h?h:g:g>i?i:g,f==g?(g==d?e.push(ab):(e.push(bb),d=g),b--,c--):f==h?(e.push(db),b--,d=h):(e.push(cb),c--,d=i)}else e.push(db),b--;else e.push(cb),c--;return e.reverse(),e}function y(a,b,c){for(var d=0;c>d;d++)if(a[d]!==b[d])return d;return c}function z(a,b,c){for(var d=a.length,e=b.length,f=0;c>f&&a[--d]===b[--e];)f++;return f}function A(a,b,c){return{index:a,removed:b,addedCount:c}}function B(a,b,c,d,e,f){var g=0,h=0,i=Math.min(c-b,f-e);if(0==b&&0==e&&(g=y(a,d,i)),c==a.length&&f==d.length&&(h=z(a,d,i-g)),b+=g,e+=g,c-=h,f-=h,c-b==0&&f-e==0)return[];if(b==c){for(var j=A(b,[],0);f>e;)j.removed.push(d[e++]);return[j]}if(e==f)return[A(b,[],c-b)];for(var k=x(w(a,b,c,d,e,f)),j=void 0,l=[],m=b,n=e,o=0;ob||a>d?-1:b==c||d==a?0:c>a?d>b?b-c:d-c:b>d?d-a:b-a}function D(a,b,c,d){for(var e=A(b,c,d),f=!1,g=0,h=0;h=0){a.splice(h,1),h--,g-=i.addedCount-i.removed.length,e.addedCount+=i.addedCount-j;var k=e.removed.length+i.removed.length-j;if(e.addedCount||k){var c=i.removed;if(e.indexi.index+i.addedCount){var m=e.removed.slice(i.index+i.addedCount-e.index);Array.prototype.push.apply(c,m)}e.removed=c,i.indexh)continue;D(e,h,[g.oldValue],1);break;default:console.error("Unexpected record type: "+JSON.stringify(g))}}return e}function F(a,b){var c=[];return E(a,b).forEach(function(b){return 1==b.addedCount&&1==b.removed.length?(b.removed[0]!==a[b.index]&&c.push(b),void 0):(c=c.concat(B(a,b.index,b.index+b.addedCount,b.removed,0,b.removed.length)),void 0)}),c}var G=b(),H=!1;try{var I=new Function("","return true;");H=I()}catch(J){}var K=a.Number.isNaN||function(b){return"number"==typeof b&&a.isNaN(b)},L="__proto__"in{}?function(a){return a}:function(a){var b=a.__proto__;if(!b)return a;var c=Object.create(b);return Object.getOwnPropertyNames(a).forEach(function(b){Object.defineProperty(c,b,Object.getOwnPropertyDescriptor(a,b))}),c},M="[$_a-zA-Z]",N="[$_a-zA-Z0-9]",O=M+"+"+N+"*",P="(?:[0-9]|[1-9]+[0-9]+)",Q="(?:"+O+"|"+P+")",R="(?:"+Q+")(?:\\."+Q+")*",S=new RegExp("^"+R+"$"),T={};i.prototype=L({__proto__:[],toString:function(){return this.join(".")},getValueFrom:function(a){for(var b=0;ba&&b.anyChanged);o._allObserversCount=V.length,X=!1}}},W&&(a.Platform.clearObservers=function(){V=[]}),q.prototype=L({__proto__:o.prototype,connect:function(){G&&Object.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=n(this.object))},check:function(a){var b,c;if(G){if(!a)return!1;c={},b=v(this.object,a,c)}else c=this.oldObject,b=m(this.object,this.oldObject);return l(b)?!1:(this.reportArgs=[b.added||{},b.removed||{},b.changed||{}],this.reportArgs.push(function(a){return c[a]}),!0)},disconnect:function(){G?this.object&&Object.unobserve(this.object,this.boundInternalCallback):this.oldObject=void 0}}),r.prototype=L({__proto__:q.prototype,connect:function(){G&&Array.observe(this.object,this.boundInternalCallback)},sync:function(){G||(this.oldObject=this.object.slice())},check:function(a){var b;if(G){if(!a)return!1;b=F(this.object,a)}else b=B(this.object,0,this.object.length,this.oldObject,0,this.oldObject.length);return b&&b.length?(this.reportArgs=[b],!0):!1}}),r.applySplices=function(a,b,c){c.forEach(function(c){for(var d=[c.index,c.removed.length],e=c.index;e=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}},t.prototype=L({__proto__:o.prototype,connect:function(){G&&(this.observedSet=new s(this.boundInternalCallback))},disconnect:function(){this.value=void 0,G&&(this.observedSet.reset(),this.observedSet.cleanup(),this.observedSet=void 0)},check:function(){return this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object),f(this.value,this.oldValue)?!1:(this.reportArgs=[this.value,this.oldValue],!0)},sync:function(a){a&&(this.value=G?this.path.getValueFromObserved(this.object,this.observedSet):this.path.getValueFrom(this.object)),this.oldValue=this.value}}),t.getValueAtPath=function(a,b){var c=h(b);if(c)return c.getValueFrom(a)},t.setValueAtPath=function(a,b,c){var d=h(b);d&&d.setValueFrom(a,c)};var _={"new":!0,updated:!0,deleted:!0};t.defineProperty=function(a,b,c){var d=c.object,e=h(c.path),f=u(a,b),g=new t(d,c.path,function(a,b){f&&f("updated",b)});return Object.defineProperty(a,b,{get:function(){return e.getValueFrom(d)},set:function(a){e.setValueFrom(d,a)},configurable:!0}),{close:function(){var c=e.getValueFrom(d);f&&g.deliver(),g.close(),Object.defineProperty(a,b,{value:c,writable:!0,configurable:!0})}}};var ab=0,bb=1,cb=2,db=3;a.Observer=o,a.Observer.hasObjectObserve=G,a.ArrayObserver=r,a.ArrayObserver.calculateSplices=function(a,b){return B(a,0,a.length,b,0,b.length)},a.ObjectObserver=q,a.PathObserver=t,a.Path=i}((c.Number={isNaN:window.isNaN})?c:c)},{}],27:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isObject(b))try{var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeValidate)(a,b)}).then(function(b){return f.$q.promisify(i.validate)(a,b)}).then(function(b){return f.$q.promisify(i.afterValidate)(a,b)}).then(function(b){return f.$q.promisify(i.beforeCreate)(a,b)}).then(function(a){return j.POST(d.makePath(i.baseUrl,i.endpoint),a,null)}).then(function(b){return f.$q.promisify(i.afterCreate)(a,b)}).then(function(a){return j.inject(i.name,a)}),c.resolve(b)}catch(k){c.reject(new e.UnhandledError(k))}else c.reject(new e.IllegalArgumentError(g+"attrs: Must be an object!",{attrs:{actual:typeof b,expected:"object"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.create(resourceName, attrs): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],28:[function(a,b){function c(a,b){var c=f.$q.defer(),h=c.promise;if(f.store[a])if(d.isString(b)||d.isNumber(b)){var i=f.store[a],j=this;h=h.then(function(b){return f.$q.promisify(i.beforeDestroy)(a,b)}).then(function(){return j.DEL(d.makePath(i.baseUrl,i.endpoint,b),null)}).then(function(){return f.$q.promisify(i.afterDestroy)(a,i.index[b])}).then(function(){return j.eject(a,b),b}),c.resolve(i.index[b])}else c.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else c.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return h}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.destroy(resourceName, id): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],29:[function(a,b){function c(a,b,c){var h=f.$q.defer(),i=h.promise;if(c=c||{},f.store[a])if(d.isString(b)||d.isNumber(b))if(d.isObject(c))try{var j=f.store[a],k=this;if(c.bypassCache&&delete j.completedQueries[b],!(b in j.completedQueries))return b in j.pendingQueries||(i=j.pendingQueries[b]=k.GET(d.makePath(j.baseUrl,j.endpoint,b),null).then(function(c){return delete j.pendingQueries[b],j.completedQueries[b]=(new Date).getTime(),k.inject(a,c)})),j.pendingQueries[b];h.resolve(k.get(a,b))}catch(l){h.reject(l)}else h.reject(new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}}));else h.reject(new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}}));else h.reject(new e.RuntimeError(g+a+" is not a registered resource!"));return i}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.find(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],30:[function(a,b){function c(a,b,c){var d=h.store[b];a=a||[],delete d.pendingQueries[c],d.completedQueries[c]=(new Date).getTime();for(var e=0;e"in b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return!(e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed))}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return b in g.store[a].changes?c(g.store[a].changes[b]):!1}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){function d(b,c,d,h){try{var i=h(a.idAttribute);a.changes[i]=e.diffObjectFromOldObject(a.index[i],a.previous_attributes[i]),a.modified[i]=e.updateTimestamp(a.modified[i]),a.collectionModified=e.updateTimestamp(a.collectionModified),a.idAttribute in d&&g.$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 \""+a.name+'" resource is now in an undefined (probably broken) state.')}catch(j){throw new f.UnhandledError(j)}}var j=this;if(e.isArray(b))for(var k=0;k=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":1,"mout/array/filter":2,"mout/array/slice":5,"mout/array/sort":6,"mout/array/toLookup":7,"mout/lang/isEmpty":12,"mout/object/deepMixIn":19,"mout/object/forOwn":21,"mout/string/makePath":23,"mout/string/upperCase":24}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[52]); \ No newline at end of file +require=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)},{}],observejs:[function(a,b){b.exports=a("u+GZEJ")},{}],3:[function(a,b){function c(a,b){return-1!==d(a,b)}var d=a("./indexOf");b.exports=c},{"./indexOf":5}],4:[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":4}],7:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(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 b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return!(e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed))}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return b in g.store[a].changes?c(g.store[a].changes[b]):!1}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){function d(b,c,d,h){try{var i=h(a.idAttribute);a.changes[i]=e.diffObjectFromOldObject(a.index[i],a.previous_attributes[i]),a.modified[i]=e.updateTimestamp(a.modified[i]),a.collectionModified=e.updateTimestamp(a.collectionModified),a.idAttribute in d&&g.$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 \""+a.name+'" resource is now in an undefined (probably broken) state.')}catch(j){throw new f.UnhandledError(j)}}var j=this;if(e.isArray(b))for(var k=0;k=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":3,"mout/array/filter":4,"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/string/makePath":25,"mout/string/upperCase":26}]},{},[52]); \ No newline at end of file diff --git a/lib/observe-js.js b/lib/observe-js.js new file mode 100644 index 0000000..7478e1f --- /dev/null +++ b/lib/observe-js.js @@ -0,0 +1,468 @@ +// Copyright 2012 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +(function(global) { + 'use strict'; + + function detectObjectObserve() { + if (typeof Object.observe !== 'function' || + typeof Array.observe !== 'function') { + return false; + } + + var gotSplice = false; + function callback(records) { + if (records[0].type === 'splice' && records[1].type === 'splice') + gotSplice = true; + } + + var test = [0]; + Array.observe(test, callback); + test[1] = 1; + test.length = 0; + Object.deliverChangeRecords(callback); + return gotSplice; + } + + var hasObserve = detectObjectObserve(); + + var hasEval = false; + try { + var f = new Function('', 'return true;'); + hasEval = f(); + } catch (ex) { + } + + function isObject(obj) { + return obj === Object(obj); + } + + var numberIsNaN = global.Number.isNaN || function isNaN(value) { + return typeof value === 'number' && global.isNaN(value); + } + + var createObject = ('__proto__' in {}) ? + function(obj) { return obj; } : + function(obj) { + var proto = obj.__proto__; + if (!proto) + return obj; + var newObject = Object.create(proto); + Object.getOwnPropertyNames(obj).forEach(function(name) { + Object.defineProperty(newObject, name, + Object.getOwnPropertyDescriptor(obj, name)); + }); + return newObject; + }; + + var MAX_DIRTY_CHECK_CYCLES = 1000; + + function dirtyCheck(observer) { + var cycles = 0; + while (cycles < MAX_DIRTY_CHECK_CYCLES && observer.check()) { + observer.report(); + cycles++; + } + } + + function objectIsEmpty(object) { + for (var prop in object) + return false; + return true; + } + + function diffIsEmpty(diff) { + return objectIsEmpty(diff.added) && + objectIsEmpty(diff.removed) && + objectIsEmpty(diff.changed); + } + + function diffObjectFromOldObject(object, oldObject) { + var added = {}; + var removed = {}; + var changed = {}; + var oldObjectHas = {}; + + 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; + } + + for (var prop in object) { + if (prop in oldObject) + continue; + + added[prop] = object[prop]; + } + + if (Array.isArray(object) && object.length !== oldObject.length) + changed.length = object.length; + + return { + added: added, + removed: removed, + changed: changed + }; + } + + function copyObject(object, opt_copy) { + var copy = opt_copy || (Array.isArray(object) ? [] : {}); + for (var prop in object) { + copy[prop] = object[prop]; + }; + if (Array.isArray(object)) + copy.length = object.length; + return copy; + } + + function Observer(object, callback, target, token) { + this.closed = false; + this.object = object; + this.callback = callback; + // TODO(rafaelw): Hold this.target weakly when WeakRef is available. + this.target = target; + this.token = token; + this.reporting = true; + if (hasObserve) { + var self = this; + this.boundInternalCallback = function(records) { + self.internalCallback(records); + }; + } + + addToAll(this); + this.connect(); + this.sync(true); + } + + Observer.prototype = { + internalCallback: function(records) { + if (this.closed) + return; + if (this.reporting && this.check(records)) { + this.report(); + if (this.testingResults) + this.testingResults.anyChanged = true; + } + }, + + close: function() { + if (this.closed) + return; + if (this.object && typeof this.object.unobserved === 'function') + this.object.unobserved(); + + this.disconnect(); + this.object = undefined; + this.closed = true; + }, + + deliver: function(testingResults) { + if (this.closed) + return; + if (hasObserve) { + this.testingResults = testingResults; + Object.deliverChangeRecords(this.boundInternalCallback); + this.testingResults = undefined; + } else { + dirtyCheck(this); + } + }, + + report: function() { + if (!this.reporting) + return; + + this.sync(false); + this.reportArgs.push(this.token); + this.invokeCallback(this.reportArgs); + this.reportArgs = undefined; + }, + + invokeCallback: function(args) { + try { + this.callback.apply(this.target, args); + } catch (ex) { + Observer._errorThrownDuringCallback = true; + console.error('Exception caught during observer callback: ' + ex); + } + }, + + reset: function() { + if (this.closed) + return; + + if (hasObserve) { + this.reporting = false; + Object.deliverChangeRecords(this.boundInternalCallback); + this.reporting = true; + } + + this.sync(true); + } + } + + var collectObservers = !hasObserve || global.forceCollectObservers; + var allObservers; + Observer._allObserversCount = 0; + + if (collectObservers) { + allObservers = []; + } + + function addToAll(observer) { + if (!collectObservers) + return; + + allObservers.push(observer); + Observer._allObserversCount++; + } + + var runningMicrotaskCheckpoint = false; + + var hasDebugForceFullDelivery = typeof Object.deliverAllChangeRecords == 'function'; + + global.Platform = global.Platform || {}; + + global.Platform.performMicrotaskCheckpoint = function() { + if (runningMicrotaskCheckpoint) + return; + + if (hasDebugForceFullDelivery) { + Object.deliverAllChangeRecords(); + return; + } + + if (!collectObservers) + return; + + runningMicrotaskCheckpoint = true; + + var cycles = 0; + var results = {}; + + do { + cycles++; + var toCheck = allObservers; + allObservers = []; + results.anyChanged = false; + + for (var i = 0; i < toCheck.length; i++) { + var observer = toCheck[i]; + if (observer.closed) + continue; + + if (hasObserve) { + observer.deliver(results); + } else if (observer.check()) { + results.anyChanged = true; + observer.report(); + } + + allObservers.push(observer); + } + } while (cycles < MAX_DIRTY_CHECK_CYCLES && results.anyChanged); + + Observer._allObserversCount = allObservers.length; + runningMicrotaskCheckpoint = false; + }; + + if (collectObservers) { + global.Platform.clearObservers = function() { + allObservers = []; + }; + } + + function ObjectObserver(object, callback, target, token) { + Observer.call(this, object, callback, target, token); + } + + ObjectObserver.prototype = createObject({ + __proto__: Observer.prototype, + + connect: function() { + if (hasObserve) + Object.observe(this.object, this.boundInternalCallback); + }, + + sync: function(hard) { + if (!hasObserve) + this.oldObject = copyObject(this.object); + }, + + check: function(changeRecords) { + var diff; + var oldValues; + if (hasObserve) { + if (!changeRecords) + return false; + + oldValues = {}; + diff = diffObjectFromChangeRecords(this.object, changeRecords, + oldValues); + } else { + oldValues = this.oldObject; + diff = diffObjectFromOldObject(this.object, this.oldObject); + } + + if (diffIsEmpty(diff)) + return false; + + this.reportArgs = + [diff.added || {}, diff.removed || {}, diff.changed || {}]; + this.reportArgs.push(function(property) { + return oldValues[property]; + }); + + return true; + }, + + disconnect: function() { + if (!hasObserve) + this.oldObject = undefined; + else if (this.object) + Object.unobserve(this.object, this.boundInternalCallback); + } + }); + + function ObservedSet(callback) { + this.arr = []; + this.callback = callback; + this.isObserved = true; + } + + var objProto = Object.getPrototypeOf({}); + var arrayProto = Object.getPrototypeOf([]); + ObservedSet.prototype = { + reset: function() { + this.isObserved = !this.isObserved; + }, + + observe: function(obj) { + if (!isObject(obj) || obj === objProto || obj === arrayProto) + return; + var i = this.arr.indexOf(obj); + if (i >= 0 && this.arr[i+1] === this.isObserved) + return; + + if (i < 0) { + i = this.arr.length; + this.arr[i] = obj; + Object.observe(obj, this.callback); + } + + this.arr[i+1] = this.isObserved; + this.observe(Object.getPrototypeOf(obj)); + }, + + cleanup: function() { + var i = 0, j = 0; + var isObserved = this.isObserved; + while(j < this.arr.length) { + var obj = this.arr[j]; + if (this.arr[j + 1] == isObserved) { + if (i < j) { + this.arr[i] = obj; + this.arr[i + 1] = isObserved; + } + i += 2; + } else { + Object.unobserve(obj, this.callback); + } + j += 2; + } + + this.arr.length = i; + } + }; + + var knownRecordTypes = { + 'new': true, + 'updated': true, + 'deleted': true + }; + + function diffObjectFromChangeRecords(object, changeRecords, oldValues) { + var added = {}; + var removed = {}; + + for (var i = 0; i < changeRecords.length; i++) { + var record = changeRecords[i]; + if (!knownRecordTypes[record.type]) { + console.error('Unknown changeRecord type: ' + record.type); + console.error(record); + continue; + } + + if (!(record.name in oldValues)) + oldValues[record.name] = record.oldValue; + + if (record.type == 'updated') + continue; + + if (record.type == 'new') { + if (record.name in removed) + delete removed[record.name]; + else + added[record.name] = true; + + continue; + } + + // type = 'deleted' + if (record.name in added) { + delete added[record.name]; + delete oldValues[record.name]; + } else { + removed[record.name] = true; + } + } + + for (var prop in added) + added[prop] = object[prop]; + + for (var prop in removed) + removed[prop] = undefined; + + var changed = {}; + for (var prop in oldValues) { + if (prop in added || prop in removed) + continue; + + var newValue = object[prop]; + if (oldValues[prop] !== newValue) + changed[prop] = newValue; + } + + return { + added: added, + removed: removed, + changed: changed + }; + } + + global.Observer = Observer; + global.Observer.hasObjectObserve = hasObserve; + global.ObjectObserver = ObjectObserver; +})(typeof global !== 'undefined' && global ? global : window); diff --git a/src/datastore/async_methods/save/index.js b/src/datastore/async_methods/save/index.js index e104a08..3807964 100644 --- a/src/datastore/async_methods/save/index.js +++ b/src/datastore/async_methods/save/index.js @@ -85,6 +85,7 @@ function save(resourceName, id, options) { }) .then(function (data) { var saved = _this.inject(resource.name, data, options); + resource.previous_attributes[id] = utils.deepMixIn({}, data); resource.saved[id] = utils.updateTimestamp(resource.saved[id]); return saved; }); diff --git a/src/datastore/sync_methods/changes/index.js b/src/datastore/sync_methods/changes/index.js index c207483..ddcbdc6 100644 --- a/src/datastore/sync_methods/changes/index.js +++ b/src/datastore/sync_methods/changes/index.js @@ -45,7 +45,7 @@ function changes(resourceName, id) { } try { - return utils.deepMixIn({}, services.store[resourceName].changes[id]); + return angular.copy(services.store[resourceName].changes[id]); } catch (err) { throw new errors.UnhandledError(err); } diff --git a/src/datastore/sync_methods/eject/index.js b/src/datastore/sync_methods/eject/index.js index 6d63260..6b473bc 100644 --- a/src/datastore/sync_methods/eject/index.js +++ b/src/datastore/sync_methods/eject/index.js @@ -5,23 +5,30 @@ var utils = require('utils'), function _eject(resource, id) { if (id) { + var found = false; for (var i = 0; i < resource.collection.length; i++) { - if (resource.collection[i][resource.idAttribute || 'id'] == id) { + if (resource.collection[i][resource.idAttribute] == id) { + found = true; break; } } - resource.collection.splice(i, 1); - resource.observers[id].close(); - delete resource.observers[id]; - delete resource.index[id]; - delete resource.changes[id]; - delete resource.previous_attributes[id]; - delete resource.modified[id]; - delete resource.saved[id]; + if (found) { + resource.collection.splice(i, 1); + resource.observers[id].close(); + delete resource.observers[id]; + delete resource.index[id]; + delete resource.changes[id]; + delete resource.previous_attributes[id]; + delete resource.modified[id]; + delete resource.saved[id]; + } } else { resource.collection = []; resource.index = {}; resource.modified = {}; + resource.saved = {}; + resource.changes = {}; + resource.previous_attributes = {}; } resource.collectionModified = utils.updateTimestamp(resource.collectionModified); } @@ -67,7 +74,7 @@ function _eject(resource, id) { * - `{UnhandledError}` * * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. - * @param {string|number} id The primary key of the item to eject. + * @param {string|number=} id The primary key of the item to eject. */ function eject(resourceName, id) { if (!services.store[resourceName]) { diff --git a/src/datastore/sync_methods/lastModified/index.js b/src/datastore/sync_methods/lastModified/index.js index 08fe2c2..49a3035 100644 --- a/src/datastore/sync_methods/lastModified/index.js +++ b/src/datastore/sync_methods/lastModified/index.js @@ -41,7 +41,7 @@ function lastModified(resourceName, id) { if (!services.store[resourceName]) { throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); } else if (id && !utils.isString(id) && !utils.isNumber(id)) { - throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or number!', { id: { actual: typeof id, expected: 'string|number' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); } try { if (id) { diff --git a/src/datastore/sync_methods/lastSaved/index.js b/src/datastore/sync_methods/lastSaved/index.js index e1532c7..e626560 100644 --- a/src/datastore/sync_methods/lastSaved/index.js +++ b/src/datastore/sync_methods/lastSaved/index.js @@ -48,7 +48,7 @@ function lastSaved(resourceName, id) { if (!services.store[resourceName]) { throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); } else if (id && !utils.isString(id) && !utils.isNumber(id)) { - throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or number!', { id: { actual: typeof id, expected: 'string|number' } }); + throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); } try { if (id) { diff --git a/test/integration/datastore/index.test.js b/test/integration/datastore/index.test.js new file mode 100644 index 0000000..39479bc --- /dev/null +++ b/test/integration/datastore/index.test.js @@ -0,0 +1,115 @@ +describe('DSProvider.config(options)', function () { + var errorPrefix = 'DSProvider.config(options): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + angular.forEach(TYPES_EXCEPT_OBJECT, function (key) { + if (key) { + assert.throws(function () { + DSProvider.config(key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options: Must be an object!'); + } else { + assert.doesNotThrow(function () { + DSProvider.config(key); + }, 'Should not fail'); + } + }); + + angular.forEach(TYPES_EXCEPT_STRING, function (key) { + assert.throws(function () { + DSProvider.config({ + baseUrl: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.baseUrl: Must be a string!'); + }); + + angular.forEach(TYPES_EXCEPT_STRING, function (key) { + assert.throws(function () { + DSProvider.config({ + idAttribute: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.idAttribute: Must be a string!'); + }); + + angular.forEach(TYPES_EXCEPT_STRING, function (key) { + assert.throws(function () { + DSProvider.config({ + mergeStrategy: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.mergeStrategy: Must be a string!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + beforeValidate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.beforeValidate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + validate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.validate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + afterValidate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.afterValidate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + beforeCreate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.beforeCreate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + afterCreate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.afterCreate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + beforeUpdate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.beforeUpdate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + afterUpdate: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.afterUpdate: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + beforeDestroy: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.beforeDestroy: Must be a function!'); + }); + + angular.forEach(TYPES_EXCEPT_FUNCTION, function (key) { + assert.throws(function () { + DSProvider.config({ + afterDestroy: key + }); + }, DS.errors.IllegalArgumentError, errorPrefix + 'options.afterDestroy: Must be a function!'); + }); + + done(); + }); +}); diff --git a/test/integration/datastore/sync_methods/changes/index.test.js b/test/integration/datastore/sync_methods/changes/index.test.js new file mode 100644 index 0000000..0cfc078 --- /dev/null +++ b/test/integration/datastore/sync_methods/changes/index.test.js @@ -0,0 +1,39 @@ +describe('DS.changes(resourceName, id)', function () { + var errorPrefix = 'DS.changes(resourceName, id): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.changes('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + assert.throws(function () { + DS.changes('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + }); + + done(); + }); + it('should return false if the item is not in the store', function (done) { + + assert.isUndefined(DS.changes('post', 5)); + done(); + }); + it('should return the changes in an object', function (done) { + + DS.inject('post', p1); + + assert.deepEqual(DS.changes('post', 5), {added: {}, changed: {}, removed: {}}); + + var post = DS.get('post', 5); + post.author = 'Jake'; + + DS.digest(); + + assert.deepEqual(DS.changes('post', 5), {added: {}, changed: { + author: 'Jake' + }, removed: {}}); + + done(); + }); +}); diff --git a/test/integration/datastore/sync_methods/eject/index.test.js b/test/integration/datastore/sync_methods/eject/index.test.js new file mode 100644 index 0000000..98d8619 --- /dev/null +++ b/test/integration/datastore/sync_methods/eject/index.test.js @@ -0,0 +1,66 @@ +describe('DS.eject(resourceName, id)', function () { + var errorPrefix = 'DS.eject(resourceName, id): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.eject('does not exist', 5); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + if (key) { + assert.throws(function () { + DS.eject('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + } + }); + + done(); + }); + it('should do nothing if the item is not in the store', function (done) { + + assert.equal(DS.lastModified('post', 5), 0); + assert.doesNotThrow(function () { + DS.eject('post', 5); + }); + assert.equal(DS.lastModified('post', 5), 0); + + done(); + }); + it('should eject an item from the store', function (done) { + + DS.inject('post', p3); + DS.inject('post', p2); + DS.inject('post', p1); + assert.notEqual(DS.lastModified('post', 5), 0); + assert.doesNotThrow(function () { + DS.eject('post', 5); + }); + assert.isUndefined(DS.get('post', 5)); + assert.equal(DS.lastModified('post', 5), 0); + + done(); + }); + it('should eject all items from the store', function (done) { + + DS.inject('post', p1); + DS.inject('post', p2); + DS.inject('post', p3); + DS.inject('post', p4); + + assert.deepEqual(DS.get('post', 5), p1); + assert.deepEqual(DS.get('post', 6), p2); + assert.deepEqual(DS.get('post', 7), p3); + assert.deepEqual(DS.get('post', 8), p4); + + assert.doesNotThrow(function () { + DS.eject('post'); + }); + + assert.isUndefined(DS.get('post', 5)); + assert.isUndefined(DS.get('post', 6)); + assert.isUndefined(DS.get('post', 7)); + assert.isUndefined(DS.get('post', 8)); + + done(); + }); +}); diff --git a/test/integration/datastore/sync_methods/hasChanges/index.test.js b/test/integration/datastore/sync_methods/hasChanges/index.test.js index 29574bd..b8b9ec9 100644 --- a/test/integration/datastore/sync_methods/hasChanges/index.test.js +++ b/test/integration/datastore/sync_methods/hasChanges/index.test.js @@ -19,7 +19,7 @@ describe('DS.hasChanges(resourceName, id)', function () { assert.isFalse(DS.hasChanges('post', 5)); done(); }); - it('should return undefined and send the query to the server if the query has never been made before and loadFromServer is set to true', function (done) { + it('should return whether an item has changes', function (done) { DS.inject('post', p1); diff --git a/test/integration/datastore/sync_methods/lastModified/index.test.js b/test/integration/datastore/sync_methods/lastModified/index.test.js new file mode 100644 index 0000000..e659bfc --- /dev/null +++ b/test/integration/datastore/sync_methods/lastModified/index.test.js @@ -0,0 +1,79 @@ +describe('DS.lastModified(resourceName[, id])', function () { + var errorPrefix = 'DS.lastModified(resourceName[, id]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.lastModified('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + if (key) { + assert.throws(function () { + DS.lastModified('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + } + }); + + done(); + }); + it('should lastModified an item into the store', function (done) { + + var collectionLastModified; + + assert.equal(DS.lastModified('post', 5), 0); + assert.equal(DS.lastModified('post'), 0); + + collectionLastModified = DS.lastModified('post'); + + assert.doesNotThrow(function () { + DS.inject('post', p1); + }); + + assert.notEqual(DS.lastModified('post'), collectionLastModified); + collectionLastModified = DS.lastModified('post'); + assert.notEqual(DS.lastModified('post', 5), 0); + var lastModified = DS.lastModified('post', 5); + assert.isNumber(lastModified); + + assert.doesNotThrow(function () { + DS.inject('post', p2); + }); + + assert.notEqual(DS.lastModified('post'), collectionLastModified); + + collectionLastModified = DS.lastModified('post'); + assert.equal(DS.lastModified('post', 5), lastModified); + + assert.doesNotThrow(function () { + DS.inject('post', p3); + }); + + assert.notEqual(DS.lastModified('post'), collectionLastModified); + + collectionLastModified = DS.lastModified('post'); + assert.equal(DS.lastModified('post', 5), lastModified); + + done(); + }); +// it('should lastModified an item into the store', function (done) { +// +// assert.equal(DS.lastModified('post', 5), 0); +// assert.doesNotThrow(function () { +// assert.deepEqual(DS.lastModified('post', p1), p1); +// }); +// assert.notEqual(DS.lastModified('post', 5), 0); +// assert.isNumber(DS.lastModified('post', 5)); +// +// var post = DS.get('post', 5); +// +// post.id = 10; +// +// DS.digest(); +// +// assert.deepEqual('Doh! You just changed the primary key of an object! ' + +// 'I don\'t know how to handle this yet, so your data for the "post' + +// '" resource is now in an undefined (probably broken) state.', $log.error.logs[0][0]); +// +// done(); +// }); +}); diff --git a/test/integration/datastore/sync_methods/lastSaved/index.test.js b/test/integration/datastore/sync_methods/lastSaved/index.test.js new file mode 100644 index 0000000..776c03e --- /dev/null +++ b/test/integration/datastore/sync_methods/lastSaved/index.test.js @@ -0,0 +1,64 @@ +describe('DS.lastSaved(resourceName[, id])', function () { + var errorPrefix = 'DS.lastSaved(resourceName[, id]): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.lastSaved('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + if (key) { + assert.throws(function () { + DS.lastSaved('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + } + }); + + done(); + }); + it('should lastSaved an item into the store', function (done) { + + assert.equal(DS.lastSaved('post', 5), 0); + assert.equal(DS.lastSaved('post'), 0); + + assert.doesNotThrow(function () { + DS.inject('post', p1); + }); + + $httpBackend.expectPUT('http://test.angular-cache.com/posts/5').respond(200, p1); + + var post = DS.get('post', 5); + + post.author = 'Jake'; + + DS.save('post', 5); + + $httpBackend.flush(); + + assert.notEqual(DS.lastSaved('post', 5), 0); + assert.isNumber(DS.lastSaved('post', 5)); + + done(); + }); +// it('should lastSaved an item into the store', function (done) { +// +// assert.equal(DS.lastSaved('post', 5), 0); +// assert.doesNotThrow(function () { +// assert.deepEqual(DS.lastSaved('post', p1), p1); +// }); +// assert.notEqual(DS.lastSaved('post', 5), 0); +// assert.isNumber(DS.lastSaved('post', 5)); +// +// var post = DS.get('post', 5); +// +// post.id = 10; +// +// DS.digest(); +// +// assert.deepEqual('Doh! You just changed the primary key of an object! ' + +// 'I don\'t know how to handle this yet, so your data for the "post' + +// '" resource is now in an undefined (probably broken) state.', $log.error.logs[0][0]); +// +// done(); +// }); +}); diff --git a/test/integration/datastore/sync_methods/previous/index.test.js b/test/integration/datastore/sync_methods/previous/index.test.js new file mode 100644 index 0000000..9f764d8 --- /dev/null +++ b/test/integration/datastore/sync_methods/previous/index.test.js @@ -0,0 +1,48 @@ +describe('DS.previous(resourceName, id)', function () { + var errorPrefix = 'DS.previous(resourceName, id): '; + + it('should throw an error when method pre-conditions are not met', function (done) { + assert.throws(function () { + DS.previous('does not exist', {}); + }, DS.errors.RuntimeError, errorPrefix + 'does not exist is not a registered resource!'); + + angular.forEach(TYPES_EXCEPT_STRING_OR_NUMBER, function (key) { + assert.throws(function () { + DS.previous('post', key); + }, DS.errors.IllegalArgumentError, errorPrefix + 'id: Must be a string or a number!'); + }); + + done(); + }); + it('should return false if the item is not in the store', function (done) { + + assert.isUndefined(DS.previous('post', 5)); + done(); + }); + it('should return the previous in an object', function (done) { + + DS.inject('post', p1); + + var post = DS.get('post', 5); + + assert.deepEqual(DS.previous('post', 5), p1); + + post.author = 'Jake'; + + DS.digest(); + + assert.deepEqual(DS.previous('post', 5), p1); + assert.deepEqual(DS.get('post', 5), { author: 'Jake', age: 30, id: 5 }); + + $httpBackend.expectPUT('http://test.angular-cache.com/posts/5', DS.get('post', 5)).respond(200, { author: 'Jake', age: 30, id: 5 }); + + DS.save('post', 5); + + $httpBackend.flush(); + + assert.deepEqual(DS.previous('post', 5), { author: 'Jake', age: 30, id: 5 }, 'previous attributes should have been updated'); + assert.deepEqual(DS.get('post', 5), { author: 'Jake', age: 30, id: 5 }); + + done(); + }); +}); From 15fe82041760eb6b0a2e08f379c8057ca16708f8 Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Thu, 16 Jan 2014 22:30:51 -0700 Subject: [PATCH 7/7] Adapters now pluggable. API needs work though. --- Gruntfile.js | 1 + dist/angular-data.js | 832 ++++++++++--------- dist/angular-data.min.js | 4 +- index.html | 17 - src/datastore/{ => adapters}/http/index.js | 0 src/datastore/async_methods/create/index.js | 2 +- src/datastore/async_methods/destroy/index.js | 2 +- src/datastore/async_methods/find/index.js | 2 +- src/datastore/async_methods/findAll/index.js | 2 +- src/datastore/async_methods/save/index.js | 2 +- src/datastore/index.js | 93 ++- src/datastore/services/index.js | 9 +- test.js | 49 -- 13 files changed, 534 insertions(+), 481 deletions(-) delete mode 100644 index.html rename src/datastore/{ => adapters}/http/index.js (100%) delete mode 100644 test.js diff --git a/Gruntfile.js b/Gruntfile.js index 9d8410b..d52290e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -74,6 +74,7 @@ module.exports = function (grunt) { options: { alias: [ 'lib/observe-js.js:observejs', + 'src/datastore/adapters/http/index.js:HttpAdapter', 'src/datastore/services/index.js:services', 'src/errors/index.js:errors', 'src/utils/index.js:utils' diff --git a/dist/angular-data.js b/dist/angular-data.js index e70ee40..0c30ee7 100644 --- a/dist/angular-data.js +++ b/dist/angular-data.js @@ -1067,7 +1067,241 @@ var toString = require('../lang/toString'); module.exports = upperCase; -},{"../lang/toString":19}],27:[function(require,module,exports){ +},{"../lang/toString":19}],"clHM+W":[function(require,module,exports){ +var utils = require('utils'), + errors = require('errors'), + services = require('services'); + +function _$http(deferred, config) { + var start = new Date().getTime(); + + services.$http(config).success(function (data, status, headers, config) { + services.$log.debug(config.method + ' request:' + config.url + ' Time taken: ' + (new Date().getTime() - start) + 'ms', arguments); + deferred.resolve(data); + }).error(function (data, status, headers, config) { + services.$log.error(arguments); + deferred.reject(data); + }); +} + +/** + * @doc method + * @id DS.async_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. + */ +function HTTP(config) { + var deferred = services.$q.defer(); + + try { + if (!services.$rootScope.$$phase) { + services.$rootScope.$apply(function () { + _$http(deferred, config); + }); + } else { + _$http(deferred, config); + } + } catch (err) { + deferred.reject(new errors.UnhandledError(err)); + } + + return deferred.promise; +} + +/** + * @doc method + * @id DS.async_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. + */ +function GET(url, config) { + config = config || {}; + return HTTP(utils.deepMixIn(config, { + url: url, + method: 'GET' + })); +} + +/** + * @doc method + * @id DS.async_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. + */ +function PUT(url, attrs, config) { + config = config || {}; + return HTTP(utils.deepMixIn(config, { + url: url, + data: attrs, + method: 'PUT' + })); +} + +/** + * @doc method + * @id DS.async_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. + */ +function POST(url, attrs, config) { + config = config || {}; + return HTTP(utils.deepMixIn(config, { + url: url, + data: attrs, + method: 'POST' + })); +} + +/** + * @doc method + * @id DS.async_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. + */ +function DEL(url, config) { + config = config || {}; + return HTTP(utils.deepMixIn(config, { + url: url, + method: 'DELETE' + })); +} + +module.exports = { + /** + * @doc method + * @id DS.async_methods:HTTP + * @name HTTP + * @methodOf DS + * @description + * See [DS.HTTP](/documentation/api/api/DS.async_methods:HTTP). + */ + HTTP: HTTP, + + /** + * @doc method + * @id DS.async_methods:GET + * @name GET + * @methodOf DS + * @description + * See [DS.GET](/documentation/api/api/DS.async_methods:GET). + */ + GET: GET, + + /** + * @doc method + * @id DS.async_methods:POST + * @name POST + * @methodOf DS + * @description + * See [DS.POST](/documentation/api/api/DS.async_methods:POST). + */ + POST: POST, + + /** + * @doc method + * @id DS.async_methods:PUT + * @name PUT + * @methodOf DS + * @description + * See [DS.PUT](/documentation/api/api/DS.async_methods:PUT). + */ + PUT: PUT, + + /** + * @doc method + * @id DS.async_methods:DEL + * @name DEL + * @methodOf DS + * @description + * See [DS.DEL](/documentation/api/api/DS.async_methods:DEL). + */ + DEL: DEL +}; + +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],"HttpAdapter":[function(require,module,exports){ +module.exports=require('clHM+W'); +},{}],29:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -1141,7 +1375,7 @@ function create(resourceName, attrs) { return services.$q.promisify(resource.beforeCreate)(resourceName, attrs); }) .then(function (attrs) { - return _this.POST(utils.makePath(resource.baseUrl, resource.endpoint), attrs, null); + return services.adapters[resource.defaultAdapter].POST.apply(_this, [utils.makePath(resource.baseUrl, resource.endpoint), attrs, null]); }) .then(function (data) { return services.$q.promisify(resource.afterCreate)(resourceName, data); @@ -1161,7 +1395,7 @@ function create(resourceName, attrs) { module.exports = create; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],28:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],30:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -1225,7 +1459,7 @@ function destroy(resourceName, id) { return services.$q.promisify(resource.beforeDestroy)(resourceName, attrs); }) .then(function () { - return _this.DEL(utils.makePath(resource.baseUrl, resource.endpoint, id), null); + return services.adapters[resource.defaultAdapter].DEL(utils.makePath(resource.baseUrl, resource.endpoint, id), null); }) .then(function () { return services.$q.promisify(resource.afterDestroy)(resourceName, resource.index[id]); @@ -1243,7 +1477,7 @@ function destroy(resourceName, id) { module.exports = destroy; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],29:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],31:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -1316,7 +1550,7 @@ function find(resourceName, id, options) { if (!(id in resource.completedQueries)) { if (!(id in resource.pendingQueries)) { - promise = resource.pendingQueries[id] = _this.GET(utils.makePath(resource.baseUrl, resource.endpoint, id), null) + promise = resource.pendingQueries[id] = services.adapters[resource.defaultAdapter].GET(utils.makePath(resource.baseUrl, resource.endpoint, id), null) .then(function (data) { // Query is no longer pending delete resource.pendingQueries[id]; @@ -1339,7 +1573,7 @@ function find(resourceName, id, options) { module.exports = find; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],30:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],32:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -1382,7 +1616,7 @@ function _findAll(resourceName, params, options) { if (!(queryHash in resource.pendingQueries)) { // This particular query has never even been made - resource.pendingQueries[queryHash] = _this.GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) + resource.pendingQueries[queryHash] = services.adapters[resource.defaultAdapter].GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) .then(function (data) { try { return processResults.apply(_this, [data, resourceName, queryHash]); @@ -1494,7 +1728,7 @@ function findAll(resourceName, params, options) { module.exports = findAll; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],31:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],33:[function(require,module,exports){ module.exports = { /** * @doc method @@ -1557,7 +1791,7 @@ module.exports = { save: require('./save') }; -},{"./create":27,"./destroy":28,"./find":29,"./findAll":30,"./refresh":32,"./save":33}],32:[function(require,module,exports){ +},{"./create":29,"./destroy":30,"./find":31,"./findAll":32,"./refresh":34,"./save":35}],34:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -1604,376 +1838,146 @@ var utils = require('utils'), * @returns {false|Promise} `false` if the item doesn't already exist in the data store. A `Promise` if the item does * exist in the data store and is being refreshed. * - * ## Resolves with: - * - * - `{object}` - `item` - A reference to the refreshed item. - * - * ## Rejects with: - * - * - `{IllegalArgumentError}` - * - `{RuntimeError}` - * - `{UnhandledError}` - */ -function refresh(resourceName, id, options) { - options = options || {}; - - if (!services.store[resourceName]) { - throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); - } else if (!utils.isString(id) && !utils.isNumber(id)) { - throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); - } else if (!utils.isObject(options)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); - } else { - options.bypassCache = true; - - if (id in services.store[resourceName].index) { - return this.find(resourceName, id, options); - } else { - return false; - } - } -} - -module.exports = refresh; - -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],33:[function(require,module,exports){ -var utils = require('utils'), - errors = require('errors'), - services = require('services'), - errorPrefix = 'DS.save(resourceName, id[, options]): '; - -/** - * @doc method - * @id DS.async_methods:save - * @name save - * @description - * Save the item of the type specified by `resourceName` that has the primary key specified by `id`. - * - * ## Signature: - * ```js - * DS.save(resourceName, id[, options]) - * ``` - * - * ## Example: - * - * ```js - * var document = DS.get('document', 'ee7f3f4d-98d5-4934-9e5a-6a559b08479f'); - * - * document.title = 'How to cook in style'; - * - * DS.save('document', 'ee7f3f4d-98d5-4934-9e5a-6a559b08479f') - * .then(function (document) { - * document; // A reference to the document that's been saved to the server - * }); - * ``` - * - * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. - * @param {string|number} id The primary key of the item to retrieve. - * @param {object=} options Optional configuration. Properties: - * - `{string=}` - `mergeStrategy` - When the updated item returns from the server, specify the merge strategy that - * should be used when the updated item is injected into the data store. Default: `"mergeWithExisting"`. - * - * @returns {Promise} Promise produced by the `$q` service. - * - * ## Resolves with: - * - * - `{object}` - `item` - A reference to the newly saved item. - * - * ## Rejects with: - * - * - `{IllegalArgumentError}` - * - `{RuntimeError}` - * - `{UnhandledError}` - */ -function save(resourceName, id, options) { - var deferred = services.$q.defer(), - promise = deferred.promise; - - options = options || {}; - - if (!services.store[resourceName]) { - deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); - } else if (!utils.isString(id) && !utils.isNumber(id)) { - deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); - } else if (!utils.isObject(options)) { - deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); - } else if (!(id in services.store[resourceName].index)) { - deferred.reject(new errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); - } else { - var resource = services.store[resourceName], - _this = this; - - promise = promise - .then(function (attrs) { - return services.$q.promisify(resource.beforeValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return services.$q.promisify(resource.validate)(resourceName, attrs); - }) - .then(function (attrs) { - return services.$q.promisify(resource.afterValidate)(resourceName, attrs); - }) - .then(function (attrs) { - return services.$q.promisify(resource.beforeUpdate)(resourceName, attrs); - }) - .then(function (attrs) { - return _this.PUT(utils.makePath(resource.baseUrl, resource.endpoint, id), attrs, null); - }) - .then(function (data) { - return services.$q.promisify(resource.afterUpdate)(resourceName, data); - }) - .then(function (data) { - var saved = _this.inject(resource.name, data, options); - resource.previous_attributes[id] = utils.deepMixIn({}, data); - resource.saved[id] = utils.updateTimestamp(resource.saved[id]); - return saved; - }); - - deferred.resolve(resource.index[id]); - } - - return promise; -} - -module.exports = save; - -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],34:[function(require,module,exports){ -var utils = require('utils'), - errors = require('errors'), - services = require('services'); - -function _$http(deferred, config) { - var start = new Date().getTime(); - - services.$http(config).success(function (data, status, headers, config) { - services.$log.debug(config.method + ' request:' + config.url + ' Time taken: ' + (new Date().getTime() - start) + 'ms', arguments); - deferred.resolve(data); - }).error(function (data, status, headers, config) { - services.$log.error(arguments); - deferred.reject(data); - }); -} - -/** - * @doc method - * @id DS.async_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. - */ -function HTTP(config) { - var deferred = services.$q.defer(); - - try { - if (!services.$rootScope.$$phase) { - services.$rootScope.$apply(function () { - _$http(deferred, config); - }); - } else { - _$http(deferred, config); - } - } catch (err) { - deferred.reject(new errors.UnhandledError(err)); - } - - return deferred.promise; -} - -/** - * @doc method - * @id DS.async_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. - */ -function GET(url, config) { - config = config || {}; - return HTTP(utils.deepMixIn(config, { - url: url, - method: 'GET' - })); -} - -/** - * @doc method - * @id DS.async_methods:PUT - * @name PUT - * @description - * Wrapper for `$http.put()`. - * - * ## Signature: - * ```js - * DS.PUT(url[, attrs][, config]) - * ``` - * - * ## Example: + * ## Resolves with: * - * ```js - * Works the same as $http.put() - * ``` + * - `{object}` - `item` - A reference to the refreshed item. * - * @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. + * ## Rejects with: + * + * - `{IllegalArgumentError}` + * - `{RuntimeError}` + * - `{UnhandledError}` */ -function PUT(url, attrs, config) { - config = config || {}; - return HTTP(utils.deepMixIn(config, { - url: url, - data: attrs, - method: 'PUT' - })); +function refresh(resourceName, id, options) { + options = options || {}; + + if (!services.store[resourceName]) { + throw new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!'); + } else if (!utils.isString(id) && !utils.isNumber(id)) { + throw new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } }); + } else if (!utils.isObject(options)) { + throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } }); + } else { + options.bypassCache = true; + + if (id in services.store[resourceName].index) { + return this.find(resourceName, id, options); + } else { + return false; + } + } } +module.exports = refresh; + +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],35:[function(require,module,exports){ +var utils = require('utils'), + errors = require('errors'), + services = require('services'), + errorPrefix = 'DS.save(resourceName, id[, options]): '; + /** * @doc method - * @id DS.async_methods:POST - * @name POST + * @id DS.async_methods:save + * @name save * @description - * Wrapper for `$http.post()`. + * Save the item of the type specified by `resourceName` that has the primary key specified by `id`. * * ## Signature: * ```js - * DS.POST(url[, attrs][, config]) + * DS.save(resourceName, id[, options]) * ``` * * ## Example: * * ```js - * Works the same as $http.post() + * var document = DS.get('document', 'ee7f3f4d-98d5-4934-9e5a-6a559b08479f'); + * + * document.title = 'How to cook in style'; + * + * DS.save('document', 'ee7f3f4d-98d5-4934-9e5a-6a559b08479f') + * .then(function (document) { + * document; // A reference to the document that's been saved to the server + * }); * ``` * - * @param {string} url The url of the request. - * @param {object=} attrs Request payload. - * @param {object=} config Configuration for the request. + * @param {string} resourceName The resource type, e.g. 'user', 'comment', etc. + * @param {string|number} id The primary key of the item to retrieve. + * @param {object=} options Optional configuration. Properties: + * - `{string=}` - `mergeStrategy` - When the updated item returns from the server, specify the merge strategy that + * should be used when the updated item is injected into the data store. Default: `"mergeWithExisting"`. + * * @returns {Promise} Promise produced by the `$q` service. - */ -function POST(url, attrs, config) { - config = config || {}; - return HTTP(utils.deepMixIn(config, { - url: url, - data: attrs, - method: 'POST' - })); -} - -/** - * @doc method - * @id DS.async_methods:DEL - * @name DEL - * @description - * Wrapper for `$http.delete()`. * - * ## Signature: - * ```js - * DS.DEL(url[, config]) - * ``` + * ## Resolves with: * - * ## Example: + * - `{object}` - `item` - A reference to the newly saved item. * - * ```js - * Works the same as $http.delete - * ``` + * ## Rejects with: * - * @param {string} url The url of the request. - * @param {object} config Configuration for the request. - * @returns {Promise} Promise produced by the `$q` service. + * - `{IllegalArgumentError}` + * - `{RuntimeError}` + * - `{UnhandledError}` */ -function DEL(url, config) { - config = config || {}; - return HTTP(utils.deepMixIn(config, { - url: url, - method: 'DELETE' - })); -} +function save(resourceName, id, options) { + var deferred = services.$q.defer(), + promise = deferred.promise; -module.exports = { - /** - * @doc method - * @id DS.async_methods:HTTP - * @name HTTP - * @methodOf DS - * @description - * See [DS.HTTP](/documentation/api/api/DS.async_methods:HTTP). - */ - HTTP: HTTP, + options = options || {}; - /** - * @doc method - * @id DS.async_methods:GET - * @name GET - * @methodOf DS - * @description - * See [DS.GET](/documentation/api/api/DS.async_methods:GET). - */ - GET: GET, + if (!services.store[resourceName]) { + deferred.reject(new errors.RuntimeError(errorPrefix + resourceName + ' is not a registered resource!')); + } else if (!utils.isString(id) && !utils.isNumber(id)) { + deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'id: Must be a string or a number!', { id: { actual: typeof id, expected: 'string|number' } })); + } else if (!utils.isObject(options)) { + deferred.reject(new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { options: { actual: typeof options, expected: 'object' } })); + } else if (!(id in services.store[resourceName].index)) { + deferred.reject(new errors.RuntimeError(errorPrefix + 'id: "' + id + '" not found!')); + } else { + var resource = services.store[resourceName], + _this = this; - /** - * @doc method - * @id DS.async_methods:POST - * @name POST - * @methodOf DS - * @description - * See [DS.POST](/documentation/api/api/DS.async_methods:POST). - */ - POST: POST, + promise = promise + .then(function (attrs) { + return services.$q.promisify(resource.beforeValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.validate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.afterValidate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.$q.promisify(resource.beforeUpdate)(resourceName, attrs); + }) + .then(function (attrs) { + return services.adapters[resource.defaultAdapter].PUT(utils.makePath(resource.baseUrl, resource.endpoint, id), attrs, null); + }) + .then(function (data) { + return services.$q.promisify(resource.afterUpdate)(resourceName, data); + }) + .then(function (data) { + var saved = _this.inject(resource.name, data, options); + resource.previous_attributes[id] = utils.deepMixIn({}, data); + resource.saved[id] = utils.updateTimestamp(resource.saved[id]); + return saved; + }); - /** - * @doc method - * @id DS.async_methods:PUT - * @name PUT - * @methodOf DS - * @description - * See [DS.PUT](/documentation/api/api/DS.async_methods:PUT). - */ - PUT: PUT, + deferred.resolve(resource.index[id]); + } - /** - * @doc method - * @id DS.async_methods:DEL - * @name DEL - * @methodOf DS - * @description - * See [DS.DEL](/documentation/api/api/DS.async_methods:DEL). - */ - DEL: DEL -}; + return promise; +} -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],35:[function(require,module,exports){ +module.exports = save; + +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],36:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), - errorPrefix = 'DSProvider.config(options): '; + HttpAdapter = require('HttpAdapter'), + configErrorPrefix = 'DSProvider.config(options): ', + registerAdapterErrorPrefix = 'DSProvider.registerAdapter(name, adapter): '; /** * @doc method @@ -2020,36 +2024,76 @@ function config(options) { options = options || {}; if (!utils.isObject(options)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); } else if ('baseUrl' in options && !utils.isString(options.baseUrl)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.baseUrl: Must be a string!', { baseUrl: { actual: typeof options.baseUrl, expected: 'string' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.baseUrl: Must be a string!', { baseUrl: { actual: typeof options.baseUrl, expected: 'string' } }); } else if ('idAttribute' in options && !utils.isString(options.idAttribute)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.idAttribute: Must be a string!', { idAttribute: { actual: typeof options.idAttribute, expected: 'string' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.idAttribute: Must be a string!', { idAttribute: { actual: typeof options.idAttribute, expected: 'string' } }); } else if ('mergeStrategy' in options && !utils.isString(options.mergeStrategy)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.mergeStrategy: Must be a string!', { mergeStrategy: { actual: typeof options.mergeStrategy, expected: 'string' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.mergeStrategy: Must be a string!', { mergeStrategy: { actual: typeof options.mergeStrategy, expected: 'string' } }); } else if ('beforeValidate' in options && !utils.isFunction(options.beforeValidate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeValidate: Must be a function!', { beforeValidate: { actual: typeof options.beforeValidate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeValidate: Must be a function!', { beforeValidate: { actual: typeof options.beforeValidate, expected: 'function' } }); } else if ('validate' in options && !utils.isFunction(options.validate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.validate: Must be a function!', { validate: { actual: typeof options.validate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.validate: Must be a function!', { validate: { actual: typeof options.validate, expected: 'function' } }); } else if ('afterValidate' in options && !utils.isFunction(options.afterValidate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterValidate: Must be a function!', { afterValidate: { actual: typeof options.afterValidate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterValidate: Must be a function!', { afterValidate: { actual: typeof options.afterValidate, expected: 'function' } }); } else if ('beforeCreate' in options && !utils.isFunction(options.beforeCreate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeCreate: Must be a function!', { beforeCreate: { actual: typeof options.beforeCreate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeCreate: Must be a function!', { beforeCreate: { actual: typeof options.beforeCreate, expected: 'function' } }); } else if ('afterCreate' in options && !utils.isFunction(options.afterCreate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterCreate: Must be a function!', { afterCreate: { actual: typeof options.afterCreate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterCreate: Must be a function!', { afterCreate: { actual: typeof options.afterCreate, expected: 'function' } }); } else if ('beforeUpdate' in options && !utils.isFunction(options.beforeUpdate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeUpdate: Must be a function!', { beforeUpdate: { actual: typeof options.beforeUpdate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeUpdate: Must be a function!', { beforeUpdate: { actual: typeof options.beforeUpdate, expected: 'function' } }); } else if ('afterUpdate' in options && !utils.isFunction(options.afterUpdate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterUpdate: Must be a function!', { afterUpdate: { actual: typeof options.afterUpdate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterUpdate: Must be a function!', { afterUpdate: { actual: typeof options.afterUpdate, expected: 'function' } }); } else if ('beforeDestroy' in options && !utils.isFunction(options.beforeDestroy)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeDestroy: Must be a function!', { beforeDestroy: { actual: typeof options.beforeDestroy, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeDestroy: Must be a function!', { beforeDestroy: { actual: typeof options.beforeDestroy, expected: 'function' } }); } else if ('afterDestroy' in options && !utils.isFunction(options.afterDestroy)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterDestroy: Must be a function!', { afterDestroy: { actual: typeof options.afterDestroy, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterDestroy: Must be a function!', { afterDestroy: { actual: typeof options.afterDestroy, expected: 'function' } }); + } else if ('defaultAdapter' in options && !utils.isString(options.defaultAdapter)) { + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.defaultAdapter: Must be a function!', { defaultAdapter: { actual: typeof options.defaultAdapter, expected: 'string' } }); } services.config = new services.BaseConfig(options); } +/** + * @doc method + * @id DSProvider.methods:registerAdapter + * @name registerAdapter + * @description + * Register a new adapter. + * + * ## Signature: + * ```js + * DSProvider.registerAdapter(name, adapter); + * ``` + * + * ## Example: + * ```js + * DSProvider.registerAdapter('IndexedDBAdapter', {...}); + * ``` + * + * ## Throws: + * + * - `{IllegalArgumentError}` + * - `{RuntimeError}` + * + * @param {string} name The name of the new adapter. + * @param {object} adapter New adapter. + */ +function registerAdapter(name, adapter) { + + if (!utils.isString(name)) { + throw new errors.IllegalArgumentError(registerAdapterErrorPrefix + 'name: Must be a string!', { actual: typeof name, expected: 'string' }); + } else if (!utils.isObject(adapter)) { + throw new errors.IllegalArgumentError(registerAdapterErrorPrefix + 'adapter: Must be an object!', { actual: typeof adapter, expected: 'object' }); + } else if (services.adapters[name]) { + throw new errors.RuntimeError(registerAdapterErrorPrefix + name + ' is already registered!'); + } + + services.adapters[name] = adapter; +} + /** * @doc interface * @id DSProvider @@ -2067,6 +2111,18 @@ function DSProvider() { */ this.config = config; + config({}); + + /** + * @doc method + * @id DSProvider.methods:registerAdapter + * @name config + * @methodOf DSProvider + * @description + * See [DSProvider.registerAdapter](/documentation/api/api/DSProvider.methods:registerAdapter). + */ + this.registerAdapter = registerAdapter; + this.$get = ['$rootScope', '$log', '$http', '$q', function ($rootScope, $log, $http, $q) { services.$rootScope = $rootScope; @@ -2074,6 +2130,9 @@ function DSProvider() { services.$http = $http; services.$q = $q; services.store = {}; + services.adapters = {}; + + registerAdapter('HttpAdapter', HttpAdapter); /** * @doc interface @@ -2082,14 +2141,14 @@ function DSProvider() { * @description * Data store */ - var DS = {}; + var DS = { + HttpAdapter: HttpAdapter, + errors: errors + }; - utils.deepMixIn(DS, require('./http')); utils.deepMixIn(DS, require('./sync_methods')); utils.deepMixIn(DS, require('./async_methods')); - DS.errors = errors; - utils.deepFreeze(DS); var $dirtyCheckScope = $rootScope.$new(); @@ -2107,15 +2166,12 @@ function DSProvider() { module.exports = DSProvider; -},{"./async_methods":31,"./http":34,"./sync_methods":45,"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],"cX8q+p":[function(require,module,exports){ +},{"./async_methods":33,"./sync_methods":46,"HttpAdapter":"clHM+W","errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],"cX8q+p":[function(require,module,exports){ function lifecycleNoop(resourceName, attrs, cb) { cb(null, attrs); } var services = module.exports = { - config: { - idAttribute: 'id' - }, store: {}, BaseConfig: function (options) { if ('idAttribute' in options) { @@ -2161,11 +2217,15 @@ var services = module.exports = { if ('afterDestroy' in options) { this.afterDestroy = options.afterDestroy; } + + if ('defaultAdapter' in options) { + this.defaultAdapter = options.defaultAdapter; + } } }; - services.BaseConfig.prototype.idAttribute = 'id'; +services.BaseConfig.prototype.defaultAdapter = 'HttpAdapter'; services.BaseConfig.prototype.baseUrl = ''; services.BaseConfig.prototype.endpoint = ''; services.BaseConfig.prototype.beforeValidate = lifecycleNoop; @@ -2180,7 +2240,7 @@ services.BaseConfig.prototype.afterDestroy = lifecycleNoop; },{}],"services":[function(require,module,exports){ module.exports=require('cX8q+p'); -},{}],38:[function(require,module,exports){ +},{}],39:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2236,7 +2296,7 @@ function changes(resourceName, id) { module.exports = changes; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],39:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],40:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2346,7 +2406,7 @@ function defineResource(definition) { module.exports = defineResource; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],40:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],41:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2391,7 +2451,7 @@ function digest() { module.exports = digest; -},{"errors":"hIh4e1","observejs":"u+GZEJ","services":"cX8q+p","utils":"uE/lJt"}],41:[function(require,module,exports){ +},{"errors":"hIh4e1","observejs":"u+GZEJ","services":"cX8q+p","utils":"uE/lJt"}],42:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2492,7 +2552,7 @@ function eject(resourceName, id) { module.exports = eject; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],42:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],43:[function(require,module,exports){ /* jshint loopfunc: true */ var utils = require('utils'), errors = require('errors'), @@ -2671,7 +2731,7 @@ function filter(resourceName, params, options) { module.exports = filter; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],43:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],44:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2736,7 +2796,7 @@ function get(resourceName, id, options) { module.exports = get; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],44:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],45:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -2802,7 +2862,7 @@ function hasChanges(resourceName, id) { module.exports = hasChanges; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],45:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],46:[function(require,module,exports){ module.exports = { /** * @doc method @@ -2914,7 +2974,7 @@ module.exports = { hasChanges: require('./hasChanges') }; -},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(require,module,exports){ +},{"./changes":39,"./defineResource":40,"./digest":41,"./eject":42,"./filter":43,"./get":44,"./hasChanges":45,"./inject":47,"./lastModified":48,"./lastSaved":49,"./previous":50}],47:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -3054,7 +3114,7 @@ function inject(resourceName, attrs, options) { module.exports = inject; -},{"errors":"hIh4e1","observejs":"u+GZEJ","services":"cX8q+p","utils":"uE/lJt"}],47:[function(require,module,exports){ +},{"errors":"hIh4e1","observejs":"u+GZEJ","services":"cX8q+p","utils":"uE/lJt"}],48:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -3115,7 +3175,7 @@ function lastModified(resourceName, id) { module.exports = lastModified; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],48:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],49:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -3183,7 +3243,7 @@ function lastSaved(resourceName, id) { module.exports = lastSaved; -},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],49:[function(require,module,exports){ +},{"errors":"hIh4e1","services":"cX8q+p","utils":"uE/lJt"}],50:[function(require,module,exports){ var utils = require('utils'), errors = require('errors'), services = require('services'), @@ -3455,7 +3515,7 @@ module.exports = { RuntimeError: RuntimeError }; -},{}],52:[function(require,module,exports){ +},{}],53:[function(require,module,exports){ (function (window, angular, undefined) { 'use strict'; @@ -3531,9 +3591,7 @@ module.exports = { })(window, window.angular); -},{"./datastore":35}],"utils":[function(require,module,exports){ -module.exports=require('uE/lJt'); -},{}],"uE/lJt":[function(require,module,exports){ +},{"./datastore":36}],"uE/lJt":[function(require,module,exports){ module.exports = { isString: angular.isString, isArray: angular.isArray, @@ -3611,4 +3669,6 @@ module.exports = { } }; -},{"mout/array/contains":3,"mout/array/filter":4,"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/string/makePath":25,"mout/string/upperCase":26}]},{},[52]) +},{"mout/array/contains":3,"mout/array/filter":4,"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/string/makePath":25,"mout/string/upperCase":26}],"utils":[function(require,module,exports){ +module.exports=require('uE/lJt'); +},{}]},{},[53]) diff --git a/dist/angular-data.min.js b/dist/angular-data.min.js index 10df5e4..c309dce 100644 --- a/dist/angular-data.min.js +++ b/dist/angular-data.min.js @@ -7,5 +7,5 @@ * * @overview Data store for Angular.js. */ -require=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)},{}],observejs:[function(a,b){b.exports=a("u+GZEJ")},{}],3:[function(a,b){function c(a,b){return-1!==d(a,b)}var d=a("./indexOf");b.exports=c},{"./indexOf":5}],4:[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":4}],7:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(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 b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],43:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a){return!(e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed))}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return b in g.store[a].changes?c(g.store[a].changes[b]):!1}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":38,"./defineResource":39,"./digest":40,"./eject":41,"./filter":42,"./get":43,"./hasChanges":44,"./inject":46,"./lastModified":47,"./lastSaved":48,"./previous":49}],46:[function(a,b){function c(a,b){function d(b,c,d,h){try{var i=h(a.idAttribute);a.changes[i]=e.diffObjectFromOldObject(a.index[i],a.previous_attributes[i]),a.modified[i]=e.updateTimestamp(a.modified[i]),a.collectionModified=e.updateTimestamp(a.collectionModified),a.idAttribute in d&&g.$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 \""+a.name+'" resource is now in an undefined (probably broken) state.')}catch(j){throw new f.UnhandledError(j)}}var j=this;if(e.isArray(b))for(var k=0;k=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":3,"mout/array/filter":4,"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/string/makePath":25,"mout/string/upperCase":26}]},{},[52]); \ No newline at end of file +require=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)},{}],observejs:[function(a,b){b.exports=a("u+GZEJ")},{}],3:[function(a,b){function c(a,b){return-1!==d(a,b)}var d=a("./indexOf");b.exports=c},{"./indexOf":5}],4:[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":4}],7:[function(a,b){function c(a,b,c){return d.call(a,b,c)}var d=Array.prototype.slice;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;)c(a[0],b[0])<=0?d.push(a.shift()):d.push(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 b?c=c&&a[e]>b[">"]:">="in b?c=c&&a[e]>=b[">="]:"<"in b?c=c&&a[e]f?-1:f>e?1:0:f>e?-1:e>f?1:0})}}return d.isNumber(b.query.limit)&&d.isNumber(b.query.skip)?j=d.slice(j,b.query.skip,b.query.skip+b.query.limit):d.isNumber(b.query.limit)?j=d.slice(j,0,b.query.limit):d.isNumber(b.query.skip)&&(j=d.slice(j,b.query.skip)),j}catch(l){throw l instanceof e.IllegalArgumentError?l:new e.UnhandledError(l)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.filter(resourceName, params[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],44:[function(a,b){function c(a,b,c){if(c=c||{},!f.store[a])throw new e.RuntimeError(g+a+" is not a registered resource!");if(!d.isString(b)&&!d.isNumber(b))throw new e.IllegalArgumentError(g+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});if(!d.isObject(c))throw new e.IllegalArgumentError(g+"options: Must be an object!",{options:{actual:typeof c,expected:"object"}});try{return b in f.store[a].index||!c.loadFromServer||this.find(a,b).then(null,function(a){throw a}),f.store[a].index[b]}catch(h){throw new e.UnhandledError(h)}}var d=a("utils"),e=a("errors"),f=a("services"),g="DS.get(resourceName, id[, options]): ";b.exports=c},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],45:[function(a,b){function c(a){return!(e.isEmpty(a.added)&&e.isEmpty(a.removed)&&e.isEmpty(a.changed))}function d(a,b){if(!g.store[a])throw new f.RuntimeError(h+a+" is not a registered resource!");if(!e.isString(b)&&!e.isNumber(b))throw new f.IllegalArgumentError(h+"id: Must be a string or a number!",{id:{actual:typeof b,expected:"string|number"}});try{return b in g.store[a].changes?c(g.store[a].changes[b]):!1}catch(d){throw new f.UnhandledError(d)}}var e=a("utils"),f=a("errors"),g=a("services"),h="DS.hasChanges(resourceName, id): ";b.exports=d},{errors:"hIh4e1",services:"cX8q+p",utils:"uE/lJt"}],46:[function(a,b){b.exports={defineResource:a("./defineResource"),eject:a("./eject"),filter:a("./filter"),get:a("./get"),inject:a("./inject"),lastModified:a("./lastModified"),lastSaved:a("./lastSaved"),digest:a("./digest"),changes:a("./changes"),previous:a("./previous"),hasChanges:a("./hasChanges")}},{"./changes":39,"./defineResource":40,"./digest":41,"./eject":42,"./filter":43,"./get":44,"./hasChanges":45,"./inject":47,"./lastModified":48,"./lastSaved":49,"./previous":50}],47:[function(a,b){function c(a,b){function d(b,c,d,h){try{var i=h(a.idAttribute);a.changes[i]=e.diffObjectFromOldObject(a.index[i],a.previous_attributes[i]),a.modified[i]=e.updateTimestamp(a.modified[i]),a.collectionModified=e.updateTimestamp(a.collectionModified),a.idAttribute in d&&g.$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 \""+a.name+'" resource is now in an undefined (probably broken) state.')}catch(j){throw new f.UnhandledError(j)}}var j=this;if(e.isArray(b))for(var k=0;k=b?a+1:b},deepFreeze:function c(a){if("function"==typeof Object.freeze){var b,d;Object.freeze(a);for(d in a)b=a[d],a.hasOwnProperty(d)&&"object"==typeof b&&!Object.isFrozen(b)&&c(b)}},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":3,"mout/array/filter":4,"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/string/makePath":25,"mout/string/upperCase":26}],utils:[function(a,b){b.exports=a("uE/lJt")},{}]},{},[53]); \ No newline at end of file diff --git a/index.html b/index.html deleted file mode 100644 index 9e59039..0000000 --- a/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -

add

-

change

-

remove

- - - - - - - diff --git a/src/datastore/http/index.js b/src/datastore/adapters/http/index.js similarity index 100% rename from src/datastore/http/index.js rename to src/datastore/adapters/http/index.js diff --git a/src/datastore/async_methods/create/index.js b/src/datastore/async_methods/create/index.js index 5dcc4b3..d06d62a 100644 --- a/src/datastore/async_methods/create/index.js +++ b/src/datastore/async_methods/create/index.js @@ -71,7 +71,7 @@ function create(resourceName, attrs) { return services.$q.promisify(resource.beforeCreate)(resourceName, attrs); }) .then(function (attrs) { - return _this.POST(utils.makePath(resource.baseUrl, resource.endpoint), attrs, null); + return services.adapters[resource.defaultAdapter].POST.apply(_this, [utils.makePath(resource.baseUrl, resource.endpoint), attrs, null]); }) .then(function (data) { return services.$q.promisify(resource.afterCreate)(resourceName, data); diff --git a/src/datastore/async_methods/destroy/index.js b/src/datastore/async_methods/destroy/index.js index 4dfe842..cafce4a 100644 --- a/src/datastore/async_methods/destroy/index.js +++ b/src/datastore/async_methods/destroy/index.js @@ -61,7 +61,7 @@ function destroy(resourceName, id) { return services.$q.promisify(resource.beforeDestroy)(resourceName, attrs); }) .then(function () { - return _this.DEL(utils.makePath(resource.baseUrl, resource.endpoint, id), null); + return services.adapters[resource.defaultAdapter].DEL(utils.makePath(resource.baseUrl, resource.endpoint, id), null); }) .then(function () { return services.$q.promisify(resource.afterDestroy)(resourceName, resource.index[id]); diff --git a/src/datastore/async_methods/find/index.js b/src/datastore/async_methods/find/index.js index 12ac463..cc4d98b 100644 --- a/src/datastore/async_methods/find/index.js +++ b/src/datastore/async_methods/find/index.js @@ -70,7 +70,7 @@ function find(resourceName, id, options) { if (!(id in resource.completedQueries)) { if (!(id in resource.pendingQueries)) { - promise = resource.pendingQueries[id] = _this.GET(utils.makePath(resource.baseUrl, resource.endpoint, id), null) + promise = resource.pendingQueries[id] = services.adapters[resource.defaultAdapter].GET(utils.makePath(resource.baseUrl, resource.endpoint, id), null) .then(function (data) { // Query is no longer pending delete resource.pendingQueries[id]; diff --git a/src/datastore/async_methods/findAll/index.js b/src/datastore/async_methods/findAll/index.js index 66d7b89..74296b0 100644 --- a/src/datastore/async_methods/findAll/index.js +++ b/src/datastore/async_methods/findAll/index.js @@ -40,7 +40,7 @@ function _findAll(resourceName, params, options) { if (!(queryHash in resource.pendingQueries)) { // This particular query has never even been made - resource.pendingQueries[queryHash] = _this.GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) + resource.pendingQueries[queryHash] = services.adapters[resource.defaultAdapter].GET(utils.makePath(resource.baseUrl, resource.endpoint), { params: params }) .then(function (data) { try { return processResults.apply(_this, [data, resourceName, queryHash]); diff --git a/src/datastore/async_methods/save/index.js b/src/datastore/async_methods/save/index.js index 3807964..194621a 100644 --- a/src/datastore/async_methods/save/index.js +++ b/src/datastore/async_methods/save/index.js @@ -78,7 +78,7 @@ function save(resourceName, id, options) { return services.$q.promisify(resource.beforeUpdate)(resourceName, attrs); }) .then(function (attrs) { - return _this.PUT(utils.makePath(resource.baseUrl, resource.endpoint, id), attrs, null); + return services.adapters[resource.defaultAdapter].PUT(utils.makePath(resource.baseUrl, resource.endpoint, id), attrs, null); }) .then(function (data) { return services.$q.promisify(resource.afterUpdate)(resourceName, data); diff --git a/src/datastore/index.js b/src/datastore/index.js index b8651db..192563c 100644 --- a/src/datastore/index.js +++ b/src/datastore/index.js @@ -1,7 +1,9 @@ var utils = require('utils'), errors = require('errors'), services = require('services'), - errorPrefix = 'DSProvider.config(options): '; + HttpAdapter = require('HttpAdapter'), + configErrorPrefix = 'DSProvider.config(options): ', + registerAdapterErrorPrefix = 'DSProvider.registerAdapter(name, adapter): '; /** * @doc method @@ -48,36 +50,76 @@ function config(options) { options = options || {}; if (!utils.isObject(options)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options: Must be an object!', { actual: typeof options, expected: 'object' }); } else if ('baseUrl' in options && !utils.isString(options.baseUrl)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.baseUrl: Must be a string!', { baseUrl: { actual: typeof options.baseUrl, expected: 'string' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.baseUrl: Must be a string!', { baseUrl: { actual: typeof options.baseUrl, expected: 'string' } }); } else if ('idAttribute' in options && !utils.isString(options.idAttribute)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.idAttribute: Must be a string!', { idAttribute: { actual: typeof options.idAttribute, expected: 'string' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.idAttribute: Must be a string!', { idAttribute: { actual: typeof options.idAttribute, expected: 'string' } }); } else if ('mergeStrategy' in options && !utils.isString(options.mergeStrategy)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.mergeStrategy: Must be a string!', { mergeStrategy: { actual: typeof options.mergeStrategy, expected: 'string' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.mergeStrategy: Must be a string!', { mergeStrategy: { actual: typeof options.mergeStrategy, expected: 'string' } }); } else if ('beforeValidate' in options && !utils.isFunction(options.beforeValidate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeValidate: Must be a function!', { beforeValidate: { actual: typeof options.beforeValidate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeValidate: Must be a function!', { beforeValidate: { actual: typeof options.beforeValidate, expected: 'function' } }); } else if ('validate' in options && !utils.isFunction(options.validate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.validate: Must be a function!', { validate: { actual: typeof options.validate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.validate: Must be a function!', { validate: { actual: typeof options.validate, expected: 'function' } }); } else if ('afterValidate' in options && !utils.isFunction(options.afterValidate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterValidate: Must be a function!', { afterValidate: { actual: typeof options.afterValidate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterValidate: Must be a function!', { afterValidate: { actual: typeof options.afterValidate, expected: 'function' } }); } else if ('beforeCreate' in options && !utils.isFunction(options.beforeCreate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeCreate: Must be a function!', { beforeCreate: { actual: typeof options.beforeCreate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeCreate: Must be a function!', { beforeCreate: { actual: typeof options.beforeCreate, expected: 'function' } }); } else if ('afterCreate' in options && !utils.isFunction(options.afterCreate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterCreate: Must be a function!', { afterCreate: { actual: typeof options.afterCreate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterCreate: Must be a function!', { afterCreate: { actual: typeof options.afterCreate, expected: 'function' } }); } else if ('beforeUpdate' in options && !utils.isFunction(options.beforeUpdate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeUpdate: Must be a function!', { beforeUpdate: { actual: typeof options.beforeUpdate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeUpdate: Must be a function!', { beforeUpdate: { actual: typeof options.beforeUpdate, expected: 'function' } }); } else if ('afterUpdate' in options && !utils.isFunction(options.afterUpdate)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterUpdate: Must be a function!', { afterUpdate: { actual: typeof options.afterUpdate, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterUpdate: Must be a function!', { afterUpdate: { actual: typeof options.afterUpdate, expected: 'function' } }); } else if ('beforeDestroy' in options && !utils.isFunction(options.beforeDestroy)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.beforeDestroy: Must be a function!', { beforeDestroy: { actual: typeof options.beforeDestroy, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.beforeDestroy: Must be a function!', { beforeDestroy: { actual: typeof options.beforeDestroy, expected: 'function' } }); } else if ('afterDestroy' in options && !utils.isFunction(options.afterDestroy)) { - throw new errors.IllegalArgumentError(errorPrefix + 'options.afterDestroy: Must be a function!', { afterDestroy: { actual: typeof options.afterDestroy, expected: 'function' } }); + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.afterDestroy: Must be a function!', { afterDestroy: { actual: typeof options.afterDestroy, expected: 'function' } }); + } else if ('defaultAdapter' in options && !utils.isString(options.defaultAdapter)) { + throw new errors.IllegalArgumentError(configErrorPrefix + 'options.defaultAdapter: Must be a function!', { defaultAdapter: { actual: typeof options.defaultAdapter, expected: 'string' } }); } services.config = new services.BaseConfig(options); } +/** + * @doc method + * @id DSProvider.methods:registerAdapter + * @name registerAdapter + * @description + * Register a new adapter. + * + * ## Signature: + * ```js + * DSProvider.registerAdapter(name, adapter); + * ``` + * + * ## Example: + * ```js + * DSProvider.registerAdapter('IndexedDBAdapter', {...}); + * ``` + * + * ## Throws: + * + * - `{IllegalArgumentError}` + * - `{RuntimeError}` + * + * @param {string} name The name of the new adapter. + * @param {object} adapter New adapter. + */ +function registerAdapter(name, adapter) { + + if (!utils.isString(name)) { + throw new errors.IllegalArgumentError(registerAdapterErrorPrefix + 'name: Must be a string!', { actual: typeof name, expected: 'string' }); + } else if (!utils.isObject(adapter)) { + throw new errors.IllegalArgumentError(registerAdapterErrorPrefix + 'adapter: Must be an object!', { actual: typeof adapter, expected: 'object' }); + } else if (services.adapters[name]) { + throw new errors.RuntimeError(registerAdapterErrorPrefix + name + ' is already registered!'); + } + + services.adapters[name] = adapter; +} + /** * @doc interface * @id DSProvider @@ -95,6 +137,18 @@ function DSProvider() { */ this.config = config; + config({}); + + /** + * @doc method + * @id DSProvider.methods:registerAdapter + * @name config + * @methodOf DSProvider + * @description + * See [DSProvider.registerAdapter](/documentation/api/api/DSProvider.methods:registerAdapter). + */ + this.registerAdapter = registerAdapter; + this.$get = ['$rootScope', '$log', '$http', '$q', function ($rootScope, $log, $http, $q) { services.$rootScope = $rootScope; @@ -102,6 +156,9 @@ function DSProvider() { services.$http = $http; services.$q = $q; services.store = {}; + services.adapters = {}; + + registerAdapter('HttpAdapter', HttpAdapter); /** * @doc interface @@ -110,14 +167,14 @@ function DSProvider() { * @description * Data store */ - var DS = {}; + var DS = { + HttpAdapter: HttpAdapter, + errors: errors + }; - utils.deepMixIn(DS, require('./http')); utils.deepMixIn(DS, require('./sync_methods')); utils.deepMixIn(DS, require('./async_methods')); - DS.errors = errors; - utils.deepFreeze(DS); var $dirtyCheckScope = $rootScope.$new(); diff --git a/src/datastore/services/index.js b/src/datastore/services/index.js index 87e2ae4..02b4854 100644 --- a/src/datastore/services/index.js +++ b/src/datastore/services/index.js @@ -3,9 +3,6 @@ function lifecycleNoop(resourceName, attrs, cb) { } var services = module.exports = { - config: { - idAttribute: 'id' - }, store: {}, BaseConfig: function (options) { if ('idAttribute' in options) { @@ -51,11 +48,15 @@ var services = module.exports = { if ('afterDestroy' in options) { this.afterDestroy = options.afterDestroy; } + + if ('defaultAdapter' in options) { + this.defaultAdapter = options.defaultAdapter; + } } }; - services.BaseConfig.prototype.idAttribute = 'id'; +services.BaseConfig.prototype.defaultAdapter = 'HttpAdapter'; services.BaseConfig.prototype.baseUrl = ''; services.BaseConfig.prototype.endpoint = ''; services.BaseConfig.prototype.beforeValidate = lifecycleNoop; diff --git a/test.js b/test.js deleted file mode 100644 index 7363c1b..0000000 --- a/test.js +++ /dev/null @@ -1,49 +0,0 @@ -var myApp = angular.module('myApp', ['jmdobry.angular-data']); - -myApp.controller('myCtrl', function ($scope, myService, DS) { - $scope.test = 'hello'; - $scope.add = myService.add; - $scope.change = myService.change; - $scope.remove = myService.remove; - $scope.$watch(function () { - return DS.lastModified('document', 46); - }, function () { - console.log('46 changed!'); - console.log(JSON.stringify(DS.get('document', 46), null, 2)); - console.log(DS.changes('document', 46)); - console.log(DS.previous('document', 46)); - }); -}); -myApp.service('myService', function (DS) { - DS.defineResource('document'); - DS.inject('document', { - id: 45, - title: 'test' - }); - DS.inject('document', { - id: 46, - title: 'test2' - }); - - var count = 1; - return { - add: function () { - console.log('myService.test()'); - var d = DS.get('document', 46); - d['newTitle' + count] = 'newTitle' + count; - count += 1; - }, - change: function () { - console.log('myService.test()'); - var d = DS.get('document', 46); - d.title = 'newTitle' + count; - count += 1; - }, - remove: function () { - console.log('myService.test()'); - var d = DS.get('document', 46); - delete d.title; - count += 1; - } - } -});