ts-cache-mongoose
Version:
Cache plugin for mongoose Queries and Aggregate (in-memory, redis)
322 lines (311 loc) • 11.5 kB
JavaScript
var ms = require('ms');
var bson = require('bson');
var IORedis = require('ioredis');
var mongoose = require('mongoose');
var semver = require('semver');
var node_crypto = require('node:crypto');
var sortKeys = require('sort-keys');
var __typeError$3 = (msg) => {
throw TypeError(msg);
};
var __accessCheck$3 = (obj, member, msg) => member.has(obj) || __typeError$3("Cannot " + msg);
var __privateGet$3 = (obj, member, getter) => (__accessCheck$3(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd$3 = (obj, member, value) => member.has(obj) ? __typeError$3("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet$3 = (obj, member, value, setter) => (__accessCheck$3(obj, member, "write to private field"), member.set(obj, value), value);
var _cache;
class MemoryCacheEngine {
constructor() {
__privateAdd$3(this, _cache);
__privateSet$3(this, _cache, /* @__PURE__ */ new Map());
}
get(key) {
const item = __privateGet$3(this, _cache).get(key);
if (!item || item.expiresAt < Date.now()) {
this.del(key);
return void 0;
}
return item.value;
}
set(key, value, ttl) {
const givenTTL = typeof ttl === "string" ? ms(ttl) : ttl;
const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY;
__privateGet$3(this, _cache).set(key, {
value,
expiresAt: Date.now() + actualTTL
});
}
del(key) {
__privateGet$3(this, _cache).delete(key);
}
clear() {
__privateGet$3(this, _cache).clear();
}
close() {
}
}
_cache = new WeakMap();
const isMongooseLessThan7 = semver.satisfies(mongoose.version, "<7");
const convertToObject = (value) => {
if (isMongooseLessThan7) {
if (value != null && typeof value === "object" && !Array.isArray(value) && value.toObject) {
return value.toObject();
}
if (Array.isArray(value)) {
return value.map((doc) => convertToObject(doc));
}
}
return value;
};
var __typeError$2 = (msg) => {
throw TypeError(msg);
};
var __accessCheck$2 = (obj, member, msg) => member.has(obj) || __typeError$2("Cannot " + msg);
var __privateGet$2 = (obj, member, getter) => (__accessCheck$2(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd$2 = (obj, member, value) => member.has(obj) ? __typeError$2("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet$2 = (obj, member, value, setter) => (__accessCheck$2(obj, member, "write to private field"), member.set(obj, value), value);
var _client;
class RedisCacheEngine {
constructor(options) {
__privateAdd$2(this, _client);
options.keyPrefix ??= "cache-mongoose:";
__privateSet$2(this, _client, new IORedis(options));
}
async get(key) {
try {
const value = await __privateGet$2(this, _client).get(key);
if (value === null) {
return void 0;
}
return bson.EJSON.parse(value);
} catch (err) {
console.error(err);
return void 0;
}
}
async set(key, value, ttl) {
try {
const givenTTL = typeof ttl === "string" ? ms(ttl) : ttl;
const actualTTL = givenTTL ?? Number.POSITIVE_INFINITY;
const serializedValue = bson.EJSON.stringify(convertToObject(value));
await __privateGet$2(this, _client).setex(key, Math.ceil(actualTTL / 1e3), serializedValue);
} catch (err) {
console.error(err);
}
}
async del(key) {
await __privateGet$2(this, _client).del(key);
}
async clear() {
await __privateGet$2(this, _client).flushdb();
}
async close() {
await __privateGet$2(this, _client).quit();
}
}
_client = new WeakMap();
var __typeError$1 = (msg) => {
throw TypeError(msg);
};
var __accessCheck$1 = (obj, member, msg) => member.has(obj) || __typeError$1("Cannot " + msg);
var __privateGet$1 = (obj, member, getter) => (__accessCheck$1(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd$1 = (obj, member, value) => member.has(obj) ? __typeError$1("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet$1 = (obj, member, value, setter) => (__accessCheck$1(obj, member, "write to private field"), member.set(obj, value), value);
var _engine, _defaultTTL, _debug, _engines;
class Cache {
constructor(cacheOptions) {
__privateAdd$1(this, _engine);
__privateAdd$1(this, _defaultTTL);
__privateAdd$1(this, _debug);
__privateAdd$1(this, _engines, ["memory", "redis"]);
if (!__privateGet$1(this, _engines).includes(cacheOptions.engine)) {
throw new Error(`Invalid engine name: ${cacheOptions.engine}`);
}
if (cacheOptions.engine === "redis" && !cacheOptions.engineOptions) {
throw new Error(`Engine options are required for ${cacheOptions.engine} engine`);
}
cacheOptions.defaultTTL ??= "1 minute";
__privateSet$1(this, _defaultTTL, typeof cacheOptions.defaultTTL === "string" ? ms(cacheOptions.defaultTTL) : cacheOptions.defaultTTL);
if (cacheOptions.engine === "redis" && cacheOptions.engineOptions) {
__privateSet$1(this, _engine, new RedisCacheEngine(cacheOptions.engineOptions));
}
if (cacheOptions.engine === "memory") {
__privateSet$1(this, _engine, new MemoryCacheEngine());
}
__privateSet$1(this, _debug, cacheOptions.debug === true);
}
async get(key) {
const cacheEntry = await __privateGet$1(this, _engine).get(key);
if (__privateGet$1(this, _debug)) {
const cacheHit = cacheEntry != null ? "HIT" : "MISS";
console.log(`[ts-cache-mongoose] GET '${key}' - ${cacheHit}`);
}
return cacheEntry;
}
async set(key, value, ttl) {
const givenTTL = typeof ttl === "string" ? ms(ttl) : ttl;
const actualTTL = givenTTL ?? __privateGet$1(this, _defaultTTL);
await __privateGet$1(this, _engine).set(key, value, actualTTL);
if (__privateGet$1(this, _debug)) {
console.log(`[ts-cache-mongoose] SET '${key}' - ttl: ${actualTTL.toFixed(0)} ms`);
}
}
async del(key) {
await __privateGet$1(this, _engine).del(key);
if (__privateGet$1(this, _debug)) {
console.log(`[ts-cache-mongoose] DEL '${key}'`);
}
}
async clear() {
await __privateGet$1(this, _engine).clear();
if (__privateGet$1(this, _debug)) {
console.log("[ts-cache-mongoose] CLEAR");
}
}
async close() {
return __privateGet$1(this, _engine).close();
}
}
_engine = new WeakMap();
_defaultTTL = new WeakMap();
_debug = new WeakMap();
_engines = new WeakMap();
function getKey(data) {
const sortedObj = sortKeys(data, { deep: true });
const sortedStr = JSON.stringify(sortedObj, (_, val) => {
return val instanceof RegExp ? String(val) : val;
});
return node_crypto.createHash("sha1").update(sortedStr).digest("hex");
}
function extendAggregate(mongoose, cache) {
const mongooseExec = mongoose.Aggregate.prototype.exec;
mongoose.Aggregate.prototype.getCacheKey = function() {
if (this._key != null) return this._key;
return getKey({
pipeline: this.pipeline()
});
};
mongoose.Aggregate.prototype.getCacheTTL = function() {
return this._ttl;
};
mongoose.Aggregate.prototype.cache = function(ttl, customKey) {
this._ttl = ttl ?? null;
this._key = customKey ?? null;
return this;
};
mongoose.Aggregate.prototype.exec = async function(...args) {
if (!Object.prototype.hasOwnProperty.call(this, "_ttl")) {
return mongooseExec.apply(this, args);
}
const key = this.getCacheKey();
const ttl = this.getCacheTTL();
const resultCache = await cache.get(key).catch((err) => {
console.error(err);
});
if (resultCache) {
return resultCache;
}
const result = await mongooseExec.call(this);
await cache.set(key, result, ttl).catch((err) => {
console.error(err);
});
return result;
};
}
function extendQuery(mongoose, cache) {
const mongooseExec = mongoose.Query.prototype.exec;
mongoose.Query.prototype.getCacheKey = function() {
if (this._key != null) return this._key;
const filter = this.getFilter();
const update = this.getUpdate();
const options = this.getOptions();
const mongooseOptions = this.mongooseOptions();
return getKey({
model: this.model.modelName,
op: this.op,
filter,
update,
options,
mongooseOptions,
_path: this._path,
_fields: this._fields,
_distinct: this._distinct,
_conditions: this._conditions
});
};
mongoose.Query.prototype.getCacheTTL = function() {
return this._ttl;
};
mongoose.Query.prototype.cache = function(ttl, customKey) {
this._ttl = ttl ?? null;
this._key = customKey ?? null;
return this;
};
mongoose.Query.prototype.exec = async function(...args) {
if (!Object.prototype.hasOwnProperty.call(this, "_ttl")) {
return mongooseExec.apply(this, args);
}
const key = this.getCacheKey();
const ttl = this.getCacheTTL();
const mongooseOptions = this.mongooseOptions();
const isCount = this.op?.includes("count") ?? false;
const isDistinct = this.op === "distinct";
const model = this.model.modelName;
const resultCache = await cache.get(key).catch((err) => {
console.error(err);
});
if (resultCache) {
if (isCount || isDistinct || mongooseOptions.lean) {
return resultCache;
}
const modelConstructor = mongoose.model(model);
if (Array.isArray(resultCache)) {
return resultCache.map((item) => {
return modelConstructor.hydrate(item);
});
}
return modelConstructor.hydrate(resultCache);
}
const result = await mongooseExec.call(this);
await cache.set(key, result, ttl).catch((err) => {
console.error(err);
});
return result;
};
}
var __typeError = (msg) => {
throw TypeError(msg);
};
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
var _instance;
const _CacheMongoose = class _CacheMongoose {
constructor() {
}
static init(mongoose, cacheOptions) {
if (!__privateGet(_CacheMongoose, _instance)) {
__privateSet(_CacheMongoose, _instance, new _CacheMongoose());
__privateGet(_CacheMongoose, _instance).cache = new Cache(cacheOptions);
const cache = __privateGet(_CacheMongoose, _instance).cache;
extendQuery(mongoose, cache);
extendAggregate(mongoose, cache);
}
return __privateGet(_CacheMongoose, _instance);
}
async clear(customKey) {
if (customKey != null) {
await this.cache.del(customKey);
} else {
await this.cache.clear();
}
}
async close() {
await this.cache.close();
}
};
_instance = new WeakMap();
__privateAdd(_CacheMongoose, _instance);
let CacheMongoose = _CacheMongoose;
module.exports = CacheMongoose;
;