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

218 lines (200 loc) 5.59 kB
module.exports = class CachePersist extends require('./cache-static') { #synced = false; #syncing = false; #dataStore; #basePath; constructor(name, opts) { super(name, opts); this.get = this.utils.maybePromisify(this.get); this.set = this.utils.maybePromisify(this.set); this.increment = this.utils.maybePromisify(this.increment); this.remove = this.utils.maybePromisify(this.remove); this.clear = this.utils.maybePromisify(this.clear); this.sync = this.utils.maybePromisify(this.sync); this.#basePath = `/_SYSTEM/_CACHE/${opts.key_prefix || 'default'}`; if (!opts.dataStore) { throw new Error(`no dataStore defined for persisted cache on path: ${this.#basePath}`); } this.#dataStore = opts.dataStore; } get isSynced() { return this.#synced; } get isSyncing() { return this.#syncing; } get(key, opts = {}, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } if (!this.#checkSynced(callback)) { return; } let found = super.get(key, this.commons._.omit(opts, 'default')); if (found != null) { return callback(null, found); } if (opts.default == null) { return callback(null, null); } this.set(key, opts.default.value, opts.default.opts, (e, item) => { if (e) return callback(e); if (item.noclone && opts.clone === false) { return callback(null, item.data); } return callback(null, this.utils.clone(item.data)); }); } set(key, data, opts = {}, transformedAlready, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } if (typeof transformedAlready === 'function') { callback = transformedAlready; transformedAlready = false; } if (!this.#syncing && !this.#checkSynced(callback)) { return; } if (!opts) opts = {}; if (!opts.ttl) opts.ttl = this.opts.defaultTTL; if (opts.noPersist) { const result = super.set(key, data, opts, transformedAlready); return callback(null, result); } this.#persistData(key, this.createCacheItem(key, data, opts), (e) => { if (e) return callback(e); const result = super.set(key, data, opts, transformedAlready); callback(null, result); }); } increment(key, by = 1, opts = {}, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } if (typeof by === 'function') { callback = by; opts = {}; by = 1; } if (!this.#checkSynced(callback)) { return; } const incremented = super.increment(key, by, opts); this.#persistData(key, this.createCacheItem(key, incremented, opts), (e) => { if (e) return callback(e); callback(null, incremented); }); } remove(key, opts, callback) { if (typeof opts === 'function') { callback = opts; opts = {}; } // ttl remove wont pass in a callback callback = callback || function (e) { //system must break here throw e; }; if (!this.#checkSynced(callback)) { return; } this.get(key, (e, existing) => { if (e) return callback(e); if (!existing) { return callback(null, null); } if (opts.noPersist) { super.remove(key); return callback(null, existing); } this.#removeData(key, (e) => { if (e) return callback(e); super.remove(key); callback(null, existing); }); }); } clear(callback) { if (!this.#checkSynced(callback)) { return; } this.#removeData('*', (e) => { if (e) return callback(e); super.clearInternal(); callback(); }); } sync(callback) { this.#syncing = true; //make sure all our keys and items are cleared, so we are not duplicating entries on resyncing super.clearInternal(); this.#dataStore.get(`${this.#basePath}/*`, (e, items) => { if (e) { this.#syncing = false; return callback(e); } if (!items || items.length === 0) { this.#synced = true; this.#syncing = false; return callback(null); } this.commons.async.eachSeries( items, (item, itemCB) => { if (item.data.ttl > 0) { if (Date.now() - item._meta.modified > item.data.ttl) { return this.#removeData(item.data.key, itemCB); } } this.set( item.data.key, item.data.data, { ttl: item.data.ttl, noPersist: true, }, itemCB ); }, (e) => { this.#syncing = false; if (e) return callback(e); this.#synced = true; callback(); } ); }); } #removeData(key, callback) { this.#dataStore.remove(`${this.#basePath}/${key}`, callback); } #persistData(key, data, callback) { this.#dataStore.upsert( `${this.#basePath}/${key}`, data, { merge: true, }, callback ); } #checkSynced(callback) { if (!this.#synced) { const unsyncedError = new Error( `attempt to operate on unsynced persisted cache on path: ${this.#basePath}` ); if (typeof callback !== 'function') { throw unsyncedError; } callback(unsyncedError); return false; // indicates that the callback was called with an error, so don't continue in caller } return true; } };