102 lines
3.4 KiB
JavaScript
102 lines
3.4 KiB
JavaScript
import { redisClient } from "../db/redis.js";
|
|
import { encode, decode } from "msgpackr";
|
|
import config from "../config/index.js";
|
|
export class Cache {
|
|
ttl;
|
|
prefix;
|
|
constructor(name, ttlSeconds){
|
|
this.ttl = ttlSeconds;
|
|
this.prefix = `cache:${name}`;
|
|
}
|
|
prefixedKey(key) {
|
|
return key ? `${this.prefix}:${key}` : this.prefix;
|
|
}
|
|
async set(key, value, transaction) {
|
|
const _key = this.prefixedKey(key);
|
|
const _value = Buffer.from(encode(value));
|
|
const commander = transaction ?? redisClient;
|
|
await commander.set(_key, _value, "EX", this.ttl);
|
|
}
|
|
async get(key, renew = false) {
|
|
const _key = this.prefixedKey(key);
|
|
const cached = await redisClient.getBuffer(_key);
|
|
if (cached === null) return undefined;
|
|
if (renew) await redisClient.expire(_key, this.ttl);
|
|
return decode(cached);
|
|
}
|
|
async getAll(renew = false) {
|
|
const finalPrefix = `${config.redis.prefix}:${this.prefix}:`;
|
|
const keys = (await redisClient.keys(`${finalPrefix}*`)).map((p)=>p.substring(finalPrefix.length));
|
|
const prefixedKeys = keys.map((p)=>this.prefixedKey(p));
|
|
const map = new Map();
|
|
if (keys.length === 0) {
|
|
return map;
|
|
}
|
|
const values = await redisClient.mgetBuffer(prefixedKeys);
|
|
for (const [i, key] of keys.entries()){
|
|
const val = values[i];
|
|
if (val !== null) {
|
|
map.set(key, decode(val));
|
|
}
|
|
}
|
|
if (renew) {
|
|
const trans = redisClient.multi();
|
|
for (const key of map.keys()){
|
|
trans.expire(this.prefixedKey(key), this.ttl);
|
|
}
|
|
await trans.exec();
|
|
}
|
|
return map;
|
|
}
|
|
async delete(...keys) {
|
|
if (keys.length > 0) {
|
|
const _keys = keys.map((p)=>this.prefixedKey(p));
|
|
await redisClient.del(_keys);
|
|
}
|
|
}
|
|
/**
|
|
* Returns if cached value exists. Otherwise, calls fetcher and caches.
|
|
* Overwrites cached value if invalidated by the optional validator.
|
|
*/ async fetch(key, fetcher, renew = false, validator) {
|
|
const cachedValue = await this.get(key, renew);
|
|
if (cachedValue !== undefined) {
|
|
if (validator) {
|
|
if (validator(cachedValue)) {
|
|
// Cache HIT
|
|
return cachedValue;
|
|
}
|
|
} else {
|
|
// Cache HIT
|
|
return cachedValue;
|
|
}
|
|
}
|
|
// Cache MISS
|
|
const value = await fetcher();
|
|
await this.set(key, value);
|
|
return value;
|
|
}
|
|
/**
|
|
* Returns if cached value exists. Otherwise, calls fetcher and caches if the fetcher returns a value.
|
|
* Overwrites cached value if invalidated by the optional validator.
|
|
*/ async fetchMaybe(key, fetcher, renew = false, validator) {
|
|
const cachedValue = await this.get(key, renew);
|
|
if (cachedValue !== undefined) {
|
|
if (validator) {
|
|
if (validator(cachedValue)) {
|
|
// Cache HIT
|
|
return cachedValue;
|
|
}
|
|
} else {
|
|
// Cache HIT
|
|
return cachedValue;
|
|
}
|
|
}
|
|
// Cache MISS
|
|
const value = await fetcher();
|
|
if (value !== undefined) {
|
|
await this.set(key, value);
|
|
}
|
|
return value;
|
|
}
|
|
}
|