const crypto = require('crypto') const { ClassicLevel } = require('classic-level') module.exports = class TransformCache { constructor (filePath, invalidationKey) { this.filePath = filePath this.invalidationKey = invalidationKey this.db = null this.usedKeys = new Set() } async loadOrCreate () { await this._initialize() const oldKey = await this._get('invalidation-key') const newKey = crypto.createHash('sha1').update(this.invalidationKey).digest('hex') if (oldKey !== newKey) { const keys = await this._allKeys() const deleteOperations = Array.from(keys).map((key) => { return {key, type: 'del'} }) await this._batch(deleteOperations) await this._put('invalidation-key', newKey) } } async dispose () { await this.db.close() } async put ({filePath, original, transformed, requires}) { const key = crypto.createHash('sha1').update(original).digest('hex') await this._put(filePath + ':' + key + ':source', transformed) await this._put(filePath + ':' + key + ':requires', JSON.stringify(requires)) } async get ({filePath, content}) { const key = crypto.createHash('sha1').update(content).digest('hex') const source = await this._get(filePath + ':' + key + ':source') const requires = await this._get(filePath + ':' + key + ':requires') if (source && requires) { return {source, requires: JSON.parse(requires)} } else { return null } } async deleteUnusedEntries () { const unusedKeys = await this._allKeys() for (const key of this.usedKeys) { unusedKeys.delete(key) } const deleteOperations = Array.from(unusedKeys).map((key) => { return {key, type: 'del'} }) await this._batch(deleteOperations) } async _initialize () { this.db = new ClassicLevel(this.filePath, { keyEncoding: 'utf8', valueEncoding: 'utf8' }) await this.db.open() } async _put (key, value) { await this.db.put(key, value) this.usedKeys.add(key) } async _get (key) { try { const value = await this.db.get(key) this.usedKeys.add(key) return value } catch (error) { if (error.code === 'LEVEL_NOT_FOUND') { return null } throw error } } async _allKeys () { return new Set(await this.db.keys().all()) } async _batch (operations) { if (operations.length > 0) { await this.db.batch(operations) } } }