import EventEmitter from 'events'
/**
* Cache store class
*
* @class
*/
class Store extends EventEmitter {
/**
* Constructor.
*
* @param {Bajo.Plugin} plugin
*/
constructor (plugin) {
super()
/**
* Reference to the plugin instance.
* @type {Bajo.Plugin}
*/
this.plugin = plugin
/**
* Reference to the app instance.
* @type {Bajo.App}
*/
this.app = plugin.app
/**
* Reference to the storage model.
* @type {Dobo.Model}
*/
this.storage = this.app.dobo.getModel('CacheStorage')
}
/**
* Utility function to build options for storage operations.
*
* @param {Object} items - Items to build options for
* @returns {Object} - Built options
*/
buildOpts = (items = {}) => {
const { defaultsDeep } = this.app.lib.aneka
const def = { noMagic: true, noResult: true }
return defaultsDeep(items, def)
}
_getContent = async (key, result) => {
const { get } = this.app.lib._
let content = get(result, 'content')
if (!content) return undefined
content = JSON.parse(content)
if (content.expires && Date.now() > content.expires) {
await this.delete(key)
return undefined
}
return JSON.stringify(content)
}
/**
* Get the cached item for the given key.
*
* @async
* @method
* @param {string} key - The key of the cached item
* @returns {Promise<any>} - Returns the cached item or undefined if expired/not found
*/
get = async (key) => {
const result = await this.storage.getRecord(key, this.buildOpts())
return await this._getContent(key, result)
}
/**
* Get multiple cached items for the given keys.
*
* @async
* @method
* @param {string[]} keys - The keys of the cached items
* @returns {Promise<any[]>} - Returns an array of cached items or undefined for expired/not found items
*/
getMany = async (keys = []) => {
const filter = {
query: { id: { $in: keys } }
}
const results = await this.storage.findAllRecord(filter, this.buildOpts())
const values = []
for (const k of keys) {
const result = results.find(r => r.key === k)
if (!result) {
values.push(undefined)
continue
}
const value = await this._getContent(k, result)
values.push(value)
}
return values
}
/**
* Set a cached item for the given key with an optional TTL.
*
* @async
* @method
* @param {string} key - The key of the cached item
* @param {any} value - The value to cache
* @param {number} ttl - Time to live in milliseconds
* @returns {Promise<boolean>} - Returns true if the item was successfully cached
*/
set = async (key, value, ttl = 0) => {
const exp = Date.now() + ttl
let content = value
try {
content = JSON.parse(value)
content.expires = exp
content = JSON.stringify(content)
} catch (err) {}
const body = { id: key, content, exp }
try {
const [, model, action] = key.split('|')
body.model = model
body.action = action
} catch (err) {}
await this.storage.upsertRecord(body, this.buildOpts())
return true
}
/**
* Delete the cached item for the given key.
*
* @async
* @method
* @param {string} key - The key of the cached item
* @returns {Promise<boolean>} - Returns true if the item was successfully deleted
*/
delete = async (key) => {
try {
await this.storage.removeRecord(key, this.buildOpts())
} catch (err) {
this.plugin.log.error('errDelCache%s', err.message)
}
return true
}
/**
* Delete multiple cached items for the given keys.
*
* @async
* @method
* @param {string[]} keys - The keys of the cached items
* @returns {Promise<boolean>} - Returns true if the items were successfully deleted
*/
deleteMany = async (keys = []) => {
for (const k of keys) {
await this.delete(k)
}
return true
}
/**
* Clear all cached items.
*
* @async
* @method
* @returns {Promise<boolean>} - Returns true if the cache was successfully cleared
*/
clear = async () => {
try {
await this.storage.clear()
} catch (err) {
this.plugin.log.error('errClearCache%s', err.message)
}
return true
}
/**
* Clear all expired cached items.
*
* @async
* @method
* @returns {Promise<boolean>} - Returns true if the expired cache items were successfully cleared
*/
clearExpired = async () => {
const filter = {
query: { exp: { $lte: Date.now() } }
}
const items = await this.model.findAllRecord(filter, this.buildOpts())
for (const id of items.map(i => i.id)) {
try {
await this.model.removeRecord(id, this.buildOpts())
} catch (err) {
this.plugin.log.error('errClearExpCache%s', err.message)
}
}
return true
}
/**
* Check if a cached item exists for the given key.
*
* @async
* @method
* @param {string} key - The key of the cached item
* @returns {Promise<boolean>} - Returns true if the item exists and is not expired
*/
has = async (key) => {
const filter = {
query: { id: key }
}
const result = await this.model.findOneRecord(filter, this.buildOpts())
return await this._getContent(key, result)
}
/*
async * iterator () {
let page = 0
for (;;) {
const data = await this.storage.findRecord({ page, limit: 20 }, { noHook: true, noModelHook: true, noDynHook: true, dataOnly: true })
if (data.length === 0) break
page++
for (const d of data) {
yield [d.id, d.content]
}
}
}
*/
}
export default Store