Files
2026-07-26 18:25:37 +09:00

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;
}
}