const { WeakLRUCache } = require('weak-lru-cache') let getLastVersion const mapGet = Map.prototype.get exports.CachingStore = Store => class extends Store { constructor(dbName, options) { super(dbName, options) if (!this.env.cacheCommitter) { this.env.cacheCommitter = true this.on('aftercommit', ({ operations, results }) => { results = results || [] let activeCache for (let i = 0, l = operations.length; i < l; i++) { let operation = operations[i] if (typeof operation[1] === 'object') { if (activeCache) { if (results[i] === 0) { let expirationPriority = ((operation[1] || 0).length || 0) >> 10 let entry = mapGet.call(activeCache, operation[0]) if (entry) activeCache.used(entry, expirationPriority) // this will enter it into the LRFU } else activeCache.delete(operation[0]) // just delete it from the map } } else if (operation && operation.length === undefined) { activeCache = operation.cachingDb && operation.cachingDb.cache } } }) } this.db.cachingDb = this this.cache = new WeakLRUCache(options.cache) } get(id, cacheMode) { let value = this.cache.getValue(id) if (value !== undefined) return value value = super.get(id) if (value && typeof value === 'object' && !cacheMode && typeof id !== 'object') { let entry = this.cache.setValue(id, value, this.lastSize >> 10) if (this.useVersions) { entry.version = getLastVersion() } } return value } getEntry(id, cacheMode) { let entry = this.cache.get(id) if (entry) return entry let value = super.get(id) if (value === undefined) return if (value && typeof value === 'object' && !cacheMode && typeof id !== 'object') { entry = this.cache.setValue(id, value, this.lastSize >> 10) } else { entry = { value } } if (this.useVersions) { entry.version = getLastVersion() } return entry } putEntry(id, entry, ifVersion) { let result = super.put(id, entry.value, entry.version, ifVersion) if (typeof id === 'object') return result if (result && result.then) this.cache.setManually(id, entry) // set manually so we can keep it pinned in memory until it is committed else // sync operation, immediately add to cache this.cache.set(id, entry) } put(id, value, version, ifVersion) { // if (this.cache.get(id)) // if there is a cache entry, remove it from scheduledEntries and let result = super.put(id, value, version, ifVersion) if (typeof id !== 'object') { // sync operation, immediately add to cache, otherwise keep it pinned in memory until it is committed let entry = this.cache.setValue(id, value, result.isSync ? 0 : -1) if (version !== undefined) entry.version = version } return result } putSync(id, value, version, ifVersion) { if (id !== 'object') { // sync operation, immediately add to cache, otherwise keep it pinned in memory until it is committed if (value && typeof value === 'object') { let entry = this.cache.setValue(id, value) if (version !== undefined) entry.version = version } else // it is possible that a value used to exist here this.cache.delete(id) } return super.putSync(id, value, version, ifVersion) } remove(id, ifVersion) { this.cache.delete(id) return super.remove(id, ifVersion) } removeSync(id, ifVersion) { this.cache.delete(id) return super.removeSync(id, ifVersion) } clear() { this.cache.clear() super.clear() } childTransaction(execute) { throw new Error('Child transactions are not supported in caching stores') } } exports.setGetLastVersion = (get) => { getLastVersion = get }