Initial commit
authorNick Downing <downing.nick@gmail.com>
Tue, 9 Oct 2018 00:56:05 +0000 (11:56 +1100)
committerNick Downing <downing.nick@gmail.com>
Tue, 9 Oct 2018 03:17:51 +0000 (14:17 +1100)
.gitignore [new file with mode: 0644]
json_cache.js [new file with mode: 0644]
package.json [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..3633f2f
--- /dev/null
@@ -0,0 +1 @@
+/json_cache-*.tgz
diff --git a/json_cache.js b/json_cache.js
new file mode 100644 (file)
index 0000000..563bc8e
--- /dev/null
@@ -0,0 +1,113 @@
+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
diff --git a/package.json b/package.json
new file mode 100644 (file)
index 0000000..b19a575
--- /dev/null
@@ -0,0 +1,14 @@
+{
+  "name": "json_cache",
+  "version": "1.0.0",
+  "description": "Simple caching scheme with atomic modification and delayed write",
+  "main": "json_cache.js",
+  "directories": {},
+  "dependencies": {
+    "fs": "^0.0.1-security"
+  },
+  "devDependencies": {},
+  "scripts": {},
+  "author": "Nick Downing",
+  "license": "GPL-3.0"
+}