Comparing version 3.9.1 to 3.10.0
@@ -20,3 +20,3 @@ (function() { | ||
getAbsyncProvider.$inject = ["$injector", "$provide", "absyncCache"]; | ||
getAbsyncProvider.$inject = ["$provide", "absyncCache"]; | ||
angular | ||
@@ -33,4 +33,4 @@ .module( "absync" ) | ||
*/ | ||
function getAbsyncProvider( $injector, $provide, absyncCache ) { | ||
return new AbsyncProvider( $injector, $provide, absyncCache ); | ||
function getAbsyncProvider( $provide, absyncCache ) { | ||
return new AbsyncProvider( $provide, absyncCache ); | ||
} | ||
@@ -40,3 +40,2 @@ | ||
* Retrieves the absync provider. | ||
* @param {angular.auto.IInjectorService|Object} $injector The $injector provider. | ||
* @param {angular.auto.IProvideService|Object} $provide The $provide provider. | ||
@@ -46,7 +45,5 @@ * @param {Function} absyncCache The AbsyncCache service constructor. | ||
*/ | ||
function AbsyncProvider( $injector, $provide, absyncCache ) { | ||
function AbsyncProvider( $provide, absyncCache ) { | ||
var self = this; | ||
// Store a reference to the inject provider. | ||
self.__injector = $injector; | ||
// Store a reference to the provide provider. | ||
@@ -195,2 +192,9 @@ self.__provide = $provide; | ||
if( configuration.provideService === false ) { | ||
if( !configuration.injector ) { | ||
throw new Error( "Injector is missing in service configuration." ); | ||
} | ||
return configuration.injector.instantiate( self.__collections[ name ].constructor ); | ||
} | ||
// Register the new service. | ||
@@ -229,2 +233,7 @@ // Yes, we want an Angular "service" here, because we want it constructed with "new". | ||
if( configuration.provideService === false ) { | ||
var $injector = angular.injector( [ "ng", "absync" ] ); | ||
return $injector.instantiate( self.__collections[ name ].constructor ); | ||
} | ||
// Register the new service. | ||
@@ -237,2 +246,24 @@ // Yes, we want an Angular "service" here, because we want it constructed with "new". | ||
/** | ||
* Destroy a service. | ||
* @param {CacheService} service | ||
*/ | ||
AbsyncProvider.prototype.teardown = function AbsyncProvider$teardown( service ) { | ||
var self = this; | ||
var serviceDefinition = self.__entities[ service.name ] || self.__collections[ service.name ]; | ||
if( !serviceDefinition ) { | ||
throw new Error( "A service with the name '" + service.name + "' was not registered." ); | ||
} | ||
if( serviceDefinition.configuration.provideService !== false ) { | ||
throw new Error( "The service '" + service.name + "' was registered as an injectable service and can not be torn down." ); | ||
} | ||
delete self.__entities[ service.name ]; | ||
delete self.__collections[ service.name ]; | ||
service.teardown(); | ||
}; | ||
/** | ||
* Register an event listener that is called when a specific entity is received on the websocket. | ||
@@ -271,2 +302,18 @@ * @param {String} eventName The event name, usually the name of the entity. | ||
/** | ||
* Remove a previous registered listener. | ||
* @param {Function} callback | ||
*/ | ||
AbsyncProvider.prototype.off = function AbsyncProvider$off( eventName, callback ) { | ||
var self = this; | ||
for( var listenerIndex = 0; listenerIndex < self.__listeners.length; ++listenerIndex ) { | ||
if( self.__listeners[ listenerIndex ].eventName === eventName && self.__listeners[ listenerIndex ].callback === callback ) { | ||
self.__listeners.splice( listenerIndex, 1 ); | ||
self.__listeners[ listenerIndex ].unregister(); | ||
return; | ||
} | ||
} | ||
}; | ||
/** | ||
* Register an event listener on the websocket. | ||
@@ -412,5 +459,7 @@ * @param {String} eventName The event name, usually the name of the entity. | ||
// The scope on which we broadcast all our relevant events. | ||
self.scope = $rootScope; | ||
self.scope = configuration.scope || $rootScope; | ||
// Keep a reference to $q. | ||
self.q = $q; | ||
// Keep a reference to absync itself. | ||
self.absync = absync; | ||
@@ -422,6 +471,6 @@ // Prefix log messages with this string. | ||
// Otherwise, absync will wait for them to be published through the websocket channel. | ||
self.forceEarlyCacheUpdate = false; | ||
self.forceEarlyCacheUpdate = configuration.forceEarlyCacheUpdate || false; | ||
// Throws failures so that they can be handled outside of absync. | ||
self.throwFailures = true; | ||
self.throwFailures = typeof configuration.throwFailures !== "undefined" ? configuration.throwFailures : true; | ||
@@ -435,8 +484,14 @@ // Expose the serializer/deserializer so that they can be adjusted at any time. | ||
// Bind event handlers to this cache service, to ensure consistent this binding. | ||
self.__onEntityOnWebsocketBound = self.__onEntityOnWebsocket.bind( self ); | ||
self.__onCollectionOnWebsocketBound = self.__onCollectionOnWebsocket.bind( self ); | ||
self.__onEntityReceivedBound = self.__onEntityReceived.bind( self ); | ||
self.__onCollectionReceivedBound = self.__onCollectionReceived.bind( self ); | ||
// Tell absync to register an event listener for both our entity and its collection. | ||
// When we receive these events, we broadcast an equal Angular event on the root scope. | ||
// This way the user can already peek at the data (manipulating it is discouraged though). | ||
absync.on( configuration.entityName, self.__onEntityOnWebsocket.bind( self ) ); | ||
absync.on( configuration.entityName, self.__onEntityOnWebsocketBound ); | ||
if( configuration.collectionName ) { | ||
absync.on( configuration.collectionName, self.__onCollectionOnWebsocket.bind( self ) ); | ||
absync.on( configuration.collectionName, self.__onCollectionOnWebsocketBound ); | ||
} | ||
@@ -446,5 +501,7 @@ | ||
// This is where our own absync synchronization logic kicks in. | ||
$rootScope.$on( configuration.entityName, self.__onEntityReceived.bind( self ) ); | ||
self.__onEntityReceivedBound.unregister = $rootScope.$on( configuration.entityName, | ||
self.__onEntityReceivedBound ); | ||
if( configuration.collectionName ) { | ||
$rootScope.$on( configuration.collectionName, self.__onCollectionReceived.bind( self ) ); | ||
self.__onCollectionReceivedBound.unregister = $rootScope.$on( configuration.collectionName, | ||
self.__onCollectionReceivedBound ); | ||
} | ||
@@ -1468,2 +1525,16 @@ | ||
CacheService.prototype.teardown = function CacheService$teardown() { | ||
var self = this; | ||
self.absync.off( self.__onEntityOnWebsocketBound ); | ||
self.absync.off( self.__onCollectionOnWebsocketBound ); | ||
if( self.__onEntityReceivedBound.unregister ) { | ||
self.__onEntityReceivedBound.unregister(); | ||
} | ||
if( self.__onCollectionReceivedBound.unregister ) { | ||
self.__onCollectionReceivedBound.unregister(); | ||
} | ||
}; | ||
return CacheService; | ||
@@ -1470,0 +1541,0 @@ } |
@@ -15,3 +15,3 @@ (function() { | ||
getAbsyncProvider.$inject = ["$injector", "$provide", "absyncCache"]; | ||
getAbsyncProvider.$inject = ["$provide", "absyncCache"]; | ||
angular | ||
@@ -28,4 +28,4 @@ .module( "absync" ) | ||
*/ | ||
function getAbsyncProvider( $injector, $provide, absyncCache ) { | ||
return new AbsyncProvider( $injector, $provide, absyncCache ); | ||
function getAbsyncProvider( $provide, absyncCache ) { | ||
return new AbsyncProvider( $provide, absyncCache ); | ||
} | ||
@@ -35,3 +35,2 @@ | ||
* Retrieves the absync provider. | ||
* @param {angular.auto.IInjectorService|Object} $injector The $injector provider. | ||
* @param {angular.auto.IProvideService|Object} $provide The $provide provider. | ||
@@ -41,7 +40,5 @@ * @param {Function} absyncCache The AbsyncCache service constructor. | ||
*/ | ||
function AbsyncProvider( $injector, $provide, absyncCache ) { | ||
function AbsyncProvider( $provide, absyncCache ) { | ||
var self = this; | ||
// Store a reference to the inject provider. | ||
self.__injector = $injector; | ||
// Store a reference to the provide provider. | ||
@@ -190,2 +187,9 @@ self.__provide = $provide; | ||
if( configuration.provideService === false ) { | ||
if( !configuration.injector ) { | ||
throw new Error( "Injector is missing in service configuration." ); | ||
} | ||
return configuration.injector.instantiate( self.__collections[ name ].constructor ); | ||
} | ||
// Register the new service. | ||
@@ -224,2 +228,7 @@ // Yes, we want an Angular "service" here, because we want it constructed with "new". | ||
if( configuration.provideService === false ) { | ||
var $injector = angular.injector( [ "ng", "absync" ] ); | ||
return $injector.instantiate( self.__collections[ name ].constructor ); | ||
} | ||
// Register the new service. | ||
@@ -232,2 +241,24 @@ // Yes, we want an Angular "service" here, because we want it constructed with "new". | ||
/** | ||
* Destroy a service. | ||
* @param {CacheService} service | ||
*/ | ||
AbsyncProvider.prototype.teardown = function AbsyncProvider$teardown( service ) { | ||
var self = this; | ||
var serviceDefinition = self.__entities[ service.name ] || self.__collections[ service.name ]; | ||
if( !serviceDefinition ) { | ||
throw new Error( "A service with the name '" + service.name + "' was not registered." ); | ||
} | ||
if( serviceDefinition.configuration.provideService !== false ) { | ||
throw new Error( "The service '" + service.name + "' was registered as an injectable service and can not be torn down." ); | ||
} | ||
delete self.__entities[ service.name ]; | ||
delete self.__collections[ service.name ]; | ||
service.teardown(); | ||
}; | ||
/** | ||
* Register an event listener that is called when a specific entity is received on the websocket. | ||
@@ -266,2 +297,18 @@ * @param {String} eventName The event name, usually the name of the entity. | ||
/** | ||
* Remove a previous registered listener. | ||
* @param {Function} callback | ||
*/ | ||
AbsyncProvider.prototype.off = function AbsyncProvider$off( eventName, callback ) { | ||
var self = this; | ||
for( var listenerIndex = 0; listenerIndex < self.__listeners.length; ++listenerIndex ) { | ||
if( self.__listeners[ listenerIndex ].eventName === eventName && self.__listeners[ listenerIndex ].callback === callback ) { | ||
self.__listeners.splice( listenerIndex, 1 ); | ||
self.__listeners[ listenerIndex ].unregister(); | ||
return; | ||
} | ||
} | ||
}; | ||
/** | ||
* Register an event listener on the websocket. | ||
@@ -268,0 +315,0 @@ * @param {String} eventName The event name, usually the name of the entity. |
@@ -95,5 +95,7 @@ (function() { | ||
// The scope on which we broadcast all our relevant events. | ||
self.scope = $rootScope; | ||
self.scope = configuration.scope || $rootScope; | ||
// Keep a reference to $q. | ||
self.q = $q; | ||
// Keep a reference to absync itself. | ||
self.absync = absync; | ||
@@ -105,6 +107,6 @@ // Prefix log messages with this string. | ||
// Otherwise, absync will wait for them to be published through the websocket channel. | ||
self.forceEarlyCacheUpdate = false; | ||
self.forceEarlyCacheUpdate = configuration.forceEarlyCacheUpdate || false; | ||
// Throws failures so that they can be handled outside of absync. | ||
self.throwFailures = true; | ||
self.throwFailures = typeof configuration.throwFailures !== "undefined" ? configuration.throwFailures : true; | ||
@@ -118,8 +120,14 @@ // Expose the serializer/deserializer so that they can be adjusted at any time. | ||
// Bind event handlers to this cache service, to ensure consistent this binding. | ||
self.__onEntityOnWebsocketBound = self.__onEntityOnWebsocket.bind( self ); | ||
self.__onCollectionOnWebsocketBound = self.__onCollectionOnWebsocket.bind( self ); | ||
self.__onEntityReceivedBound = self.__onEntityReceived.bind( self ); | ||
self.__onCollectionReceivedBound = self.__onCollectionReceived.bind( self ); | ||
// Tell absync to register an event listener for both our entity and its collection. | ||
// When we receive these events, we broadcast an equal Angular event on the root scope. | ||
// This way the user can already peek at the data (manipulating it is discouraged though). | ||
absync.on( configuration.entityName, self.__onEntityOnWebsocket.bind( self ) ); | ||
absync.on( configuration.entityName, self.__onEntityOnWebsocketBound ); | ||
if( configuration.collectionName ) { | ||
absync.on( configuration.collectionName, self.__onCollectionOnWebsocket.bind( self ) ); | ||
absync.on( configuration.collectionName, self.__onCollectionOnWebsocketBound ); | ||
} | ||
@@ -129,5 +137,7 @@ | ||
// This is where our own absync synchronization logic kicks in. | ||
$rootScope.$on( configuration.entityName, self.__onEntityReceived.bind( self ) ); | ||
self.__onEntityReceivedBound.unregister = $rootScope.$on( configuration.entityName, | ||
self.__onEntityReceivedBound ); | ||
if( configuration.collectionName ) { | ||
$rootScope.$on( configuration.collectionName, self.__onCollectionReceived.bind( self ) ); | ||
self.__onCollectionReceivedBound.unregister = $rootScope.$on( configuration.collectionName, | ||
self.__onCollectionReceivedBound ); | ||
} | ||
@@ -1151,2 +1161,16 @@ | ||
CacheService.prototype.teardown = function CacheService$teardown() { | ||
var self = this; | ||
self.absync.off( self.__onEntityOnWebsocketBound ); | ||
self.absync.off( self.__onCollectionOnWebsocketBound ); | ||
if( self.__onEntityReceivedBound.unregister ) { | ||
self.__onEntityReceivedBound.unregister(); | ||
} | ||
if( self.__onCollectionReceivedBound.unregister ) { | ||
self.__onCollectionReceivedBound.unregister(); | ||
} | ||
}; | ||
return CacheService; | ||
@@ -1153,0 +1177,0 @@ } |
@@ -1,2 +0,2 @@ | ||
!function(){"use strict";angular.module("absync",[])}(),function(){"use strict";function e(e,n,r){return new t(e,n,r)}function t(e,t,n){var r=this;r.__injector=e,r.__provide=t,r.__absyncCache=n,r.__ioSocket=null,r.__registerLater=[],r.__listeners=[],r.__collections={},r.__entities={},r.debug=void 0}e.$inject=["$injector","$provide","absyncCache"],angular.module("absync").provider("absync",e),t.prototype.configure=function(e){var t=this;if("undefined"!=typeof e.socket){var n=e.socket,r="undefined"!=typeof io&&io.Socket&&n instanceof io.Socket;if("function"==typeof n)t.__ioSocket=new n;else{if(!r)throw new Error("configure() expects input to be a function or a socket.io Socket instance.");t.__ioSocket=n}t.__registerLater.length&&(angular.forEach(t.__registerLater,t.__registerListener.bind(t)),t.__registerLater=[])}"undefined"!=typeof e.debug&&(t.debug=e.debug||!1),t.debug&&(angular.forEach(t.__collections,function(e){e.configuration.debug=!0}),angular.forEach(t.__entities,function(e){e.configuration.debug=!0}))},t.prototype.disconnect=function(e){var t=this;e=e||!1,angular.forEach(t.__listeners,function(e){e.unregister(),delete e.unregister,t.__registerLater.push(e)}),t.__listeners=[],e&&(t.__ioSocket.disconnect(),t.__ioSocket=null)},t.prototype.__registerListener=function(e){var t=this;t.__listeners.push(e),e.unregister=t.__handleEntityEvent(e.eventName,e.callback)},t.prototype.collection=function(e,t){var n=this;if(n.__collections[e])throw new Error("A collection with the name '"+e+"' was already requested. Names for collections must be unique.");if(n.__entities[e])throw new Error("An entity with the name '"+e+"' was already requested. Names for collections must be unique and can't be shared with entities.");t.debug="undefined"==typeof t.debug?n.debug:t.debug,n.__collections[e]={constructor:n.__absyncCache(e,t),configuration:t},n.__provide.service(e,n.__collections[e].constructor)},t.prototype.entity=function(e,t){var n=this;if(n.__entities[e])throw new Error("An entity with the name '"+e+"' was already requested. Names for entities must be unique.");if(n.__collections[e])throw new Error("A collection with the name '"+e+"' was already requested. Names for entities must be unique and can't be shared with collections.");t.debug="undefined"==typeof t.debug?n.debug:t.debug,n.__entities[e]={constructor:n.__absyncCache(e,t),configuration:t},n.__provide.service(e,n.__entities[e].constructor)},t.prototype.on=function(e,t){var n=this;return n.__ioSocket?n.__registerListener({eventName:e,callback:t}):n.__registerLater.length>8192?null:(n.__registerLater.push({eventName:e,callback:t}),null)},t.prototype.__handleEntityEvent=function(e,t){var n=this;return n.__ioSocket.on(e,t),function(){n.__ioSocket.removeListener(e,t)}},t.prototype.emit=function(e,t,n){var r=this;if(!r.__ioSocket)throw new Error("socket.io is not initialized.");r.__ioSocket.emit(e,t,function(){n&&n.apply(r.__ioSocket,arguments)})},t.prototype.$get=function(){return this}}(),function(){"use strict";function e(e,n){function r(r,i,o,a,c,s,l,u){var h=this,d=n.injector||i,_=d.has(n.model);if(!_)throw new Error("Unable to construct the '"+e+"' service, because the referenced model '"+n.model+"' is not available for injection.");var f="string"==typeof n.model?d.get(n.model):n.model,y=f.serialize||n.serialize||t,p=f.deserialize||n.deserialize||t;h.name=e,h.configuration=n,h.entityCache=n.collectionName?[]:{},h.entityCache.__lookup={},h.__entityCacheRaw=null,h.enableRequestCache=!0,h.__requestCache={},h.allowBrowserCache=(angular.merge||angular.extend)({},{sync:!0,request:!0},n.allowBrowserCache),h.__uncached=u,h.httpInterface=r,h.logInterface=n.debug?o:l,h.scope=c,h.q=a,h.logPrefix="absync:"+e.toLocaleUpperCase()+" ",h.forceEarlyCacheUpdate=!1,h.throwFailures=!0,h.serializer=y,h.deserializer=p,h.filter=n.filter,s.on(n.entityName,h.__onEntityOnWebsocket.bind(h)),n.collectionName&&s.on(n.collectionName,h.__onCollectionOnWebsocket.bind(h)),c.$on(n.entityName,h.__onEntityReceived.bind(h)),n.collectionName&&c.$on(n.collectionName,h.__onCollectionReceived.bind(h)),h.logInterface.info(h.logPrefix+"service was instantiated.")}function i(e,t){var r=this;if((e||r.forceEarlyCacheUpdate)&&t.data[n.entityName]){var i=t.data[n.entityName];if(r.forceEarlyCacheUpdate){var o=r.deserializer(i);if(r.__updateCacheWithEntity(o),e)return o}if(e)return i}}function o(e){var t=this;if(t.logInterface.error(t.logPrefix+"Unable to store entity on the server.",e),t.logInterface.error(e),t.scope.$emit("absyncError",e),t.throwFailures)throw e}return r.$inject=["$http","$injector","$log","$q","$rootScope","absync","absyncNoopLog","absyncUncachedFilter"],r.prototype.__onEntityOnWebsocket=function(e){var t=this;t.scope.$broadcast(n.entityName,e[n.entityName])},r.prototype.__onCollectionOnWebsocket=function(e){var t=this;t.scope.$broadcast(n.collectionName,e[n.collectionName])},r.prototype.__onDataAvailable=function(e){function t(e){var t=r.deserializer(e);r.entityCache.push(t),r.entityCache.__lookup&&(r.entityCache.__lookup[t.id]=r.entityCache.length-1)}var r=this;if(Array.isArray(r.entityCache))angular.forEach(e[n.collectionName],t),r.scope.$broadcast("collectionNew",{service:r,cache:r.entityCache});else{var i=r.deserializer(e[n.entityName]);r.__updateCacheWithEntity(i)}return r.entityCache},r.prototype.__onEntityReceived=function(e,t){var r=this,i=t;if(null===n.collectionUri&&null===n.entityUri){var o={service:r,cache:r.entityCache,entity:i};return r.scope.$broadcast("beforeEntityNew",o),r.scope.$broadcast("entityNew",o),r.q.when()}return r.__entityCacheRaw&&r.__entityCacheRaw[n.collectionName||n.entityName]?1===Object.keys(i).length&&i.hasOwnProperty("id")?(r.logInterface.info(r.logPrefix+"Entity was deleted from the server. Updating cache…"),r.__cacheMaintain(r.__entityCacheRaw[n.collectionName||n.entityName],i,"delete",!1),r.__removeEntityFromCache(i.id)):(r.logInterface.debug(r.logPrefix+"Entity was updated on the server. Updating cache…"),r.__cacheMaintain(r.__entityCacheRaw[n.collectionName||n.entityName],i,"update",!1),r.__updateCacheWithEntity(r.deserializer(i))):r.ensureLoaded().then(function(){return r.read(i.id)})},r.prototype.__onCollectionReceived=function(e,t){function n(e){var t=r.deserializer(e);r.__updateCacheWithEntity(t)}var r=this,i=t;r.entityCache.length=0,angular.forEach(i,n)},r.prototype.ensureLoaded=function(e){function t(e){if(!e.data[n.collectionName])throw new Error("The response from the server was not in the expected format. It should have a member named '"+n.collectionName+"'.");return a.__entityCacheRaw=e.data,a.entityCache.splice(0,a.entityCache.length),a.__onDataAvailable(e.data)}function r(e){if(a.logInterface.error(a.logPrefix+"Unable to retrieve the collection from the server.",e),a.__entityCacheRaw=null,a.scope.$emit("absyncError",e),a.throwFailures)throw e}function i(e){if(!e.data[n.entityName])throw new Error("The response from the server was not in the expected format. It should have a member named '"+n.entityName+"'.");a.__entityCacheRaw=e.data,a.__onDataAvailable(e.data)}function o(e){if(a.logInterface.error(a.logPrefix+"Unable to retrieve the entity from the server.",e),a.__entityCacheRaw=null,a.scope.$emit("absyncError",e),a.throwFailures)throw e}var a=this;if(e=e===!0,e&&delete a.__loading,a.__loading)return a.__loading;if(null===a.__entityCacheRaw||e){if(n.collectionName&&n.collectionUri)a.logInterface.info(a.logPrefix+"Retrieving '"+n.collectionName+"' collection…"),a.__loading=a.httpInterface.get(a.allowBrowserCache.sync?n.collectionUri:a.__uncached(n.collectionUri)).then(t,r);else{if(!n.entityName||!n.entityUri)return a.q.when([]);a.__loading=a.httpInterface.get(a.allowBrowserCache.sync?n.entityUri:a.__uncached(n.entityUri)).then(i,o)}return a.__loading}return a.q.when(a.entityCache)},r.prototype.seed=function(e){var t=this;return t.__entityCacheRaw=e,t.__onDataAvailable(t.__entityCacheRaw)},r.prototype.phantom=function(e,t){var n=this;return n.has(e.id)?n.read(e.id):(t=null===t?Number.POSITIVE_INFINITY:t,t="undefined"==typeof t?null:t,n.q.when(n.__onEntityReceived(null,e)).then(function(){return new n.q(function(r,i){if(null===t)return r(e);var o=null,a=null;t<Number.POSITIVE_INFINITY&&(o=setTimeout(function(){a&&a(),i(new Error("Phantom was not replaced in time."))},t)),a=n.scope.$on("entityUpdated",function(t,n){n.entity.id===e.id&&(a(),clearTimeout(o),a=null,o=null,r(n.entity))})})}))},r.prototype.sync=function(){var e=this;return e.__entityCacheRaw=null,e.ensureLoaded(!0)},r.prototype.has=function(e){var t=this;return t.entityCache.__lookup.hasOwnProperty(e)},r.prototype.read=function(e,t){function r(e){if(!e.data[n.entityName])throw new Error("The response from the server was not in the expected format. It should have a member named '"+n.entityName+"'.");var t=e.data[n.entityName];o.__cacheMaintain(o.__entityCacheRaw[n.collectionName||n.entityName],t,"update",!1);var r=o.deserializer(t);return o.__updateCacheWithEntity(r),r}function i(t){if(o.logInterface.error(o.logPrefix+"Unable to retrieve entity with ID '"+e+"' from the server.",t),o.scope.$emit("absyncError",t),o.throwFailures)throw t}var o=this;if(t=t===!0,o.logInterface.debug(o.logPrefix+"Requesting entity '"+e+"' (forceReload:"+t+")…"),!t){var a=0;o.entityCache.__lookup&&(a=o.entityCache.__lookup.hasOwnProperty(e)?o.entityCache.__lookup[e]:o.entityCache.length);for(var c=o.entityCache[a];a<o.entityCache.length;++a,c=o.entityCache[a])if(c.id===e)return o.logInterface.debug(o.logPrefix+"Requested entity '"+e+"' is served from cache."),o.q.when(c)}return o.logInterface.debug(o.logPrefix+"Requested entity '"+e+"' is fetched from backend."),o.__requestEntity(e).then(r,i)},r.prototype.__requestEntity=function(e){function t(e,t){return delete r.__requestCache[e],t}var r=this;if(r.enableRequestCache&&r.__requestCache&&r.__requestCache[e])return r.logInterface.debug(r.logPrefix+"Entity request '"+e+"' served from request cache."),r.__requestCache[e];r.__entityCacheRaw=r.__entityCacheRaw||{},r.__entityCacheRaw[n.collectionName]=r.__entityCacheRaw[n.collectionName]||[];var i=n.entityUri+(e?"/"+e:""),o=r.httpInterface.get(r.allowBrowserCache.request?i:r.__uncached(i)).then(t.bind(r,e));return r.enableRequestCache&&r.__requestCache&&(r.__requestCache[e]=o),o},r.prototype.update=function(e,t){var r=this;t=t||!1;var a=r.reduceComplex(e),c=r.serializer(a),s={};return s[n.entityName]=c,"undefined"!=typeof e.id?r.httpInterface.put(n.entityUri+"/"+e.id,s).then(i.bind(r,t),o.bind(r)):r.httpInterface.post(n.collectionUri,s).then(i.bind(r,t),o.bind(r))},r.prototype.patch=function(e,t){var r=this;t=t||!1;var a=r.reduceComplex(e),c=r.serializer(a),s={};if(s[n.entityName]=c,"undefined"!=typeof e.id)return r.httpInterface.patch(n.entityUri+"/"+e.id,s).then(i.bind(r,t),o.bind(r));throw new Error("Attempted to patch an entity that was never stored on the server.")},r.prototype.create=r.prototype.update,r.prototype["delete"]=function(e){function t(t){return i.__cacheMaintain(i.__entityCacheRaw[n.collectionName||n.entityName],e,"delete",!1),i.__removeEntityFromCache(o)}function r(e){if(i.logInterface.error(e.data),i.scope.$emit("absyncError",e),i.throwFailures)throw e}var i=this,o=e.id;return i.httpInterface["delete"](n.entityUri+"/"+o).then(t)["catch"](r)},r.prototype.__updateCacheWithEntity=function(e){var t=this;return t.logInterface.info(t.logPrefix+"Updating entity '"+(e.id||t.name)+"' in cache…",e),t.__cacheMaintain(t.entityCache,e,"update",!0)},r.prototype.__cacheMaintain=function(e,t,n,r){var i=this,o=0,a=e[o];if(i.filter&&!i.filter(t))return void i.logInterface.info(i.logPrefix+"Entity '"+(t.id||i.name)+"' was filtered.",t);switch(e.__lookup&&(o=e.__lookup.hasOwnProperty(t.id)?e.__lookup[t.id]:e.length,a=e[o]),n){case"update":if(!Array.isArray(e))return r&&i.scope.$broadcast("beforeEntityUpdated",{service:i,cache:e,entity:e,updated:t}),"function"==typeof e.copyFrom?e.copyFrom(t):angular.extend(e,t),void i.scope.$broadcast("entityUpdated",{service:i,cache:e,entity:e});var c=!1;for(angular.noop;o<e.length;++o,a=e[o])if(a.id===t.id){r&&i.scope.$broadcast("beforeEntityUpdated",{service:i,cache:e,entity:e[o],updated:t});var s=e[o];"function"==typeof s.copyFrom?s.copyFrom(t):angular.extend(s,t),c=!0,r&&i.scope.$broadcast("entityUpdated",{service:i,cache:e,entity:e[o]});break}c||(r&&i.scope.$broadcast("beforeEntityNew",{service:i,cache:e,entity:t}),e.push(t),e.__lookup&&(e.__lookup[t.id]=e.length-1),r&&i.scope.$broadcast("entityNew",{service:i,cache:e,entity:t}));break;case"delete":for(angular.noop;o<e.length;++o,a=e[o])if(a.id===t.id){if(r&&i.scope.$broadcast("beforeEntityRemoved",{service:i,cache:e,entity:a}),e.splice(o,1),e.__lookup){delete e.__lookup[t.id];for(var l in e.__lookup)o<=e.__lookup[l]&&--e.__lookup[l]}r&&i.scope.$broadcast("entityRemoved",{service:i,cache:e,entity:a});break}}},r.prototype.__removeEntityFromCache=function(e){var t=this;return t.__cacheMaintain(t.entityCache,{id:e},"delete",!0)},r.prototype.lookupTableById=function(){for(var e=this,t=[],n=0;n<e.entityCache.length;++n)t[e.entityCache[n].id]=e.entityCache[n];return t},r.prototype.reduceComplex=function(e,t){var n=this,r=t?[]:{};for(var i in e)e.hasOwnProperty(i)&&(Array.isArray(e[i])?r[i]=n.reduceComplex(e[i],!0):e[i]&&e[i].id?r[i]=e[i].id:r[i]=e[i]);return r},r.prototype.populateComplex=function(e,t,n,r){function i(i,o){function s(n){return e[t][o]=n,r.crossLink&&a(n,e),e}if("string"!=typeof e[t][o]){if(!r.force||"object"!=typeof e[t][o]||"string"!=typeof e[t][o].id){if(c.throwFailures)throw new Error("The referenced entity did not have an 'id' property that would be expected.");return c.q.when(!1)}e[t][o]=e[t][o].id}return n.read(e[t][o]).then(s)}function o(n){return e[t]=n,r.crossLink&&a(n,e),e}function a(e,t){if(Array.isArray(e[r.crossLinkProperty])){var n=e[r.crossLinkProperty].indexOf(t.id);if(-1<n)return void(e[r.crossLinkProperty][n]=t);for(var i=0,o=e[r.crossLinkProperty][0];i<e[r.crossLinkProperty].length;++i,o=e[r.crossLinkProperty][i]){if(o===t)return;if(o.id===t.id)return void(e[r.crossLinkProperty][i]=t)}return void e[r.crossLinkProperty].push(t)}e[r.crossLinkProperty]=t}var c=this;if(r=r||{},"boolean"==typeof r&&(c.logInterface.warn("Argument 'force' is deprecated. Provide an options hash instead."),r={force:r}),r.force=r.force||!1,r.crossLink=r.crossLink||!1,r.crossLinkProperty=r.crossLinkProperty||"",r.crossLink&&!r.crossLinkProperty&&(c.logInterface.warn("Option 'crossLink' given without 'crossLinkProperty'. Cross-linking will be disabled."),r.crossLink=!1),Array.isArray(e[t])){var s=e[t].map(i);return c.q.all(s)}if("string"!=typeof e[t]){if(!r.force||"object"!=typeof e[t]||"string"!=typeof e[t].id){if(c.throwFailures)throw new Error("The referenced entity did not have an 'id' property that would be expected.");return c.q.when(!1)}e[t]=e[t].id}return n.read(e[t]).then(o)},r.prototype.reset=function(){var e=this;e.entityCache=e.configuration.collectionName?[]:{},e.entityCache.__lookup=e.entityCache.__lookup||{},e.__entityCacheRaw=null,e.__requestCache={}},r}function t(e){return e}angular.module("absync").constant("absyncCache",e)}(),function(){"use strict";function e(){return t}function t(e,t,n,r,i,o,a,c,s,l){this.model=e,this.collectionUri=t,this.entityUri=n;var u=e.prototype.constructor.name.toLowerCase();this.collectionName=r||u+"s",this.entityName=i||u,this.deserialize=o||void 0,this.serialize=a||void 0,this.injector=c||void 0,this.debug=s||!1,this.allowBrowserCache=angular.merge({},{sync:!0,request:!0},l)}angular.module("absync").service("AbsyncServiceConfiguration",e)}(),function(){"use strict";angular.module("absync").constant("absyncNoopLog",{debug:angular.noop,info:angular.noop,warn:angular.noop,error:angular.noop})}(),function(){"use strict";function e(){function e(e){if(!e)return e;var t=-1<e.indexOf("?")?"&":"?",n=(new Date).getTime();return e+t+"t="+n}return e}angular.module("absync").filter("absyncUncached",e)}(); | ||
!function(){"use strict";angular.module("absync",[])}(),function(){"use strict";function e(e,n){return new t(e,n)}function t(e,t){var n=this;n.__provide=e,n.__absyncCache=t,n.__ioSocket=null,n.__registerLater=[],n.__listeners=[],n.__collections={},n.__entities={},n.debug=void 0}e.$inject=["$provide","absyncCache"],angular.module("absync").provider("absync",e),t.prototype.configure=function(e){var t=this;if("undefined"!=typeof e.socket){var n=e.socket,r="undefined"!=typeof io&&io.Socket&&n instanceof io.Socket;if("function"==typeof n)t.__ioSocket=new n;else{if(!r)throw new Error("configure() expects input to be a function or a socket.io Socket instance.");t.__ioSocket=n}t.__registerLater.length&&(angular.forEach(t.__registerLater,t.__registerListener.bind(t)),t.__registerLater=[])}"undefined"!=typeof e.debug&&(t.debug=e.debug||!1),t.debug&&(angular.forEach(t.__collections,function(e){e.configuration.debug=!0}),angular.forEach(t.__entities,function(e){e.configuration.debug=!0}))},t.prototype.disconnect=function(e){var t=this;e=e||!1,angular.forEach(t.__listeners,function(e){e.unregister(),delete e.unregister,t.__registerLater.push(e)}),t.__listeners=[],e&&(t.__ioSocket.disconnect(),t.__ioSocket=null)},t.prototype.__registerListener=function(e){var t=this;t.__listeners.push(e),e.unregister=t.__handleEntityEvent(e.eventName,e.callback)},t.prototype.collection=function(e,t){var n=this;if(n.__collections[e])throw new Error("A collection with the name '"+e+"' was already requested. Names for collections must be unique.");if(n.__entities[e])throw new Error("An entity with the name '"+e+"' was already requested. Names for collections must be unique and can't be shared with entities.");if(t.debug="undefined"==typeof t.debug?n.debug:t.debug,n.__collections[e]={constructor:n.__absyncCache(e,t),configuration:t},t.provideService===!1){if(!t.injector)throw new Error("Injector is missing in service configuration.");return t.injector.instantiate(n.__collections[e].constructor)}n.__provide.service(e,n.__collections[e].constructor)},t.prototype.entity=function(e,t){var n=this;if(n.__entities[e])throw new Error("An entity with the name '"+e+"' was already requested. Names for entities must be unique.");if(n.__collections[e])throw new Error("A collection with the name '"+e+"' was already requested. Names for entities must be unique and can't be shared with collections.");if(t.debug="undefined"==typeof t.debug?n.debug:t.debug,n.__entities[e]={constructor:n.__absyncCache(e,t),configuration:t},t.provideService===!1){var r=angular.injector(["ng","absync"]);return r.instantiate(n.__collections[e].constructor)}n.__provide.service(e,n.__entities[e].constructor)},t.prototype.teardown=function(e){var t=this,n=t.__entities[e.name]||t.__collections[e.name];if(!n)throw new Error("A service with the name '"+e.name+"' was not registered.");if(n.configuration.provideService!==!1)throw new Error("The service '"+e.name+"' was registered as an injectable service and can not be torn down.");delete t.__entities[e.name],delete t.__collections[e.name],e.teardown()},t.prototype.on=function(e,t){var n=this;return n.__ioSocket?n.__registerListener({eventName:e,callback:t}):n.__registerLater.length>8192?null:(n.__registerLater.push({eventName:e,callback:t}),null)},t.prototype.off=function(e,t){for(var n=this,r=0;r<n.__listeners.length;++r)if(n.__listeners[r].eventName===e&&n.__listeners[r].callback===t)return n.__listeners.splice(r,1),void n.__listeners[r].unregister()},t.prototype.__handleEntityEvent=function(e,t){var n=this;return n.__ioSocket.on(e,t),function(){n.__ioSocket.removeListener(e,t)}},t.prototype.emit=function(e,t,n){var r=this;if(!r.__ioSocket)throw new Error("socket.io is not initialized.");r.__ioSocket.emit(e,t,function(){n&&n.apply(r.__ioSocket,arguments)})},t.prototype.$get=function(){return this}}(),function(){"use strict";function e(e,n){function r(r,o,i,a,c,s,l,u){var d=this,h=n.injector||o,_=h.has(n.model);if(!_)throw new Error("Unable to construct the '"+e+"' service, because the referenced model '"+n.model+"' is not available for injection.");var f="string"==typeof n.model?h.get(n.model):n.model,y=f.serialize||n.serialize||t,p=f.deserialize||n.deserialize||t;d.name=e,d.configuration=n,d.entityCache=n.collectionName?[]:{},d.entityCache.__lookup={},d.__entityCacheRaw=null,d.enableRequestCache=!0,d.__requestCache={},d.allowBrowserCache=(angular.merge||angular.extend)({},{sync:!0,request:!0},n.allowBrowserCache),d.__uncached=u,d.httpInterface=r,d.logInterface=n.debug?i:l,d.scope=n.scope||c,d.q=a,d.absync=s,d.logPrefix="absync:"+e.toLocaleUpperCase()+" ",d.forceEarlyCacheUpdate=n.forceEarlyCacheUpdate||!1,d.throwFailures="undefined"==typeof n.throwFailures||n.throwFailures,d.serializer=y,d.deserializer=p,d.filter=n.filter,d.__onEntityOnWebsocketBound=d.__onEntityOnWebsocket.bind(d),d.__onCollectionOnWebsocketBound=d.__onCollectionOnWebsocket.bind(d),d.__onEntityReceivedBound=d.__onEntityReceived.bind(d),d.__onCollectionReceivedBound=d.__onCollectionReceived.bind(d),s.on(n.entityName,d.__onEntityOnWebsocketBound),n.collectionName&&s.on(n.collectionName,d.__onCollectionOnWebsocketBound),d.__onEntityReceivedBound.unregister=c.$on(n.entityName,d.__onEntityReceivedBound),n.collectionName&&(d.__onCollectionReceivedBound.unregister=c.$on(n.collectionName,d.__onCollectionReceivedBound)),d.logInterface.info(d.logPrefix+"service was instantiated.")}function o(e,t){var r=this;if((e||r.forceEarlyCacheUpdate)&&t.data[n.entityName]){var o=t.data[n.entityName];if(r.forceEarlyCacheUpdate){var i=r.deserializer(o);if(r.__updateCacheWithEntity(i),e)return i}if(e)return o}}function i(e){var t=this;if(t.logInterface.error(t.logPrefix+"Unable to store entity on the server.",e),t.logInterface.error(e),t.scope.$emit("absyncError",e),t.throwFailures)throw e}return r.$inject=["$http","$injector","$log","$q","$rootScope","absync","absyncNoopLog","absyncUncachedFilter"],r.prototype.__onEntityOnWebsocket=function(e){var t=this;t.scope.$broadcast(n.entityName,e[n.entityName])},r.prototype.__onCollectionOnWebsocket=function(e){var t=this;t.scope.$broadcast(n.collectionName,e[n.collectionName])},r.prototype.__onDataAvailable=function(e){function t(e){var t=r.deserializer(e);r.entityCache.push(t),r.entityCache.__lookup&&(r.entityCache.__lookup[t.id]=r.entityCache.length-1)}var r=this;if(Array.isArray(r.entityCache))angular.forEach(e[n.collectionName],t),r.scope.$broadcast("collectionNew",{service:r,cache:r.entityCache});else{var o=r.deserializer(e[n.entityName]);r.__updateCacheWithEntity(o)}return r.entityCache},r.prototype.__onEntityReceived=function(e,t){var r=this,o=t;if(null===n.collectionUri&&null===n.entityUri){var i={service:r,cache:r.entityCache,entity:o};return r.scope.$broadcast("beforeEntityNew",i),r.scope.$broadcast("entityNew",i),r.q.when()}return r.__entityCacheRaw&&r.__entityCacheRaw[n.collectionName||n.entityName]?1===Object.keys(o).length&&o.hasOwnProperty("id")?(r.logInterface.info(r.logPrefix+"Entity was deleted from the server. Updating cache…"),r.__cacheMaintain(r.__entityCacheRaw[n.collectionName||n.entityName],o,"delete",!1),r.__removeEntityFromCache(o.id)):(r.logInterface.debug(r.logPrefix+"Entity was updated on the server. Updating cache…"),r.__cacheMaintain(r.__entityCacheRaw[n.collectionName||n.entityName],o,"update",!1),r.__updateCacheWithEntity(r.deserializer(o))):r.ensureLoaded().then(function(){return r.read(o.id)})},r.prototype.__onCollectionReceived=function(e,t){function n(e){var t=r.deserializer(e);r.__updateCacheWithEntity(t)}var r=this,o=t;r.entityCache.length=0,angular.forEach(o,n)},r.prototype.ensureLoaded=function(e){function t(e){if(!e.data[n.collectionName])throw new Error("The response from the server was not in the expected format. It should have a member named '"+n.collectionName+"'.");return a.__entityCacheRaw=e.data,a.entityCache.splice(0,a.entityCache.length),a.__onDataAvailable(e.data)}function r(e){if(a.logInterface.error(a.logPrefix+"Unable to retrieve the collection from the server.",e),a.__entityCacheRaw=null,a.scope.$emit("absyncError",e),a.throwFailures)throw e}function o(e){if(!e.data[n.entityName])throw new Error("The response from the server was not in the expected format. It should have a member named '"+n.entityName+"'.");a.__entityCacheRaw=e.data,a.__onDataAvailable(e.data)}function i(e){if(a.logInterface.error(a.logPrefix+"Unable to retrieve the entity from the server.",e),a.__entityCacheRaw=null,a.scope.$emit("absyncError",e),a.throwFailures)throw e}var a=this;if(e=e===!0,e&&delete a.__loading,a.__loading)return a.__loading;if(null===a.__entityCacheRaw||e){if(n.collectionName&&n.collectionUri)a.logInterface.info(a.logPrefix+"Retrieving '"+n.collectionName+"' collection…"),a.__loading=a.httpInterface.get(a.allowBrowserCache.sync?n.collectionUri:a.__uncached(n.collectionUri)).then(t,r);else{if(!n.entityName||!n.entityUri)return a.q.when([]);a.__loading=a.httpInterface.get(a.allowBrowserCache.sync?n.entityUri:a.__uncached(n.entityUri)).then(o,i)}return a.__loading}return a.q.when(a.entityCache)},r.prototype.seed=function(e){var t=this;return t.__entityCacheRaw=e,t.__onDataAvailable(t.__entityCacheRaw)},r.prototype.phantom=function(e,t){var n=this;return n.has(e.id)?n.read(e.id):(t=null===t?Number.POSITIVE_INFINITY:t,t="undefined"==typeof t?null:t,n.q.when(n.__onEntityReceived(null,e)).then(function(){return new n.q(function(r,o){if(null===t)return r(e);var i=null,a=null;t<Number.POSITIVE_INFINITY&&(i=setTimeout(function(){a&&a(),o(new Error("Phantom was not replaced in time."))},t)),a=n.scope.$on("entityUpdated",function(t,n){n.entity.id===e.id&&(a(),clearTimeout(i),a=null,i=null,r(n.entity))})})}))},r.prototype.sync=function(){var e=this;return e.__entityCacheRaw=null,e.ensureLoaded(!0)},r.prototype.has=function(e){var t=this;return t.entityCache.__lookup.hasOwnProperty(e)},r.prototype.read=function(e,t){function r(e){if(!e.data[n.entityName])throw new Error("The response from the server was not in the expected format. It should have a member named '"+n.entityName+"'.");var t=e.data[n.entityName];i.__cacheMaintain(i.__entityCacheRaw[n.collectionName||n.entityName],t,"update",!1);var r=i.deserializer(t);return i.__updateCacheWithEntity(r),r}function o(t){if(i.logInterface.error(i.logPrefix+"Unable to retrieve entity with ID '"+e+"' from the server.",t),i.scope.$emit("absyncError",t),i.throwFailures)throw t}var i=this;if(t=t===!0,i.logInterface.debug(i.logPrefix+"Requesting entity '"+e+"' (forceReload:"+t+")…"),!t){var a=0;i.entityCache.__lookup&&(a=i.entityCache.__lookup.hasOwnProperty(e)?i.entityCache.__lookup[e]:i.entityCache.length);for(var c=i.entityCache[a];a<i.entityCache.length;++a,c=i.entityCache[a])if(c.id===e)return i.logInterface.debug(i.logPrefix+"Requested entity '"+e+"' is served from cache."),i.q.when(c)}return i.logInterface.debug(i.logPrefix+"Requested entity '"+e+"' is fetched from backend."),i.__requestEntity(e).then(r,o)},r.prototype.__requestEntity=function(e){function t(e,t){return delete r.__requestCache[e],t}var r=this;if(r.enableRequestCache&&r.__requestCache&&r.__requestCache[e])return r.logInterface.debug(r.logPrefix+"Entity request '"+e+"' served from request cache."),r.__requestCache[e];r.__entityCacheRaw=r.__entityCacheRaw||{},r.__entityCacheRaw[n.collectionName]=r.__entityCacheRaw[n.collectionName]||[];var o=n.entityUri+(e?"/"+e:""),i=r.httpInterface.get(r.allowBrowserCache.request?o:r.__uncached(o)).then(t.bind(r,e));return r.enableRequestCache&&r.__requestCache&&(r.__requestCache[e]=i),i},r.prototype.update=function(e,t){var r=this;t=t||!1;var a=r.reduceComplex(e),c=r.serializer(a),s={};return s[n.entityName]=c,"undefined"!=typeof e.id?r.httpInterface.put(n.entityUri+"/"+e.id,s).then(o.bind(r,t),i.bind(r)):r.httpInterface.post(n.collectionUri,s).then(o.bind(r,t),i.bind(r))},r.prototype.patch=function(e,t){var r=this;t=t||!1;var a=r.reduceComplex(e),c=r.serializer(a),s={};if(s[n.entityName]=c,"undefined"!=typeof e.id)return r.httpInterface.patch(n.entityUri+"/"+e.id,s).then(o.bind(r,t),i.bind(r));throw new Error("Attempted to patch an entity that was never stored on the server.")},r.prototype.create=r.prototype.update,r.prototype["delete"]=function(e){function t(t){return o.__cacheMaintain(o.__entityCacheRaw[n.collectionName||n.entityName],e,"delete",!1),o.__removeEntityFromCache(i)}function r(e){if(o.logInterface.error(e.data),o.scope.$emit("absyncError",e),o.throwFailures)throw e}var o=this,i=e.id;return o.httpInterface["delete"](n.entityUri+"/"+i).then(t)["catch"](r)},r.prototype.__updateCacheWithEntity=function(e){var t=this;return t.logInterface.info(t.logPrefix+"Updating entity '"+(e.id||t.name)+"' in cache…",e),t.__cacheMaintain(t.entityCache,e,"update",!0)},r.prototype.__cacheMaintain=function(e,t,n,r){var o=this,i=0,a=e[i];if(o.filter&&!o.filter(t))return void o.logInterface.info(o.logPrefix+"Entity '"+(t.id||o.name)+"' was filtered.",t);switch(e.__lookup&&(i=e.__lookup.hasOwnProperty(t.id)?e.__lookup[t.id]:e.length,a=e[i]),n){case"update":if(!Array.isArray(e))return r&&o.scope.$broadcast("beforeEntityUpdated",{service:o,cache:e,entity:e,updated:t}),"function"==typeof e.copyFrom?e.copyFrom(t):angular.extend(e,t),void o.scope.$broadcast("entityUpdated",{service:o,cache:e,entity:e});var c=!1;for(angular.noop;i<e.length;++i,a=e[i])if(a.id===t.id){r&&o.scope.$broadcast("beforeEntityUpdated",{service:o,cache:e,entity:e[i],updated:t});var s=e[i];"function"==typeof s.copyFrom?s.copyFrom(t):angular.extend(s,t),c=!0,r&&o.scope.$broadcast("entityUpdated",{service:o,cache:e,entity:e[i]});break}c||(r&&o.scope.$broadcast("beforeEntityNew",{service:o,cache:e,entity:t}),e.push(t),e.__lookup&&(e.__lookup[t.id]=e.length-1),r&&o.scope.$broadcast("entityNew",{service:o,cache:e,entity:t}));break;case"delete":for(angular.noop;i<e.length;++i,a=e[i])if(a.id===t.id){if(r&&o.scope.$broadcast("beforeEntityRemoved",{service:o,cache:e,entity:a}),e.splice(i,1),e.__lookup){delete e.__lookup[t.id];for(var l in e.__lookup)i<=e.__lookup[l]&&--e.__lookup[l]}r&&o.scope.$broadcast("entityRemoved",{service:o,cache:e,entity:a});break}}},r.prototype.__removeEntityFromCache=function(e){var t=this;return t.__cacheMaintain(t.entityCache,{id:e},"delete",!0)},r.prototype.lookupTableById=function(){for(var e=this,t=[],n=0;n<e.entityCache.length;++n)t[e.entityCache[n].id]=e.entityCache[n];return t},r.prototype.reduceComplex=function(e,t){var n=this,r=t?[]:{};for(var o in e)e.hasOwnProperty(o)&&(Array.isArray(e[o])?r[o]=n.reduceComplex(e[o],!0):e[o]&&e[o].id?r[o]=e[o].id:r[o]=e[o]);return r},r.prototype.populateComplex=function(e,t,n,r){function o(o,i){function s(n){return e[t][i]=n,r.crossLink&&a(n,e),e}if("string"!=typeof e[t][i]){if(!r.force||"object"!=typeof e[t][i]||"string"!=typeof e[t][i].id){if(c.throwFailures)throw new Error("The referenced entity did not have an 'id' property that would be expected.");return c.q.when(!1)}e[t][i]=e[t][i].id}return n.read(e[t][i]).then(s)}function i(n){return e[t]=n,r.crossLink&&a(n,e),e}function a(e,t){if(Array.isArray(e[r.crossLinkProperty])){var n=e[r.crossLinkProperty].indexOf(t.id);if(-1<n)return void(e[r.crossLinkProperty][n]=t);for(var o=0,i=e[r.crossLinkProperty][0];o<e[r.crossLinkProperty].length;++o,i=e[r.crossLinkProperty][o]){if(i===t)return;if(i.id===t.id)return void(e[r.crossLinkProperty][o]=t)}return void e[r.crossLinkProperty].push(t)}e[r.crossLinkProperty]=t}var c=this;if(r=r||{},"boolean"==typeof r&&(c.logInterface.warn("Argument 'force' is deprecated. Provide an options hash instead."),r={force:r}),r.force=r.force||!1,r.crossLink=r.crossLink||!1,r.crossLinkProperty=r.crossLinkProperty||"",r.crossLink&&!r.crossLinkProperty&&(c.logInterface.warn("Option 'crossLink' given without 'crossLinkProperty'. Cross-linking will be disabled."),r.crossLink=!1),Array.isArray(e[t])){var s=e[t].map(o);return c.q.all(s)}if("string"!=typeof e[t]){if(!r.force||"object"!=typeof e[t]||"string"!=typeof e[t].id){if(c.throwFailures)throw new Error("The referenced entity did not have an 'id' property that would be expected.");return c.q.when(!1)}e[t]=e[t].id}return n.read(e[t]).then(i)},r.prototype.reset=function(){var e=this;e.entityCache=e.configuration.collectionName?[]:{},e.entityCache.__lookup=e.entityCache.__lookup||{},e.__entityCacheRaw=null,e.__requestCache={}},r.prototype.teardown=function(){var e=this;e.absync.off(e.__onEntityOnWebsocketBound),e.absync.off(e.__onCollectionOnWebsocketBound),e.__onEntityReceivedBound.unregister&&e.__onEntityReceivedBound.unregister(),e.__onCollectionReceivedBound.unregister&&e.__onCollectionReceivedBound.unregister()},r}function t(e){return e}angular.module("absync").constant("absyncCache",e)}(),function(){"use strict";function e(){return t}function t(e,t,n,r,o,i,a,c,s,l){this.model=e,this.collectionUri=t,this.entityUri=n;var u=e.prototype.constructor.name.toLowerCase();this.collectionName=r||u+"s",this.entityName=o||u,this.deserialize=i||void 0,this.serialize=a||void 0,this.injector=c||void 0,this.debug=s||!1,this.allowBrowserCache=angular.merge({},{sync:!0,request:!0},l)}angular.module("absync").service("AbsyncServiceConfiguration",e)}(),function(){"use strict";angular.module("absync").constant("absyncNoopLog",{debug:angular.noop,info:angular.noop,warn:angular.noop,error:angular.noop})}(),function(){"use strict";function e(){function e(e){if(!e)return e;var t=-1<e.indexOf("?")?"&":"?",n=(new Date).getTime();return e+t+"t="+n}return e}angular.module("absync").filter("absyncUncached",e)}(); | ||
//# sourceMappingURL=maps/absync.concat.min.js.map |
{ | ||
"name": "absync", | ||
"version": "3.9.1", | ||
"version": "3.10.0", | ||
"description": "absync", | ||
@@ -5,0 +5,0 @@ "main": "dist/development/absync.concat.js", |
@@ -24,4 +24,4 @@ /* globals angular, io */ | ||
*/ | ||
function getAbsyncProvider( $injector, $provide, absyncCache ) { | ||
return new AbsyncProvider( $injector, $provide, absyncCache ); | ||
function getAbsyncProvider( $provide, absyncCache ) { | ||
return new AbsyncProvider( $provide, absyncCache ); | ||
} | ||
@@ -31,3 +31,2 @@ | ||
* Retrieves the absync provider. | ||
* @param {angular.auto.IInjectorService|Object} $injector The $injector provider. | ||
* @param {angular.auto.IProvideService|Object} $provide The $provide provider. | ||
@@ -37,7 +36,5 @@ * @param {Function} absyncCache The AbsyncCache service constructor. | ||
*/ | ||
function AbsyncProvider( $injector, $provide, absyncCache ) { | ||
function AbsyncProvider( $provide, absyncCache ) { | ||
var self = this; | ||
// Store a reference to the inject provider. | ||
self.__injector = $injector; | ||
// Store a reference to the provide provider. | ||
@@ -186,2 +183,9 @@ self.__provide = $provide; | ||
if( configuration.provideService === false ) { | ||
if( !configuration.injector ) { | ||
throw new Error( "Injector is missing in service configuration." ); | ||
} | ||
return configuration.injector.instantiate( self.__collections[ name ].constructor ); | ||
} | ||
// Register the new service. | ||
@@ -220,2 +224,7 @@ // Yes, we want an Angular "service" here, because we want it constructed with "new". | ||
if( configuration.provideService === false ) { | ||
var $injector = angular.injector( [ "ng", "absync" ] ); | ||
return $injector.instantiate( self.__collections[ name ].constructor ); | ||
} | ||
// Register the new service. | ||
@@ -228,2 +237,24 @@ // Yes, we want an Angular "service" here, because we want it constructed with "new". | ||
/** | ||
* Destroy a service. | ||
* @param {CacheService} service | ||
*/ | ||
AbsyncProvider.prototype.teardown = function AbsyncProvider$teardown( service ) { | ||
var self = this; | ||
var serviceDefinition = self.__entities[ service.name ] || self.__collections[ service.name ]; | ||
if( !serviceDefinition ) { | ||
throw new Error( "A service with the name '" + service.name + "' was not registered." ); | ||
} | ||
if( serviceDefinition.configuration.provideService !== false ) { | ||
throw new Error( "The service '" + service.name + "' was registered as an injectable service and can not be torn down." ); | ||
} | ||
delete self.__entities[ service.name ]; | ||
delete self.__collections[ service.name ]; | ||
service.teardown(); | ||
}; | ||
/** | ||
* Register an event listener that is called when a specific entity is received on the websocket. | ||
@@ -262,2 +293,18 @@ * @param {String} eventName The event name, usually the name of the entity. | ||
/** | ||
* Remove a previous registered listener. | ||
* @param {Function} callback | ||
*/ | ||
AbsyncProvider.prototype.off = function AbsyncProvider$off( eventName, callback ) { | ||
var self = this; | ||
for( var listenerIndex = 0; listenerIndex < self.__listeners.length; ++listenerIndex ) { | ||
if( self.__listeners[ listenerIndex ].eventName === eventName && self.__listeners[ listenerIndex ].callback === callback ) { | ||
self.__listeners.splice( listenerIndex, 1 ); | ||
self.__listeners[ listenerIndex ].unregister(); | ||
return; | ||
} | ||
} | ||
}; | ||
/** | ||
* Register an event listener on the websocket. | ||
@@ -264,0 +311,0 @@ * @param {String} eventName The event name, usually the name of the entity. |
@@ -92,5 +92,7 @@ /* globals angular */ | ||
// The scope on which we broadcast all our relevant events. | ||
self.scope = $rootScope; | ||
self.scope = configuration.scope || $rootScope; | ||
// Keep a reference to $q. | ||
self.q = $q; | ||
// Keep a reference to absync itself. | ||
self.absync = absync; | ||
@@ -102,6 +104,6 @@ // Prefix log messages with this string. | ||
// Otherwise, absync will wait for them to be published through the websocket channel. | ||
self.forceEarlyCacheUpdate = false; | ||
self.forceEarlyCacheUpdate = configuration.forceEarlyCacheUpdate || false; | ||
// Throws failures so that they can be handled outside of absync. | ||
self.throwFailures = true; | ||
self.throwFailures = typeof configuration.throwFailures !== "undefined" ? configuration.throwFailures : true; | ||
@@ -115,8 +117,14 @@ // Expose the serializer/deserializer so that they can be adjusted at any time. | ||
// Bind event handlers to this cache service, to ensure consistent this binding. | ||
self.__onEntityOnWebsocketBound = self.__onEntityOnWebsocket.bind( self ); | ||
self.__onCollectionOnWebsocketBound = self.__onCollectionOnWebsocket.bind( self ); | ||
self.__onEntityReceivedBound = self.__onEntityReceived.bind( self ); | ||
self.__onCollectionReceivedBound = self.__onCollectionReceived.bind( self ); | ||
// Tell absync to register an event listener for both our entity and its collection. | ||
// When we receive these events, we broadcast an equal Angular event on the root scope. | ||
// This way the user can already peek at the data (manipulating it is discouraged though). | ||
absync.on( configuration.entityName, self.__onEntityOnWebsocket.bind( self ) ); | ||
absync.on( configuration.entityName, self.__onEntityOnWebsocketBound ); | ||
if( configuration.collectionName ) { | ||
absync.on( configuration.collectionName, self.__onCollectionOnWebsocket.bind( self ) ); | ||
absync.on( configuration.collectionName, self.__onCollectionOnWebsocketBound ); | ||
} | ||
@@ -126,5 +134,7 @@ | ||
// This is where our own absync synchronization logic kicks in. | ||
$rootScope.$on( configuration.entityName, self.__onEntityReceived.bind( self ) ); | ||
self.__onEntityReceivedBound.unregister = $rootScope.$on( configuration.entityName, | ||
self.__onEntityReceivedBound ); | ||
if( configuration.collectionName ) { | ||
$rootScope.$on( configuration.collectionName, self.__onCollectionReceived.bind( self ) ); | ||
self.__onCollectionReceivedBound.unregister = $rootScope.$on( configuration.collectionName, | ||
self.__onCollectionReceivedBound ); | ||
} | ||
@@ -1148,2 +1158,16 @@ | ||
CacheService.prototype.teardown = function CacheService$teardown() { | ||
var self = this; | ||
self.absync.off( self.__onEntityOnWebsocketBound ); | ||
self.absync.off( self.__onCollectionOnWebsocketBound ); | ||
if( self.__onEntityReceivedBound.unregister ) { | ||
self.__onEntityReceivedBound.unregister(); | ||
} | ||
if( self.__onCollectionReceivedBound.unregister ) { | ||
self.__onCollectionReceivedBound.unregister(); | ||
} | ||
}; | ||
return CacheService; | ||
@@ -1150,0 +1174,0 @@ } |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
646581
4740