-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlib.js
More file actions
130 lines (130 loc) · 4.27 KB
/
lib.js
File metadata and controls
130 lines (130 loc) · 4.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
const { readFile, writeFileSync } = require('fs')
const isFunction = (fn) => typeof fn === 'function'
async function defaultLoader() {
return new Promise((resolve) => {
readFile('./caches', (err, cachedData) => {
if (err) return resolve({})
try {
cachedData = JSON.parse(cachedData.toString())
} catch (error) {
cachedData = {}
}
resolve(cachedData)
})
})
}
let onExitDumper
const DEFAULT_CACHE = {}
let caches = DEFAULT_CACHE;
function defaultDumper(signal, data) {
try {
writeFileSync('./caches', JSON.stringify(data || caches))
} catch (error) {
console.log(error); process.exit(1);
}
signal = isNaN(+signal) ? 0 : signal
if (signal) process.exit(signal)
}
class EmCache {
key
expiries = {}
watcher
loaded = false
syncOnSet
init
queue = []
/**
* EmCache - A simple in-memory cache backed by a simpler JSON backed disk backup.
* @param {String} params.name Name to use for cache access and identification.
* @param {Function} params.inSink Function to pull cache data in. Defaults to local disk JSON read.
* @param {Function} params.outSink Function to push cache data on exit/failure. Defaults to disk JSON write.
* @param {Boolean} params.syncOnSet Boolean to indicate if cache should be synced on every set state.
*/
constructor({name, inSink, outSink, syncOnSet}) {
if (!name) throw new Error('Invalid invocation. Cache name is mandatory')
this.key = name
this.syncOnSet = syncOnSet
this.init = new Date().getTime()
if (!isFunction(inSink)) inSink = defaultLoader;
inSink().then((results) => { this.loaded = true; caches = results; })
onExitDumper = isFunction(outSink) ? outSink : defaultDumper
process.on('beforeExit', () => { onExitDumper('processExit', caches) });
const signals = ['SIGTERM', 'SIGINT', 'SIGQUIT'] // SIGKILL doesn't work on 14.x
signals.map((signal) => {
process.on(`${signal}`, () => {
onExitDumper(signal, caches);
process.exit(signal === 'SIGINT' ? 0 : 1)
});
});
if (process.env.DEBUG) { this.watcher = setInterval(() => { console.log(JSON.stringify(caches, null, 1)) }, 1000) }
let queueWatcher = setInterval(() => {
if (this.loaded) {
clearInterval(queueWatcher)
while (this.queue.length) {
const [fn, [key, value, expiryInMS]] = this.queue.shift(); fn.bind(this)(key, value, expiryInMS);
}
this.queue = []
}
}, 100)
}
/**
*
* @param {String} key Key to set on the cache.
* @param {Object} value Value to set on the cache.
* @param {Number} expiryInMS Key expiry timing in milliseconds. Keys are automatically booted off after timeout.
* @returns EmCache
*/
set(key, value, expiryInMS) {
if (!this.loaded) {
this.queue = this.queue || []; this.queue.push([this.set, [key, value, expiryInMS]]); return this
}
const values = this.values
if (value === null || value === undefined) {
delete values[key]
} else {
Object.assign(values, {[key]: value})
}
caches[this.key] = values
this.expiries[key] = isNaN(+expiryInMS) || !expiryInMS ? 0 : +expiryInMS
if (this.expiries[key]) {
setTimeout(() => {
this.expiries[this.key] && delete this.expiries[key]
caches[this.key] && delete caches[this.key][key]
this.watcher && clearInterval(this.watcher)
onExitDumper(null, caches)
}, this.expiries[key])
}
if (this.syncOnSet) onExitDumper(null, caches)
return this
}
get(key) { return (this.values || {})[key] }
get values() { return caches[this.key] || {} }
get stats() {
const { values = {}} = this
const keys = Object.keys(values)
const { length: count } = keys
const expiries =
Object.fromEntries(
Object.entries(this.expiries)
.filter(([key, expiry]) => !!expiry && !!values[key])
.map(([key, expiryInMS]) => {
const expiresAt = this.init + expiryInMS
return [key, [{ expiryInMS, expiresAt}]]
})
)
return {
keys: {
count,
keys
},
expiries
}
}
flush() {
Object.values(caches).map((obj) => {
Object.keys(obj).map((key) => delete this.expiries[key])
})
caches = DEFAULT_CACHE
}
}
module.exports = EmCache;