--- /dev/null
+let fs = require('fs')
+let util = require('util')
+
+let fs_readFile = util.promisify(fs.readFile)
+let fs_writeFile = util.promisify(fs.writeFile)
+
+let JSONCache = function() {
+ if (!this instanceof JSONCache)
+ throw Error('JSONCache is a constructor')
+ this.map = new Map()
+ this.diag = false
+}
+
+let load = (diag, key, default_value) => {
+ if (diag)
+ console.log('parsing', key)
+ let result = {dirty: false}
+ result.done = (
+ async () => {
+ try {
+ result.value = JSON.parse(
+ await fs_readFile(key, {encoding: 'utf-8'})
+ )
+ }
+ catch (err) {
+ if (default_value === undefined || err.code !== 'ENOENT') // err type???
+ throw err
+ result.value = default_value
+ }
+ }
+ )()
+ return result
+}
+
+JSONCache.prototype.get = async function(key, default_value) {
+ let result = this.map.get(key)
+ if (result === undefined) {
+ result = load(this.diag, key, default_value)
+ this.map.set(key, result)
+ await result.done
+ delete result.done
+ }
+ else
+ while (result.done !== undefined)
+ await result.done
+ return result.value
+}
+
+let save = (diag, key, result, timeout) => {
+ if (!result.dirty) {
+ result.dirty = true
+ setTimeout(
+ async () => {
+ if (diag)
+ console.log('writing', key)
+ result.dirty = false
+ let text = JSON.stringify(result.value) + '\n'
+ try {
+ await fs_writeFile(key, text, {encoding: 'utf-8'})
+ }
+ catch (err) {
+ console.err(err.stack || err.message)
+ }
+ },
+ timeout || 5000
+ )
+ }
+}
+
+JSONCache.prototype.set = async function(key, value, timeout) {
+ let result = this.map.get(key)
+ if (result === undefined) {
+ assert(value !== undefined)
+ result = {dirty: false, value: value}
+ this.map.set(key, result)
+ }
+ else if (value !== undefined) {
+ while (result.done !== undefined)
+ await result.done
+ result.value = value
+ }
+ save(this.diag, key, result, timeout)
+}
+
+JSONCache.prototype.modify = async function(
+ key,
+ default_value,
+ modify_func,
+ timeout
+) {
+ // duplicate the get() here, we can't await get() directly because we must
+ // atomically check that result.done === undefined before the modification
+ let result = this.map.get(key)
+ if (result === undefined) {
+ result = load(this.diag, key, default_value)
+ this.map.set(key, result)
+ await result.done
+ delete result.done
+ }
+ else
+ while (result.done !== undefined)
+ await result.done
+ result.done = (
+ async () => {
+ result.value = await modify_func(result.value)
+ save(this.diag, key, result, timeout)
+ }
+ )()
+ await result.done
+ delete result.done
+}
+
+module.exports = JSONCache