| 1 | const fs = require('fs'); |
| 2 | const path = require('path'); |
| 3 | |
| 4 | function atomicWriteSync(filePath, str) { |
| 5 | const dir = path.dirname(filePath); |
| 6 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); |
| 7 | const tmp = filePath + '.tmp'; |
| 8 | fs.writeFileSync(tmp, str); |
| 9 | fs.renameSync(tmp, filePath); |
| 10 | } |
| 11 | |
| 12 | function readJsonSync(filePath, fallback) { |
| 13 | if (!fs.existsSync(filePath)) return fallback; |
| 14 | try { |
| 15 | return JSON.parse(fs.readFileSync(filePath, 'utf8')); |
| 16 | } catch (e) { |
| 17 | throw new Error('corrupt json at ' + filePath + ': ' + e.message); |
| 18 | } |
| 19 | } |
| 20 | |
| 21 | class JsonStore { |
| 22 | constructor(filePath, defaults) { |
| 23 | this.path = filePath; |
| 24 | this.data = readJsonSync(filePath, defaults != null ? defaults : {}); |
| 25 | this.timer = null; |
| 26 | } |
| 27 | get(key) { return key == null ? this.data : this.data[key]; } |
| 28 | has(key) { return Object.prototype.hasOwnProperty.call(this.data, key); } |
| 29 | set(key, value) { this.data[key] = value; this.scheduleSave(); } |
| 30 | delete(key) { delete this.data[key]; this.scheduleSave(); } |
| 31 | keys() { return Object.keys(this.data); } |
| 32 | values() { return Object.values(this.data); } |
| 33 | entries() { return Object.entries(this.data); } |
| 34 | replace(obj) { this.data = obj; this.scheduleSave(); } |
| 35 | scheduleSave() { |
| 36 | if (this.timer) return; |
| 37 | this.timer = setTimeout(() => { this.timer = null; this.flush(); }, 50); |
| 38 | } |
| 39 | flush() { |
| 40 | atomicWriteSync(this.path, JSON.stringify(this.data)); |
| 41 | } |
| 42 | } |
| 43 | |
| 44 | module.exports = { JsonStore, atomicWriteSync, readJsonSync }; |
| 45 | |