-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
068b217
commit 7028d39
Showing
6 changed files
with
174 additions
and
0 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# editorconfig.org | ||
root = true | ||
|
||
[*] | ||
indent_style = space | ||
indent_size = 4 | ||
end_of_line = lf | ||
charset = utf-8 | ||
trim_trailing_whitespace = true | ||
insert_final_newline = true | ||
|
||
[*.md] | ||
trim_trailing_whitespace = false |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# Set default behaviour, in case users don't have core.autocrlf set. | ||
* text=auto | ||
|
||
# Explicitly declare text files we want to always be normalized and converted | ||
# to native line endings on checkout. | ||
*.js text | ||
*.less text | ||
*.html text | ||
|
||
# Denote all files that are truly binary and should not be modified. | ||
*.png binary | ||
*.jpg binary | ||
*.gif binary |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
module.exports = { | ||
MemoryCache: require('./src/MemoryCache'), | ||
SingleOperation: require('./src/SingleOperation'), | ||
}; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
{ | ||
"name": "@thinkpixellab-public/px-long-operations", | ||
"version": "1.0.0", | ||
"description": "Utilities for long running operations", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/thinkpixellab/px-long-operations.git" | ||
}, | ||
"author": "Pixel Lab", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/thinkpixellab/px-long-operations/issues" | ||
}, | ||
"homepage": "https://github.com/thinkpixellab/px-long-operations#readme" | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
var SingleOperation = require('./SingleOperation'); | ||
|
||
class MemoryCache { | ||
constructor({ defaultTTL = 5 * 60 * 1000, ttlByKey = {}, debug = false } = {}) { | ||
this.cache = {}; | ||
this.singleOperation = new SingleOperation(); | ||
|
||
// default time to live in ms | ||
this.ttlByKey = ttlByKey; | ||
this.defaultTTL = defaultTTL; | ||
|
||
// enable logging in debug mode | ||
this.debug = !!debug; | ||
//console.log('MemoryCache options: ' + JSON.stringify(options)); | ||
} | ||
|
||
clear() { | ||
this.cache = {}; | ||
} | ||
|
||
async ensure(key, valueFactory, { ttl, valueErrorRetries = 3, allowLazyRefresh = false } = {}) { | ||
var refreshValue = oldValue => { | ||
return this.singleOperation.run(key, async () => { | ||
let retry = 0; | ||
while (true) { | ||
try { | ||
const newValue = await valueFactory(oldValue); | ||
this.set(key, newValue, ttl); | ||
return newValue; | ||
} catch (error) { | ||
// automatically retry up to limit | ||
retry++; | ||
if (retry > valueErrorRetries) { | ||
return Promise.reject(error); | ||
} | ||
} | ||
} | ||
}); | ||
}; | ||
|
||
var entry = key ? this.cache[key] : null; | ||
if (!entry) { | ||
return refreshValue(); | ||
} | ||
|
||
if (Date.now() < entry.expires) { | ||
// not yet expired | ||
return entry.value; | ||
} else if (allowLazyRefresh) { | ||
// refresh in background, return expired result | ||
refreshValue(entry.value).catch(error => { | ||
// log but suppress any errors because they happen on a background thread | ||
console.error('Error refreshing cache value: ' + key); | ||
console.error(error); | ||
}); | ||
return entry.value; | ||
} else { | ||
// refresh and wait | ||
return refreshValue(entry.value); | ||
} | ||
} | ||
|
||
ensureLazy(key, valueFactory, { ttl, valueErrorRetries = 3 } = {}) { | ||
return this.ensure(key, valueFactory, { ttl, valueErrorRetries, allowLazyRefresh: true }); | ||
} | ||
|
||
get(key, allowExpired) { | ||
if (key) { | ||
var entry = this.cache[key]; | ||
if (entry) { | ||
if (allowExpired || Date.now() < entry.expires) { | ||
return entry.value; | ||
} else if (this.debug) { | ||
console.log('expired cache entry: ' + key); | ||
} | ||
} | ||
} | ||
|
||
if (this.debug) { | ||
console.log('no cache entry: ' + key); | ||
} | ||
return undefined; | ||
} | ||
|
||
set(key, value, ttl) { | ||
// fallback to config then default ttl if not specified | ||
ttl = ttl || this.ttlByKey[key] || this.defaultTTL; | ||
|
||
var expires = Date.now() + ttl; | ||
this.cache[key] = new CacheEntry(value, expires); | ||
|
||
if (this.debug) { | ||
const ttlSeconds = Math.round(ttl / 1000); | ||
console.log('caching ' + key + ' (ttl:' + ttlSeconds + ' secs)'); | ||
} | ||
} | ||
} | ||
|
||
class CacheEntry { | ||
constructor(value, expires) { | ||
this.value = value; | ||
this.expires = expires; | ||
} | ||
} | ||
|
||
module.exports = MemoryCache; |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
class SingleOperation { | ||
constructor() { | ||
this.pending = {}; | ||
} | ||
|
||
run(key, valueFactory) { | ||
var operationPromise = this.pending[key]; | ||
if (operationPromise) { | ||
return operationPromise; | ||
} | ||
|
||
var promise = (this.pending[key] = valueFactory().finally(() => { | ||
delete this.pending[key]; | ||
})); | ||
|
||
return promise; | ||
} | ||
} | ||
module.exports = SingleOperation; |