198 lines
6.6 KiB
JavaScript
198 lines
6.6 KiB
JavaScript
define("dstore/Cache", [
|
|
'dojo/_base/array',
|
|
'dojo/when',
|
|
'dojo/_base/declare',
|
|
'dojo/_base/lang',
|
|
'./Store',
|
|
'./Memory',
|
|
'./QueryResults'
|
|
], function (arrayUtil, when, declare, lang, Store, Memory, QueryResults) {
|
|
|
|
// module:
|
|
// dstore/Cache
|
|
|
|
|
|
function cachingQuery(type) {
|
|
// ensure querying creates a parallel caching store query
|
|
return function () {
|
|
var subCollection = this.inherited(arguments);
|
|
var cachingCollection = this.cachingCollection || this.cachingStore;
|
|
subCollection.cachingCollection = cachingCollection[type].apply(cachingCollection, arguments);
|
|
subCollection.isValidFetchCache = this.canCacheQuery === true || this.canCacheQuery(type, arguments);
|
|
return subCollection;
|
|
};
|
|
}
|
|
|
|
function init (store) {
|
|
if (!store.cachingStore) {
|
|
store.cachingStore = new Memory();
|
|
}
|
|
|
|
store.cachingStore.Model = store.Model;
|
|
store.cachingStore.idProperty = store.idProperty;
|
|
}
|
|
var CachePrototype = {
|
|
cachingStore: null,
|
|
constructor: function () {
|
|
init(this);
|
|
},
|
|
canCacheQuery: function (method, args) {
|
|
// summary:
|
|
// Indicates if a queried (filter, sort, etc.) collection should using caching
|
|
return false;
|
|
},
|
|
isAvailableInCache: function () {
|
|
// summary:
|
|
// Indicates if the collection's cachingCollection is a viable source
|
|
// for a fetch
|
|
return (this.isValidFetchCache && (this.allLoaded || this.fetchRequest)) ||
|
|
this._parent && this._parent.isAvailableInCache();
|
|
},
|
|
fetch: function () {
|
|
return this._fetch(arguments);
|
|
},
|
|
fetchRange: function () {
|
|
return this._fetch(arguments, true);
|
|
},
|
|
_fetch: function (args, isRange) {
|
|
// if the data is available in the cache (via any parent), we use fetch from the caching store
|
|
var cachingStore = this.cachingStore;
|
|
var cachingCollection = this.cachingCollection || cachingStore;
|
|
var store = this;
|
|
var available = this.isAvailableInCache();
|
|
if (available) {
|
|
return new QueryResults(when(available, function () {
|
|
// need to double check to make sure the flag hasn't been cleared
|
|
// and we really have all data loaded
|
|
if (store.isAvailableInCache()) {
|
|
return isRange ?
|
|
cachingCollection.fetchRange(args[0]) :
|
|
cachingCollection.fetch();
|
|
} else {
|
|
return store.inherited(args);
|
|
}
|
|
}));
|
|
}
|
|
var results = this.fetchRequest = this.inherited(args);
|
|
when(results, function (results) {
|
|
var allLoaded = !isRange;
|
|
store.fetchRequest = null;
|
|
// store each object before calling the callback
|
|
arrayUtil.forEach(results, function (object) {
|
|
// store each object before calling the callback
|
|
if (!store.isLoaded || store.isLoaded(object)) {
|
|
cachingStore.put(object);
|
|
} else {
|
|
// if anything is not loaded, we can't consider them all loaded
|
|
allLoaded = false;
|
|
}
|
|
});
|
|
if (allLoaded) {
|
|
store.allLoaded = true;
|
|
}
|
|
|
|
return results;
|
|
});
|
|
return results;
|
|
},
|
|
// TODO: for now, all forEach() calls delegate to fetch(), but that may be different
|
|
// with IndexedDB, so we may need to intercept forEach as well (and hopefully not
|
|
// double load elements.
|
|
// isValidFetchCache: boolean
|
|
// This flag indicates if a previous fetch can be used as a cache for subsequent
|
|
// fetches (in this collection, or downstream).
|
|
isValidFetchCache: false,
|
|
get: function (id, directives) {
|
|
var cachingStore = this.cachingStore;
|
|
var masterGet = this.getInherited(arguments);
|
|
var masterStore = this;
|
|
// if everything is being loaded, we always wait for that to finish
|
|
return when(this.fetchRequest, function () {
|
|
return when(cachingStore.get(id), function (result) {
|
|
if (result !== undefined) {
|
|
return result;
|
|
} else if (masterGet) {
|
|
return when(masterGet.call(masterStore, id, directives), function (result) {
|
|
if (result) {
|
|
cachingStore.put(result, {id: id});
|
|
}
|
|
return result;
|
|
});
|
|
}
|
|
});
|
|
});
|
|
},
|
|
add: function (object, directives) {
|
|
var cachingStore = this.cachingStore;
|
|
return when(this.inherited(arguments), function (result) {
|
|
// now put result in cache (note we don't do add, because add may have
|
|
// called put() and already added it)
|
|
var cachedPutResult =
|
|
cachingStore.put(result && typeof result === 'object' ? result : object, directives);
|
|
// the result from the add should be dictated by the master store and be unaffected by the cachingStore,
|
|
// unless the master store doesn't implement add
|
|
return result || cachedPutResult;
|
|
});
|
|
},
|
|
put: function (object, directives) {
|
|
// first remove from the cache, so it is empty until we get a response from the master store
|
|
var cachingStore = this.cachingStore;
|
|
cachingStore.remove((directives && directives.id) || this.getIdentity(object));
|
|
return when(this.inherited(arguments), function (result) {
|
|
// now put result in cache
|
|
var cachedPutResult =
|
|
cachingStore.put(result && typeof result === 'object' ? result : object, directives);
|
|
// the result from the put should be dictated by the master store and be unaffected by the cachingStore,
|
|
// unless the master store doesn't implement put
|
|
return result || cachedPutResult;
|
|
});
|
|
},
|
|
remove: function (id, directives) {
|
|
var cachingStore = this.cachingStore;
|
|
return when(this.inherited(arguments), function (result) {
|
|
return when(cachingStore.remove(id, directives), function () {
|
|
return result;
|
|
});
|
|
});
|
|
},
|
|
evict: function (id) {
|
|
// summary:
|
|
// Evicts an object from the cache
|
|
// any eviction means that we don't have everything loaded anymore
|
|
this.allLoaded = false;
|
|
return this.cachingStore.remove(id);
|
|
},
|
|
invalidate: function () {
|
|
// summary:
|
|
// Invalidates this collection's cache as being a valid source of
|
|
// future fetches
|
|
this.allLoaded = false;
|
|
},
|
|
_createSubCollection: function () {
|
|
var subCollection = this.inherited(arguments);
|
|
subCollection._parent = this;
|
|
return subCollection;
|
|
},
|
|
|
|
sort: cachingQuery('sort'),
|
|
filter: cachingQuery('filter'),
|
|
select: cachingQuery('select'),
|
|
|
|
_getQuerierFactory: function (type) {
|
|
var cachingStore = this.cachingStore;
|
|
return this.inherited(arguments) || lang.hitch(cachingStore, cachingStore._getQuerierFactory(type));
|
|
}
|
|
};
|
|
var Cache = declare(null, CachePrototype);
|
|
Cache.create = function (target, properties) {
|
|
// create a delegate of an existing store with caching
|
|
// functionality mixed in
|
|
target = declare.safeMixin(lang.delegate(target), CachePrototype);
|
|
declare.safeMixin(target, properties);
|
|
// we need to initialize it since the constructor won't have been called
|
|
init(target);
|
|
return target;
|
|
};
|
|
return Cache;
|
|
});
|