Comparing version 0.0.2 to 1.0.0
{ | ||
"name": "stash.js", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"authors": [ | ||
@@ -25,3 +25,6 @@ "Tadeu Zagallo <tadeuzagallo@gmail.com>" | ||
"chai": "~1.9.1" | ||
}, | ||
"dependencies": { | ||
"bluebird": "~1.2.4" | ||
} | ||
} |
{ | ||
"name": "stash.js", | ||
"version": "0.0.2", | ||
"version": "1.0.0", | ||
"description": "Multilayer Cache Manager for JavaScript", | ||
"main": "src/stash.js", | ||
"scripts": { | ||
"test": "mocha spec" | ||
"test": "mocha -- test/*_test.js test/*/*_test.js test/*/*/*_test.js" | ||
}, | ||
@@ -27,3 +27,20 @@ "repository": { | ||
"mocha": "~1.18.2" | ||
}, | ||
"dependencies": { | ||
"bluebird": "~1.2.4" | ||
}, | ||
"testling": { | ||
"harness": "mocha-bdd", | ||
"files": ["test/test_helper.js", "test/*_test.js", "test/*/*_test.js", "test/*/*/*_test.js"], | ||
"browsers": [ | ||
"ie/9..latest", | ||
"chrome/22..latest", | ||
"firefox/16..latest", | ||
"safari/5..latest", | ||
"opera/11.0..latest", | ||
"iphone/5..latest", | ||
"ipad/5..latest", | ||
"android-browser/4..latest" | ||
] | ||
} | ||
} |
@@ -1,10 +0,16 @@ | ||
# Stash.js # | ||
# Stash.js [![Build Status](https://travis-ci.org/tadeuzagallo/stash.js.svg?branch=master)](https://travis-ci.org/tadeuzagallo/stash.js) [![Code Climate](https://codeclimate.com/github/tadeuzagallo/stash.js.png)](https://codeclimate.com/github/tadeuzagallo/stash.js) # | ||
Inspired by the [php library](https://github.com/tedivm/Stash), Stash makes it easier to save your data on multiple layers of cache. | ||
Inspired by the [php library](https://github.com/tedivm/Stash), Stash makes it easier to save your data on multiple layers of cache | ||
## Usage ## | ||
You can install it via [bower](http://bower.io/) | ||
You can either install via [npm](https://www.npmjs.org) | ||
```javascript | ||
$ npm install stash.js | ||
``` | ||
Or via [bower](http://bower.io/) | ||
``` | ||
$ bower install stash | ||
@@ -17,16 +23,35 @@ ``` | ||
*Syntax Updated:* in order to add new drivers, I had to replace the old sync syntax, the new one is based on promises, and it now depends on [bluebird](https://github.com/petkaantonov/bluebird) | ||
```javascript | ||
// Initialize a stash pool | ||
var stash = new Stash.Pool(); | ||
var item = stash.getItem('my/key/path'); | ||
var data = item.get(); | ||
if (item.isMiss()) { | ||
item.lock(); // Check invalidation options to see how it affects lock behavior | ||
// Short version -- with Promise | ||
data = ... | ||
stash.get('my/key/path').catch(function (err) { | ||
// Data is either inexistent or expired | ||
return slowFuncThatReturnsPromise().then(this.save); | ||
}); | ||
item.set(data, cacheDuration); | ||
} | ||
// Long Version (stash.get does all of it internally) | ||
var item = stash.getItem('my/key/path'); | ||
item.get().then(function (data) { | ||
item.isMiss().then(function (missed) { | ||
if (missed) { | ||
item.lock(); // Async lock | ||
actuallyFetchData(function (data) { | ||
item.set(data); | ||
callback(data); | ||
}); | ||
} else { | ||
callback(data); | ||
} | ||
}); | ||
}); | ||
``` | ||
You can run the node.js samples on the `examples` folder to see a really basic demo | ||
### Managing drivers ### | ||
@@ -37,3 +62,3 @@ | ||
var localStorage = new Stash.Drivers.LocalStorage('my-custom-namespace'); | ||
var pool = new Stash.Pool([ephemeral, localStorage]); // It read on this order, and writes in reverse order | ||
var pool = new Stash.Pool([ephemeral, localStorage]); // It reads on this order, and writes in reverse order | ||
``` | ||
@@ -62,3 +87,3 @@ | ||
* Add more drivers | ||
* Suporte Node.js as well | ||
* ~~Suporte Node.js as well~~ Add Node.js exclusive drivers | ||
* Maybe a jQuery plugin to integrate with ajax calls | ||
@@ -65,0 +90,0 @@ |
373
src/stash.js
@@ -1,4 +0,10 @@ | ||
(function (exports) { | ||
var Stash = exports.Stash || {}; | ||
(function (global) { | ||
var Stash = global.Stash || {}; | ||
var isNode = typeof require !== 'undefined'; | ||
var Promise = global.Promise; | ||
if (isNode) { | ||
Promise = require('bluebird'); | ||
} | ||
Stash.Item = (function () { | ||
@@ -8,2 +14,7 @@ function Item(key, pool) { | ||
this.pool = pool; | ||
this.save = (function (that) { | ||
return function () { | ||
that.save.apply(that, arguments); | ||
}; | ||
})(this); | ||
@@ -19,25 +30,25 @@ this._unload_(); | ||
Item.prototype._unload_ = function () { | ||
this._loaded_ = false; | ||
this.value = null; | ||
this.expiration = false; | ||
this.locked = false; | ||
} | ||
this.locked = undefined; | ||
}; | ||
Item.prototype._load_ = function () { | ||
if (!this._loaded_) { | ||
this._loaded_ = true; | ||
var that = this; | ||
Item.prototype._load_ = function (callback) { | ||
var that = this; | ||
var value = this.pool.drivers.reduce(function (a, b) { | ||
return a || b.get(that.key); | ||
}, false); | ||
return new Promise(function (resolve, reject) { | ||
that.pool.drivers.forEach(function (d) { | ||
d.get(that.key).then(resolve); | ||
}); | ||
}); | ||
}; | ||
if (value) { | ||
this.value = value.value; | ||
this.expiration = value.expiration; | ||
this.locked = value.locked; | ||
} | ||
} | ||
Item.prototype._write_ = function (callback) { | ||
var that = this; | ||
return this; | ||
return new Promise(function (resolve, reject) { | ||
that.pool.drivers.reverse().forEach(function (d) { | ||
d.put(that.key, that.value, that.expiration).then(resolve); | ||
}); | ||
}); | ||
}; | ||
@@ -56,43 +67,85 @@ | ||
Item.prototype._write_ = function () { | ||
Item.prototype.get = function (cachePolicy, policyData) { | ||
var that = this; | ||
this.pool.drivers.reverse().forEach(function (driver) { | ||
driver.put(that.key, that.value, that.expiration, that.locked); | ||
}); | ||
}; | ||
Item.prototype.get = function (cachePolicy, policyData) { | ||
this.cachePolicy = cachePolicy || Stash.Item.SP_NONE; | ||
this.policyData = policyData; | ||
if (cachePolicy & Stash.Item.SP_VALUE && this.locked) { | ||
return policyData; | ||
function load(resolve) { | ||
that._load_().then(function (data) { | ||
if (data) { | ||
that.value = data.value; | ||
that.expiration = data.expiration; | ||
data = typeof data.value === 'undefined' ? null : data.value; | ||
} | ||
resolve(data); | ||
}); | ||
} | ||
return this._load_().value; | ||
return new Promise(function (resolve, reject) { | ||
if (that.cachePolicy & Stash.Item.SP_VALUE) { | ||
that.isLocked().then(function (locked) { | ||
if (locked) { | ||
resolve(that.policyData); | ||
} else { | ||
load(resolve); | ||
} | ||
}); | ||
} else { | ||
load(resolve); | ||
} | ||
}); | ||
}; | ||
// alias for better syntax on Pool#get | ||
Item.prototype.save = function (value) { | ||
this.set(value); | ||
return value; | ||
}; | ||
Item.prototype.set = function (value, expiration) { | ||
if (typeof expiration === 'function') { | ||
callback = expiration; | ||
expiration = null; | ||
} | ||
this._unload_(); | ||
this.expiration = Item._calculateExpiration_(expiration); | ||
this.locked = false; | ||
this.value = value; | ||
this._write_(); | ||
this.unlock(); | ||
return this._write_(); | ||
}; | ||
Item.prototype.isMiss = function () { | ||
this._load_(); | ||
Item.prototype.isMiss = function (callback) { | ||
var that = this; | ||
if (this.locked && this.cachePolicy & Stash.Item.SP_OLD) { | ||
return false; | ||
} else if (!this.locked && | ||
this.cachePolicy & Stash.Item.SP_PRECOMPUTE && | ||
this.policyData * 1000 >= this.expiration - Date.now()) { | ||
return true; | ||
var isMissed = function (locked, resolve) { | ||
var miss; | ||
if (locked && (that.cachePolicy & Stash.Item.SP_OLD)) { | ||
miss = false; | ||
} else if (!locked && | ||
(that.cachePolicy & Stash.Item.SP_PRECOMPUTE) && | ||
that.policyData * 1000 >= that.expiration - Date.now()) { | ||
miss = true; | ||
} else { | ||
miss = that.value === null || | ||
typeof(that.expiration) === 'number' && | ||
that.expiration < Date.now(); | ||
} | ||
resolve(miss); | ||
} | ||
return typeof(this.expiration) === 'number' && this.expiration < Date.now(); | ||
return new Promise(function (resolve, reject) { | ||
if (that.locked !== undefined) { | ||
isMissed(that.locked, resolve); | ||
} else { | ||
that.isLocked().then(function (locked) { | ||
isMissed(locked, resolve); | ||
}); | ||
} | ||
}); | ||
}; | ||
@@ -102,7 +155,8 @@ | ||
var that = this; | ||
this._unload_(); | ||
this.pool.drivers.forEach(function (driver) { | ||
driver.delete(that.key); | ||
return new Promise(function (resolve, reject) { | ||
that.pool.drivers.forEach(function (driver) { | ||
driver.delete(that.key).then(resolve); | ||
}); | ||
}); | ||
@@ -112,7 +166,47 @@ }; | ||
Item.prototype.lock = function () { | ||
this._load_(); | ||
if (this.locked === true) { | ||
return Promise.cast(this.locked); | ||
} | ||
this.locked = true; | ||
this._write_(); | ||
var that = this; | ||
return new Promise(function (resolve, reject) { | ||
that.pool.drivers.forEach(function (d) { | ||
d.lock(that.key).then(resolve); | ||
}); | ||
}); | ||
}; | ||
Item.prototype.isLocked = function () { | ||
if (this.locked !== undefined) { | ||
return Promise.cast(this.locked); | ||
} | ||
var that = this; | ||
return new Promise(function (resolve, reject) { | ||
that.pool.drivers.forEach(function (d) { | ||
d.isLocked(that.key).then(function (locked) { | ||
that.locked = locked; | ||
resolve(locked); | ||
}); | ||
}); | ||
}); | ||
}; | ||
Item.prototype.unlock = function () { | ||
if (this.locked === false) { | ||
return Promise.cast(); | ||
} | ||
this.locked = false; | ||
var that = this; | ||
return new Promise(function (resolve, reject) { | ||
that.pool.drivers.forEach(function (d) { | ||
d.unlock(that.key).then(resolve); | ||
}); | ||
}); | ||
} | ||
return Item; | ||
@@ -133,3 +227,3 @@ })(); | ||
Pool.prototype.getItem = function (key) { | ||
item = new Stash.Item(key, this); | ||
var item = new Stash.Item(key, this); | ||
@@ -145,2 +239,19 @@ return item; | ||
Pool.prototype.get = function (key) { | ||
var item = this.getItem(key); | ||
return new Promise(function (resolve, reject) { | ||
item.get().then(function (data) { | ||
item.isMiss().then(function (missed) { | ||
if (missed) { | ||
item.lock(); | ||
reject('Cache is missing'); | ||
} else { | ||
resolve(data); | ||
} | ||
}); | ||
}); | ||
}).bind(item); | ||
}; | ||
return Pool; | ||
@@ -151,25 +262,20 @@ })(); | ||
Stash.Drivers.Utils = { | ||
validateValue: function (value) { | ||
if (!JSON.stringify(value)) { | ||
assemble: function (value, expiration) { | ||
if (typeof value === 'function') { | ||
throw new TypeError('Only serializables values can be cached'); | ||
} | ||
}, | ||
assemble: function (value, expiration, locked) { | ||
Stash.Drivers.Utils.validateValue(value); | ||
return { value: value, expiration: expiration, locked: locked || false }; | ||
return JSON.stringify({ value: value, expiration: expiration }); | ||
}, | ||
cd: function (cache, key) { | ||
if (!key) { | ||
return cache; | ||
key: function (namespace, key) { | ||
if (key instanceof Array) { | ||
key.unshift(namespace); | ||
return key.filter(String).join('/'); | ||
} else { | ||
key = key.toString() | ||
.trim() | ||
.replace(/^\/+/g, '') | ||
.replace(/\/+$/g, ''); | ||
return namespace + '/' + key; | ||
} | ||
return key.split('/').reduce(function (cache, folder) { | ||
if (!cache[folder]) { | ||
cache[folder] = { __stash_value__: null }; | ||
} | ||
cache = cache[folder]; | ||
return cache; | ||
}, cache); | ||
} | ||
@@ -179,32 +285,58 @@ }; | ||
Stash.Drivers.Ephemeral = (function () { | ||
function Ephemeral () { | ||
this._cache_ = {}; | ||
} | ||
var cache = {}; | ||
function Ephemeral () {} | ||
Ephemeral.prototype.get = function (key) { | ||
var cache = Stash.Drivers.Utils.cd(this._cache_, key); | ||
key = Stash.Drivers.Utils.key('', key); | ||
var data = typeof cache[key] === 'undefined' ? null : cache[key]; | ||
return cache.__stash_value__ || null; | ||
if (data) { | ||
data = JSON.parse(data); | ||
} | ||
return Promise.cast(data); | ||
}; | ||
Ephemeral.prototype.put = function (key, value, expiration, locked) { | ||
Ephemeral.prototype.put = function (key, value, expiration) { | ||
key = Stash.Drivers.Utils.key('', key); | ||
var data = Stash.Drivers.Utils.assemble(value, expiration); | ||
cache[key] = data; | ||
return Promise.cast(); | ||
}; | ||
var cache = Stash.Drivers.Utils.cd(this._cache_, key); | ||
Ephemeral.prototype.delete = function (key) { | ||
key = Stash.Drivers.Utils.key('', key); | ||
var length = key.length; | ||
cache.__stash_value__ = Stash.Drivers.Utils.assemble(value, expiration, locked); | ||
Object.keys(cache).forEach(function (_key) { | ||
if (_key.substr(0, length) === key) { | ||
cache[_key] = null; | ||
} | ||
}); | ||
cache[key] = null; | ||
return Promise.cast(); | ||
}; | ||
Ephemeral.prototype.delete = function (key) { | ||
var key = key.split('/'); | ||
var last = key.pop(); | ||
var cache = Stash.Drivers.Utils.cd(this._cache_, key.join('/')); | ||
Ephemeral.prototype.flush = function () { | ||
cache = {}; | ||
return Promise.cast(); | ||
}; | ||
cache[last] = null; | ||
Ephemeral.prototype.lock = function (key) { | ||
var key = Stash.Drivers.Utils.key('', key) + '_lock'; | ||
cache[key] = 1; | ||
return Promise.cast(); | ||
}; | ||
Ephemeral.prototype.flush = function () { | ||
this._cache_ = {}; | ||
Ephemeral.prototype.isLocked = function (key) { | ||
key = Stash.Drivers.Utils.key('', key) + '_lock'; | ||
return Promise.cast(Boolean(cache[key])); | ||
} | ||
Ephemeral.prototype.parent = Ephemeral.prototype; | ||
Ephemeral.prototype.unlock = function (key) { | ||
key = Stash.Drivers.Utils.key('', key) + '_lock'; | ||
cache[key] = null; | ||
return Promise.cast(); | ||
}; | ||
@@ -217,44 +349,75 @@ return Ephemeral; | ||
this.namespace = namespace || 'stash'; | ||
this._loadCache_(); | ||
} | ||
LocalStorage.prototype = new Stash.Drivers.Ephemeral(); | ||
LocalStorage.prototype.get = function (key) { | ||
key = Stash.Drivers.Utils.key(this.namespace, key); | ||
var data = localStorage.getItem(key); | ||
if (data) { | ||
data = JSON.parse(data); | ||
} | ||
LocalStorage.prototype._loadCache_ = function () { | ||
this._cache_ = {}; | ||
return Promise.cast(data); | ||
} | ||
if (typeof(localStorage) !== 'undefined') { | ||
var saved = localStorage.getItem(this.namespace); | ||
LocalStorage.prototype.put = function (key, value, expiration) { | ||
key = Stash.Drivers.Utils.key(this.namespace, key); | ||
var data = Stash.Drivers.Utils.assemble(value, expiration); | ||
if (saved) { | ||
this._cache_ = JSON.parse(saved); | ||
return this.putRaw(key, data); | ||
}; | ||
LocalStorage.prototype.putRaw = function (key, value) { | ||
localStorage.setItem(key, value); | ||
return Promise.cast(); | ||
} | ||
LocalStorage.prototype.delete = function (key) { | ||
key = Stash.Drivers.Utils.key(this.namespace, key); | ||
var length = key.length; | ||
for (var i = 0, l = localStorage.length; i < l; i++) { | ||
var _key = localStorage.key(i); | ||
if (_key && _key.substr(0, length) === key) { | ||
localStorage.removeItem(_key); | ||
} | ||
} | ||
}; | ||
LocalStorage.prototype._commit_ = function () { | ||
if (typeof(localStorage) !== 'undefined') { | ||
localStorage.setItem(this.namespace, JSON.stringify(this._cache_)); | ||
} | ||
localStorage.removeItem(key); | ||
return Promise.cast(); | ||
}; | ||
LocalStorage.prototype.put = function (key, value, expiration, locked) { | ||
this.parent.put.apply(this, arguments); | ||
this._commit_(); | ||
LocalStorage.prototype.flush = function () { | ||
return this.delete(''); | ||
}; | ||
LocalStorage.prototype.delete = function (key) { | ||
this.parent.delete.apply(this, arguments); | ||
this._commit_(); | ||
LocalStorage.prototype.lock = function (key) { | ||
key = Stash.Drivers.Utils.key(this.namespace, key) + '_lock'; | ||
return this.putRaw(key, 1); | ||
}; | ||
LocalStorage.prototype.flush = function () { | ||
this.parent.flush.apply(this, arguments); | ||
this._commit_(); | ||
LocalStorage.prototype.isLocked = function (key) { | ||
var that = this; | ||
return new Promise(function (resolve, reject) { | ||
that.get(key + '_lock').then(function (data) { | ||
resolve(Boolean(data)); | ||
}); | ||
}); | ||
}; | ||
LocalStorage.prototype.unlock = function (key) { | ||
key = Stash.Drivers.Utils.key(this.namespace, key) + '_lock'; | ||
localStorage.removeItem(key); | ||
return Promise.cast(); | ||
} | ||
return LocalStorage; | ||
})(); | ||
exports.Stash = Stash; | ||
})(typeof(window) === 'undefined' ? module.exports : window); | ||
if (isNode) { | ||
module.exports = Stash; | ||
} else { | ||
global.Stash = Stash; | ||
} | ||
})(this); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
36967
21
971
1
91
1
2
+ Addedbluebird@~1.2.4
+ Addedbluebird@1.2.4(transitive)