Comparing version 1.1.0 to 2.0.0
'use strict'; | ||
/** | ||
Construct our Cachely class, setting the configuration from the options | ||
@param {Object} opts | ||
@param {Number} opts.duration - the milliseconds that the cache should be valid for, defaults to one day | ||
@param {Function} [opts.log] - defaults to `null`, can be a function that receives the arguments: `logLevel`, `...args` | ||
@param {Function} [opts.retrieve] - the method that fetches the new source data, it should return a promise that resolves the result that will be cached | ||
@public | ||
*/ | ||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); | ||
@@ -7,44 +16,31 @@ | ||
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } | ||
var Cachely = function () { | ||
function Cachely() { | ||
var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } | ||
// Define | ||
var Cachely = function (_require$EventEmitter) { | ||
_inherits(Cachely, _require$EventEmitter); | ||
_createClass(Cachely, null, [{ | ||
key: 'create', | ||
value: function create() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return new (Function.prototype.bind.apply(this, [null].concat(args)))(); | ||
} | ||
}]); | ||
function Cachely(opts) { | ||
_classCallCheck(this, Cachely); | ||
var _this = _possibleConstructorReturn(this, Object.getPrototypeOf(Cachely).call(this)); | ||
this.duration = opts.duration || require('oneday'); | ||
this.log = opts.log || function () {}; | ||
this.retrieve = opts.retrieve; | ||
opts = opts || {}; | ||
_this.duration = opts.duration || require('oneday'); | ||
_this.log = opts.log || function () {}; | ||
_this.method = opts.method; | ||
if (typeof _this.method !== 'function') { | ||
throw new Error('Cachely requires a method to be specified'); | ||
if (typeof this.retrieve !== 'function') { | ||
throw new Error('Cachely requires a retrieve method to be specified that returns a promise'); | ||
} | ||
_this.data = null; | ||
_this.retry = false; | ||
_this.lastRequest = null; | ||
_this.lastUpdate = null; | ||
return _this; | ||
// Private properties | ||
this.data = null; | ||
this.refresh = false; | ||
this.lastRequested = null; | ||
this.lastRetrieval = null; | ||
this.lastUpdated = null; | ||
} | ||
// returns either 'valid', 'invalid', 'updating', or 'empty' | ||
/** | ||
Creates and returns new instance of the current class | ||
@param {...*} args - the arguments to be forwarded along to the constructor | ||
@return {Object} The new instance. | ||
@static | ||
@public | ||
*/ | ||
@@ -54,2 +50,9 @@ | ||
key: 'validate', | ||
/** | ||
Determines whether or not the cache is still valid, by returning its current status | ||
@returns {string} - 'valid', 'invalid', 'updating', or 'empty' | ||
@private | ||
*/ | ||
value: function validate() { | ||
@@ -59,4 +62,4 @@ var nowTime = new Date().getTime(); | ||
// have we manually invalidated the cache? | ||
if (this.retry) { | ||
this.retry = false; | ||
if (this.refresh) { | ||
this.refresh = false; | ||
return 'invalid'; | ||
@@ -66,10 +69,10 @@ } | ||
// have we fetched the data yet? | ||
else if (this.lastUpdate) { | ||
else if (this.lastUpdated) { | ||
// yes we have, so let's check if it is still valid | ||
// if the current time, minus the cache duration, is than the last time we requested the data, then our cache is invalid | ||
return new Date(nowTime - this.duration) < this.lastRequest ? 'valid' : 'invalid'; | ||
// if the current time, minus the cache duration, is than the last time we retrieved the data, then our cache is invalid | ||
return new Date(nowTime - this.duration) < this.lastRequested ? 'valid' : 'invalid'; | ||
} | ||
// are we doing the first fetch? | ||
else if (this.lastRequest) { | ||
else if (this.lastRequested) { | ||
return 'updating'; | ||
@@ -83,53 +86,70 @@ } | ||
} | ||
/** | ||
Invalidates the current cache, so that it is retrieved again. | ||
Only applies to future resolution requets, does not cancel or modify active retrieval requests. | ||
@returns {this} | ||
@chainable | ||
@public | ||
*/ | ||
}, { | ||
key: 'invalidate', | ||
value: function invalidate() { | ||
this.retry = true; | ||
this.refresh = true; | ||
return this; | ||
} | ||
// next(err, data) | ||
/** | ||
Resolve the cache, if it is valid use the cache's data, otherwise retrieve new data | ||
@returns {Promise<*>} | ||
@public | ||
*/ | ||
}, { | ||
key: 'request', | ||
value: function request(next) { | ||
var _this2 = this; | ||
key: 'resolve', | ||
value: async function resolve() { | ||
var cache = this.validate(); | ||
switch (cache) { | ||
case 'valid': | ||
this.log('debug', 'Cachely is returning cached data'); | ||
next(null, this.data); | ||
break; | ||
this.log('debug', 'Cachely has resolved cached data'); | ||
return this.data; | ||
case 'invalid': | ||
case 'empty': | ||
this.log('debug', 'Cachely is fetching new data'); | ||
this.lastRequest = new Date(); | ||
this.once('update', next); | ||
this.method(function (err, data) { | ||
if (!err) { | ||
_this2.data = data; | ||
} | ||
_this2.lastUpdate = new Date(); | ||
_this2.log('debug', 'Cachely has fetched the new data'); | ||
_this2.emit('update', err, _this2.data); | ||
}); | ||
break; | ||
this.log('debug', 'Cachely must resolve new data'); | ||
this.lastRequested = new Date(); | ||
this.lastUpdated = null; | ||
this.lastRetrieval = Promise.resolve(this.retrieve()); | ||
try { | ||
this.data = await this.lastRetrieval; | ||
this.lastUpdated = new Date(); | ||
} catch (err) { | ||
this.log('debug', 'Cachely failed to resolve new data'); | ||
return Promise.reject(err); | ||
} | ||
this.log('debug', 'Cachely has resolved the new data'); | ||
return this.data; | ||
case 'updating': | ||
this.log('debug', 'Cachely is waiting for new data'); | ||
this.once('update', next); | ||
break; | ||
this.log('debug', 'Cachely is waiting for the new data to resolve'); | ||
return this.lastRetrieval; | ||
default: | ||
next(new Error('Unknown cache state')); | ||
break; | ||
return Promise.reject(new Error('Unknown cache state')); | ||
} | ||
return this; | ||
} | ||
}], [{ | ||
key: 'create', | ||
value: function create() { | ||
for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) { | ||
args[_key] = arguments[_key]; | ||
} | ||
return new (Function.prototype.bind.apply(this, [null].concat(args)))(); | ||
} | ||
}]); | ||
return Cachely; | ||
}(require('events').EventEmitter); | ||
}(); | ||
@@ -136,0 +156,0 @@ // Export |
# History | ||
## v2.0.0 2018 July 12 | ||
- API updated to use promises instead, as such methods have been renamed to ensure the correct upgrades occur | ||
- Minimum supported node version is now version 8, due to the usage of async/await | ||
## v1.1.0 2016 May 27 | ||
- Updated internal conventions | ||
- Moved from [ESNextGuardian](https://github.com/bevry/esnextguardian) to [Editions](https://github.com/bevry/editions) | ||
- Moved from [ESNextGuardian](https://github.com/bevry/esnextguardian) to [Editions](https://github.com/bevry/editions) | ||
@@ -7,0 +11,0 @@ ## v1.0.1 2015 December 10 |
@@ -1,3 +0,3 @@ | ||
// 2016 March 8 | ||
// https://github.com/bevry/editions | ||
'use strict' | ||
module.exports = require('editions').requirePackage(__dirname, require) |
{ | ||
"name": "cachely", | ||
"version": "1.1.0", | ||
"version": "2.0.0", | ||
"description": "A tiny wrapper that sits around your request function that caches its data for a specified duration, provides updates as requested rather than polling each interval", | ||
"homepage": "https://github.com/bevry/cachely", | ||
"homepage": "ssh://git@github.com/bevry/cachely", | ||
"license": "MIT", | ||
@@ -20,13 +20,14 @@ "keywords": [ | ||
"---", | ||
"slackin", | ||
"patreon", | ||
"gratipay", | ||
"opencollective", | ||
"flattr", | ||
"paypal", | ||
"bitcoin", | ||
"wishlist" | ||
"wishlist", | ||
"---", | ||
"slackin" | ||
], | ||
"config": { | ||
"patreonUsername": "bevry", | ||
"gratipayUsername": "bevry", | ||
"opencollectiveUsername": "bevry", | ||
"flattrUsername": "balupton", | ||
@@ -44,14 +45,13 @@ "paypalURL": "https://bevry.me/paypal", | ||
"contributors": [ | ||
"Benjamin Lupton <b@lupton.cc> (http://balupton.com)", | ||
"Benjamin Lupton (https://balupton.com)" | ||
"Benjamin Lupton <b@lupton.cc> (http://balupton.com)" | ||
], | ||
"bugs": { | ||
"url": "https://github.com/bevry/cachely/issues" | ||
"url": "ssh://git@github.com/bevry/cachely/issues" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/bevry/cachely.git" | ||
"url": "ssh://git@github.com/bevry/cachely.git" | ||
}, | ||
"engines": { | ||
"node": ">=0.12" | ||
"node": ">=8" | ||
}, | ||
@@ -61,3 +61,3 @@ "editions": [ | ||
"description": "Source + ESNext + Require", | ||
"entry": "source/index.js", | ||
"entry": "index.js", | ||
"directory": "source", | ||
@@ -70,3 +70,5 @@ "syntaxes": [ | ||
"spread", | ||
"rest" | ||
"rest", | ||
"async", | ||
"promises" | ||
] | ||
@@ -76,3 +78,3 @@ }, | ||
"description": "Babel Compiled + ES2015 + Require", | ||
"entry": "es2015/index.js", | ||
"entry": "index.js", | ||
"directory": "es2015", | ||
@@ -89,31 +91,38 @@ "syntaxes": [ | ||
"dependencies": { | ||
"editions": "^1.1.1", | ||
"editions": "^1.3.4", | ||
"oneday": "^1.0.0" | ||
}, | ||
"devDependencies": { | ||
"assert-helpers": "^4.2.0", | ||
"babel-cli": "^6.9.0", | ||
"babel-preset-es2015": "^6.9.0", | ||
"eslint": "^2.10.2", | ||
"eslint-plugin-babel": "^3.2.0", | ||
"joe": "^1.6.0", | ||
"joe-reporter-console": "^1.2.1", | ||
"projectz": "^1.1.5" | ||
"assert-helpers": "^4.5.1", | ||
"babel-cli": "^6.26.0", | ||
"babel-preset-es2015": "^6.24.1", | ||
"documentation": "^8.0.0", | ||
"eslint": "^5.1.0", | ||
"joe": "^2.0.2", | ||
"joe-reporter-console": "^2.0.1", | ||
"projectz": "^1.4.0", | ||
"surge": "^0.20.1", | ||
"typechecker": "^4.5.0" | ||
}, | ||
"scripts": { | ||
"setup": "npm install", | ||
"clean": "rm -Rf ./docs ./es2015", | ||
"compile": "npm run compile:es2015", | ||
"compile:es2015": "babel ./source --out-dir ./es2015 --presets es2015", | ||
"meta": "npm run meta:projectz", | ||
"meta:projectz": "projectz compile", | ||
"prepare": "npm run compile && npm run test && npm run meta", | ||
"release": "npm run prepare && npm run release:publish && npm run release:tag && npm run release:push", | ||
"release:publish": "npm publish", | ||
"release:tag": "git tag v$npm_package_version -a", | ||
"release:push": "git push origin master && git push origin --tags", | ||
"pretest": "npm run test:eslint", | ||
"test:eslint": "eslint ./source", | ||
"test": "node --harmony -e \"require('editions').requirePackage(process.cwd(), require, 'test.js')\"" | ||
"our:setup": "npm run our:setup:npm", | ||
"our:setup:npm": "npm install", | ||
"our:clean": "rm -Rf ./docs ./es2015 ./es5 ./out", | ||
"our:compile": "npm run our:compile:es2015", | ||
"our:compile:es2015": "babel ./source --out-dir ./es2015 --presets es2015", | ||
"our:meta": "npm run our:meta:docs && npm run our:meta:projectz", | ||
"our:meta:docs": "documentation build -f html -o ./docs -g --shallow ./source/**.js", | ||
"our:meta:projectz": "projectz compile", | ||
"our:verify": "npm run our:verify:eslint", | ||
"our:verify:eslint": "eslint --fix ./source", | ||
"our:deploy": "echo no need for this project", | ||
"our:test": "npm run our:verify && npm test", | ||
"our:release": "npm run our:release:prepare && npm run our:release:check-changelog && npm run our:release:check-dirty && npm run our:release:tag && npm run our:release:push", | ||
"our:release:prepare": "npm run our:clean && npm run our:compile && npm run our:test && npm run our:meta", | ||
"our:release:check-changelog": "cat ./HISTORY.md | grep v$npm_package_version || (echo add a changelog entry for v$npm_package_version && exit -1)", | ||
"our:release:check-dirty": "git diff --exit-code", | ||
"our:release:tag": "export MESSAGE=$(cat ./HISTORY.md | sed -n \"/## v$npm_package_version/,/##/p\" | sed 's/## //' | awk 'NR>1{print buf}{buf = $0}') && test \"$MESSAGE\" || (echo 'proper changelog entry not found' && exit -1) && git tag v$npm_package_version -am \"$MESSAGE\"", | ||
"our:release:push": "git push origin master && git push origin --tags", | ||
"test": "node --harmony ./test.js --joe-reporter=console" | ||
} | ||
} |
@@ -16,5 +16,4 @@ <!-- TITLE/ --> | ||
<br class="badge-separator" /> | ||
<span class="badge-slackin"><a href="https://slack.bevry.me" title="Join this project's slack community"><img src="https://slack.bevry.me/badge.svg" alt="Slack community badge" /></a></span> | ||
<span class="badge-patreon"><a href="http://patreon.com/bevry" title="Donate to this project using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" alt="Patreon donate button" /></a></span> | ||
<span class="badge-gratipay"><a href="https://www.gratipay.com/bevry" title="Donate weekly to this project using Gratipay"><img src="https://img.shields.io/badge/gratipay-donate-yellow.svg" alt="Gratipay donate button" /></a></span> | ||
<span class="badge-patreon"><a href="https://patreon.com/bevry" title="Donate to this project using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" alt="Patreon donate button" /></a></span> | ||
<span class="badge-opencollective"><a href="https://opencollective.com/bevry" title="Donate to this project using Open Collective"><img src="https://img.shields.io/badge/open%20collective-donate-yellow.svg" alt="Open Collective donate button" /></a></span> | ||
<span class="badge-flattr"><a href="https://flattr.com/profile/balupton" title="Donate to this project using Flattr"><img src="https://img.shields.io/badge/flattr-donate-yellow.svg" alt="Flattr donate button" /></a></span> | ||
@@ -24,2 +23,4 @@ <span class="badge-paypal"><a href="https://bevry.me/paypal" title="Donate to this project using Paypal"><img src="https://img.shields.io/badge/paypal-donate-yellow.svg" alt="PayPal donate button" /></a></span> | ||
<span class="badge-wishlist"><a href="https://bevry.me/wishlist" title="Buy an item on our wishlist for us"><img src="https://img.shields.io/badge/wishlist-donate-yellow.svg" alt="Wishlist browse button" /></a></span> | ||
<br class="badge-separator" /> | ||
<span class="badge-slackin"><a href="https://slack.bevry.me" title="Join this project's slack community"><img src="https://slack.bevry.me/badge.svg" alt="Slack community badge" /></a></span> | ||
@@ -47,3 +48,3 @@ <!-- /BADGES --> | ||
<li>Module: <code>require('cachely')</code></li> | ||
<li>CDN URL: <code>//wzrd.in/bundle/cachely@1.1.0</code></li></ul> | ||
<li>CDN URL: <code>//wzrd.in/bundle/cachely@2.0.0</code></li></ul> | ||
@@ -72,56 +73,44 @@ <a href="http://enderjs.com" title="Ender is a full featured package manager for your browser"><h3>Ender</h3></a><ul> | ||
const cachely = require('cachely').create({ | ||
// The method that will fetch the data | ||
// It receives one argument which is a completion callback that accepts two arguments (err and data) | ||
method: function (next) { | ||
// in this case, after a one second delay, return the number of fetches that we have done | ||
setTimeout(function () { | ||
next(null, ++fetches) // err, data | ||
}, 1000) | ||
}, | ||
// The method that will fetch the data | ||
retrieve () { | ||
return new Promise(function (resolve) { | ||
// after a one second delay, return the number of fetches that we have done | ||
setTimeout(() => resolve(++fetches), 1000) | ||
}) | ||
}, | ||
// An optional duration in milliseconds that our cache of the data will be valid for | ||
// When expired, on the next request of the data, we will use the method to get the latest data | ||
// Defaults to one day | ||
duration: 2000, // in this example we set it to two seconds | ||
// An optional duration in milliseconds that our cache of the data will be valid for | ||
// When expired, on the next request of the data, we will use the method to get the latest data | ||
// Defaults to one day | ||
duration: 2000, // in this example we set it to two seconds | ||
// An optional function that receives debugging log messages | ||
// Defaults to nothing | ||
log: console.log | ||
// An optional function that receives debugging log messages | ||
// Defaults to nothing | ||
log: console.log | ||
}) | ||
// do an initial fetch of the data | ||
cachely.request(function (err, data) { | ||
console.log('after one second as specified in our method, the result data should still be 1:', data, err) | ||
}) | ||
// do an initial fetch of the dat | ||
cachely.resolve().catch(console.error).then(console.log.bind(console, 'after one second as specified in our method, the result data should still be 1:')) | ||
// do a subsequent fetch of the data that will be from the cache | ||
cachely.request(function (err, data) { | ||
console.log('after a tiny delay this will be from cache, the result data should still be 1:', data, err) | ||
}) | ||
// do a subsequent fetch of the data that will be from the cach | ||
cachely.resolve().catch(console.error).then(console.log.bind(console, 'after a tiny delay this will be from cache, the result data should still be 1:')) | ||
// wait for the cache to invalidate itself | ||
setTimeout(function () { | ||
// do an second fetch of the data | ||
cachely.request(function (err, data) { | ||
console.log('after one second as specified in our method, the result data should be 2, as it was our second fetch:', data, err) | ||
}) | ||
// do an second fetch of the data | ||
cachely.resolve().catch(console.error).then(console.log.bind(console, 'after one second as specified in our method, the result data should be 2, as it was our second fetch:')) | ||
// do a subsequent fetch of the data that will be from the cache | ||
cachely.request(function (err, data) { | ||
console.log('after a tiny delay this will be from cache, the result data should still be 2:', data, err) | ||
}) | ||
// do a subsequent fetch of the data that will be from the cache | ||
cachely.resolve().catch(console.error).then(console.log.bind(console, 'after a tiny delay this will be from cache, the result data should still be 2:')) | ||
// peform a manual invalidation | ||
cachely.invalidate() | ||
// peform a manual invalidation | ||
cachely.invalidate() | ||
// do a third fetch of the data | ||
cachely.request(function (err, data) { | ||
console.log('after one second as specified in our method, the result data should be 3, as it was our third fetch:', data, err) | ||
}) | ||
// do a third fetch of the data | ||
cachely.resolve().catch(console.error).then(console.log.bind(console, 'after one second as specified in our method, the result data should be 3, as it was our third fetch:')) | ||
// do a subsequent fetch of the data that will be from the cache | ||
cachely.request(function (err, data) { | ||
console.log('after a tiny delay this will be from cache, the result data should still be 3:', data, err) | ||
}) | ||
// do a subsequent fetch of the data that will be from the cache | ||
cachely.resolve().catch(console.error).then(console.log.bind(console, 'after a tiny delay this will be from cache, the result data should still be 3:')) | ||
}, 3000) | ||
``` | ||
@@ -155,3 +144,3 @@ | ||
<ul><li><a href="http://balupton.com">Benjamin Lupton</a></li></ul> | ||
<ul><li><a href="http://balupton.com">Benjamin Lupton</a> — <a href="https://github.com/bevry/cachely/commits?author=balupton" title="View the GitHub contributions of Benjamin Lupton on repository bevry/cachely">view contributions</a></li></ul> | ||
@@ -162,4 +151,4 @@ <h3>Sponsors</h3> | ||
<span class="badge-patreon"><a href="http://patreon.com/bevry" title="Donate to this project using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" alt="Patreon donate button" /></a></span> | ||
<span class="badge-gratipay"><a href="https://www.gratipay.com/bevry" title="Donate weekly to this project using Gratipay"><img src="https://img.shields.io/badge/gratipay-donate-yellow.svg" alt="Gratipay donate button" /></a></span> | ||
<span class="badge-patreon"><a href="https://patreon.com/bevry" title="Donate to this project using Patreon"><img src="https://img.shields.io/badge/patreon-donate-yellow.svg" alt="Patreon donate button" /></a></span> | ||
<span class="badge-opencollective"><a href="https://opencollective.com/bevry" title="Donate to this project using Open Collective"><img src="https://img.shields.io/badge/open%20collective-donate-yellow.svg" alt="Open Collective donate button" /></a></span> | ||
<span class="badge-flattr"><a href="https://flattr.com/profile/balupton" title="Donate to this project using Flattr"><img src="https://img.shields.io/badge/flattr-donate-yellow.svg" alt="Flattr donate button" /></a></span> | ||
@@ -174,4 +163,3 @@ <span class="badge-paypal"><a href="https://bevry.me/paypal" title="Donate to this project using Paypal"><img src="https://img.shields.io/badge/paypal-donate-yellow.svg" alt="PayPal donate button" /></a></span> | ||
<ul><li><a href="http://balupton.com">Benjamin Lupton</a></li> | ||
<li><a href="https://balupton.com">Benjamin Lupton</a> — <a href="https://github.com/bevry/cachely/commits?author=balupton" title="View the GitHub contributions of Benjamin Lupton on repository bevry/cachely">view contributions</a></li></ul> | ||
<ul><li><a href="http://balupton.com">Benjamin Lupton</a> — <a href="https://github.com/bevry/cachely/commits?author=balupton" title="View the GitHub contributions of Benjamin Lupton on repository bevry/cachely">view contributions</a></li></ul> | ||
@@ -178,0 +166,0 @@ <a href="https://github.com/bevry/cachely/blob/master/CONTRIBUTING.md#files">Discover how you can contribute by heading on over to the <code>CONTRIBUTING.md</code> file.</a> |
@@ -1,26 +0,45 @@ | ||
// Define | ||
class Cachely extends require('events').EventEmitter { | ||
static create (...args) { | ||
return new this(...args) | ||
} | ||
'use strict' | ||
constructor (opts) { | ||
super() | ||
opts = opts || {} | ||
/** | ||
Construct our Cachely class, setting the configuration from the options | ||
@param {Object} opts | ||
@param {Number} opts.duration - the milliseconds that the cache should be valid for, defaults to one day | ||
@param {Function} [opts.log] - defaults to `null`, can be a function that receives the arguments: `logLevel`, `...args` | ||
@param {Function} [opts.retrieve] - the method that fetches the new source data, it should return a promise that resolves the result that will be cached | ||
@public | ||
*/ | ||
class Cachely { | ||
constructor (opts = {}) { | ||
this.duration = opts.duration || require('oneday') | ||
this.log = opts.log || function () {} | ||
this.method = opts.method | ||
this.log = opts.log || function () { } | ||
this.retrieve = opts.retrieve | ||
if ( typeof this.method !== 'function' ) { | ||
throw new Error('Cachely requires a method to be specified') | ||
if (typeof this.retrieve !== 'function') { | ||
throw new Error('Cachely requires a retrieve method to be specified that returns a promise') | ||
} | ||
// Private properties | ||
this.data = null | ||
this.retry = false | ||
this.lastRequest = null | ||
this.lastUpdate = null | ||
this.refresh = false | ||
this.lastRequested = null | ||
this.lastRetrieval = null | ||
this.lastUpdated = null | ||
} | ||
// returns either 'valid', 'invalid', 'updating', or 'empty' | ||
/** | ||
Creates and returns new instance of the current class | ||
@param {...*} args - the arguments to be forwarded along to the constructor | ||
@return {Object} The new instance. | ||
@static | ||
@public | ||
*/ | ||
static create (...args) { | ||
return new this(...args) | ||
} | ||
/** | ||
Determines whether or not the cache is still valid, by returning its current status | ||
@returns {string} - 'valid', 'invalid', 'updating', or 'empty' | ||
@private | ||
*/ | ||
validate () { | ||
@@ -30,4 +49,4 @@ const nowTime = (new Date()).getTime() | ||
// have we manually invalidated the cache? | ||
if ( this.retry ) { | ||
this.retry = false | ||
if (this.refresh) { | ||
this.refresh = false | ||
return 'invalid' | ||
@@ -37,10 +56,10 @@ } | ||
// have we fetched the data yet? | ||
else if ( this.lastUpdate ) { | ||
else if (this.lastUpdated) { | ||
// yes we have, so let's check if it is still valid | ||
// if the current time, minus the cache duration, is than the last time we requested the data, then our cache is invalid | ||
return new Date(nowTime - this.duration) < this.lastRequest ? 'valid' : 'invalid' | ||
// if the current time, minus the cache duration, is than the last time we retrieved the data, then our cache is invalid | ||
return new Date(nowTime - this.duration) < this.lastRequested ? 'valid' : 'invalid' | ||
} | ||
// are we doing the first fetch? | ||
else if ( this.lastRequest ) { | ||
else if (this.lastRequested) { | ||
return 'updating' | ||
@@ -55,41 +74,50 @@ } | ||
/** | ||
Invalidates the current cache, so that it is retrieved again. | ||
Only applies to future resolution requets, does not cancel or modify active retrieval requests. | ||
@returns {this} | ||
@chainable | ||
@public | ||
*/ | ||
invalidate () { | ||
this.retry = true | ||
this.refresh = true | ||
return this | ||
} | ||
// next(err, data) | ||
request (next) { | ||
/** | ||
Resolve the cache, if it is valid use the cache's data, otherwise retrieve new data | ||
@returns {Promise<*>} | ||
@public | ||
*/ | ||
async resolve () { | ||
const cache = this.validate() | ||
switch ( cache ) { | ||
switch (cache) { | ||
case 'valid': | ||
this.log('debug', 'Cachely is returning cached data') | ||
next(null, this.data) | ||
break | ||
this.log('debug', 'Cachely has resolved cached data') | ||
return this.data | ||
case 'invalid': | ||
case 'empty': | ||
this.log('debug', 'Cachely is fetching new data') | ||
this.lastRequest = new Date() | ||
this.once('update', next) | ||
this.method((err, data) => { | ||
if ( !err ) { | ||
this.data = data | ||
} | ||
this.lastUpdate = new Date() | ||
this.log('debug', 'Cachely has fetched the new data') | ||
this.emit('update', err, this.data) | ||
}) | ||
break | ||
this.log('debug', 'Cachely must resolve new data') | ||
this.lastRequested = new Date() | ||
this.lastUpdated = null | ||
this.lastRetrieval = Promise.resolve(this.retrieve()) | ||
try { | ||
this.data = await this.lastRetrieval | ||
this.lastUpdated = new Date() | ||
} | ||
catch (err) { | ||
this.log('debug', 'Cachely failed to resolve new data') | ||
return Promise.reject(err) | ||
} | ||
this.log('debug', 'Cachely has resolved the new data') | ||
return this.data | ||
case 'updating': | ||
this.log('debug', 'Cachely is waiting for new data') | ||
this.once('update', next) | ||
break | ||
this.log('debug', 'Cachely is waiting for the new data to resolve') | ||
return this.lastRetrieval | ||
default: | ||
next(new Error('Unknown cache state')) | ||
break | ||
return Promise.reject(new Error('Unknown cache state')) | ||
} | ||
return this | ||
} | ||
@@ -96,0 +124,0 @@ } |
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
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
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
24440
236
10
7
177
2
Updatededitions@^1.3.4