Last active
August 29, 2015 14:08
-
-
Save smt/f3e86363de4ab89fc5ec to your computer and use it in GitHub Desktop.
Stache
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(function (window, document, undefined) { | |
"use strict"; | |
var _cache; | |
var _local = window.localStorage; | |
var _session = window.sessionStorage; | |
/** | |
* Validate a well-formed cache/storage object (used internally). | |
* @function | |
* @param {Object} item | |
* @returns {Boolean} | |
*/ | |
var validateItem = function validateItem(item) { | |
return (item && | |
item.hasOwnProperty('id') && | |
item.hasOwnProperty('value') && | |
item.hasOwnProperty('expires') && | |
item.hasOwnProperty('isLongTerm') && | |
typeof item.id !== 'undefined' && | |
typeof item.value !== 'undefined' && | |
typeof item.expires !== 'undefined' && | |
typeof item.isLongTerm !== 'undefined') | |
}; | |
/** | |
* Creates a new Stache object | |
* @class Stache | |
* @param {Object} [options] | |
*/ | |
var Stache = function Stache(options) { | |
var _options = options; | |
if (!_options) { | |
_options = {}; | |
} | |
// Ensure this was called with the 'new' keyword | |
if (!(this instanceof Stache)) { | |
return new Stache(options); | |
} | |
if (_cache) { | |
console.warn('There can only be one Stache cache'); | |
return this; | |
} | |
_cache = {}; | |
/** @member {Number} */ | |
this.ttl = _options.ttl || 1000*60*60*24; | |
/** @member {Number} */ | |
this.longTtl = _options.longTtl || 1000*60*60*24*30; | |
/** @member {String} */ | |
this.ns = _options.ns || 'Stache'; | |
if (_options.debug) { | |
console.log('Cleaning localStorage and sessionStorage (for testing only)'); | |
_local.removeItem(this.ns); | |
_session.removeItem(this.ns); | |
} | |
}; | |
/** | |
* Add a TTL value to a date and return a new date to use as expiry value. | |
* @private | |
* @param {Number} [ttl] | |
* @param {Date} [now] | |
* @returns {Date} | |
*/ | |
Stache.prototype._calculateExpiresDate = function _getNewExpiresDate(ttl, now) { | |
var _now = (typeof now === 'object' && now.toDateString) ? now : new Date(); | |
var _ttl = ttl || this.ttl; | |
return new Date(_now.valueOf() + _ttl); | |
}; | |
/** | |
* Create a new well-formed cache/storage object (used internally). | |
* @private | |
* @param {String} key | |
* @param {*} value | |
* @param {Boolean} [isLongTerm] | |
* @returns {Object} | |
*/ | |
Stache.prototype._createNewItem = function _createNewItem(key, value, isLongTerm) { | |
var _value = (typeof value === 'undefined') ? null : value; | |
var _isLongTerm = !!isLongTerm; | |
return { | |
id: key, | |
value: _value, | |
expires: this._calculateExpiresDate((isLongTerm ? this.ttl : this.longTtl)), | |
isLongTerm: _isLongTerm | |
}; | |
}; | |
/** | |
* @private | |
* @param {String} key | |
* @returns {Object} | |
*/ | |
Stache.prototype._getCacheItem = function _getCacheItem(key) { | |
if (!key) { | |
console.warn('Please provide a key'); | |
return; | |
} | |
var item = _cache[key]; | |
return validateItem(item) ? item : null; | |
}; | |
/** | |
* @private | |
* @param {Object} item | |
*/ | |
Stache.prototype._setCacheItem = function _setCacheItem(item) { | |
if (!validateItem(item)) { | |
console.warn('Item was not set or properly formed: ', item); | |
return; | |
} | |
_cache[item.id] = item; | |
}; | |
/** | |
* @private | |
* @param {String} key | |
*/ | |
Stache.prototype._deleteCacheItem = function _deleteCacheItem(key) { | |
if (!key) { | |
console.warn('Please provide a key'); | |
return; | |
} | |
if (typeof _cache[key] !== 'undefined') { | |
_cache[key] = null; | |
delete _cache[key]; | |
} | |
}; | |
/** | |
* @private | |
* @param {String} key | |
* @returns {Object} | |
*/ | |
Stache.prototype._getStoreItem = function _getStoreItem(key) { | |
if (!key) { | |
console.warn('Please provide a key'); | |
return; | |
} | |
var item = _cache[key]; | |
// default to session storage | |
var storeMechanism = (validateItem(item) && item.isLongTerm) ? _local : _session; | |
var storeObj = JSON.parse(storeMechanism.getItem(this.ns)); | |
if (!storeObj) { | |
return null; | |
} | |
item = (typeof storeObj[key] === 'undefined') ? null : storeObj[key]; | |
return item; | |
}; | |
/** | |
* @private | |
* @param {Object} item | |
*/ | |
Stache.prototype._setStoreItem = function _setStoreItem(item) { | |
if (!validateItem(item)) { | |
console.warn('Item was not set or properly formed: ', item); | |
return; | |
} | |
var storeMechanism = item.isLongTerm ? _local : _session; | |
var storeObj = JSON.parse(storeMechanism.getItem(this.ns)); | |
if (!storeObj) { | |
storeObj = {}; | |
} | |
storeObj[item.id] = item; | |
storeMechanism.setItem(this.ns, JSON.stringify(storeObj)); | |
}; | |
/** | |
* @private | |
* @param {String} key | |
*/ | |
Stache.prototype._deleteStoreItem = function _deleteStoreItem(key) { | |
if (!key) { | |
console.warn('Please provide a key'); | |
return; | |
} | |
var item = _cache[key]; | |
if (!validateItem(item)) { | |
console.warn('Item was not set or properly formed: ', item); | |
return; | |
} | |
var storeMechanism = item.isLongTerm ? _local : _session; | |
var storeObj = JSON.parse(storeMechanism.getItem(this.ns)); | |
storeObj[item.id] = null; | |
delete storeObj[item.id]; | |
storeMechanism.setItem(this.ns, JSON.stringify(storeObj)); | |
}; | |
/** | |
* @private | |
* @param {Object} item | |
* @returns {Boolean} | |
*/ | |
Stache.prototype._isExpired = function _isExpired(item) { | |
if (!item) { | |
console.warn('Please provide an item'); | |
return; | |
} | |
var now = new Date(); | |
return new Date(item.expires.valueOf()) < now; | |
}; | |
/** | |
* @method | |
* @param {String} key | |
* @returns {*} | |
*/ | |
Stache.prototype.get = function get(key) { | |
var now = new Date(); | |
var storeObj; | |
var item = this._getCacheItem(key); | |
if (!item) { | |
// prime cache | |
storeObj = this._getStoreItem(key); | |
if (!storeObj) { | |
storeObj = this._createNewItem(key, null); | |
} | |
this._setCacheItem(storeObj); | |
item = this._getCacheItem(key); | |
console.log('Initialized new cache item because blank "' + key + '"'); | |
console.table(_cache); | |
} else if (this._isExpired(item)) { | |
// refresh expired cached item from storage | |
storeObj = this._getStoreItem(key); | |
if (validateItem(storeObj)) { | |
item.value = storeObj.value; | |
} | |
item.expires = this._calculateExpiresDate((item.isLongTerm ? this.ttl : this.longTtl)); | |
this._setStoreItem(item); | |
this._setCacheItem(item); | |
console.log('Updated cache item because expired "' + key + '" (' + item.value + ')'); | |
console.table(_cache); | |
} else { | |
console.log('No cache init or update required, our data is still fresh.'); | |
console.table(_cache); | |
} | |
console.log('It is now ' + now.toString() + ' and _cache[' + key + '] expires at ', item.expires.toString()); | |
return item.value; | |
}; | |
/** | |
* @method | |
* @param {String} key | |
* @param {*} value | |
* @param {Boolean} [isLongTerm] | |
*/ | |
Stache.prototype.set = function set(key, value, isLongTerm) { | |
var now = new Date(); | |
var item = this._getCacheItem(key); | |
if (!item) { | |
item = this._createNewItem(key, value, isLongTerm); | |
console.log('Initialized new cache item because blank "' + key + '"'); | |
console.table(_cache); | |
} else { | |
item.value = value; | |
item.expires = this._calculateExpiresDate((isLongTerm ? this.ttl : this.longTtl)); | |
console.log('Updated existing cache item "' + key + '" (' + value + ') : ' + now.toString()); | |
console.table(_cache); | |
} | |
// Ensure we never save the same key to two different types of storage. | |
// If a different isLongTerm option is passed, delete the existing storage item. | |
if (item.isLongTerm !== !!isLongTerm) { | |
this._deleteStoreItem(key); | |
item.isLongTerm = isLongTerm; | |
} | |
this._setStoreItem(item); | |
this._setCacheItem(item); | |
}; | |
/** | |
* @method | |
* @param {String} key | |
* @param {Boolean} [confirmDeleteAll] | |
*/ | |
Stache.prototype.del = function del(key, confirmDeleteAll) { | |
// Provide the means to wipe the whole slate clean if desired. | |
if (!key && confirmDeleteAll) { | |
_session.removeItem(this.ns); | |
_local.removeItem(this.ns); | |
_cache = null; | |
_cache = {}; | |
console.log('Nuked the Stache, kaboom!'); | |
return; | |
} | |
// Important to delete store item first so that the storage mechanism, if any, can be determined | |
this._deleteStoreItem(key) | |
this._deleteCacheItem(key) | |
}; | |
return window.Stache = Stache; | |
}(this, this.document)); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!doctype html> | |
<html> | |
<head> | |
<title>Stache Example</title> | |
<script src="https://gist.githack.com/smt/f3e86363de4ab89fc5ec/raw/e722954f8442c71e1c14bead3d2d45992035d323/stache.js"></script> | |
</head> | |
<body> | |
<script> | |
var stache = new Stache({ttl:2000, longTtl:4000, debug:true}) | |
setTimeout(function () { | |
console.log('--- Getting SESSION STORAGE initial poop') | |
stache.get('poop') }, 1000) | |
setTimeout(function () { | |
console.log('--- Setting SESSION STORAGE poop to fart') | |
stache.set('poop', 'fart') }, 1100) | |
setTimeout(function () { | |
console.log('--- Getting SESSION STORAGE poop within cache expire time') | |
stache.get('poop') }, 2000) | |
setTimeout(function () { | |
console.log('--- Getting SESSION STORAGE poop after cache expire time') | |
stache.get('poop') }, 3200) | |
setTimeout(function () { | |
console.log('--- Setting SESSION STORAGE poop to null') | |
stache.set('poop', null) }, 3900) | |
setTimeout(function () { | |
console.log('--- Getting SESSION STORAGE poop within cache expire time') | |
stache.get('poop') }, 4000) | |
setTimeout(function () { | |
console.log('--- Getting SESSION STORAGE poop after cache expire time') | |
stache.get('poop') }, 6000) | |
setTimeout(function () { | |
console.log('--- Getting LOCAL STORAGE initial foo') | |
stache.get('foo') }, 7000) | |
setTimeout(function () { | |
console.log('--- Setting LOCAL STORAGE foo to bar') | |
stache.set('foo', 'bar', true) }, 7100) | |
setTimeout(function () { | |
console.log('--- Getting LOCAL STORAGE foo within cache expire time') | |
stache.get('foo') }, 8000) | |
setTimeout(function () { | |
console.log('--- Getting LOCAL STORAGE foo after cache expire time') | |
stache.get('foo') }, 11200) | |
setTimeout(function () { | |
console.log('--- Setting LOCAL STORAGE foo to null') | |
stache.set('foo', null, true) }, 11900) | |
setTimeout(function () { | |
console.log('--- Getting LOCAL STORAGE foo within cache expire time') | |
stache.get('foo') }, 13000) | |
setTimeout(function () { | |
console.log('--- Getting LOCAL STORAGE foo after cache expire time') | |
stache.get('foo') }, 16000) | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Should I even be including the longTtl value for sessionStorage item expiry? Not sure what the best use case is there.