UNPKG

ts-cache-mongoose

Version:

Cache plugin for mongoose Queries and Aggregate (in-memory, redis)

322 lines (311 loc) 11.5 kB
'use strict'; 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;