UNPKG

happn-3

Version:

pub/sub api as a service using primus and mongo & redis or nedb, can work as cluster, single process or embedded using nedb

167 lines (147 loc) 3.89 kB
const lt = require('long-timeout'); module.exports = class CacheBase extends require('events').EventEmitter { #stats = { hits: 0, misses: 0, }; #timeouts = {}; #name; #opts; constructor(name, opts) { super(); if (typeof name !== 'string') throw new Error(`invalid name for cache: ${name}`); this.commons = require('happn-commons'); this.utils = this.commons.utils; this.#name = name; this.#opts = opts; } get name() { return this.#name; } get opts() { return this.#opts; } #transform(key) { if (!this.#opts?.keyTransformers) { return key; } let match; const transformerFound = this.#opts.keyTransformers.find((transformer) => { match = key.match(transformer.regex); return match !== null; }); if (!transformerFound) { return key; } if (transformerFound.transform) { return transformerFound.transform(key); } return match.groups.keyMask.toString(); } get(key, opts = {}) { let transformedKey = this.#transform(key); let cached = this.getInternal(transformedKey, opts); if (cached == null) { this.#stats.misses++; if (opts.default) { this.set(transformedKey, opts.default.value, opts.default.opts, true); cached = { data: opts.default.value, noclone: opts.default?.opts?.noclone }; } else { return null; } } else { this.#stats.hits++; } // the item was stored with the noclone flag, and the clone option is not explicitly set to true if (cached.noclone && opts.clone !== true) { return cached.data; } return this.utils.clone(cached.data); } set(key, data, opts = {}, transformedAlready) { let transformedKey = transformedAlready ? key : this.#transform(key); const cacheItem = { data: opts.clone === false ? data : this.utils.clone(data), key: transformedKey, ttl: opts.ttl, noclone: opts.clone === false, }; this.setInternal(transformedKey, cacheItem, opts); return cacheItem; } increment(key, by = 1, opts = {}) { let transformedKey = this.#transform(key); let currentValue = this.getInternal(transformedKey)?.data; if (typeof currentValue !== 'number') { currentValue = opts.initial || 0; } currentValue += by; this.set(transformedKey, currentValue, opts, true); return currentValue; } remove(key, opts) { let transformedKey = this.#transform(key); const existing = this.get(transformedKey, opts); if (existing) { this.removeInternal(transformedKey); } return existing; } size() { if (typeof this.sizeInternal === 'function') { return this.sizeInternal(); } return this.keys().length; } has(key) { return this.hasInternal(key); } keys() { return this.keysInternal(); } values() { return this.valuesInternal(); } all(filter) { let all = this.values(); if (!filter) return all; return this.commons.mongoFilter( { $and: [filter], }, all ); } clear() { const cleared = this.clearInternal(); this.stop(); return cleared; } stats() { return this.utils.clone(this.commons._.merge(this.#stats, { size: this.size() })); } stop() { Object.keys(this.#timeouts).forEach((key) => { this.clearTimeout(key); }); } clearTimeout(key) { if (!this.#timeouts[key]) return; lt.clearTimeout(this.#timeouts[key]); delete this.#timeouts[key]; } appendTimeout(key, ttl) { this.clearTimeout(key); this.#timeouts[key] = lt.setTimeout(() => { this.remove(key); }, ttl); } createCacheItem(key, data, opts = {}) { return { data: opts.clone === false ? data : this.utils.clone(data), key, ttl: opts.ttl, noclone: opts.clone === false, }; } };