src/service.js
import angular from 'angular'
import deepcopy from 'deepcopy'
export default httpEtagProvider
function httpEtagProvider () {
var httpEtagProvider = this
var serviceAdapterMethods = [
'createCache',
'getCache'
]
var cacheAdapterMethods = [
'setItem',
'getItem',
'removeItem',
'removeAllItems'
// info method hard-coded
]
var requiredAdapterMethods = serviceAdapterMethods.concat(cacheAdapterMethods)
// Built-in adapters defined in ./cacheServiceAdapters.js
var cacheAdapters = {}
var cacheDefinitions = {}
// Cache config defaults
var defaultCacheId = 'httpEtagCache'
var defaultEtagCacheConfig = {
deepCopy: false,
cacheResponseData: true,
cacheService: '$cacheFactory',
cacheOptions: {
number: 25
}
}
/**
* SERVICE PROVIDER
* .setDefaultCacheConfig(config)
* .defineCache(cacheId, config)
* .defineCacheServiceAdapter(serviceName, config)
* .getCacheServiceAdapter(serviceName)
*/
httpEtagProvider.setDefaultCacheConfig = function httpEtagSetDefaultCacheOptions (config) {
defaultEtagCacheConfig = angular.extend({}, defaultEtagCacheConfig, config)
return httpEtagProvider
}
httpEtagProvider.getDefaultCacheConfig = function httpEtagGetDefaultCacheOptions () {
return defaultEtagCacheConfig
}
httpEtagProvider.defineCache = function httpEtagDefineCache (cacheId, config) {
config = angular.extend({}, defaultEtagCacheConfig, config, { id: cacheId })
cacheDefinitions[cacheId] = config
return httpEtagProvider
}
httpEtagProvider.defineCacheServiceAdapter = function httpEtagDefineCacheServiceAdapter (serviceName, config) {
if (!config) throw new Error('Missing cache service adapter configuration')
if (!config.methods) throw new Error('Missing cache service adapter configuration methods')
angular.forEach(requiredAdapterMethods, function (method) {
if (typeof config.methods[method] !== 'function') {
throw new Error('Expected cache service adapter method "' + method + '" to be a function')
}
})
cacheAdapters[serviceName] = config
return httpEtagProvider
}
httpEtagProvider.getCacheServiceAdapter = function httpEtagGetCacheServiceAdapter (serviceName) {
return cacheAdapters[serviceName]
}
/**
* SERVICE
* .info()
* .getCache(acheId)
* .getItemCache(cacheId, itemKey)
*/
httpEtagProvider.$get = ['$injector', function httpEtagFactory ($injector) {
var httpEtagService = {}
var services = {}
var adaptedServices = {}
var caches = {}
var adaptedCaches = {}
// Add default cache if not already defined
if (!cacheDefinitions[defaultCacheId]) httpEtagProvider.defineCache(defaultCacheId)
// Find/inject cache service and create adapted versions
angular.forEach(cacheAdapters, function adaptCacheService (adapter, serviceName) {
var service = services[serviceName] = window[serviceName] || $injector.get(serviceName)
var adaptedService = adaptedServices[serviceName] = {}
angular.forEach(serviceAdapterMethods, function (method) {
adaptedService[method] = angular.bind({}, adapter.methods[method], service)
})
})
// Instantiate caches and create adapted versions
angular.forEach(cacheDefinitions, function adaptCache (config, cacheId) {
adaptedServices[config.cacheService].createCache(cacheId, config)
var cache = caches[cacheId] = adaptedServices[config.cacheService].getCache(cacheId)
var adaptedCache = adaptedCaches[cacheId] = {}
// Determine whether to perform deepcopying or not
var serviceDeepCopies = cacheAdapters[config.cacheService].config.storesDeepCopies
var deepCopy = !serviceDeepCopies && cacheDefinitions[cacheId].deepCopy
var copy = function (value) {
return deepCopy ? deepcopy(value) : value
}
angular.forEach(cacheAdapterMethods, function (method) {
var adapterMethod = cacheAdapters[config.cacheService].methods[method]
var wrappedMethod
var wrappedRawMethod
// Wrap set/get methods to set/get to the `responseData` property of an
// object. This is where the $http interceptor stores response data.
if (method === 'getItem') {
wrappedMethod = function getCacheItemResponseData (cache, itemKey, options) {
var cachedData = adapterMethod(cache, itemKey, options)
return cachedData && copy(cachedData.responseData)
}
wrappedRawMethod = function getCacheItemData (cache, itemKey, options) {
return copy(adapterMethod(cache, itemKey, options))
}
}
if (method === 'setItem') {
wrappedMethod = function setCacheItemResponseData (cache, itemKey, value, options) {
var cachedData = adaptedCache.$getItem(itemKey)
value = copy(value)
if (cachedData && typeof cachedData === 'object') {
cachedData.responseData = value
value = cachedData
} else value = { responseData: value }
adapterMethod(cache, itemKey, value, options)
}
wrappedRawMethod = function setCacheItemData (cache, itemKey, value, options) {
adapterMethod(cache, itemKey, copy(value), options)
}
}
adaptedCache[method] = angular.bind({}, (wrappedMethod || adapterMethod), cache)
if (wrappedRawMethod) {
adaptedCache['$' + method] = angular.bind({}, wrappedRawMethod, cache)
}
})
adaptedCache.unsetItem = function adaptedCacheUnsetItemCache (itemKey) {
adaptedCache.setItem(itemKey, undefined)
}
adaptedCache.expireItem = function adaptedCacheUnsetItemCache (itemKey) {
var data = adaptedCache.$getItem(itemKey)
delete data.etagHeader
adaptedCache.$setItem(itemKey, data)
}
adaptedCache.getItemCache = function adaptedCacheGetItemCache (itemKey) {
return httpEtagService.getItemCache(cacheId, itemKey)
}
adaptedCache.info = function adaptedCacheInfo () {
return cacheDefinitions[cacheId]
}
adaptedCache.isCache = true
})
httpEtagService.info = function httpEtagServiceInfo () {
return cacheDefinitions
}
httpEtagService.getCache = function httpEtagServiceGetCache (cacheId) {
var cache = adaptedCaches[processCacheId(cacheId)]
if (cache) return cache
}
httpEtagService.getItemCache = function httpEtagServiceGeItemCache (cacheId, itemKey) {
var cache = httpEtagService.getCache(cacheId)
var itemCache = {}
if (!cache) return
var methodMappings = [
['set', 'setItem'],
['get', 'getItem'],
['$set', '$setItem'],
['$get', '$getItem'],
['unset', 'unsetItem'],
['expire', 'expireItem'],
['remove', 'removeItem']
]
angular.forEach(methodMappings, function mapCacheMethdodsToItemCache (methods) {
itemCache[methods[0]] = angular.bind({}, cache[methods[1]], itemKey)
})
itemCache.info = function itemCacheInfo () {
var itemCacheInfo = cache.info()
itemCacheInfo.itemKey = itemKey
return itemCacheInfo
}
itemCache.isItemCache = true
return itemCache
}
httpEtagService.purgeCaches = function httpEtagPurgeCaches () {
angular.forEach(adaptedCaches, function (cache) {
cache.removeAllItems()
})
}
function processCacheId (cacheId) {
var type = typeof cacheId
var isDefined = type === 'number' || (type === 'string' && !!cacheId)
return isDefined ? cacheId : defaultCacheId
}
return httpEtagService
}]
}