Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

absync

Package Overview
Dependencies
Maintainers
1
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

absync - npm Package Compare versions

Comparing version 3.2.1 to 3.3.0

coverage/PhantomJS 2.1.1 (Windows 8 0.0.0)/lcov-report/base.css

372

dist/development/absync.concat.js

@@ -376,5 +376,7 @@ (function() {

/* @type {Array<configuration.model>|configuration.model} */
self.entityCache = configuration.collectionName ? [] : {};
self.entityCache = configuration.collectionName ? [] : {};
// Create the ID -> entityIndex lookup table.
self.entityCache.__lookup = {};
// The raw cache is data that hasn't been deserialized and is used internally.
self.__entityCacheRaw = null;
self.__entityCacheRaw = null;

@@ -490,3 +492,7 @@ // Should request caching be used at all?

function deserializeCollectionEntry( rawEntity ) {
self.entityCache.push( self.deserializer( rawEntity ) );
var entityToCache = self.deserializer( rawEntity );
self.entityCache.push( entityToCache );
if( self.entityCache.__lookup ) {
self.entityCache.__lookup[ entityToCache.id ] = self.entityCache.length - 1;
}
}

@@ -505,2 +511,12 @@ };

// Check if our raw entity cache was even initialized.
// It's possible that it isn't, because websocket updates can be received before any manual requests
// were made to the backend.
if( !self.__entityCacheRaw || !self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ] ) {
// We ignore this update and just stack a new read request on top of any existing ones.
// This makes sure that we load the freshest entity in an orderly fashion and lose the state we received
// here, as we're getting the latest version of the entity.
return self.read( _entityReceived.id );
}
// Determine if the received record consists ONLY of an id property,

@@ -510,2 +526,8 @@ // which would mean that this record was deleted from the backend.

self.logInterface.info( self.logPrefix + "Entity was deleted from the server. Updating cache…" );
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
_entityReceived,
"delete",
false );
return self.__removeEntityFromCache( _entityReceived.id );

@@ -515,2 +537,8 @@

self.logInterface.debug( self.logPrefix + "Entity was updated on the server. Updating cache…" );
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
_entityReceived,
"update",
false );
return self.__updateCacheWithEntity( self.deserializer( _entityReceived ) );

@@ -677,2 +705,6 @@ }

// Check if the entity is in the cache and return instantly if found.
if( self.entityCache.__lookup ) {
entityIndex = self.entityCache.__lookup[ id ] || self.entityCache.length;
}
for( var entityIndex = 0, entity = self.entityCache[ 0 ];

@@ -702,7 +734,16 @@ entityIndex < self.entityCache.length;

var rawEntity = serverResponse.data[ configuration.entityName ];
// Put the raw entity into our raw entity cache.
// We keep the raw copy to allow caching of the raw data.
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
rawEntity,
"update",
false );
// Deserialize the object and place it into the cache.
// We do not need to check here if the object already exists in the cache.
// While it could be possible that the same entity is retrieved multiple times, __updateCacheWithEntity
// will not insert duplicated into the cache.
var deserialized = self.deserializer( serverResponse.data[ configuration.entityName ] );
// will not insert duplicates into the cache.
var deserialized = self.deserializer( rawEntity );
self.__updateCacheWithEntity( deserialized );

@@ -741,2 +782,6 @@ return deserialized;

// Make sure our raw entity cache exists.
self.__entityCacheRaw = self.__entityCacheRaw || {};
self.__entityCacheRaw[ configuration.collectionName ] = self.__entityCacheRaw[ configuration.collectionName ] || [];
var requestUri = configuration.entityUri + ( id ? ( "/" + id ) : "" );

@@ -886,6 +931,11 @@

/**
* Invoked when the entity was deleted from the server.
* Invoked when the entity was successfully deleted from the server.
* @param {angular.IHttpPromiseCallbackArg|Object} serverResponse The reply sent from the server.
*/
function onEntityDeleted( serverResponse ) {
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
entity,
"delete",
false );
return self.__removeEntityFromCache( entityId );

@@ -919,80 +969,153 @@ }

if( !Array.isArray( self.entityCache ) ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache,
updated : entityToCache
} );
return self.__cacheMaintain( self.entityCache, entityToCache, "update", true );
};
if( typeof self.entityCache.copyFrom === "function" ) {
self.entityCache.copyFrom( entityToCache );
/**
* Perform maintenance operations on a cache.
* @param cache The cache to operate on.
* @param entityToCache The entity that the operation is relating to.
* @param {String} operation The operation to perform.
* @param {Boolean} [emit=false] Should appropriate absync events be broadcast to notify other actors?
* @private
*/
CacheService.prototype.__cacheMaintain = function CacheService$cacheMaintain( cache, entityToCache, operation, emit ) {
var self = this;
} else {
angular.extend( self.entityCache, entityToCache );
}
var entityIndex = 0;
var entity = cache[ entityIndex ];
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache
} );
return;
if( cache.__lookup ) {
entityIndex = cache.__lookup[ entityToCache.id ] || cache.length;
}
var found = false;
for( var entityIndex = 0, entity = self.entityCache[ 0 ];
entityIndex < self.entityCache.length;
++entityIndex, entity = self.entityCache[ entityIndex ] ) {
if( entity.id == entityToCache.id ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache[ entityIndex ],
updated : entityToCache
} );
switch( operation ) {
case "update":
if( !Array.isArray( cache ) ) {
if( emit ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : cache,
entity : cache,
updated : entityToCache
} );
}
// Use the "copyFrom" method on the entity, if it exists, otherwise use naive approach.
var targetEntity = self.entityCache[ entityIndex ];
if( typeof targetEntity.copyFrom === "function" ) {
targetEntity.copyFrom( entityToCache );
if( typeof cache.copyFrom === "function" ) {
cache.copyFrom( entityToCache );
} else {
angular.extend( targetEntity, entityToCache );
} else {
angular.extend( cache, entityToCache );
}
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : cache,
entity : cache
} );
return;
}
found = true;
var found = false;
for( angular.noop; entityIndex < cache.length; ++entityIndex, entity = cache[ entityIndex ] ) {
if( entity.id === entityToCache.id ) {
if( emit ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : cache,
entity : cache[ entityIndex ],
updated : entityToCache
} );
}
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache[ entityIndex ]
} );
// Use the "copyFrom" method on the entity, if it exists, otherwise use naive approach.
var targetEntity = cache[ entityIndex ];
if( typeof targetEntity.copyFrom === "function" ) {
targetEntity.copyFrom( entityToCache );
} else {
angular.extend( targetEntity, entityToCache );
}
found = true;
if( emit ) {
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : cache,
entity : cache[ entityIndex ]
} );
}
break;
}
}
// If the entity wasn't found in our records, it's a new entity.
if( !found ) {
if( emit ) {
self.scope.$broadcast( "beforeEntityNew", {
service : self,
cache : cache,
entity : entityToCache
} );
}
cache.push( entityToCache );
if( cache.__lookup ) {
cache.__lookup[ entityToCache.id ] = cache.length - 1;
}
if( emit ) {
self.scope.$broadcast( "entityNew", {
service : self,
cache : cache,
entity : entityToCache
} );
}
}
break;
}
}
// If the entity wasn't found in our records, it's a new entity.
if( !found ) {
self.scope.$broadcast( "beforeEntityNew", {
service : self,
cache : self.entityCache,
entity : entityToCache
} );
case "delete":
// The "delete" operation is not expected to happen for single cached entities.
for( angular.noop; entityIndex < cache.length; ++entityIndex, entity = cache[ entityIndex ] ) {
if( entity.id === entityToCache.id ) {
if( emit ) {
// Before removing the entity, allow the user to react.
self.scope.$broadcast( "beforeEntityRemoved", {
service : self,
cache : cache,
entity : entity
} );
}
self.entityCache.push( entityToCache );
// Remove the entity from the cache.
cache.splice( entityIndex, 1 );
self.scope.$broadcast( "entityNew", {
service : self,
cache : self.entityCache,
entity : entityToCache
} );
if( cache.__lookup ) {
for( var cacheEntry in cache.__lookup ) {
if( entityIndex <= cache.__lookup[ cacheEntry ] ) {
--cache.__lookup[ cacheEntry ];
}
}
}
if( emit ) {
// Send another event to allow the user to take note of the removal.
self.scope.$broadcast( "entityRemoved", {
service : self,
cache : cache,
entity : entity
} );
}
break;
}
}
break;
}

@@ -1009,25 +1132,5 @@ };

for( var entityIndex = 0, entity = self.entityCache[ 0 ];
entityIndex < self.entityCache.length;
++entityIndex, entity = self.entityCache[ entityIndex ] ) {
if( entity.id == id ) {
// Before removing the entity, allow the user to react.
self.scope.$broadcast( "beforeEntityRemoved", {
service : self,
cache : self.entityCache,
entity : entity
} );
// Remove the entity from the cache.
self.entityCache.splice( entityIndex, 1 );
// Send another event to allow the user to take note of the removal.
self.scope.$broadcast( "entityRemoved", {
service : self,
cache : self.entityCache,
entity : entity
} );
break;
}
}
return self.__cacheMaintain( self.entityCache, {
id : id
}, "delete", true );
};

@@ -1043,3 +1146,6 @@

// TODO: Keep a copy of the lookup table and only update it when the cached data updates
if( self.entityCache.__lookup ) {
return angular.copy( self.entityCache.__lookup );
}
var lookupTable = [];

@@ -1094,9 +1200,31 @@ for( var entityIndex = 0;

* type instances which are being referenced in entity.
* @param {Boolean} [force=false] If true, all complex types will be replaced with references to the
* @param {Object|Boolean} [options] A hash with options relating to the population process.
* @param {Boolean} [options.force=false] If true, all complex types will be replaced with references to the
* instances in cache; otherwise, only properties that are string representations of complex type IDs will be replaced.
* @param {Boolean} [options.crossLink=false] If true, the entity will also be put into a relating property in the
* foreign entity.
* @param {String} [options.crossLinkProperty] The name of the property in the foreign type into which the entity
* should be cross-linked.
* @returns {IPromise<TResult>|IPromise<any[]>|IPromise<{}>|angular.IPromise<TResult>}
*/
CacheService.prototype.populateComplex = function CacheService$populateComplex( entity, propertyName, cache, force ) {
CacheService.prototype.populateComplex = function CacheService$populateComplex( entity, propertyName, cache, options ) {
var self = this;
options = options || {};
if( typeof options === "boolean" ) {
self.logInterface.warn( "Argument 'force' is deprecated. Provide an options hash instead." );
options = {
force : options
};
}
options.force = options.force || false;
options.crossLink = options.crossLink || false;
options.crossLinkProperty = options.crossLinkProperty || "";
if( options.crossLink && !options.crossLinkProperty ) {
self.logInterface.warn(
"Option 'crossLink' given without 'crossLinkProperty'. Cross-linking will be disabled." );
options.crossLink = false;
}
// If the target property is an array, ...

@@ -1113,3 +1241,3 @@ if( Array.isArray( entity[ propertyName ] ) ) {

// If "force" is enabled, we check if this non-string property is an object and has an "id" member, which is a string.
if( force && typeof entity[ propertyName ] === "object" && typeof entity[ propertyName ].id === "string" ) {
if( options.force && typeof entity[ propertyName ] === "object" && typeof entity[ propertyName ].id === "string" ) {
// If that is true, then we replace the whole object with the ID and continue as usual.

@@ -1119,2 +1247,5 @@ entity[ propertyName ] = entity[ propertyName ].id;

} else {
if( self.throwFailures ) {
throw new Error( "The referenced entity did not have an 'id' property that would be expected." );
}
return self.q.when( false );

@@ -1133,3 +1264,3 @@ }

// If "force" is enabled, we check if this non-string property is an object and has an "id" member, which is a string.
if( force && typeof entity[ propertyName ][ index ] === "object" && typeof entity[ propertyName ][ index ].id === "string" ) {
if( options.force && typeof entity[ propertyName ][ index ] === "object" && typeof entity[ propertyName ][ index ].id === "string" ) {
// If that is true, then we replace the whole object with the ID and continue as usual.

@@ -1139,2 +1270,6 @@ entity[ propertyName ][ index ] = entity[ propertyName ][ index ].id;

} else {
if( self.throwFailures ) {
throw new Error( "The referenced entity did not have an 'id' property that would be expected." );
}
return self.q.when( false );

@@ -1151,2 +1286,7 @@ }

entity[ propertyName ][ index ] = complex;
if( options.crossLink ) {
crossLink( complex, entity );
}
return entity;

@@ -1159,3 +1299,34 @@ }

entity[ propertyName ] = complex;
if( options.crossLink ) {
crossLink( complex, entity );
}
return entity;
}
function crossLink( complex, entity ) {
// If cross-linking is enabled, put our entity into the foreign complex.
if( Array.isArray( complex[ options.crossLinkProperty ] ) ) {
// Check if the entity is already linked into the array.
var entityIndex = complex[ options.crossLinkProperty ].indexOf( entity );
if( -1 < entityIndex ) {
return;
}
// Check if the ID exists in the array.
var idIndex = complex[ options.crossLinkProperty ].indexOf( entity.id );
if( -1 < idIndex ) {
// Replace the ID with the entity.
complex[ options.crossLinkProperty ][ idIndex ] = entity;
return;
}
// Just push the element into the array.
complex[ options.crossLinkProperty ].push( entity );
return;
}
complex[ options.crossLinkProperty ] = entity;
}
};

@@ -1171,3 +1342,5 @@

self.entityCache = self.configuration.collectionName ? [] : {};
self.entityCache = self.configuration.collectionName ? [] : {};
self.entityCache.__lookup = self.entityCache.__lookup || {};
self.__entityCacheRaw = null;

@@ -1250,2 +1423,3 @@ self.__requestCache = {};

info : angular.noop,
warn : angular.noop,
error : angular.noop

@@ -1252,0 +1426,0 @@ } );

@@ -67,5 +67,7 @@ (function() {

/* @type {Array<configuration.model>|configuration.model} */
self.entityCache = configuration.collectionName ? [] : {};
self.entityCache = configuration.collectionName ? [] : {};
// Create the ID -> entityIndex lookup table.
self.entityCache.__lookup = {};
// The raw cache is data that hasn't been deserialized and is used internally.
self.__entityCacheRaw = null;
self.__entityCacheRaw = null;

@@ -181,3 +183,7 @@ // Should request caching be used at all?

function deserializeCollectionEntry( rawEntity ) {
self.entityCache.push( self.deserializer( rawEntity ) );
var entityToCache = self.deserializer( rawEntity );
self.entityCache.push( entityToCache );
if( self.entityCache.__lookup ) {
self.entityCache.__lookup[ entityToCache.id ] = self.entityCache.length - 1;
}
}

@@ -196,2 +202,12 @@ };

// Check if our raw entity cache was even initialized.
// It's possible that it isn't, because websocket updates can be received before any manual requests
// were made to the backend.
if( !self.__entityCacheRaw || !self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ] ) {
// We ignore this update and just stack a new read request on top of any existing ones.
// This makes sure that we load the freshest entity in an orderly fashion and lose the state we received
// here, as we're getting the latest version of the entity.
return self.read( _entityReceived.id );
}
// Determine if the received record consists ONLY of an id property,

@@ -201,2 +217,8 @@ // which would mean that this record was deleted from the backend.

self.logInterface.info( self.logPrefix + "Entity was deleted from the server. Updating cache…" );
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
_entityReceived,
"delete",
false );
return self.__removeEntityFromCache( _entityReceived.id );

@@ -206,2 +228,8 @@

self.logInterface.debug( self.logPrefix + "Entity was updated on the server. Updating cache…" );
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
_entityReceived,
"update",
false );
return self.__updateCacheWithEntity( self.deserializer( _entityReceived ) );

@@ -368,2 +396,6 @@ }

// Check if the entity is in the cache and return instantly if found.
if( self.entityCache.__lookup ) {
entityIndex = self.entityCache.__lookup[ id ] || self.entityCache.length;
}
for( var entityIndex = 0, entity = self.entityCache[ 0 ];

@@ -393,7 +425,16 @@ entityIndex < self.entityCache.length;

var rawEntity = serverResponse.data[ configuration.entityName ];
// Put the raw entity into our raw entity cache.
// We keep the raw copy to allow caching of the raw data.
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
rawEntity,
"update",
false );
// Deserialize the object and place it into the cache.
// We do not need to check here if the object already exists in the cache.
// While it could be possible that the same entity is retrieved multiple times, __updateCacheWithEntity
// will not insert duplicated into the cache.
var deserialized = self.deserializer( serverResponse.data[ configuration.entityName ] );
// will not insert duplicates into the cache.
var deserialized = self.deserializer( rawEntity );
self.__updateCacheWithEntity( deserialized );

@@ -432,2 +473,6 @@ return deserialized;

// Make sure our raw entity cache exists.
self.__entityCacheRaw = self.__entityCacheRaw || {};
self.__entityCacheRaw[ configuration.collectionName ] = self.__entityCacheRaw[ configuration.collectionName ] || [];
var requestUri = configuration.entityUri + ( id ? ( "/" + id ) : "" );

@@ -577,6 +622,11 @@

/**
* Invoked when the entity was deleted from the server.
* Invoked when the entity was successfully deleted from the server.
* @param {angular.IHttpPromiseCallbackArg|Object} serverResponse The reply sent from the server.
*/
function onEntityDeleted( serverResponse ) {
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
entity,
"delete",
false );
return self.__removeEntityFromCache( entityId );

@@ -610,80 +660,153 @@ }

if( !Array.isArray( self.entityCache ) ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache,
updated : entityToCache
} );
return self.__cacheMaintain( self.entityCache, entityToCache, "update", true );
};
if( typeof self.entityCache.copyFrom === "function" ) {
self.entityCache.copyFrom( entityToCache );
/**
* Perform maintenance operations on a cache.
* @param cache The cache to operate on.
* @param entityToCache The entity that the operation is relating to.
* @param {String} operation The operation to perform.
* @param {Boolean} [emit=false] Should appropriate absync events be broadcast to notify other actors?
* @private
*/
CacheService.prototype.__cacheMaintain = function CacheService$cacheMaintain( cache, entityToCache, operation, emit ) {
var self = this;
} else {
angular.extend( self.entityCache, entityToCache );
}
var entityIndex = 0;
var entity = cache[ entityIndex ];
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache
} );
return;
if( cache.__lookup ) {
entityIndex = cache.__lookup[ entityToCache.id ] || cache.length;
}
var found = false;
for( var entityIndex = 0, entity = self.entityCache[ 0 ];
entityIndex < self.entityCache.length;
++entityIndex, entity = self.entityCache[ entityIndex ] ) {
if( entity.id == entityToCache.id ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache[ entityIndex ],
updated : entityToCache
} );
switch( operation ) {
case "update":
if( !Array.isArray( cache ) ) {
if( emit ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : cache,
entity : cache,
updated : entityToCache
} );
}
// Use the "copyFrom" method on the entity, if it exists, otherwise use naive approach.
var targetEntity = self.entityCache[ entityIndex ];
if( typeof targetEntity.copyFrom === "function" ) {
targetEntity.copyFrom( entityToCache );
if( typeof cache.copyFrom === "function" ) {
cache.copyFrom( entityToCache );
} else {
angular.extend( targetEntity, entityToCache );
} else {
angular.extend( cache, entityToCache );
}
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : cache,
entity : cache
} );
return;
}
found = true;
var found = false;
for( angular.noop; entityIndex < cache.length; ++entityIndex, entity = cache[ entityIndex ] ) {
if( entity.id === entityToCache.id ) {
if( emit ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : cache,
entity : cache[ entityIndex ],
updated : entityToCache
} );
}
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache[ entityIndex ]
} );
// Use the "copyFrom" method on the entity, if it exists, otherwise use naive approach.
var targetEntity = cache[ entityIndex ];
if( typeof targetEntity.copyFrom === "function" ) {
targetEntity.copyFrom( entityToCache );
} else {
angular.extend( targetEntity, entityToCache );
}
found = true;
if( emit ) {
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : cache,
entity : cache[ entityIndex ]
} );
}
break;
}
}
// If the entity wasn't found in our records, it's a new entity.
if( !found ) {
if( emit ) {
self.scope.$broadcast( "beforeEntityNew", {
service : self,
cache : cache,
entity : entityToCache
} );
}
cache.push( entityToCache );
if( cache.__lookup ) {
cache.__lookup[ entityToCache.id ] = cache.length - 1;
}
if( emit ) {
self.scope.$broadcast( "entityNew", {
service : self,
cache : cache,
entity : entityToCache
} );
}
}
break;
}
}
// If the entity wasn't found in our records, it's a new entity.
if( !found ) {
self.scope.$broadcast( "beforeEntityNew", {
service : self,
cache : self.entityCache,
entity : entityToCache
} );
case "delete":
// The "delete" operation is not expected to happen for single cached entities.
for( angular.noop; entityIndex < cache.length; ++entityIndex, entity = cache[ entityIndex ] ) {
if( entity.id === entityToCache.id ) {
if( emit ) {
// Before removing the entity, allow the user to react.
self.scope.$broadcast( "beforeEntityRemoved", {
service : self,
cache : cache,
entity : entity
} );
}
self.entityCache.push( entityToCache );
// Remove the entity from the cache.
cache.splice( entityIndex, 1 );
self.scope.$broadcast( "entityNew", {
service : self,
cache : self.entityCache,
entity : entityToCache
} );
if( cache.__lookup ) {
for( var cacheEntry in cache.__lookup ) {
if( entityIndex <= cache.__lookup[ cacheEntry ] ) {
--cache.__lookup[ cacheEntry ];
}
}
}
if( emit ) {
// Send another event to allow the user to take note of the removal.
self.scope.$broadcast( "entityRemoved", {
service : self,
cache : cache,
entity : entity
} );
}
break;
}
}
break;
}

@@ -700,25 +823,5 @@ };

for( var entityIndex = 0, entity = self.entityCache[ 0 ];
entityIndex < self.entityCache.length;
++entityIndex, entity = self.entityCache[ entityIndex ] ) {
if( entity.id == id ) {
// Before removing the entity, allow the user to react.
self.scope.$broadcast( "beforeEntityRemoved", {
service : self,
cache : self.entityCache,
entity : entity
} );
// Remove the entity from the cache.
self.entityCache.splice( entityIndex, 1 );
// Send another event to allow the user to take note of the removal.
self.scope.$broadcast( "entityRemoved", {
service : self,
cache : self.entityCache,
entity : entity
} );
break;
}
}
return self.__cacheMaintain( self.entityCache, {
id : id
}, "delete", true );
};

@@ -734,3 +837,6 @@

// TODO: Keep a copy of the lookup table and only update it when the cached data updates
if( self.entityCache.__lookup ) {
return angular.copy( self.entityCache.__lookup );
}
var lookupTable = [];

@@ -785,9 +891,31 @@ for( var entityIndex = 0;

* type instances which are being referenced in entity.
* @param {Boolean} [force=false] If true, all complex types will be replaced with references to the
* @param {Object|Boolean} [options] A hash with options relating to the population process.
* @param {Boolean} [options.force=false] If true, all complex types will be replaced with references to the
* instances in cache; otherwise, only properties that are string representations of complex type IDs will be replaced.
* @param {Boolean} [options.crossLink=false] If true, the entity will also be put into a relating property in the
* foreign entity.
* @param {String} [options.crossLinkProperty] The name of the property in the foreign type into which the entity
* should be cross-linked.
* @returns {IPromise<TResult>|IPromise<any[]>|IPromise<{}>|angular.IPromise<TResult>}
*/
CacheService.prototype.populateComplex = function CacheService$populateComplex( entity, propertyName, cache, force ) {
CacheService.prototype.populateComplex = function CacheService$populateComplex( entity, propertyName, cache, options ) {
var self = this;
options = options || {};
if( typeof options === "boolean" ) {
self.logInterface.warn( "Argument 'force' is deprecated. Provide an options hash instead." );
options = {
force : options
};
}
options.force = options.force || false;
options.crossLink = options.crossLink || false;
options.crossLinkProperty = options.crossLinkProperty || "";
if( options.crossLink && !options.crossLinkProperty ) {
self.logInterface.warn(
"Option 'crossLink' given without 'crossLinkProperty'. Cross-linking will be disabled." );
options.crossLink = false;
}
// If the target property is an array, ...

@@ -804,3 +932,3 @@ if( Array.isArray( entity[ propertyName ] ) ) {

// If "force" is enabled, we check if this non-string property is an object and has an "id" member, which is a string.
if( force && typeof entity[ propertyName ] === "object" && typeof entity[ propertyName ].id === "string" ) {
if( options.force && typeof entity[ propertyName ] === "object" && typeof entity[ propertyName ].id === "string" ) {
// If that is true, then we replace the whole object with the ID and continue as usual.

@@ -810,2 +938,5 @@ entity[ propertyName ] = entity[ propertyName ].id;

} else {
if( self.throwFailures ) {
throw new Error( "The referenced entity did not have an 'id' property that would be expected." );
}
return self.q.when( false );

@@ -824,3 +955,3 @@ }

// If "force" is enabled, we check if this non-string property is an object and has an "id" member, which is a string.
if( force && typeof entity[ propertyName ][ index ] === "object" && typeof entity[ propertyName ][ index ].id === "string" ) {
if( options.force && typeof entity[ propertyName ][ index ] === "object" && typeof entity[ propertyName ][ index ].id === "string" ) {
// If that is true, then we replace the whole object with the ID and continue as usual.

@@ -830,2 +961,6 @@ entity[ propertyName ][ index ] = entity[ propertyName ][ index ].id;

} else {
if( self.throwFailures ) {
throw new Error( "The referenced entity did not have an 'id' property that would be expected." );
}
return self.q.when( false );

@@ -842,2 +977,7 @@ }

entity[ propertyName ][ index ] = complex;
if( options.crossLink ) {
crossLink( complex, entity );
}
return entity;

@@ -850,3 +990,34 @@ }

entity[ propertyName ] = complex;
if( options.crossLink ) {
crossLink( complex, entity );
}
return entity;
}
function crossLink( complex, entity ) {
// If cross-linking is enabled, put our entity into the foreign complex.
if( Array.isArray( complex[ options.crossLinkProperty ] ) ) {
// Check if the entity is already linked into the array.
var entityIndex = complex[ options.crossLinkProperty ].indexOf( entity );
if( -1 < entityIndex ) {
return;
}
// Check if the ID exists in the array.
var idIndex = complex[ options.crossLinkProperty ].indexOf( entity.id );
if( -1 < idIndex ) {
// Replace the ID with the entity.
complex[ options.crossLinkProperty ][ idIndex ] = entity;
return;
}
// Just push the element into the array.
complex[ options.crossLinkProperty ].push( entity );
return;
}
complex[ options.crossLinkProperty ] = entity;
}
};

@@ -862,3 +1033,5 @@

self.entityCache = self.configuration.collectionName ? [] : {};
self.entityCache = self.configuration.collectionName ? [] : {};
self.entityCache.__lookup = self.entityCache.__lookup || {};
self.__entityCacheRaw = null;

@@ -865,0 +1038,0 @@ self.__requestCache = {};

@@ -10,4 +10,5 @@ (function() {

info : angular.noop,
warn : angular.noop,
error : angular.noop
} );
}());

@@ -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,u,h){var l=this,d=n.injector||i,y=d.has(n.model);if(!y)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,_=f.serialize||n.serialize||t,p=f.deserialize||n.deserialize||t;l.name=e,l.configuration=n,l.entityCache=n.collectionName?[]:{},l.__entityCacheRaw=null,l.enableRequestCache=!0,l.__requestCache={},l.allowBrowserCache=(angular.merge||angular.extend)({},{sync:!0,request:!0},n.allowBrowserCache),l.__uncached=h,l.httpInterface=r,l.logInterface=n.debug?o:s,l.scope=c,l.q=a,l.logPrefix="absync:"+e.toLocaleUpperCase()+" ",l.forceEarlyCacheUpdate=!1,l.throwFailures=!0,l.serializer=_,l.deserializer=p,u.on(n.entityName,l.__onEntityOnWebsocket.bind(l)),n.collectionName&&u.on(n.collectionName,l.__onCollectionOnWebsocket.bind(l)),c.$on(n.entityName,l.__onEntityReceived.bind(l)),n.collectionName&&c.$on(n.collectionName,l.__onCollectionReceived.bind(l)),l.logInterface.info(l.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","absyncNoopLog","absync","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){r.entityCache.push(r.deserializer(e))}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 n=this,r=t;return 1===Object.keys(r).length&&r.hasOwnProperty("id")?(n.logInterface.info(n.logPrefix+"Entity was deleted from the server. Updating cache…"),n.__removeEntityFromCache(r.id)):(n.logInterface.debug(n.logPrefix+"Entity was updated on the server. Updating cache…"),n.__updateCacheWithEntity(n.deserializer(r)))},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;return e=e===!0,null===a.__entityCacheRaw||e?n.collectionName&&n.collectionUri?(a.logInterface.info(a.logPrefix+"Retrieving '"+n.collectionName+"' collection…"),a.httpInterface.get(a.allowBrowserCache.sync?n.collectionUri:a.__uncached(n.collectionUri)).then(t,r)):n.entityName&&n.entityUri?a.httpInterface.get(a.allowBrowserCache.sync?n.entityUri:a.__uncached(n.entityUri)).then(i,o):a.q.when([]):a.q.when(a.entityCache)},r.prototype.seed=function(e){var t=this;return t.__entityCacheRaw=e,t.__onDataAvailable(t.__entityCacheRaw)},r.prototype.sync=function(){var e=this;return e.__entityCacheRaw=null,e.ensureLoaded(!0)},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=o.deserializer(e.data[n.entityName]);return o.__updateCacheWithEntity(t),t}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)for(var a=0,c=o.entityCache[0];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];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){var t=this,r=t.reduceComplex(e),a=t.serializer(r),c={};if(c[n.entityName]=a,"undefined"!=typeof e.id)return t.httpInterface.patch(n.entityUri+"/"+e.id,c).then(i.bind(t),o.bind(t));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(e){return 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;if(t.logInterface.info(t.logPrefix+"Updating entity '"+(e.id||t.name)+"' in cache…",e),!Array.isArray(t.entityCache))return t.scope.$broadcast("beforeEntityUpdated",{service:t,cache:t.entityCache,entity:t.entityCache,updated:e}),"function"==typeof t.entityCache.copyFrom?t.entityCache.copyFrom(e):angular.extend(t.entityCache,e),void t.scope.$broadcast("entityUpdated",{service:t,cache:t.entityCache,entity:t.entityCache});for(var n=!1,r=0,i=t.entityCache[0];r<t.entityCache.length;++r,i=t.entityCache[r])if(i.id==e.id){t.scope.$broadcast("beforeEntityUpdated",{service:t,cache:t.entityCache,entity:t.entityCache[r],updated:e});var o=t.entityCache[r];"function"==typeof o.copyFrom?o.copyFrom(e):angular.extend(o,e),n=!0,t.scope.$broadcast("entityUpdated",{service:t,cache:t.entityCache,entity:t.entityCache[r]});break}n||(t.scope.$broadcast("beforeEntityNew",{service:t,cache:t.entityCache,entity:e}),t.entityCache.push(e),t.scope.$broadcast("entityNew",{service:t,cache:t.entityCache,entity:e}))},r.prototype.__removeEntityFromCache=function(e){for(var t=this,n=0,r=t.entityCache[0];n<t.entityCache.length;++n,r=t.entityCache[n])if(r.id==e){t.scope.$broadcast("beforeEntityRemoved",{service:t,cache:t.entityCache,entity:r}),t.entityCache.splice(n,1),t.scope.$broadcast("entityRemoved",{service:t,cache:t.entityCache,entity:r});break}},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 c(n){return e[t][o]=n,e}if("string"!=typeof e[t][o]){if(!r||"object"!=typeof e[t][o]||"string"!=typeof e[t][o].id)return a.q.when(!1);e[t][o]=e[t][o].id}return n.read(e[t][o]).then(c)}function o(n){e[t]=n}var a=this;if(Array.isArray(e[t])){var c=e[t].map(i);return a.q.all(c)}if("string"!=typeof e[t]){if(!r||"object"!=typeof e[t]||"string"!=typeof e[t].id)return a.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.__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,u){this.model=e,this.collectionUri=t,this.entityUri=n;var h=e.prototype.constructor.name.toLowerCase();this.collectionName=r||h+"s",this.entityName=i||h,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},u)}angular.module("absync").service("AbsyncServiceConfiguration",e)}(),function(){"use strict";angular.module("absync").constant("absyncNoopLog",{debug:angular.noop,info: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,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,o,i,a,c,s,l,u){var h=this,d=n.injector||o,_=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 y="string"==typeof n.model?d.get(n.model):n.model,f=y.serialize||n.serialize||t,p=y.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?i:s,h.scope=c,h.q=a,h.logPrefix="absync:"+e.toLocaleUpperCase()+" ",h.forceEarlyCacheUpdate=!1,h.throwFailures=!0,h.serializer=f,h.deserializer=p,l.on(n.entityName,h.__onEntityOnWebsocket.bind(h)),n.collectionName&&l.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 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","absyncNoopLog","absync","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;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.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;return e=e===!0,null===a.__entityCacheRaw||e?n.collectionName&&n.collectionUri?(a.logInterface.info(a.logPrefix+"Retrieving '"+n.collectionName+"' collection…"),a.httpInterface.get(a.allowBrowserCache.sync?n.collectionUri:a.__uncached(n.collectionUri)).then(t,r)):n.entityName&&n.entityUri?a.httpInterface.get(a.allowBrowserCache.sync?n.entityUri:a.__uncached(n.entityUri)).then(o,i):a.q.when([]):a.q.when(a.entityCache)},r.prototype.seed=function(e){var t=this;return t.__entityCacheRaw=e,t.__onDataAvailable(t.__entityCacheRaw)},r.prototype.sync=function(){var e=this;return e.__entityCacheRaw=null,e.ensureLoaded(!0)},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){i.entityCache.__lookup&&(a=i.entityCache.__lookup[e]||i.entityCache.length);for(var a=0,c=i.entityCache[0];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){var t=this,r=t.reduceComplex(e),a=t.serializer(r),c={};if(c[n.entityName]=a,"undefined"!=typeof e.id)return t.httpInterface.patch(n.entityUri+"/"+e.id,c).then(o.bind(t),i.bind(t));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];switch(e.__lookup&&(i=e.__lookup[t.id]||e.length),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)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(){var e=this;if(e.entityCache.__lookup)return angular.copy(e.entityCache.__lookup);for(var 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);if(n>-1)return;var o=e[r.crossLinkProperty].indexOf(t.id);return o>-1?void(e[r.crossLinkProperty][o]=t):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}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.2.1",
"version": "3.3.0",
"description": "absync",

@@ -5,0 +5,0 @@ "main": "dist/development/absync.concat.js",

@@ -64,5 +64,7 @@ /* globals angular */

/* @type {Array<configuration.model>|configuration.model} */
self.entityCache = configuration.collectionName ? [] : {};
self.entityCache = configuration.collectionName ? [] : {};
// Create the ID -> entityIndex lookup table.
self.entityCache.__lookup = {};
// The raw cache is data that hasn't been deserialized and is used internally.
self.__entityCacheRaw = null;
self.__entityCacheRaw = null;

@@ -178,3 +180,7 @@ // Should request caching be used at all?

function deserializeCollectionEntry( rawEntity ) {
self.entityCache.push( self.deserializer( rawEntity ) );
var entityToCache = self.deserializer( rawEntity );
self.entityCache.push( entityToCache );
if( self.entityCache.__lookup ) {
self.entityCache.__lookup[ entityToCache.id ] = self.entityCache.length - 1;
}
}

@@ -193,2 +199,12 @@ };

// Check if our raw entity cache was even initialized.
// It's possible that it isn't, because websocket updates can be received before any manual requests
// were made to the backend.
if( !self.__entityCacheRaw || !self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ] ) {
// We ignore this update and just stack a new read request on top of any existing ones.
// This makes sure that we load the freshest entity in an orderly fashion and lose the state we received
// here, as we're getting the latest version of the entity.
return self.read( _entityReceived.id );
}
// Determine if the received record consists ONLY of an id property,

@@ -198,2 +214,8 @@ // which would mean that this record was deleted from the backend.

self.logInterface.info( self.logPrefix + "Entity was deleted from the server. Updating cache…" );
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
_entityReceived,
"delete",
false );
return self.__removeEntityFromCache( _entityReceived.id );

@@ -203,2 +225,8 @@

self.logInterface.debug( self.logPrefix + "Entity was updated on the server. Updating cache…" );
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
_entityReceived,
"update",
false );
return self.__updateCacheWithEntity( self.deserializer( _entityReceived ) );

@@ -365,2 +393,6 @@ }

// Check if the entity is in the cache and return instantly if found.
if( self.entityCache.__lookup ) {
entityIndex = self.entityCache.__lookup[ id ] || self.entityCache.length;
}
for( var entityIndex = 0, entity = self.entityCache[ 0 ];

@@ -390,7 +422,16 @@ entityIndex < self.entityCache.length;

var rawEntity = serverResponse.data[ configuration.entityName ];
// Put the raw entity into our raw entity cache.
// We keep the raw copy to allow caching of the raw data.
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
rawEntity,
"update",
false );
// Deserialize the object and place it into the cache.
// We do not need to check here if the object already exists in the cache.
// While it could be possible that the same entity is retrieved multiple times, __updateCacheWithEntity
// will not insert duplicated into the cache.
var deserialized = self.deserializer( serverResponse.data[ configuration.entityName ] );
// will not insert duplicates into the cache.
var deserialized = self.deserializer( rawEntity );
self.__updateCacheWithEntity( deserialized );

@@ -429,2 +470,6 @@ return deserialized;

// Make sure our raw entity cache exists.
self.__entityCacheRaw = self.__entityCacheRaw || {};
self.__entityCacheRaw[ configuration.collectionName ] = self.__entityCacheRaw[ configuration.collectionName ] || [];
var requestUri = configuration.entityUri + ( id ? ( "/" + id ) : "" );

@@ -574,6 +619,11 @@

/**
* Invoked when the entity was deleted from the server.
* Invoked when the entity was successfully deleted from the server.
* @param {angular.IHttpPromiseCallbackArg|Object} serverResponse The reply sent from the server.
*/
function onEntityDeleted( serverResponse ) {
self.__cacheMaintain( self.__entityCacheRaw[ configuration.collectionName || configuration.entityName ],
entity,
"delete",
false );
return self.__removeEntityFromCache( entityId );

@@ -607,80 +657,153 @@ }

if( !Array.isArray( self.entityCache ) ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache,
updated : entityToCache
} );
return self.__cacheMaintain( self.entityCache, entityToCache, "update", true );
};
if( typeof self.entityCache.copyFrom === "function" ) {
self.entityCache.copyFrom( entityToCache );
/**
* Perform maintenance operations on a cache.
* @param cache The cache to operate on.
* @param entityToCache The entity that the operation is relating to.
* @param {String} operation The operation to perform.
* @param {Boolean} [emit=false] Should appropriate absync events be broadcast to notify other actors?
* @private
*/
CacheService.prototype.__cacheMaintain = function CacheService$cacheMaintain( cache, entityToCache, operation, emit ) {
var self = this;
} else {
angular.extend( self.entityCache, entityToCache );
}
var entityIndex = 0;
var entity = cache[ entityIndex ];
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache
} );
return;
if( cache.__lookup ) {
entityIndex = cache.__lookup[ entityToCache.id ] || cache.length;
}
var found = false;
for( var entityIndex = 0, entity = self.entityCache[ 0 ];
entityIndex < self.entityCache.length;
++entityIndex, entity = self.entityCache[ entityIndex ] ) {
if( entity.id == entityToCache.id ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache[ entityIndex ],
updated : entityToCache
} );
switch( operation ) {
case "update":
if( !Array.isArray( cache ) ) {
if( emit ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : cache,
entity : cache,
updated : entityToCache
} );
}
// Use the "copyFrom" method on the entity, if it exists, otherwise use naive approach.
var targetEntity = self.entityCache[ entityIndex ];
if( typeof targetEntity.copyFrom === "function" ) {
targetEntity.copyFrom( entityToCache );
if( typeof cache.copyFrom === "function" ) {
cache.copyFrom( entityToCache );
} else {
angular.extend( targetEntity, entityToCache );
} else {
angular.extend( cache, entityToCache );
}
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : cache,
entity : cache
} );
return;
}
found = true;
var found = false;
for( angular.noop; entityIndex < cache.length; ++entityIndex, entity = cache[ entityIndex ] ) {
if( entity.id === entityToCache.id ) {
if( emit ) {
// Allow the user to intervene in the update process, before updating the entity.
self.scope.$broadcast( "beforeEntityUpdated",
{
service : self,
cache : cache,
entity : cache[ entityIndex ],
updated : entityToCache
} );
}
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : self.entityCache,
entity : self.entityCache[ entityIndex ]
} );
// Use the "copyFrom" method on the entity, if it exists, otherwise use naive approach.
var targetEntity = cache[ entityIndex ];
if( typeof targetEntity.copyFrom === "function" ) {
targetEntity.copyFrom( entityToCache );
} else {
angular.extend( targetEntity, entityToCache );
}
found = true;
if( emit ) {
// After updating the entity, send another event to allow the user to react.
self.scope.$broadcast( "entityUpdated",
{
service : self,
cache : cache,
entity : cache[ entityIndex ]
} );
}
break;
}
}
// If the entity wasn't found in our records, it's a new entity.
if( !found ) {
if( emit ) {
self.scope.$broadcast( "beforeEntityNew", {
service : self,
cache : cache,
entity : entityToCache
} );
}
cache.push( entityToCache );
if( cache.__lookup ) {
cache.__lookup[ entityToCache.id ] = cache.length - 1;
}
if( emit ) {
self.scope.$broadcast( "entityNew", {
service : self,
cache : cache,
entity : entityToCache
} );
}
}
break;
}
}
// If the entity wasn't found in our records, it's a new entity.
if( !found ) {
self.scope.$broadcast( "beforeEntityNew", {
service : self,
cache : self.entityCache,
entity : entityToCache
} );
case "delete":
// The "delete" operation is not expected to happen for single cached entities.
for( angular.noop; entityIndex < cache.length; ++entityIndex, entity = cache[ entityIndex ] ) {
if( entity.id === entityToCache.id ) {
if( emit ) {
// Before removing the entity, allow the user to react.
self.scope.$broadcast( "beforeEntityRemoved", {
service : self,
cache : cache,
entity : entity
} );
}
self.entityCache.push( entityToCache );
// Remove the entity from the cache.
cache.splice( entityIndex, 1 );
self.scope.$broadcast( "entityNew", {
service : self,
cache : self.entityCache,
entity : entityToCache
} );
if( cache.__lookup ) {
for( var cacheEntry in cache.__lookup ) {
if( entityIndex <= cache.__lookup[ cacheEntry ] ) {
--cache.__lookup[ cacheEntry ];
}
}
}
if( emit ) {
// Send another event to allow the user to take note of the removal.
self.scope.$broadcast( "entityRemoved", {
service : self,
cache : cache,
entity : entity
} );
}
break;
}
}
break;
}

@@ -697,25 +820,5 @@ };

for( var entityIndex = 0, entity = self.entityCache[ 0 ];
entityIndex < self.entityCache.length;
++entityIndex, entity = self.entityCache[ entityIndex ] ) {
if( entity.id == id ) {
// Before removing the entity, allow the user to react.
self.scope.$broadcast( "beforeEntityRemoved", {
service : self,
cache : self.entityCache,
entity : entity
} );
// Remove the entity from the cache.
self.entityCache.splice( entityIndex, 1 );
// Send another event to allow the user to take note of the removal.
self.scope.$broadcast( "entityRemoved", {
service : self,
cache : self.entityCache,
entity : entity
} );
break;
}
}
return self.__cacheMaintain( self.entityCache, {
id : id
}, "delete", true );
};

@@ -731,3 +834,6 @@

// TODO: Keep a copy of the lookup table and only update it when the cached data updates
if( self.entityCache.__lookup ) {
return angular.copy( self.entityCache.__lookup );
}
var lookupTable = [];

@@ -782,9 +888,31 @@ for( var entityIndex = 0;

* type instances which are being referenced in entity.
* @param {Boolean} [force=false] If true, all complex types will be replaced with references to the
* @param {Object|Boolean} [options] A hash with options relating to the population process.
* @param {Boolean} [options.force=false] If true, all complex types will be replaced with references to the
* instances in cache; otherwise, only properties that are string representations of complex type IDs will be replaced.
* @param {Boolean} [options.crossLink=false] If true, the entity will also be put into a relating property in the
* foreign entity.
* @param {String} [options.crossLinkProperty] The name of the property in the foreign type into which the entity
* should be cross-linked.
* @returns {IPromise<TResult>|IPromise<any[]>|IPromise<{}>|angular.IPromise<TResult>}
*/
CacheService.prototype.populateComplex = function CacheService$populateComplex( entity, propertyName, cache, force ) {
CacheService.prototype.populateComplex = function CacheService$populateComplex( entity, propertyName, cache, options ) {
var self = this;
options = options || {};
if( typeof options === "boolean" ) {
self.logInterface.warn( "Argument 'force' is deprecated. Provide an options hash instead." );
options = {
force : options
};
}
options.force = options.force || false;
options.crossLink = options.crossLink || false;
options.crossLinkProperty = options.crossLinkProperty || "";
if( options.crossLink && !options.crossLinkProperty ) {
self.logInterface.warn(
"Option 'crossLink' given without 'crossLinkProperty'. Cross-linking will be disabled." );
options.crossLink = false;
}
// If the target property is an array, ...

@@ -801,3 +929,3 @@ if( Array.isArray( entity[ propertyName ] ) ) {

// If "force" is enabled, we check if this non-string property is an object and has an "id" member, which is a string.
if( force && typeof entity[ propertyName ] === "object" && typeof entity[ propertyName ].id === "string" ) {
if( options.force && typeof entity[ propertyName ] === "object" && typeof entity[ propertyName ].id === "string" ) {
// If that is true, then we replace the whole object with the ID and continue as usual.

@@ -807,2 +935,5 @@ entity[ propertyName ] = entity[ propertyName ].id;

} else {
if( self.throwFailures ) {
throw new Error( "The referenced entity did not have an 'id' property that would be expected." );
}
return self.q.when( false );

@@ -821,3 +952,3 @@ }

// If "force" is enabled, we check if this non-string property is an object and has an "id" member, which is a string.
if( force && typeof entity[ propertyName ][ index ] === "object" && typeof entity[ propertyName ][ index ].id === "string" ) {
if( options.force && typeof entity[ propertyName ][ index ] === "object" && typeof entity[ propertyName ][ index ].id === "string" ) {
// If that is true, then we replace the whole object with the ID and continue as usual.

@@ -827,2 +958,6 @@ entity[ propertyName ][ index ] = entity[ propertyName ][ index ].id;

} else {
if( self.throwFailures ) {
throw new Error( "The referenced entity did not have an 'id' property that would be expected." );
}
return self.q.when( false );

@@ -839,2 +974,7 @@ }

entity[ propertyName ][ index ] = complex;
if( options.crossLink ) {
crossLink( complex, entity );
}
return entity;

@@ -847,3 +987,34 @@ }

entity[ propertyName ] = complex;
if( options.crossLink ) {
crossLink( complex, entity );
}
return entity;
}
function crossLink( complex, entity ) {
// If cross-linking is enabled, put our entity into the foreign complex.
if( Array.isArray( complex[ options.crossLinkProperty ] ) ) {
// Check if the entity is already linked into the array.
var entityIndex = complex[ options.crossLinkProperty ].indexOf( entity );
if( -1 < entityIndex ) {
return;
}
// Check if the ID exists in the array.
var idIndex = complex[ options.crossLinkProperty ].indexOf( entity.id );
if( -1 < idIndex ) {
// Replace the ID with the entity.
complex[ options.crossLinkProperty ][ idIndex ] = entity;
return;
}
// Just push the element into the array.
complex[ options.crossLinkProperty ].push( entity );
return;
}
complex[ options.crossLinkProperty ] = entity;
}
};

@@ -859,3 +1030,5 @@

self.entityCache = self.configuration.collectionName ? [] : {};
self.entityCache = self.configuration.collectionName ? [] : {};
self.entityCache.__lookup = self.entityCache.__lookup || {};
self.__entityCacheRaw = null;

@@ -862,0 +1035,0 @@ self.__requestCache = {};

@@ -8,3 +8,4 @@ /* globals angular */

info : angular.noop,
warn : angular.noop,
error : angular.noop
} );

@@ -7,3 +7,5 @@ // jscs:disable requireNamedUnassignedFunctions

var $rootScope;
var devices;
var users;

@@ -26,7 +28,30 @@ beforeEach( function() {

} )
.constant( "Device", {} );
.config( function( _absyncProvider_ ) {
var serviceDefinition = {
model : "User",
collectionName : "users",
collectionUri : "/api/users",
entityName : "user",
entityUri : "/api/user",
debug : true
};
_absyncProvider_.configure( SockMock );
_absyncProvider_.collection( "users", serviceDefinition );
} )
.constant( "Device", {
deserialize : function( data ) {
return angular.copy( data );
}
} )
.constant( "User", {
deserialize : function( data ) {
return angular.copy( data );
}
} );
module( "absync", "test" );
} );
// Set up the devices API
beforeEach( inject( function( _$httpBackend_, _$rootScope_ ) {

@@ -40,4 +65,5 @@ $httpBackend = _$httpBackend_;

devices : [ {
id : 1,
name : "My Device"
id : "1",
name : "My Device",
owner : "1"
} ]

@@ -50,11 +76,64 @@ } );

device : {
id : 1,
name : "My Device"
id : "1",
name : "My Device",
owner : "1"
}
} );
// This device is not served with the collection GET.
$httpBackend
.when( "GET", "/api/device/2" )
.respond( {
device : {
id : "2",
name : "Another Device",
owner : "2"
}
} );
$httpBackend
.when( "DELETE", "/api/device/1" )
.respond( 200 );
} ) );
beforeEach( inject( function( _devices_ ) {
// Set up the users API
beforeEach( inject( function( _$httpBackend_, _$rootScope_ ) {
$httpBackend = _$httpBackend_;
$rootScope = _$rootScope_;
$httpBackend
.when( "GET", "/api/users" )
.respond( {
users : [ {
id : "1",
name : "John Doe"
} ]
} );
$httpBackend
.when( "GET", "/api/user/1" )
.respond( {
user : {
id : "1",
name : "John Doe"
}
} );
// This user is not served with the collection GET.
$httpBackend
.when( "GET", "/api/user/2" )
.respond( {
user : {
id : "2",
name : "Jane Smith"
}
} );
} ) );
beforeEach( inject( function( _devices_, _users_ ) {
devices = _devices_;
devices.reset();
users = _users_;
users.reset();
} ) );

@@ -72,87 +151,204 @@

it( "should cached the loaded collection", function() {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
describe( "caching", function() {
it( "should cached the loaded collection", function() {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
var entity = devices.entityCache[ 0 ];
var entity = devices.entityCache[ 0 ];
devices.ensureLoaded();
expect( devices.entityCache[ 0 ] ).to.equal( entity );
} );
devices.ensureLoaded();
expect( devices.entityCache[ 0 ] ).to.equal( entity );
} );
it( "should forget cached collections when reset", function() {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
it( "should forget cached collections when reset", function() {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
var entity = devices.entityCache[ 0 ];
devices.reset();
var entity = devices.entityCache[ 0 ];
devices.reset();
devices.ensureLoaded();
expect( devices.entityCache[ 0 ] ).to.not.equal( entity );
} );
devices.ensureLoaded();
expect( devices.entityCache[ 0 ] ).to.not.equal( entity );
} );
it( "should provide an entity", function( done ) {
devices.ensureLoaded();
$httpBackend.flush();
devices.read( 1 )
.then( function( device ) {
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
expect( device ).to.be.an( "object" ).with.property( "name" ).that.equals( "My Device" );
} )
.then( done )
.catch( done );
$rootScope.$digest();
it( "should maintain the raw entity cache for reads", function( done ) {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 1 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
devices.read( 2 )
.then( function( device ) {
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 2 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 2 );
} )
.then( done )
.catch( done );
$httpBackend.flush();
$rootScope.$digest();
} );
it( "should maintain the raw entity cache for deletes", function( done ) {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 1 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
devices.delete( {
id : "1"
} )
.then( function() {
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 0 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 0 );
} )
.then( done )
.catch( done );
$httpBackend.flush();
$rootScope.$digest();
} );
it( "should maintain the raw entity cache for updates over socket", function() {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 1 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
var updated = {
id : "1",
name : "My Updated Device",
owner : "1"
};
devices.__onEntityReceived( null, updated );
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 1 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
expect( devices.__entityCacheRaw.devices[ 0 ] ).to.eql( updated );
expect( devices.entityCache[ 0 ] ).to.eql( updated );
} );
it( "should maintain the raw entity cache for deletes over socket", function() {
devices.ensureLoaded();
$httpBackend.flush();
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 1 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
var deleted = {
id : "1"
};
devices.__onEntityReceived( null, deleted );
expect( devices.__entityCacheRaw.devices ).to.be.an( "array" ).with.length( 0 );
expect( devices.entityCache ).to.be.an( "array" ).with.length( 0 );
} );
} );
it( "should provide an entity when collection is not loaded", function( done ) {
devices.read( 1 )
.then( function( device ) {
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
expect( device ).to.be.an( "object" ).with.property( "name" ).that.equals( "My Device" );
} )
.then( done )
.catch( done );
$httpBackend.flush();
describe( "reads", function() {
it( "should provide an entity", function( done ) {
devices.ensureLoaded();
$httpBackend.flush();
devices.read( "1" )
.then( function( device ) {
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
expect( device ).to.be.an( "object" ).with.property( "name" ).that.equals( "My Device" );
} )
.then( done )
.catch( done );
$rootScope.$digest();
} );
it( "should provide an entity when collection is not loaded", function( done ) {
devices.read( 1 )
.then( function( device ) {
expect( devices.entityCache ).to.be.an( "array" ).with.length( 1 );
expect( device ).to.be.an( "object" ).with.property( "name" ).that.equals( "My Device" );
} )
.then( done )
.catch( done );
$httpBackend.flush();
} );
} );
it( "should provide seeded content", function( done ) {
devices.seed( {
describe( "seeding", function() {
it( "should provide seeded content", function( done ) {
devices.seed( {
devices : [ {
id : "1",
name : "My Device"
} ]
}
);
devices.read( "1" )
.then( function( device ) {
expect( device ).to.be.an( "object" ).with.property( "name" ).that.equals( "My Device" );
} )
.then( done )
.catch( done );
$rootScope.$digest();
} );
it( "should provide updated content when syncing after seeding", function( done ) {
var seed = {
devices : [ {
id : 1,
id : "1",
name : "My Device"
} ]
}
);
};
devices.read( 1 )
.then( function( device ) {
expect( device ).to.be.an( "object" ).with.property( "name" ).that.equals( "My Device" );
} )
.then( done )
.catch( done );
$rootScope.$digest();
devices.seed( seed );
devices.sync();
$httpBackend.flush();
devices.read( "1" )
.then( function( device ) {
expect( device ).to.not.equal( seed.devices[ 0 ] );
} )
.then( done )
.catch( done );
$rootScope.$digest();
} );
} );
it( "should provide updated content when syncing after seeding", function( done ) {
var seed = {
devices : [ {
id : 1,
name : "My Device"
} ]
};
describe( "populate complex", function() {
it( "should populate referenced complex types", function( done ) {
devices.read( "1" );
$httpBackend.flush();
devices.seed( seed );
devices.populateComplex( devices.entityCache[ 0 ], "owner", users )
.then( function() {
devices.entityCache[ 0 ].owner.should.equal( users.entityCache[ 0 ] );
} )
.then( done )
.catch( done );
devices.sync();
$httpBackend.flush();
$httpBackend.flush();
} );
devices.read( 1 )
.then( function( device ) {
expect( device ).to.not.equal( seed.devices[ 0 ] );
} )
.then( done )
.catch( done );
$rootScope.$digest();
it( "should populate referenced complex types and cross-link", function( done ) {
users.ensureLoaded();
devices.read( "1" );
$httpBackend.flush();
users.entityCache[ 0 ].devices = [];
devices.populateComplex( devices.entityCache[ 0 ], "owner", users, {
crossLink : true,
crossLinkProperty : "devices"
} )
.then( function() {
devices.entityCache[ 0 ].owner.should.equal( users.entityCache[ 0 ] );
users.entityCache[ 0 ].devices.should.be.an( "array" ).with.length( 1 );
users.entityCache[ 0 ].devices[ 0 ].should.equal( devices.entityCache[ 0 ] );
} )
.then( done )
.catch( done );
$httpBackend.flush();
} );
} );

@@ -159,0 +355,0 @@ } );

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc