Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 161 additions & 74 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,90 @@ let _keySet = collection => {
return keySet;
};

/**
* Replacement Strategies must implement onGet, onPut and onRemove callbacks and priorityQueue
* onGet -> Called after an item is retrieved from the cache by the client
* onPut -> Called after an item is added to the cache for the first time by the client.
* Also called when reinitializing a cache from localStorage.
* onRemove -> Called after an item is removed from the cache by the client
* priorityQueue -> Retrieves an object that implements the following API
* priorityQueue.size() - the number of items being tracked
* priorityQueue.peek() - which returns next item to remove
* priorityQueue.removeAll() - which resets underlying tracking mechanism
*/
class BaseLruStrategy {
constructor() {
this.$$lruHeap = new BinaryHeap(function (x) {
return x.accessed;
}, utils.equals);
}

priorityQueue() {
return this.$$lruHeap;
}
}

class DefaultLruStrategy extends BaseLruStrategy {
constructor() {
super();
}

onGet(key, item) {
let now = Date.now();
this.$$lruHeap.remove(item);
item.accessed = now;
this.$$lruHeap.push(item);
return true;
}
onPut(key, item) {
this.$$lruHeap.push(item);
}
onRemove(key, item) {
this.$$lruHeap.remove(item);
}
}

class StorageLruStrategy extends BaseLruStrategy {
constructor() {
super();
}

onGet(key, item) {
let now = Date.now();
this.$$lruHeap.remove({
key : key,
accessed: item.accessed
});
item.accessed = now;
this.$$lruHeap.push({
key : key,
accessed: now
});
return true;
}
onPut(key, item) {
this.$$lruHeap.push({
key : key,
accessed: item.accessed
});
}
onRemove(key, item) {
this.$$lruHeap.remove({
key : key,
accessed: item.accessed
});
}
}

let lru = {
default() {
return new DefaultLruStrategy();
},
withStorage() {
return new StorageLruStrategy();
}
};

let defaults = {
capacity: Number.MAX_VALUE,
maxAge: Number.MAX_VALUE,
Expand All @@ -75,7 +159,8 @@ let defaults = {
disabled: false,
storagePrefix: 'cachefactory.caches.',
storeOnResolve: false,
storeOnReject: false
storeOnReject: false,
replacementStrategy: lru
};

let caches = {};
Expand All @@ -90,8 +175,8 @@ let createCache = (cacheId, options) => {
let $$data = {};
let $$promises = {};
let $$storage = null;
let $$replacementStrategy = null;
let $$expiresHeap = new BinaryHeap(x => x.expires, utils.equals);
let $$lruHeap = new BinaryHeap(x => x.accessed, utils.equals);

let cache = caches[cacheId] = {

Expand All @@ -107,7 +192,7 @@ let createCache = (cacheId, options) => {
}
$$storage = null;
$$data = null;
$$lruHeap = null;
$$replacementStrategy = null;
$$expiresHeap = null;
this.$$prefix = null;
delete caches[this.$$id];
Expand Down Expand Up @@ -151,48 +236,15 @@ let createCache = (cacheId, options) => {
throw new Error('options.onExpire must be a function!');
}

let item;

if ($$storage) {
if ($$promises[key]) {
return $$promises[key];
}

let itemJson = $$storage().getItem(`${this.$$prefix}.data.${key}`);

if (itemJson) {
item = utils.fromJson(itemJson);
} else {
return;
}
} else {
if (!(key in $$data)) {
return;
}

item = $$data[key];
let item = this.doGetItem(key);
if (!item || _isPromiseLike(item)) {
return item;
}

let value = item.value;
let now = new Date().getTime();

if ($$storage) {
$$lruHeap.remove({
key: key,
accessed: item.accessed
});
item.accessed = now;
$$lruHeap.push({
key: key,
accessed: now
});
} else {
$$lruHeap.remove(item);
item.accessed = now;
$$lruHeap.push(item);
}
var modified = $$replacementStrategy.onGet(key, item);

if (this.$$deleteOnExpire === 'passive' && 'expires' in item && item.expires < now) {
let value = item.value;
if (this.$$deleteOnExpire === 'passive' && 'expires' in item && item.expires < Date.now()) {
this.remove(key);

if (this.$$onExpire) {
Expand All @@ -201,7 +253,7 @@ let createCache = (cacheId, options) => {
options.onExpire.call(this, key, item.value);
}
value = undefined;
} else if ($$storage) {
} else if ($$storage && modified) {
$$storage().setItem(`${this.$$prefix}.data.${key}`, JSON.stringify(item));
}

Expand Down Expand Up @@ -251,7 +303,7 @@ let createCache = (cacheId, options) => {
storageMode: this.$$storageMode,
storageImpl: $$storage ? $$storage() : undefined,
disabled: !!this.$$disabled,
size: $$lruHeap && $$lruHeap.size() || 0
size: $$replacementStrategy && $$replacementStrategy.priorityQueue().size() || 0
};
}
},
Expand Down Expand Up @@ -359,10 +411,7 @@ let createCache = (cacheId, options) => {
expires: item.expires
});
// Add to lru heap
$$lruHeap.push({
key: key,
accessed: item.accessed
});
$$replacementStrategy.onPut(key, item);
// Set item
$$storage().setItem(`${this.$$prefix}.data.${key}`, JSON.stringify(item));
let exists = false;
Expand All @@ -384,15 +433,16 @@ let createCache = (cacheId, options) => {
// Add to expires heap
$$expiresHeap.push(item);
// Add to lru heap
$$lruHeap.push(item);
$$replacementStrategy.onPut(key, item);
// Set item
$$data[key] = item;
delete $$promises[key];
}

// Handle exceeded capacity
if ($$lruHeap.size() > this.$$capacity) {
this.remove($$lruHeap.peek().key);
let replacementQueue = $$replacementStrategy.priorityQueue();
if (replacementQueue.size() > this.$$capacity) {
this.remove(replacementQueue.peek().key);
}

return value;
Expand All @@ -406,10 +456,7 @@ let createCache = (cacheId, options) => {

if (itemJson) {
let item = utils.fromJson(itemJson);
$$lruHeap.remove({
key: key,
accessed: item.accessed
});
$$replacementStrategy.onRemove(key, item);
$$expiresHeap.remove({
key: key,
expires: item.expires
Expand All @@ -427,7 +474,7 @@ let createCache = (cacheId, options) => {
}
} else {
let value = $$data[key] ? $$data[key].value : undefined;
$$lruHeap.remove($$data[key]);
$$replacementStrategy.onRemove(key, $$data[key]);
$$expiresHeap.remove($$data[key]);
$$data[key] = null;
delete $$data[key];
Expand All @@ -436,9 +483,11 @@ let createCache = (cacheId, options) => {
},

removeAll() {
if ($$replacementStrategy) {
$$replacementStrategy.priorityQueue().removeAll();
}
$$expiresHeap.removeAll();
if ($$storage) {
$$lruHeap.removeAll();
$$expiresHeap.removeAll();
let keysJson = $$storage().getItem(`${this.$$prefix}.keys`);

if (keysJson) {
Expand All @@ -450,8 +499,6 @@ let createCache = (cacheId, options) => {
}
$$storage().setItem(`${this.$$prefix}.keys`, JSON.stringify([]));
} else {
$$lruHeap.removeAll();
$$expiresHeap.removeAll();
for (var key in $$data) {
$$data[key] = null;
}
Expand Down Expand Up @@ -523,8 +570,9 @@ let createCache = (cacheId, options) => {
this.$$capacity = capacity;
}
let removed = {};
while ($$lruHeap.size() > this.$$capacity) {
removed[$$lruHeap.peek().key] = this.remove($$lruHeap.peek().key);
let replacementQueue = $$replacementStrategy.priorityQueue();
while (replacementQueue.size() > this.$$capacity) {
removed[replacementQueue.peek().key] = this.remove(replacementQueue.peek().key);
}
return removed;
},
Expand Down Expand Up @@ -632,9 +680,9 @@ let createCache = (cacheId, options) => {
}

if ('storageMode' in cacheOptions || 'storageImpl' in cacheOptions) {
this.setStorageMode(cacheOptions.storageMode || defaults.storageMode, cacheOptions.storageImpl || defaults.storageImpl);
this.setStorageMode(cacheOptions.storageMode || defaults.storageMode, cacheOptions.storageImpl || defaults.storageImpl, cacheOptions.replacementStrategy || defaults.replacementStrategy);
} else if (strict) {
this.setStorageMode(defaults.storageMode, defaults.storageImpl);
this.setStorageMode(defaults.storageMode, defaults.storageImpl, defaults.replacementStrategy);
}

if ('storeOnResolve' in cacheOptions) {
Expand Down Expand Up @@ -708,7 +756,29 @@ let createCache = (cacheId, options) => {
}
},

setStorageMode(storageMode, storageImpl) {
doGetItem(key) {
if ($$storage) {
if ($$promises[key]) {
return $$promises[key];
}

let itemJson = $$storage().getItem(this.$$prefix + '.data.' + key);

if (itemJson) {
return utils.fromJson(itemJson);
} else {
return;
}
} else {
if (!(key in $$data)) {
return;
}

return $$data[key];
}
},

setStorageMode(storageMode, storageImpl, replacementStrategy) {
if (!utils.isString(storageMode)) {
throw new Error('storageMode must be a string!');
} else if (storageMode !== 'memory' && storageMode !== 'localStorage' && storageMode !== 'sessionStorage') {
Expand All @@ -718,18 +788,20 @@ let createCache = (cacheId, options) => {
let shouldReInsert = false;
let items = {};

let keys = this.keys();
if (typeof this.$$storageMode === 'string' && this.$$storageMode !== storageMode) {
let keys = this.keys();

if (keys.length) {
for (var i = 0; i < keys.length; i++) {
items[keys[i]] = this.get(keys[i]);
}
for (i = 0; i < keys.length; i++) {
this.remove(keys[i]);
if (keys.length) {
for (var i = 0; i < keys.length; i++) {
items[keys[i]] = this.get(keys[i]);
}
for (i = 0; i < keys.length; i++) {
this.remove(keys[i]);
}
shouldReInsert = true;
}
shouldReInsert = true;
}

this.$$storageMode = storageMode;

if (storageImpl) {
Expand Down Expand Up @@ -763,10 +835,25 @@ let createCache = (cacheId, options) => {
}
}

$$replacementStrategy = $$storage ? replacementStrategy.withStorage() : replacementStrategy.default();

if (shouldReInsert) {
for (var key in items) {
this.put(key, items[key]);
}
} else if ($$storage) {
let keys = this.keys();
if (keys.length) {
for (var j = 0; j < keys.length; j++) {
let k = keys[j];
let item = this.doGetItem(k);
$$replacementStrategy.onPut(k, item);
$$expiresHeap.push({
key: k,
expires: item.expires
});
}
}
}
},

Expand Down
1 change: 1 addition & 0 deletions test/unit/DSCacheFactory/index.info.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ describe('CacheFactory.info()', function () {
assert.equal(info.recycleFreq, CACHE_DEFAULTS.recycleFreq);
assert.equal(info.storageMode, CACHE_DEFAULTS.storageMode);
assert.equal(info.storageImpl, CACHE_DEFAULTS.storageImpl);
assert.isNotNull(info.replacementStrategy);

assert.equal(info.caches.cache.id, caches[0].info().id);
assert.equal(info.caches.cache.capacity, caches[0].info().capacity);
Expand Down