fs-cache-fast/index.mjs
2024-09-20 23:18:10 +00:00

157 lines
4.1 KiB
JavaScript

import fsSyncOriginal from 'fs'
import fsPromisesOriginal from 'fs/promises'
import crypto from 'crypto'
import path from 'path'
import os from 'os'
export default class FSCache {
constructor(options = {}, fsSync, fsPromises) {
this.fsSync = fsSync || fsSyncOriginal
this.fsPromises = fsPromises || fsPromisesOriginal
this.id = crypto.randomBytes(15).toString('base64').replace(/\//g, '-')
this.prefix = options.prefix ? options.prefix + '-' : '-'
this.hash_alg = options.hash_alg || 'md5'
this.cache_dir = options.cache_dir || path.join(os.tmpdir(), this.id)
this.ttl = options.ttl || 0
// Verify hash algorithm is supported on this system
crypto.createHash(this.hash_alg)
this.fsSync.mkdirSync(this.cache_dir, { recursive: true })
}
_checkIsExpired(parsed, now) {
return parsed.ttl != null && now > parsed.ttl
}
_parseCacheData(data, fallback, overwrite = {}) {
let parsed = JSON.parse(data)
if (this._checkIsExpired(parsed, new Date().getTime())) {
return fallback || null
}
return parsed.content
}
_parseSetData(key, data, overwrite = {}) {
if (!(overwrite.ttl ?? this.ttl)) {
return JSON.stringify({ key: key, content: data })
}
return JSON.stringify({
key: key,
content: data,
ttl: new Date().getTime() + (overwrite.ttl || this.ttl) * 1000,
})
}
hash(name) {
return path.join(this.cache_dir, this.prefix + crypto.hash(this.hash_alg, name))
}
get(name, fallback, opts) {
return this.fsPromises.readFile(this.hash(name), { encoding: 'utf8' })
.then(
data => this._parseCacheData(data, fallback, opts),
err => (fallback)
)
}
getSync(name, fallback, opts) {
let data;
try {
data = this.fsSync.readFileSync(this.hash(name), { encoding: 'utf8' })
} catch {
return fallback
}
return this._parseCacheData(data, fallback, opts)
}
set(name, data, orgOpts = {}) {
let opts = typeof orgOpts === 'number' ? { ttl: orgOpts } : orgOpts
try {
return this.fsPromises.writeFile(
this.hash(name),
this._parseSetData(name, data, opts),
{ encoding: opts.encoding || 'utf8' }
)
} catch (err) {
return Promise.reject(err)
}
}
async setMany(items, options) {
for (let item of items) {
await this.set(item.key, item.content ?? item.value, options)
}
}
save(items, options) {
return this.setMany(items, options)
}
setSync(name, data, orgOpts = {}) {
let opts = typeof orgOpts === 'number' ? { ttl: orgOpts } : orgOpts
this.fsSync.writeFileSync(
this.hash(name),
this._parseSetData(name, data, opts),
{ encoding: opts.encoding || 'utf8' }
)
}
setManySync(items, options) {
for (let item of items) {
this.setSync(item.key, item.content ?? item.value, options)
}
}
saveSync(items, options) {
return this.setManySync(items, options)
}
remove(name) {
return this.fsPromises.rm(this.hash(name), { force: true })
}
removeSync(name) {
return this.fsSync.rmSync(this.hash(name), { force: true })
}
async clear() {
for (let file of await this.fsPromises.readdir(this.cache_dir)) {
if (!file.startsWith(this.prefix)) continue
await this.fsPromises.rm(path.join(this.cache_dir, file), { force: true })
}
}
clearSync() {
for (let file of this.fsSync.readdirSync(this.cache_dir)) {
if (!file.startsWith(this.prefix)) continue
this.fsSync.rmSync(path.join(this.cache_dir, file), { force: true })
}
}
async getAll() {
let out = []
let now = new Date().getTime()
for (let file of await this.fsPromises.readdir(this.cache_dir)) {
if (!file.startsWith(this.prefix)) continue
let data = await this.fsPromises.readFile(path.join(this.cache_dir, file), { encoding: 'utf8' })
let entry = JSON.parse(data)
if (entry.content && !this._checkIsExpired(entry, now)) {
out.push(entry)
}
}
return out
}
load() {
return this.getAll().then(res => {
return {
files: res.map(entry => ({ path: this.hash(entry.key), value: entry.content, key: entry.key }))
}
})
}
}